diff --git a/.gitattributes b/.gitattributes index 4ca25977a..6fd1589a1 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,5 @@ *.txz filter=lfs diff=lfs merge=lfs -text +app/src/main/assets/imagefs.tzst filter=lfs diff=lfs merge=lfs -text +app/src/main/assets/imagefs.part*.tzst filter=lfs diff=lfs merge=lfs -text reference/tooling/jadx.zip filter=lfs diff=lfs merge=lfs -text reference/tooling/jadx/lib/jadx-1.5.3-all.jar filter=lfs diff=lfs merge=lfs -text diff --git a/.github/workflows/pr-ci.yml b/.github/workflows/pr-ci.yml index 9342e3918..c32e0daa6 100644 --- a/.github/workflows/pr-ci.yml +++ b/.github/workflows/pr-ci.yml @@ -38,6 +38,10 @@ jobs: artifactName: apk-pubg gradleTask: assemblePubgDebug apkPath: app/build/outputs/apk/pubg/debug/pubg.apk + - name: Antutu + artifactName: apk-antutu + gradleTask: assembleAntutuDebug + apkPath: app/build/outputs/apk/antutu/debug/antutu.apk steps: - name: Clone repository @@ -63,22 +67,37 @@ jobs: path: | app/.cxx app/build/intermediates/cxx - key: ndk-${{ matrix.flavor.name }}-${{ hashFiles('app/src/main/cpp/**', 'app/build.gradle', 'app/src/main/cpp/CMakeLists.txt') }} + key: ndk-${{ hashFiles('app/src/main/cpp/**', 'app/build.gradle', 'app/src/main/cpp/CMakeLists.txt') }} restore-keys: | - ndk-${{ matrix.flavor.name }}- ndk- + # Cargo target dir + registry sit outside the NDK cache paths; a cold Rust build dominates job time. + - name: Cache Rust Steam client + uses: actions/cache@v5 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + app/src/main/cpp/wn-steam-client/rust/target + key: cargo-${{ hashFiles('app/src/main/cpp/wn-steam-client/rust/Cargo.lock') }} + restore-keys: cargo- + - name: Set up JDK 17 uses: actions/setup-java@v5 with: java-version: '17' distribution: 'temurin' + - name: Install Rust Android target + run: rustup target add aarch64-linux-android + - name: Set up Gradle uses: gradle/actions/setup-gradle@v6 with: validate-wrappers: true cache-cleanup: 'on-success' + # PRs restore the Gradle cache read-only; only main pushes write it. + cache-read-only: ${{ github.event_name == 'pull_request' }} - name: Decode keystore and build timeout-minutes: 15 @@ -141,6 +160,8 @@ jobs: path: artifacts/${{ matrix.flavor.artifactName }}/*.apk retention-days: 7 if-no-files-found: error + # APK contents are already compressed; deflating ~560MB again wastes CPU. + compression-level: 0 cleanup-caches: needs: build @@ -155,7 +176,8 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - gh cache list --repo "$GITHUB_REPOSITORY" --json id,key,createdAt --limit 100 | + # Dedupe only main-scope caches so a newer PR-scoped twin can't get main's copy deleted. + gh cache list --repo "$GITHUB_REPOSITORY" --ref refs/heads/main --json id,key,createdAt --limit 100 | jq -r ' group_by(.key | sub("-[a-f0-9]{20,}$"; "")) | .[] diff --git a/.github/workflows/pr-release-publish.yml b/.github/workflows/pr-release-publish.yml index 068e21d1e..2825147c0 100644 --- a/.github/workflows/pr-release-publish.yml +++ b/.github/workflows/pr-release-publish.yml @@ -8,7 +8,8 @@ on: - completed concurrency: - group: pr-release-${{ github.event.workflow_run.id }} + # Serialize publishes of the same PR branch; per-run-id groups let them race each other. + group: pr-release-${{ github.event.workflow_run.head_repository.full_name }}-${{ github.event.workflow_run.head_branch }} cancel-in-progress: false jobs: @@ -84,9 +85,11 @@ jobs: echo "standard=WinNative-Debug-Standard-${label}.apk" echo "ludashi=WinNative-Debug-Ludashi-${label}.apk" echo "pubg=WinNative-Debug-Pubg-${label}.apk" + echo "antutu=WinNative-Debug-Antutu-${label}.apk" echo "release_standard=WinNative-Debug-Standard-${release_label}.apk" echo "release_ludashi=WinNative-Debug-Ludashi-${release_label}.apk" echo "release_pubg=WinNative-Debug-Pubg-${release_label}.apk" + echo "release_antutu=WinNative-Debug-Antutu-${release_label}.apk" } >> "$GITHUB_OUTPUT" - name: Download PR build artifacts @@ -95,9 +98,15 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | mkdir -p artifacts - gh run download "$RUN_ID" --repo "$SOURCE_REPO" --name apk-standard --dir artifacts/standard - gh run download "$RUN_ID" --repo "$SOURCE_REPO" --name apk-ludashi --dir artifacts/ludashi - gh run download "$RUN_ID" --repo "$SOURCE_REPO" --name apk-pubg --dir artifacts/pubg + # Download all flavors in parallel; the sequential form left the runner's network idle per transfer. + pids=() + for flavor in standard ludashi pubg antutu; do + gh run download "$RUN_ID" --repo "$SOURCE_REPO" --name "apk-$flavor" --dir "artifacts/$flavor" & + pids+=($!) + done + for pid in "${pids[@]}"; do + wait "$pid" + done - name: Build release notes from PR metadata if: ${{ steps.scope.outputs.allowed == 'true' }} @@ -127,6 +136,7 @@ jobs: { printf '# PR #%s\n\n' "$PR_NUMBER" printf 'Title: %s\n\n' "$safe_title" + printf 'Author: @%s\n\n' "$pr_author" printf 'Source PR: %s\n' "$pr_url" printf 'Source branch: %s\n' "$head_ref" printf 'Source commit: `%s`\n' "$HEAD_SHA" @@ -141,6 +151,7 @@ jobs: printf -- '%s\n' "- ${{ steps.apk.outputs.release_standard }}" printf -- '%s\n' "- ${{ steps.apk.outputs.release_ludashi }}" printf -- '%s\n' "- ${{ steps.apk.outputs.release_pubg }}" + printf -- '%s\n' "- ${{ steps.apk.outputs.release_antutu }}" printf '\nBuilt from source commit `%s`.\n' "$short_sha" } > release-notes.md @@ -158,9 +169,20 @@ jobs: discord_marker='' discord_posted=false - if gh release view "$tag" --repo "$TARGET_REPO" >/dev/null 2>&1; then + # A transient API error must not be read as "release doesn't exist" — that ends in + # a create over an existing tag (HTTP 422). + if view_out=$(gh api "repos/$TARGET_REPO/releases/tags/$tag" 2>&1); then + release_exists=true + elif printf '%s' "$view_out" | grep -qi 'Not Found'; then + release_exists=false + else + printf '%s\n' "$view_out" + exit 1 + fi + + if [ "$release_exists" = true ]; then created=false - existing_body=$(gh release view "$tag" --repo "$TARGET_REPO" --json body --jq '.body // ""') + existing_body=$(printf '%s' "$view_out" | jq -r '.body // ""') if printf '%s' "$existing_body" | grep -Fq "$discord_marker"; then discord_posted=true if ! grep -Fq "$discord_marker" release-notes.md; then @@ -174,26 +196,38 @@ jobs: --prerelease else created=true - gh release create "$tag" \ + if ! gh release create "$tag" \ --repo "$TARGET_REPO" \ --title "$title" \ --notes-file release-notes.md \ - --prerelease + --prerelease; then + # Another publish run created it between our check and the create — update instead. + created=false + gh release edit "$tag" \ + --repo "$TARGET_REPO" \ + --title "$title" \ + --notes-file release-notes.md \ + --prerelease + fi fi standard_path="artifacts/standard/${{ steps.apk.outputs.standard }}" ludashi_path="artifacts/ludashi/${{ steps.apk.outputs.ludashi }}" pubg_path="artifacts/pubg/${{ steps.apk.outputs.pubg }}" + antutu_path="artifacts/antutu/${{ steps.apk.outputs.antutu }}" release_standard_path="release-assets/${{ steps.apk.outputs.release_standard }}" release_ludashi_path="release-assets/${{ steps.apk.outputs.release_ludashi }}" release_pubg_path="release-assets/${{ steps.apk.outputs.release_pubg }}" + release_antutu_path="release-assets/${{ steps.apk.outputs.release_antutu }}" test -f "$standard_path" test -f "$ludashi_path" test -f "$pubg_path" + test -f "$antutu_path" mkdir -p release-assets cp "$standard_path" "$release_standard_path" cp "$ludashi_path" "$release_ludashi_path" cp "$pubg_path" "$release_pubg_path" + cp "$antutu_path" "$release_antutu_path" release_json=$(gh api "repos/$TARGET_REPO/releases/tags/$tag") existing_apk_assets=$( @@ -231,6 +265,7 @@ jobs: upload_asset "$release_standard_path" upload_asset "$release_ludashi_path" upload_asset "$release_pubg_path" + upload_asset "$release_antutu_path" release_url=$(gh release view "$tag" --repo "$TARGET_REPO" --json url --jq '.url') { diff --git a/.github/workflows/tag-apk-artifacts.yml b/.github/workflows/tag-apk-artifacts.yml index d08b727df..ba0955edb 100644 --- a/.github/workflows/tag-apk-artifacts.yml +++ b/.github/workflows/tag-apk-artifacts.yml @@ -31,6 +31,10 @@ jobs: variant: Pubg gradleTask: assemblePubgDebug apkPath: app/build/outputs/apk/pubg/debug/pubg.apk + - name: Antutu + variant: Antutu + gradleTask: assembleAntutuDebug + apkPath: app/build/outputs/apk/antutu/debug/antutu.apk steps: - name: Clone repository @@ -56,18 +60,30 @@ jobs: path: | app/.cxx app/build/intermediates/cxx - key: tag-ndk-${{ matrix.flavor.name }}-${{ hashFiles('app/src/main/cpp/**', 'app/build.gradle', 'app/src/main/cpp/CMakeLists.txt') }} + key: tag-ndk-${{ hashFiles('app/src/main/cpp/**', 'app/build.gradle', 'app/src/main/cpp/CMakeLists.txt') }} restore-keys: | - tag-ndk-${{ matrix.flavor.name }}- tag-ndk- ndk- + - name: Cache Rust Steam client + uses: actions/cache@v5 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + app/src/main/cpp/wn-steam-client/rust/target + key: cargo-${{ hashFiles('app/src/main/cpp/wn-steam-client/rust/Cargo.lock') }} + restore-keys: cargo- + - name: Set up JDK 17 uses: actions/setup-java@v5 with: java-version: '17' distribution: 'temurin' + - name: Install Rust Android target + run: rustup target add aarch64-linux-android + - name: Set up Gradle uses: gradle/actions/setup-gradle@v6 with: @@ -121,3 +137,4 @@ jobs: path: artifacts/apk/*.apk retention-days: 30 if-no-files-found: error + compression-level: 0 diff --git a/.gitignore b/.gitignore index 4a6805100..d3a4ae7a1 100644 --- a/.gitignore +++ b/.gitignore @@ -17,16 +17,16 @@ app/src/main/assets/proton-9.0-x86_64.txz *.log **/*.log -# Kotlin/Gradle artifacts -.kotlin/ -.tmp/ -.gradle/ -.gradle-user/ -.gradle-user-fresh/ -build/ -app/build/ -app/.cxx/ -app/release/ +# Kotlin/Gradle artifacts +.kotlin/ +.tmp/ +.gradle/ +.gradle-user/ +.gradle-user-fresh/ +build/ +app/build/ +app/.cxx/ +app/release/ # Python artifacts __pycache__/ @@ -47,3 +47,18 @@ app/schemas/ *.jks *.keystore signing.properties + +# Local-only dev artifacts (never check in) +.claude/ +References/ +*.hprof +android_sysvshm/build64/ + +# local build artifacts (FEX/Proton wcp) +dist/ + +/cores/*/ +!/cores/patches/ + +/armsx2/emucore-src/ +/armsx2/emucore-out/ diff --git a/.gitmodules b/.gitmodules index e1ec52737..b5cf369f6 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,7 @@ [submodule "app/src/main/cpp/adrenotools"] path = app/src/main/cpp/adrenotools url = https://github.com/Pipetto-crypto/libadrenotools.git +[submodule "app/src/main/cpp/vkbasalt"] + path = app/src/main/cpp/vkbasalt + url = https://github.com/WinNative-Emu/vkBasalt.git + branch = Test diff --git a/EMULATOR_CREDITS.md b/EMULATOR_CREDITS.md new file mode 100644 index 000000000..5327d1b5b --- /dev/null +++ b/EMULATOR_CREDITS.md @@ -0,0 +1,74 @@ +# Emulator & Library Credits + +WinNative's retro-console features are built on open-source emulators and libraries. +This project is distributed under the **GNU General Public License v3.0** (see [LICENSE](LICENSE)). +In compliance with the GPL and the other licenses below, the corresponding source code +for every GPL/copyleft component is available from the upstream projects linked here, +and their copyright and license notices are preserved. + +## PlayStation 2 + +PS2 games are recognized and imported into the library. PS2 emulation is built on +**ARMSX2** (a GPL-3.0 fork of PCSX2) and is in active development. + +| Component | Role | License | Source | +| --- | --- | --- | --- | +| ARMSX2 | PS2 emulation + RetroAchievements | GPL-3.0 | https://github.com/ARMSX2/ARMSX2 | +| PCSX2 | Upstream project ARMSX2 is derived from | GPL-3.0 | https://github.com/pcsx2/pcsx2 | + +## GameCube / Wii + +GameCube and Wii games are recognized and imported into the library. GC/Wii emulation is +built on an embedded build of **Dolphin**, rendered on Vulkan and driven entirely by +WinNative (Dolphin's own UI is stripped). Local and online multiplayer use Dolphin's own +native NetPlay engine. + +| Component | Role | License | Source | +| --- | --- | --- | --- | +| Dolphin | GameCube/Wii emulation + NetPlay | GPL-2.0-or-later | https://github.com/dolphin-emu/dolphin | + +## Bundled libretro cores + +Each core is shipped as an unmodified `arm64-v8a` build and loaded through LibretroDroid. + +| System | Core | License | Source | +| --- | --- | --- | --- | +| Game Boy / Color | Gambatte | GPL-2.0 | https://github.com/libretro/gambatte-libretro | +| Game Boy Advance | mGBA | MPL-2.0 | https://github.com/libretro/mgba | +| Genesis / Master System / Game Gear | Genesis Plus GX | Genesis Plus GX License (non-commercial) | https://github.com/libretro/Genesis-Plus-GX | +| NES | FCEUmm | GPL-2.0 | https://github.com/libretro/libretro-fceumm | +| Nintendo 64 | ParaLLEl N64 | GPL-2.0 | https://github.com/libretro/parallel-n64 | +| Nintendo 64 | Mupen64Plus-Next | GPL-2.0 | https://github.com/libretro/mupen64plus-libretro-nx | +| PlayStation | Beetle PSX (mednafen_psx) | GPL-2.0 | https://github.com/libretro/beetle-psx-libretro | +| SNES | Snes9x | Snes9x License (non-commercial) | https://github.com/libretro/snes9x | + +### Also evaluated for PlayStation + +| Component | License | Source | +| --- | --- | --- | +| SwanStation | GPL-3.0 | https://github.com/libretro/swanstation | + +## Frontend, achievements, and supporting libraries + +| Component | Role | License | Source | +| --- | --- | --- | --- | +| LibretroDroid | libretro frontend the retro backend is built on | GPL-3.0 | https://github.com/Swordfish90/LibretroDroid | +| LibretroDroid (WinNative fork) | the build WinNative ships, carrying the RetroAchievements, netplay and SGSR changes | GPL-3.0 | https://github.com/WinNative-Emu/LibretroDroid/tree/winnative | +| Oboe | Audio output | Apache-2.0 | https://github.com/google/oboe | +| rcheevos | RetroAchievements client library | MIT | https://github.com/RetroAchievements/rcheevos | +| Snapdragon Game Super Resolution (SGSR) | Upscaling shader | BSD-3-Clause | https://github.com/quic/snapdragon-gsr | +| Winlator | Windows-on-Android base this project forks | GPL-3.0 | https://github.com/brunodev85/winlator | + +## Source availability + +WinNative is released under the GPL-3.0. As required by that license and by the GPL-2.0 +cores above, the complete corresponding source for every copyleft component is obtainable +from the repositories linked in this document, and the bundled license texts are retained +in the source tree of the repository that builds each component. + +The libretro frontend is no longer built from source in this repository. WinNative links +against `libretrodroid.aar`, published from the `winnative` branch of +https://github.com/WinNative-Emu/LibretroDroid, which is where its GPL-3.0 source, the +rcheevos `LICENSE` and `SGSR_LICENSE` texts now live. The exact build WinNative ships is +pinned by release tag and SHA-256 in `tools/libretrodroid.version`, so the corresponding +source for any given APK is the commit that tag was built from. diff --git a/README.md b/README.md index 9b57a18df..2f15049e8 100644 --- a/README.md +++ b/README.md @@ -1,58 +1,98 @@ -

- WinNative -

-

- - Discord - -

- -## WinNative: A Community Built Windows Emulation App for Android - -**WinNative** is an advanced, high-performance Windows (x86_64) emulation environment for Android. It bridges the gap between desktop gaming and mobile by unifying the best technologies from **Winlator Bionic** and **Pluvia**. - -Designed for enthusiasts and power users, WinNative delivers the full Winlator experience while making it easy to connect your Steam, Epic, and GOG game libraries. - ---- - -### Installation - -1. **Download:** Get the latest APK from the [Releases](https://github.com/maxjivi05/WinNative/releases) section. -2. **Variants:** - - `Ludashi`: Best for Xiaomi/RedMagic (Performance Mode trigger). - - `Vanilla`: Standard package name for side-loading with other forks. -3. **Setup:** Launch the app, allow the ImageFS to install, and start adding your games manually or sync your library. - ---- - -### How to Build - -**Requirements:** Android Studio, JDK 17, NDK `27.3.13750724`, and CMake. - -1. **Clone the repository and update submodules** (Required): - ```bash - git clone https://github.com/MaxsTechReview/WinNative.git - cd WinNative - git submodule update --init --recursive - ``` -2. **Build via Android Studio:** Open the `WinNative` directory, let Gradle sync, then select **Build > Build APK(s)**. -3. **Build via CLI:** Run `.\gradlew.bat assembleDebug` (Windows). - ---- - -### Contributing - -We welcome community contributions! Feel free to open a pull request for bug fixes, driver updates, UI improvements, or anything else you'd like to add. - -Please match the existing code style and ensure any AI-assisted code is thoroughly reviewed and tested before submission. - ---- - -### Credits & Acknowledgments - -- **Original Winlator** by [brunodev85](https://github.com/brunodev85/winlator) -- **Winlator Bionic** by [Pipetto-crypto](https://github.com/Pipetto-crypto/winlator) -- **Pluvia** features by the [Pluvia](https://github.com/oxters168/Pluvia) / [GameNative](https://github.com/utkarshdalal/GameNative) community -- **Mesa/Turnip** contributions by the [Mesa3D](https://www.mesa3d.org/) team -- **Goldberg Steam Emulator** by [Mr. Goldberg](https://gitlab.com/Mr_Goldberg/goldberg_emulator), maintained by [Detanup01](https://github.com/Detanup01/gbe_fork) +

+ WinNative +

+

+ + Discord + +

+ +## WinNative: A Community Built Windows Emulation App for Android + +**WinNative** is an advanced, high-performance Windows (x86_64) emulation environment for Android. It bridges the gap between desktop gaming and mobile by unifying the best technologies from **Winlator Bionic** and **Pluvia**. + +Designed for enthusiasts and power users, WinNative delivers the full Winlator experience while making it easy to connect your Steam, Epic, and GOG game libraries. + +--- + +### Installation + +1. **Download:** Get the latest APK from the [Releases](https://github.com/WinNative-Emu/WinNative/releases) section. +2. **Variants:** + - `Ludashi`: Forces both Max GPU and CPU clocks on some devices. (Performance Mode trigger). + - `Vanilla`: Standard package name for side-loading with other forks. + - `Antutu`: Forces Max GPU clocks on most devices. (antutu benchmark spoof) + - `Pubg`: Standard pubg package name which allows some Game Booster advanced Features. +3. **Setup:** Launch the app, allow the ImageFS to install, and start adding your games manually or sync your library. + +--- + +### How to Build + +**Requirements:** Android Studio, JDK 17, and [Git LFS](https://git-lfs.com). The NDK +(`27.3.13750724`) and CMake are only needed if you build native cores from source (see below). + +1. **Clone with submodules and pull LFS objects** (Required): + ```bash + git clone --recursive https://github.com/MaxsTechReview/WinNative.git + cd WinNative + git lfs pull # fetches imagefs + git submodule update --init --recursive + ``` +2. **Build via Android Studio:** Open the `WinNative` directory, let Gradle sync, then select **Build > Build APK(s)**. +3. **Build via CLI:** Run `./gradlew assembleStandardDebug` (or `.\gradlew.bat` on Windows). + +The APK carries no retro console cores. Each core is built from its own fork under the +[WinNative-Emu](https://github.com/WinNative-Emu) org, and +[Retro-Consoles](https://github.com/WinNative-Emu/Retro-Consoles) packs every core plus the +Dolphin and ARMSX2 runtime data into one `retro-consoles.tzst`. The app downloads and +verifies it on demand from **Settings > Retro > Download console cores**, so a core update +no longer needs an app release. To change a core, change its fork and re-run the +Retro-Consoles bundle workflow. + +--- + +### Retro Console Support + +WinNative can also run classic console games alongside your PC library. Retro games live in the same Library and launch just like PC games, but run on an embedded libretro backend instead of Wine. + +Supported systems (bundled cores): + +| System | Core | ROM extensions | +| --- | --- | --- | +| NES | FCEUmm | `.nes` `.unf` `.unif` | +| SNES | Snes9x | `.smc` `.sfc` `.swc` `.fig` | +| Game Boy / Color | Gambatte | `.gb` `.gbc` | +| Game Boy Advance | mGBA | `.gba` | +| Genesis / Mega Drive, Master System, Game Gear | Genesis Plus GX | `.gen` `.md` `.smd` `.sms` `.gg` | +| Nintendo 64 | Mupen64Plus-Next | `.n64` `.z64` `.v64` | +| PlayStation | Beetle PSX | `.cue` `.chd` `.pbp` `.m3u` `.iso` | +| PlayStation 2 | ARMSX2 (PCSX2 fork) | `.iso` `.chd` `.cso` `.bin` | + +Cores ship **prebuilt** (committed via Git LFS) and are used by default; they are built from +source with the opt-in flags above (see `cores/` for the libretro cores and +`armsx2/build-emucore.sh` / `dolphin/build-emucore.sh` for the PS2 and GameCube/Wii cores). +PlayStation 2 online play is supported through the emulated DEV9 network adapter (see the +in-game **Online** tab). + +**How to use:** In the Library, tap **Add Custom Game** and select a ROM instead of an `.exe`. WinNative detects the console and adds the game to your Library. Tap **Play** to launch it with on-screen touch controls and physical gamepad support; the in-game menu (Back button or on-screen **MENU**) offers save/load state, reset, and fast-forward. PlayStation and PlayStation 2 BIOS files can be imported from **Settings → Retro**. + +### Contributing + +We welcome community contributions! Feel free to open a pull request for bug fixes, driver updates, UI improvements, or anything else you'd like to add. + +Please match the existing code style and ensure any AI-assisted code is thoroughly reviewed and tested before submission. + +--- + +### Credits & Acknowledgments + +- **Original Winlator** by [brunodev85](https://github.com/brunodev85/winlator) +- **Winlator Bionic** by [Pipetto-crypto](https://github.com/Pipetto-crypto/winlator) +- **Pluvia** features by the [Pluvia](https://github.com/oxters168/Pluvia) / [GameNative](https://github.com/utkarshdalal/GameNative) community +- **Mesa/Turnip** contributions by the [Mesa3D](https://www.mesa3d.org/) team +- **Goldberg Steam Emulator** by [Mr. Goldberg](https://gitlab.com/Mr_Goldberg/goldberg_emulator), maintained by [Detanup01](https://github.com/Detanup01/gbe_fork) +- **LibretroDroid** by [Filippo Scognamiglio](https://github.com/Swordfish90/LibretroDroid) (GPL-3.0) — the embedded libretro host for retro console support +- **libretro / RetroArch** and the individual core authors, built from source: [FCEUmm](https://github.com/libretro/libretro-fceumm), [Snes9x](https://github.com/libretro/snes9x), [Gambatte](https://github.com/libretro/gambatte-libretro), [mGBA](https://github.com/libretro/mgba), [Genesis Plus GX](https://github.com/libretro/Genesis-Plus-GX), [Mupen64Plus-Next](https://github.com/libretro/mupen64plus-libretro-nx), [Beetle PSX](https://github.com/libretro/beetle-psx-libretro) +- **ARMSX2** by the [ARMSX2](https://github.com/ARMSX2/ARMSX2) team (GPL-3.0) — the PlayStation 2 core, a fork of **[PCSX2](https://github.com/pcsx2/pcsx2)** (GPL-3.0), built from source into `libemucore`. PS2 online play uses PCSX2's DEV9 network adapter diff --git a/android_sysvshm/android_sysvshm.c b/android_sysvshm/android_sysvshm.c index 4c890c77e..0602dcb2a 100644 --- a/android_sysvshm/android_sysvshm.c +++ b/android_sysvshm/android_sysvshm.c @@ -40,7 +40,8 @@ static int find_shmemory_index(int shmid) { static void sysvshm_connect() { if (sysvshm_server_fd >= 0) return; char* path = getenv("ANDROID_SYSVSHM_SERVER"); - + if (path == NULL) return; + int fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd < 0) return; @@ -183,7 +184,7 @@ int shmget(key_t key, size_t size, int flags) { void* shmat(int shmid, const void* shmaddr, int shmflg) { pthread_mutex_lock(&mutex); - void* addr; + void* addr = (void *)-1; int index = find_shmemory_index(shmid); if (index != -1) { if (shmemories[index].addr == NULL) { diff --git a/app/build.gradle b/app/build.gradle index 2144fd93b..14fecd478 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,9 +8,45 @@ plugins { alias(libs.plugins.spotless) } +/* +abstract class InstallVulkanValidationLayerTask extends DefaultTask { + @Input + abstract Property getLayerVersion() + + @OutputFile + abstract RegularFileProperty getOutputFile() + + @TaskAction + void install() { + def version = layerVersion.get() + def zipFile = new File(temporaryDir, "android-binaries-${version}.zip") + if (!zipFile.exists()) { + temporaryDir.mkdirs() + def url = new URL("https://github.com/KhronosGroup/Vulkan-ValidationLayers/releases/download/vulkan-sdk-${version}/android-binaries-${version}.zip") + url.withInputStream { input -> + zipFile.withOutputStream { output -> output << input } + } + } + + def targetFile = outputFile.get().asFile + targetFile.parentFile.mkdirs() + new java.util.zip.ZipFile(zipFile).withCloseable { zip -> + def entry = zip.entries().find { + it.name.endsWith("/arm64-v8a/libVkLayer_khronos_validation.so") + } + if (entry == null) { + throw new GradleException("arm64-v8a Vulkan validation layer not found in ${zipFile.name}") + } + zip.getInputStream(entry).withCloseable { input -> + targetFile.withOutputStream { output -> output << input } + } + } + } +} +*/ + task checkSubmodules { - // Resolve the file path during the configuration phase so we don't - // pass the Gradle Project object into the execution closure (which breaks the Configuration Cache). + // Keep the execution closure configuration-cache friendly. def submodulePath = file("src/main/cpp/adrenotools").absolutePath doFirst { @@ -23,6 +59,67 @@ task checkSubmodules { preBuild.dependsOn(checkSubmodules) +abstract class FetchLibretroDroidTask extends DefaultTask { + @Input + abstract Property getTag() + + @Input + abstract Property getSha256() + + @OutputFile + abstract RegularFileProperty getOutputFile() + + static String digestOf(File file) { + def digest = java.security.MessageDigest.getInstance("SHA-256") + file.withInputStream { input -> + byte[] buffer = new byte[1 << 16] + int read + while ((read = input.read(buffer)) >= 0) digest.update(buffer, 0, read) + } + digest.digest().collect { String.format("%02x", it) }.join() + } + + @TaskAction + void fetch() { + def expected = sha256.get() + def target = outputFile.get().asFile + if (target.isFile() && digestOf(target) == expected) return + + target.parentFile.mkdirs() + def source = "https://github.com/WinNative-Emu/LibretroDroid/releases/download/${tag.get()}/libretrodroid.aar" + def staging = new File(target.parentFile, target.name + ".part") + new URL(source).withInputStream { input -> + staging.withOutputStream { output -> output << input } + } + + def actual = digestOf(staging) + if (actual != expected) { + staging.delete() + throw new GradleException("libretrodroid.aar from ${tag.get()} has checksum ${actual}, expected ${expected}") + } + target.delete() + staging.renameTo(target) + } +} + +def libretroDroidPin = rootProject.file("tools/libretrodroid.version").readLines() + .collect { it.trim() } + .findAll { !it.isEmpty() } +def libretroDroidAar = layout.buildDirectory.file("libretrodroid/libretrodroid.aar") + +def fetchLibretroDroid = tasks.register("fetchLibretroDroid", FetchLibretroDroidTask) { + tag.set(libretroDroidPin[0]) + sha256.set(libretroDroidPin[1]) + outputFile.set(libretroDroidAar) +} + +/* +def installVulkanValidationLayer = tasks.register("installVulkanValidationLayer", InstallVulkanValidationLayerTask) { + layerVersion.set("1.4.341.0") + outputFile.set(layout.projectDirectory.file("src/debug/jniLibs/arm64-v8a/libVkLayer_khronos_validation.so")) +} +*/ + ksp { arg("room.schemaLocation", "$projectDir/schemas") } @@ -31,6 +128,9 @@ def appVersionName = providers.gradleProperty("VERSION_NAME") .orElse(providers.environmentVariable("VERSION_NAME")) .getOrElse("Nightly") +def coldClientVersionFile = rootProject.file("tools/gbe_fork.version") +def coldClientVersion = coldClientVersionFile.exists() ? coldClientVersionFile.text.trim() : "unknown" + android { namespace 'com.winlator.cmod' compileSdk 35 @@ -39,13 +139,15 @@ android { applicationId "com.winnative.cmod" minSdk 26 targetSdk 28 - versionCode 21 + versionCode 2 versionName appVersionName externalNativeBuild { cmake { cppFlags '' - arguments "-DCMAKE_SHARED_LINKER_FLAGS=-Wl,-z,max-page-size=16384", "-DCMAKE_EXE_LINKER_FLAGS=-Wl,-z,max-page-size=16384" + arguments "-DCMAKE_SHARED_LINKER_FLAGS=-Wl,-z,max-page-size=16384", + "-DCMAKE_EXE_LINKER_FLAGS=-Wl,-z,max-page-size=16384", + "-DANDROID_STL=c++_shared" } } @@ -53,13 +155,14 @@ android { abiFilters 'arm64-v8a' } - resConfigs "en", "da", "de", "es", "fr", "it", "ko", "pl", "pt-rBR", "ro", "uk", "zh-rCN", "zh-rTW" + resConfigs "en", "da", "de", "es", "b+es+419", "fi", "fr", "hi", "it", "ja", "ko", "no", "pl", "pt", "pt-rBR", "ro", "ru", "sv", "th", "tr", "uk", "zh-rCN", "zh-rTW" + + buildConfigField("String", "COLD_CLIENT_VERSION", "\"${coldClientVersion}\"") + buildConfigField "String", "RESHADE_CATALOG_URL", "\"https://raw.githubusercontent.com/nicholasx417/WinNative-Components/main/Reshade.json\"" + } - buildConfigField("boolean", "GOLD", "false") - buildConfigField("String", "POSTHOG_API_KEY", "\"\"") - buildConfigField("String", "POSTHOG_HOST", "\"\"") - buildConfigField("String", "SUPABASE_URL", "\"\"") - buildConfigField("String", "SUPABASE_KEY", "\"\"") + androidResources { + noCompress += ['tzst', 'txz'] } compileOptions { @@ -92,11 +195,14 @@ android { buildTypes { debug { minifyEnabled false - // Use release signing if available, otherwise fall back to default debug signing + proguardFiles 'proguard-rules.pro' + // Prefer release signing when configured. signingConfig signingConfigs.release.storeFile != null ? signingConfigs.release : signingConfigs.debug } release { minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), + 'proguard-rules.pro' signingConfig signingConfigs.release } } @@ -116,6 +222,10 @@ android { dimension "branding" applicationId "com.tencent.ig" } + antutu { + dimension "branding" + applicationId "com.antutu.ABenchMark" + } } ndkVersion '27.3.13750724' @@ -132,15 +242,10 @@ android { excludes += "META-INF/versions/9/OSGI-INF/MANIFEST.MF" } jniLibs { + // PulseAudio runs libpulseaudio.so directly from nativeLibraryDir and loads + // libpulse*/libltdl/libsndfile as its dependencies, so these must be packaged + // and extracted to disk (useLegacyPackaging) rather than excluded. useLegacyPackaging = true - excludes += [ - "lib/arm64-v8a/libltdl.so", - "lib/arm64-v8a/libpulseaudio.so", - "lib/arm64-v8a/libpulse.so", - "lib/arm64-v8a/libpulsecommon-13.0.so", - "lib/arm64-v8a/libpulsecore-13.0.so", - "lib/arm64-v8a/libsndfile.so" - ] } } @@ -148,6 +253,7 @@ android { compose true viewBinding true buildConfig true + prefab true // native Steam/runtime components link OpenSSL + curl via NDK Prefab AARs } sourceSets { @@ -157,13 +263,24 @@ android { 'src/main/feature', 'src/main/sharedmemory', 'src/main/runtime', - 'src/main/shared' + 'src/main/shared', + // SDL2 and love-android glue, vendored for the 3D engine. + // Their class names are a JNI contract with liblove.so and + // cannot be renamed or repackaged, so they are kept apart + // from WinNative's own sources with their upstream licence + // headers intact. See EMULATOR_CREDITS.md. + 'src/main/engine' ] } } applicationVariants.configureEach { variant -> if (variant.buildType.name == "debug") { +/* + tasks.named("pre${variant.name.capitalize()}Build").configure { + dependsOn installVulkanValidationLayer + } +*/ variant.outputs.configureEach { output -> output.outputFileName = "${variant.flavorName}.apk" } @@ -176,8 +293,8 @@ dependencies { implementation libs.okhttp implementation libs.okhttpDnsOverHttps - implementation libs.okhttpLoggingInterceptor implementation libs.serializationJson + implementation 'org.yaml:snakeyaml:2.2' implementation libs.jwtDecode implementation libs.securityCrypto @@ -197,7 +314,6 @@ dependencies { implementation libs.composeMaterialIconsExtended implementation libs.activityCompose implementation libs.navigationCompose - implementation libs.lifecycleRuntimeCompose implementation libs.lifecycleViewmodelCompose implementation libs.composeUiTextGoogleFonts implementation libs.coilCompose @@ -207,9 +323,7 @@ dependencies { implementation libs.zxingCore implementation libs.commonsIo implementation libs.commonsLang3 - implementation libs.protobufJava implementation("com.github.luben:zstd-jni:${libs.versions.zstdJni.get()}@aar") - implementation libs.bouncyCastle implementation libs.flexbox implementation libs.appcompat @@ -224,13 +338,18 @@ dependencies { implementation libs.recyclerview implementation libs.coreKtx - implementation libs.javaSteam - implementation libs.javaSteamDepotDownloader + + implementation 'com.android.ndk.thirdparty:openssl:1.1.1q-beta-1' + implementation 'com.android.ndk.thirdparty:curl:7.85.0-beta-1' implementation libs.playServicesGamesV2 - implementation libs.playServicesAuth implementation libs.workRuntimeKtx + + implementation files(libretroDroidAar) { builtBy fetchLibretroDroid } + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' + implementation project(':armsx2') + implementation project(':dolphin') } spotless { diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 000000000..309cd5ea7 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,105 @@ +# Project ProGuard / R8 rules. Built APK is `minifyEnabled true`; without +# these keeps, R8's release build strips classes that the runtime reaches +# only via reflection — most notably BouncyCastle's java.security.Provider +# Service registrations (the SPI map BouncyCastleProvider uses to expose +# KeyStore.BKS, MessageDigest.*, etc.). +# +# Crash symptom 2026-05-19 (commit e1cb6c6 onward; not caused by hybrid work): +# java.security.NoSuchAlgorithmException: BKS KeyStore not available +# at sun.security.jca.GetInstance.getInstance:159 +# at java.security.Security.getImpl:628 +# at java.security.KeyStore.getInstance:901 +# at com.android.org.conscrypt.KeyManagerFactoryImpl.engineInit +# at OkHttp Platform.systemDefaultTrustManager (obfuscated e8.j.n) +# +# PluviaApp.onCreate calls Security.addProvider(BouncyCastleProvider()), +# which DOES expose "BKS" — but only if R8 didn't strip the Service +# classes BC registers reflectively. The rules below keep them. +# [[project_bks_keystore_crash]] + +-keep class org.bouncycastle.** { *; } +-keep class org.bouncycastle.jce.provider.** { *; } +-keep class org.bouncycastle.jcajce.provider.** { *; } +-dontwarn org.bouncycastle.** + +# BouncyCastle reads service-loader entries from META-INF/services. R8's +# default config copies META-INF/services for the kept classes; the +# explicit `-keep` above ensures the impl classes survive. + +# JNI bridge classes — Rust `libwnsteam.so` and the wn-steam-bootstrap C++ lib +# look these up by string in JNI_OnLoad / via FindClass. R8 renames break +# the lookup → JNI_OnLoad returns JNI_ERR and the entire native side dies. +# Crash 2026-05-19: "JNI_ERR returned from JNI_OnLoad in libwnsteam.so" +# was triggered by R8 renaming WnConnectionObserver and the auth/library/ +# session callback observers the native JNI layer finds by name. +-keep class com.winlator.cmod.feature.stores.steam.wnsteam.** { +} +-keepclassmembers class com.winlator.cmod.feature.stores.steam.wnsteam.** { +} +# JNI also calls back into PrefManager / SteamService observers + Steam +# data classes (UserFileInfo, AppMetadata, etc.). Keep the feature +# package broadly — the .so depends on the runtime layout, and shaving +# bytes here is high-risk-low-reward. +-keep class com.winlator.cmod.feature.stores.steam.** { *; } +-keepclassmembers class com.winlator.cmod.feature.stores.steam.** { *; } + +# Native-method classes: `native` keyword on Kotlin/Java methods is a +# universal R8 keep signal, but pinning the enclosing class avoids +# subclass-rename surprises. +-keepclasseswithmembernames class * { + native ; +} + +# zstd-jni — the native side does GetFieldID("srcPos", "J") / +# GetFieldID("dstPos", "J") on ZstdInputStreamNoFinalizer and +# similar via JNI; R8 renames the Kotlin/Java fields and the lookup +# crashes the process at sign-in (`libwnsteam.so` decompresses Steam +# CM messages with zstd). Diagnosed 2026-05-19 from +# `Abort message: java.lang.NoSuchFieldError: no "J" field "srcPos" +# in class Lcom/github/luben/zstd/ZstdInputStreamNoFinalizer`. +-keep class com.github.luben.zstd.** { *; } +-keepclassmembers class com.github.luben.zstd.** { *; } +-dontwarn com.github.luben.zstd.** + +# Conscrypt / OkHttp use reflection to discover security providers; keep +# the Provider names they look up by string. +-keep class java.security.Provider { *; } +-keep class * extends java.security.Provider { *; } +-keep class * extends java.security.KeyStoreSpi { *; } +-keep class * extends java.security.MessageDigestSpi { *; } +-keep class * extends javax.crypto.CipherSpi { *; } +-keep class * extends javax.crypto.MacSpi { *; } +-keep class * extends javax.crypto.KeyAgreementSpi { *; } +-keep class * extends java.security.KeyFactorySpi { *; } +-keep class * extends javax.crypto.SecretKeyFactorySpi { *; } +-keep class * extends javax.crypto.KeyGeneratorSpi { *; } +-keep class * extends java.security.AlgorithmParametersSpi { *; } +-keep class * extends java.security.SignatureSpi { *; } +-keep class com.winlator.cmod.shared.io.NativeContentIO { + *; +} + +-keep class com.winlator.cmod.shared.util.OnExtractFileListener { + public java.io.File onExtractFile(java.io.File, long); +} + +-keep class com.winlator.cmod.runtime.content.Downloader$DownloadListener { + public void onProgress(long, long); +} + +-dontwarn androidx.window.extensions.WindowExtensions +-dontwarn androidx.window.extensions.WindowExtensionsProvider +-dontwarn androidx.window.extensions.area.ExtensionWindowAreaPresentation +-dontwarn androidx.window.extensions.core.util.function.Consumer +-dontwarn androidx.window.extensions.core.util.function.Function +-dontwarn androidx.window.extensions.core.util.function.Predicate +-dontwarn androidx.window.extensions.layout.DisplayFeature +-dontwarn androidx.window.extensions.layout.FoldingFeature +-dontwarn androidx.window.extensions.layout.WindowLayoutComponent +-dontwarn androidx.window.extensions.layout.WindowLayoutInfo +-dontwarn androidx.window.sidecar.SidecarDeviceState +-dontwarn androidx.window.sidecar.SidecarDisplayFeature +-dontwarn androidx.window.sidecar.SidecarInterface$SidecarCallback +-dontwarn androidx.window.sidecar.SidecarInterface +-dontwarn androidx.window.sidecar.SidecarProvider +-dontwarn androidx.window.sidecar.SidecarWindowLayoutInfo diff --git a/app/src/antutu/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/antutu/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 000000000..d372a4fca --- /dev/null +++ b/app/src/antutu/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/antutu/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/antutu/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 000000000..d372a4fca --- /dev/null +++ b/app/src/antutu/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/antutu/res/mipmap-hdpi/ic_launcher.png b/app/src/antutu/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..eeda513bc Binary files /dev/null and b/app/src/antutu/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/antutu/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/antutu/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..fd40adda6 Binary files /dev/null and b/app/src/antutu/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/app/src/antutu/res/mipmap-hdpi/ic_launcher_round.png b/app/src/antutu/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 000000000..91ecbc7c6 Binary files /dev/null and b/app/src/antutu/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/app/src/antutu/res/mipmap-mdpi/ic_launcher.png b/app/src/antutu/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..1d28f9b4e Binary files /dev/null and b/app/src/antutu/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/antutu/res/mipmap-mdpi/ic_launcher_foreground.png b/app/src/antutu/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..28f3b653f Binary files /dev/null and b/app/src/antutu/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/app/src/antutu/res/mipmap-mdpi/ic_launcher_round.png b/app/src/antutu/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 000000000..5309989cd Binary files /dev/null and b/app/src/antutu/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/app/src/antutu/res/mipmap-xhdpi/ic_launcher.png b/app/src/antutu/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..c36adff62 Binary files /dev/null and b/app/src/antutu/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/antutu/res/mipmap-xhdpi/ic_launcher_foreground.png b/app/src/antutu/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..15834a0d4 Binary files /dev/null and b/app/src/antutu/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/app/src/antutu/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/antutu/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 000000000..514cd6047 Binary files /dev/null and b/app/src/antutu/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/app/src/antutu/res/mipmap-xxhdpi/ic_launcher.png b/app/src/antutu/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..6d4571181 Binary files /dev/null and b/app/src/antutu/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/antutu/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app/src/antutu/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..d19894c47 Binary files /dev/null and b/app/src/antutu/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/antutu/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/antutu/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 000000000..e4642022c Binary files /dev/null and b/app/src/antutu/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/app/src/antutu/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/antutu/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..aec9083dc Binary files /dev/null and b/app/src/antutu/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/antutu/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src/antutu/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..f059f3ddb Binary files /dev/null and b/app/src/antutu/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/antutu/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/antutu/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 000000000..9e6038c70 Binary files /dev/null and b/app/src/antutu/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/app/src/debug/AndroidManifest.xml b/app/src/debug/AndroidManifest.xml new file mode 100644 index 000000000..f47226353 --- /dev/null +++ b/app/src/debug/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/app/src/debug/jniLibs/arm64-v8a/libVkLayer_khronos_validation.so b/app/src/debug/jniLibs/arm64-v8a/libVkLayer_khronos_validation.so new file mode 100644 index 000000000..60983e850 Binary files /dev/null and b/app/src/debug/jniLibs/arm64-v8a/libVkLayer_khronos_validation.so differ diff --git a/app/src/ludashi/res/mipmap-hdpi/ic_launcher.png b/app/src/ludashi/res/mipmap-hdpi/ic_launcher.png index eeda513bc..b9dff1158 100644 Binary files a/app/src/ludashi/res/mipmap-hdpi/ic_launcher.png and b/app/src/ludashi/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/ludashi/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/ludashi/res/mipmap-hdpi/ic_launcher_foreground.png index fd40adda6..cc7dfe0b2 100644 Binary files a/app/src/ludashi/res/mipmap-hdpi/ic_launcher_foreground.png and b/app/src/ludashi/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/app/src/ludashi/res/mipmap-hdpi/ic_launcher_round.png b/app/src/ludashi/res/mipmap-hdpi/ic_launcher_round.png index 91ecbc7c6..dfaa43758 100644 Binary files a/app/src/ludashi/res/mipmap-hdpi/ic_launcher_round.png and b/app/src/ludashi/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/app/src/ludashi/res/mipmap-mdpi/ic_launcher.png b/app/src/ludashi/res/mipmap-mdpi/ic_launcher.png index 1d28f9b4e..992f8fbed 100644 Binary files a/app/src/ludashi/res/mipmap-mdpi/ic_launcher.png and b/app/src/ludashi/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/ludashi/res/mipmap-mdpi/ic_launcher_foreground.png b/app/src/ludashi/res/mipmap-mdpi/ic_launcher_foreground.png index 28f3b653f..c2a7fc0fc 100644 Binary files a/app/src/ludashi/res/mipmap-mdpi/ic_launcher_foreground.png and b/app/src/ludashi/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/app/src/ludashi/res/mipmap-mdpi/ic_launcher_round.png b/app/src/ludashi/res/mipmap-mdpi/ic_launcher_round.png index 5309989cd..d95315bf6 100644 Binary files a/app/src/ludashi/res/mipmap-mdpi/ic_launcher_round.png and b/app/src/ludashi/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/app/src/ludashi/res/mipmap-xhdpi/ic_launcher.png b/app/src/ludashi/res/mipmap-xhdpi/ic_launcher.png index c36adff62..f75bc0375 100644 Binary files a/app/src/ludashi/res/mipmap-xhdpi/ic_launcher.png and b/app/src/ludashi/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/ludashi/res/mipmap-xhdpi/ic_launcher_foreground.png b/app/src/ludashi/res/mipmap-xhdpi/ic_launcher_foreground.png index 15834a0d4..2bcc3709e 100644 Binary files a/app/src/ludashi/res/mipmap-xhdpi/ic_launcher_foreground.png and b/app/src/ludashi/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/app/src/ludashi/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/ludashi/res/mipmap-xhdpi/ic_launcher_round.png index 514cd6047..84f357dd5 100644 Binary files a/app/src/ludashi/res/mipmap-xhdpi/ic_launcher_round.png and b/app/src/ludashi/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/app/src/ludashi/res/mipmap-xxhdpi/ic_launcher.png b/app/src/ludashi/res/mipmap-xxhdpi/ic_launcher.png index 6d4571181..7ce21a60b 100644 Binary files a/app/src/ludashi/res/mipmap-xxhdpi/ic_launcher.png and b/app/src/ludashi/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/ludashi/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app/src/ludashi/res/mipmap-xxhdpi/ic_launcher_foreground.png index d19894c47..0bf39ea0f 100644 Binary files a/app/src/ludashi/res/mipmap-xxhdpi/ic_launcher_foreground.png and b/app/src/ludashi/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/ludashi/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/ludashi/res/mipmap-xxhdpi/ic_launcher_round.png index e4642022c..745f05e2e 100644 Binary files a/app/src/ludashi/res/mipmap-xxhdpi/ic_launcher_round.png and b/app/src/ludashi/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/app/src/ludashi/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/ludashi/res/mipmap-xxxhdpi/ic_launcher.png index aec9083dc..1dd339ca2 100644 Binary files a/app/src/ludashi/res/mipmap-xxxhdpi/ic_launcher.png and b/app/src/ludashi/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/ludashi/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src/ludashi/res/mipmap-xxxhdpi/ic_launcher_foreground.png index f059f3ddb..9a6c604b5 100644 Binary files a/app/src/ludashi/res/mipmap-xxxhdpi/ic_launcher_foreground.png and b/app/src/ludashi/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/ludashi/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/ludashi/res/mipmap-xxxhdpi/ic_launcher_round.png index 9e6038c70..3ca95afb8 100644 Binary files a/app/src/ludashi/res/mipmap-xxxhdpi/ic_launcher_round.png and b/app/src/ludashi/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7e2ec759f..7d06e5609 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,10 +7,22 @@ + + + + + + + + @@ -19,8 +31,11 @@ + + + @@ -88,7 +103,23 @@ android:supportsPictureInPicture="true" android:screenOrientation="sensorLandscape" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|smallestScreenSize|density|navigation" - android:exported="true" /> + android:exported="true"> + + + + + + + + + + + + + + + + + + + + + + + + + @@ -111,6 +174,18 @@ android:exported="false" android:foregroundServiceType="dataSync" /> + + + + + + + + + + + android:value="false" /> + + + + + + + + diff --git a/app/src/main/app/PluviaApp.kt b/app/src/main/app/PluviaApp.kt index 9ca57b4e8..7db378cbe 100644 --- a/app/src/main/app/PluviaApp.kt +++ b/app/src/main/app/PluviaApp.kt @@ -8,6 +8,7 @@ import com.winlator.cmod.app.update.UpdateChecker import com.winlator.cmod.feature.stores.gog.service.GOGAuthManager import com.winlator.cmod.feature.stores.gog.service.GOGConstants import com.winlator.cmod.feature.stores.steam.events.EventDispatcher +import com.winlator.cmod.feature.stores.steam.service.SteamService import com.winlator.cmod.feature.stores.steam.utils.PrefManager import com.winlator.cmod.runtime.display.XServerDisplayActivity import com.winlator.cmod.shared.android.RefreshRateUtils @@ -17,39 +18,59 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import org.bouncycastle.jce.provider.BouncyCastleProvider -import java.security.Security +import java.io.File @HiltAndroidApp class PluviaApp : Application() { private val appScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) + private fun isPs2Process(): Boolean { + val name = + if (android.os.Build.VERSION.SDK_INT >= 28) { + Application.getProcessName() + } else { + runCatching { + val pid = android.os.Process.myPid() + val am = getSystemService(ACTIVITY_SERVICE) as android.app.ActivityManager + am.runningAppProcesses?.firstOrNull { it.pid == pid }?.processName + }.getOrNull() + } + return name?.endsWith(":ps2") == true || name?.endsWith(":gc") == true + } + override fun onCreate() { super.onCreate() instance = this - registerRefreshRateLifecycleCallbacks() + Thread.setDefaultUncaughtExceptionHandler { thread, throwable -> + Log.e("PluviaApp", "CRASH in thread ${thread.name}", throwable) + } + + com.winlator.cmod.feature.retro.Ps2GameOverlay.install() + com.winlator.cmod.feature.retro.DolphinGameOverlay.install() + if (isPs2Process()) return - // Replace Android's limited BouncyCastle provider with the full one - // so that JavaSteam can use SHA-1 (and other algorithms) via the "BC" provider. - Security.removeProvider("BC") - Security.addProvider(BouncyCastleProvider()) + // Cached probe for devices whose native stack still needs system libjpeg preloaded. + preloadSystemLibraries() + + registerRefreshRateLifecycleCallbacks() - // Register application context so secure Steam prefs can initialize lazily. PrefManager.install(this) GOGConstants.init(this) - // Initialize process-wide reactive network state com.winlator.cmod.app.service.NetworkMonitor .init(this) scheduleColdStartWarmups() - - Thread.setDefaultUncaughtExceptionHandler { thread, throwable -> - Log.e("PluviaApp", "CRASH in thread ${thread.name}", throwable) - } } companion object { + private const val STARTUP_PROBES_PREFS = "startup_probes" + private const val KEY_SYSTEM_JPEG_PRELOAD_STATE = "system_jpeg_preload_state" + private const val KEY_SYSTEM_JPEG_PRELOAD_VERSION = "system_jpeg_preload_version" + private const val SYSTEM_JPEG_PRELOAD_UNKNOWN = 0 + private const val SYSTEM_JPEG_PRELOAD_SUCCESS = 1 + private const val SYSTEM_JPEG_PRELOAD_UNSUPPORTED = 2 + lateinit var instance: PluviaApp private set @@ -59,6 +80,76 @@ class PluviaApp : Application() { @JvmField val events = EventDispatcher() + + // Visible activity count; mutated only on the main thread. + @Volatile + private var startedActivityCount = 0 + + // Live game windows, including backgrounded sessions. + @Volatile + private var gameActivityCount = 0 + + fun isGameSessionActive(): Boolean = gameActivityCount > 0 + } + + private fun preloadSystemLibraries() { + val prefs = getSharedPreferences(STARTUP_PROBES_PREFS, MODE_PRIVATE) + val currentVersion = currentVersionCode() + val state = + if (prefs.getLong(KEY_SYSTEM_JPEG_PRELOAD_VERSION, -1L) == currentVersion) { + prefs.getInt(KEY_SYSTEM_JPEG_PRELOAD_STATE, SYSTEM_JPEG_PRELOAD_UNKNOWN) + } else { + SYSTEM_JPEG_PRELOAD_UNKNOWN + } + + if (state == SYSTEM_JPEG_PRELOAD_UNSUPPORTED) return + + val is64 = android.os.Build.SUPPORTED_64_BIT_ABIS.isNotEmpty() + val candidates = if (is64) { + listOf("/system/lib64/libjpeg.so", "/system/lib/libjpeg.so") + } else { + listOf("/system/lib/libjpeg.so", "/system/lib64/libjpeg.so") + } + for (path in candidates) { + if (!File(path).exists()) continue + try { + System.load(path) + prefs.edit() + .putInt(KEY_SYSTEM_JPEG_PRELOAD_STATE, SYSTEM_JPEG_PRELOAD_SUCCESS) + .putLong(KEY_SYSTEM_JPEG_PRELOAD_VERSION, currentVersion) + .apply() + Log.i("PluviaApp", "Preloaded $path") + return + } catch (t: Throwable) { + if (isPermanentSystemLibraryPreloadFailure(t)) { + prefs.edit() + .putInt(KEY_SYSTEM_JPEG_PRELOAD_STATE, SYSTEM_JPEG_PRELOAD_UNSUPPORTED) + .putLong(KEY_SYSTEM_JPEG_PRELOAD_VERSION, currentVersion) + .apply() + Log.i("PluviaApp", "Skipping future system libjpeg preload attempts: ${t.message}") + return + } + Log.w("PluviaApp", "Preload $path failed: ${t.message}") + } + } + } + + private fun isPermanentSystemLibraryPreloadFailure(error: Throwable): Boolean { + val message = error.message.orEmpty() + return message.contains("not accessible for the namespace", ignoreCase = true) || + message.contains("is not accessible", ignoreCase = true) + } + + private fun currentVersionCode(): Long { + return runCatching { + val info = packageManager.getPackageInfo(packageName, 0) + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) { + info.longVersionCode + } else { + @Suppress("DEPRECATION") + info.versionCode.toLong() + } + }.getOrDefault(0L) } private fun registerRefreshRateLifecycleCallbacks() { @@ -68,6 +159,9 @@ class PluviaApp : Application() { activity: Activity, savedInstanceState: Bundle?, ) { + if (activity is XServerDisplayActivity) { + gameActivityCount++ + } if (shouldManageAppRefreshRate(activity)) { RefreshRateUtils.onActivityCreated(activity) } @@ -80,7 +174,11 @@ class PluviaApp : Application() { } } - override fun onActivityStarted(activity: Activity) {} + override fun onActivityStarted(activity: Activity) { + if (startedActivityCount++ == 0) { + SteamService.onAppForegrounded() + } + } override fun onActivityPaused(activity: Activity) { if (currentForegroundActivity === activity) { @@ -88,7 +186,12 @@ class PluviaApp : Application() { } } - override fun onActivityStopped(activity: Activity) {} + override fun onActivityStopped(activity: Activity) { + startedActivityCount = (startedActivityCount - 1).coerceAtLeast(0) + if (startedActivityCount == 0) { + SteamService.onAppBackgrounded() + } + } override fun onActivitySaveInstanceState( activity: Activity, @@ -102,13 +205,20 @@ class PluviaApp : Application() { if (currentForegroundActivity === activity) { currentForegroundActivity = null } + if (activity is XServerDisplayActivity) { + gameActivityCount = (gameActivityCount - 1).coerceAtLeast(0) + // If the last game window ends while backgrounded, let Steam sleep. + if (gameActivityCount == 0 && startedActivityCount == 0) { + SteamService.onAppBackgrounded() + } + } } }, ) } private fun shouldManageAppRefreshRate(activity: Activity): Boolean { - // Game windows own per-title refresh policy and should not inherit the global app override. + // Game windows own per-title refresh policy. return activity !is XServerDisplayActivity } @@ -118,8 +228,7 @@ class PluviaApp : Application() { withContext(Dispatchers.IO) { GOGAuthManager.updateLoginStatus(this@PluviaApp) - // Pre-warm encrypted preferences off the UI thread so launcher auth checks - // are less likely to pay MasterKey/EncryptedSharedPreferences startup cost. + // Keep encrypted prefs setup off launcher auth checks. val steamLogsEnabled = runCatching { PrefManager.init(this@PluviaApp) @@ -137,10 +246,7 @@ class PluviaApp : Application() { runCatching { PluviaDatabase.init(this@PluviaApp) } .onFailure { Log.e("PluviaApp", "Database warmup failed", it) } - // Initialize the cross-store DownloadCoordinator and auto-resume any - // downloads that were running when the app was killed. PAUSED downloads - // stay PAUSED; DOWNLOADING ones are demoted to QUEUED and dispatched as - // store services start. + // Restore interrupted downloads after DB/coordinator startup. runCatching { val db = PluviaDatabase.getInstance(this@PluviaApp) com.winlator.cmod.app.service.download.DownloadCoordinator.init(db) @@ -148,8 +254,6 @@ class PluviaApp : Application() { .attemptStartupRestoration() }.onFailure { Log.e("PluviaApp", "DownloadCoordinator startup failed", it) } - com.winlator.cmod.runtime.system.LogManager - .rotateLogsOnAppStart(this@PluviaApp) com.winlator.cmod.runtime.system.LogManager .startAppLogging(this@PluviaApp) diff --git a/app/src/main/app/config/SettingsConfig.kt b/app/src/main/app/config/SettingsConfig.kt index ed9d2de21..528abe7fd 100644 --- a/app/src/main/app/config/SettingsConfig.kt +++ b/app/src/main/app/config/SettingsConfig.kt @@ -5,7 +5,13 @@ import androidx.preference.PreferenceManager object SettingsConfig { @JvmField - val DEFAULT_WINE_DEBUG_CHANNELS: String = "warn,err,fixme" + val DEFAULT_WINE_DEBUG_CHANNELS: String = "module,loaddll,seh" + + @JvmField + val DEFAULT_WINE_DEBUG_CLASSES: String = "err,warn,fixme" + + @JvmField + val WINE_DEBUG_CLASSES: List = listOf("err", "warn", "fixme", "trace") @JvmField val DEFAULT_WINLATOR_PATH: String = diff --git a/app/src/main/app/db/PluviaDatabase.kt b/app/src/main/app/db/PluviaDatabase.kt index 888a1beb9..cb118149e 100644 --- a/app/src/main/app/db/PluviaDatabase.kt +++ b/app/src/main/app/db/PluviaDatabase.kt @@ -2,6 +2,8 @@ package com.winlator.cmod.app.db import androidx.room.Database import androidx.room.RoomDatabase import androidx.room.TypeConverters +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase import com.winlator.cmod.feature.stores.steam.data.AppInfo import com.winlator.cmod.feature.stores.steam.data.CachedLicense import com.winlator.cmod.feature.stores.steam.data.ChangeNumbers @@ -43,7 +45,7 @@ const val DATABASE_NAME = "pluvia_database" DownloadingAppInfo::class, DownloadRecord::class, ], - version = 4, + version = 8, exportSchema = false, ) @TypeConverters( @@ -89,7 +91,8 @@ abstract class PluviaDatabase : RoomDatabase() { context.applicationContext, PluviaDatabase::class.java, DATABASE_NAME, - ).fallbackToDestructiveMigration() + ).addMigrations(MIGRATION_6_7, MIGRATION_7_8) + .fallbackToDestructiveMigration(true) .build() .also { instance = it } } @@ -97,5 +100,19 @@ abstract class PluviaDatabase : RoomDatabase() { fun getInstance(context: android.content.Context): PluviaDatabase = init(context) fun getInstance(): PluviaDatabase = instance ?: throw IllegalStateException("PluviaDatabase not initialized") + + private val MIGRATION_7_8 = + object : Migration(7, 8) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE app_info ADD COLUMN install_path TEXT") + } + } + + private val MIGRATION_6_7 = + object : Migration(6, 7) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE gog_games ADD COLUMN hero_image_url TEXT NOT NULL DEFAULT ''") + } + } } } diff --git a/app/src/main/app/db/download/DownloadRecord.kt b/app/src/main/app/db/download/DownloadRecord.kt index bad13ed34..a9d287280 100644 --- a/app/src/main/app/db/download/DownloadRecord.kt +++ b/app/src/main/app/db/download/DownloadRecord.kt @@ -32,6 +32,9 @@ data class DownloadRecord( val selectedDlcs: String = "", @ColumnInfo("language") val language: String = "", + /** One of: INSTALL, UPDATE, VERIFY. */ + @ColumnInfo("task_type") + val taskType: String = TASK_INSTALL, /** One of: QUEUED, DOWNLOADING, PAUSED, COMPLETE, CANCELLED, FAILED. */ @ColumnInfo("status") val status: String = STATUS_QUEUED, @@ -51,6 +54,10 @@ data class DownloadRecord( const val STORE_EPIC = "EPIC" const val STORE_GOG = "GOG" + const val TASK_INSTALL = "INSTALL" + const val TASK_UPDATE = "UPDATE" + const val TASK_VERIFY = "VERIFY" + const val STATUS_QUEUED = "QUEUED" const val STATUS_DOWNLOADING = "DOWNLOADING" const val STATUS_PAUSED = "PAUSED" diff --git a/app/src/main/app/db/download/DownloadRecordDao.kt b/app/src/main/app/db/download/DownloadRecordDao.kt index 21d2eb6d4..c0942d848 100644 --- a/app/src/main/app/db/download/DownloadRecordDao.kt +++ b/app/src/main/app/db/download/DownloadRecordDao.kt @@ -54,7 +54,11 @@ interface DownloadRecordDao { ): Int @Query( - "UPDATE download_records SET bytes_downloaded = :bytesDownloaded, bytes_total = :bytesTotal, updated_at = :now WHERE id = :id", + "UPDATE download_records SET " + + "bytes_downloaded = CASE " + + "WHEN :bytesTotal = bytes_total AND :bytesDownloaded < bytes_downloaded THEN bytes_downloaded " + + "ELSE :bytesDownloaded END, " + + "bytes_total = :bytesTotal, updated_at = :now WHERE id = :id", ) suspend fun updateProgress( id: Long, diff --git a/app/src/main/app/service/DownloadService.kt b/app/src/main/app/service/DownloadService.kt index 754f72058..c6c99e8d7 100644 --- a/app/src/main/app/service/DownloadService.kt +++ b/app/src/main/app/service/DownloadService.kt @@ -116,13 +116,36 @@ object DownloadService { // (PAUSED or QUEUED) for which no store has yet created an in-memory DownloadInfo. // Fabricate stub DownloadInfos for those so the Downloads tab shows them and the user // can Resume / Cancel them. - val knownIds = list.map { it.first }.toSet() val coord = com.winlator.cmod.app.service.download.DownloadCoordinator - coord.snapshotRecords().forEach { record -> + val records = coord.snapshotRecords() + val recordsByUiId = records.associateBy { "${it.store}_${it.storeGameId}" } + list.forEach { (id, info) -> + val record = recordsByUiId[id] ?: return@forEach + if (record.bytesTotal > 0L) { + info.setDisplayTotalExpectedBytes(record.bytesTotal) + if (info.getTotalExpectedBytes() <= 0L) { + info.setTotalExpectedBytes(record.bytesTotal) + info.initializeBytesDownloaded(record.bytesDownloaded) + } + } + } + + val knownIds = list.map { it.first }.toSet() + records.forEach { record -> val id = "${record.store}_${record.storeGameId}" if (id in knownIds) return@forEach val phase = mapRecordStatusToPhase(record.status) val gameIdInt = record.storeGameId.toIntOrNull() ?: 0 + // Preserve the original FAILED reason across restarts so the row + // doesn't collapse to a generic "Unknown error" on rehydrate. + val stubStatusMessage = + when (phase) { + com.winlator.cmod.feature.stores.steam.enums.DownloadPhase.PAUSED -> + appContext?.getString(R.string.downloads_queue_paused_resume_hint) + com.winlator.cmod.feature.stores.steam.enums.DownloadPhase.FAILED -> + record.errorMessage?.takeUnless { it.isBlank() } + else -> null + } val stub = com.winlator.cmod.feature.stores.steam.data.DownloadInfo( jobCount = 1, @@ -130,14 +153,10 @@ object DownloadService { downloadingAppIds = java.util.concurrent.CopyOnWriteArrayList(), ).apply { setActive(false) - updateStatus( - phase, - appContext?.getString(R.string.downloads_queue_paused_resume_hint).takeIf { - phase == com.winlator.cmod.feature.stores.steam.enums.DownloadPhase.PAUSED - }, - ) + updateStatus(phase, stubStatusMessage) if (record.bytesTotal > 0L) { setTotalExpectedBytes(record.bytesTotal) + setDisplayTotalExpectedBytes(record.bytesTotal) initializeBytesDownloaded(record.bytesDownloaded) } } diff --git a/app/src/main/app/service/download/DownloadCoordinator.kt b/app/src/main/app/service/download/DownloadCoordinator.kt index 5900f694c..992fb27ed 100644 --- a/app/src/main/app/service/download/DownloadCoordinator.kt +++ b/app/src/main/app/service/download/DownloadCoordinator.kt @@ -21,46 +21,25 @@ import kotlinx.coroutines.withContext import timber.log.Timber /** - * Single source of truth for downloads across Steam / Epic / GOG. - * - * Responsibilities: - * * Enforces a single active download shared across all stores. - * * Persists every download as a [DownloadRecord] so they survive app restarts. - * * Auto-resumes downloads that were active when the app exited; leaves PAUSED ones paused. - * - * Flow of a download: - * 1. The store-specific service receives a download request from the UI. - * 2. It calls [requestSlot]. The coordinator persists or updates a DownloadRecord and tells - * the caller whether to start now ([Decision.Start]) or wait ([Decision.Queue]). - * 3. When a download finishes, the store calls [notifyFinished]; the coordinator updates the - * record and dispatches the next queued download (if any) via the registered [Dispatcher]. - * 4. UI controls (pause / resume / cancel / clear) call into the coordinator, which mutates - * the record and asks the dispatcher to perform the side effect (cancel the running job, - * delete partial files, etc.). + * Coordinates persisted downloads across stores, allowing one active transfer at a time and + * dispatching queued work to each store service. */ object DownloadCoordinator { private const val MAX_PARALLEL_DOWNLOADS = 1 - /** - * A per-store hook the coordinator uses to start, pause, resume, or cancel an actual - * download. Stores register their dispatcher at service startup. - */ + /** Per-store hook for actual download side effects. */ interface Dispatcher { - /** - * Start a download that the coordinator just dequeued. The store should look up the - * pending request matching this record and launch the actual coroutine. Called from - * the coordinator's IO scope. - */ + /** Start a download the coordinator just dequeued. */ fun startQueued(record: DownloadRecord) - /** Pause an actively running download, persisting partial files. */ + /** Pause a running download while keeping partial files. */ fun pauseRunning(record: DownloadRecord) - /** - * Cancel an actively running download and delete partial files. The coordinator has - * already marked the record CANCELLED. - */ + /** Cancel a running download and delete partial files. */ fun cancelRunning(record: DownloadRecord) + + /** True while the store has a live transfer for this record. */ + fun isTransferActive(record: DownloadRecord): Boolean = true } private val mutex = Mutex() @@ -79,6 +58,10 @@ object DownloadCoordinator { private val recordChanges = MutableSharedFlow(extraBufferCapacity = 16) val changes = recordChanges.asSharedFlow() + /** True only while a download is actively transferring. */ + fun hasActiveDownload(): Boolean = + recordsState.value.any { it.status == DownloadRecord.STATUS_DOWNLOADING } + fun init(database: PluviaDatabase) { if (dao != null) return dao = database.downloadRecordDao() @@ -86,8 +69,7 @@ object DownloadCoordinator { fun registerDispatcher(store: String, dispatcher: Dispatcher) { dispatchers[store] = dispatcher - // A newly-registered dispatcher might have queued records waiting from a previous - // process or from a moment ago when it wasn't available yet. Drain the queue. + // Pick up queued records that were waiting for this dispatcher. if (dao != null) { scope.launch { tick() } } @@ -104,12 +86,7 @@ object DownloadCoordinator { data class Queue(val record: DownloadRecord) : Decision() } - /** - * Persist a download request and decide whether to start it immediately or queue it. The - * caller (a store service) should inspect the result; on Start it should launch the actual - * download, on Queue it should create a UI entry showing QUEUED status and wait for the - * coordinator to dispatch via [Dispatcher.startQueued]. - */ + /** Persist a request and decide whether it starts now or waits in the queue. */ suspend fun requestSlot( store: String, storeGameId: String, @@ -118,6 +95,7 @@ object DownloadCoordinator { installPath: String = "", selectedDlcs: String = "", language: String = "", + taskType: String = DownloadRecord.TASK_INSTALL, bytesTotal: Long = 0L, ): Decision { val daoRef = dao ?: throw IllegalStateException("DownloadCoordinator not initialised") @@ -126,11 +104,7 @@ object DownloadCoordinator { val now = System.currentTimeMillis() val existing = daoRef.findByStoreGame(store, storeGameId) - // Re-entry guard: tick() promotes a QUEUED record to DOWNLOADING and then - // dispatches it to the store, which calls back into requestSlot via the public - // download API. The slot was already granted by tick(); without this short - // circuit we'd count the record against itself, decide there are no free slots, - // and rewrite it back to QUEUED — making Resume hang at "Queued" forever. + // tick() may dispatch into code that calls requestSlot again; keep the granted slot. if (existing != null && existing.status == DownloadRecord.STATUS_DOWNLOADING) { return@withLock Decision.Start(existing) } @@ -157,6 +131,7 @@ object DownloadCoordinator { installPath = installPath, selectedDlcs = selectedDlcs, language = language, + taskType = taskType, bytesTotal = bytesTotal, status = status, createdAt = now, @@ -165,14 +140,7 @@ object DownloadCoordinator { val id = daoRef.upsert(newRecord) newRecord.copy(id = id) } else { - // We only reach here for re-enqueue cases (record was COMPLETE/CANCELLED/ - // FAILED/PAUSED/QUEUED — anything but DOWNLOADING, which short-circuits - // above). For a re-enqueue the caller is fully respecifying the request, - // so OVERWRITE the row with the new values. Previously we did - // `selectedDlcs.ifEmpty { existing.selectedDlcs }` which was ambiguous — - // an empty list (user wants base game only) was indistinguishable from - // "caller didn't supply", so old DLC selections leaked through to a fresh - // download. + // Re-enqueue replaces request fields so empty DLC selection means base game only. val updated = existing.copy( title = title, @@ -180,7 +148,9 @@ object DownloadCoordinator { installPath = installPath, selectedDlcs = selectedDlcs, language = language, + taskType = taskType, bytesTotal = if (bytesTotal > 0L) bytesTotal else existing.bytesTotal, + bytesDownloaded = 0L, status = status, errorMessage = null, updatedAt = now, @@ -194,19 +164,27 @@ object DownloadCoordinator { } } - /** Update progress for a running download. Lightweight; runs without locking the queue. */ + /** Update progress without locking the queue. */ fun updateProgress(store: String, storeGameId: String, bytesDownloaded: Long, bytesTotal: Long) { val daoRef = dao ?: return scope.launch { val record = daoRef.findByStoreGame(store, storeGameId) ?: return@launch - daoRef.updateProgress(record.id, bytesDownloaded, bytesTotal) + val safeTotal = bytesTotal.coerceAtLeast(0L) + val safeDownloaded = bytesDownloaded.coerceAtLeast(0L).let { next -> + if (safeTotal == record.bytesTotal && next < record.bytesDownloaded) { + record.bytesDownloaded + } else { + next + } + }.let { next -> + if (safeTotal > 0L) next.coerceAtMost(safeTotal) else next + } + daoRef.updateProgress(record.id, safeDownloaded, safeTotal) + refreshState(daoRef) } } - /** - * Notify the coordinator that a download has terminated (success / fail / cancel / pause). - * The coordinator persists the new status and starts the next queued download. - */ + /** Persist a terminal status and start the next queued download. */ suspend fun notifyFinished( store: String, storeGameId: String, @@ -219,11 +197,11 @@ object DownloadCoordinator { daoRef.updateStatus(record.id, finalStatus, error) refreshState(daoRef) } - // Drain the queue outside the lock to avoid re-entrancy with dispatcher callbacks. + // Drain after releasing the lock so dispatcher callbacks can re-enter. tick() } - /** Pause a running download. Marks PAUSED and asks the dispatcher to cancel its job. */ + /** Mark a download PAUSED and ask its dispatcher to stop work. */ suspend fun pause(store: String, storeGameId: String) { val daoRef = dao ?: return val record = daoRef.findByStoreGame(store, storeGameId) ?: return @@ -247,7 +225,7 @@ object DownloadCoordinator { running.forEach { pause(it.store, it.storeGameId) } } - /** Resume a paused / queued / failed download. */ + /** Resume a paused, queued, or failed download. */ suspend fun resume(store: String, storeGameId: String) { val daoRef = dao ?: return val record = daoRef.findByStoreGame(store, storeGameId) ?: return @@ -262,17 +240,43 @@ object DownloadCoordinator { } tick() } + DownloadRecord.STATUS_DOWNLOADING -> { + // Requeue records wedged in DOWNLOADING with no live transfer so Retry works. + val live = dispatchers[store]?.isTransferActive(record) ?: false + if (!live) { + Timber.w("resume: requeuing wedged DOWNLOADING record ${record.store}/${record.storeGameId}") + mutex.withLock { + daoRef.updateStatus(record.id, DownloadRecord.STATUS_QUEUED) + refreshState(daoRef) + } + tick() + } + } else -> Unit } } + /** Put a just-dispatched record back in the queue without ticking; a later tick() retries it. */ + suspend fun requeue(store: String, storeGameId: String) { + val daoRef = dao ?: return + val record = daoRef.findByStoreGame(store, storeGameId) ?: return + if (record.status != DownloadRecord.STATUS_DOWNLOADING) return + mutex.withLock { + daoRef.updateStatus(record.id, DownloadRecord.STATUS_QUEUED) + refreshState(daoRef) + } + } + suspend fun resumeAll() { val daoRef = dao ?: return - val toResume = daoRef.findByStatus(DownloadRecord.STATUS_PAUSED) + // FAILED downloads keep enough state for Resume All to continue them. + val toResume = + daoRef.findByStatus(DownloadRecord.STATUS_PAUSED) + + daoRef.findByStatus(DownloadRecord.STATUS_FAILED) toResume.forEach { resume(it.store, it.storeGameId) } } - /** Cancel a download and delete partial files via the dispatcher. */ + /** Cancel a download and ask its dispatcher to delete partial files. */ suspend fun cancel(store: String, storeGameId: String) { val daoRef = dao ?: return val record = daoRef.findByStoreGame(store, storeGameId) ?: return @@ -293,14 +297,13 @@ object DownloadCoordinator { cancellable.forEach { cancel(it.store, it.storeGameId) } } - /** Remove finished records (COMPLETE / CANCELLED / FAILED) from the table. */ + /** Remove finished records from the table. */ suspend fun clear() { val daoRef = dao ?: return mutex.withLock { daoRef.deleteFinished() refreshState(daoRef) } - // Notify the Downloads tab to refresh. PluviaApp.events.emit(AndroidEvent.DownloadStatusChanged(0, false)) } @@ -309,10 +312,7 @@ object DownloadCoordinator { runBlocking { clear() } } - /** - * Drain the queue: while there are free slots and queued records, dispatch the oldest one - * to its store-specific dispatcher. - */ + /** Dispatch queued records while slots are available. */ suspend fun tick() { val daoRef = dao ?: return val toStart = mutableListOf() @@ -323,6 +323,8 @@ object DownloadCoordinator { for (record in queued) { if (activeCount >= MAX_PARALLEL_DOWNLOADS) break + // Keep records QUEUED until their store dispatcher is ready. + if (dispatchers[record.store] == null) continue val started = record.copy(status = DownloadRecord.STATUS_DOWNLOADING, updatedAt = now) daoRef.update(started) toStart.add(started) @@ -331,8 +333,7 @@ object DownloadCoordinator { refreshState(daoRef) } - // Dispatch outside the lock so dispatchers can synchronously call back into the - // coordinator if needed. + // Dispatch outside the lock so callbacks can re-enter safely. toStart.forEach { record -> val dispatcher = dispatchers[record.store] if (dispatcher != null) { @@ -347,38 +348,27 @@ object DownloadCoordinator { } } - /** - * Called once on app startup. Records that were DOWNLOADING when the process died are - * moved back to QUEUED (auto-resume); PAUSED records stay PAUSED until the user resumes - * them. Then the queue is drained. - * - * Idempotent: subsequent calls within the same process are no-ops. - */ + /** Restore interrupted downloads once per process and drain the queue. */ suspend fun onAppStart() { if (startupRestored) return startupRestored = true val daoRef = dao ?: return mutex.withLock { - // DOWNLOADING -> QUEUED (auto-resume on next launch). + // Auto-resume interrupted active downloads. daoRef.replaceStatus(DownloadRecord.STATUS_DOWNLOADING, DownloadRecord.STATUS_QUEUED) refreshState(daoRef) } tick() } - /** Triggers onAppStart from a non-coroutine caller. */ + /** Trigger startup restoration from a non-coroutine caller. */ fun attemptStartupRestoration() { if (startupRestored) return scope.launch { onAppStart() } } - /** - * Called by AppTerminationHelper when the app is exiting. Does NOT pause everything — it - * leaves DOWNLOADING records in DOWNLOADING state so they auto-resume on next launch, and - * leaves PAUSED records PAUSED. - */ + /** Exit hook; statuses are already persisted during each transition. */ fun onAppExit() { - // Nothing to persist here — every status transition was already written to the DAO. } private suspend fun refreshState(daoRef: DownloadRecordDao) { @@ -392,16 +382,13 @@ object DownloadCoordinator { scope.launch { tick() } } - /** Initialize records flow on startup. Safe to call multiple times. */ + /** Initialize records flow on startup. */ suspend fun loadInitial() { val daoRef = dao ?: return refreshState(daoRef) } - /** - * Look up the persisted record for a given store+gameId. Useful for resume to recover the - * original install path / dlcs / language without going through the in-memory params map. - */ + /** Look up a persisted record by store and game id. */ suspend fun findRecord(store: String, storeGameId: String): DownloadRecord? { val daoRef = dao ?: return null return daoRef.findByStoreGame(store, storeGameId) diff --git a/app/src/main/app/shell/BoxBlurTransformation.kt b/app/src/main/app/shell/BoxBlurTransformation.kt new file mode 100644 index 000000000..6149d8174 --- /dev/null +++ b/app/src/main/app/shell/BoxBlurTransformation.kt @@ -0,0 +1,94 @@ +package com.winlator.cmod.app.shell + +import android.graphics.Bitmap +import coil.size.Size +import coil.transform.Transformation + +/** Bakes a blur into the decoded bitmap (two separable box passes ≈ Gaussian). */ +class BoxBlurTransformation( + private val radius: Int, +) : Transformation { + override val cacheKey = "boxBlur:$radius" + + override suspend fun transform( + input: Bitmap, + size: Size, + ): Bitmap { + if (radius < 1) return input + val w = input.width + val h = input.height + val a = IntArray(w * h) + val b = IntArray(w * h) + input.getPixels(a, 0, w, 0, 0, w, h) + repeat(2) { + horizontalPass(a, b, w, h) + verticalPass(b, a, w, h) + } + val out = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888) + out.setPixels(a, 0, w, 0, 0, w, h) + return out + } + + private fun horizontalPass( + src: IntArray, + dst: IntArray, + w: Int, + h: Int, + ) { + val div = 2 * radius + 1 + for (y in 0 until h) { + val row = y * w + var sa = 0 + var sr = 0 + var sg = 0 + var sb = 0 + for (i in -radius..radius) { + val p = src[row + i.coerceIn(0, w - 1)] + sa += p ushr 24 + sr += (p shr 16) and 0xFF + sg += (p shr 8) and 0xFF + sb += p and 0xFF + } + for (x in 0 until w) { + dst[row + x] = ((sa / div) shl 24) or ((sr / div) shl 16) or ((sg / div) shl 8) or (sb / div) + val add = src[row + (x + radius + 1).coerceAtMost(w - 1)] + val sub = src[row + (x - radius).coerceAtLeast(0)] + sa += (add ushr 24) - (sub ushr 24) + sr += ((add shr 16) and 0xFF) - ((sub shr 16) and 0xFF) + sg += ((add shr 8) and 0xFF) - ((sub shr 8) and 0xFF) + sb += (add and 0xFF) - (sub and 0xFF) + } + } + } + + private fun verticalPass( + src: IntArray, + dst: IntArray, + w: Int, + h: Int, + ) { + val div = 2 * radius + 1 + for (x in 0 until w) { + var sa = 0 + var sr = 0 + var sg = 0 + var sb = 0 + for (i in -radius..radius) { + val p = src[i.coerceIn(0, h - 1) * w + x] + sa += p ushr 24 + sr += (p shr 16) and 0xFF + sg += (p shr 8) and 0xFF + sb += p and 0xFF + } + for (y in 0 until h) { + dst[y * w + x] = ((sa / div) shl 24) or ((sr / div) shl 16) or ((sg / div) shl 8) or (sb / div) + val add = src[(y + radius + 1).coerceAtMost(h - 1) * w + x] + val sub = src[(y - radius).coerceAtLeast(0) * w + x] + sa += (add ushr 24) - (sub ushr 24) + sr += ((add shr 16) and 0xFF) - ((sub shr 16) and 0xFF) + sg += ((add shr 8) and 0xFF) - ((sub shr 8) and 0xFF) + sb += (add and 0xFF) - (sub and 0xFF) + } + } + } +} diff --git a/app/src/main/app/shell/Eyeglasses2Icon.kt b/app/src/main/app/shell/Eyeglasses2Icon.kt new file mode 100644 index 000000000..126f92610 --- /dev/null +++ b/app/src/main/app/shell/Eyeglasses2Icon.kt @@ -0,0 +1,32 @@ +package com.winlator.cmod.app.shell + +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.PathParser +import androidx.compose.ui.unit.dp + +// "eyeglasses_2" glyph (absent from compose material-icons); its 960x960 viewBox is offset -960 in y, so the path sits in a group translated down 960. +private const val EYEGLASSES_2_PATH = + "M218-320q-42 0-75.5-27T100-416L71-550l-44 3-7-80q78-7 133.5-10t99.5-3q65 0 105 6t72 21q14 7 " + + "26.5 10t23.5 3q11 0 21.5-3t24.5-9q33-15 76-21.5t114-6.5q46 0 102 3t122 9l-7 79-43-3-30 137q-9 " + + "42-42 68.5T743-320h-89q-42 0-74-25.5T538-411l-27-107h-61l-27 107q-11 41-43 66t-73 25h-89Zm-40-112q3 " + + "14 14 23t25 9h89q14 0 25-8.5t14-21.5l31-121q-27-5-61-6.5t-62-1.5q-23 0-49.5.5T154-556l24 124Zm437 " + + "2q3 13 14 21.5t25 8.5h89q14 0 25-9t14-23l26-125q-20-1-46-1.5t-46-.5q-30 0-66.5 1.5T584-551l31 121Z" + +val Eyeglasses2Icon: ImageVector by lazy { + ImageVector.Builder( + name = "Eyeglasses2", + defaultWidth = 24.dp, + defaultHeight = 24.dp, + viewportWidth = 960f, + viewportHeight = 960f, + ).apply { + addGroup(translationY = 960f) + addPath( + pathData = PathParser().parsePathString(EYEGLASSES_2_PATH).toNodes(), + fill = SolidColor(Color.Black), + ) + clearGroup() + }.build() +} diff --git a/app/src/main/app/shell/LibraryGameLaunchScreen.kt b/app/src/main/app/shell/LibraryGameLaunchScreen.kt new file mode 100644 index 000000000..1fd6cacb1 --- /dev/null +++ b/app/src/main/app/shell/LibraryGameLaunchScreen.kt @@ -0,0 +1,1309 @@ +package com.winlator.cmod.app.shell + +import android.os.Build +import android.view.WindowManager +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.MutableTransitionState +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.spring +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.scaleIn +import androidx.compose.animation.scaleOut +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsPressedAsState +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.WindowInsetsSides +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBars +import androidx.compose.foundation.layout.only +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.outlined.ArrowBack +import androidx.compose.material.icons.automirrored.outlined.FactCheck +import androidx.compose.material.icons.outlined.ArrowDropDown +import androidx.compose.material.icons.outlined.Bolt +import androidx.compose.material.icons.outlined.CloudSync +import androidx.compose.material.icons.outlined.SaveAlt +import androidx.compose.material.icons.outlined.Construction +import androidx.compose.material.icons.outlined.Delete +import androidx.compose.material.icons.outlined.DesktopWindows +import androidx.compose.material.icons.outlined.EmojiEvents +import androidx.compose.material.icons.outlined.History +import androidx.compose.material.icons.outlined.Refresh +import androidx.compose.material.icons.outlined.Home +import androidx.compose.material.icons.outlined.PlayArrow +import androidx.compose.material.icons.outlined.Save +import androidx.compose.material.icons.outlined.Schedule +import androidx.compose.material.icons.outlined.Settings +import androidx.compose.material.icons.outlined.SportsEsports +import androidx.compose.material.icons.outlined.Storage +import androidx.compose.material.icons.outlined.Warning +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Switch +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.blur +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.TransformOrigin +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.DpOffset +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties +import androidx.compose.ui.window.DialogWindowProvider +import androidx.compose.ui.window.Popup +import androidx.compose.ui.window.PopupProperties +import androidx.core.view.WindowCompat +import coil.compose.AsyncImage +import coil.request.CachePolicy +import coil.request.ImageRequest +import com.winlator.cmod.R +import androidx.compose.runtime.CompositionLocalProvider +import com.winlator.cmod.shared.ui.focus.controllerFocusGlow +import com.winlator.cmod.shared.ui.outlinedSwitchColors +import com.winlator.cmod.shared.ui.nav.DialogPaneNav +import com.winlator.cmod.shared.ui.nav.LocalPaneNav +import com.winlator.cmod.shared.ui.nav.PaneNavRegistry +import com.winlator.cmod.shared.ui.nav.paneNavItem +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + +private val LaunchBlack = Color.Black +private val LaunchCard = Color(0xFF12121B) +private val LaunchAccent = Color(0xFF1A9FFF) +private val LaunchAccentGlow = Color(0xFF58A6FF) +private val LaunchTextPrimary = Color(0xFFF0F4FF) +private val LaunchTextSecondary = Color(0xFF93A6BC) +private val LaunchDanger = Color(0xFFFF6B6B) + +@Composable +internal fun LibraryGameLaunchScreen( + appName: String, + subtitle: String, + sourceLabel: String, + heroImageUrl: Any?, + customHeroImageCacheKey: String?, + releaseDateEpochSeconds: Long, + totalPlaytimeMillis: Long, + playCount: Int, + lastPlayedMillis: Long, + installSizeText: String?, + isCustom: Boolean, + isRetro: Boolean = false, + showBootToDesktop: Boolean = !isRetro, + showSaveTransfer: Boolean = false, + hasPinnedShortcut: Boolean, + steamMenuEnabled: Boolean = false, + areSteamActionsEnabled: Boolean = true, + showVerifyFiles: Boolean = true, + showCheckForUpdate: Boolean = true, + showWorkshop: Boolean = true, + playEnabled: Boolean = true, + playDisabledLabel: String? = null, + /** + * An alternative engine this particular game can be played with, offered + * right above Play because it changes what Play does. Absent (and the row + * not drawn at all) for every game that has no such choice, which is all + * but a handful. + */ + altEngineLabel: String? = null, + altEngineEnabled: Boolean = false, + onAltEngineChange: ((Boolean) -> Unit)? = null, + onBack: () -> Unit, + onPlay: () -> Unit, + onSettings: () -> Unit, + onBootToDesktop: () -> Unit, + onAchievements: (() -> Unit)? = null, + onCheats: (() -> Unit)? = null, + cheatsEnabled: Boolean = true, + onShortcut: () -> Unit, + onCloudSaves: () -> Unit, + onSaveTransfer: (() -> Unit)? = null, + onUninstall: () -> Unit, + onVerifyFiles: () -> Unit = {}, + onCheckForUpdate: () -> Unit = {}, + onWorkshop: () -> Unit = {}, +) { + val context = LocalContext.current + var uninstallMenuOpen by remember { mutableStateOf(false) } + val saveTransferVisible = showSaveTransfer && onSaveTransfer != null + val bootVisible = showBootToDesktop + val actionIconCount = + 1 + + (if (saveTransferVisible) 1 else 0) + + 1 + + (if (bootVisible) 1 else 0) + + 1 + + 1 + + LaunchScreenCutoutMode() + + Box(Modifier.fillMaxSize()) { + val edgePadding = 22.dp + val bottomPadding = 20.dp + val actionIconSize = 46.dp + val actionIconSpacing = 8.dp + val actionWidth = actionIconSize * actionIconCount + actionIconSpacing * (actionIconCount - 1).coerceAtLeast(0) + val playHeight = 56.dp + val contentGap = 18.dp + val horizontalNavInsets = WindowInsets.navigationBars.only(WindowInsetsSides.Horizontal) + + if (heroImageUrl != null) { + val heroRequest = + remember(heroImageUrl, customHeroImageCacheKey, context) { + ImageRequest + .Builder(context) + .data(heroImageUrl) + .apply { + if (customHeroImageCacheKey != null) { + memoryCacheKey(customHeroImageCacheKey) + diskCacheKey(customHeroImageCacheKey) + } + }.crossfade(150) + .memoryCachePolicy(CachePolicy.ENABLED) + .diskCachePolicy(CachePolicy.ENABLED) + .build() + } + AsyncImage( + model = heroRequest, + contentDescription = "$appName artwork", + modifier = Modifier.fillMaxSize(), + contentScale = ContentScale.Crop, + alignment = Alignment.Center, + ) + } else { + Box( + Modifier + .fillMaxSize() + .background( + Brush.radialGradient( + colors = listOf(LaunchAccent.copy(alpha = 0.34f), LaunchCard, LaunchBlack), + radius = 980f, + ), + ), + contentAlignment = Alignment.Center, + ) { + Icon( + Icons.Outlined.SportsEsports, + contentDescription = null, + tint = LaunchTextPrimary.copy(alpha = 0.18f), + modifier = Modifier.size(132.dp), + ) + } + } + + Box( + Modifier + .fillMaxSize() + .background( + Brush.horizontalGradient( + colorStops = + arrayOf( + 0.0f to LaunchBlack.copy(alpha = 0.9f), + 0.36f to LaunchBlack.copy(alpha = 0.58f), + 0.72f to LaunchBlack.copy(alpha = 0.18f), + 1.0f to LaunchBlack.copy(alpha = 0.62f), + ), + ), + ), + ) + Box( + Modifier + .fillMaxSize() + .background( + Brush.verticalGradient( + colorStops = + arrayOf( + 0.0f to LaunchBlack.copy(alpha = 0.54f), + 0.36f to Color.Transparent, + 0.72f to LaunchBlack.copy(alpha = 0.32f), + 1.0f to LaunchBlack.copy(alpha = 0.94f), + ), + ), + ), + ) + + Row( + modifier = + Modifier + .fillMaxWidth() + .windowInsetsPadding(horizontalNavInsets) + .padding(start = edgePadding, top = 12.dp, end = edgePadding), + verticalAlignment = Alignment.CenterVertically, + ) { + IconButton( + onClick = onBack, + modifier = + Modifier + .size(44.dp) + .clip(CircleShape) + .background(LaunchBlack.copy(alpha = 0.5f)) + .border(1.dp, Color.White.copy(alpha = 0.18f), CircleShape), + ) { + Icon( + Icons.AutoMirrored.Outlined.ArrowBack, + contentDescription = stringResource(R.string.common_ui_back), + tint = LaunchTextPrimary, + modifier = Modifier.size(24.dp), + ) + } + Spacer(Modifier.weight(1f)) + SourceTag( + sourceLabel = sourceLabel, + menuEnabled = steamMenuEnabled, + showVerifyFiles = showVerifyFiles, + showCheckForUpdate = showCheckForUpdate, + showWorkshop = showWorkshop, + showAchievements = onAchievements != null, + showCheats = onCheats != null, + cheatsEnabled = cheatsEnabled, + areSteamActionsEnabled = areSteamActionsEnabled, + onVerifyFiles = onVerifyFiles, + onCheckForUpdate = onCheckForUpdate, + onWorkshop = onWorkshop, + onAchievements = { onAchievements?.invoke() }, + onCheats = { onCheats?.invoke() }, + ) + } + + Column( + modifier = + Modifier + .fillMaxSize() + .windowInsetsPadding(WindowInsets.navigationBars) + .padding(start = edgePadding, top = 68.dp, end = edgePadding, bottom = bottomPadding), + verticalArrangement = Arrangement.SpaceBetween, + ) { + Column( + modifier = Modifier.widthIn(max = 640.dp), + verticalArrangement = Arrangement.spacedBy(6.dp), + ) { + Text( + appName, + style = MaterialTheme.typography.headlineLarge, + color = LaunchTextPrimary, + fontWeight = FontWeight.Bold, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + if (subtitle.isNotBlank()) { + Text( + subtitle, + style = MaterialTheme.typography.titleSmall, + color = LaunchTextPrimary.copy(alpha = 0.72f), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + if (releaseDateEpochSeconds > 0L) { + val releaseDateText = remember(releaseDateEpochSeconds) { formatReleaseDate(releaseDateEpochSeconds) } + Text( + releaseDateText, + style = MaterialTheme.typography.bodyMedium, + color = LaunchTextPrimary.copy(alpha = 0.6f), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + } + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(contentGap), + verticalAlignment = Alignment.Bottom, + ) { + FlowRow( + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalArrangement = Arrangement.spacedBy(8.dp), + modifier = Modifier.weight(1f), + ) { + if (totalPlaytimeMillis > 0L) { + val playtimeText = remember(totalPlaytimeMillis) { formatLibraryPlaytime(totalPlaytimeMillis) } + GameStatChip( + icon = Icons.Outlined.Schedule, + label = stringResource(R.string.library_games_playtime), + value = playtimeText, + ) + } + if (playCount > 0) { + GameStatChip( + icon = Icons.Outlined.SportsEsports, + label = stringResource(R.string.library_games_plays), + value = playCount.toString(), + ) + } + if (lastPlayedMillis > 0L) { + val lastPlayedText = remember(lastPlayedMillis) { formatLibraryLastPlayed(lastPlayedMillis) } + GameStatChip( + icon = Icons.Outlined.History, + label = stringResource(R.string.library_games_last_played), + value = lastPlayedText, + ) + } + if (installSizeText != null) { + GameStatChip( + icon = Icons.Outlined.Storage, + label = stringResource(R.string.common_ui_size), + value = installSizeText, + ) + } + } + + Column( + modifier = Modifier.width(actionWidth), + verticalArrangement = Arrangement.spacedBy(12.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + if (altEngineLabel != null && onAltEngineChange != null) { + LaunchAltEngineToggle( + label = altEngineLabel, + checked = altEngineEnabled, + width = actionWidth, + onCheckedChange = onAltEngineChange, + ) + } + + LaunchPlayButton( + height = playHeight, + enabled = playEnabled, + disabledLabel = playDisabledLabel, + onClick = onPlay, + ) + + Row( + horizontalArrangement = Arrangement.spacedBy(actionIconSpacing), + verticalAlignment = Alignment.CenterVertically, + ) { + LaunchIconActionButton( + icon = Icons.Outlined.Settings, + contentDescription = stringResource(R.string.common_ui_settings), + size = actionIconSize, + onClick = onSettings, + ) + if (saveTransferVisible) { + LaunchIconActionButton( + icon = Icons.Outlined.SaveAlt, + contentDescription = stringResource(R.string.retro_save_transfer_title), + size = actionIconSize, + onClick = { onSaveTransfer?.invoke() }, + ) + } + LaunchIconActionButton( + icon = Icons.Outlined.CloudSync, + contentDescription = stringResource(R.string.cloud_saves_title), + size = actionIconSize, + onClick = onCloudSaves, + ) + if (bootVisible) { + LaunchIconActionButton( + icon = Icons.Outlined.DesktopWindows, + contentDescription = stringResource(R.string.hero_boot_to_desktop_title), + size = actionIconSize, + onClick = onBootToDesktop, + ) + } + LaunchIconActionButton( + icon = Icons.Outlined.Home, + contentDescription = + stringResource( + if (hasPinnedShortcut) R.string.common_ui_remove else R.string.common_ui_shortcut, + ), + size = actionIconSize, + onClick = onShortcut, + ) + Box { + LaunchIconActionButton( + icon = Icons.Outlined.Delete, + contentDescription = + stringResource(if (isCustom) R.string.common_ui_remove else R.string.common_ui_uninstall), + size = actionIconSize, + onClick = { uninstallMenuOpen = true }, + tint = LaunchDanger, + ) + LaunchUninstallMenu( + expanded = uninstallMenuOpen, + appName = appName, + isCustom = isCustom, + onDismissRequest = { uninstallMenuOpen = false }, + onConfirm = { + uninstallMenuOpen = false + onUninstall() + }, + ) + } + } + } + } + } + + if (uninstallMenuOpen) { + Box( + modifier = + Modifier + .fillMaxSize() + .background(LaunchBlack.copy(alpha = 0.46f)) + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null, + onClick = { uninstallMenuOpen = false }, + ), + ) + } + } +} + +@Composable +private fun LaunchScreenCutoutMode() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) return + + val view = LocalView.current + DisposableEffect(view) { + val window = (view.parent as? DialogWindowProvider)?.window + ?: return@DisposableEffect onDispose { } + + val originalCutoutMode = window.attributes.layoutInDisplayCutoutMode + val originalWidth = window.attributes.width + val originalHeight = window.attributes.height + val originalNavigationBarColor = window.navigationBarColor + val originalNavBarContrastEnforced = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + window.isNavigationBarContrastEnforced + } else { + false + } + + WindowCompat.setDecorFitsSystemWindows(window, false) + window.clearFlags( + WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION or + WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, + ) + // FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS is required for navigationBarColor; Dialog windows don't set it by default. + window.addFlags( + WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS or + WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, + ) + window.setLayout( + WindowManager.LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.MATCH_PARENT, + ) + window.attributes = window.attributes.apply { + layoutInDisplayCutoutMode = launchScreenCutoutMode() + } + window.navigationBarColor = android.graphics.Color.TRANSPARENT + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + window.isNavigationBarContrastEnforced = false + } + + onDispose { + window.attributes = window.attributes.apply { + layoutInDisplayCutoutMode = originalCutoutMode + } + window.navigationBarColor = originalNavigationBarColor + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + window.isNavigationBarContrastEnforced = originalNavBarContrastEnforced + } + window.clearFlags( + WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS or + WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, + ) + WindowCompat.setDecorFitsSystemWindows(window, true) + window.setLayout(originalWidth, originalHeight) + } + } +} + +private fun launchScreenCutoutMode(): Int = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS + } else { + WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES + } + +@Composable +private fun LaunchUninstallMenu( + expanded: Boolean, + appName: String, + isCustom: Boolean, + onDismissRequest: () -> Unit, + onConfirm: () -> Unit, +) { + val title = stringResource(if (isCustom) R.string.library_games_remove_game else R.string.library_games_uninstall_game) + val confirmLabel = stringResource(if (isCustom) R.string.common_ui_remove else R.string.common_ui_uninstall) + val message = + stringResource( + if (isCustom) R.string.library_games_remove_confirm else R.string.library_games_uninstall_confirm, + appName, + ) + + LaunchDangerConfirmDialog( + visible = expanded, + title = title, + message = message, + confirmLabel = confirmLabel, + onDismissRequest = onDismissRequest, + onConfirm = onConfirm, + icon = Icons.Outlined.Delete, + cancelColor = LaunchAccent, + ) +} + +@Composable +internal fun LaunchDangerConfirmMenu( + expanded: Boolean, + title: String, + message: String, + confirmLabel: String, + onDismissRequest: () -> Unit, + onConfirm: () -> Unit, + icon: ImageVector = Icons.Outlined.Delete, +) { + DropdownMenu( + expanded = expanded, + onDismissRequest = onDismissRequest, + offset = DpOffset(x = 0.dp, y = (-56).dp), + modifier = Modifier.width(286.dp), + shape = RoundedCornerShape(12.dp), + containerColor = LaunchCard, + border = BorderStroke(1.dp, Color.White.copy(alpha = 0.14f)), + tonalElevation = 0.dp, + shadowElevation = 14.dp, + ) { + Column( + modifier = + Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp, vertical = 10.dp), + verticalArrangement = Arrangement.spacedBy(10.dp), + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(9.dp), + ) { + Icon( + icon, + contentDescription = null, + tint = LaunchDanger, + modifier = Modifier.size(18.dp), + ) + Text( + title, + color = LaunchTextPrimary, + fontSize = 13.sp, + fontWeight = FontWeight.Bold, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + Text( + message, + color = LaunchTextSecondary, + fontSize = 12.sp, + lineHeight = 16.sp, + ) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + LaunchMenuTextAction( + label = stringResource(R.string.common_ui_cancel), + textColor = LaunchTextSecondary, + onClick = onDismissRequest, + ) + LaunchMenuTextAction( + label = confirmLabel, + textColor = LaunchDanger, + onClick = onConfirm, + ) + } + } + } +} + +@Composable +internal fun LaunchDangerConfirmDialog( + visible: Boolean, + title: String, + message: String, + confirmLabel: String, + onDismissRequest: () -> Unit, + onConfirm: () -> Unit, + icon: ImageVector = Icons.Outlined.Warning, + titleTextAlign: TextAlign = TextAlign.Start, + messageTextAlign: TextAlign = TextAlign.Start, + accentColor: Color = LaunchDanger, + cancelColor: Color = LaunchTextSecondary, +) { + if (!visible) return + + val registry = remember { PaneNavRegistry() } + Dialog( + onDismissRequest = onDismissRequest, + properties = DialogProperties(usePlatformDefaultWidth = false), + ) { + CompositionLocalProvider(LocalPaneNav provides registry) { + DialogPaneNav(registry, onDismiss = onDismissRequest) + Box( + modifier = + Modifier + .fillMaxSize() + .background(LaunchBlack.copy(alpha = 0.46f)) + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null, + onClick = onDismissRequest, + ), + contentAlignment = Alignment.Center, + ) { + Surface( + modifier = + Modifier + .width(286.dp) + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null, + onClick = { }, + ), + shape = RoundedCornerShape(12.dp), + color = LaunchCard, + border = BorderStroke(1.dp, Color.White.copy(alpha = 0.14f)), + shadowElevation = 14.dp, + tonalElevation = 0.dp, + ) { + LaunchDangerConfirmContent( + title = title, + message = message, + confirmLabel = confirmLabel, + onDismissRequest = onDismissRequest, + onConfirm = onConfirm, + icon = icon, + titleTextAlign = titleTextAlign, + messageTextAlign = messageTextAlign, + accentColor = accentColor, + cancelColor = cancelColor, + ) + } + } + } + } +} + +@Composable +private fun LaunchDangerConfirmContent( + title: String, + message: String, + confirmLabel: String, + onDismissRequest: () -> Unit, + onConfirm: () -> Unit, + icon: ImageVector, + titleTextAlign: TextAlign, + messageTextAlign: TextAlign, + accentColor: Color, + cancelColor: Color, +) { + Column( + modifier = + Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp, vertical = 10.dp), + verticalArrangement = Arrangement.spacedBy(10.dp), + ) { + if (titleTextAlign == TextAlign.Center) { + Box( + modifier = Modifier.fillMaxWidth(), + contentAlignment = Alignment.Center, + ) { + Icon( + icon, + contentDescription = null, + tint = accentColor, + modifier = + Modifier + .align(Alignment.CenterStart) + .size(18.dp), + ) + Text( + title, + modifier = + Modifier + .fillMaxWidth() + .padding(horizontal = 28.dp), + color = LaunchTextPrimary, + fontSize = 13.sp, + fontWeight = FontWeight.Bold, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + textAlign = TextAlign.Center, + ) + } + } else { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(9.dp), + ) { + Icon( + icon, + contentDescription = null, + tint = accentColor, + modifier = Modifier.size(18.dp), + ) + Text( + title, + color = LaunchTextPrimary, + fontSize = 13.sp, + fontWeight = FontWeight.Bold, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + } + Text( + message, + modifier = Modifier.fillMaxWidth(), + color = LaunchTextSecondary, + fontSize = 12.sp, + lineHeight = 16.sp, + textAlign = messageTextAlign, + ) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + LaunchMenuTextAction( + label = stringResource(R.string.common_ui_cancel), + textColor = cancelColor, + onClick = onDismissRequest, + modifier = Modifier.paneNavItem(onActivate = onDismissRequest), + ) + LaunchMenuTextAction( + label = confirmLabel, + textColor = accentColor, + onClick = onConfirm, + modifier = Modifier.paneNavItem(onActivate = onConfirm, isEntry = true), + ) + } + } +} + +@Composable +private fun LaunchMenuTextAction( + label: String, + textColor: Color, + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { + Box( + modifier = + modifier + .clip(RoundedCornerShape(8.dp)) + .controllerFocusGlow(cornerRadius = 8.dp) + .clickable(onClick = onClick) + .padding(horizontal = 10.dp, vertical = 7.dp), + contentAlignment = Alignment.Center, + ) { + Text( + label, + color = textColor, + fontSize = 12.sp, + fontWeight = FontWeight.SemiBold, + maxLines = 1, + ) + } +} + +@Composable +private fun SourceTag( + sourceLabel: String, + menuEnabled: Boolean = false, + showVerifyFiles: Boolean = true, + showCheckForUpdate: Boolean = true, + showWorkshop: Boolean = true, + showAchievements: Boolean = false, + showCheats: Boolean = false, + cheatsEnabled: Boolean = true, + areSteamActionsEnabled: Boolean = true, + onVerifyFiles: () -> Unit = {}, + onCheckForUpdate: () -> Unit = {}, + onWorkshop: () -> Unit = {}, + onAchievements: () -> Unit = {}, + onCheats: () -> Unit = {}, +) { + var menuOpen by remember { mutableStateOf(false) } + var anchorHeightPx by remember { mutableStateOf(0) } + val menuInteractive = menuEnabled || showAchievements || showCheats + Box { + Surface( + color = Color.White.copy(alpha = 0.1f), + shape = RoundedCornerShape(8.dp), + border = BorderStroke(1.dp, Color.White.copy(alpha = 0.14f)), + modifier = + Modifier + .onSizeChanged { anchorHeightPx = it.height } + .then(if (menuInteractive) Modifier.clickable { menuOpen = true } else Modifier), + ) { + Row( + modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + Box( + Modifier + .size(8.dp) + .clip(CircleShape) + .background(LaunchAccent), + ) + Text( + sourceLabel.uppercase(), + color = LaunchTextPrimary, + fontSize = 12.sp, + fontWeight = FontWeight.Bold, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + if (menuInteractive) { + Icon( + Icons.Outlined.ArrowDropDown, + contentDescription = stringResource(R.string.store_game_steam_options), + tint = LaunchTextPrimary, + modifier = Modifier.size(18.dp), + ) + } + } + } + if (menuInteractive) { + val gapPx = with(LocalDensity.current) { 6.dp.roundToPx() } + LaunchSourceActionPopup( + expanded = menuOpen, + onDismissRequest = { menuOpen = false }, + offset = IntOffset(0, anchorHeightPx + gapPx), + ) { + if (menuEnabled && showVerifyFiles) { + LaunchSourceMenuItem( + icon = Icons.AutoMirrored.Outlined.FactCheck, + label = stringResource(R.string.store_game_verify_files), + enabled = areSteamActionsEnabled, + ) { menuOpen = false; onVerifyFiles() } + } + if (menuEnabled && showCheckForUpdate) { + LaunchSourceMenuItem( + icon = Icons.Outlined.Refresh, + label = stringResource(R.string.store_game_check_for_update), + enabled = areSteamActionsEnabled, + ) { menuOpen = false; onCheckForUpdate() } + } + if (menuEnabled && showWorkshop) { + LaunchSourceMenuItem( + icon = Icons.Outlined.Construction, + label = stringResource(R.string.store_game_workshop), + enabled = areSteamActionsEnabled, + ) { menuOpen = false; onWorkshop() } + } + if (showAchievements) { + LaunchSourceMenuItem( + icon = Icons.Outlined.EmojiEvents, + label = stringResource(R.string.steam_achievements_title), + ) { menuOpen = false; onAchievements() } + } + if (showCheats) { + LaunchSourceMenuItem( + icon = Icons.Outlined.Bolt, + label = stringResource(R.string.retro_cheats_title), + enabled = cheatsEnabled, + ) { menuOpen = false; onCheats() } + } + } + } + } +} + +@Composable +private fun LaunchSourceActionPopup( + expanded: Boolean, + onDismissRequest: () -> Unit, + offset: IntOffset, + content: @Composable () -> Unit, +) { + val transitionState = remember { MutableTransitionState(false) } + transitionState.targetState = expanded + if (!transitionState.currentState && !transitionState.targetState) return + + Popup( + alignment = Alignment.TopEnd, + offset = offset, + onDismissRequest = onDismissRequest, + properties = PopupProperties(focusable = true), + ) { + AnimatedVisibility( + visibleState = transitionState, + enter = + fadeIn(animationSpec = tween(durationMillis = 90)) + + scaleIn( + animationSpec = + spring( + dampingRatio = 0.78f, + stiffness = Spring.StiffnessMediumLow, + ), + initialScale = 0.88f, + transformOrigin = TransformOrigin(1f, 0f), + ), + exit = + fadeOut(animationSpec = tween(durationMillis = 80)) + + scaleOut( + animationSpec = tween(durationMillis = 110), + targetScale = 0.92f, + transformOrigin = TransformOrigin(1f, 0f), + ), + ) { + Surface( + color = LaunchBlack.copy(alpha = 0.78f), + shape = RoundedCornerShape(12.dp), + border = BorderStroke(1.dp, Color.White.copy(alpha = 0.22f)), + tonalElevation = 0.dp, + shadowElevation = 16.dp, + ) { + Column { content() } + } + } + } +} + +@Composable +private fun LaunchSourceMenuItem( + icon: ImageVector, + label: String, + enabled: Boolean = true, + onClick: () -> Unit, +) { + val contentColor = if (enabled) Color.White else Color.White.copy(alpha = 0.45f) + Row( + modifier = + Modifier + .then(if (enabled) Modifier.clickable(onClick = onClick) else Modifier) + .padding(start = 14.dp, end = 14.dp, top = 10.dp, bottom = 10.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(10.dp), + ) { + Icon( + icon, + contentDescription = null, + tint = contentColor, + modifier = Modifier.size(16.dp), + ) + Text( + label, + color = contentColor, + fontSize = 12.sp, + fontWeight = FontWeight.SemiBold, + ) + } +} + +@Composable +private fun GameStatChip( + icon: ImageVector, + label: String, + value: String, + modifier: Modifier = Modifier, +) { + Surface( + modifier = modifier, + color = LaunchBlack.copy(alpha = 0.44f), + shape = RoundedCornerShape(12.dp), + border = BorderStroke(1.dp, Color.White.copy(alpha = 0.11f)), + ) { + Row( + modifier = + Modifier.padding( + horizontal = 10.dp, + vertical = 8.dp, + ), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(7.dp), + ) { + Icon(icon, contentDescription = null, modifier = Modifier.size(16.dp), tint = LaunchAccentGlow) + Column(verticalArrangement = Arrangement.spacedBy(1.dp)) { + Text( + label.uppercase(), + color = LaunchTextSecondary, + fontSize = 9.sp, + fontWeight = FontWeight.Bold, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + Text( + value, + color = LaunchTextPrimary, + fontSize = 12.sp, + fontWeight = FontWeight.SemiBold, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + } + } +} + +/** + * The alternative-engine switch above Play. + * + * Deliberately the same width as the Play button and immediately above it: it + * decides which engine Play will start, so it belongs in the reading path to + * that button rather than buried in a settings pane. The state is the game's + * own saved setting, so what it shows survives leaving the screen. + */ +@Composable +private fun LaunchAltEngineToggle( + label: String, + checked: Boolean, + width: Dp, + onCheckedChange: (Boolean) -> Unit, +) { + Row( + modifier = Modifier + .width(width) + .clip(RoundedCornerShape(14.dp)) + .background(Color.White.copy(alpha = 0.06f)) + .clickable { onCheckedChange(!checked) } + .padding(horizontal = 16.dp, vertical = 10.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Text( + text = label, + color = Color.White.copy(alpha = 0.92f), + fontSize = 14.sp, + fontWeight = FontWeight.Medium, + ) + Switch( + checked = checked, + onCheckedChange = onCheckedChange, + colors = outlinedSwitchColors( + accentColor = LaunchAccent, + textSecondaryColor = Color.White.copy(alpha = 0.55f), + ), + ) + } +} + +@Composable +private fun LaunchPlayButton( + height: Dp, + enabled: Boolean = true, + disabledLabel: String? = null, + onClick: () -> Unit, +) { + val interactionSource = remember { MutableInteractionSource() } + val isPressed by interactionSource.collectIsPressedAsState() + val scale by animateFloatAsState( + targetValue = if (enabled && isPressed) 0.96f else 1f, + animationSpec = spring(dampingRatio = 0.5f, stiffness = 600f), + label = "launchPlayScale", + ) + val flare by animateFloatAsState( + targetValue = if (enabled && isPressed) 1f else 0f, + animationSpec = spring(dampingRatio = 0.6f, stiffness = 500f), + label = "launchPlayFlare", + ) + + val playShape = remember { RoundedCornerShape(14.dp) } + // Disabled: drop the clickable entirely so focus skips it and a stray controller A-press can't fire onClick. + val backgroundBrush = + if (enabled) { + Brush.horizontalGradient( + colors = + listOf( + Color(0xFF00B4D8).copy(alpha = 0.38f), + LaunchAccent.copy(alpha = 0.38f), + Color(0xFF7B2FF7).copy(alpha = 0.38f), + ), + ) + } else { + Brush.horizontalGradient( + colors = + listOf( + Color(0xFF3A3F4A).copy(alpha = 0.35f), + Color(0xFF2D313A).copy(alpha = 0.35f), + Color(0xFF3A3F4A).copy(alpha = 0.35f), + ), + ) + } + val glassSheenBrush = + if (enabled) { + Brush.verticalGradient( + 0.00f to Color.White.copy(alpha = 0.28f), + 0.35f to Color.White.copy(alpha = 0.06f), + 0.55f to Color.Transparent, + 1.00f to Color.Black.copy(alpha = 0.12f), + ) + } else { + Brush.verticalGradient( + 0.0f to Color.White.copy(alpha = 0.10f), + 0.6f to Color.Transparent, + 1.0f to Color.Black.copy(alpha = 0.08f), + ) + } + val glassRimBrush = + if (enabled) { + Brush.verticalGradient( + 0.0f to Color.White.copy(alpha = 0.55f + 0.35f * flare), + 0.5f to Color.White.copy(alpha = 0.08f + 0.18f * flare), + 1.0f to Color.White.copy(alpha = 0.22f + 0.22f * flare), + ) + } else { + Brush.verticalGradient( + 0.0f to Color.White.copy(alpha = 0.16f), + 1.0f to Color.White.copy(alpha = 0.04f), + ) + } + val foregroundAlpha = if (enabled) 1f else 0.75f + + val baseModifier = + Modifier + .fillMaxWidth() + .height(height) + .graphicsLayer { + scaleX = scale + scaleY = scale + }.clip(playShape) + .background(backgroundBrush) + .background(glassSheenBrush) + .border(1.dp, glassRimBrush, playShape) + val finalModifier = + if (enabled) { + baseModifier + .controllerFocusGlow(cornerRadius = 14.dp) + .clickable( + interactionSource = interactionSource, + indication = null, + onClick = onClick, + ) + } else { + baseModifier + } + + val showStatus = !enabled && !disabledLabel.isNullOrBlank() + val icon = if (showStatus) Icons.Outlined.Refresh else Icons.Outlined.PlayArrow + val label = if (showStatus) disabledLabel!! else stringResource(R.string.library_games_play) + + Box( + modifier = finalModifier, + contentAlignment = Alignment.Center, + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + ) { + Icon( + icon, + contentDescription = null, + modifier = Modifier.size(28.dp), + tint = Color.White.copy(alpha = foregroundAlpha), + ) + Spacer(Modifier.width(8.dp)) + Text( + label, + color = Color.White.copy(alpha = foregroundAlpha), + fontSize = 19.sp, + fontWeight = FontWeight.Bold, + maxLines = 1, + ) + } + } +} + +@Composable +private fun LaunchIconActionButton( + icon: ImageVector, + contentDescription: String, + size: Dp, + onClick: () -> Unit, + tint: Color = Color.White, +) { + Surface( + modifier = + Modifier + .size(size) + .clip(RoundedCornerShape(8.dp)) + .clickable(onClick = onClick), + color = LaunchBlack.copy(alpha = 0.46f), + shape = RoundedCornerShape(8.dp), + border = BorderStroke(1.dp, tint.copy(alpha = 0.18f)), + ) { + Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Icon( + icon, + contentDescription = contentDescription, + modifier = Modifier.size(28.dp), + tint = tint, + ) + } + } +} + +private fun formatReleaseDate(releaseDateEpochSeconds: Long): String = + SimpleDateFormat("MMM d, yyyy", Locale.getDefault()) + .format(Date(releaseDateEpochSeconds * 1000L)) + +private fun formatLibraryPlaytime(playtimeMillis: Long): String { + val totalMinutes = (playtimeMillis / 60000L).coerceAtLeast(1L) + val hours = totalMinutes / 60L + val minutes = totalMinutes % 60L + return when { + hours > 0L && minutes > 0L -> "${hours}h ${minutes}m" + hours > 0L -> "${hours}h" + else -> "${minutes}m" + } +} + +private fun formatLibraryLastPlayed(lastPlayedMillis: Long): String = + SimpleDateFormat("MMM d", Locale.getDefault()).format(Date(lastPlayedMillis)) diff --git a/app/src/main/app/shell/RetroConsoleRibbon.kt b/app/src/main/app/shell/RetroConsoleRibbon.kt new file mode 100644 index 000000000..7b2d6c04f --- /dev/null +++ b/app/src/main/app/shell/RetroConsoleRibbon.kt @@ -0,0 +1,72 @@ +package com.winlator.cmod.app.shell + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.width +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.layout +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp + +internal val retroLibrarySystemIds = mutableStateOf>(emptyMap()) + +internal fun libraryBadgeLabel( + appId: Int, + isCustom: Boolean, +): String? { + val systemId = retroLibrarySystemIds.value[appId] + if (systemId != null) { + return com.winlator.cmod.feature.retro.RetroSystems + .fromId(systemId) + ?.badgeLabel + ?: systemId + } + return if (isCustom) "PC" else null +} + +@Composable +internal fun RetroConsoleRibbon( + label: String, + modifier: Modifier = Modifier, +) { + Box( + modifier = + modifier + .fillMaxHeight() + .width(14.dp) + .background(Color(0xD9090C10)), + contentAlignment = Alignment.Center, + ) { + Text( + text = label, + color = Color(0xFFE6EDF3), + fontSize = 8.sp, + fontWeight = FontWeight.Bold, + letterSpacing = 1.2.sp, + maxLines = 1, + softWrap = false, + modifier = Modifier.verticalRibbonText(), + ) + } +} + +private fun Modifier.verticalRibbonText(): Modifier = + this.layout { measurable, _ -> + val placeable = measurable.measure(androidx.compose.ui.unit.Constraints()) + + layout(placeable.height, placeable.width) { + placeable.placeWithLayer( + x = -(placeable.width - placeable.height) / 2, + y = -(placeable.height - placeable.width) / 2, + ) { + rotationZ = -90f + } + } + } diff --git a/app/src/main/app/shell/StoreGameDetailScreen.kt b/app/src/main/app/shell/StoreGameDetailScreen.kt new file mode 100644 index 000000000..7697cf1bc --- /dev/null +++ b/app/src/main/app/shell/StoreGameDetailScreen.kt @@ -0,0 +1,1313 @@ +package com.winlator.cmod.app.shell + +import android.os.Build +import android.view.WindowManager +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.MutableTransitionState +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.spring +import androidx.compose.animation.core.tween +import androidx.compose.animation.expandVertically +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.scaleIn +import androidx.compose.animation.scaleOut +import androidx.compose.animation.shrinkVertically +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsPressedAsState +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.WindowInsetsSides +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.navigationBars +import androidx.compose.foundation.layout.only +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.outlined.ArrowBack +import androidx.compose.material.icons.automirrored.outlined.FactCheck +import androidx.compose.material.icons.outlined.ArrowDropDown +import androidx.compose.material.icons.outlined.CloudSync +import androidx.compose.material.icons.outlined.Construction +import androidx.compose.material.icons.outlined.Delete +import androidx.compose.material.icons.outlined.Download +import androidx.compose.material.icons.outlined.ExpandLess +import androidx.compose.material.icons.outlined.ExpandMore +import androidx.compose.material.icons.outlined.Extension +import androidx.compose.material.icons.outlined.Folder +import androidx.compose.material.icons.outlined.Refresh +import androidx.compose.material.icons.outlined.SportsEsports +import androidx.compose.material.icons.outlined.Storage +import androidx.compose.material.icons.outlined.SystemUpdate +import androidx.compose.material3.Checkbox +import androidx.compose.material3.CheckboxDefaults +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.TransformOrigin +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.DialogWindowProvider +import androidx.compose.ui.window.Popup +import androidx.compose.ui.window.PopupProperties +import androidx.core.view.WindowCompat +import coil.compose.AsyncImage +import coil.request.CachePolicy +import coil.request.ImageRequest +import com.winlator.cmod.R +import com.winlator.cmod.shared.io.StorageUtils +import com.winlator.cmod.shared.ui.nav.DialogPaneNav +import com.winlator.cmod.shared.ui.nav.LocalPaneNav +import com.winlator.cmod.shared.ui.nav.PaneNavRegistry +import com.winlator.cmod.shared.ui.nav.paneNavHandlers +import com.winlator.cmod.shared.ui.nav.paneNavItem + +internal data class StoreDlcItem( + val id: Int, + val name: String, + val downloadSize: Long, + val isInstalled: Boolean = false, +) + +private val StoreBlack = Color.Black +private val StoreCard = Color(0xFF12121B) +private val StoreAccent = Color(0xFF1A9FFF) +private val StoreAccentGlow = Color(0xFF58A6FF) +private val StoreTextPrimary = Color(0xFFF0F4FF) +private val StoreTextSecondary = Color(0xFF93A6BC) +private val StoreDanger = Color(0xFFFF6B6B) + +@Composable +internal fun StoreGameDetailScreen( + title: String, + subtitle: String, + sourceLabel: String, + heroImageUrl: Any?, + isLoading: Boolean, + isInstalled: Boolean, + installPathDisplay: String, + downloadSize: Long, + installSize: Long, + availableBytes: Long, + isInstallEnabled: Boolean, + isDownloadActionEnabled: Boolean = isInstallEnabled, + customPathLabel: String, + showCustomPath: Boolean = true, + showCloudSync: Boolean = false, + showUninstall: Boolean = true, + showUpdateCheck: Boolean = false, + isCheckingForUpdate: Boolean = false, + isUpdateAvailable: Boolean = false, + updateDownloadSize: Long = 0L, + updateStatusText: String? = null, + isUpdateActionEnabled: Boolean = true, + isUpdateCheckCoolingDown: Boolean = false, + showWorkshop: Boolean = false, + showVerifyFiles: Boolean = false, + areSteamActionsEnabled: Boolean = true, + dlcs: List = emptyList(), + selectedDlcIds: Set = emptySet(), + isDlcSelectionEnabled: Boolean = true, + onBack: () -> Unit, + onInstall: () -> Unit = {}, + onCheckForUpdate: () -> Unit = {}, + onWorkshop: () -> Unit = {}, + onVerifyFiles: () -> Unit = {}, + onDownloadUpdate: () -> Unit = {}, + onUninstall: () -> Unit = {}, + onCloudSync: () -> Unit = {}, + onCustomPath: () -> Unit = {}, + onToggleDlc: (Int) -> Unit = {}, + onToggleSelectAllDlcs: () -> Unit = {}, +) { + val context = LocalContext.current + val density = LocalDensity.current + var dlcExpanded by remember { mutableStateOf(false) } + var dlcHeaderHeightPx by remember { mutableIntStateOf(0) } + + val mainRegistry = remember { PaneNavRegistry() } + val menuRegistry = remember { PaneNavRegistry() } + var sourceMenuOpen by remember { mutableStateOf(false) } + + StoreScreenCutoutMode() + + CompositionLocalProvider(LocalPaneNav provides mainRegistry) { + DialogPaneNav( + paneNavHandlers( + onDismiss = { if (sourceMenuOpen) sourceMenuOpen = false else onBack() }, + ) { if (sourceMenuOpen) menuRegistry else mainRegistry }, + ) + Box(Modifier.fillMaxSize()) { + val edgePadding = 22.dp + val bottomPadding = 8.dp + val actionIconSize = 48.dp + val actionIconSpacing = 8.dp + val actionWidth = actionIconSize * 5 + actionIconSpacing * 4 + val ctaHeight = 56.dp + val contentGap = 18.dp + val horizontalNavInsets = WindowInsets.navigationBars.only(WindowInsetsSides.Horizontal) + val hasSelectedInstallableDlc = dlcs.any { !it.isInstalled && it.id in selectedDlcIds } + val showDownloadCta = !isInstalled || hasSelectedInstallableDlc + val updateCheckAvailable = showUpdateCheck && isInstalled + val showUpdateCta = updateCheckAvailable && isUpdateAvailable + val verifyFilesAvailable = showVerifyFiles && isInstalled + val workshopAvailable = showWorkshop && isInstalled + val sourceMenuEnabled = updateCheckAvailable || verifyFilesAvailable || workshopAvailable + val showDlcCard = dlcs.isNotEmpty() + val showActionColumn = + showDownloadCta || showUpdateCta || + (showCloudSync || showUninstall) + + if (heroImageUrl != null) { + val heroRequest = + remember(heroImageUrl, context) { + ImageRequest + .Builder(context) + .data(heroImageUrl) + .crossfade(150) + .memoryCachePolicy(CachePolicy.ENABLED) + .diskCachePolicy(CachePolicy.ENABLED) + .build() + } + AsyncImage( + model = heroRequest, + contentDescription = "$title artwork", + modifier = Modifier.fillMaxSize(), + contentScale = ContentScale.Crop, + alignment = Alignment.Center, + ) + } else { + Box( + Modifier + .fillMaxSize() + .background( + Brush.radialGradient( + colors = listOf(StoreAccent.copy(alpha = 0.34f), StoreCard, StoreBlack), + radius = 980f, + ), + ), + contentAlignment = Alignment.Center, + ) { + Icon( + Icons.Outlined.SportsEsports, + contentDescription = null, + tint = StoreTextPrimary.copy(alpha = 0.18f), + modifier = Modifier.size(132.dp), + ) + } + } + + Box( + Modifier + .fillMaxSize() + .background( + Brush.horizontalGradient( + colorStops = + arrayOf( + 0.0f to StoreBlack.copy(alpha = 0.9f), + 0.36f to StoreBlack.copy(alpha = 0.58f), + 0.72f to StoreBlack.copy(alpha = 0.18f), + 1.0f to StoreBlack.copy(alpha = 0.62f), + ), + ), + ), + ) + Box( + Modifier + .fillMaxSize() + .background( + Brush.verticalGradient( + colorStops = + arrayOf( + 0.0f to StoreBlack.copy(alpha = 0.54f), + 0.36f to Color.Transparent, + 0.72f to StoreBlack.copy(alpha = 0.32f), + 1.0f to StoreBlack.copy(alpha = 0.94f), + ), + ), + ), + ) + + Row( + modifier = + Modifier + .fillMaxWidth() + .windowInsetsPadding(horizontalNavInsets) + .padding(start = edgePadding, top = 12.dp, end = edgePadding), + verticalAlignment = Alignment.CenterVertically, + ) { + IconButton( + onClick = onBack, + modifier = + Modifier + .size(44.dp) + .clip(CircleShape) + .paneNavItem(cornerRadius = 22.dp, onActivate = onBack, navRow = 0, navCol = 0) + .background(StoreBlack.copy(alpha = 0.5f)) + .border(1.dp, Color.White.copy(alpha = 0.18f), CircleShape), + ) { + Icon( + Icons.AutoMirrored.Outlined.ArrowBack, + contentDescription = stringResource(R.string.common_ui_back), + tint = StoreTextPrimary, + modifier = Modifier.size(24.dp), + ) + } + Spacer(Modifier.weight(1f)) + StoreSourceTag( + sourceLabel = sourceLabel, + menuEnabled = sourceMenuEnabled, + menuOpen = sourceMenuOpen, + onMenuOpenChange = { sourceMenuOpen = it }, + menuRegistry = menuRegistry, + showCheckForUpdate = updateCheckAvailable, + showVerifyFiles = verifyFilesAvailable, + showWorkshop = workshopAvailable, + isCheckingForUpdate = isCheckingForUpdate, + areSteamActionsEnabled = areSteamActionsEnabled, + isUpdateCheckEnabled = + !isLoading && + !isCheckingForUpdate && + !isUpdateCheckCoolingDown && + isUpdateActionEnabled, + onVerifyFiles = onVerifyFiles, + onCheckForUpdate = onCheckForUpdate, + onWorkshop = onWorkshop, + ) + } + + val dlcHeaderReserveHeight = + if (showDlcCard && dlcHeaderHeightPx > 0) { + with(density) { dlcHeaderHeightPx.toDp() } + 12.dp + } else { + 0.dp + } + + Column( + modifier = + Modifier + .fillMaxSize() + .windowInsetsPadding(WindowInsets.navigationBars) + .padding( + start = edgePadding, + top = 68.dp, + end = edgePadding, + bottom = bottomPadding + dlcHeaderReserveHeight, + ), + verticalArrangement = Arrangement.SpaceBetween, + ) { + Column( + modifier = Modifier.widthIn(max = 640.dp), + verticalArrangement = Arrangement.spacedBy(6.dp), + ) { + Text( + title, + style = MaterialTheme.typography.headlineLarge, + color = StoreTextPrimary, + fontWeight = FontWeight.Bold, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + if (subtitle.isNotBlank()) { + Text( + subtitle, + style = MaterialTheme.typography.titleSmall, + color = StoreTextPrimary.copy(alpha = 0.72f), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + } + + Column { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(contentGap), + verticalAlignment = Alignment.Bottom, + ) { + FlowRow( + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalArrangement = Arrangement.spacedBy(8.dp), + modifier = Modifier.weight(1f), + ) { + if (isLoading) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(10.dp), + ) { + CircularProgressIndicator( + modifier = Modifier.size(20.dp), + color = StoreAccent, + strokeWidth = 2.dp, + ) + Text( + stringResource(R.string.common_ui_loading), + color = StoreTextSecondary, + fontSize = 12.sp, + ) + } + } else if (isInstalled) { + StoreStatChip( + icon = Icons.Outlined.Storage, + label = stringResource(R.string.library_games_install_path), + value = installPathDisplay, + ) + if (isUpdateAvailable && updateDownloadSize > 0L) { + StoreStatChip( + icon = Icons.Outlined.SystemUpdate, + label = stringResource(R.string.store_game_update), + value = StorageUtils.formatBinarySize(updateDownloadSize), + ) + } + } else { + if (downloadSize > 0L) { + StoreStatChip( + icon = Icons.Outlined.Download, + label = stringResource(R.string.common_ui_download), + value = StorageUtils.formatBinarySize(downloadSize), + ) + } + if (installSize > 0L) { + StoreStatChip( + icon = Icons.Outlined.Storage, + label = stringResource(R.string.common_ui_size), + value = StorageUtils.formatBinarySize(installSize), + valueColor = if (!isInstallEnabled) StoreDanger else null, + ) + } + if (availableBytes > 0L) { + StoreStatChip( + icon = Icons.Outlined.Folder, + label = stringResource(R.string.common_ui_available), + value = StorageUtils.formatBinarySize(availableBytes), + valueColor = if (!isInstallEnabled) StoreDanger else null, + ) + } + if (showCustomPath) { + StoreActionChip( + icon = Icons.Outlined.Folder, + label = customPathLabel, + onClick = onCustomPath, + ) + } + } + } + + if (showActionColumn) { + Column( + modifier = Modifier.width(actionWidth), + verticalArrangement = Arrangement.spacedBy(12.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + if (showUpdateCta) { + StoreCtaButton( + height = ctaHeight, + icon = Icons.Outlined.SystemUpdate, + label = stringResource(R.string.store_game_download_update), + enabled = + !isLoading && + isUpdateActionEnabled && + !isCheckingForUpdate, + loading = false, + onClick = onDownloadUpdate, + isEntry = true, + navRow = 1, + navCol = 3, + ) + } + + if (updateCheckAvailable && !updateStatusText.isNullOrBlank()) { + Text( + updateStatusText, + color = + if (updateStatusText == stringResource(R.string.store_game_update_check_failed)) { + StoreDanger + } else { + StoreTextSecondary + }, + fontSize = 11.sp, + fontWeight = FontWeight.SemiBold, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + + if (showDownloadCta && !isLoading && !isInstallEnabled && installSize > 0L) { + val deficit = (installSize - availableBytes).coerceAtLeast(0L) + if (deficit > 0L) { + Text( + stringResource( + R.string.library_games_not_enough_space, + StorageUtils.formatBinarySize(deficit), + ), + color = StoreDanger, + fontSize = 11.sp, + fontWeight = FontWeight.SemiBold, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + } + + Row( + horizontalArrangement = Arrangement.spacedBy(actionIconSpacing), + verticalAlignment = Alignment.Top, + ) { + if (showCloudSync && isInstalled) { + StoreIconActionButton( + icon = Icons.Outlined.CloudSync, + contentDescription = stringResource(R.string.cloud_saves_title), + size = actionIconSize, + onClick = onCloudSync, + navRow = 1, + navCol = 1, + ) + } + if (showUninstall && isInstalled) { + StoreIconActionButton( + icon = Icons.Outlined.Delete, + contentDescription = stringResource(R.string.common_ui_uninstall), + size = actionIconSize, + onClick = onUninstall, + tint = StoreDanger, + navRow = 1, + navCol = 2, + ) + } + } + + if (showDownloadCta) { + StoreCtaButton( + height = ctaHeight, + icon = Icons.Outlined.Download, + label = stringResource(R.string.common_ui_download), + enabled = !isLoading && isDownloadActionEnabled, + loading = isLoading, + onClick = onInstall, + isEntry = true, + navRow = 1, + navCol = 4, + ) + } + } + } + } + } + } + + if (showDlcCard) { + BoxWithConstraints( + modifier = + Modifier + .fillMaxSize() + .windowInsetsPadding(WindowInsets.navigationBars) + .padding( + start = edgePadding, + top = 68.dp, + end = edgePadding, + bottom = bottomPadding, + ), + contentAlignment = Alignment.BottomStart, + ) { + val maxListHeight = (maxHeight - 96.dp).coerceAtLeast(120.dp) + StoreDlcCard( + dlcs = dlcs, + selectedDlcIds = selectedDlcIds, + selectionEnabled = isDlcSelectionEnabled, + expanded = dlcExpanded, + onToggleExpanded = { dlcExpanded = !dlcExpanded }, + onToggleDlc = onToggleDlc, + onToggleSelectAll = onToggleSelectAllDlcs, + maxListHeight = maxListHeight, + onHeaderMeasured = { dlcHeaderHeightPx = it }, + ) + } + } + } + } +} + +@Composable +private fun StoreDlcCard( + dlcs: List, + selectedDlcIds: Set, + selectionEnabled: Boolean, + expanded: Boolean, + onToggleExpanded: () -> Unit, + onToggleDlc: (Int) -> Unit, + onToggleSelectAll: () -> Unit, + maxListHeight: Dp = 280.dp, + onHeaderMeasured: (Int) -> Unit = {}, +) { + val selectableDlcs = remember(dlcs) { dlcs.filterNot { it.isInstalled } } + val totalSize = remember(selectableDlcs) { selectableDlcs.sumOf { it.downloadSize.coerceAtLeast(0L) } } + val selectedCount = selectableDlcs.count { it.id in selectedDlcIds } + val installedCount = dlcs.count { it.isInstalled } + val selectedSize = remember(dlcs, selectedDlcIds) { + dlcs.filter { !it.isInstalled && it.id in selectedDlcIds }.sumOf { it.downloadSize.coerceAtLeast(0L) } + } + val allSelected = selectableDlcs.isNotEmpty() && selectedCount == selectableDlcs.size + + Surface( + modifier = Modifier.fillMaxWidth(), + color = StoreBlack, + shape = RoundedCornerShape(14.dp), + border = BorderStroke(1.dp, Color.White.copy(alpha = 0.18f)), + ) { + Column(modifier = Modifier.fillMaxWidth()) { + Row( + modifier = + Modifier + .fillMaxWidth() + .onSizeChanged { onHeaderMeasured(it.height) } + .paneNavItem(cornerRadius = 12.dp, onActivate = onToggleExpanded, navRow = 2, navCol = 0) + .clickable(onClick = onToggleExpanded) + .padding(horizontal = 12.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(10.dp), + ) { + Icon( + Icons.Outlined.Extension, + contentDescription = null, + tint = StoreAccentGlow, + modifier = Modifier.size(16.dp), + ) + Column( + modifier = Modifier.weight(1f), + verticalArrangement = Arrangement.spacedBy(1.dp), + ) { + Text( + stringResource(R.string.library_games_dlcs).uppercase(), + color = StoreTextSecondary, + fontSize = 9.sp, + fontWeight = FontWeight.Bold, + letterSpacing = 0.7.sp, + ) + Text( + buildDlcSummary( + selectedCount = selectedCount, + totalCount = selectableDlcs.size, + installedCount = installedCount, + selectedSize = selectedSize, + totalSize = totalSize, + ), + color = StoreTextPrimary, + fontSize = 12.sp, + fontWeight = FontWeight.SemiBold, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + Icon( + if (expanded) Icons.Outlined.ExpandLess else Icons.Outlined.ExpandMore, + contentDescription = null, + tint = StoreTextPrimary, + modifier = Modifier.size(18.dp), + ) + } + + AnimatedVisibility( + visible = expanded, + enter = expandVertically(), + exit = shrinkVertically(), + ) { + Column { + StoreDlcDivider() + if (selectableDlcs.isNotEmpty()) { + Row( + modifier = + Modifier + .fillMaxWidth() + .then( + if (selectionEnabled) { + Modifier.paneNavItem(cornerRadius = 8.dp, onActivate = onToggleSelectAll, navRow = 3, navCol = 0) + } else { + Modifier + }, + ) + .clickable(enabled = selectionEnabled, onClick = onToggleSelectAll) + .padding(horizontal = 6.dp, vertical = 0.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Checkbox( + checked = allSelected, + onCheckedChange = { onToggleSelectAll() }, + enabled = selectionEnabled, + colors = + CheckboxDefaults.colors( + checkedColor = StoreAccent, + uncheckedColor = StoreTextSecondary, + checkmarkColor = Color.White, + ), + ) + Text( + stringResource( + if (allSelected) R.string.common_ui_deselect_all else R.string.common_ui_select_all, + ), + color = StoreTextPrimary, + fontSize = 13.sp, + fontWeight = FontWeight.SemiBold, + modifier = Modifier.weight(1f), + ) + } + StoreDlcDivider() + } + Column( + modifier = + Modifier + .fillMaxWidth() + .heightIn(max = maxListHeight) + .verticalScroll(rememberScrollState()), + ) { + dlcs.forEachIndexed { index, dlc -> + if (index > 0) { + StoreDlcDivider() + } + Row( + modifier = + Modifier + .fillMaxWidth() + .then( + if (dlc.isInstalled || !selectionEnabled) { + Modifier + } else { + Modifier + .paneNavItem(cornerRadius = 8.dp, onActivate = { onToggleDlc(dlc.id) }, navRow = 4 + index, navCol = 0) + .clickable { onToggleDlc(dlc.id) } + }, + ) + .padding(horizontal = 6.dp, vertical = 0.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + if (dlc.isInstalled) { + Checkbox( + checked = true, + onCheckedChange = {}, + enabled = false, + colors = + CheckboxDefaults.colors( + checkedColor = Color(0xFF38D77A), + disabledCheckedColor = Color(0xFF38D77A), + checkmarkColor = Color.White, + ), + ) + } else { + Checkbox( + checked = dlc.id in selectedDlcIds, + onCheckedChange = { onToggleDlc(dlc.id) }, + enabled = selectionEnabled, + colors = + CheckboxDefaults.colors( + checkedColor = StoreAccent, + uncheckedColor = StoreTextSecondary, + checkmarkColor = Color.White, + ), + ) + } + Text( + dlc.name, + color = StoreTextPrimary, + fontSize = 13.sp, + modifier = Modifier.weight(1f), + maxLines = 2, + overflow = TextOverflow.Ellipsis, + ) + Text( + if (dlc.downloadSize > 0L) StorageUtils.formatBinarySize(dlc.downloadSize) else "—", + color = StoreTextSecondary, + fontSize = 12.sp, + fontWeight = FontWeight.SemiBold, + modifier = Modifier.padding(horizontal = 10.dp), + ) + } + } + } + } + } + } + } +} + +@Composable +private fun StoreDlcDivider() { + HorizontalDivider( + color = Color.White.copy(alpha = 0.16f), + thickness = 1.dp, + modifier = Modifier.padding(horizontal = 12.dp), + ) +} + +private fun buildDlcSummary( + selectedCount: Int, + totalCount: Int, + installedCount: Int, + selectedSize: Long, + totalSize: Long, +): String { + val totalSizeStr = if (totalSize > 0L) StorageUtils.formatBinarySize(totalSize) else null + val selectedSizeStr = if (selectedSize > 0L) StorageUtils.formatBinarySize(selectedSize) else null + val selectionText = + when { + totalCount == 0 -> null + selectedCount == 0 && totalSizeStr != null -> "$totalCount available · $totalSizeStr total" + selectedCount == 0 -> "$totalCount available" + selectedSizeStr != null && totalSizeStr != null -> + "$selectedCount of $totalCount · $selectedSizeStr / $totalSizeStr" + else -> "$selectedCount of $totalCount selected" + } + val installedText = if (installedCount > 0) "$installedCount installed" else null + return when { + selectionText != null && installedText != null -> "$selectionText · $installedText" + selectionText != null -> selectionText + installedText != null -> installedText + else -> "" + } +} + +@Composable +private fun StoreSourceTag( + sourceLabel: String, + menuEnabled: Boolean = false, + menuOpen: Boolean = false, + onMenuOpenChange: (Boolean) -> Unit = {}, + menuRegistry: PaneNavRegistry? = null, + showCheckForUpdate: Boolean = false, + showVerifyFiles: Boolean = false, + showWorkshop: Boolean = false, + isCheckingForUpdate: Boolean = false, + areSteamActionsEnabled: Boolean = true, + isUpdateCheckEnabled: Boolean = true, + onVerifyFiles: () -> Unit = {}, + onCheckForUpdate: () -> Unit = {}, + onWorkshop: () -> Unit = {}, +) { + var anchorHeightPx by remember { mutableIntStateOf(0) } + Box { + Surface( + color = Color.White.copy(alpha = 0.1f), + shape = RoundedCornerShape(8.dp), + border = BorderStroke(1.dp, Color.White.copy(alpha = 0.14f)), + modifier = + Modifier + .onSizeChanged { anchorHeightPx = it.height } + .then( + if (menuEnabled) { + Modifier + .paneNavItem(cornerRadius = 8.dp, onActivate = { onMenuOpenChange(!menuOpen) }, navRow = 0, navCol = 1) + .clickable { onMenuOpenChange(!menuOpen) } + } else { + Modifier + }, + ), + ) { + Row( + modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + Box( + Modifier + .size(8.dp) + .clip(CircleShape) + .background(StoreAccent), + ) + Text( + sourceLabel.uppercase(), + color = StoreTextPrimary, + fontSize = 12.sp, + fontWeight = FontWeight.Bold, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + if (menuEnabled) { + Icon( + Icons.Outlined.ArrowDropDown, + contentDescription = stringResource(R.string.store_game_steam_options), + tint = StoreTextPrimary, + modifier = Modifier.size(18.dp), + ) + } + } + } + if (menuEnabled) { + val gapPx = with(LocalDensity.current) { 6.dp.roundToPx() } + StoreSourceActionPopup( + expanded = menuOpen, + onDismissRequest = { onMenuOpenChange(false) }, + offset = IntOffset(0, anchorHeightPx + gapPx), + ) { + CompositionLocalProvider(LocalPaneNav provides menuRegistry) { + if (showVerifyFiles) { + StoreSourceMenuItem( + icon = Icons.AutoMirrored.Outlined.FactCheck, + label = stringResource(R.string.store_game_verify_files), + enabled = areSteamActionsEnabled && !isCheckingForUpdate, + ) { onMenuOpenChange(false); onVerifyFiles() } + } + if (showCheckForUpdate) { + StoreSourceMenuItem( + icon = Icons.Outlined.Refresh, + label = + if (isCheckingForUpdate) { + stringResource(R.string.store_game_checking_for_update) + } else { + stringResource(R.string.store_game_check_for_update) + }, + enabled = areSteamActionsEnabled && isUpdateCheckEnabled, + ) { onMenuOpenChange(false); onCheckForUpdate() } + } + if (showWorkshop) { + StoreSourceMenuItem( + icon = Icons.Outlined.Construction, + label = stringResource(R.string.store_game_workshop), + enabled = areSteamActionsEnabled, + ) { onMenuOpenChange(false); onWorkshop() } + } + } + } + } + } +} + +@Composable +private fun StoreSourceActionPopup( + expanded: Boolean, + onDismissRequest: () -> Unit, + offset: IntOffset, + content: @Composable () -> Unit, +) { + val transitionState = remember { MutableTransitionState(false) } + transitionState.targetState = expanded + if (!transitionState.currentState && !transitionState.targetState) return + + Popup( + alignment = Alignment.TopEnd, + offset = offset, + onDismissRequest = onDismissRequest, + properties = PopupProperties(focusable = false), + ) { + AnimatedVisibility( + visibleState = transitionState, + enter = + fadeIn(animationSpec = tween(durationMillis = 90)) + + scaleIn( + animationSpec = + spring( + dampingRatio = 0.78f, + stiffness = Spring.StiffnessMediumLow, + ), + initialScale = 0.88f, + transformOrigin = TransformOrigin(1f, 0f), + ), + exit = + fadeOut(animationSpec = tween(durationMillis = 80)) + + scaleOut( + animationSpec = tween(durationMillis = 110), + targetScale = 0.92f, + transformOrigin = TransformOrigin(1f, 0f), + ), + ) { + Surface( + color = StoreBlack.copy(alpha = 0.78f), + shape = RoundedCornerShape(12.dp), + border = BorderStroke(1.dp, Color.White.copy(alpha = 0.22f)), + tonalElevation = 0.dp, + shadowElevation = 16.dp, + ) { + Column { content() } + } + } + } +} + +@Composable +private fun StoreSourceMenuItem( + icon: ImageVector, + label: String, + enabled: Boolean = true, + onClick: () -> Unit, +) { + val contentColor = if (enabled) Color.White else Color.White.copy(alpha = 0.45f) + Row( + modifier = + Modifier + .then( + if (enabled) { + Modifier + .paneNavItem(cornerRadius = 8.dp, onActivate = onClick) + .clickable(onClick = onClick) + } else { + Modifier + }, + ) + .padding(start = 14.dp, end = 14.dp, top = 10.dp, bottom = 10.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(10.dp), + ) { + Icon( + icon, + contentDescription = null, + tint = contentColor, + modifier = Modifier.size(16.dp), + ) + Text( + label, + color = contentColor, + fontSize = 12.sp, + fontWeight = FontWeight.SemiBold, + ) + } +} + +@Composable +private fun StoreStatChip( + icon: ImageVector, + label: String, + value: String, + valueColor: Color? = null, +) { + Surface( + color = StoreBlack.copy(alpha = 0.44f), + shape = RoundedCornerShape(12.dp), + border = BorderStroke(1.dp, Color.White.copy(alpha = 0.11f)), + ) { + Row( + modifier = Modifier.padding(horizontal = 10.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(7.dp), + ) { + Icon(icon, contentDescription = null, modifier = Modifier.size(16.dp), tint = StoreAccentGlow) + Column(verticalArrangement = Arrangement.spacedBy(1.dp)) { + Text( + label.uppercase(), + color = StoreTextSecondary, + fontSize = 9.sp, + fontWeight = FontWeight.Bold, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + Text( + value, + color = valueColor ?: StoreTextPrimary, + fontSize = 12.sp, + fontWeight = FontWeight.SemiBold, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + } + } +} + +@Composable +private fun StoreActionChip( + icon: ImageVector, + label: String, + onClick: () -> Unit, +) { + Surface( + color = StoreBlack.copy(alpha = 0.44f), + shape = RoundedCornerShape(12.dp), + border = BorderStroke(1.dp, StoreAccentGlow.copy(alpha = 0.36f)), + modifier = + Modifier + .paneNavItem(cornerRadius = 12.dp, onActivate = onClick, navRow = 1, navCol = 0) + .clickable(onClick = onClick), + ) { + Row( + modifier = Modifier.padding(horizontal = 12.dp, vertical = 10.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + Icon(icon, contentDescription = null, modifier = Modifier.size(18.dp), tint = StoreAccentGlow) + Text( + label, + color = StoreTextPrimary, + fontSize = 13.sp, + fontWeight = FontWeight.SemiBold, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + } +} + +@Composable +private fun StoreCtaButton( + height: Dp, + icon: ImageVector, + label: String, + enabled: Boolean, + loading: Boolean, + onClick: () -> Unit, + isEntry: Boolean = false, + navRow: Int? = null, + navCol: Int? = null, +) { + val interactionSource = remember { MutableInteractionSource() } + val isPressed by interactionSource.collectIsPressedAsState() + val scale by animateFloatAsState( + targetValue = if (isPressed && enabled) 0.96f else 1f, + animationSpec = spring(dampingRatio = 0.5f, stiffness = 600f), + label = "storeCtaScale", + ) + val flare by animateFloatAsState( + targetValue = if (isPressed && enabled) 1f else 0f, + animationSpec = spring(dampingRatio = 0.6f, stiffness = 500f), + label = "storeCtaFlare", + ) + val shape = remember { RoundedCornerShape(14.dp) } + val activeBrush = + Brush.horizontalGradient( + colors = + listOf( + Color(0xFF00B4D8).copy(alpha = 0.38f), + StoreAccent.copy(alpha = 0.38f), + Color(0xFF7B2FF7).copy(alpha = 0.38f), + ), + ) + val disabledBrush = + Brush.horizontalGradient( + colors = + listOf( + Color(0xFF3A3A4A).copy(alpha = 0.35f), + Color(0xFF2A2A36).copy(alpha = 0.35f), + ), + ) + val glassSheenBrush = + if (enabled) { + Brush.verticalGradient( + 0.00f to Color.White.copy(alpha = 0.28f), + 0.35f to Color.White.copy(alpha = 0.06f), + 0.55f to Color.Transparent, + 1.00f to Color.Black.copy(alpha = 0.12f), + ) + } else { + Brush.verticalGradient( + 0.0f to Color.White.copy(alpha = 0.10f), + 0.6f to Color.Transparent, + 1.0f to Color.Black.copy(alpha = 0.08f), + ) + } + val glassRimBrush = + if (enabled) { + Brush.verticalGradient( + 0.0f to Color.White.copy(alpha = 0.55f + 0.35f * flare), + 0.5f to Color.White.copy(alpha = 0.08f + 0.18f * flare), + 1.0f to Color.White.copy(alpha = 0.22f + 0.22f * flare), + ) + } else { + Brush.verticalGradient( + 0.0f to Color.White.copy(alpha = 0.16f), + 1.0f to Color.White.copy(alpha = 0.04f), + ) + } + Box( + modifier = + Modifier + .fillMaxWidth() + .height(height) + .graphicsLayer { + scaleX = scale + scaleY = scale + }.clip(shape) + .paneNavItem( + cornerRadius = 14.dp, + onActivate = { if (enabled && !loading) onClick() }, + isEntry = isEntry, + navRow = navRow, + navCol = navCol, + ) + .background(if (enabled) activeBrush else disabledBrush) + .background(glassSheenBrush) + .border(1.dp, glassRimBrush, shape) + .clickable( + interactionSource = interactionSource, + indication = null, + onClick = { if (enabled && !loading) onClick() }, + ), + contentAlignment = Alignment.Center, + ) { + if (loading) { + CircularProgressIndicator( + modifier = Modifier.size(26.dp), + color = Color.White, + strokeWidth = 2.dp, + ) + } else { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + ) { + Icon( + icon, + contentDescription = null, + modifier = Modifier.size(28.dp), + tint = Color.White, + ) + Spacer(Modifier.width(8.dp)) + Text( + label, + color = Color.White, + fontSize = 19.sp, + fontWeight = FontWeight.Bold, + maxLines = 1, + ) + } + } + } +} + +@Composable +private fun StoreIconActionButton( + icon: ImageVector, + contentDescription: String, + size: Dp, + onClick: () -> Unit, + tint: Color = Color.White, + navRow: Int? = null, + navCol: Int? = null, +) { + Surface( + modifier = + Modifier + .size(size) + .clip(RoundedCornerShape(8.dp)) + .paneNavItem(cornerRadius = 8.dp, onActivate = onClick, navRow = navRow, navCol = navCol) + .clickable(onClick = onClick), + color = StoreBlack.copy(alpha = 0.46f), + shape = RoundedCornerShape(8.dp), + border = BorderStroke(1.dp, tint.copy(alpha = 0.18f)), + ) { + Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Icon( + icon, + contentDescription = contentDescription, + modifier = Modifier.size(28.dp), + tint = tint, + ) + } + } +} + +@Composable +private fun StoreScreenCutoutMode() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) return + + val view = LocalView.current + DisposableEffect(view) { + val window = + (view.parent as? DialogWindowProvider)?.window + ?: return@DisposableEffect onDispose { } + + val originalCutoutMode = window.attributes.layoutInDisplayCutoutMode + val originalWidth = window.attributes.width + val originalHeight = window.attributes.height + val originalNavigationBarColor = window.navigationBarColor + val originalNavBarContrastEnforced = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + window.isNavigationBarContrastEnforced + } else { + false + } + + WindowCompat.setDecorFitsSystemWindows(window, false) + window.clearFlags( + WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION or + WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, + ) + // FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS is required for navigationBarColor to take effect. + // Compose Dialog windows use Theme.DeviceDefault.Dialog which doesn't set it by default, + // so the system would otherwise draw its own opaque navbar over our transparent request. + window.addFlags( + WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS or + WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, + ) + window.setLayout( + WindowManager.LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.MATCH_PARENT, + ) + window.attributes = + window.attributes.apply { + layoutInDisplayCutoutMode = storeCutoutMode() + } + window.navigationBarColor = android.graphics.Color.TRANSPARENT + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + window.isNavigationBarContrastEnforced = false + } + + onDispose { + window.attributes = + window.attributes.apply { + layoutInDisplayCutoutMode = originalCutoutMode + } + window.navigationBarColor = originalNavigationBarColor + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + window.isNavigationBarContrastEnforced = originalNavBarContrastEnforced + } + window.clearFlags( + WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS or + WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, + ) + WindowCompat.setDecorFitsSystemWindows(window, true) + window.setLayout(originalWidth, originalHeight) + } + } +} + +private fun storeCutoutMode(): Int = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS + } else { + WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES + } diff --git a/app/src/main/app/shell/UnifiedActivity.kt b/app/src/main/app/shell/UnifiedActivity.kt index df889f92d..3151462fe 100644 --- a/app/src/main/app/shell/UnifiedActivity.kt +++ b/app/src/main/app/shell/UnifiedActivity.kt @@ -3,7 +3,6 @@ package com.winlator.cmod.app.shell import android.app.Activity import android.app.PendingIntent import android.content.Intent -import android.content.IntentSender import android.content.res.Configuration import android.hardware.input.InputManager import android.graphics.Bitmap @@ -18,7 +17,6 @@ import androidx.activity.compose.BackHandler import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge -import androidx.activity.result.IntentSenderRequest import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity import androidx.compose.animation.AnimatedContent @@ -53,7 +51,9 @@ import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.focusable +import androidx.compose.foundation.gestures.detectHorizontalDragGestures import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior +import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.collectIsPressedAsState import androidx.compose.foundation.layout.* @@ -78,9 +78,11 @@ import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.outlined.ArrowBack +import androidx.compose.material.icons.automirrored.outlined.ExitToApp import androidx.compose.material.icons.automirrored.outlined.OpenInNew import androidx.compose.material.icons.outlined.* import androidx.compose.material3.* +import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.runtime.* import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.saveable.rememberSaveable @@ -88,8 +90,10 @@ import androidx.compose.runtime.snapshotFlow import androidx.compose.runtime.snapshots.SnapshotStateMap import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.zIndex import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.draw.shadow import androidx.compose.ui.focus.FocusRequester @@ -106,11 +110,14 @@ import androidx.compose.ui.graphics.TileMode import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.input.pointer.PointerEventPass +import androidx.compose.ui.input.pointer.PointerEventType import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.res.stringResource @@ -142,9 +149,12 @@ import com.winlator.cmod.R import com.winlator.cmod.app.PluviaApp import com.winlator.cmod.app.db.PluviaDatabase import com.winlator.cmod.app.service.DownloadService +import com.winlator.cmod.app.service.download.DownloadCoordinator import com.winlator.cmod.app.update.UpdateChecker import com.winlator.cmod.feature.settings.InputControlsFragment +import com.winlator.cmod.feature.settings.SettingsFocusZone import com.winlator.cmod.feature.settings.SettingsHost +import com.winlator.cmod.feature.settings.SettingsNavBridge import com.winlator.cmod.feature.settings.SettingsNavItem import com.winlator.cmod.feature.setup.SetupWizardActivity import com.winlator.cmod.feature.shortcuts.LibraryShortcutUtils @@ -152,6 +162,7 @@ import com.winlator.cmod.feature.shortcuts.LibraryShortcutArtwork import com.winlator.cmod.feature.shortcuts.ShortcutBroadcastReceiver import com.winlator.cmod.feature.shortcuts.ShortcutSettingsComposeDialog import com.winlator.cmod.feature.shortcuts.ShortcutsFragment +import com.winlator.cmod.feature.stores.common.StoreArtworkCache import com.winlator.cmod.feature.stores.epic.data.EpicCredentials import com.winlator.cmod.feature.stores.epic.data.EpicGame import com.winlator.cmod.feature.stores.epic.data.EpicGameToken @@ -162,12 +173,16 @@ import com.winlator.cmod.feature.stores.epic.service.EpicDownloadManager import com.winlator.cmod.feature.stores.epic.service.EpicGameLauncher import com.winlator.cmod.feature.stores.epic.service.EpicManager import com.winlator.cmod.feature.stores.epic.service.EpicService +import com.winlator.cmod.feature.stores.epic.service.EpicUpdateInfo import com.winlator.cmod.feature.stores.epic.ui.auth.EpicOAuthActivity +import com.winlator.cmod.feature.stores.gog.data.GOGDlcInfo import com.winlator.cmod.feature.stores.gog.data.GOGGame import com.winlator.cmod.feature.stores.gog.data.LibraryItem import com.winlator.cmod.feature.stores.gog.service.GOGAuthManager import com.winlator.cmod.feature.stores.gog.service.GOGConstants +import com.winlator.cmod.feature.stores.gog.service.GOGManifestSizes import com.winlator.cmod.feature.stores.gog.service.GOGService +import com.winlator.cmod.feature.stores.gog.service.GOGUpdateInfo import com.winlator.cmod.feature.stores.gog.ui.auth.GOGOAuthActivity import com.winlator.cmod.feature.stores.steam.SteamLoginActivity import com.winlator.cmod.feature.stores.steam.data.DepotInfo @@ -182,6 +197,7 @@ import com.winlator.cmod.feature.stores.steam.utils.getAvatarURL import com.winlator.cmod.feature.sync.CloudSyncHelper import com.winlator.cmod.feature.sync.google.CloudSyncManager import com.winlator.cmod.feature.sync.google.GameSaveBackupManager +import com.winlator.cmod.feature.sync.ui.CloudSavesContent import com.winlator.cmod.runtime.container.ContainerManager import com.winlator.cmod.runtime.container.Shortcut import com.winlator.cmod.runtime.display.XServerDisplayActivity @@ -196,17 +212,34 @@ import com.winlator.cmod.shared.android.RefreshRateUtils import com.winlator.cmod.shared.io.StorageUtils import com.winlator.cmod.shared.io.FileUtils import com.winlator.cmod.shared.ui.CarouselView +import com.winlator.cmod.shared.ui.dialog.PopupDialog +import com.winlator.cmod.shared.ui.dialog.PopupTextAction +import androidx.compose.foundation.focusGroup +import com.winlator.cmod.shared.ui.focus.controllerFocusGlow +import com.winlator.cmod.shared.ui.focus.controllerMenuInput +import com.winlator.cmod.shared.ui.focus.controllerTextFieldEscape +import com.winlator.cmod.shared.ui.nav.DialogPaneNav +import com.winlator.cmod.shared.ui.nav.LocalPaneNav +import com.winlator.cmod.shared.ui.nav.PANE_DIR_ACTIVATE +import com.winlator.cmod.shared.ui.nav.PANE_DIR_DOWN +import com.winlator.cmod.shared.ui.nav.PANE_DIR_LEFT +import com.winlator.cmod.shared.ui.nav.PANE_DIR_RIGHT +import com.winlator.cmod.shared.ui.nav.PANE_DIR_SECONDARY +import com.winlator.cmod.shared.ui.nav.PANE_DIR_UP +import com.winlator.cmod.shared.ui.nav.PaneNavRegistry +import com.winlator.cmod.shared.ui.nav.paneNavItem import com.winlator.cmod.shared.ui.FourByTwoGridView import com.winlator.cmod.shared.ui.JoystickGridScroll import com.winlator.cmod.shared.ui.JoystickListScroll import com.winlator.cmod.shared.ui.ListView -import com.winlator.cmod.shared.ui.outlinedSwitchColors import com.winlator.cmod.shared.ui.widget.chasingBorder import com.winlator.cmod.shared.theme.WinNativeTheme import dagger.hilt.android.AndroidEntryPoint import dagger.Lazy -import `in`.dragonbra.javasteam.enums.EPersonaState +import com.winlator.cmod.feature.stores.steam.enums.EPersonaState import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -214,26 +247,25 @@ import javax.inject.Inject import kotlin.math.abs import kotlin.math.roundToInt -// Color palette -private val BgDark = Color(0xFF18181D) -private val SurfaceDark = Color(0xFF1E252E) -private val CardDark = Color(0xFF12121B) -private val CardBorder = Color(0xFF2A2A3A) -private val Accent = Color(0xFF1A9FFF) -private val AccentGlow = Color(0xFF58A6FF) -private val TextPrimary = Color(0xFFF0F4FF) -private val TextSecondary = Color(0xFF7A8FA8) -private val DangerRed = Color(0xFFFF6B6B) -private val StatusOnline = Color(0xFF3FB950) -private val StatusAway = Color(0xFFF0C040) +internal val BgDark = Color(0xFF18181D) +internal val SurfaceDark = Color(0xFF1E252E) +internal val CardDark = Color(0xFF12121B) +internal val CardBorder = Color(0xFF2A2A3A) +internal val Accent = Color(0xFF1A9FFF) +internal val AccentGlow = Color(0xFF58A6FF) +internal val TextPrimary = Color(0xFFF0F4FF) +internal val TextSecondary = Color(0xFF7A8FA8) +internal val DangerRed = Color(0xFFFF6B6B) +internal val StatusOnline = Color(0xFF3FB950) +internal val StatusAway = Color(0xFFF0C040) private val StatusOffline = Color(0xFF6E7681) -private val DownloadCardBlack = Color.Black.copy(alpha = 0.46f) -private val DownloadCardSelectedBlack = Color.Black.copy(alpha = 0.58f) -private val DownloadButtonBlack = Color.Black.copy(alpha = 0.38f) +internal val DownloadCardBlack = Color.Black.copy(alpha = 0.46f) +internal val DownloadCardSelectedBlack = Color.Black.copy(alpha = 0.58f) +internal val DownloadButtonBlack = Color.Black.copy(alpha = 0.38f) private val DownloadChaseBlue = Color(0xFF2196F3) private val DownloadChaseSky = Color(0xFF29B6F6) private val DownloadChaseCyan = Color(0xFF00E5FF) -private val DownloadChaseGradientStops = +internal val DownloadChaseGradientStops = arrayOf( 0.00f to DownloadChaseBlue, 0.125f to DownloadChaseSky, @@ -247,22 +279,22 @@ private val DownloadChaseGradientStops = ) private val TabScreenHorizontalPadding = 16.dp private val TabScreenBottomPadding = 8.dp -private val UnifiedTopBarHorizontalPadding = 8.dp -private val UnifiedTopBarTopPadding = 4.dp -private val UnifiedTopBarHeight = 56.dp -private val TabListContentPadding = PaddingValues(top = 4.dp, bottom = 12.dp) -private val TabGridContentPadding = PaddingValues(top = 8.dp, bottom = 16.dp) -private val TabGridTopPadding = 8.dp -private val TabCarouselTopPadding = 12.dp -private val TabCarouselBottomPadding = 20.dp -private val DownloadsHeaderTopPadding = 2.dp - -private fun Modifier.tabScreenPadding( +internal val UnifiedTopBarHorizontalPadding = 12.dp +internal val UnifiedTopBarTopPadding = 4.dp +internal val UnifiedTopBarHeight = 56.dp +internal val TabListContentPadding = PaddingValues(top = 4.dp, bottom = 12.dp) +internal val TabGridContentPadding = PaddingValues(top = 8.dp, bottom = 16.dp) +internal val TabGridTopPadding = 8.dp +internal val TabCarouselTopPadding = 12.dp +internal val TabCarouselBottomPadding = 20.dp +internal val DownloadsHeaderTopPadding = 2.dp + +internal fun Modifier.tabScreenPadding( top: Dp = 0.dp, bottom: Dp = TabScreenBottomPadding, ): Modifier = padding(start = TabScreenHorizontalPadding, top = top, end = TabScreenHorizontalPadding, bottom = bottom) -private val LIBRARY_NAME_SANITIZE_REGEX = "[^A-Za-z0-9 _-]".toRegex() +internal val LIBRARY_NAME_SANITIZE_REGEX = "[^A-Za-z0-9 _-]".toRegex() enum class LibraryLayoutMode { GRID_4, @@ -270,94 +302,141 @@ enum class LibraryLayoutMode { LIST, } -//test +internal class DownloadsNavBridge { + var controllerActive by mutableStateOf(false) + var navSignal by mutableStateOf(0) + private set + var navDir by mutableStateOf(0) + private set + + private fun nav(dir: Int) { + navDir = dir + navSignal++ + } + + fun left() = nav(PANE_DIR_LEFT) + + fun right() = nav(PANE_DIR_RIGHT) + + fun up() = nav(PANE_DIR_UP) + + fun down() = nav(PANE_DIR_DOWN) + + fun activate() = nav(PANE_DIR_ACTIVATE) + + fun secondary() = nav(PANE_DIR_SECONDARY) +} + @AndroidEntryPoint class UnifiedActivity : FixedFontScaleAppCompatActivity(), ActivityResultHost { @Inject lateinit var dbProvider: Lazy - private val db: PluviaDatabase + internal val db: PluviaDatabase get() = dbProvider.get() - private data class PendingNavigation( + internal data class PendingNavigation( val item: SettingsNavItem = SettingsNavItem.CONTAINERS, val profileId: Int = 0, val editContainerId: Int = 0, val returnToGameOnBack: Boolean = false, ) - private data class ControllerConnectionState( + internal data class ControllerConnectionState( val isConnected: Boolean = ControllerHelper.isControllerConnected(), val isPlayStation: Boolean = ControllerHelper.isPlayStationController(), ) - // Root navigation controller for hub <-> settings transitions - private var rootNavController: NavHostController? = null + internal var rootNavController: NavHostController? = null - // Queued navigation to process once the nav controller is ready - private var pendingNavigation: PendingNavigation? = null + internal var pendingNavigation: PendingNavigation? = null - // Guards against rapid Back presses during the settings → hub exit animation. - // Without this, two popBackStack() calls inside the 300ms transition can desync - // the NavHost state and leave the root composable rendering nothing (black screen). - private var isPoppingSettings: Boolean = false + // Absorb rapid Back presses during the settings exit animation. + internal var isPoppingSettings: Boolean = false - // Track the currently selected game in the carousel for Game Settings button - private var selectedSteamAppId: Int = 0 - private var selectedSteamAppName: String = "" - private var selectedLibrarySource: String = "" - private var selectedGogGameId: String = "" + internal var selectedSteamAppId: Int = 0 + internal var selectedSteamAppName: String = "" + internal var selectedLibrarySource: String = "" + internal var selectedGogGameId: String = "" - // Full library refresh trigger for installs, shortcuts, and external changes. var libraryRefreshSignal by mutableIntStateOf(0) - // Lightweight refresh trigger for playtime/order changes when returning from a game. + var libraryPlaytimeRefreshSignal by mutableIntStateOf(0) private var hasCompletedInitialResume = false - // Freezes the library/store card chasing borders while any full-screen - // dialog is open, so the ~120 Hz animation cost isn't paid for content - // the user can't see or interact with. - private val chasingBordersPaused = mutableStateOf(false) + // Activity-level so task progress survives game-detail dialog teardown. + internal var taskProgressInfo by mutableStateOf(null) + internal var taskProgressGameName by mutableStateOf("") + internal var taskProgressCompleteMsg by mutableStateOf("") + internal var taskProgressFailedMsg by mutableStateOf("") + internal var taskProgressShown by mutableStateOf(false) + internal var taskDoneMessage by mutableStateOf(null) + internal var taskDoneFailed by mutableStateOf(false) + internal var taskCheckingShown by mutableStateOf(false) + internal var taskCheckingGameName by mutableStateOf("") + + internal var taskProgressCompleteAsToast by mutableStateOf(false) - // Keep the first composition light until secure prefs/auth state and the Room DB - // are primed off the UI thread. Rapid relaunches after task removal otherwise - // hit cold-start work here and can stall input. - private var startupBootstrapReady by mutableStateOf(false) - private var startupLibraryLayoutMode by mutableStateOf(null) - private var startupStoreVisible: Map? = null - private var startupContentFilters: Map? = null + // Prevent overlapping checks after the checking pop-up is dismissed. + internal var updateCheckInProgress = false - // LibraryCarousel is always composed (kept alive behind an alpha(0f) when - // another tab is active). This flag lets GameCapsule skip its animation - // while the library is invisible. - private val libraryTabActive = mutableStateOf(true) + // Avoid paying card border animation cost behind full-screen dialogs. + internal val chasingBordersPaused = mutableStateOf(false) + + // Keep first composition light while prefs/auth state and Room warm up. + internal var startupBootstrapReady by mutableStateOf(false) + internal var startupLibraryLayoutMode by mutableStateOf(null) + internal var startupStoreVisible: Map? = null + internal var startupContentFilters: Map? = null + + // Lets kept-alive library cards skip animation while their tab is hidden. + internal val libraryTabActive = mutableStateOf(true) val rightStickScrollState = kotlinx.coroutines.flow.MutableStateFlow(0f) val leftStickScrollState = kotlinx.coroutines.flow.MutableStateFlow(0f) val keyEventFlow = kotlinx.coroutines.flow.MutableSharedFlow(extraBufferCapacity = 10) - // Library grid focus: tracked index and item count, controlled by DPAD + val openHeroForFocusedSignal = kotlinx.coroutines.flow.MutableSharedFlow(extraBufferCapacity = 1) + + val openSearchSignal = kotlinx.coroutines.flow.MutableSharedFlow(extraBufferCapacity = 1) + + val openFriendsSignal = kotlinx.coroutines.flow.MutableSharedFlow(extraBufferCapacity = 1) + + val openGlassesSignal = kotlinx.coroutines.flow.MutableSharedFlow(extraBufferCapacity = 1) + internal var l2KeyDown = false + internal var r2KeyDown = false + internal var l2AxisDown = false + internal var r2AxisDown = false + internal var glassesComboArmed = true + val libraryFocusIndex = kotlinx.coroutines.flow.MutableStateFlow(0) var libraryItemCount: Int = 0 - private var currentLibraryLayoutMode: LibraryLayoutMode = LibraryLayoutMode.GRID_4 + internal var currentLibraryLayoutMode: LibraryLayoutMode = LibraryLayoutMode.GRID_4 + + // Coil model for the focused game's immersive background. + val immersiveBackgroundRef = kotlinx.coroutines.flow.MutableStateFlow(null) + + private val defaultNavigationBarColor: Int = android.graphics.Color.TRANSPARENT - // Store grid focus: same pattern for store/steam/epic/gog tabs val storeFocusIndex = kotlinx.coroutines.flow.MutableStateFlow(0) var storeItemCount: Int = 0 - private var storeColumns: Int = 4 + internal var storeColumns: Int = 4 - // Reference to the active store tab's grid state so we can snap focus to visible area var storeGridState: androidx.compose.foundation.lazy.grid.LazyGridState? = null - // Single shared gate for ALL navigation inputs (dpad keys, hat axes, joystick) - // so that simultaneous events from the same physical input don't cause double moves. + // Shared gate for d-pad, hat, and joystick navigation events. private var lastMoveTime = 0L - // Tracks whether a d-pad direction is currently held so we can distinguish - // a fresh press (fires immediately) from a held repeat (throttled at 250ms). private var dpadHeld = false private var joystickActive = false + @Volatile private var retroCloudUploadBusy = false + + internal val settingsNavBridge = SettingsNavBridge() + internal val downloadsNavBridge = DownloadsNavBridge() + internal val drawerNavBridge = DownloadsNavBridge() + internal val friendsDrawerNavBridge = DownloadsNavBridge() + private var settingsStickEngaged = 0 companion object { private const val MOVE_INTERVAL_MS = 250L @@ -389,92 +468,11 @@ class UnifiedActivity : } } - private val driveAuthLauncher = - registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { result -> - GameSaveBackupManager.onDriveAuthResult(this, result.resultCode) - } - override fun launchWallpaperImagePicker() { wallpaperImagePickerLauncher.launch("image/*") } - override fun launchDriveAuthRequest(intentSender: IntentSender) { - driveAuthLauncher.launch(IntentSenderRequest.Builder(intentSender).build()) - } - - private fun moveLibraryFocus( - left: Boolean, - right: Boolean, - up: Boolean, - down: Boolean, - ) { - val idx = libraryFocusIndex.value - val count = libraryItemCount - if (count <= 0) return - var newIdx = idx - when (currentLibraryLayoutMode) { - LibraryLayoutMode.GRID_4 -> { - if (left) newIdx = (idx - 1).coerceAtLeast(0) - if (right) newIdx = (idx + 1).coerceAtMost(count - 1) - if (up) newIdx = (idx - 4).coerceAtLeast(0) - if (down) newIdx = (idx + 4).coerceAtMost(count - 1) - } - - LibraryLayoutMode.CAROUSEL -> { - if (left) newIdx = (idx - 1).coerceAtLeast(0) - if (right) newIdx = (idx + 1).coerceAtMost(count - 1) - } - - LibraryLayoutMode.LIST -> { - if (up) newIdx = (idx - 1).coerceAtLeast(0) - if (down) newIdx = (idx + 1).coerceAtMost(count - 1) - } - } - libraryFocusIndex.value = newIdx - } - - private fun moveStoreFocus( - left: Boolean, - right: Boolean, - up: Boolean, - down: Boolean, - ) { - val count = storeItemCount - if (count <= 0) return - val cols = storeColumns - - // If the current focus index is not visible (e.g. user scrolled with right joystick), - // snap focus to the top-left of the visible area first. - var idx = storeFocusIndex.value - val grid = storeGridState - if (grid != null) { - val visibleItems = grid.layoutInfo.visibleItemsInfo - if (visibleItems.isNotEmpty()) { - val firstVisible = visibleItems.first().index - val lastVisible = visibleItems.last().index - if (idx < firstVisible || idx > lastVisible) { - idx = firstVisible - storeFocusIndex.value = idx - return // just snap, don't move further this press - } - } - } - - var newIdx = idx - if (left) newIdx = (idx - 1).coerceAtLeast(0) - if (right) newIdx = (idx + 1).coerceAtMost(count - 1) - if (up) newIdx = (idx - cols).coerceAtLeast(0) - if (down) newIdx = (idx + cols).coerceAtMost(count - 1) - storeFocusIndex.value = newIdx - } - - private fun gogPseudoId(gameId: String): Int { - val normalized = gameId.hashCode() and 0x1FFFFFFF - return 1_500_000_000 + normalized - } - - // Cached reference to avoid fragment tree traversal on every input event. - // Invalidated via FragmentLifecycleCallbacks. + // Avoid fragment tree traversal on every input event. private var cachedInputControlsFragment: InputControlsFragment? = null private val inputControlsFragmentTracker = object : androidx.fragment.app.FragmentManager.FragmentLifecycleCallbacks() { @@ -494,7 +492,6 @@ class UnifiedActivity : } override fun dispatchKeyEvent(event: android.view.KeyEvent): Boolean { - // Forward to InputControlsFragment if it's active (for gamepad binding capture) cachedInputControlsFragment?.let { fragment -> if (fragment.dispatchKeyEvent(event)) return true } @@ -502,7 +499,39 @@ class UnifiedActivity : val keyCode = event.keyCode val action = event.action - // Intercept keys we handle globally to prevent fall-through (e.g. Start button launching a game) + if (keyCode == android.view.KeyEvent.KEYCODE_BUTTON_B && + action == android.view.KeyEvent.ACTION_DOWN && + hideImeIfVisible() + ) { + return true + } + + if (keyCode == android.view.KeyEvent.KEYCODE_BUTTON_MODE) { + handleGuideButton(action, event.repeatCount) + return true + } + + if (keyCode == android.view.KeyEvent.KEYCODE_BUTTON_L2 || + keyCode == android.view.KeyEvent.KEYCODE_BUTTON_R2 + ) { + val down = action == android.view.KeyEvent.ACTION_DOWN + if (keyCode == android.view.KeyEvent.KEYCODE_BUTTON_L2) l2KeyDown = down else r2KeyDown = down + updateGlassesCombo() + } + + if (menuNavActive) { + return dispatchMenuNavKey(event, keyCode, action) + } + + if (drawerOpen) { + return dispatchDrawerNavKey(event, keyCode, action) + } + + if (rightDrawerOpen) { + return dispatchDrawerNavKey(event, keyCode, action, friendsDrawerNavBridge) + } + + // Prevent global controller buttons from falling through to launch actions. val isHandledGlobally = when (keyCode) { android.view.KeyEvent.KEYCODE_BUTTON_START, @@ -512,13 +541,15 @@ class UnifiedActivity : android.view.KeyEvent.KEYCODE_BUTTON_Y, android.view.KeyEvent.KEYCODE_BUTTON_L1, android.view.KeyEvent.KEYCODE_BUTTON_R1, + android.view.KeyEvent.KEYCODE_BUTTON_SELECT, + android.view.KeyEvent.KEYCODE_BUTTON_THUMBL, + android.view.KeyEvent.KEYCODE_BUTTON_THUMBR, android.view.KeyEvent.KEYCODE_DPAD_CENTER, -> true else -> false } - // Intercept DPAD events on all tabs for throttled, grid-aware navigation val isDpad = keyCode == android.view.KeyEvent.KEYCODE_DPAD_LEFT || keyCode == android.view.KeyEvent.KEYCODE_DPAD_RIGHT || @@ -526,13 +557,11 @@ class UnifiedActivity : keyCode == android.view.KeyEvent.KEYCODE_DPAD_DOWN if (isDpad) { if (action == android.view.KeyEvent.ACTION_UP) { - // Release: allow next press to fire immediately dpadHeld = false return true } if (action == android.view.KeyEvent.ACTION_DOWN) { val now = android.os.SystemClock.uptimeMillis() - // Fresh press fires immediately; held repeat is throttled at 250ms if (!dpadHeld || (now - lastMoveTime >= MOVE_INTERVAL_MS)) { val left = keyCode == android.view.KeyEvent.KEYCODE_DPAD_LEFT val right = keyCode == android.view.KeyEvent.KEYCODE_DPAD_RIGHT @@ -540,13 +569,32 @@ class UnifiedActivity : val down = keyCode == android.view.KeyEvent.KEYCODE_DPAD_DOWN when (currentTabKey) { "library" -> moveLibraryFocus(left, right, up, down) + "downloads" -> routeDownloadsNav(left, right, up, down) else -> moveStoreFocus(left, right, up, down) } lastMoveTime = now dpadHeld = true } } - return true // consume both DOWN and UP + return true + } + + if (currentTabKey == "downloads" && action == android.view.KeyEvent.ACTION_DOWN) { + when (keyCode) { + android.view.KeyEvent.KEYCODE_BUTTON_A, + android.view.KeyEvent.KEYCODE_DPAD_CENTER, + -> { + downloadsNavBridge.controllerActive = true + downloadsNavBridge.activate() + return true + } + + android.view.KeyEvent.KEYCODE_BUTTON_Y -> { + downloadsNavBridge.controllerActive = true + downloadsNavBridge.secondary() + return true + } + } } if (action == android.view.KeyEvent.ACTION_DOWN) { @@ -555,7 +603,6 @@ class UnifiedActivity : return true } } else if (action == android.view.KeyEvent.ACTION_UP && isHandledGlobally) { - // Consume ACTION_UP for handled keys to ensure balanced event stream for super return true } @@ -571,15 +618,24 @@ class UnifiedActivity : override fun onResume() { super.onResume() + settingsStickEngaged = 0 + joystickActive = false chasingBordersPaused.value = false if (hasCompletedInitialResume) { libraryPlaytimeRefreshSignal++ + SteamService.ensureHealthySession() } else { hasCompletedInitialResume = true } - // (Re)start the background update loop (checks hourly + on first tick) UpdateChecker.startBackgroundLoop(this) + processPendingRetroCloudBackup() + } + + override fun onWindowFocusChanged(hasFocus: Boolean) { + super.onWindowFocusChanged(hasFocus) + settingsStickEngaged = 0 + joystickActive = false } override fun onDestroy() { @@ -594,7 +650,6 @@ class UnifiedActivity : } override fun dispatchGenericMotionEvent(event: android.view.MotionEvent): Boolean { - // Forward to InputControlsFragment if it's active (for gamepad binding capture) cachedInputControlsFragment?.let { fragment -> if (fragment.dispatchGenericMotionEvent(event)) return true } @@ -602,15 +657,52 @@ class UnifiedActivity : if ((event.source and android.view.InputDevice.SOURCE_JOYSTICK) == android.view.InputDevice.SOURCE_JOYSTICK && event.action == android.view.MotionEvent.ACTION_MOVE ) { - // Handle Right Joystick Y axis for scrolling in stores + val lt = kotlin.math.max( + event.getAxisValue(android.view.MotionEvent.AXIS_LTRIGGER), + event.getAxisValue(android.view.MotionEvent.AXIS_BRAKE), + ) + val rt = kotlin.math.max( + event.getAxisValue(android.view.MotionEvent.AXIS_RTRIGGER), + event.getAxisValue(android.view.MotionEvent.AXIS_GAS), + ) + l2AxisDown = lt > 0.5f + r2AxisDown = rt > 0.5f + updateGlassesCombo() + + if (menuNavActive) { + val sx = event.getAxisValue(android.view.MotionEvent.AXIS_X) + val sy = event.getAxisValue(android.view.MotionEvent.AXIS_Y) + val shx = event.getAxisValue(android.view.MotionEvent.AXIS_HAT_X) + val shy = event.getAxisValue(android.view.MotionEvent.AXIS_HAT_Y) + val code = + when { + sx < -0.5f || shx < -0.5f -> android.view.KeyEvent.KEYCODE_DPAD_LEFT + sx > 0.5f || shx > 0.5f -> android.view.KeyEvent.KEYCODE_DPAD_RIGHT + sy < -0.5f || shy < -0.5f -> android.view.KeyEvent.KEYCODE_DPAD_UP + sy > 0.5f || shy > 0.5f -> android.view.KeyEvent.KEYCODE_DPAD_DOWN + else -> 0 + } + if (code != 0) { + if (settingsStickEngaged == 0) { + settingsStickEngaged = code + handleSettingsStick(code) + } + return true + } + if (kotlin.math.abs(sx) < 0.35f && kotlin.math.abs(sy) < 0.35f && + kotlin.math.abs(shx) < 0.35f && kotlin.math.abs(shy) < 0.35f + ) { + settingsStickEngaged = 0 + } + return true + } + val rz = event.getAxisValue(android.view.MotionEvent.AXIS_RZ) rightStickScrollState.value = rz - // Handle Left Joystick Y axis for scrolling in stores val leftY = event.getAxisValue(android.view.MotionEvent.AXIS_Y) leftStickScrollState.value = leftY - // Handle Left Joystick/D-pad for grid navigation on all tabs val x = event.getAxisValue(android.view.MotionEvent.AXIS_X) val y = event.getAxisValue(android.view.MotionEvent.AXIS_Y) val hatX = event.getAxisValue(android.view.MotionEvent.AXIS_HAT_X) @@ -638,16 +730,28 @@ class UnifiedActivity : val right = isHatRight || isJoystickRight val up = isHatUp || isJoystickUp val down = isHatDown || isJoystickDown - when (currentTabKey) { - "library" -> moveLibraryFocus(left, right, up, down) - else -> moveStoreFocus(left, right, up, down) + if (menuNavActive || drawerOpen || rightDrawerOpen) { + val dpadCode = + when { + left -> android.view.KeyEvent.KEYCODE_DPAD_LEFT + right -> android.view.KeyEvent.KEYCODE_DPAD_RIGHT + up -> android.view.KeyEvent.KEYCODE_DPAD_UP + down -> android.view.KeyEvent.KEYCODE_DPAD_DOWN + else -> 0 + } + if (dpadCode != 0) injectKeyEvent(dpadCode) + } else { + when (currentTabKey) { + "library" -> moveLibraryFocus(left, right, up, down) + "downloads" -> routeDownloadsNav(left, right, up, down) + else -> moveStoreFocus(left, right, up, down) + } } lastMoveTime = now joystickActive = true } return true } else if (joystickActive) { - // Joystick returned to center — reset so next flick fires immediately joystickActive = false lastMoveTime = 0L } @@ -655,151 +759,232 @@ class UnifiedActivity : return super.dispatchGenericMotionEvent(event) } - private var currentTabKey: String = "library" + internal var currentTabKey: String = "library" + + @Volatile + private var inSettingsRoute: Boolean = false + + @Volatile + internal var drawerOpen: Boolean = false + internal var rightDrawerOpen: Boolean = false + internal val guideHandler = android.os.Handler(android.os.Looper.getMainLooper()) + internal var guideHoldRunnable: Runnable? = null + + internal val menuNavActive: Boolean + get() = inSettingsRoute - // Callback set by the active store tab so the A-button handler can trigger a click on the focused item var storeItemClickCallback: ((Int) -> Unit)? = null - private fun injectKeyEvent(keyCode: Int) { - window.decorView.rootView.dispatchKeyEvent(android.view.KeyEvent(android.view.KeyEvent.ACTION_DOWN, keyCode)) - window.decorView.rootView.dispatchKeyEvent(android.view.KeyEvent(android.view.KeyEvent.ACTION_UP, keyCode)) - } + private fun dispatchMenuNavKey( + event: android.view.KeyEvent, + keyCode: Int, + action: Int, + ): Boolean { + when (keyCode) { + android.view.KeyEvent.KEYCODE_DPAD_UP, + android.view.KeyEvent.KEYCODE_DPAD_DOWN, + android.view.KeyEvent.KEYCODE_DPAD_LEFT, + android.view.KeyEvent.KEYCODE_DPAD_RIGHT, + -> { + if (settingsNavBridge.zone == SettingsFocusZone.SIDEBAR) { + if (action == android.view.KeyEvent.ACTION_DOWN) applySettingsSidebarNav(keyCode) + return true + } + if (action == android.view.KeyEvent.ACTION_DOWN) navigateSettingsContent(keyCode) + return true + } - private fun reapplyPreferredRefreshRate() { - if (isFinishing || isDestroyed) return - RefreshRateUtils.applyPreferredRefreshRate(this) - } + android.view.KeyEvent.KEYCODE_BUTTON_A, + android.view.KeyEvent.KEYCODE_DPAD_CENTER, + -> { + if (settingsNavBridge.zone == SettingsFocusZone.SIDEBAR) { + if (action == android.view.KeyEvent.ACTION_DOWN) enterSettingsContent() + return true + } + if (action == android.view.KeyEvent.ACTION_DOWN) settingsNavBridge.contentActivate() + return true + } - private fun navigateToSettings( - item: SettingsNavItem = SettingsNavItem.CONTAINERS, - profileId: Int = 0, - editContainerId: Int = 0, - returnToGameOnBack: Boolean = false, - ) { - // Settings is an in-activity navigation target, so entering it does not trigger an - // Activity resume. Reassert the preferred display mode at the activity boundary. - reapplyPreferredRefreshRate() - val route = buildSettingsRoute(item, profileId, editContainerId, returnToGameOnBack) - val nav = rootNavController - if (nav == null) { - pendingNavigation = PendingNavigation(item, profileId, editContainerId, returnToGameOnBack) - return - } - isPoppingSettings = false - nav.navigate(route) { - launchSingleTop = true - } - } + android.view.KeyEvent.KEYCODE_BUTTON_B -> { + if (action == android.view.KeyEvent.ACTION_DOWN) { + onBackPressedDispatcher.onBackPressed() + } + return true + } - private fun buildSettingsRoute( - item: SettingsNavItem = SettingsNavItem.CONTAINERS, - profileId: Int = 0, - editContainerId: Int = 0, - returnToGameOnBack: Boolean = false, - ): String = - "settings?item=${item.name}&profileId=$profileId&editContainerId=$editContainerId&returnToGameOnBack=$returnToGameOnBack" + android.view.KeyEvent.KEYCODE_BUTTON_Y -> { + if (action == android.view.KeyEvent.ACTION_DOWN && + settingsNavBridge.zone == SettingsFocusZone.CONTENT + ) { + settingsNavBridge.contentSecondary() + } + return true + } - private fun extractSettingsNavigation(intent: Intent?): PendingNavigation? { - if (intent == null) return null + android.view.KeyEvent.KEYCODE_BUTTON_L1 -> { + if (action == android.view.KeyEvent.ACTION_DOWN && + settingsNavBridge.zone == SettingsFocusZone.CONTENT + ) { + settingsNavBridge.contentSectionPrev() + } + return true + } - val editContainerId = intent.getIntExtra("edit_container_id", 0) - if (editContainerId > 0) { - return PendingNavigation(SettingsNavItem.CONTAINERS, 0, editContainerId) - } + android.view.KeyEvent.KEYCODE_BUTTON_R1 -> { + if (action == android.view.KeyEvent.ACTION_DOWN && + settingsNavBridge.zone == SettingsFocusZone.CONTENT + ) { + settingsNavBridge.contentSectionNext() + } + return true + } - if (intent.getBooleanExtra("edit_input_controls", false)) { - val profileId = intent.getIntExtra("selected_profile_id", 0) - val returnToGameOnBack = intent.getBooleanExtra("return_to_game_on_back", false) - return PendingNavigation(SettingsNavItem.INPUT_CONTROLS, profileId, 0, returnToGameOnBack) + android.view.KeyEvent.KEYCODE_BUTTON_X, + android.view.KeyEvent.KEYCODE_BUTTON_START, + -> return true } + return super.dispatchKeyEvent(event) + } - val selectedMenuItemId = intent.getIntExtra("selected_menu_item_id", 0) - if (selectedMenuItemId > 0) { - val target = SettingsNavItem.fromMenuId(selectedMenuItemId) ?: SettingsNavItem.CONTAINERS - return PendingNavigation(target, 0, 0) - } + private fun dispatchDrawerNavKey( + event: android.view.KeyEvent, + keyCode: Int, + action: Int, + bridge: DownloadsNavBridge = drawerNavBridge, + ): Boolean { + when (keyCode) { + android.view.KeyEvent.KEYCODE_DPAD_LEFT, + android.view.KeyEvent.KEYCODE_DPAD_RIGHT, + android.view.KeyEvent.KEYCODE_DPAD_UP, + android.view.KeyEvent.KEYCODE_DPAD_DOWN, + -> { + if (action == android.view.KeyEvent.ACTION_DOWN && event.repeatCount == 0) { + bridge.controllerActive = true + when (keyCode) { + android.view.KeyEvent.KEYCODE_DPAD_LEFT -> bridge.left() + android.view.KeyEvent.KEYCODE_DPAD_RIGHT -> bridge.right() + android.view.KeyEvent.KEYCODE_DPAD_UP -> bridge.up() + android.view.KeyEvent.KEYCODE_DPAD_DOWN -> bridge.down() + } + } + return true + } - return null - } + android.view.KeyEvent.KEYCODE_BUTTON_A, + android.view.KeyEvent.KEYCODE_DPAD_CENTER, + -> { + if (action == android.view.KeyEvent.ACTION_DOWN) { + bridge.controllerActive = true + bridge.activate() + } + return true + } - private fun consumeSettingsIntent(intent: Intent?) { - intent ?: return - intent.removeExtra("edit_container_id") - intent.removeExtra("edit_input_controls") - intent.removeExtra("selected_profile_id") - intent.removeExtra("selected_menu_item_id") - intent.removeExtra("return_to_game_on_back") - } + android.view.KeyEvent.KEYCODE_BUTTON_Y -> { + if (action == android.view.KeyEvent.ACTION_DOWN) { + bridge.controllerActive = true + bridge.secondary() + } + return true + } + + android.view.KeyEvent.KEYCODE_BUTTON_B, + android.view.KeyEvent.KEYCODE_BUTTON_SELECT, + -> { + if (action == android.view.KeyEvent.ACTION_DOWN) keyEventFlow.tryEmit(event) + return true + } - private fun handleSettingsIntent(intent: Intent?) { - val request = extractSettingsNavigation(intent) ?: return - consumeSettingsIntent(intent) - navigateToSettings(request.item, request.profileId, request.editContainerId, request.returnToGameOnBack) + android.view.KeyEvent.KEYCODE_BUTTON_X, + android.view.KeyEvent.KEYCODE_BUTTON_START, + android.view.KeyEvent.KEYCODE_BUTTON_L1, + android.view.KeyEvent.KEYCODE_BUTTON_R1, + android.view.KeyEvent.KEYCODE_BUTTON_THUMBL, + android.view.KeyEvent.KEYCODE_BUTTON_THUMBR, + -> return true + } + return super.dispatchKeyEvent(event) } override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) setIntent(intent) + if (maybeForwardFrontendLaunch()) return handleSettingsIntent(intent) } - private fun bootstrapStartupState() { - startupBootstrapReady = false - startupLibraryLayoutMode = null - startupStoreVisible = null - startupContentFilters = null - + internal fun retryPendingRetroCloudBackup() = processPendingRetroCloudBackup() + + private fun processPendingRetroCloudBackup() { + val prefs = androidx.preference.PreferenceManager.getDefaultSharedPreferences(this) + val hasLegacy = prefs.getString("retro_pending_backup_id", null) != null + val hasDolphin = com.winlator.cmod.feature.retro.DolphinCloudSync.peekPending(this) != null + if (!hasLegacy && !hasDolphin) return + if (!com.winlator.cmod.feature.sync.google.GameSaveBackupManager.isDriveConnected(this)) return + runCatching { + com.winlator.cmod.feature.sync.google.PlayGamesBootstrap.ensureInitialized(this) + com.google.android.gms.games.PlayGames + .getGamesSignInClient(this) + .signIn() + .addOnCompleteListener { runPendingRetroUploads() } + }.onFailure { runPendingRetroUploads() } + } + + private fun runPendingRetroUploads() { + if (retroCloudUploadBusy) return + val prefs = androidx.preference.PreferenceManager.getDefaultSharedPreferences(this) + val pendingId = prefs.getString("retro_pending_backup_id", null) + val pendingName = prefs.getString("retro_pending_backup_name", null) + val dolphinPending = com.winlator.cmod.feature.retro.DolphinCloudSync.peekPending(this) + if ((pendingId == null || pendingName == null) && dolphinPending == null) return + retroCloudUploadBusy = true lifecycleScope.launch(Dispatchers.IO) { - val appContext = applicationContext - val resolvedLayoutMode = - runCatching { - PrefManager.init(appContext) - LibraryLayoutMode.valueOf(PrefManager.libraryLayoutMode) - }.getOrElse { error -> - Log.w("UnifiedActivity", "Failed to resolve initial library layout", error) - LibraryLayoutMode.GRID_4 + try { + dolphinPending?.let { p -> + if (uploadRetroCloudBackup(p.cloudId, p.gameName)) { + com.winlator.cmod.feature.retro.DolphinCloudSync.clearPending(this@UnifiedActivity) + if (p.fingerprint.isNotEmpty()) { + prefs.edit().putString("retro_cloud_fp_${p.cloudId}", p.fingerprint).apply() + } + } } - - val resolvedStoreVisible = - runCatching { - val saved = PrefManager.libraryStoreVisible.split(",").toSet() - mapOf("steam" to ("steam" in saved), "epic" to ("epic" in saved), "gog" to ("gog" in saved)) - }.getOrElse { mapOf("steam" to true, "epic" to true, "gog" to true) } - - val resolvedContentFilters = - runCatching { - val saved = PrefManager.libraryContentFilters.split(",").toSet() - mapOf( - "games" to ("games" in saved), - "dlc" to ("dlc" in saved), - "applications" to ("applications" in saved), - "tools" to ("tools" in saved), - ) - }.getOrElse { mapOf("games" to true, "dlc" to false, "applications" to false, "tools" to false) } - - runCatching { dbProvider.get() } - .onFailure { Log.w("UnifiedActivity", "Database warmup failed", it) } - runCatching { EpicAuthManager.updateLoginStatus(appContext) } - .onFailure { Log.w("UnifiedActivity", "Epic auth warmup failed", it) } - runCatching { GOGAuthManager.updateLoginStatus(appContext) } - .onFailure { Log.w("UnifiedActivity", "GOG auth warmup failed", it) } - runCatching { SteamService.initLoginStatus(appContext) } - .onFailure { Log.w("UnifiedActivity", "Steam auth warmup failed", it) } - - withContext(Dispatchers.Main.immediate) { - startupLibraryLayoutMode = resolvedLayoutMode - currentLibraryLayoutMode = resolvedLayoutMode - startupStoreVisible = resolvedStoreVisible - startupContentFilters = resolvedContentFilters - startupBootstrapReady = true + if (pendingId != null && pendingName != null && + uploadRetroCloudBackup(pendingId, pendingName) + ) { + prefs.edit().remove("retro_pending_backup_id").remove("retro_pending_backup_name").apply() + } + } finally { + retroCloudUploadBusy = false } } } + private suspend fun uploadRetroCloudBackup(cloudId: String, gameName: String): Boolean { + val result = + runCatching { + GameSaveBackupManager.backupSaveToGoogle( + this@UnifiedActivity, + GameSaveBackupManager.GameSource.CUSTOM, + cloudId, + gameName, + GameSaveBackupManager.BackupOrigin.AUTO, + com.winlator.cmod.feature.sync.google.GoogleAuthMode.RESUME, + customSaveDir = com.winlator.cmod.feature.retro.RetroSaveStates.gameDir(this, gameName), + ) + }.getOrNull() + android.util.Log.i("WnDolphin", "upload id=$cloudId success=${result?.success} msg=${result?.message}") + if (result?.success == true) { + androidx.preference.PreferenceManager.getDefaultSharedPreferences(this) + .edit().putLong("retro_cloud_mark_$cloudId", System.currentTimeMillis()).apply() + return true + } + return false + } + override fun onCreate(savedInstanceState: Bundle?) { instance = this super.onCreate(savedInstanceState) - if (!SetupWizardActivity.isSetupComplete(this) || !ImageFs.find(this).isValid) { + if (!SetupWizardActivity.isSetupComplete(this) || !ImageFs.find(this).isUpToDate) { startActivity( Intent(this, SetupWizardActivity::class.java) .addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION), @@ -810,10 +995,15 @@ class UnifiedActivity : return } + if (maybeForwardFrontendLaunch()) return + supportFragmentManager.registerFragmentLifecycleCallbacks(inputControlsFragmentTracker, true) + com.winlator.cmod.runtime.display.GlassesManager.init(this) bootstrapStartupState() + maybeAutoSignInGoogleOnLaunch() + processPendingRetroCloudBackup() - // Surface store-session events (e.g. Epic refresh-token death, cloud restore) as toasts. + // Surface store-session events as toasts. lifecycleScope.launch { com.winlator.cmod.feature.stores.common.StoreSessionBus.events.collect { event -> val label = @@ -832,26 +1022,34 @@ class UnifiedActivity : } is com.winlator.cmod.feature.stores.common.StoreSessionEvent.SessionRestored -> Unit is com.winlator.cmod.feature.stores.common.StoreSessionEvent.SessionRefreshed -> { - // informational — no UI surface + Unit } } } } enableEdgeToEdge( - navigationBarStyle = SystemBarStyle.dark(0xFF141B24.toInt()), + navigationBarStyle = SystemBarStyle.dark(android.graphics.Color.TRANSPARENT), ) + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) { + window.isNavigationBarContrastEnforced = false + } val initialSettingsNavigation = extractSettingsNavigation(intent) if (initialSettingsNavigation != null) { consumeSettingsIntent(intent) } - // Exclude left edge from system back gesture so the drawer can capture swipes + // Exclude the drawer edge from system back gesture where Android allows it. if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) { - window.decorView.post { - val leftEdgeWidth = (40 * resources.displayMetrics.density).toInt() - val exclusionRect = android.graphics.Rect(0, 0, leftEdgeWidth, window.decorView.height) - window.decorView.systemGestureExclusionRects = listOf(exclusionRect) + val decorView = window.decorView + val updateDrawerGestureExclusion = { + val leftEdgeWidth = (32 * resources.displayMetrics.density).toInt() + val exclusionRect = android.graphics.Rect(0, 0, leftEdgeWidth, decorView.height) + decorView.systemGestureExclusionRects = listOf(exclusionRect) + } + decorView.post(updateDrawerGestureExclusion) + decorView.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> + updateDrawerGestureExclusion() } } @@ -859,7 +1057,15 @@ class UnifiedActivity : val navController = rememberNavController() rootNavController = navController - // Drain any queued navigation or process the launch intent + DisposableEffect(navController) { + val listener = + androidx.navigation.NavController.OnDestinationChangedListener { _, destination, _ -> + inSettingsRoute = destination.route?.startsWith("settings") == true + } + navController.addOnDestinationChangedListener(listener) + onDispose { navController.removeOnDestinationChangedListener(listener) } + } + LaunchedEffect(Unit) { val pending = pendingNavigation if (pending != null) { @@ -949,8 +1155,6 @@ class UnifiedActivity : }, ) { composable("hub") { - // Once hub is the current destination, the previous settings-pop is - // complete — clear the guard so the next settings session starts fresh. LaunchedEffect(Unit) { isPoppingSettings = false } UnifiedHub() } @@ -988,9 +1192,6 @@ class UnifiedActivity : val returnToGameOnBack = backStackEntry.arguments?.getBoolean("returnToGameOnBack") ?: false - // Idempotent exit: the first Back press returns to the hub, any further - // presses during the 220ms exit animation are absorbed here so NavHost - // state stays consistent (see isPoppingSettings field for full context). val exitSettingsToHubOnce: () -> Unit = { if (!isPoppingSettings) { isPoppingSettings = true @@ -1012,6 +1213,7 @@ class UnifiedActivity : } SettingsHost( + bridge = settingsNavBridge, startItem = startItem, selectedProfileId = profileId, bordersPaused = chasingBordersPaused.value, @@ -1019,7 +1221,6 @@ class UnifiedActivity : ) BackHandler(enabled = true) { exitSettingsToHubOnce() } - // Handle edit_container_id deep link — show dialog on main thread outside composition if (editContainerId > 0) { LaunchedEffect(editContainerId) { val activity = this@UnifiedActivity @@ -1039,9403 +1240,130 @@ class UnifiedActivity : } } } - } - } - scheduleDeferredStoreBootstrap() - } - - private fun scheduleDeferredStoreBootstrap() { - window.decorView.post { - if (isFinishing || isDestroyed) return@post - lifecycleScope.launch(Dispatchers.IO) { - if (EpicService.hasStoredCredentials(this@UnifiedActivity)) { - EpicService.start(this@UnifiedActivity) - // Refresh outside the first-frame path so the UI can render before - // token validation/network work begins. - EpicAuthManager.getStoredCredentials(this@UnifiedActivity) - com.winlator.cmod.feature.stores.epic.service.EpicTokenRefreshWorker - .schedule(this@UnifiedActivity) - } - - if (SteamService.hasStoredCredentials(this@UnifiedActivity)) { - SteamService.start(this@UnifiedActivity) - } - if (GOGAuthManager.isLoggedIn(this@UnifiedActivity)) { - GOGService.start(this@UnifiedActivity) - } - - SteamService.maybeRepairInstalledMetadataOnStartup(this@UnifiedActivity) + TaskProgressHost() } } + scheduleDeferredStoreBootstrap() } - // Tab definitions - private data class TabDef( + internal data class TabDef( val label: String, val key: String, ) - private fun buildTabs(storeVisible: Map): List { - val base = - mutableListOf( - TabDef(getString(R.string.common_ui_library), "library"), - TabDef(getString(R.string.common_ui_downloads), "downloads"), - ) - if (storeVisible["steam"] != false) base.add(TabDef("Steam", "steam")) - if (storeVisible["epic"] != false) base.add(TabDef("Epic", "epic")) - if (storeVisible["gog"] != false) base.add(TabDef("GOG", "gog")) - return base + internal enum class GameSettingsScreen { + Menu, + Shortcut, + CloudSaves, + Uninstall, } - @Composable - private fun rememberSteamInstallStateMap(apps: List): Map { - var installStateMap by remember { mutableStateOf>(emptyMap()) } + internal data class HomeShortcutUiState( + val shortcut: Shortcut? = null, + val isPinned: Boolean = false, + val loaded: Boolean = false, + ) - LaunchedEffect(apps) { - installStateMap = - withContext(Dispatchers.IO) { - apps.associate { it.id to SteamService.isAppInstalled(it.id) } - } - } + internal data class ArtworkCacheId( + val store: String, + val gameId: String, + ) - return installStateMap - } + internal data class GameSettingsActionItem( + val title: String, + val icon: ImageVector, + val accentColor: Color = Accent, + val onClick: () -> Unit, + ) - @Composable - private fun rememberInstallPathStateMap(entries: List>): Map - where K : Any { - var installStateMap by remember { mutableStateOf>(emptyMap()) } + // Library Game Detail Dialog - LaunchedEffect(entries) { - installStateMap = - withContext(Dispatchers.IO) { - entries.associate { (key, path) -> - key to (path?.isNotBlank() == true && java.io.File(path).exists()) - } - } - } + internal enum class LibraryDetailScreen { Main, Shortcut, CloudSaves, Uninstall } - return installStateMap - } + internal enum class LibraryDetailPopup { CloudSaves } - // Main scaffold - @Composable - fun UnifiedHub() { - val horizontalNavigationInsets = - WindowInsets.navigationBars.only(WindowInsetsSides.Horizontal) - val initialLibraryLayoutMode = startupLibraryLayoutMode - val initialStoreVisible = startupStoreVisible ?: mapOf("steam" to true, "epic" to true, "gog" to true) - val initialContentFilters = startupContentFilters ?: mapOf("games" to true, "dlc" to false, "applications" to false, "tools" to false) - if (!startupBootstrapReady || initialLibraryLayoutMode == null) { - Box( - modifier = - Modifier - .fillMaxSize() - .background(BgDark) - .windowInsetsPadding(horizontalNavigationInsets), - contentAlignment = Alignment.Center, - ) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(16.dp), - ) { - CircularProgressIndicator(color = Accent) - Text( - text = stringResource(R.string.common_ui_app_name), - color = TextPrimary, - style = MaterialTheme.typography.titleMedium, - ) - } - } - return - } + internal enum class HeroLaunchPopup { BootToDesktop, RemoveShortcut } - val storeVisible = remember { mutableStateMapOf(*initialStoreVisible.entries.map { it.key to it.value }.toTypedArray()) } - var showAddCustomGame by remember { mutableStateOf(false) } - var showExitDialog by remember { mutableStateOf(false) } - var searchQueryTfv by remember { mutableStateOf(TextFieldValue("")) } - val searchQuery = searchQueryTfv.text - var localLibraryRefreshKey by remember { mutableIntStateOf(0) } - var shortcutDataRefreshKey by remember { mutableIntStateOf(0) } - var iconRefreshKey by remember { mutableIntStateOf(0) } - - val currentRefreshSignal = this@UnifiedActivity.libraryRefreshSignal - val libraryRefreshKey = currentRefreshSignal + localLibraryRefreshKey - val shortcutRefreshKey = libraryRefreshKey + shortcutDataRefreshKey - val playtimeRefreshKey = this@UnifiedActivity.libraryPlaytimeRefreshSignal - - val contentFilters = remember { mutableStateMapOf(*initialContentFilters.entries.map { it.key to it.value }.toTypedArray()) } - var libraryLayoutMode by remember { - mutableStateOf( - initialLibraryLayoutMode, - ) - } - val tabs = remember(storeVisible.toMap()) { buildTabs(storeVisible) } - var selectedIdx by rememberSaveable { mutableIntStateOf(0) } - var selectedDownloadId by remember { mutableStateOf(null) } - val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed) - val isLoggedIn by SteamService.isLoggedInFlow.collectAsState() - val isEpicLoggedIn by EpicAuthManager.isLoggedInFlow.collectAsState() - val isGogLoggedIn by GOGAuthManager.isLoggedInFlow.collectAsState() - val steamApps by db.steamAppDao().getAllOwnedApps().collectAsState(initial = emptyList()) - val context = LocalContext.current - val persona by SteamService.instance?.localPersona?.collectAsState() - ?: remember { mutableStateOf(null) } - val scope = rememberCoroutineScope() - - // Collect Epic/GOG apps from DB flows (Room flows auto-update on data changes) - val epicApps by db.epicGameDao().getAll().collectAsState(initial = emptyList()) - val gogApps by db.gogGameDao().getAll().collectAsState(initial = emptyList()) - - val controllerState = rememberControllerConnectionState() - val isControllerConnected = controllerState.isConnected - val isPS = controllerState.isPlayStation - val isLibraryTab = tabs.getOrNull(selectedIdx)?.key == "library" - - val libraryRefreshListener = - remember { - object : EventDispatcher.JavaEventListener { - override fun onEvent(event: Any) { - when (event) { - is AndroidEvent.LibraryInstallStatusChanged -> { - localLibraryRefreshKey++ - shortcutDataRefreshKey++ - iconRefreshKey++ - } - is AndroidEvent.LibraryArtworkChanged -> { - shortcutDataRefreshKey++ - iconRefreshKey++ - } - } - } - } - } - DisposableEffect(libraryRefreshListener) { - PluviaApp.events.onJava(AndroidEvent.LibraryInstallStatusChanged::class, libraryRefreshListener) - PluviaApp.events.onJava(AndroidEvent.LibraryArtworkChanged::class, libraryRefreshListener) - onDispose { - PluviaApp.events.offJava(AndroidEvent.LibraryInstallStatusChanged::class, libraryRefreshListener) - PluviaApp.events.offJava(AndroidEvent.LibraryArtworkChanged::class, libraryRefreshListener) - } - } + internal enum class HeroBootChoice { Desktop, Cube32, Cube64, Input32, Input64 } - LaunchedEffect(isEpicLoggedIn) { - if (isEpicLoggedIn) { - EpicService.start(context) - } - } + internal data class DownloadCancelRequest( + val ids: List, + val isCancelAll: Boolean, + ) - LaunchedEffect(isGogLoggedIn) { - if (isGogLoggedIn) { - GOGService.start(context) - } - } + internal fun Shortcut.hasExistingArtwork(extraKey: String): Boolean = + (getExtra(extraKey) + .takeIf { it.isNotBlank() } + ?.let { java.io.File(it).isFile } == true) - val epicLoginLauncher = - rememberLauncherForActivityResult( - contract = ActivityResultContracts.StartActivityForResult(), - ) { result -> - if (result.resultCode == android.app.Activity.RESULT_OK) { - val code = result.data?.getStringExtra(EpicOAuthActivity.EXTRA_AUTH_CODE) - if (code != null) { - scope.launch { - val authResult = EpicAuthManager.authenticateWithCode(context, code) - if (authResult.isSuccess) { - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - R.string.stores_accounts_logged_in_epic, - android.widget.Toast.LENGTH_SHORT, - ) - } else { - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - getString(R.string.stores_accounts_epic_login_failed, authResult.exceptionOrNull()?.message), - android.widget.Toast.LENGTH_LONG, - ) - } - } - } - } - } + internal fun buildWineExecCommand( + container: com.winlator.cmod.runtime.container.Container?, + gameInstallPath: String, + relativeExePath: String, + ): String { + val exeFile = java.io.File(gameInstallPath, relativeExePath.replace("\\", "/")) + return buildWineExecCommand(container, gameInstallPath, exeFile) + } - val gogLoginLauncher = - rememberLauncherForActivityResult( - contract = ActivityResultContracts.StartActivityForResult(), - ) { result -> - if (result.resultCode == android.app.Activity.RESULT_OK) { - val code = result.data?.getStringExtra(GOGOAuthActivity.EXTRA_AUTH_CODE) - if (!code.isNullOrBlank()) { - scope.launch { - val authResult = GOGAuthManager.authenticateWithCode(context, code) - if (authResult.isSuccess) { - GOGService.start(context) - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - R.string.stores_accounts_logged_in_gog, - android.widget.Toast.LENGTH_SHORT, - ) - } else { - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - getString(R.string.stores_accounts_gog_login_failed, authResult.exceptionOrNull()?.message), - android.widget.Toast.LENGTH_LONG, - ) - } - } - } - } + internal fun buildWineExecCommand( + container: com.winlator.cmod.runtime.container.Container?, + gameInstallPath: String, + exeFile: java.io.File, + ): String { + val windowsPath = + container?.let { + com.winlator.cmod.runtime.wine.WineUtils + .getDriveCGameWindowsPath( + it, + "CUSTOM", + gameInstallPath, + exeFile.absolutePath, + ) ?: com.winlator.cmod.runtime.wine.WineUtils + .getWindowsPath(it, exeFile.absolutePath) + } ?: run { + com.winlator.cmod.runtime.wine.WineUtils.getDosPath(exeFile.absolutePath) } + return "wine \"$windowsPath\"" + } - val filteredSteamApps = - remember(steamApps, contentFilters.toMap()) { - steamApps.filter { app -> - when (app.type) { - com.winlator.cmod.feature.stores.steam.enums.AppType.game -> contentFilters["games"] == true - com.winlator.cmod.feature.stores.steam.enums.AppType.demo -> contentFilters["games"] == true - com.winlator.cmod.feature.stores.steam.enums.AppType.dlc -> contentFilters["dlc"] == true - com.winlator.cmod.feature.stores.steam.enums.AppType.application -> contentFilters["applications"] == true - com.winlator.cmod.feature.stores.steam.enums.AppType.tool -> contentFilters["tools"] == true - com.winlator.cmod.feature.stores.steam.enums.AppType.config -> contentFilters["tools"] == true - else -> contentFilters["games"] == true - } - } - } - - // Clamp selectedIdx if tabs shrink - var globalSettingsApp by remember { mutableStateOf(null) } - var globalSettingsGogGame by remember { mutableStateOf(null) } - - LaunchedEffect(tabs.size) { if (selectedIdx >= tabs.size) selectedIdx = 0 } - LaunchedEffect(isLoggedIn, persona) { - if (isLoggedIn && persona == null) { - SteamService.requestUserPersona() - } - } - - val activity = LocalContext.current as? UnifiedActivity - - LaunchedEffect(tabs) { - activity?.keyEventFlow?.collect { event -> - val key = tabs.getOrNull(selectedIdx)?.key ?: "library" - when (event.keyCode) { - android.view.KeyEvent.KEYCODE_BUTTON_L1 -> { - selectedIdx = if (selectedIdx > 0) selectedIdx - 1 else tabs.size - 1 - } - - android.view.KeyEvent.KEYCODE_BUTTON_R1 -> { - selectedIdx = (selectedIdx + 1) % tabs.size - } - - android.view.KeyEvent.KEYCODE_BUTTON_START -> { - navigateToSettings(SettingsNavItem.STORES) - } - - android.view.KeyEvent.KEYCODE_BUTTON_X -> { - if (key != "downloads") { - if (drawerState.isOpen) drawerState.close() else drawerState.open() - } - } - - android.view.KeyEvent.KEYCODE_BUTTON_B -> { - // Close menus in order, or show exit confirmation if none are open - if (drawerState.isOpen) { - drawerState.close() - } else if (globalSettingsApp != null) { - globalSettingsApp = null - } else if (globalSettingsGogGame != null) { - globalSettingsGogGame = null - } else if (showAddCustomGame) { - showAddCustomGame = false - } else { - showExitDialog = true - } - } - - android.view.KeyEvent.KEYCODE_BUTTON_Y -> { - if (key == "library" && (selectedSteamAppId != 0 || selectedGogGameId.isNotEmpty())) { - if (selectedLibrarySource == "GOG") { - globalSettingsGogGame = gogApps.find { it.id == selectedGogGameId } - return@collect - } - val isCustom = selectedSteamAppId < 0 - val epicId = if (selectedSteamAppId >= 2000000000) selectedSteamAppId - 2000000000 else 0 - - // Handle Steam, Custom, and Epic semi-unified logic for the settings dialog trigger - globalSettingsApp = ( - steamApps.find { it.id == selectedSteamAppId } - ?: if (isCustom) { - SteamApp(id = selectedSteamAppId, name = selectedSteamAppName, developer = "Custom") - } else if (epicId > 0) { - val epic = epicApps.find { it.id == epicId } - SteamApp( - id = selectedSteamAppId, - name = selectedSteamAppName, - developer = epic?.developer ?: "Epic Games", - gameDir = epic?.installPath ?: "", - ) - } else { - null - } - ) - } - } - - android.view.KeyEvent.KEYCODE_BUTTON_A, android.view.KeyEvent.KEYCODE_DPAD_CENTER -> { - if (key == "library" && (selectedSteamAppId != 0 || selectedGogGameId.isNotEmpty())) { - val isCustom = selectedSteamAppId < 0 - val epicId = if (selectedSteamAppId >= 2000000000) selectedSteamAppId - 2000000000 else 0 - val containerManager = ContainerManager(context) - if (isCustom) { - launchCustomGame(context, containerManager, selectedSteamAppName) - } else if (selectedLibrarySource == "GOG") { - gogApps.find { it.id == selectedGogGameId }?.let { - launchGogGame(context, containerManager, it) - } - } else if (epicId > 0) { - val epic = epicApps.find { it.id == epicId } - if (epic != null && epic.isInstalled) { - val dummyApp = - SteamApp(id = selectedSteamAppId, name = selectedSteamAppName, gameDir = epic.installPath) - launchSteamGame(context, containerManager, dummyApp) - } - } else { - val steam = steamApps.find { it.id == selectedSteamAppId } - if (steam != null) { - launchSteamGame(context, containerManager, steam) - } - } - } else if (key != "library" && key != "downloads") { - // Store tabs: trigger click on focused item - storeItemClickCallback?.invoke(storeFocusIndex.value) - } - } - - } - } - } - - ModalNavigationDrawer( - drawerState = drawerState, - drawerContent = { - DrawerContent( - persona = persona, - context = context, - scope = scope, - storeVisible = storeVisible, - contentFilters = contentFilters, - libraryLayoutMode = libraryLayoutMode, - onLibraryLayoutSelected = { - libraryLayoutMode = it - PrefManager.libraryLayoutMode = it.name - }, - onStoreVisibleChanged = { key, value -> - storeVisible[key] = value - PrefManager.libraryStoreVisible = storeVisible.entries.filter { it.value }.joinToString(",") { it.key } - }, - onContentFiltersChanged = { key, value -> - contentFilters[key] = value - PrefManager.libraryContentFilters = contentFilters.entries.filter { it.value }.joinToString(",") { it.key } - }, - onClose = { scope.launch { drawerState.close() } }, - ) - }, - scrimColor = Color.Black.copy(alpha = 0.5f), - gesturesEnabled = true, - ) { - Box( - Modifier - .fillMaxSize() - .background(BgDark) - .windowInsetsPadding(horizontalNavigationInsets), - ) { - Scaffold( - containerColor = BgDark, - contentWindowInsets = WindowInsets(0, 0, 0, 0), - topBar = { - TopBar(tabs, selectedIdx, { - selectedIdx = it - }, persona, context, scope, isControllerConnected, isPS, isLibraryTab, searchQueryTfv, { - searchQueryTfv = - it - }, onFilterClicked = { scope.launch { drawerState.open() } }) { - if (selectedLibrarySource == "GOG") { - globalSettingsGogGame = gogApps.find { it.id == selectedGogGameId } - } else { - // Try Steam apps first, then fall back to custom or epic pseudo-apps - globalSettingsApp = ( - steamApps.find { it.id == selectedSteamAppId } - ?: if (selectedSteamAppId < 0) { - // Build a pseudo SteamApp for the custom game - SteamApp( - id = selectedSteamAppId, - name = selectedSteamAppName, - developer = "Custom", - ) - } else if (selectedSteamAppId >= 2000000000) { - val epicId = selectedSteamAppId - 2000000000 - val epic = epicApps.find { it.id == epicId } - SteamApp( - id = selectedSteamAppId, - name = selectedSteamAppName, - developer = epic?.developer ?: "Epic Games", - gameDir = epic?.installPath ?: "", - ) - } else { - null - } - ) - } - } - }, - ) { padding -> - LaunchedEffect(selectedIdx, tabs) { - currentTabKey = tabs.getOrNull(selectedIdx)?.key ?: "library" - // Reset store focus when switching tabs - storeFocusIndex.value = 0 - storeItemClickCallback = null - } - - Box(Modifier.padding(padding).fillMaxSize().background(BgDark)) { - val key = tabs.getOrNull(selectedIdx)?.key ?: "library" - - LaunchedEffect(key) { libraryTabActive.value = (key == "library") } - - // Keep Library tab always composed so its state survives tab switches - Box( - Modifier.fillMaxSize().let { - if (key == "library") { - it - } else { - it.alpha(0f).pointerInput(Unit) { /* block ghost taps */ } - } - }, - ) { - LibraryCarousel( - isLoggedIn = isLoggedIn, - steamApps = filteredSteamApps, - epicApps = epicApps, - gogApps = gogApps, - layoutMode = libraryLayoutMode, - libraryRefreshKey = libraryRefreshKey, - shortcutRefreshKey = shortcutRefreshKey, - playtimeRefreshKey = playtimeRefreshKey, - iconRefreshKey = iconRefreshKey, - searchQuery = searchQuery, - isControllerConnected = isControllerConnected, - ) - } - - if (key != "library") { - AnimatedContent( - targetState = key, - transitionSpec = { - fadeIn(tween(200)) togetherWith fadeOut(tween(150)) - }, - label = "tabContent", - ) { animatedKey -> - when (animatedKey) { - "downloads" -> { - DownloadsTab( - selectedDownloadId, - animationsActive = key == "downloads", - onSelectDownload = { selectedDownloadId = it }, - ) - } - - "steam" -> { - SteamStoreTab(isLoggedIn, filteredSteamApps, searchQuery, libraryLayoutMode) - } - - "epic" -> { - EpicStoreTab(isEpicLoggedIn, epicApps, searchQuery, libraryLayoutMode) { - epicLoginLauncher.launch(Intent(this@UnifiedActivity, EpicOAuthActivity::class.java)) - } - } - - "gog" -> { - GOGStoreTab(isGogLoggedIn, gogApps, searchQuery, libraryLayoutMode) { - gogLoginLauncher.launch(Intent(this@UnifiedActivity, GOGOAuthActivity::class.java)) - } - } - - else -> {} - } - } - } - - val configuration = LocalConfiguration.current - val libraryFabBase = minOf(configuration.screenWidthDp, configuration.screenHeightDp) - val addGameFabSize = (libraryFabBase * 0.125f).dp.coerceIn(56.dp, 64.dp) - val addGameFabMargin = (libraryFabBase * 0.035f).dp.coerceIn(12.dp, 20.dp) - val addGameFabIconSize = (libraryFabBase * 0.055f).dp.coerceIn(24.dp, 28.dp) - - // Bottom-right Add Custom Game button - if (key == "library") { - Box( - modifier = - Modifier - .align(Alignment.BottomEnd) - .windowInsetsPadding( - WindowInsets.navigationBars.only(WindowInsetsSides.Bottom), - ) - .padding(end = addGameFabMargin, bottom = addGameFabMargin) - .size(addGameFabSize) - .shadow(10.dp, CircleShape, spotColor = Accent.copy(alpha = 0.4f)) - .clip(CircleShape) - .background(SurfaceDark.copy(alpha = 0.96f), CircleShape) - .border(1.5.dp, Accent.copy(alpha = 0.55f), CircleShape) - .focusProperties { canFocus = false } // No specific button for this, handle via long press or touch - .clickable { showAddCustomGame = true }, - contentAlignment = Alignment.Center, - ) { - Icon( - Icons.Outlined.Add, - contentDescription = "Add Custom Game", - tint = Color.White, - modifier = Modifier.size(addGameFabIconSize), - ) - } - } - } - } - } - } // end ModalNavigationDrawer - - if (globalSettingsApp != null) { - GameSettingsDialog( - app = globalSettingsApp!!, - onDismissRequest = { globalSettingsApp = null }, - ) - } - if (globalSettingsGogGame != null) { - GOGGameSettingsDialog( - app = globalSettingsGogGame!!, - onDismissRequest = { globalSettingsGogGame = null }, - ) - } - - if (showAddCustomGame) { - AddCustomGameDialog(onDismiss = { - showAddCustomGame = false - localLibraryRefreshKey++ - }) - } - - // Back button exit confirmation - BackHandler(enabled = true) { - // Consistent behavior: close overlays first, then show exit confirmation - if (drawerState.isOpen) { - scope.launch { drawerState.close() } - } else if (globalSettingsApp != null) { - globalSettingsApp = null - } else if (globalSettingsGogGame != null) { - globalSettingsGogGame = null - } else if (showAddCustomGame) { - showAddCustomGame = false - } else { - showExitDialog = true - } - } - - if (showExitDialog) { - Dialog( - onDismissRequest = { showExitDialog = false }, - properties = DialogProperties(dismissOnBackPress = true, dismissOnClickOutside = true), - ) { - Box( - modifier = - Modifier - .width(320.dp) - .clip(RoundedCornerShape(20.dp)) - .background(SurfaceDark) - .border(1.dp, Accent.copy(alpha = 0.3f), RoundedCornerShape(20.dp)) - .padding(28.dp), - contentAlignment = Alignment.Center, - ) { - Column(horizontalAlignment = Alignment.CenterHorizontally) { - Text( - text = stringResource(R.string.common_ui_exit_app_confirm), - style = MaterialTheme.typography.titleLarge, - color = TextPrimary, - fontWeight = FontWeight.Bold, - ) - Spacer(Modifier.height(24.dp)) - Row( - horizontalArrangement = Arrangement.spacedBy(16.dp), - ) { - // Cancel button - OutlinedButton( - onClick = { showExitDialog = false }, - colors = ButtonDefaults.outlinedButtonColors(contentColor = TextSecondary), - border = androidx.compose.foundation.BorderStroke(1.dp, TextSecondary.copy(alpha = 0.5f)), - shape = RoundedCornerShape(12.dp), - modifier = Modifier.weight(1f), - ) { - Text(stringResource(R.string.common_ui_cancel), fontWeight = FontWeight.Medium) - } - // Exit button - Button( - onClick = { - AppTerminationHelper.exitApplication(this@UnifiedActivity, "hub_exit_menu") - }, - colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFE53935)), - shape = RoundedCornerShape(12.dp), - modifier = Modifier.weight(1f), - ) { - Text(stringResource(R.string.common_ui_exit), color = Color.White, fontWeight = FontWeight.Bold) - } - } - } - } - } - } - } - - // Top bar - @Composable - private fun TopBar( - tabs: List, - selectedIdx: Int, - onSelect: (Int) -> Unit, - persona: com.winlator.cmod.feature.stores.steam.data.SteamFriend?, - context: android.content.Context, - scope: kotlinx.coroutines.CoroutineScope, - isControllerConnected: Boolean, - isPS: Boolean, - isLibraryTab: Boolean, - searchQuery: TextFieldValue, - onSearchQueryChange: (TextFieldValue) -> Unit, - onFilterClicked: () -> Unit, - onGameSettingsClicked: () -> Unit, - ) { - var isSearchExpanded by remember { mutableStateOf(false) } - val searchFocusRequester = remember { FocusRequester() } - val keyboardController = LocalSoftwareKeyboardController.current - val isDownloadsTab = tabs.getOrNull(selectedIdx)?.key == "downloads" - - // Auto-collapse search when switching tabs - LaunchedEffect(selectedIdx) { - if (isSearchExpanded) { - onSearchQueryChange(TextFieldValue("")) - isSearchExpanded = false - } - } - - // Auto-focus the search field when expanded - LaunchedEffect(isSearchExpanded) { - if (isSearchExpanded) { - kotlinx.coroutines.delay(150) - searchFocusRequester.requestFocus() - } else if (searchQuery.text.isNotEmpty()) { - onSearchQueryChange(TextFieldValue("")) - } - } - - Column(modifier = Modifier.fillMaxWidth()) { - Box( - modifier = - Modifier - .fillMaxWidth() - .padding( - start = UnifiedTopBarHorizontalPadding, - end = UnifiedTopBarHorizontalPadding, - top = UnifiedTopBarTopPadding, - ) - .height(UnifiedTopBarHeight), - ) { - // Center Block: Tabs (absolutely centered, unaffected by left/right content) - Row( - modifier = Modifier.align(Alignment.Center), - verticalAlignment = Alignment.CenterVertically, - ) { - if (isControllerConnected) { - ControllerBadge("L1") - Spacer(Modifier.width(8.dp)) - } - @Suppress("DEPRECATION") - CompositionLocalProvider( - androidx.compose.material3.LocalRippleConfiguration provides null, - ) { - val tabWidth = 100.dp - val tabSideGutter = 12.dp - val tabBarShape = RoundedCornerShape(18.dp) - val visibleCount = minOf(3, tabs.size) - val tabListState = rememberLazyListState() - val snapFlingBehavior = rememberSnapFlingBehavior(lazyListState = tabListState) - - LaunchedEffect(selectedIdx) { - val scrollTo = maxOf(0, selectedIdx - 1) - tabListState.animateScrollToItem(scrollTo) - } - - Box( - modifier = - Modifier - .width(tabWidth * visibleCount + tabSideGutter * 2) - .height(44.dp) - .shadow(8.dp, tabBarShape, spotColor = Color.Black.copy(alpha = 0.5f)) - .clip(tabBarShape) - .background(CardDark) - .border(1.dp, CardBorder, tabBarShape), - ) { - LazyRow( - state = tabListState, - flingBehavior = snapFlingBehavior, - modifier = - Modifier - .align(Alignment.Center) - .width(tabWidth * visibleCount) - .fillMaxHeight() - .focusProperties { canFocus = !isLibraryTab }, - userScrollEnabled = tabs.size > visibleCount, - ) { - itemsIndexed(tabs) { index, tab -> - val selected = selectedIdx == index - val interactionSource = remember { MutableInteractionSource() } - val isPressed by interactionSource.collectIsPressedAsState() - val tabScale by animateFloatAsState( - targetValue = if (isPressed) 0.92f else 1f, - animationSpec = spring(stiffness = Spring.StiffnessHigh), - label = "tabScale", - ) - val textColor by animateColorAsState( - targetValue = if (selected) Accent else TextSecondary, - animationSpec = tween(280), - label = "tabTextColor", - ) - - Box( - modifier = - Modifier - .width(tabWidth) - .fillMaxHeight() - .focusProperties { canFocus = false } - .graphicsLayer { - scaleX = tabScale - scaleY = tabScale - }.clickable( - interactionSource = interactionSource, - indication = null, - ) { onSelect(index) }, - contentAlignment = Alignment.Center, - ) { - Text( - text = tab.label.uppercase(), - style = MaterialTheme.typography.labelLarge, - fontWeight = if (selected) FontWeight.Bold else FontWeight.Medium, - fontSize = 13.sp, - maxLines = 1, - color = textColor, - ) - } - } - } - } - } - if (isControllerConnected) { - Spacer(Modifier.width(8.dp)) - ControllerBadge("R1") - } - } - - // Left Block: Settings & Search - Row( - modifier = Modifier.align(Alignment.CenterStart).fillMaxHeight(), - verticalAlignment = Alignment.CenterVertically, - ) { - // Settings Button - Box( - modifier = - Modifier - .size(44.dp) - .shadow(6.dp, CircleShape, spotColor = Color.Black.copy(alpha = 0.5f)) - .clip(CircleShape) - .background(SurfaceDark) - .focusProperties { canFocus = !isLibraryTab }, - contentAlignment = Alignment.Center, - ) { - IconButton(onClick = { - navigateToSettings(SettingsNavItem.STORES) - }, modifier = Modifier.size(44.dp), enabled = true) { - Icon(Icons.Outlined.Settings, contentDescription = "Menu", tint = TextPrimary, modifier = Modifier.size(24.dp)) - } - } - if (isControllerConnected) { - Spacer(Modifier.width(8.dp)) - ControllerBadge(if (isPS) "\u2261" else "Start") - } - - // Search Button (disabled on downloads tab) - Spacer(Modifier.width(12.dp)) - - val searchIconRotation by animateFloatAsState( - targetValue = if (isSearchExpanded) 90f else 0f, - animationSpec = - spring( - dampingRatio = Spring.DampingRatioMediumBouncy, - stiffness = Spring.StiffnessLow, - ), - label = "searchIconRotation", - ) - - Box( - modifier = - Modifier - .size(44.dp) - .shadow(6.dp, CircleShape, spotColor = Color.Black.copy(alpha = 0.5f)) - .clip(CircleShape) - .background( - if (isDownloadsTab) { - SurfaceDark.copy(alpha = 0.4f) - } else if (isSearchExpanded) { - Accent.copy(alpha = 0.15f) - } else { - SurfaceDark - }, - ).focusProperties { canFocus = !isLibraryTab }, - contentAlignment = Alignment.Center, - ) { - IconButton( - onClick = { - if (!isDownloadsTab) { - if (isSearchExpanded) { - onSearchQueryChange(TextFieldValue("")) - isSearchExpanded = false - } else { - isSearchExpanded = true - } - } - }, - modifier = Modifier.size(44.dp), - enabled = !isDownloadsTab, - ) { - Icon( - Icons.Outlined.Search, - contentDescription = "Search", - tint = - if (isDownloadsTab) { - TextSecondary.copy(alpha = 0.4f) - } else if (isSearchExpanded) { - Accent - } else { - TextPrimary - }, - modifier = - Modifier - .size(24.dp) - .graphicsLayer { rotationZ = searchIconRotation }, - ) - } - } - } - - // Right Block: Status & Actions - Row( - modifier = Modifier.align(Alignment.CenterEnd).fillMaxHeight(), - horizontalArrangement = Arrangement.End, - verticalAlignment = Alignment.CenterVertically, - ) { - val isStore = tabs.getOrNull(selectedIdx)?.label?.contains("Store", ignoreCase = true) == true - if (isControllerConnected && !isStore) { - ControllerBadge(if (isPS) "\u25B3" else "Y") - Spacer(Modifier.width(8.dp)) - } - - Spacer(Modifier.width(8.dp)) - - // Filter button (opens drawer) - Box( - modifier = - Modifier - .size(44.dp) - .shadow(6.dp, CircleShape, spotColor = Color.Black.copy(alpha = 0.5f)) - .clip(CircleShape) - .background(SurfaceDark) - .focusProperties { canFocus = !isLibraryTab } - .clickable { onFilterClicked() }, - contentAlignment = Alignment.Center, - ) { - Icon(Icons.Outlined.FilterList, contentDescription = "Filter", tint = TextPrimary, modifier = Modifier.size(24.dp)) - } - if (isControllerConnected) { - Spacer(Modifier.width(8.dp)) - ControllerBadge(if (isPS) "\u25A1" else "X") - } - } - } - - // Dropdown Search Bar - AnimatedVisibility( - visible = isSearchExpanded && !isDownloadsTab, - enter = - expandVertically( - animationSpec = - spring( - dampingRatio = Spring.DampingRatioNoBouncy, - stiffness = Spring.StiffnessMedium, - ), - expandFrom = Alignment.Top, - ) + fadeIn(animationSpec = tween(200)), - exit = - shrinkVertically( - animationSpec = - spring( - dampingRatio = Spring.DampingRatioNoBouncy, - stiffness = Spring.StiffnessMedium, - ), - shrinkTowards = Alignment.Top, - ) + fadeOut(animationSpec = tween(120)), - ) { - Box( - modifier = - Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 6.dp), - contentAlignment = Alignment.Center, - ) { - Box( - modifier = - Modifier - .widthIn(max = 600.dp) - .fillMaxWidth(0.7f) - .height(44.dp) - .shadow(8.dp, RoundedCornerShape(24.dp), spotColor = Color.Black.copy(alpha = 0.4f)) - .clip(RoundedCornerShape(24.dp)) - .background(SurfaceDark), - contentAlignment = Alignment.CenterStart, - ) { - Row( - modifier = - Modifier - .fillMaxSize() - .padding(horizontal = 16.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - Icon( - Icons.Outlined.Search, - contentDescription = null, - tint = Accent, - modifier = Modifier.size(22.dp), - ) - Spacer(Modifier.width(12.dp)) - BasicTextField( - value = searchQuery, - onValueChange = onSearchQueryChange, - singleLine = true, - textStyle = - TextStyle( - color = TextPrimary, - fontSize = 15.sp, - ), - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search), - keyboardActions = KeyboardActions(onSearch = { keyboardController?.hide() }), - cursorBrush = Brush.verticalGradient(listOf(Accent, AccentGlow)), - modifier = - Modifier - .weight(1f) - .focusRequester(searchFocusRequester), - decorationBox = { innerTextField -> - Box(contentAlignment = Alignment.CenterStart) { - if (searchQuery.text.isEmpty()) { - Text( - "Search games...", - style = - TextStyle( - color = TextSecondary, - fontSize = 15.sp, - ), - ) - } - innerTextField() - } - }, - ) - if (searchQuery.text.isNotEmpty()) { - IconButton( - onClick = { onSearchQueryChange(TextFieldValue("")) }, - modifier = Modifier.size(32.dp), - ) { - Icon( - Icons.Outlined.Close, - contentDescription = "Clear", - tint = TextSecondary, - modifier = Modifier.size(18.dp), - ) - } - } - } - } - } - } - } // end Column - } - - // PS5-style Library Carousel - @Composable - fun LibraryCarousel( - isLoggedIn: Boolean, - steamApps: List, - epicApps: List, - gogApps: List, - layoutMode: LibraryLayoutMode, - libraryRefreshKey: Int = 0, - shortcutRefreshKey: Int = 0, - playtimeRefreshKey: Int = 0, - iconRefreshKey: Int = 0, - searchQuery: String = "", - isControllerConnected: Boolean = false, - ) { - val context = LocalContext.current - - // Load all shortcuts once and cache for both custom app discovery and GameCapsule icon lookup - var cachedShortcuts by remember { mutableStateOf>(emptyList()) } - var customApps by remember { mutableStateOf>(emptyList()) } - var localLibraryRefreshKey by remember { mutableIntStateOf(0) } - var shortcutsLoaded by remember { mutableStateOf(false) } - LaunchedEffect(shortcutRefreshKey, localLibraryRefreshKey) { - shortcutsLoaded = false - - val shortcutScanResult = - runCatching { - withContext(Dispatchers.IO) { - val cm = ContainerManager(context) - cm.upgradeShortcuts { - localLibraryRefreshKey++ - } - val allShortcuts = cm.loadShortcuts() - val apps = - allShortcuts - .mapNotNull { shortcut -> - if (!LibraryShortcutUtils.isCustomLibraryShortcut(shortcut)) { - return@mapNotNull null - } - - val displayName = - shortcut - .getExtra("custom_name", shortcut.name) - .ifBlank { shortcut.name } - - val uuid = shortcut.getExtra("uuid") - val customId = if (uuid.isNotEmpty()) { - // Use UUID hash to ensure ID stability across renames - -(uuid.hashCode().and(0x7FFFFFFF) + 1) - } else { - -(displayName.hashCode().and(0x7FFFFFFF) + 1) - } - - SteamApp( - id = customId, - name = displayName, - developer = "Custom", - gameDir = - shortcut.getExtra( - "game_install_path", - shortcut.getExtra("custom_game_folder", ""), - ), - ) - } - - allShortcuts to apps - } - }.getOrNull() - - if (shortcutScanResult != null) { - cachedShortcuts = shortcutScanResult.first - customApps = shortcutScanResult.second - } - - shortcutsLoaded = true - } - - // Move expensive filtering (runBlocking DB queries, file I/O) off the main thread. - // This set only changes on real library mutations; playtime resorts are handled separately. - var mergedInstalledApps by remember { mutableStateOf>(emptyList()) } - var installedApps by remember { mutableStateOf>(emptyList()) } - var stableInstalledApps by remember { mutableStateOf>(emptyList()) } - var gogByPseudoId by remember { mutableStateOf>(emptyMap()) } - var epicByPseudoId by remember { mutableStateOf>(emptyMap()) } - var stableGogByPseudoId by remember { mutableStateOf>(emptyMap()) } - var stableEpicByPseudoId by remember { mutableStateOf>(emptyMap()) } - var customArtworkPathByAppId by remember { mutableStateOf>(emptyMap()) } - var customGridArtworkPathByAppId by remember { mutableStateOf>(emptyMap()) } - var customCarouselArtworkPathByAppId by remember { mutableStateOf>(emptyMap()) } - var customListArtworkPathByAppId by remember { mutableStateOf>(emptyMap()) } - var customIconPathByAppId by remember { mutableStateOf>(emptyMap()) } - var stableCustomArtworkPathByAppId by remember { mutableStateOf>(emptyMap()) } - var stableCustomGridArtworkPathByAppId by remember { mutableStateOf>(emptyMap()) } - var stableCustomCarouselArtworkPathByAppId by remember { mutableStateOf>(emptyMap()) } - var stableCustomListArtworkPathByAppId by remember { mutableStateOf>(emptyMap()) } - var stableCustomIconPathByAppId by remember { mutableStateOf>(emptyMap()) } - var libraryLoaded by remember { mutableStateOf(false) } - // Track whether a new source snapshot is awaiting recomputation. The token - // changes during composition as soon as any input list changes, so we can - // suppress transient empty states before the background coroutine starts. - val scanInputToken = - remember(steamApps, epicApps, gogApps, customApps, libraryRefreshKey) { Any() } - var processedScanToken by remember { mutableStateOf(null) } - - LaunchedEffect(scanInputToken) { - withContext(Dispatchers.IO) { - val steamInstalled = steamApps.filter { SteamService.isAppInstalled(it.id) } - - val epicInstalled = epicApps.filter { it.isInstalled } - - val gogInstalled = gogApps.filter { it.isInstalled && java.io.File(it.installPath).exists() } - - val gogMap = gogInstalled.associateBy { gogPseudoId(it.id) } - val epicMap = epicInstalled.associateBy { 2000000000 + it.id } - - val playtimePrefs = context.getSharedPreferences("playtime_stats", android.content.Context.MODE_PRIVATE) - val allPlaytime = playtimePrefs.all - val mappedEpic = - epicInstalled.map { epic -> - SteamApp( - id = 2000000000 + epic.id, - name = epic.title, - developer = epic.developer, - gameDir = epic.installPath, - ) - } - val mappedGog = - gogInstalled.map { gog -> - SteamApp( - id = gogPseudoId(gog.id), - name = gog.title, - developer = gog.developer, - gameDir = gog.installPath, - ) - } - val merged = steamInstalled + customApps + mappedEpic + mappedGog - val sorted = - merged.sortedByDescending { app -> - val searchKey = - if (app.id >= 2000000000 || app.id < 0) { - app.name - } else { - app.name.replace(LIBRARY_NAME_SANITIZE_REGEX, "") - } - (allPlaytime["${searchKey}_last_played"] as? Long) ?: 0L - } - - withContext(Dispatchers.Main) { - gogByPseudoId = gogMap - epicByPseudoId = epicMap - mergedInstalledApps = merged - installedApps = sorted - if (sorted.isNotEmpty()) { - stableInstalledApps = sorted - stableGogByPseudoId = gogMap - stableEpicByPseudoId = epicMap - } - libraryLoaded = true - processedScanToken = scanInputToken - } - } - } - - LaunchedEffect(installedApps, gogByPseudoId, cachedShortcuts, iconRefreshKey) { - val appsSnapshot = installedApps - val gogSnapshot = gogByPseudoId - val shortcutsSnapshot = cachedShortcuts - - val artworkPaths = - withContext(Dispatchers.IO) { - buildMap { - appsSnapshot.forEach { app -> - val gogGame = gogSnapshot[app.id] - val isCustom = app.id < 0 - val isEpic = app.id >= 2000000000 - val epicId = if (isEpic) app.id - 2000000000 else 0 - val shortcut = - if (gogGame != null) { - shortcutsSnapshot.find { - it.getExtra("game_source") == "GOG" && it.getExtra("gog_id") == gogGame.id - } - } else { - findShortcutForGame(shortcutsSnapshot, app, isCustom, isEpic, epicId) - } - val customPath = - shortcut - ?.getExtra("customLibraryIconPath") - ?.ifBlank { shortcut.getExtra("customCoverArtPath") } - if (!customPath.isNullOrBlank() && java.io.File(customPath).exists()) { - put(app.id, customPath) - } - } - } - } - - val gridArtworkPaths = - withContext(Dispatchers.IO) { - buildMap { - appsSnapshot.forEach { app -> - val gogGame = gogSnapshot[app.id] - val isCustom = app.id < 0 - val isEpic = app.id >= 2000000000 - val epicId = if (isEpic) app.id - 2000000000 else 0 - val shortcut = - if (gogGame != null) { - shortcutsSnapshot.find { - it.getExtra("game_source") == "GOG" && it.getExtra("gog_id") == gogGame.id - } - } else { - findShortcutForGame(shortcutsSnapshot, app, isCustom, isEpic, epicId) - } - val customPath = shortcut?.getExtra(LibraryShortcutArtwork.LibraryArtworkSlot.GRID.extraKey) - if (!customPath.isNullOrBlank() && java.io.File(customPath).exists()) { - put(app.id, customPath) - } - } - } - } - - val carouselArtworkPaths = - withContext(Dispatchers.IO) { - buildMap { - appsSnapshot.forEach { app -> - val gogGame = gogSnapshot[app.id] - val isCustom = app.id < 0 - val isEpic = app.id >= 2000000000 - val epicId = if (isEpic) app.id - 2000000000 else 0 - val shortcut = - if (gogGame != null) { - shortcutsSnapshot.find { - it.getExtra("game_source") == "GOG" && it.getExtra("gog_id") == gogGame.id - } - } else { - findShortcutForGame(shortcutsSnapshot, app, isCustom, isEpic, epicId) - } - val customPath = shortcut?.getExtra(LibraryShortcutArtwork.LibraryArtworkSlot.CAROUSEL.extraKey) - if (!customPath.isNullOrBlank() && java.io.File(customPath).exists()) { - put(app.id, customPath) - } - } - } - } - - val listArtworkPaths = - withContext(Dispatchers.IO) { - buildMap { - appsSnapshot.forEach { app -> - val gogGame = gogSnapshot[app.id] - val isCustom = app.id < 0 - val isEpic = app.id >= 2000000000 - val epicId = if (isEpic) app.id - 2000000000 else 0 - val shortcut = - if (gogGame != null) { - shortcutsSnapshot.find { - it.getExtra("game_source") == "GOG" && it.getExtra("gog_id") == gogGame.id - } - } else { - findShortcutForGame(shortcutsSnapshot, app, isCustom, isEpic, epicId) - } - val customPath = shortcut?.getExtra(LibraryShortcutArtwork.LibraryArtworkSlot.LIST.extraKey) - if (!customPath.isNullOrBlank() && java.io.File(customPath).exists()) { - put(app.id, customPath) - } - } - } - } - - val customIconPaths = - withContext(Dispatchers.IO) { - buildMap { - appsSnapshot.forEach { app -> - if (app.id >= 0) return@forEach - val safeName = app.name.replace("/", "_").replace("\\", "_") - val iconFile = java.io.File(context.filesDir, "custom_icons/$safeName.png") - if (iconFile.exists()) { - put(app.id, iconFile.absolutePath) - } - } - } - } - - customArtworkPathByAppId = artworkPaths - customGridArtworkPathByAppId = gridArtworkPaths - customCarouselArtworkPathByAppId = carouselArtworkPaths - customListArtworkPathByAppId = listArtworkPaths - customIconPathByAppId = customIconPaths - if (appsSnapshot.isNotEmpty()) { - stableCustomArtworkPathByAppId = artworkPaths - stableCustomGridArtworkPathByAppId = gridArtworkPaths - stableCustomCarouselArtworkPathByAppId = carouselArtworkPaths - stableCustomListArtworkPathByAppId = listArtworkPaths - stableCustomIconPathByAppId = customIconPaths - } - } - - LaunchedEffect(mergedInstalledApps, playtimeRefreshKey) { - if (mergedInstalledApps.isEmpty()) { - installedApps = emptyList() - return@LaunchedEffect - } - - val sorted = - withContext(Dispatchers.IO) { - val playtimePrefs = context.getSharedPreferences("playtime_stats", android.content.Context.MODE_PRIVATE) - val allPlaytime = playtimePrefs.all - mergedInstalledApps.sortedByDescending { app -> - val searchKey = - if (app.id >= 2000000000 || app.id < 0) { - app.name - } else { - app.name.replace(LIBRARY_NAME_SANITIZE_REGEX, "") - } - (allPlaytime["${searchKey}_last_played"] as? Long) ?: 0L - } - } - - installedApps = sorted - } - - val awaitingShortcutScan = installedApps.isEmpty() && !shortcutsLoaded - val keepPreviousLibraryVisible = - installedApps.isEmpty() && - stableInstalledApps.isNotEmpty() && - (processedScanToken !== scanInputToken || awaitingShortcutScan) - val visibleInstalledApps = if (keepPreviousLibraryVisible) stableInstalledApps else installedApps - val visibleGogByPseudoId = if (keepPreviousLibraryVisible) stableGogByPseudoId else gogByPseudoId - val visibleEpicByPseudoId = if (keepPreviousLibraryVisible) stableEpicByPseudoId else epicByPseudoId - val visibleCustomArtworkPathByAppId = - if (keepPreviousLibraryVisible) stableCustomArtworkPathByAppId else customArtworkPathByAppId - val visibleCustomGridArtworkPathByAppId = - if (keepPreviousLibraryVisible) stableCustomGridArtworkPathByAppId else customGridArtworkPathByAppId - val visibleCustomCarouselArtworkPathByAppId = - if (keepPreviousLibraryVisible) stableCustomCarouselArtworkPathByAppId else customCarouselArtworkPathByAppId - val visibleCustomListArtworkPathByAppId = - if (keepPreviousLibraryVisible) stableCustomListArtworkPathByAppId else customListArtworkPathByAppId - val visibleCustomIconPathByAppId = - if (keepPreviousLibraryVisible) stableCustomIconPathByAppId else customIconPathByAppId - - val displayedApps = - remember(visibleInstalledApps, searchQuery) { - if (searchQuery.isBlank()) { - visibleInstalledApps - } else { - visibleInstalledApps.filter { it.name.contains(searchQuery, ignoreCase = true) } - } - } - - // The startup bootstrap screen already masks the first frame. Do not - // force an extra minimum spinner duration here or the library visibly - // bounces through two loading states on launch. - // A logged-in store whose owned-apps list is still empty hasn't finished - // its initial library fetch yet — keep the spinner up instead of flashing - // "No games installed". This resolves itself once the store populates its - // DB (steamApps/epicApps/gogApps become non-empty) or if other sources - // (custom apps, other stores) already have installed games. - val awaitingStoreSync = - installedApps.isEmpty() && ( - (isLoggedIn && steamApps.isEmpty()) || - (epicApps.isEmpty() && EpicService.hasStoredCredentials(context)) || - (gogApps.isEmpty() && GOGAuthManager.isLoggedIn(context)) - ) - // Only block the surface while the first library result is unresolved. - // After that, keep the current content/empty state visible during - // background refreshes so the UI does not flicker back to a spinner. - val initialLibraryLoadPending = !libraryLoaded - val waitingForFirstEmptyStateResolution = - installedApps.isEmpty() && (processedScanToken !== scanInputToken || awaitingStoreSync || awaitingShortcutScan) - val showLoading = initialLibraryLoadPending || waitingForFirstEmptyStateResolution - if (showLoading) { - Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - val spinAlpha by animateFloatAsState( - targetValue = 1f, - animationSpec = tween(durationMillis = 600), - label = "loaderFade", - ) - CircularProgressIndicator( - color = Accent, - strokeWidth = 3.dp, - modifier = Modifier.size(48.dp).alpha(spinAlpha), - ) - } - return - } - - if (visibleInstalledApps.isEmpty()) { - val epicLoggedIn by EpicAuthManager.isLoggedInFlow.collectAsState() - val gogLoggedIn by GOGAuthManager.isLoggedInFlow.collectAsState() - val anyLoggedIn = isLoggedIn || epicLoggedIn || gogLoggedIn - val hasAnyCredentials = - anyLoggedIn || - SteamService.hasStoredCredentials(context) || - EpicService.hasStoredCredentials(context) || - GOGAuthManager.isLoggedIn(context) - if (!anyLoggedIn && !hasAnyCredentials) { - LoginRequiredScreen("Library") { - navigateToSettings(SettingsNavItem.STORES) - } - } else if (anyLoggedIn) { - Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - EmptyStateMessage(stringResource(R.string.library_games_no_games_installed)) - } - } - return - } - - var selectedAppForSettings by remember { mutableStateOf(null) } - var selectedGogGameForSettings by remember { mutableStateOf(null) } - var detailApp by remember { mutableStateOf(null) } - var detailGogGame by remember { mutableStateOf(null) } - val gridState = rememberLazyGridState() - val carouselState = rememberLazyListState() - val activity = LocalContext.current as? UnifiedActivity - - // Pause chasing borders on library cards while any dialog is open. - LaunchedEffect(selectedAppForSettings, selectedGogGameForSettings, detailApp) { - chasingBordersPaused.value = - selectedAppForSettings != null || selectedGogGameForSettings != null || detailApp != null - } - DisposableEffect(Unit) { - onDispose { chasingBordersPaused.value = false } - } - - LaunchedEffect(layoutMode) { - currentLibraryLayoutMode = layoutMode - } - - // Keep activity's item count in sync - LaunchedEffect(displayedApps.size) { - activity?.libraryItemCount = displayedApps.size - val lastIndex = (displayedApps.size - 1).coerceAtLeast(0) - if (activity != null && displayedApps.isNotEmpty() && activity.libraryFocusIndex.value > lastIndex) { - activity.libraryFocusIndex.value = lastIndex - } - } - - // FocusRequesters for each grid item - val focusRequesters = - remember(displayedApps.size) { - List(displayedApps.size) { FocusRequester() } - } - - // Observe focus index changes from the activity and request focus on the target item - val focusIndex by (activity?.libraryFocusIndex ?: kotlinx.coroutines.flow.MutableStateFlow(0)).collectAsState() - LaunchedEffect(focusIndex, focusRequesters.size, layoutMode) { - if (searchQuery.isEmpty() && - layoutMode == LibraryLayoutMode.GRID_4 && - focusRequesters.isNotEmpty() && - focusIndex in focusRequesters.indices - ) { - gridState.animateScrollToItem(focusIndex) - try { - focusRequesters[focusIndex].requestFocus() - } catch (_: Exception) { - } - } - } - - // Track selected app for the top-right Game Settings button - LaunchedEffect(focusIndex, displayedApps) { - val app = displayedApps.getOrNull(focusIndex) ?: displayedApps.firstOrNull() - selectedSteamAppId = app?.id ?: 0 - selectedSteamAppName = app?.name ?: "" - val gogGame = app?.let { visibleGogByPseudoId[it.id] } - selectedLibrarySource = - when { - gogGame != null -> "GOG" - app == null -> "" - app.id >= 2000000000 -> "EPIC" - app.id < 0 -> "CUSTOM" - else -> "STEAM" - } - selectedGogGameId = gogGame?.id.orEmpty() - } - - val openSettingsForApp: (Int, SteamApp) -> Unit = { index, app -> - activity?.libraryFocusIndex?.value = index - selectedSteamAppId = app.id - selectedSteamAppName = app.name - val gogGame = visibleGogByPseudoId[app.id] - selectedLibrarySource = - when { - gogGame != null -> "GOG" - app.id >= 2000000000 -> "EPIC" - app.id < 0 -> "CUSTOM" - else -> "STEAM" - } - selectedGogGameId = gogGame?.id.orEmpty() - - if (gogGame != null) { - selectedGogGameForSettings = gogGame - } else { - selectedAppForSettings = app - } - } - - when (layoutMode) { - LibraryLayoutMode.GRID_4 -> { - FourByTwoGridView( - items = displayedApps, - modifier = Modifier.tabScreenPadding(), - gridState = gridState, - contentPadding = TabGridContentPadding, - clipContent = false, - keyOf = { it.id }, - ) { app, index, rowHeight -> - GameCapsule( - app = app, - gogGame = visibleGogByPseudoId[app.id], - epicGame = visibleEpicByPseudoId[app.id], - iconRefreshKey = iconRefreshKey, - isFocusedOverride = index == focusIndex, - isControllerActive = isControllerConnected, - customArtworkPath = visibleCustomGridArtworkPathByAppId[app.id] ?: visibleCustomArtworkPathByAppId[app.id], - customIconPath = visibleCustomIconPathByAppId[app.id], - onClick = { - detailGogGame = visibleGogByPseudoId[app.id] - detailApp = app - }, - onLongClick = { - openSettingsForApp(index, app) - }, - modifier = - Modifier - .height(rowHeight) - .then( - if (index in focusRequesters.indices) { - Modifier.focusRequester(focusRequesters[index]) - } else { - Modifier - }, - ), - ) - } - } - - LibraryLayoutMode.CAROUSEL -> { - CarouselView( - items = displayedApps, - modifier = Modifier.tabScreenPadding(top = TabCarouselTopPadding, bottom = TabCarouselBottomPadding), - listState = carouselState, - selectedIndex = focusIndex, - onCenteredIndexChanged = { centeredIndex -> - if (activity != null && activity.libraryFocusIndex.value != centeredIndex) { - activity.libraryFocusIndex.value = centeredIndex - } - }, - ) { app, index, isSelected, cardWidth, cardHeight -> - GameCapsule( - app = app, - gogGame = visibleGogByPseudoId[app.id], - epicGame = visibleEpicByPseudoId[app.id], - iconRefreshKey = iconRefreshKey, - isFocusedOverride = isSelected, - isControllerActive = isControllerConnected, - customArtworkPath = visibleCustomCarouselArtworkPathByAppId[app.id] ?: visibleCustomArtworkPathByAppId[app.id], - customIconPath = visibleCustomIconPathByAppId[app.id], - onClick = { - detailGogGame = visibleGogByPseudoId[app.id] - detailApp = app - }, - onLongClick = { openSettingsForApp(index, app) }, - useLibraryCapsule = true, - modifier = - Modifier - .fillMaxSize() - .then( - if (index in focusRequesters.indices) { - Modifier.focusRequester(focusRequesters[index]) - } else { - Modifier - }, - ), - ) - } - } - - LibraryLayoutMode.LIST -> { - val listViewState = rememberLazyListState() - ListView( - items = displayedApps, - modifier = Modifier.tabScreenPadding(), - listState = listViewState, - contentPadding = TabListContentPadding, - selectedIndex = focusIndex, - onSelectedIndexChanged = { newIdx -> - activity?.libraryFocusIndex?.value = newIdx - }, - keyOf = { it.id }, - ) { app, index, isSelected -> - GameCapsule( - app = app, - gogGame = visibleGogByPseudoId[app.id], - epicGame = visibleEpicByPseudoId[app.id], - iconRefreshKey = iconRefreshKey, - isFocusedOverride = isSelected, - isControllerActive = isControllerConnected, - customArtworkPath = visibleCustomListArtworkPathByAppId[app.id] ?: visibleCustomArtworkPathByAppId[app.id], - customIconPath = visibleCustomIconPathByAppId[app.id], - onClick = { - detailGogGame = visibleGogByPseudoId[app.id] - detailApp = app - }, - onLongClick = { openSettingsForApp(index, app) }, - listMode = true, - modifier = - Modifier - .then( - if (index in focusRequesters.indices) { - Modifier.focusRequester(focusRequesters[index]) - } else { - Modifier - }, - ), - ) - } - JoystickListScroll( - listState = listViewState, - stickFlow = activity?.rightStickScrollState, - minSpeed = 2.5f, - maxSpeed = 16f, - quadratic = true, - ) - } - } - - if (selectedAppForSettings != null) { - GameSettingsDialog( - app = selectedAppForSettings!!, - onDismissRequest = { selectedAppForSettings = null }, - ) - } - if (selectedGogGameForSettings != null) { - GOGGameSettingsDialog( - app = selectedGogGameForSettings!!, - onDismissRequest = { selectedGogGameForSettings = null }, - ) - } - if (detailApp != null) { - LibraryGameDetailDialog( - app = detailApp!!, - gogGame = detailGogGame, - onDismissRequest = { - detailApp = null - detailGogGame = null - }, - ) - } - } - - private enum class GameSettingsScreen { - Menu, - Shortcut, - Saves, - CloudSaves, - Uninstall, - } - - private data class HomeShortcutUiState( - val shortcut: Shortcut? = null, - val isPinned: Boolean = false, - ) - - private data class GameSettingsActionItem( - val title: String, - val icon: ImageVector, - val accentColor: Color = Accent, - val onClick: () -> Unit, - ) - - @Composable - private fun GameSettingsDialogFrame( - title: String, - onDismissRequest: () -> Unit, - wide: Boolean = false, - content: @Composable ColumnScope.() -> Unit, - ) { - Dialog( - onDismissRequest = onDismissRequest, - properties = - DialogProperties( - usePlatformDefaultWidth = false, - decorFitsSystemWindows = false, - ), - ) { - BoxWithConstraints( - modifier = - Modifier - .fillMaxSize() - .windowInsetsPadding(WindowInsets.navigationBars), - contentAlignment = Alignment.Center, - ) { - val widthModifier = - if (wide) { - Modifier.widthIn(min = 320.dp, max = (maxWidth - 32.dp).coerceAtMost(560.dp)) - } else { - Modifier.widthIn(min = 200.dp, max = 280.dp) - } - val maxContentHeight = (maxHeight - 48.dp).coerceAtLeast(320.dp) - Surface( - modifier = widthModifier.heightIn(max = maxContentHeight), - shape = RoundedCornerShape(14.dp), - color = CardDark, - border = BorderStroke(1.dp, CardBorder), - tonalElevation = 8.dp, - ) { - Column( - modifier = Modifier.padding(vertical = 6.dp), - ) { - // Title header - Text( - text = title, - modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp), - style = MaterialTheme.typography.titleSmall, - color = TextPrimary, - fontWeight = FontWeight.Bold, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - HorizontalDivider(color = CardBorder, thickness = 0.5.dp) - Column( - modifier = - Modifier - .weight(1f, fill = false) - .verticalScroll(rememberScrollState()), - ) { - content() - } - } - } - } - } - } - - @Composable - private fun GameSettingsActionGrid( - actions: List, - modifier: Modifier = Modifier, - ) { - Column(modifier = modifier) { - actions.forEachIndexed { index, action -> - if (index > 0) { - HorizontalDivider( - color = CardBorder.copy(alpha = 0.5f), - thickness = 0.5.dp, - modifier = Modifier.padding(horizontal = 16.dp), - ) - } - GameSettingsActionCard(action = action) - } - } - } - - @Composable - private fun GameSettingsActionCard( - action: GameSettingsActionItem, - modifier: Modifier = Modifier, - ) { - val isDanger = action.accentColor == DangerRed - val iconColor = if (isDanger) DangerRed else TextSecondary - val textColor = if (isDanger) DangerRed else TextPrimary - - val interactionSource = remember { MutableInteractionSource() } - val isPressed by interactionSource.collectIsPressedAsState() - val scale by animateFloatAsState( - targetValue = if (isPressed) 0.96f else 1f, - animationSpec = spring(stiffness = Spring.StiffnessMediumLow), - label = "actionCardScale", - ) - Row( - modifier = - modifier - .fillMaxWidth() - .graphicsLayer { - scaleX = scale - scaleY = scale - }.clickable( - interactionSource = interactionSource, - indication = null, - onClick = action.onClick, - ).padding(horizontal = 16.dp, vertical = 11.dp), - horizontalArrangement = Arrangement.spacedBy(12.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - Icon( - imageVector = action.icon, - contentDescription = null, - tint = iconColor, - modifier = Modifier.size(18.dp), - ) - Text( - text = action.title, - style = MaterialTheme.typography.bodyMedium, - color = textColor, - fontWeight = FontWeight.Medium, - maxLines = 1, - ) - } - } - - @Composable - private fun GameSettingsInfoCard( - message: String, - accentColor: Color = Accent, - ) { - Column( - modifier = - Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 12.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(6.dp), - ) { - Icon( - imageVector = Icons.Outlined.Warning, - contentDescription = null, - tint = accentColor.copy(alpha = 0.7f), - modifier = Modifier.size(18.dp), - ) - Text( - text = message, - style = MaterialTheme.typography.bodySmall, - color = TextSecondary, - lineHeight = 18.sp, - textAlign = TextAlign.Center, - ) - } - } - - /** - * Shared uninstall/remove confirmation UI used by GameSettingsDialog, - * GOGGameSettingsDialog, and LibraryGameDetailDialog. - */ - @Composable - private fun UninstallConfirmation( - message: String, - confirmLabel: String = stringResource(R.string.common_ui_uninstall), - onConfirm: () -> Unit, - onCancel: () -> Unit, - ) { - var isUninstalling by remember { mutableStateOf(false) } - - GameSettingsInfoCard(message = message, accentColor = DangerRed) - - if (isUninstalling) { - Box( - modifier = Modifier.fillMaxWidth().padding(vertical = 12.dp), - contentAlignment = Alignment.Center, - ) { - CircularProgressIndicator(color = DangerRed) - } - } else { - Row( - modifier = - Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 8.dp), - horizontalArrangement = Arrangement.End, - verticalAlignment = Alignment.CenterVertically, - ) { - OutlinedButton( - onClick = { - isUninstalling = true - onConfirm() - }, - border = BorderStroke(1.dp, DangerRed.copy(alpha = 0.5f)), - shape = RoundedCornerShape(8.dp), - colors = ButtonDefaults.outlinedButtonColors(contentColor = DangerRed), - ) { - Text( - confirmLabel, - style = MaterialTheme.typography.bodySmall, - fontWeight = FontWeight.Medium, - ) - } - Spacer(Modifier.width(8.dp)) - TextButton(onClick = onCancel) { - Text(stringResource(R.string.common_ui_cancel), color = TextSecondary, style = MaterialTheme.typography.bodySmall) - } - } - } - } - - @Composable - private fun ShortcutRemovalConfirmation( - message: String, - onConfirm: () -> Unit, - onCancel: () -> Unit, - ) { - var isRemoving by remember { mutableStateOf(false) } - - GameSettingsInfoCard(message = message, accentColor = DangerRed) - - if (isRemoving) { - Box( - modifier = Modifier.fillMaxWidth().padding(vertical = 12.dp), - contentAlignment = Alignment.Center, - ) { - CircularProgressIndicator(color = DangerRed) - } - } else { - Row( - modifier = - Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 8.dp), - horizontalArrangement = Arrangement.End, - verticalAlignment = Alignment.CenterVertically, - ) { - OutlinedButton( - onClick = { - isRemoving = true - onConfirm() - }, - border = BorderStroke(1.dp, DangerRed.copy(alpha = 0.5f)), - shape = RoundedCornerShape(8.dp), - colors = ButtonDefaults.outlinedButtonColors(contentColor = DangerRed), - ) { - Text( - stringResource(R.string.common_ui_remove), - style = MaterialTheme.typography.bodySmall, - fontWeight = FontWeight.Medium, - ) - } - Spacer(Modifier.width(8.dp)) - TextButton(onClick = onCancel) { - Text(stringResource(R.string.common_ui_cancel), color = TextSecondary, style = MaterialTheme.typography.bodySmall) - } - } - } - } - - // Game Settings Dialog - @Composable - private fun GameSettingsDialog( - app: SteamApp, - onDismissRequest: () -> Unit, - ) { - val context = LocalContext.current - var currentTab by remember { mutableStateOf(GameSettingsScreen.Menu) } - val scope = rememberCoroutineScope() - val isCustom = app.id < 0 - val isEpic = app.id >= 2000000000 - val epicId = if (isEpic) app.id - 2000000000 else 0 - var shortcutRefreshKey by remember(app.id, isCustom, isEpic, epicId) { mutableStateOf(0) } - var pinnedShortcutOverride by remember(app.id, isCustom, isEpic, epicId) { mutableStateOf(null) } - val epicArtworkUrl by produceState(initialValue = null, key1 = isEpic, key2 = epicId) { - value = - if (isEpic) { - val epicGame = db.epicGameDao().getById(epicId) - epicGame?.primaryImageUrl ?: epicGame?.iconUrl - } else { - null - } - } - val currentRefreshSignal = this@UnifiedActivity.libraryRefreshSignal - val homeShortcutState by produceState( - HomeShortcutUiState(), - app.id, - isCustom, - isEpic, - epicId, - currentRefreshSignal, - shortcutRefreshKey, - ) { - value = - withContext(Dispatchers.IO) { - val shortcut = findLibraryShortcutForGame(ContainerManager(context), app, isCustom, isEpic, epicId) - HomeShortcutUiState( - shortcut = shortcut, - isPinned = shortcut?.let { LibraryShortcutUtils.hasPinnedHomeShortcut(context, it) } == true, - ) - } - } - val artworkRefreshListener = - remember(app.id, isCustom, isEpic, epicId) { - object : EventDispatcher.JavaEventListener { - override fun onEvent(event: Any) { - if (event is AndroidEvent.LibraryArtworkChanged) { - shortcutRefreshKey++ - } - } - } - } - DisposableEffect(artworkRefreshListener) { - PluviaApp.events.onJava(AndroidEvent.LibraryArtworkChanged::class, artworkRefreshListener) - onDispose { - PluviaApp.events.offJava(AndroidEvent.LibraryArtworkChanged::class, artworkRefreshListener) - } - } - val hasPinnedShortcut = pinnedShortcutOverride ?: homeShortcutState.isPinned - - // Export logic - val exportLauncher = - rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument("application/zip")) { uri -> - if (uri != null) { - scope.launch(kotlinx.coroutines.Dispatchers.IO) { - try { - val os = context.contentResolver.openOutputStream(uri) ?: return@launch - val zos = java.util.zip.ZipOutputStream(java.io.BufferedOutputStream(os)) - - val containerManager = - com.winlator.cmod.runtime.container - .ContainerManager(context) - val shortcut = findLibraryShortcutForGame(containerManager, app, isCustom, isEpic, epicId) - - val dirsToZip = mutableListOf() - - // Goldberg saves: SteamService.getAppDirPath(app.id)/steam_settings/saves - val goldbergSaves = java.io.File(SteamService.getAppDirPath(app.id), "steam_settings/saves") - if (goldbergSaves.exists() && goldbergSaves.isDirectory) { - dirsToZip.add(goldbergSaves) - } - - // Also prefix documents/saved games/appdata if shortcut exists - if (shortcut != null) { - val prefixDir = java.io.File(shortcut.container.getRootDir(), ".wine/drive_c/users/xuser") - val docs = java.io.File(prefixDir, "Documents") - val savedGames = java.io.File(prefixDir, "Saved Games") - val appData = java.io.File(prefixDir, "AppData") - if (docs.exists()) dirsToZip.add(docs) - if (savedGames.exists()) dirsToZip.add(savedGames) - if (appData.exists()) dirsToZip.add(appData) - } - - // recursive zip function - fun zipDir( - dir: java.io.File, - baseName: String, - ) { - val children = dir.listFiles() ?: return - for (child in children) { - val name = if (baseName.isEmpty()) child.name else "$baseName/${child.name}" - if (child.isDirectory) { - zos.putNextEntry(java.util.zip.ZipEntry("$name/")) - zos.closeEntry() - zipDir(child, name) - } else { - zos.putNextEntry(java.util.zip.ZipEntry(name)) - val fis = java.io.FileInputStream(child) - val buf = ByteArray(1024 * 8) - var len: Int - while (fis.read(buf).also { len = it } > 0) { - zos.write(buf, 0, len) - } - fis.close() - zos.closeEntry() - } - } - } - - for (dir in dirsToZip) { - // We put them in a folder under the zip by their semantic name - val baseName = dir.name // e.g. "saves", "Documents" - zos.putNextEntry(java.util.zip.ZipEntry("$baseName/")) - zos.closeEntry() - zipDir(dir, baseName) - } - - zos.close() - withContext(kotlinx.coroutines.Dispatchers.Main) { - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - R.string.saves_import_export_exported, - android.widget.Toast.LENGTH_SHORT, - ) - onDismissRequest() - } - } catch (e: Exception) { - e.printStackTrace() - withContext(kotlinx.coroutines.Dispatchers.Main) { - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - getString(R.string.saves_import_export_exported_failed, e.message), - android.widget.Toast.LENGTH_SHORT, - ) - } - } - } - } - } - - // Import logic - val importLauncher = - rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) { uri -> - if (uri != null) { - scope.launch(kotlinx.coroutines.Dispatchers.IO) { - try { - val `is` = context.contentResolver.openInputStream(uri) ?: return@launch - val zis = java.util.zip.ZipInputStream(java.io.BufferedInputStream(`is`)) - - val containerManager = - com.winlator.cmod.runtime.container - .ContainerManager(context) - val shortcut = findLibraryShortcutForGame(containerManager, app, isCustom, isEpic, epicId) - - val goldbergSavesParent = - java.io.File( - if (isEpic) app.gameDir else SteamService.getAppDirPath(app.id), - if (isEpic) "" else "steam_settings", - ) - val prefixDir = shortcut?.let { java.io.File(it.container.getRootDir(), ".wine/drive_c/users/xuser") } - - var ze: java.util.zip.ZipEntry? - while (zis.nextEntry.also { ze = it } != null) { - val entry = ze!! - val name = entry.name - // Determine destination - var destFile: java.io.File? = null - if (name.startsWith("saves/")) { - destFile = java.io.File(goldbergSavesParent, name) - } else if (prefixDir != null) { - if (name.startsWith("Documents/") || name.startsWith("Saved Games/") || name.startsWith("AppData/")) { - destFile = java.io.File(prefixDir, name) - } - } - - if (destFile != null) { - if (entry.isDirectory) { - destFile.mkdirs() - } else { - destFile.parentFile?.mkdirs() - val fos = java.io.FileOutputStream(destFile) - val buf = ByteArray(1024 * 8) - var len: Int - while (zis.read(buf).also { len = it } > 0) { - fos.write(buf, 0, len) - } - fos.close() - } - } - zis.closeEntry() - } - zis.close() - withContext(kotlinx.coroutines.Dispatchers.Main) { - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - R.string.saves_import_export_imported, - android.widget.Toast.LENGTH_SHORT, - ) - onDismissRequest() - } - } catch (e: Exception) { - e.printStackTrace() - withContext(kotlinx.coroutines.Dispatchers.Main) { - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - getString(R.string.saves_import_export_imported_failed, e.message), - android.widget.Toast.LENGTH_SHORT, - ) - } - } - } - } - } - - GameSettingsDialogFrame( - title = app.name, - onDismissRequest = onDismissRequest, - wide = currentTab == GameSettingsScreen.CloudSaves, - ) { - when (currentTab) { - GameSettingsScreen.Menu -> { - val actions = - listOf( - GameSettingsActionItem( - title = stringResource(R.string.common_ui_settings), - icon = Icons.Outlined.Settings, - onClick = { - val containerManager = ContainerManager(context) - val shortcut = - findLibraryShortcutForGame(containerManager, app, isCustom, isEpic, epicId) - ?: if (isCustom) { - null - } else { - ShortcutSettingsComposeDialog.createLibraryShortcut( - context = context, - containerManager = containerManager, - source = if (isEpic) "EPIC" else "STEAM", - appId = if (isEpic) epicId else app.id, - gogId = null, - appName = app.name, - ) - } - if (shortcut != null) { - ShortcutSettingsComposeDialog(this@UnifiedActivity, shortcut).show() - } - onDismissRequest() - }, - ), - GameSettingsActionItem( - title = - stringResource( - if (hasPinnedShortcut) { - R.string.common_ui_remove - } else { - R.string.common_ui_shortcut - }, - ), - icon = Icons.Outlined.Home, - accentColor = if (hasPinnedShortcut) DangerRed else Accent, - onClick = { - if (hasPinnedShortcut) { - currentTab = GameSettingsScreen.Shortcut - } else { - scope.launch { - val created = - withContext(Dispatchers.IO) { - addLibraryShortcutToHomeScreen( - context, - app, - isCustom, - isEpic, - epicId, - epicArtworkUrl, - ) - } - if (created) { - pinnedShortcutOverride = true - shortcutRefreshKey++ - } - if (!created) { - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - context.getString( - R.string.library_games_failed_to_create_shortcut, - app.name, - ), - ) - } - } - } - }, - ), - GameSettingsActionItem( - title = stringResource(R.string.saves_import_export_title), - icon = Icons.Outlined.Save, - onClick = { currentTab = GameSettingsScreen.Saves }, - ), - GameSettingsActionItem( - title = stringResource(R.string.cloud_saves_title), - icon = Icons.Outlined.CloudSync, - onClick = { currentTab = GameSettingsScreen.CloudSaves }, - ), - GameSettingsActionItem( - title = - if (isCustom) { - stringResource( - R.string.common_ui_remove, - ) - } else { - stringResource(R.string.common_ui_uninstall) - }, - icon = Icons.Outlined.Delete, - accentColor = DangerRed, - onClick = { currentTab = GameSettingsScreen.Uninstall }, - ), - ) - - GameSettingsActionGrid(actions = actions) - } - - GameSettingsScreen.Shortcut -> { - ShortcutRemovalConfirmation( - message = stringResource(R.string.shortcuts_list_remove_game_shortcut_message, app.name), - onConfirm = { - scope.launch { - val removed = - withContext(Dispatchers.IO) { - homeShortcutState.shortcut?.let { - LibraryShortcutUtils.disablePinnedHomeShortcut(context, it) - } == true - } - pinnedShortcutOverride = if (removed) false else hasPinnedShortcut - shortcutRefreshKey++ - currentTab = GameSettingsScreen.Menu - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - if (removed) { - context.getString(R.string.shortcuts_list_removed) - } else { - context.getString(R.string.common_ui_unknown_error) - }, - ) - } - }, - onCancel = { currentTab = GameSettingsScreen.Menu }, - ) - } - - GameSettingsScreen.Saves -> { - GameSettingsActionGrid( - actions = - listOf( - GameSettingsActionItem( - title = stringResource(R.string.common_ui_export), - icon = Icons.Outlined.Upload, - onClick = { - exportLauncher.launch( - "${app.name.replace(" ", "_").replace(":", "")}_Saves.zip", - ) - }, - ), - GameSettingsActionItem( - title = stringResource(R.string.common_ui_import), - icon = Icons.Outlined.Download, - onClick = { importLauncher.launch(arrayOf("application/zip")) }, - ), - GameSettingsActionItem( - title = stringResource(R.string.common_ui_back), - icon = Icons.AutoMirrored.Outlined.ArrowBack, - onClick = { currentTab = GameSettingsScreen.Menu }, - ), - ), - ) - } - - GameSettingsScreen.CloudSaves -> { - var isWorking by remember { mutableStateOf(false) } - val shortcut = - remember(app.id, epicId, isCustom, isEpic) { - findLibraryShortcutForGame(ContainerManager(context), app, isCustom, isEpic, epicId) - } - var cloudSyncEnabled by remember(shortcut?.file?.absolutePath) { - mutableStateOf(isShortcutCloudSyncEnabled(shortcut)) - } - var offlineModeEnabled by remember(shortcut?.file?.absolutePath) { - mutableStateOf(isShortcutOfflineMode(shortcut)) - } - - val gameSource = - when { - isEpic -> GameSaveBackupManager.GameSource.EPIC - else -> GameSaveBackupManager.GameSource.STEAM - } - val gameIdStr = if (isEpic) epicId.toString() else app.id.toString() - val providerLabel = - when (gameSource) { - GameSaveBackupManager.GameSource.EPIC -> - stringResource(R.string.preloader_platform_epic) - else -> - stringResource(R.string.preloader_platform_steam) - } - - CloudSavesContent( - isWorking = isWorking, - cloudSyncEnabled = cloudSyncEnabled, - offlineModeEnabled = offlineModeEnabled, - gameSource = gameSource, - gameId = gameIdStr, - gameName = app.name, - shortcut = shortcut, - onCloudSyncToggle = { enabled -> - cloudSyncEnabled = enabled - setShortcutCloudSyncEnabled(shortcut, enabled) - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - if (enabled) { - context.getString(R.string.cloud_sync_enabled_summary) - } else { - context.getString(R.string.cloud_sync_disabled_summary) - }, - android.widget.Toast.LENGTH_SHORT, - ) - }, - onOfflineModeToggle = { enabled -> - offlineModeEnabled = enabled - setShortcutOfflineMode(shortcut, enabled) - }, - onBackup = { - if (!isWorking) { - isWorking = true - scope.launch { - val result = - GameSaveBackupManager.backupToGoogle( - this@UnifiedActivity, - gameSource, - gameIdStr, - app.name, - ) - isWorking = false - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - result.message, - android.widget.Toast.LENGTH_SHORT, - ) - } - } - }, - onRestore = { - if (!isWorking) { - isWorking = true - scope.launch { - val result = - GameSaveBackupManager.restoreFromGoogle( - this@UnifiedActivity, - gameSource, - gameIdStr, - app.name, - ) - isWorking = false - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - result.message, - android.widget.Toast.LENGTH_SHORT, - ) - } - } - }, - onSyncFromCloud = { - if (!isWorking) { - isWorking = true - scope.launch(Dispatchers.IO) { - val ok = - CloudSyncHelper.downloadCloudSaves( - context, - gameSource, - gameIdStr, - ) - withContext(Dispatchers.Main) { - isWorking = false - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - if (ok) { - context.getString( - R.string.cloud_saves_sync_from_provider_success, - providerLabel, - ) - } else { - context.getString( - R.string.cloud_saves_sync_from_provider_failed, - providerLabel, - ) - }, - android.widget.Toast.LENGTH_SHORT, - ) - } - } - } - }, - onBack = { currentTab = GameSettingsScreen.Menu }, - ) - } - - GameSettingsScreen.Uninstall -> { - UninstallConfirmation( - message = - if (isCustom) { - getString(R.string.library_games_remove_confirm, app.name) - } else { - getString(R.string.library_games_uninstall_confirm, app.name) - }, - confirmLabel = - if (isCustom) { - stringResource( - R.string.common_ui_remove, - ) - } else { - stringResource(R.string.common_ui_uninstall) - }, - onConfirm = { - if (isCustom) { - scope.launch(Dispatchers.IO) { - val cm = ContainerManager(context) - val sc = findLibraryShortcutForGame(cm, app, isCustom, isEpic, epicId) - sc?.let { LibraryShortcutUtils.deleteShortcutArtifacts(context, it) } - PluviaApp.events.emit(AndroidEvent.LibraryInstallStatusChanged(app.id)) - withContext(Dispatchers.Main) { - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - getString(R.string.library_games_game_removed, app.name), - android.widget.Toast.LENGTH_SHORT, - ) - onDismissRequest() - } - } - } else if (isEpic) { - scope.launch(Dispatchers.IO) { - val result = EpicService.deleteGame(context, epicId) - withContext(Dispatchers.Main) { - if (result.isSuccess) { - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - getString(R.string.library_games_game_uninstalled, app.name), - android.widget.Toast.LENGTH_SHORT, - ) - } else { - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - getString( - R.string.library_games_failed_to_uninstall_reason, - result.exceptionOrNull()?.message - ?: getString(R.string.common_ui_unknown_error), - ), - android.widget.Toast.LENGTH_LONG, - ) - } - onDismissRequest() - } - } - } else { - SteamService.uninstallApp(app.id) { success -> - if (success) { - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - getString(R.string.library_games_game_uninstalled, app.name), - android.widget.Toast.LENGTH_SHORT, - ) - } else { - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - getString(R.string.library_games_failed_to_uninstall), - android.widget.Toast.LENGTH_SHORT, - ) - } - onDismissRequest() - } - } - }, - onCancel = { currentTab = GameSettingsScreen.Menu }, - ) - } - } - } - } - - @Composable - private fun GOGGameSettingsDialog( - app: GOGGame, - onDismissRequest: () -> Unit, - ) { - val context = LocalContext.current - var currentTab by remember { mutableStateOf(GameSettingsScreen.Menu) } - val scope = rememberCoroutineScope() - var shortcutRefreshKey by remember(app.id) { mutableStateOf(0) } - var pinnedShortcutOverride by remember(app.id) { mutableStateOf(null) } - val currentRefreshSignal = this@UnifiedActivity.libraryRefreshSignal - val homeShortcutState by produceState( - HomeShortcutUiState(), - app.id, - currentRefreshSignal, - shortcutRefreshKey, - ) { - value = - withContext(Dispatchers.IO) { - val shortcut = - ContainerManager(context).loadShortcuts().find { - it.getExtra("game_source") == "GOG" && it.getExtra("gog_id") == app.id - } - HomeShortcutUiState( - shortcut = shortcut, - isPinned = shortcut?.let { LibraryShortcutUtils.hasPinnedHomeShortcut(context, it) } == true, - ) - } - } - val hasPinnedShortcut = pinnedShortcutOverride ?: homeShortcutState.isPinned - - GameSettingsDialogFrame( - title = app.title, - onDismissRequest = onDismissRequest, - wide = currentTab == GameSettingsScreen.CloudSaves, - ) { - when (currentTab) { - GameSettingsScreen.Menu -> { - GameSettingsActionGrid( - actions = - listOf( - GameSettingsActionItem( - title = stringResource(R.string.common_ui_settings), - icon = Icons.Outlined.Settings, - onClick = { - val containerManager = ContainerManager(context) - val shortcut = - containerManager.loadShortcuts().find { - it.getExtra("game_source") == "GOG" && it.getExtra("gog_id") == app.id - } ?: ShortcutSettingsComposeDialog.createLibraryShortcut( - context = context, - containerManager = containerManager, - source = "GOG", - appId = gogPseudoId(app.id), - gogId = app.id, - appName = app.title, - ) - if (shortcut != null) { - ShortcutSettingsComposeDialog(this@UnifiedActivity, shortcut).show() - } - onDismissRequest() - }, - ), - GameSettingsActionItem( - title = - stringResource( - if (hasPinnedShortcut) { - R.string.common_ui_remove - } else { - R.string.common_ui_shortcut - }, - ), - icon = Icons.Outlined.Home, - accentColor = if (hasPinnedShortcut) DangerRed else Accent, - onClick = { - if (hasPinnedShortcut) { - currentTab = GameSettingsScreen.Shortcut - } else { - scope.launch { - val artworkUrl = app.imageUrl.ifEmpty { app.iconUrl } - val created = - withContext(Dispatchers.IO) { - addGogShortcutToHomeScreen(context, app, artworkUrl) - } - if (created) { - pinnedShortcutOverride = true - shortcutRefreshKey++ - } - if (!created) { - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - context.getString( - R.string.library_games_failed_to_create_shortcut, - app.title, - ), - ) - } - } - } - }, - ), - GameSettingsActionItem( - title = stringResource(R.string.saves_import_export_title), - icon = Icons.Outlined.Save, - onClick = { currentTab = GameSettingsScreen.Saves }, - ), - GameSettingsActionItem( - title = stringResource(R.string.cloud_saves_title), - icon = Icons.Outlined.CloudSync, - onClick = { currentTab = GameSettingsScreen.CloudSaves }, - ), - GameSettingsActionItem( - title = stringResource(R.string.common_ui_uninstall), - icon = Icons.Outlined.Delete, - accentColor = DangerRed, - onClick = { currentTab = GameSettingsScreen.Uninstall }, - ), - ), - ) - } - - GameSettingsScreen.Shortcut -> { - ShortcutRemovalConfirmation( - message = stringResource(R.string.shortcuts_list_remove_game_shortcut_message, app.title), - onConfirm = { - scope.launch { - val removed = - withContext(Dispatchers.IO) { - homeShortcutState.shortcut?.let { - LibraryShortcutUtils.disablePinnedHomeShortcut(context, it) - } == true - } - pinnedShortcutOverride = if (removed) false else hasPinnedShortcut - shortcutRefreshKey++ - currentTab = GameSettingsScreen.Menu - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - if (removed) { - context.getString(R.string.shortcuts_list_removed) - } else { - context.getString(R.string.common_ui_unknown_error) - }, - android.widget.Toast.LENGTH_SHORT, - ) - } - }, - onCancel = { currentTab = GameSettingsScreen.Menu }, - ) - } - - GameSettingsScreen.Saves -> { - GameSettingsActionGrid( - actions = - listOf( - GameSettingsActionItem( - title = stringResource(R.string.common_ui_sync), - icon = Icons.Outlined.Cloud, - onClick = { - scope.launch(Dispatchers.IO) { - GOGService.syncCloudSaves(context, "GOG_${app.id}", "auto") - } - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - getString(R.string.google_cloud_sync_started), - android.widget.Toast.LENGTH_SHORT, - ) - }, - ), - GameSettingsActionItem( - title = stringResource(R.string.common_ui_back), - icon = Icons.AutoMirrored.Outlined.ArrowBack, - onClick = { currentTab = GameSettingsScreen.Menu }, - ), - ), - ) - } - - GameSettingsScreen.CloudSaves -> { - var isWorking by remember { mutableStateOf(false) } - val shortcut = - remember(app.id) { - ContainerManager(context).loadShortcuts().find { - it.getExtra("game_source") == "GOG" && it.getExtra("gog_id") == app.id - } - } - var cloudSyncEnabled by remember(shortcut?.file?.absolutePath) { - mutableStateOf(isShortcutCloudSyncEnabled(shortcut)) - } - var offlineModeEnabled by remember(shortcut?.file?.absolutePath) { - mutableStateOf(isShortcutOfflineMode(shortcut)) - } - - val gogProviderLabel = stringResource(R.string.preloader_platform_gog) - - CloudSavesContent( - isWorking = isWorking, - cloudSyncEnabled = cloudSyncEnabled, - offlineModeEnabled = offlineModeEnabled, - gameSource = GameSaveBackupManager.GameSource.GOG, - gameId = app.id, - gameName = app.title, - shortcut = shortcut, - onCloudSyncToggle = { enabled -> - cloudSyncEnabled = enabled - setShortcutCloudSyncEnabled(shortcut, enabled) - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - if (enabled) { - context.getString(R.string.cloud_sync_enabled_summary) - } else { - context.getString(R.string.cloud_sync_disabled_summary) - }, - android.widget.Toast.LENGTH_SHORT, - ) - }, - onOfflineModeToggle = { enabled -> - offlineModeEnabled = enabled - setShortcutOfflineMode(shortcut, enabled) - }, - onBackup = { - if (!isWorking) { - isWorking = true - scope.launch { - val result = - GameSaveBackupManager.backupToGoogle( - this@UnifiedActivity, - GameSaveBackupManager.GameSource.GOG, - app.id, - app.title, - ) - isWorking = false - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - result.message, - android.widget.Toast.LENGTH_SHORT, - ) - } - } - }, - onRestore = { - if (!isWorking) { - isWorking = true - scope.launch { - val result = - GameSaveBackupManager.restoreFromGoogle( - this@UnifiedActivity, - GameSaveBackupManager.GameSource.GOG, - app.id, - app.title, - ) - isWorking = false - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - result.message, - android.widget.Toast.LENGTH_SHORT, - ) - } - } - }, - onSyncFromCloud = { - if (!isWorking) { - isWorking = true - scope.launch(Dispatchers.IO) { - val ok = - CloudSyncHelper.downloadCloudSaves( - context, - GameSaveBackupManager.GameSource.GOG, - app.id, - ) - withContext(Dispatchers.Main) { - isWorking = false - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - if (ok) { - context.getString( - R.string.cloud_saves_sync_from_provider_success, - gogProviderLabel, - ) - } else { - context.getString( - R.string.cloud_saves_sync_from_provider_failed, - gogProviderLabel, - ) - }, - android.widget.Toast.LENGTH_SHORT, - ) - } - } - } - }, - onBack = { currentTab = GameSettingsScreen.Menu }, - ) - } - - GameSettingsScreen.Uninstall -> { - UninstallConfirmation( - message = getString(R.string.library_games_uninstall_confirm, app.title), - onConfirm = { - scope.launch(Dispatchers.IO) { - val result = GOGService.deleteGame( - context, - LibraryItem("GOG_${app.id}", app.title, com.winlator.cmod.feature.stores.steam.enums.GameSource.GOG), - ) - withContext(Dispatchers.Main) { - if (result.isSuccess) { - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - getString(R.string.library_games_game_uninstalled, app.title), - android.widget.Toast.LENGTH_SHORT, - ) - } else { - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - getString( - R.string.library_games_failed_to_uninstall_reason, - result.exceptionOrNull()?.message - ?: getString(R.string.common_ui_unknown_error), - ), - android.widget.Toast.LENGTH_LONG, - ) - } - onDismissRequest() - } - } - }, - onCancel = { currentTab = GameSettingsScreen.Menu }, - ) - } - } - } - } - - // Library Game Detail Dialog - - private enum class LibraryDetailScreen { Main, Shortcut, Saves, CloudSaves, Uninstall } - - @Composable - private fun LibraryGameDetailDialog( - app: SteamApp, - gogGame: GOGGame? = null, - onDismissRequest: () -> Unit, - ) { - val context = LocalContext.current - val scope = rememberCoroutineScope() - var currentScreen by remember { mutableStateOf(LibraryDetailScreen.Main) } - var shortcutRefreshKey by remember(app.id, gogGame?.id) { mutableStateOf(0) } - var pinnedShortcutOverride by remember(app.id, gogGame?.id) { mutableStateOf(null) } - - val isCustom = app.id < 0 - val isEpic = app.id >= 2000000000 - val isGog = gogGame != null - val epicId = if (isEpic) app.id - 2000000000 else 0 - - val epicGame by produceState(initialValue = null, key1 = epicId) { - value = if (isEpic) db.epicGameDao().getById(epicId) else null - } - - val epicArtworkUrl by produceState(initialValue = null, key1 = isEpic, key2 = epicId) { - value = - if (isEpic) { - val eg = db.epicGameDao().getById(epicId) - eg?.primaryImageUrl ?: eg?.iconUrl - } else { - null - } - } - val currentRefreshSignal = this@UnifiedActivity.libraryRefreshSignal - val homeShortcutState by produceState( - HomeShortcutUiState(), - app.id, - gogGame?.id, - isCustom, - isEpic, - isGog, - epicId, - currentRefreshSignal, - shortcutRefreshKey, - ) { - value = - withContext(Dispatchers.IO) { - val shortcut = - when { - isGog -> { - ContainerManager(context).loadShortcuts().find { - it.getExtra("game_source") == "GOG" && it.getExtra("gog_id") == gogGame!!.id - } - } - - else -> { - findLibraryShortcutForGame(ContainerManager(context), app, isCustom, isEpic, epicId) - } - } - HomeShortcutUiState( - shortcut = shortcut, - isPinned = shortcut?.let { LibraryShortcutUtils.hasPinnedHomeShortcut(context, it) } == true, - ) - } - } - val artworkRefreshListener = - remember(app.id, gogGame?.id) { - object : EventDispatcher.JavaEventListener { - override fun onEvent(event: Any) { - if (event is AndroidEvent.LibraryArtworkChanged) { - shortcutRefreshKey++ - } - } - } - } - DisposableEffect(artworkRefreshListener) { - PluviaApp.events.onJava(AndroidEvent.LibraryArtworkChanged::class, artworkRefreshListener) - onDispose { - PluviaApp.events.offJava(AndroidEvent.LibraryArtworkChanged::class, artworkRefreshListener) - } - } - val hasPinnedShortcut = pinnedShortcutOverride ?: homeShortcutState.isPinned - - // Hero image - val customHeroImageFile = - homeShortcutState.shortcut - ?.getExtra("customLibraryHeroArtPath") - ?.takeIf { it.isNotBlank() } - ?.let { java.io.File(it) } - ?.takeIf { it.exists() } - val customHeroImageCacheKey = - customHeroImageFile?.let { - "library_custom_hero:${it.absolutePath}:${it.lastModified()}" - } - val heroImageUrl: Any? = - customHeroImageFile ?: when { - isGog -> { - gogGame!!.imageUrl.ifEmpty { gogGame.iconUrl } - } - - isEpic -> { - epicGame?.primaryImageUrl ?: epicGame?.iconUrl - } - - isCustom -> { - val customCoverArt = - homeShortcutState.shortcut - ?.getExtra("customCoverArtPath") - ?.takeIf { it.isNotBlank() } - ?.let { java.io.File(it) } - ?.takeIf { it.exists() } - customCoverArt ?: run { - val safeName = app.name.replace("/", "_").replace("\\", "_") - val iconFile = java.io.File(context.filesDir, "custom_icons/$safeName.png") - if (iconFile.exists()) iconFile else null - } - } - - else -> { - app.getHeroUrl() - } - } - - val subtitle = - when { - isGog -> { - gogGame!!.developer - } - - isCustom -> { - stringResource(R.string.library_games_custom_game) - } - - isEpic -> { - epicGame?.developer ?: "" - } - - else -> { - listOfNotNull( - app.developer.takeIf { it.isNotBlank() }, - app.publisher.takeIf { it.isNotBlank() }, - ).joinToString(" • ") - } - } - - // Playtime info - val playtimePrefs = - remember { - context.getSharedPreferences("playtime_stats", android.content.Context.MODE_PRIVATE) - } - val searchKey = - remember(app) { - if (app.id >= 2000000000 || app.id < 0) { - app.name - } else { - app.name.replace(LIBRARY_NAME_SANITIZE_REGEX, "") - } - } - val lastPlayed = playtimePrefs.getLong("${searchKey}_last_played", 0L) - val totalPlaytime = playtimePrefs.getLong("${searchKey}_playtime", 0L) - val playCount = playtimePrefs.getInt("${searchKey}_play_count", 0) - - val sourceLabel = - when { - isGog -> "GOG" - isEpic -> "Epic Games" - isCustom -> "Custom" - else -> "Steam" - } - - // Install path - val installPath = - remember(app, gogGame) { - when { - isGog -> { - gogGame!!.installPath - } - - isEpic -> { - epicGame?.installPath ?: "" - } - - isCustom -> { - app.gameDir - } - - else -> { - try { - SteamService.getAppDirPath(app.id) - } catch (_: Exception) { - "" - } - } - } - } - - // Install size (computed async) - val installSizeText by produceState(initialValue = null, key1 = installPath) { - value = - if (installPath.isNotBlank()) { - withContext(Dispatchers.IO) { - try { - val bytes = StorageUtils.getFolderSize(installPath) - if (bytes > 0) StorageUtils.formatBinarySize(bytes) else null - } catch (_: Exception) { - null - } - } - } else { - null - } - } - - // Export / Import launchers (reuse GameSettingsDialog pattern) - - val exportLauncher = - rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument("application/zip")) { uri -> - if (uri != null) { - scope.launch(Dispatchers.IO) { - try { - val os = context.contentResolver.openOutputStream(uri) ?: return@launch - val zos = java.util.zip.ZipOutputStream(java.io.BufferedOutputStream(os)) - val containerManager = ContainerManager(context) - val shortcut = findLibraryShortcutForGame(containerManager, app, isCustom, isEpic, epicId) - val dirsToZip = mutableListOf() - val goldbergSaves = java.io.File(SteamService.getAppDirPath(app.id), "steam_settings/saves") - if (goldbergSaves.exists() && goldbergSaves.isDirectory) dirsToZip.add(goldbergSaves) - if (shortcut != null) { - val prefixDir = java.io.File(shortcut.container.getRootDir(), ".wine/drive_c/users/xuser") - listOf("Documents", "Saved Games", "AppData").forEach { name -> - val dir = java.io.File(prefixDir, name) - if (dir.exists()) dirsToZip.add(dir) - } - } - - fun zipDir( - dir: java.io.File, - baseName: String, - ) { - val children = dir.listFiles() ?: return - for (child in children) { - val name = if (baseName.isEmpty()) child.name else "$baseName/${child.name}" - if (child.isDirectory) { - zos.putNextEntry(java.util.zip.ZipEntry("$name/")) - zos.closeEntry() - zipDir(child, name) - } else { - zos.putNextEntry(java.util.zip.ZipEntry(name)) - child.inputStream().use { it.copyTo(zos) } - zos.closeEntry() - } - } - } - for (dir in dirsToZip) { - zos.putNextEntry(java.util.zip.ZipEntry("${dir.name}/")) - zos.closeEntry() - zipDir(dir, dir.name) - } - zos.close() - withContext(Dispatchers.Main) { - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - R.string.saves_import_export_exported, - android.widget.Toast.LENGTH_SHORT, - ) - } - } catch (e: Exception) { - e.printStackTrace() - withContext(Dispatchers.Main) { - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - getString(R.string.saves_import_export_exported_failed, e.message), - android.widget.Toast.LENGTH_SHORT, - ) - } - } - } - } - } - - val importLauncher = - rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) { uri -> - if (uri != null) { - scope.launch(Dispatchers.IO) { - try { - val inputStream = context.contentResolver.openInputStream(uri) ?: return@launch - val zis = java.util.zip.ZipInputStream(java.io.BufferedInputStream(inputStream)) - val containerManager = ContainerManager(context) - val shortcut = findLibraryShortcutForGame(containerManager, app, isCustom, isEpic, epicId) - val goldbergSavesParent = - java.io.File( - if (isEpic) app.gameDir else SteamService.getAppDirPath(app.id), - if (isEpic) "" else "steam_settings", - ) - val prefixDir = shortcut?.let { java.io.File(it.container.getRootDir(), ".wine/drive_c/users/xuser") } - var ze: java.util.zip.ZipEntry? - while (zis.nextEntry.also { ze = it } != null) { - val entry = ze!! - val name = entry.name - var destFile: java.io.File? = null - if (name.startsWith("saves/")) { - destFile = java.io.File(goldbergSavesParent, name) - } else if (prefixDir != null && - (name.startsWith("Documents/") || name.startsWith("Saved Games/") || name.startsWith("AppData/")) - ) { - destFile = java.io.File(prefixDir, name) - } - if (destFile != null) { - if (entry.isDirectory) { - destFile.mkdirs() - } else { - destFile.parentFile?.mkdirs() - java.io.FileOutputStream(destFile).use { fos -> zis.copyTo(fos) } - } - } - zis.closeEntry() - } - zis.close() - withContext(Dispatchers.Main) { - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - R.string.saves_import_export_imported, - android.widget.Toast.LENGTH_SHORT, - ) - } - } catch (e: Exception) { - e.printStackTrace() - withContext(Dispatchers.Main) { - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - getString(R.string.saves_import_export_imported_failed, e.message), - android.widget.Toast.LENGTH_SHORT, - ) - } - } - } - } - } - - Dialog( - onDismissRequest = onDismissRequest, - properties = DialogProperties(usePlatformDefaultWidth = false), - ) { - Surface( - modifier = Modifier.fillMaxWidth(0.864f).fillMaxHeight(0.96f), - shape = RoundedCornerShape(20.dp), - color = CardDark, - ) { - Box(Modifier.fillMaxSize()) { - Column(Modifier.fillMaxSize()) { - val showHero = currentScreen == LibraryDetailScreen.Main - val subScreenTitle = - when (currentScreen) { - LibraryDetailScreen.CloudSaves -> stringResource(R.string.cloud_saves_title) - LibraryDetailScreen.Saves -> stringResource(R.string.saves_import_export_title) - LibraryDetailScreen.Shortcut -> stringResource(R.string.common_ui_shortcut) - LibraryDetailScreen.Uninstall -> - stringResource( - if (isCustom) R.string.common_ui_remove else R.string.common_ui_uninstall, - ) - else -> "" - } - // Hero image section — only on the main screen. Sub-screens get a compact - // title bar so buttons/content can take the full dialog height. - if (showHero) { - Box( - modifier = Modifier.fillMaxWidth().fillMaxHeight(0.38f), - ) { - if (heroImageUrl != null) { - AsyncImage( - model = - ImageRequest - .Builder(context) - .data(heroImageUrl) - .apply { - if (customHeroImageCacheKey != null) { - memoryCacheKey(customHeroImageCacheKey) - diskCacheKey(customHeroImageCacheKey) - } - }.crossfade(150) - .memoryCachePolicy(coil.request.CachePolicy.ENABLED) - .diskCachePolicy(coil.request.CachePolicy.ENABLED) - .build(), - contentDescription = "${app.name} artwork", - modifier = Modifier.fillMaxSize(), - contentScale = ContentScale.FillWidth, - alignment = Alignment.TopCenter, - ) - } else { - Box( - Modifier.fillMaxSize().background(SurfaceDark), - contentAlignment = Alignment.Center, - ) { - Icon( - Icons.Outlined.SportsEsports, - contentDescription = null, - tint = Accent.copy(alpha = 0.4f), - modifier = Modifier.size(72.dp), - ) - } - } - Box( - modifier = - Modifier.fillMaxSize().background( - Brush.verticalGradient( - colorStops = - arrayOf( - 0.0f to Color.Transparent, - 0.45f to Color.Transparent, - 0.72f to CardDark.copy(alpha = 0.72f), - 1.0f to CardDark, - ), - ), - ), - ) - Column( - modifier = - Modifier - .align(Alignment.BottomStart) - .padding(start = 24.dp, end = 80.dp, bottom = 36.dp), - ) { - Text( - app.name, - style = MaterialTheme.typography.headlineMedium, - color = TextPrimary, - fontWeight = FontWeight.Bold, - ) - if (subtitle.isNotBlank()) { - Spacer(Modifier.height(8.dp)) - Text( - subtitle, - style = MaterialTheme.typography.bodyMedium, - color = TextSecondary, - maxLines = 2, - overflow = TextOverflow.Ellipsis, - ) - } - } - } - } else { - Row( - modifier = - Modifier - .fillMaxWidth() - .background(SurfaceDark) - .padding(horizontal = 8.dp, vertical = 8.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - IconButton(onClick = { currentScreen = LibraryDetailScreen.Main }) { - Icon( - Icons.AutoMirrored.Outlined.ArrowBack, - contentDescription = stringResource(R.string.common_ui_back), - tint = TextPrimary, - ) - } - Text( - subScreenTitle, - style = MaterialTheme.typography.titleMedium, - color = TextPrimary, - fontWeight = FontWeight.Bold, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.weight(1f).padding(start = 4.dp), - ) - Text( - app.name, - style = MaterialTheme.typography.bodySmall, - color = TextSecondary, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.padding(end = 16.dp), - ) - } - HorizontalDivider(color = CardBorder, thickness = 0.5.dp) - } - - // Bottom content - when (currentScreen) { - LibraryDetailScreen.Main -> { - Row( - modifier = - Modifier - .fillMaxSize() - .padding(horizontal = 24.dp, vertical = 10.dp), - horizontalArrangement = Arrangement.spacedBy(14.dp), - ) { - // Left: Game details as individual cards - Column( - modifier = Modifier.weight(1f).fillMaxHeight(), - verticalArrangement = Arrangement.spacedBy(6.dp, Alignment.Bottom), - ) { - // Source badge row - Surface( - color = Accent.copy(alpha = 0.15f), - shape = RoundedCornerShape(8.dp), - ) { - Text( - sourceLabel, - modifier = Modifier.padding(horizontal = 10.dp, vertical = 4.dp), - color = Accent, - fontSize = 12.sp, - fontWeight = FontWeight.Bold, - ) - } - - if (installPath.isNotBlank() || installSizeText != null) { - Row( - modifier = Modifier.fillMaxWidth().height(IntrinsicSize.Min), - horizontalArrangement = Arrangement.spacedBy(6.dp), - ) { - if (installPath.isNotBlank()) { - DetailCard( - label = stringResource(R.string.library_games_install_path), - value = installPath, - modifier = Modifier.weight(1f).fillMaxHeight(), - ) - } - if (installSizeText != null) { - DetailCard( - stringResource(R.string.common_ui_size), - installSizeText!!, - modifier = Modifier.fillMaxHeight(), - ) - } - } - } - - // Release date card - if (app.releaseDate > 0L) { - val releaseDateText = - remember(app.releaseDate) { - java.text - .SimpleDateFormat("MMM d, yyyy", java.util.Locale.getDefault()) - .format(java.util.Date(app.releaseDate * 1000L)) - } - DetailCard(stringResource(R.string.common_ui_release_date), releaseDateText) - } - } - - // Right: Compact action buttons - Column( - modifier = - Modifier - .widthIn(min = 200.dp, max = 260.dp) - .fillMaxHeight(), - verticalArrangement = Arrangement.spacedBy(6.dp, Alignment.Bottom), - ) { - // Play button — animated gradient - PlayButton(onClick = { - val containerManager = ContainerManager(context) - if (isCustom) { - launchCustomGame(context, containerManager, app.name) - } else if (isGog) { - launchGogGame(context, containerManager, gogGame!!) - } else if (isEpic) { - epicGame?.let { launchEpicGame(context, containerManager, it) } - } else { - launchSteamGame(context, containerManager, app) - } - onDismissRequest() - }) - - // Settings + Shortcut — half width each - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(6.dp), - ) { - CompactActionButton( - icon = Icons.Outlined.Settings, - label = stringResource(R.string.common_ui_settings), - modifier = Modifier.weight(1f), - onClick = { - val containerManager = ContainerManager(context) - val shortcut: com.winlator.cmod.runtime.container.Shortcut? = - when { - isGog -> { - containerManager.loadShortcuts().find { - it.getExtra("game_source") == "GOG" && - it.getExtra("gog_id") == gogGame!!.id - } ?: ShortcutSettingsComposeDialog.createLibraryShortcut( - context = context, - containerManager = containerManager, - source = "GOG", - appId = gogPseudoId(gogGame!!.id), - gogId = gogGame.id, - appName = app.name, - ) - } - - isCustom -> { - findLibraryShortcutForGame(containerManager, app, isCustom, isEpic, epicId) - } - - else -> { - findLibraryShortcutForGame(containerManager, app, isCustom, isEpic, epicId) - ?: ShortcutSettingsComposeDialog.createLibraryShortcut( - context = context, - containerManager = containerManager, - source = if (isEpic) "EPIC" else "STEAM", - appId = if (isEpic) epicId else app.id, - gogId = null, - appName = app.name, - ) - } - } - if (shortcut != null) { - // Layer the settings dialog on top; keep the detail dialog open underneath. - ShortcutSettingsComposeDialog(this@UnifiedActivity, shortcut).show() - } - }, - ) - - CompactActionButton( - icon = Icons.Outlined.Home, - label = - stringResource( - if (hasPinnedShortcut) { - R.string.common_ui_remove - } else { - R.string.common_ui_shortcut - }, - ), - tint = if (hasPinnedShortcut) DangerRed else TextPrimary, - bgColor = if (hasPinnedShortcut) DangerRed.copy(alpha = 0.12f) else SurfaceDark, - modifier = Modifier.weight(1f), - onClick = { - if (hasPinnedShortcut) { - currentScreen = LibraryDetailScreen.Shortcut - } else { - scope.launch { - val created = - withContext(Dispatchers.IO) { - if (isGog) { - val artworkUrl = gogGame!!.imageUrl.ifEmpty { gogGame.iconUrl } - addGogShortcutToHomeScreen(context, gogGame, artworkUrl) - } else { - addLibraryShortcutToHomeScreen( - context, - app, - isCustom, - isEpic, - epicId, - epicArtworkUrl, - ) - } - } - if (created) { - pinnedShortcutOverride = true - shortcutRefreshKey++ - } - if (!created) { - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - context.getString( - R.string.library_games_failed_to_create_shortcut, - app.name, - ), - ) - } - } - } - }, - ) - } - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(6.dp), - ) { - CompactActionButton( - icon = Icons.Outlined.Save, - label = stringResource(R.string.saves_import_export_title), - modifier = Modifier.weight(1f), - onClick = { currentScreen = LibraryDetailScreen.Saves }, - ) - - CompactActionButton( - icon = Icons.Outlined.CloudSync, - label = stringResource(R.string.cloud_saves_title), - modifier = Modifier.weight(1f), - onClick = { currentScreen = LibraryDetailScreen.CloudSaves }, - ) - } - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(6.dp), - ) { - CompactActionButton( - icon = Icons.Outlined.Delete, - label = - if (isCustom) { - stringResource( - R.string.common_ui_remove, - ) - } else { - stringResource(R.string.common_ui_uninstall) - }, - tint = DangerRed, - bgColor = DangerRed.copy(alpha = 0.12f), - modifier = Modifier.weight(1f), - onClick = { currentScreen = LibraryDetailScreen.Uninstall }, - ) - } - } - } - } - - LibraryDetailScreen.Shortcut -> { - Column( - modifier = - Modifier - .fillMaxSize() - .padding(horizontal = 24.dp, vertical = 20.dp), - verticalArrangement = Arrangement.spacedBy(12.dp), - ) { - Text( - stringResource(R.string.common_ui_shortcut), - style = MaterialTheme.typography.labelMedium, - color = TextSecondary, - fontWeight = FontWeight.Bold, - letterSpacing = 1.1.sp, - ) - - Spacer(Modifier.weight(1f)) - - ShortcutRemovalConfirmation( - message = - stringResource( - R.string.shortcuts_list_remove_game_shortcut_message, - if (isGog) gogGame!!.title else app.name, - ), - onConfirm = { - scope.launch { - val removed = - withContext(Dispatchers.IO) { - homeShortcutState.shortcut?.let { - LibraryShortcutUtils.disablePinnedHomeShortcut(context, it) - } == true - } - pinnedShortcutOverride = if (removed) false else hasPinnedShortcut - shortcutRefreshKey++ - currentScreen = LibraryDetailScreen.Main - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - if (removed) { - context.getString(R.string.shortcuts_list_removed) - } else { - context.getString(R.string.common_ui_unknown_error) - }, - ) - } - }, - onCancel = { currentScreen = LibraryDetailScreen.Main }, - ) - } - } - - LibraryDetailScreen.Saves -> { - Column( - modifier = - Modifier - .fillMaxSize() - .padding(horizontal = 24.dp, vertical = 20.dp), - verticalArrangement = Arrangement.spacedBy(12.dp), - ) { - Text( - stringResource(R.string.library_games_save_management), - style = MaterialTheme.typography.labelMedium, - color = TextSecondary, - fontWeight = FontWeight.Bold, - letterSpacing = 1.1.sp, - ) - - if (isGog) { - GameSettingsActionGrid( - actions = - listOf( - GameSettingsActionItem( - title = stringResource(R.string.common_ui_sync), - icon = Icons.Outlined.Cloud, - onClick = { - scope.launch(Dispatchers.IO) { - GOGService.syncCloudSaves(context, "GOG_${gogGame!!.id}", "auto") - } - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - getString(R.string.google_cloud_sync_started), - android.widget.Toast.LENGTH_SHORT, - ) - }, - ), - GameSettingsActionItem( - title = stringResource(R.string.common_ui_export), - icon = Icons.Outlined.Upload, - onClick = { - exportLauncher.launch( - "${app.name.replace(" ", "_").replace(":", "")}_Saves.zip", - ) - }, - ), - GameSettingsActionItem( - title = stringResource(R.string.common_ui_import), - icon = Icons.Outlined.Download, - onClick = { importLauncher.launch(arrayOf("application/zip")) }, - ), - ), - ) - } else { - GameSettingsActionGrid( - actions = - listOf( - GameSettingsActionItem( - title = stringResource(R.string.common_ui_export), - icon = Icons.Outlined.Upload, - onClick = { - exportLauncher.launch( - "${app.name.replace(" ", "_").replace(":", "")}_Saves.zip", - ) - }, - ), - GameSettingsActionItem( - title = stringResource(R.string.common_ui_import), - icon = Icons.Outlined.Download, - onClick = { importLauncher.launch(arrayOf("application/zip")) }, - ), - ), - ) - } - - Spacer(Modifier.weight(1f)) - TextButton(onClick = { currentScreen = LibraryDetailScreen.Main }) { - Icon( - Icons.AutoMirrored.Outlined.ArrowBack, - contentDescription = null, - tint = TextSecondary, - modifier = Modifier.size(18.dp), - ) - Spacer(Modifier.width(6.dp)) - Text(stringResource(R.string.common_ui_back), color = TextSecondary) - } - } - } - - LibraryDetailScreen.CloudSaves -> { - Column( - modifier = - Modifier - .fillMaxSize() - .verticalScroll(rememberScrollState()), - ) { - var isWorking by remember { mutableStateOf(false) } - - val detailGameSource = - when { - isGog -> GameSaveBackupManager.GameSource.GOG - isEpic -> GameSaveBackupManager.GameSource.EPIC - else -> GameSaveBackupManager.GameSource.STEAM - } - val detailGameId = - when { - isGog -> gogGame!!.id - isEpic -> epicId.toString() - else -> app.id.toString() - } - val detailShortcut = - remember(app.id, gogGame?.id, epicId, isGog, isEpic, isCustom) { - val containerManager = ContainerManager(context) - when { - isGog -> { - containerManager.loadShortcuts().find { - it.getExtra("game_source") == "GOG" && it.getExtra("gog_id") == gogGame!!.id - } - } - - else -> { - findLibraryShortcutForGame(containerManager, app, isCustom, isEpic, epicId) - } - } - } - var cloudSyncEnabled by remember(detailShortcut?.file?.absolutePath) { - mutableStateOf(isShortcutCloudSyncEnabled(detailShortcut)) - } - var offlineModeEnabled by remember(detailShortcut?.file?.absolutePath) { - mutableStateOf(isShortcutOfflineMode(detailShortcut)) - } - - val detailProviderLabel = - when (detailGameSource) { - GameSaveBackupManager.GameSource.GOG -> - stringResource(R.string.preloader_platform_gog) - GameSaveBackupManager.GameSource.EPIC -> - stringResource(R.string.preloader_platform_epic) - GameSaveBackupManager.GameSource.STEAM -> - stringResource(R.string.preloader_platform_steam) - } - - CloudSavesContent( - isWorking = isWorking, - cloudSyncEnabled = cloudSyncEnabled, - offlineModeEnabled = offlineModeEnabled, - gameSource = detailGameSource, - gameId = detailGameId, - gameName = app.name, - shortcut = detailShortcut, - onCloudSyncToggle = { enabled -> - cloudSyncEnabled = enabled - setShortcutCloudSyncEnabled(detailShortcut, enabled) - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - if (enabled) { - context.getString(R.string.cloud_sync_enabled_summary) - } else { - context.getString(R.string.cloud_sync_disabled_summary) - }, - android.widget.Toast.LENGTH_SHORT, - ) - }, - onOfflineModeToggle = { enabled -> - offlineModeEnabled = enabled - setShortcutOfflineMode(detailShortcut, enabled) - }, - onBackup = { - if (!isWorking) { - isWorking = true - scope.launch { - val result = - GameSaveBackupManager.backupToGoogle( - this@UnifiedActivity, - detailGameSource, - detailGameId, - app.name, - ) - isWorking = false - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - result.message, - android.widget.Toast.LENGTH_SHORT, - ) - } - } - }, - onRestore = { - if (!isWorking) { - isWorking = true - scope.launch { - val result = - GameSaveBackupManager.restoreFromGoogle( - this@UnifiedActivity, - detailGameSource, - detailGameId, - app.name, - ) - isWorking = false - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - result.message, - android.widget.Toast.LENGTH_SHORT, - ) - } - } - }, - onSyncFromCloud = { - if (!isWorking) { - isWorking = true - scope.launch(Dispatchers.IO) { - val ok = - CloudSyncHelper.downloadCloudSaves( - context, - detailGameSource, - detailGameId, - ) - withContext(Dispatchers.Main) { - isWorking = false - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - if (ok) { - context.getString( - R.string.cloud_saves_sync_from_provider_success, - detailProviderLabel, - ) - } else { - context.getString( - R.string.cloud_saves_sync_from_provider_failed, - detailProviderLabel, - ) - }, - android.widget.Toast.LENGTH_SHORT, - ) - } - } - } - }, - onBack = { currentScreen = LibraryDetailScreen.Main }, - ) - } - } - - LibraryDetailScreen.Uninstall -> { - Column( - modifier = - Modifier - .fillMaxSize() - .padding(horizontal = 24.dp, vertical = 20.dp), - verticalArrangement = Arrangement.spacedBy(12.dp), - ) { - Text( - stringResource( - if (isCustom) R.string.library_games_remove_game else R.string.library_games_uninstall_game, - ), - style = MaterialTheme.typography.labelMedium, - color = TextSecondary, - fontWeight = FontWeight.Bold, - letterSpacing = 1.1.sp, - ) - - Spacer(Modifier.weight(1f)) - - UninstallConfirmation( - message = - if (isCustom) { - getString(R.string.library_games_remove_confirm, app.name) - } else { - getString(R.string.library_games_uninstall_confirm, app.name) - }, - confirmLabel = - stringResource( - if (isCustom) R.string.common_ui_remove else R.string.common_ui_uninstall, - ), - onConfirm = { - if (isGog) { - scope.launch(Dispatchers.IO) { - val result = GOGService.deleteGame( - context, - LibraryItem( - "GOG_${gogGame!!.id}", - gogGame.title, - com.winlator.cmod.feature.stores.steam.enums.GameSource.GOG, - ), - ) - withContext(Dispatchers.Main) { - if (result.isSuccess) { - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - getString(R.string.library_games_game_uninstalled, app.name), - android.widget.Toast.LENGTH_SHORT, - ) - } else { - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - getString( - R.string.library_games_failed_to_uninstall_reason, - result.exceptionOrNull()?.message - ?: getString(R.string.common_ui_unknown_error), - ), - android.widget.Toast.LENGTH_LONG, - ) - } - onDismissRequest() - } - } - } else if (isCustom) { - scope.launch(Dispatchers.IO) { - val cm = ContainerManager(context) - val sc = findLibraryShortcutForGame(cm, app, isCustom, isEpic, epicId) - sc?.let { LibraryShortcutUtils.deleteShortcutArtifacts(context, it) } - java.io - .File( - context.filesDir, - "custom_icons/${app.name.replace("/", "_")}.png", - ).delete() - PluviaApp.events.emit(AndroidEvent.LibraryInstallStatusChanged(app.id)) - withContext(Dispatchers.Main) { - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - getString(R.string.library_games_game_removed, app.name), - android.widget.Toast.LENGTH_SHORT, - ) - onDismissRequest() - } - } - } else if (isEpic) { - scope.launch(Dispatchers.IO) { - val result = EpicService.deleteGame(context, epicId) - withContext(Dispatchers.Main) { - if (result.isSuccess) { - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - getString(R.string.library_games_game_uninstalled, app.name), - android.widget.Toast.LENGTH_SHORT, - ) - } else { - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - getString( - R.string.library_games_failed_to_uninstall_reason, - result.exceptionOrNull()?.message ?: "", - ), - android.widget.Toast.LENGTH_LONG, - ) - } - onDismissRequest() - } - } - } else { - SteamService.uninstallApp(app.id) { success -> - if (success) { - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - getString(R.string.library_games_game_uninstalled, app.name), - android.widget.Toast.LENGTH_SHORT, - ) - } else { - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - getString(R.string.library_games_failed_to_uninstall), - android.widget.Toast.LENGTH_SHORT, - ) - } - onDismissRequest() - } - } - }, - onCancel = { currentScreen = LibraryDetailScreen.Main }, - ) - } - } - } - } - - // Close button overlay - IconButton( - onClick = onDismissRequest, - modifier = - Modifier - .align(Alignment.TopEnd) - .padding(16.dp) - .size(42.dp) - .shadow(8.dp, CircleShape, spotColor = Color.Black.copy(alpha = 0.35f)) - .clip(CircleShape) - .background(BgDark.copy(alpha = 0.7f)), - ) { - Icon(Icons.Outlined.Close, contentDescription = "Close", tint = TextPrimary) - } - } - } - } - } - - @Composable - private fun DetailCard( - label: String, - value: String, - modifier: Modifier = Modifier.fillMaxWidth(), - valueColor: Color? = null, - onClick: (() -> Unit)? = null, - ) { - Surface( - modifier = - modifier - .then(if (onClick != null) Modifier.clip(RoundedCornerShape(10.dp)).clickable(onClick = onClick) else Modifier), - color = SurfaceDark, - shape = RoundedCornerShape(10.dp), - border = BorderStroke(1.dp, if (onClick != null) Accent.copy(alpha = 0.25f) else CardBorder), - ) { - Column( - modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp), - verticalArrangement = Arrangement.spacedBy(1.dp), - ) { - Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp)) { - Text( - label.uppercase(), - style = MaterialTheme.typography.labelSmall, - color = TextSecondary, - fontWeight = FontWeight.Bold, - letterSpacing = 0.8.sp, - fontSize = 10.sp, - ) - if (onClick != null) { - Icon( - Icons.AutoMirrored.Outlined.OpenInNew, - contentDescription = null, - modifier = Modifier.size(10.dp), - tint = Accent.copy(alpha = 0.6f), - ) - } - } - Text( - value, - style = MaterialTheme.typography.bodySmall, - color = valueColor ?: (if (onClick != null) Accent else TextPrimary), - maxLines = 2, - overflow = TextOverflow.Ellipsis, - ) - } - } - } - - @Composable - private fun PlayButton(onClick: () -> Unit) { - val interactionSource = remember { MutableInteractionSource() } - val isPressed by interactionSource.collectIsPressedAsState() - val scale by animateFloatAsState( - targetValue = if (isPressed) 0.92f else 1f, - animationSpec = spring(dampingRatio = 0.5f, stiffness = 600f), - label = "playScale", - ) - - // Idle glow pulse - val infiniteTransition = rememberInfiniteTransition(label = "playGlow") - val glowPulse by infiniteTransition.animateFloat( - initialValue = 0.3f, - targetValue = 0.6f, - animationSpec = - infiniteRepeatable( - animation = tween(1200, easing = FastOutSlowInEasing), - repeatMode = RepeatMode.Reverse, - ), - label = "playPulse", - ) - - val baseGradient = - Brush.horizontalGradient( - colors = - listOf( - Color(0xFF00B4D8), - Accent, - Color(0xFF7B2FF7), - ), - ) - - Box( - modifier = - Modifier - .fillMaxWidth() - .height(44.dp) - .graphicsLayer { - scaleX = scale - scaleY = scale - }.shadow( - elevation = 12.dp, - shape = RoundedCornerShape(12.dp), - ambientColor = Accent.copy(alpha = glowPulse), - spotColor = Accent.copy(alpha = glowPulse), - ).clip(RoundedCornerShape(12.dp)) - .background(baseGradient) - .clickable( - interactionSource = interactionSource, - indication = null, - onClick = onClick, - ), - contentAlignment = Alignment.Center, - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Center, - ) { - Icon( - Icons.Outlined.PlayArrow, - contentDescription = null, - modifier = Modifier.size(20.dp), - tint = Color.White, - ) - Spacer(Modifier.width(8.dp)) - Text( - stringResource(R.string.library_games_play), - color = Color.White, - fontSize = 16.sp, - fontWeight = FontWeight.Bold, - letterSpacing = 0.5.sp, - ) - } - } - } - - @Composable - private fun InstallButton( - loading: Boolean = false, - onClick: () -> Unit, - ) { - val interactionSource = remember { MutableInteractionSource() } - val isPressed by interactionSource.collectIsPressedAsState() - val scale by animateFloatAsState( - targetValue = if (isPressed && !loading) 0.92f else 1f, - animationSpec = spring(dampingRatio = 0.5f, stiffness = 600f), - label = "installScale", - ) - val infiniteTransition = rememberInfiniteTransition(label = "installGlow") - val glowPulse by infiniteTransition.animateFloat( - initialValue = 0.3f, - targetValue = 0.6f, - animationSpec = - infiniteRepeatable( - animation = tween(1200, easing = FastOutSlowInEasing), - repeatMode = RepeatMode.Reverse, - ), - label = "installPulse", - ) - val baseGradient = - Brush.horizontalGradient( - colors = - listOf( - Color(0xFF00B4D8), - Accent, - Color(0xFF7B2FF7), - ), - ) - Box( - modifier = - Modifier - .fillMaxWidth() - .height(44.dp) - .graphicsLayer { - scaleX = scale - scaleY = scale - }.shadow( - elevation = 12.dp, - shape = RoundedCornerShape(12.dp), - ambientColor = Accent.copy(alpha = glowPulse), - spotColor = Accent.copy(alpha = glowPulse), - ).clip(RoundedCornerShape(12.dp)) - .background(baseGradient) - .clickable( - interactionSource = interactionSource, - indication = null, - onClick = { if (!loading) onClick() }, - ), - contentAlignment = Alignment.Center, - ) { - if (loading) { - CircularProgressIndicator( - modifier = Modifier.size(22.dp), - color = Color.White, - strokeWidth = 2.dp, - ) - } else { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Center, - ) { - Icon( - Icons.Outlined.Download, - contentDescription = null, - modifier = Modifier.size(20.dp), - tint = Color.White, - ) - Spacer(Modifier.width(8.dp)) - Text( - stringResource(R.string.common_ui_download), - color = Color.White, - fontSize = 16.sp, - fontWeight = FontWeight.Bold, - letterSpacing = 0.5.sp, - ) - } - } - } - } - - @Composable - private fun CompactActionButton( - icon: ImageVector, - label: String, - tint: Color = TextPrimary, - bgColor: Color = SurfaceDark, - modifier: Modifier = Modifier, - height: Dp = 36.dp, - fontSize: TextUnit = 13.sp, - onClick: () -> Unit, - ) { - val interactionSource = remember { MutableInteractionSource() } - val isPressed by interactionSource.collectIsPressedAsState() - val scale by animateFloatAsState( - targetValue = if (isPressed) 0.93f else 1f, - animationSpec = spring(dampingRatio = 0.6f, stiffness = 800f), - label = "btnScale", - ) - val glowAlpha by animateFloatAsState( - targetValue = if (isPressed) 0.18f else 0f, - animationSpec = tween(durationMillis = 120), - label = "btnGlow", - ) - Surface( - modifier = - modifier - .fillMaxWidth() - .height(height) - .graphicsLayer { - scaleX = scale - scaleY = scale - }.clip(RoundedCornerShape(10.dp)) - .clickable( - interactionSource = interactionSource, - indication = null, - onClick = onClick, - ), - color = bgColor, - shape = RoundedCornerShape(10.dp), - border = BorderStroke(1.dp, tint.copy(alpha = glowAlpha)), - ) { - Row( - modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Center, - ) { - Icon(icon, contentDescription = null, modifier = Modifier.size(16.dp), tint = tint) - Spacer(Modifier.width(6.dp)) - Text(label, color = tint, fontSize = fontSize, fontWeight = FontWeight.SemiBold, maxLines = 1) - } - } - } - - // Single game capsule for carousel / grid / list - @Composable - @OptIn(ExperimentalFoundationApi::class) - private fun GameCapsule( - app: SteamApp, - gogGame: GOGGame? = null, - epicGame: EpicGame? = null, - iconRefreshKey: Int = 0, - isFocusedOverride: Boolean = false, - isControllerActive: Boolean = false, - customArtworkPath: String? = null, - customIconPath: String? = null, - onClick: (() -> Unit)? = null, - onLongClick: (() -> Unit)? = null, - useLibraryCapsule: Boolean = false, - listMode: Boolean = false, - modifier: Modifier = Modifier, - ) { - val context = LocalContext.current - val isCustom = app.id < 0 - val isEpic = app.id >= 2000000000 - val defaultClick: () -> Unit = { - val containerManager = - com.winlator.cmod.runtime.container - .ContainerManager(context) - if (isCustom) { - launchCustomGame(context, containerManager, app.name) - } else if (gogGame != null) { - launchGogGame(context, containerManager, gogGame) - } else if (isEpic) { - epicGame?.let { launchEpicGame(context, containerManager, it) } - } else { - launchSteamGame(context, containerManager, app) - } - } - val clickInteraction = remember { MutableInteractionSource() } - val isPressed by clickInteraction.collectIsPressedAsState() - val isFocused = isControllerActive && isFocusedOverride - val glowAlpha by animateFloatAsState( - targetValue = if (isPressed) 0.7f else 0f, - animationSpec = if (isPressed) tween(100) else tween(400), - label = "capsuleGlow", - ) - val clickModifier = - Modifier - .then( - if (glowAlpha > 0f) { - Modifier.drawWithContent { - drawContent() - drawRoundRect( - color = AccentGlow, - alpha = glowAlpha * 0.25f, - cornerRadius = CornerRadius(12.dp.toPx()), - ) - } - } else { - Modifier - }, - ).combinedClickable( - interactionSource = clickInteraction, - indication = null, - onClick = onClick ?: defaultClick, - onLongClick = onLongClick, - ) - - @Composable - fun ArtContent(artModifier: Modifier) { - val customArtworkFile = - customArtworkPath - ?.let { java.io.File(it) } - - if (customArtworkFile != null) { - val customArtworkCacheKey = - "library_custom_icon:${customArtworkFile.absolutePath}:${customArtworkFile.lastModified()}" - AsyncImage( - model = - ImageRequest - .Builder(context) - .data(customArtworkFile) - .memoryCacheKey(customArtworkCacheKey) - .diskCacheKey(customArtworkCacheKey) - .crossfade(300) - .build(), - contentDescription = app.name, - modifier = artModifier, - contentScale = ContentScale.Crop, - ) - } else if (isCustom) { - val iconFile = customIconPath?.let { path -> java.io.File(path) } - if (iconFile != null) { - AsyncImage( - model = - ImageRequest - .Builder(context) - .data(iconFile) - .crossfade(300) - .build(), - contentDescription = app.name, - modifier = artModifier, - contentScale = ContentScale.Crop, - ) - } else { - Box( - modifier = artModifier.background(SurfaceDark), - contentAlignment = Alignment.Center, - ) { - Icon( - Icons.Outlined.SportsEsports, - contentDescription = app.name, - tint = Accent.copy(alpha = 0.6f), - modifier = Modifier.size(48.dp), - ) - } - } - } else if (gogGame != null) { - AsyncImage( - model = - ImageRequest - .Builder(context) - .data(gogGame.imageUrl.ifEmpty { gogGame.iconUrl }) - .crossfade(300) - .build(), - contentDescription = app.name, - modifier = artModifier, - contentScale = ContentScale.Crop, - ) - } else if (isEpic) { - AsyncImage( - model = - ImageRequest - .Builder(context) - .data(epicGame?.primaryImageUrl ?: epicGame?.iconUrl) - .crossfade(300) - .build(), - contentDescription = app.name, - modifier = artModifier, - contentScale = ContentScale.Crop, - ) - } else { - val imageUrl = - when { - listMode -> app.getSmallCapsuleUrl() - useLibraryCapsule -> app.getLibraryCapsuleUrl() - else -> app.getCapsuleUrl() - } - AsyncImage( - model = - ImageRequest - .Builder(context) - .data(imageUrl) - .crossfade(300) - .build(), - contentDescription = app.name, - modifier = artModifier, - contentScale = ContentScale.Crop, - ) - } - } - - if (listMode) { - // Horizontal row card with hero background - val heroUrl = if (!isCustom && gogGame == null && !isEpic) app.getHeroUrl() else null - - Box( - modifier = - modifier - .fillMaxWidth() - .clip(RoundedCornerShape(14.dp)) - .border(1.dp, if (isControllerActive) CardBorder else Color.Transparent, RoundedCornerShape(14.dp)) - .chasingBorder( - isFocused = isFocused, - paused = chasingBordersPaused.value || !libraryTabActive.value, - cornerRadius = 14.dp, - ).background(CardDark, RoundedCornerShape(14.dp)) - .focusable() - .then(clickModifier), - ) { - // Hero background layer (falls back to CardDark if image fails) - if (heroUrl != null) { - AsyncImage( - model = - ImageRequest - .Builder(context) - .data(heroUrl) - .crossfade(300) - .build(), - contentDescription = null, - modifier = - Modifier - .matchParentSize() - .graphicsLayer { alpha = 0.25f }, - contentScale = ContentScale.Crop, - ) - } - - // Foreground content - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = - Modifier - .fillMaxWidth() - .padding(horizontal = 14.dp, vertical = 11.dp), - horizontalArrangement = Arrangement.Center, - ) { - Box( - modifier = - Modifier - .height(52.dp) - .aspectRatio(462f / 174f) - .clip(RoundedCornerShape(8.dp)), - ) { - ArtContent(Modifier.fillMaxSize()) - } - - Spacer(Modifier.width(14.dp)) - - Text( - text = app.name, - modifier = - Modifier - .weight(1f) - .then(if (isFocused) Modifier.basicMarquee(iterations = Int.MAX_VALUE) else Modifier), - color = TextPrimary, - fontSize = 15.sp, - fontWeight = FontWeight.SemiBold, - maxLines = 2, - overflow = TextOverflow.Ellipsis, - ) - } - } - } else { - // Vertical card: art on top, title below - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = - modifier - .fillMaxWidth() - .border(1.dp, CardDark, RoundedCornerShape(12.dp)) - .chasingBorder( - isFocused = isFocused, - paused = chasingBordersPaused.value || !libraryTabActive.value, - cornerRadius = 12.dp, - ).background(CardDark, RoundedCornerShape(12.dp)) - .focusable() - .then(clickModifier), - ) { - Box( - modifier = - Modifier - .fillMaxWidth() - .weight(1f) - .clip(RoundedCornerShape(topStart = 12.dp, topEnd = 12.dp)), - ) { - ArtContent(Modifier.fillMaxSize()) - } - - Text( - text = app.name, - modifier = - Modifier - .fillMaxWidth() - .padding(horizontal = 4.dp, vertical = 4.dp) - .then(if (isFocused) Modifier.basicMarquee(iterations = Int.MAX_VALUE) else Modifier), - style = MaterialTheme.typography.bodySmall, - color = TextPrimary, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - textAlign = TextAlign.Center, - ) - } - } - } - - // Epic Store Tab - @Composable - fun EpicStoreTab( - isLoggedIn: Boolean, - epicApps: List, - searchQuery: String = "", - layoutMode: LibraryLayoutMode = LibraryLayoutMode.GRID_4, - onLoginClick: () -> Unit, - ) { - val context = LocalContext.current - - if (!isLoggedIn) { - LoginRequiredScreen("Epic Games", onLoginClick) - return - } - - val selectedAppId = remember { mutableStateOf(null) } - val gridState = rememberLazyGridState() - val activity = LocalContext.current as? UnifiedActivity - - // Ensure library updates from cloud - LaunchedEffect(Unit) { - if (epicApps.isEmpty()) { - EpicService.triggerLibrarySync(context) - } - } - - val displayedApps = - remember(epicApps, searchQuery) { - if (searchQuery.isBlank()) { - epicApps - } else { - epicApps.filter { it.title.contains(searchQuery, ignoreCase = true) } - } - } - val installStateById = rememberInstallPathStateMap(displayedApps.map { it.id to it.installPath }) - - // Sync store focus infrastructure - LaunchedEffect(displayedApps.size) { - activity?.storeItemCount = displayedApps.size - val lastIndex = (displayedApps.size - 1).coerceAtLeast(0) - if (activity != null && displayedApps.isNotEmpty() && activity.storeFocusIndex.value > lastIndex) { - activity.storeFocusIndex.value = lastIndex - } - } - DisposableEffect(displayedApps) { - activity?.storeItemClickCallback = { idx -> - displayedApps.getOrNull(idx)?.let { selectedAppId.value = it.id } - } - activity?.storeGridState = gridState - onDispose { - activity?.storeItemClickCallback = null - activity?.storeGridState = null - } - } - - if (layoutMode == LibraryLayoutMode.LIST) { - val listViewState = rememberLazyListState() - JoystickListScroll(listViewState, activity?.rightStickScrollState) - ListView( - items = displayedApps, - modifier = Modifier.tabScreenPadding(), - listState = listViewState, - contentPadding = TabListContentPadding, - keyOf = { it.id }, - ) { app, _, _ -> - EpicStoreCapsule( - app, - isInstalled = installStateById[app.id] == true, - listMode = true, - isControllerActive = ControllerHelper.isControllerConnected(), - ) { - selectedAppId.value = - app.id - } - } - } else { - val focusIndex by (activity?.storeFocusIndex ?: kotlinx.coroutines.flow.MutableStateFlow(0)).collectAsState() - val focusRequesters = - remember(displayedApps.size) { - List(displayedApps.size) { FocusRequester() } - } - LaunchedEffect(focusIndex, focusRequesters.size) { - if (searchQuery.isEmpty() && focusRequesters.isNotEmpty() && focusIndex in focusRequesters.indices) { - gridState.animateScrollToItem(focusIndex) - try { - focusRequesters[focusIndex].requestFocus() - } catch (_: Exception) { - } - } - } - JoystickGridScroll(gridState, activity?.rightStickScrollState) - FourByTwoGridView( - items = displayedApps, - modifier = Modifier.tabScreenPadding(top = TabGridTopPadding), - gridState = gridState, - keyOf = { it.id }, - ) { app, index, rowHeight -> - Box( - Modifier.height(rowHeight).then( - if (index in focusRequesters.indices) { - Modifier.focusRequester(focusRequesters[index]) - } else { - Modifier - }, - ), - ) { - EpicStoreCapsule( - app, - isInstalled = installStateById[app.id] == true, - isFocusedOverride = index == focusIndex, - isControllerActive = ControllerHelper.isControllerConnected(), - ) { - selectedAppId.value = - app.id - } - } - } - } - - val selectedApp = epicApps.find { it.id == selectedAppId.value } - if (selectedApp != null) { - EpicGameManagerDialog( - app = selectedApp, - onDismissRequest = { selectedAppId.value = null }, - ) - } - } - - @Composable - fun EpicStoreCapsule( - app: com.winlator.cmod.feature.stores.epic.data.EpicGame, - isInstalled: Boolean, - listMode: Boolean = false, - isFocusedOverride: Boolean = false, - isControllerActive: Boolean = false, - onClick: () -> Unit, - ) { - val context = LocalContext.current - var isFocused by remember { mutableStateOf(false) } - val clickInteraction = remember { MutableInteractionSource() } - val isPressed by clickInteraction.collectIsPressedAsState() - val glowAlpha by animateFloatAsState( - targetValue = if (isPressed) 0.7f else 0f, - animationSpec = if (isPressed) tween(100) else tween(400), - label = "epicCapsuleGlow", - ) - val effectiveFocus = isControllerActive && (isFocusedOverride || isFocused) - val imageUrl = app.primaryImageUrl ?: app.iconUrl - - val borderColor = if (isControllerActive) CardBorder else Color.Transparent - - if (listMode) { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = - Modifier - .fillMaxWidth() - .clip(RoundedCornerShape(14.dp)) - .border(1.dp, borderColor, RoundedCornerShape(14.dp)) - .chasingBorder(isFocused = effectiveFocus, paused = chasingBordersPaused.value, cornerRadius = 14.dp) - .background(CardDark, RoundedCornerShape(14.dp)) - .onFocusChanged { isFocused = it.isFocused } - .focusable() - .then( - if (glowAlpha > 0f) { - Modifier.drawWithContent { - drawContent() - drawRoundRect(color = AccentGlow, alpha = glowAlpha * 0.25f, cornerRadius = CornerRadius(14.dp.toPx())) - } - } else { - Modifier - }, - ).clickable(interactionSource = clickInteraction, indication = null, onClick = onClick) - .padding(horizontal = 14.dp, vertical = 11.dp), - horizontalArrangement = Arrangement.Center, - ) { - Box( - Modifier - .height(52.dp) - .aspectRatio(462f / 174f) - .clip(RoundedCornerShape(8.dp)), - ) { - AsyncImage( - model = - ImageRequest - .Builder(context) - .data(imageUrl) - .crossfade(300) - .build(), - contentDescription = app.title, - modifier = Modifier.fillMaxSize(), - contentScale = ContentScale.Crop, - ) - if (isInstalled) { - Box( - Modifier - .align( - Alignment.BottomEnd, - ).padding(4.dp) - .background(SurfaceDark.copy(alpha = 0.7f), RoundedCornerShape(6.dp)) - .padding(3.dp), - ) { - Text( - stringResource(R.string.library_games_installed_badge), - color = StatusOnline, - fontSize = 8.sp, - fontWeight = FontWeight.Bold, - ) - } - } - } - Spacer(Modifier.width(14.dp)) - Text( - app.title, - modifier = - Modifier - .weight(1f) - .then(if (effectiveFocus) Modifier.basicMarquee(iterations = Int.MAX_VALUE) else Modifier), - color = TextPrimary, - fontSize = 15.sp, - fontWeight = FontWeight.SemiBold, - maxLines = 2, - overflow = TextOverflow.Ellipsis, - ) - } - } else { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = - Modifier - .fillMaxSize() - .border(1.dp, borderColor, RoundedCornerShape(16.dp)) - .chasingBorder(isFocused = effectiveFocus, paused = chasingBordersPaused.value, cornerRadius = 16.dp) - .background(CardDark, RoundedCornerShape(16.dp)) - .onFocusChanged { isFocused = it.isFocused } - .focusable() - .then( - if (glowAlpha > 0f) { - Modifier.drawWithContent { - drawContent() - drawRoundRect(color = AccentGlow, alpha = glowAlpha * 0.25f, cornerRadius = CornerRadius(16.dp.toPx())) - } - } else { - Modifier - }, - ).clickable(interactionSource = clickInteraction, indication = null, onClick = onClick), - ) { - Box( - Modifier - .fillMaxWidth() - .weight(1f) - .clip(RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp)), - ) { - AsyncImage( - model = - ImageRequest - .Builder(context) - .data(imageUrl) - .crossfade(300) - .build(), - contentDescription = app.title, - modifier = Modifier.fillMaxSize(), - contentScale = ContentScale.Crop, - ) - - if (isInstalled) { - Box( - Modifier - .align( - Alignment.BottomEnd, - ).padding(8.dp) - .background(SurfaceDark.copy(alpha = 0.7f), RoundedCornerShape(8.dp)) - .padding(4.dp), - ) { - Text( - stringResource(R.string.library_games_installed_badge), - color = StatusOnline, - fontSize = 10.sp, - fontWeight = FontWeight.Bold, - ) - } - } - } - - Text( - app.title, - modifier = - Modifier - .padding(horizontal = 4.dp, vertical = 4.dp) - .fillMaxWidth() - .then(if (effectiveFocus) Modifier.basicMarquee(iterations = Int.MAX_VALUE) else Modifier), - style = MaterialTheme.typography.bodySmall, - color = TextPrimary, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - textAlign = TextAlign.Center, - ) - } - } - } - - @Composable - private fun StoreInstallDialogShell( - title: String, - heroImageUrl: String?, - subtitle: String, - sourceLabel: String = "", - onDismissRequest: () -> Unit, - infoContent: @Composable ColumnScope.() -> Unit = {}, - actionsContent: @Composable ColumnScope.() -> Unit, - ) { - Dialog( - onDismissRequest = onDismissRequest, - properties = - DialogProperties( - usePlatformDefaultWidth = false, - decorFitsSystemWindows = false, - ), - ) { - Surface( - modifier = - Modifier - .windowInsetsPadding(WindowInsets.navigationBars) - .fillMaxWidth(0.864f) - .fillMaxHeight(0.92f), - shape = RoundedCornerShape(20.dp), - color = CardDark, - ) { - Box(Modifier.fillMaxSize()) { - Column(Modifier.fillMaxSize()) { - Box( - modifier = - Modifier - .fillMaxWidth() - .fillMaxHeight(0.42f), - ) { - AsyncImage( - model = - ImageRequest - .Builder(LocalContext.current) - .data(heroImageUrl) - .crossfade(150) - .memoryCachePolicy(coil.request.CachePolicy.ENABLED) - .diskCachePolicy(coil.request.CachePolicy.ENABLED) - .build(), - contentDescription = "$title artwork", - modifier = Modifier.fillMaxSize(), - contentScale = ContentScale.FillWidth, - alignment = Alignment.TopCenter, - ) - Box( - modifier = - Modifier - .fillMaxSize() - .background( - Brush.verticalGradient( - colorStops = - arrayOf( - 0.0f to Color.Transparent, - 0.45f to Color.Transparent, - 0.72f to CardDark.copy(alpha = 0.72f), - 1.0f to CardDark, - ), - ), - ), - ) - Column( - modifier = - Modifier - .align(Alignment.BottomStart) - .padding(start = 24.dp, end = 80.dp, bottom = 24.dp), - ) { - Text( - title, - style = MaterialTheme.typography.headlineMedium, - color = TextPrimary, - fontWeight = FontWeight.Bold, - ) - if (subtitle.isNotBlank()) { - Spacer(Modifier.height(8.dp)) - Text( - subtitle, - style = MaterialTheme.typography.bodyMedium, - color = TextSecondary, - maxLines = 2, - overflow = TextOverflow.Ellipsis, - ) - } - } - } - - Row( - modifier = - Modifier - .fillMaxSize() - .padding(horizontal = 24.dp, vertical = 14.dp), - horizontalArrangement = Arrangement.spacedBy(14.dp), - ) { - Column( - modifier = - Modifier - .weight(1f) - .fillMaxHeight(), - verticalArrangement = Arrangement.spacedBy(6.dp, Alignment.Bottom), - ) { - if (sourceLabel.isNotBlank()) { - Surface( - color = Accent.copy(alpha = 0.15f), - shape = RoundedCornerShape(8.dp), - ) { - Text( - sourceLabel, - modifier = Modifier.padding(horizontal = 10.dp, vertical = 4.dp), - color = Accent, - fontSize = 12.sp, - fontWeight = FontWeight.Bold, - ) - } - } - infoContent() - } - - Column( - modifier = - Modifier - .widthIn(min = 200.dp, max = 260.dp) - .fillMaxHeight(), - verticalArrangement = Arrangement.spacedBy(6.dp, Alignment.Bottom), - ) { - actionsContent() - } - } - } - - IconButton( - onClick = onDismissRequest, - modifier = - Modifier - .align(Alignment.TopEnd) - .padding(16.dp) - .size(42.dp) - .shadow(8.dp, CircleShape, spotColor = Color.Black.copy(alpha = 0.35f)) - .clip(CircleShape) - .background(BgDark.copy(alpha = 0.7f)), - ) { - Icon(Icons.Outlined.Close, contentDescription = "Close", tint = TextPrimary) - } - } - } - } - } - - @Composable - fun EpicGameManagerDialog( - app: EpicGame, - onDismissRequest: () -> Unit, - ) { - val context = LocalContext.current - val installed = app.isInstalled && java.io.File(app.installPath).exists() - val scope = rememberCoroutineScope() - - var isLoading by remember { mutableStateOf(!installed) } - var manifestSizes by remember { mutableStateOf(null) } - var dlcApps by remember { mutableStateOf>(emptyList()) } - val selectedDlcIds = remember { mutableStateListOf() } - var customPath by remember { mutableStateOf(null) } - var showCustomPathWarning by remember { mutableStateOf(false) } - var showDlcDialog by remember { mutableStateOf(false) } - - if (showCustomPathWarning) { - CustomPathWarningDialog( - onDismiss = { showCustomPathWarning = false }, - onProceed = { - showCustomPathWarning = false - DirectoryPickerDialog.show( - activity = this@UnifiedActivity, - initialPath = customPath ?: EpicConstants.getGameInstallPath(context, app.appName), - title = getString(R.string.settings_content_install_directory), - ) { path -> customPath = path } - }, - ) - } - - if (showDlcDialog && dlcApps.isNotEmpty()) { - GameSettingsDialogFrame( - title = stringResource(R.string.library_games_dlcs), - onDismissRequest = { showDlcDialog = false }, - ) { - Column( - modifier = - Modifier - .heightIn(max = 300.dp) - .verticalScroll(rememberScrollState()), - ) { - dlcApps.forEachIndexed { index, dlc -> - if (index > 0) { - HorizontalDivider( - color = CardBorder.copy(alpha = 0.5f), - thickness = 0.5.dp, - modifier = Modifier.padding(horizontal = 16.dp), - ) - } - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = - Modifier - .fillMaxWidth() - .clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = null, - ) { - if (selectedDlcIds.contains(dlc.id)) { - selectedDlcIds.remove(dlc.id) - } else { - selectedDlcIds.add(dlc.id) - } - }.padding(horizontal = 16.dp, vertical = 2.dp), - ) { - Checkbox( - checked = selectedDlcIds.contains(dlc.id), - onCheckedChange = { if (it) selectedDlcIds.add(dlc.id) else selectedDlcIds.remove(dlc.id) }, - colors = - CheckboxDefaults.colors( - checkedColor = Accent, - uncheckedColor = TextSecondary, - checkmarkColor = Color.White, - ), - ) - Text(dlc.title, color = TextPrimary, fontSize = 13.sp) - } - } - } - } - } - - LaunchedEffect(app.id, installed) { - if (!installed) { - withContext(Dispatchers.IO) { - manifestSizes = EpicService.fetchManifestSizes(context, app.id) - dlcApps = EpicService.getDLCForGameSuspend(app.id) - isLoading = false - } - } - } - - val totalInstallSize = manifestSizes?.installSize ?: 0L - val totalDownloadSize = manifestSizes?.downloadSize ?: 0L - val defaultPathSet = - if (PrefManager.useSingleDownloadFolder) { - PrefManager.defaultDownloadFolder.isNotEmpty() - } else { - PrefManager.epicDownloadFolder - .isNotEmpty() - } - val effectivePath = customPath ?: EpicConstants.getGameInstallPath(context, app.appName) - val availableBytes = - try { - StorageUtils.getAvailableSpace(effectivePath) - } catch (e: Exception) { - 0L - } - val isInstallEnabled = installed || availableBytes >= totalInstallSize - val installPathDisplay = customPath ?: EpicConstants.defaultEpicGamesPath(context) - - StoreInstallDialogShell( - title = app.title, - heroImageUrl = app.artPortrait.ifEmpty { app.primaryImageUrl }, - subtitle = - listOfNotNull( - app.developer.takeIf { it.isNotBlank() }, - app.publisher.takeIf { it.isNotBlank() }, - ).joinToString(" • "), - sourceLabel = "Epic Games", - onDismissRequest = onDismissRequest, - infoContent = { - if (isLoading && !installed) { - Spacer(Modifier.height(18.dp)) - CircularProgressIndicator(color = Accent) - } else if (installed) { - DetailCard( - label = stringResource(R.string.library_games_install_path), - value = app.installPath, - ) - DetailCard( - label = stringResource(R.string.common_ui_status), - value = stringResource(R.string.common_ui_installed), - valueColor = StatusOnline, - ) - } else { - DetailCard( - label = stringResource(R.string.library_games_install_path), - value = installPathDisplay, - ) - DetailCard( - stringResource(R.string.library_games_download_slash_install), - stringResource( - R.string.library_games_download_install_available, - StorageUtils.formatBinarySize(totalDownloadSize), - StorageUtils.formatBinarySize(totalInstallSize), - StorageUtils.formatBinarySize(availableBytes), - ), - valueColor = if (!isInstallEnabled) DangerRed else null, - ) - } - }, - ) { - if (installed) { - PlayButton(onClick = { - launchEpicGame(context, ContainerManager(context), app) - onDismissRequest() - }) - if (app.cloudSaveEnabled) { - CompactActionButton( - icon = Icons.Outlined.CloudSync, - label = stringResource(R.string.google_cloud_title), - onClick = { - scope.launch(Dispatchers.IO) { - EpicCloudSavesManager.syncCloudSaves(context, app.id, "auto") - } - onDismissRequest() - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - context.getString(R.string.google_cloud_sync_started), - android.widget.Toast.LENGTH_SHORT, - ) - }, - ) - } - CompactActionButton( - icon = Icons.Outlined.Delete, - label = stringResource(R.string.common_ui_uninstall), - tint = DangerRed, - bgColor = DangerRed.copy(alpha = 0.12f), - onClick = { - scope.launch(Dispatchers.IO) { - val result = EpicService.deleteGame(context, app.id) - withContext(Dispatchers.Main) { - if (!result.isSuccess) { - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - getString( - R.string.library_games_failed_to_uninstall_reason, - result.exceptionOrNull()?.message - ?: getString(R.string.common_ui_unknown_error), - ), - android.widget.Toast.LENGTH_LONG, - ) - } - onDismissRequest() - } - } - }, - ) - } else { - InstallButton( - loading = isLoading, - onClick = { - val installPath = - if (customPath != null) { - val sanitizedTitle = app.title.replace(Regex("[^a-zA-Z0-9 \\-_]"), "").trim() - java.io.File(customPath!!, sanitizedTitle).absolutePath - } else { - EpicConstants.getGameInstallPath(context, app.title) - } - EpicService.downloadGame(context, app.id, selectedDlcIds.toList(), installPath, "en-US") - onDismissRequest() - }, - ) - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(6.dp), - ) { - CompactActionButton( - icon = Icons.Outlined.Folder, - label = - if (customPath != - null - ) { - stringResource(R.string.common_ui_custom) - } else if (defaultPathSet) { - stringResource(R.string.common_ui_already_set) - } else { - stringResource(R.string.common_ui_custom) - }, - modifier = Modifier.weight(1f), - onClick = { - if (customPath == null && defaultPathSet) { - showCustomPathWarning = true - } else { - DirectoryPickerDialog.show( - activity = this@UnifiedActivity, - initialPath = customPath ?: EpicConstants.getGameInstallPath(context, app.appName), - title = getString(R.string.settings_content_install_directory), - ) { path -> customPath = path } - } - }, - ) - if (dlcApps.isNotEmpty()) { - CompactActionButton( - icon = Icons.Outlined.Extension, - label = stringResource(R.string.library_games_dlcs), - modifier = Modifier.weight(1f), - onClick = { showDlcDialog = true }, - ) - } - } - } - } - } - - @Composable - fun GOGStoreTab( - isLoggedIn: Boolean, - gogApps: List, - searchQuery: String = "", - layoutMode: LibraryLayoutMode = LibraryLayoutMode.GRID_4, - onLoginClick: () -> Unit, - ) { - if (!isLoggedIn) { - LoginRequiredScreen("GOG", onLoginClick) - return - } - - val selectedGameId = remember { mutableStateOf(null) } - val gridState = rememberLazyGridState() - val activity = LocalContext.current as? UnifiedActivity - - val displayedApps = - remember(gogApps, searchQuery) { - if (searchQuery.isBlank()) { - gogApps - } else { - gogApps.filter { it.title.contains(searchQuery, ignoreCase = true) } - } - } - val installStateById = rememberInstallPathStateMap(displayedApps.map { it.id to it.installPath }) - - // Sync store focus infrastructure - LaunchedEffect(displayedApps.size) { - activity?.storeItemCount = displayedApps.size - val lastIndex = (displayedApps.size - 1).coerceAtLeast(0) - if (activity != null && displayedApps.isNotEmpty() && activity.storeFocusIndex.value > lastIndex) { - activity.storeFocusIndex.value = lastIndex - } - } - DisposableEffect(displayedApps) { - activity?.storeItemClickCallback = { idx -> - displayedApps.getOrNull(idx)?.let { selectedGameId.value = it.id } - } - activity?.storeGridState = gridState - onDispose { - activity?.storeItemClickCallback = null - activity?.storeGridState = null - } - } - - val isControllerActive = ControllerHelper.isControllerConnected() - val gogBorderColor = if (isControllerActive) CardBorder else Color.Transparent - - if (layoutMode == LibraryLayoutMode.LIST) { - val listViewState = rememberLazyListState() - ListView( - items = displayedApps, - modifier = Modifier.tabScreenPadding(), - listState = listViewState, - contentPadding = TabListContentPadding, - keyOf = { it.id }, - ) { app, _, _ -> - val isInstalled = installStateById[app.id] == true - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = - Modifier - .fillMaxWidth() - .clip(RoundedCornerShape(14.dp)) - .border(1.dp, gogBorderColor, RoundedCornerShape(14.dp)) - .background(CardDark, RoundedCornerShape(14.dp)) - .clickable { selectedGameId.value = app.id } - .padding(horizontal = 14.dp, vertical = 11.dp), - horizontalArrangement = Arrangement.Center, - ) { - Box( - Modifier - .height(52.dp) - .aspectRatio(462f / 174f) - .clip(RoundedCornerShape(8.dp)), - ) { - AsyncImage( - model = - ImageRequest - .Builder(LocalContext.current) - .data(app.imageUrl.ifEmpty { app.iconUrl }) - .crossfade(300) - .build(), - contentDescription = null, - modifier = Modifier.fillMaxSize(), - contentScale = ContentScale.Crop, - ) - if (isInstalled) { - Icon( - Icons.Outlined.CheckCircle, - contentDescription = "Installed", - tint = StatusOnline, - modifier = Modifier.align(Alignment.BottomEnd).padding(4.dp).size(18.dp), - ) - } - } - Spacer(Modifier.width(14.dp)) - Text( - text = app.title, - modifier = Modifier.weight(1f), - color = TextPrimary, - fontSize = 15.sp, - fontWeight = FontWeight.SemiBold, - maxLines = 2, - overflow = TextOverflow.Ellipsis, - ) - } - } - } else { - val focusIndex by (activity?.storeFocusIndex ?: kotlinx.coroutines.flow.MutableStateFlow(0)).collectAsState() - val focusRequesters = - remember(displayedApps.size) { - List(displayedApps.size) { FocusRequester() } - } - LaunchedEffect(focusIndex, focusRequesters.size) { - if (searchQuery.isEmpty() && focusRequesters.isNotEmpty() && focusIndex in focusRequesters.indices) { - gridState.animateScrollToItem(focusIndex) - try { - focusRequesters[focusIndex].requestFocus() - } catch (_: Exception) { - } - } - } - FourByTwoGridView( - items = displayedApps, - modifier = Modifier.tabScreenPadding(top = TabGridTopPadding), - gridState = gridState, - keyOf = { it.id }, - ) { app, index, rowHeight -> - val isInstalled = installStateById[app.id] == true - val isItemFocused = isControllerActive && index == focusIndex - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = - Modifier - .fillMaxWidth() - .height(rowHeight) - .then( - if (index in focusRequesters.indices) { - Modifier.focusRequester(focusRequesters[index]) - } else { - Modifier - }, - ).border(1.dp, gogBorderColor, RoundedCornerShape(16.dp)) - .chasingBorder(isFocused = isItemFocused, paused = chasingBordersPaused.value, cornerRadius = 16.dp) - .background(CardDark, RoundedCornerShape(16.dp)) - .clickable { selectedGameId.value = app.id }, - ) { - Box( - Modifier - .fillMaxWidth() - .weight(1f) - .clip(RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp)), - ) { - AsyncImage( - model = - ImageRequest - .Builder(LocalContext.current) - .data(app.imageUrl.ifEmpty { app.iconUrl }) - .crossfade(300) - .build(), - contentDescription = null, - modifier = Modifier.fillMaxSize(), - contentScale = ContentScale.Crop, - ) - if (isInstalled) { - Icon( - Icons.Outlined.CheckCircle, - contentDescription = "Installed", - tint = StatusOnline, - modifier = Modifier.align(Alignment.BottomEnd).padding(8.dp).size(24.dp), - ) - } - } - - Text( - text = app.title, - modifier = Modifier.fillMaxWidth().padding(horizontal = 4.dp, vertical = 4.dp), - style = MaterialTheme.typography.bodySmall, - color = TextPrimary, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - textAlign = TextAlign.Center, - ) - } - } - } - - selectedGameId.value?.let { gameId -> - val app = gogApps.firstOrNull { it.id == gameId } - if (app != null) { - GOGGameManagerDialog(app = app) { selectedGameId.value = null } - } - } - } - - @Composable - fun GOGGameManagerDialog( - app: GOGGame, - onDismissRequest: () -> Unit, - ) { - val context = LocalContext.current - val installed = app.isInstalled && java.io.File(app.installPath).exists() - val scope = rememberCoroutineScope() - var customPath by remember { mutableStateOf(null) } - var showCustomPathWarning by remember { mutableStateOf(false) } - - if (showCustomPathWarning) { - CustomPathWarningDialog( - onDismiss = { showCustomPathWarning = false }, - onProceed = { - showCustomPathWarning = false - DirectoryPickerDialog.show( - activity = this@UnifiedActivity, - initialPath = customPath ?: GOGConstants.defaultGOGGamesPath, - title = getString(R.string.settings_content_install_directory), - ) { path -> customPath = path } - }, - ) - } - - val defaultPathSet = - if (PrefManager.useSingleDownloadFolder) { - PrefManager.defaultDownloadFolder.isNotEmpty() - } else { - PrefManager.gogDownloadFolder - .isNotEmpty() - } - val installRootPath = customPath ?: GOGConstants.defaultGOGGamesPath - val installPathDisplay = - if (customPath != null) { - java.io.File(customPath!!, GOGConstants.getSanitizedGameFolderName(app.title)).absolutePath - } else { - GOGConstants.getGameInstallPath(app.title) - } - val requiredBytes = maxOf(app.installSize, app.downloadSize) - val availableBytes = - try { - StorageUtils.getAvailableSpace(installRootPath) - } catch (_: Exception) { - 0L - } - val isInstallEnabled = installed || availableBytes >= requiredBytes - - StoreInstallDialogShell( - title = app.title, - heroImageUrl = app.imageUrl.ifEmpty { app.iconUrl }, - subtitle = - listOfNotNull( - app.developer.takeIf { it.isNotBlank() }, - app.publisher.takeIf { it.isNotBlank() }, - ).joinToString(" • "), - sourceLabel = "GOG", - onDismissRequest = onDismissRequest, - infoContent = { - if (installed) { - DetailCard( - label = stringResource(R.string.library_games_install_path), - value = app.installPath, - ) - DetailCard( - label = stringResource(R.string.common_ui_status), - value = stringResource(R.string.common_ui_installed), - valueColor = StatusOnline, - ) - } else { - DetailCard( - label = stringResource(R.string.library_games_install_path), - value = installPathDisplay, - ) - DetailCard( - stringResource(R.string.library_games_download_slash_install), - stringResource( - R.string.library_games_download_install_available, - StorageUtils.formatBinarySize(app.downloadSize), - StorageUtils.formatBinarySize(app.installSize), - StorageUtils.formatBinarySize(availableBytes), - ), - valueColor = if (!isInstallEnabled) DangerRed else null, - ) - } - }, - ) { - if (installed) { - PlayButton(onClick = { - launchGogGame(context, ContainerManager(context), app) - onDismissRequest() - }) - CompactActionButton( - icon = Icons.Outlined.CloudSync, - label = stringResource(R.string.google_cloud_title), - onClick = { - scope.launch(Dispatchers.IO) { - GOGService.syncCloudSaves(context, "GOG_${app.id}", "auto") - } - onDismissRequest() - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - context.getString(R.string.google_cloud_sync_started), - android.widget.Toast.LENGTH_SHORT, - ) - }, - ) - CompactActionButton( - icon = Icons.Outlined.Delete, - label = stringResource(R.string.common_ui_uninstall), - tint = DangerRed, - bgColor = DangerRed.copy(alpha = 0.12f), - onClick = { - scope.launch(Dispatchers.IO) { - val result = GOGService.deleteGame( - context, - LibraryItem("GOG_${app.id}", app.title, com.winlator.cmod.feature.stores.steam.enums.GameSource.GOG), - ) - withContext(Dispatchers.Main) { - if (!result.isSuccess) { - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - getString( - R.string.library_games_failed_to_uninstall_reason, - result.exceptionOrNull()?.message - ?: getString(R.string.common_ui_unknown_error), - ), - android.widget.Toast.LENGTH_LONG, - ) - } - onDismissRequest() - } - } - }, - ) - } else { - InstallButton( - onClick = { - GOGService.downloadGame(context, app.id, installPathDisplay, PrefManager.containerLanguage) - onDismissRequest() - }, - ) - CompactActionButton( - icon = Icons.Outlined.Folder, - label = - if (customPath != - null - ) { - stringResource(R.string.common_ui_custom) - } else if (defaultPathSet) { - stringResource(R.string.common_ui_already_set) - } else { - stringResource(R.string.common_ui_custom) - }, - onClick = { - if (customPath == null && defaultPathSet) { - showCustomPathWarning = true - } else { - DirectoryPickerDialog.show( - activity = this@UnifiedActivity, - initialPath = customPath ?: GOGConstants.defaultGOGGamesPath, - title = getString(R.string.settings_content_install_directory), - ) { path -> customPath = path } - } - }, - ) - } - } - } - - // Steam Store Tab - @Composable - fun SteamStoreTab( - isLoggedIn: Boolean, - steamApps: List, - searchQuery: String = "", - layoutMode: LibraryLayoutMode = LibraryLayoutMode.GRID_4, - ) { - if (!isLoggedIn && !SteamService.hasStoredCredentials(this)) { - LoginRequiredScreen("Steam") { - startActivity(Intent(this@UnifiedActivity, SteamLoginActivity::class.java)) - } - return - } - - var selectedAppForDialog by remember { mutableStateOf(null) } - val gridState = rememberLazyGridState() - val activity = LocalContext.current as? UnifiedActivity - - val displayedApps = - remember(steamApps, searchQuery) { - if (searchQuery.isBlank()) { - steamApps - } else { - steamApps.filter { it.name.contains(searchQuery, ignoreCase = true) } - } - } - val installStateById = rememberSteamInstallStateMap(displayedApps) - - // Sync store focus infrastructure - LaunchedEffect(displayedApps.size) { - activity?.storeItemCount = displayedApps.size - val lastIndex = (displayedApps.size - 1).coerceAtLeast(0) - if (activity != null && displayedApps.isNotEmpty() && activity.storeFocusIndex.value > lastIndex) { - activity.storeFocusIndex.value = lastIndex - } - } - // Register A-button click callback and grid state for visible-area snapping - DisposableEffect(displayedApps) { - activity?.storeItemClickCallback = { idx -> - displayedApps.getOrNull(idx)?.let { selectedAppForDialog = it } - } - activity?.storeGridState = gridState - onDispose { - activity?.storeItemClickCallback = null - activity?.storeGridState = null - } - } - - if (layoutMode == LibraryLayoutMode.LIST) { - val listViewState = rememberLazyListState() - JoystickListScroll(listViewState, activity?.rightStickScrollState, minSpeed = 2.5f, maxSpeed = 16f, quadratic = true) - ListView( - items = displayedApps, - modifier = Modifier.tabScreenPadding(), - listState = listViewState, - contentPadding = TabListContentPadding, - keyOf = { it.id }, - ) { app, _, _ -> - SteamStoreCapsule( - app, - isInstalled = installStateById[app.id] == true, - listMode = true, - isControllerActive = ControllerHelper.isControllerConnected(), - onClick = { - selectedAppForDialog = - app - }, - ) - } - } else { - val focusIndex by (activity?.storeFocusIndex ?: kotlinx.coroutines.flow.MutableStateFlow(0)).collectAsState() - val focusRequesters = - remember(displayedApps.size) { - List(displayedApps.size) { FocusRequester() } - } - LaunchedEffect(focusIndex, focusRequesters.size) { - if (searchQuery.isEmpty() && focusRequesters.isNotEmpty() && focusIndex in focusRequesters.indices) { - gridState.animateScrollToItem(focusIndex) - try { - focusRequesters[focusIndex].requestFocus() - } catch (_: Exception) { - } - } - } - // Right joystick: 2x faster at full push with quadratic speed curve - JoystickGridScroll(gridState, activity?.rightStickScrollState, minSpeed = 2.5f, maxSpeed = 16f, quadratic = true) - // Left joystick: 75% slower scrolling (vertical only, for browsing store) - JoystickGridScroll(gridState, activity?.leftStickScrollState, deadZone = 0.15f, minSpeed = 0.3125f, maxSpeed = 2f) - FourByTwoGridView( - items = displayedApps, - modifier = Modifier.tabScreenPadding(top = TabGridTopPadding), - gridState = gridState, - keyOf = { it.id }, - ) { app, index, rowHeight -> - Box( - Modifier.height(rowHeight).then( - if (index in focusRequesters.indices) { - Modifier.focusRequester(focusRequesters[index]) - } else { - Modifier - }, - ), - ) { - SteamStoreCapsule( - app, - isInstalled = installStateById[app.id] == true, - isFocusedOverride = index == focusIndex, - isControllerActive = - ControllerHelper - .isControllerConnected(), - onClick = { - selectedAppForDialog = - app - }, - ) - } - } - } - - if (selectedAppForDialog != null) { - GameManagerDialog( - app = selectedAppForDialog!!, - onDismissRequest = { selectedAppForDialog = null }, - ) - } - } - - @Composable - fun SteamStoreCapsule( - app: SteamApp, - isInstalled: Boolean, - listMode: Boolean = false, - isFocusedOverride: Boolean = false, - isControllerActive: Boolean = false, - onClick: () -> Unit, - ) { - val context = LocalContext.current - var isFocused by remember { mutableStateOf(false) } - val clickInteraction = remember { MutableInteractionSource() } - val isPressed by clickInteraction.collectIsPressedAsState() - val glowAlpha by animateFloatAsState( - targetValue = if (isPressed) 0.7f else 0f, - animationSpec = if (isPressed) tween(100) else tween(400), - label = "steamCapsuleGlow", - ) - val effectiveFocus = isControllerActive && (isFocusedOverride || isFocused) - val borderColor = if (isControllerActive) CardBorder else Color.Transparent - - if (listMode) { - Box( - modifier = - Modifier - .fillMaxWidth() - .clip(RoundedCornerShape(14.dp)) - .border(1.dp, borderColor, RoundedCornerShape(14.dp)) - .chasingBorder(isFocused = effectiveFocus, paused = chasingBordersPaused.value, cornerRadius = 14.dp) - .background(CardDark, RoundedCornerShape(14.dp)) - .onFocusChanged { isFocused = it.isFocused } - .focusable() - .then( - if (glowAlpha > 0f) { - Modifier.drawWithContent { - drawContent() - drawRoundRect(color = AccentGlow, alpha = glowAlpha * 0.25f, cornerRadius = CornerRadius(14.dp.toPx())) - } - } else { - Modifier - }, - ).clickable(interactionSource = clickInteraction, indication = null, onClick = onClick), - ) { - // Hero background - AsyncImage( - model = - ImageRequest - .Builder(context) - .data(app.getHeroUrl()) - .crossfade(300) - .build(), - contentDescription = null, - modifier = - Modifier - .matchParentSize() - .graphicsLayer { alpha = 0.25f }, - contentScale = ContentScale.Crop, - ) - - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = - Modifier - .fillMaxWidth() - .padding(horizontal = 14.dp, vertical = 11.dp), - horizontalArrangement = Arrangement.Center, - ) { - Box( - Modifier - .height(52.dp) - .aspectRatio(462f / 174f) - .clip(RoundedCornerShape(8.dp)), - ) { - AsyncImage( - model = - ImageRequest - .Builder(context) - .data(app.getSmallCapsuleUrl()) - .crossfade(300) - .build(), - contentDescription = null, - modifier = Modifier.fillMaxSize(), - contentScale = ContentScale.Crop, - ) - if (isInstalled) { - Icon( - Icons.Outlined.CheckCircle, - contentDescription = "Installed", - tint = StatusOnline, - modifier = Modifier.align(Alignment.BottomEnd).padding(4.dp).size(18.dp), - ) - } - } - Spacer(Modifier.width(14.dp)) - Text( - text = app.name, - modifier = - Modifier - .weight(1f) - .then(if (effectiveFocus) Modifier.basicMarquee(iterations = Int.MAX_VALUE) else Modifier), - color = TextPrimary, - fontSize = 15.sp, - fontWeight = FontWeight.SemiBold, - maxLines = 2, - overflow = TextOverflow.Ellipsis, - ) - } - } - } else { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = - Modifier - .fillMaxSize() - .border(1.dp, borderColor, RoundedCornerShape(16.dp)) - .chasingBorder(isFocused = effectiveFocus, paused = chasingBordersPaused.value, cornerRadius = 16.dp) - .background(CardDark, RoundedCornerShape(16.dp)) - .onFocusChanged { isFocused = it.isFocused } - .focusable() - .then( - if (glowAlpha > 0f) { - Modifier.drawWithContent { - drawContent() - drawRoundRect(color = AccentGlow, alpha = glowAlpha * 0.25f, cornerRadius = CornerRadius(16.dp.toPx())) - } - } else { - Modifier - }, - ).clickable(interactionSource = clickInteraction, indication = null, onClick = onClick), - ) { - Box( - Modifier - .fillMaxWidth() - .weight(1f) - .clip(RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp)), - ) { - val imageUrl = app.getCapsuleUrl() - - AsyncImage( - model = - ImageRequest - .Builder(context) - .data(imageUrl) - .crossfade(300) - .build(), - contentDescription = null, - modifier = Modifier.fillMaxSize(), - contentScale = ContentScale.Crop, - ) - - if (isInstalled) { - Icon( - Icons.Outlined.CheckCircle, - contentDescription = "Installed", - tint = StatusOnline, - modifier = Modifier.align(Alignment.BottomEnd).padding(8.dp).size(24.dp), - ) - } - } - - Text( - text = app.name, - modifier = - Modifier - .fillMaxWidth() - .padding(horizontal = 4.dp, vertical = 4.dp) - .then(if (effectiveFocus) Modifier.basicMarquee(iterations = Int.MAX_VALUE) else Modifier), - style = MaterialTheme.typography.bodySmall, - color = TextPrimary, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - textAlign = TextAlign.Center, - ) - } - } - } - - // Downloads Tab - @Composable - fun DownloadsTab( - selectedId: String?, - animationsActive: Boolean = true, - onSelectDownload: (String?) -> Unit, - ) { - val downloads = remember { mutableStateListOf>() } - var tick by remember { mutableIntStateOf(0) } - val scope = rememberCoroutineScope() - - val syncDownloads = - remember(selectedId, onSelectDownload) { - { - val currentDownloads = DownloadService.getAllDownloads() - downloads.clear() - downloads.addAll(currentDownloads) - if (selectedId != null && currentDownloads.none { it.first == selectedId }) { - onSelectDownload(null) - } - } - } - val latestSyncDownloads by rememberUpdatedState(syncDownloads) - - val downloadStatusListener = - remember { - object : EventDispatcher.JavaEventListener { - override fun onEvent(event: Any) { - if (event is AndroidEvent.DownloadStatusChanged) { - scope.launch { - latestSyncDownloads() - } - } - } - } - } - - DisposableEffect(downloadStatusListener, syncDownloads) { - syncDownloads() - PluviaApp.events.onJava(AndroidEvent.DownloadStatusChanged::class, downloadStatusListener) - onDispose { - PluviaApp.events.offJava(AndroidEvent.DownloadStatusChanged::class, downloadStatusListener) - } - } - - // Re-sync the list whenever the cross-store DownloadCoordinator records change. This - // is what makes PAUSED records (loaded from DB after app restart) appear in the tab, - // and what removes COMPLETE/CANCELLED/FAILED rows after Clear. - LaunchedEffect(syncDownloads) { - com.winlator.cmod.app.service.download.DownloadCoordinator.changes.collect { - latestSyncDownloads() - } - } - - downloads.forEach { (_, info) -> - LaunchedEffect(info) { - info.getStatusFlow().collect { - tick++ - } - } - } - - Column( - Modifier - .fillMaxSize() - .windowInsetsPadding(WindowInsets.navigationBars.only(WindowInsetsSides.Bottom)) - .tabScreenPadding(top = DownloadsHeaderTopPadding), - ) { - // Read tick to ensure global button state reacts to per-download status changes. - @Suppress("UNUSED_EXPRESSION") - tick - - Row( - modifier = Modifier.fillMaxWidth().padding(bottom = 10.dp), - horizontalArrangement = Arrangement.spacedBy(10.dp, Alignment.CenterHorizontally), - verticalAlignment = Alignment.CenterVertically, - ) { - val selectedInfo = downloads.find { it.first == selectedId }?.second - val selectedStatus = selectedInfo?.getStatusFlow()?.value - val isPaused = selectedStatus == DownloadPhase.PAUSED - val isComplete = selectedStatus == DownloadPhase.COMPLETE - val isCancelled = selectedStatus == DownloadPhase.CANCELLED - val pausableDownloads = - downloads.filter { - val status = it.second.getStatusFlow().value - status != DownloadPhase.COMPLETE && status != DownloadPhase.CANCELLED - } - val allPausableDownloadsPaused = - pausableDownloads.isNotEmpty() && - pausableDownloads.all { - it.second.getStatusFlow().value == DownloadPhase.PAUSED - } - - val pauseResumeLabel = - if (selectedId == null) { - if (allPausableDownloadsPaused) { - stringResource( - R.string.downloads_queue_resume_all, - ) - } else { - stringResource(R.string.downloads_queue_pause_all) - } - } else { - if (isPaused) stringResource(R.string.session_drawer_resume) else stringResource(R.string.session_drawer_pause) - } - - val cancelLabel = - if (selectedId == null) { - stringResource(R.string.downloads_queue_cancel_all) - } else { - stringResource(R.string.common_ui_cancel) - } - - // Disable pause/resume for completed or cancelled downloads - val pauseResumeEnabled = - if (selectedId != null) { - !isComplete && !isCancelled - } else { - pausableDownloads.isNotEmpty() - } - - val cancelEnabled = - if (selectedId != null) { - !isComplete && !isCancelled - } else { - pausableDownloads.isNotEmpty() - } - - DownloadsQueueButton( - label = pauseResumeLabel, - accentColor = Accent, - onClick = { - if (selectedId == null) { - if (allPausableDownloadsPaused) { - DownloadService.resumeAll() - } else { - DownloadService.pauseAll() - } - } else { - if (isPaused) { - DownloadService.resumeDownload(selectedId) - } else { - DownloadService.pauseDownload(selectedId) - } - } - }, - enabled = pauseResumeEnabled, - ) - - DownloadsQueueButton( - label = cancelLabel, - accentColor = DangerRed, - onClick = { - if (selectedId == null) { - DownloadService.cancelAll() - onSelectDownload(null) - } else { - DownloadService.cancelDownload(selectedId) - onSelectDownload(null) - } - }, - enabled = cancelEnabled, - ) - - // Clear button - clears completed, cancelled, and failed downloads - val hasCompletedOrCancelled = - downloads.any { - val s = it.second.getStatusFlow().value - s == DownloadPhase.COMPLETE || s == DownloadPhase.CANCELLED || s == DownloadPhase.FAILED - } - - DownloadsQueueButton( - label = stringResource(R.string.downloads_queue_clear), - accentColor = TextSecondary, - onClick = { - DownloadService.clearCompletedDownloads() - }, - enabled = hasCompletedOrCancelled, - ) - } - - val listState = rememberLazyListState() - val activity = LocalContext.current as? UnifiedActivity - val density = LocalContext.current.resources.displayMetrics.density - - LaunchedEffect(listState) { - activity?.rightStickScrollState?.collect { rz -> - if (kotlin.math.abs(rz) > 0.1f) { - // Max scroll speed is 20 rows per second (approx 20 * 100dp / 60fps ~ 32dp per frame) - // Min scroll speed is 0.75 rows per second (approx 0.75 * 100dp / 60fps ~ 1.25dp per frame) - // Use a square curve for more gradual acceleration - val speedFactor = kotlin.math.abs(rz) - val curveFactor = speedFactor * speedFactor - val baseSpeed = 1.25f + (curveFactor * (32f - 1.25f)) - val direction = if (rz > 0) 1f else -1f - - // Using a loop while the stick is held - while (kotlin.math.abs(activity.rightStickScrollState.value) > 0.1f) { - val currentRz = activity.rightStickScrollState.value - val currentSpeedFactor = kotlin.math.abs(currentRz) - val currentCurveFactor = currentSpeedFactor * currentSpeedFactor - val currentBaseSpeed = 1.25f + (currentCurveFactor * (32f - 1.25f)) - val currentDirection = if (currentRz > 0) 1f else -1f - - val pixelsToScroll = currentBaseSpeed * currentDirection * density - listState.dispatchRawDelta(pixelsToScroll) - kotlinx.coroutines.delay(16) // roughly 60fps - } - } - } - } - - // Sort so the user always sees what's actually running first, then everything - // they can resume, then finished items, with cancelled at the very bottom. - // The list re-sorts on phase transitions because `tick` (incremented by the - // status flow collectors above) is read here, forcing recomposition. - @Suppress("UNUSED_EXPRESSION") - tick - val sortedDownloads = - downloads.sortedBy { (_, info) -> - when (info.getStatusFlow().value) { - // In-progress states grouped together at the top. - DownloadPhase.DOWNLOADING, - DownloadPhase.PREPARING, - DownloadPhase.VERIFYING, - DownloadPhase.PATCHING, - DownloadPhase.APPLYING_DATA, - DownloadPhase.FINALIZING, - DownloadPhase.UNPACKING, - DownloadPhase.UNKNOWN, - -> 0 - DownloadPhase.PAUSED -> 1 - DownloadPhase.QUEUED -> 2 - DownloadPhase.COMPLETE -> 3 - DownloadPhase.FAILED -> 4 - DownloadPhase.CANCELLED -> 5 - } - } - - if (sortedDownloads.isEmpty()) { - Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.Center, - ) { - EmptyStateMessage(stringResource(R.string.downloads_queue_empty)) - } - } else { - LazyColumn(state = listState, modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.spacedBy(8.dp)) { - items(sortedDownloads, key = { it.first }) { (id, info) -> - DownloadItemDeck( - id, - info, - isSelected = selectedId == id, - animationsActive = animationsActive, - onClick = { - if (selectedId == id) onSelectDownload(null) else onSelectDownload(id) - }, - ) - } - } - } - } - } - - @Composable - private fun DownloadsQueueButton( - label: String, - accentColor: Color, - enabled: Boolean, - modifier: Modifier = Modifier, - onClick: () -> Unit, - ) { - val contentColor = if (enabled) accentColor else TextSecondary.copy(alpha = 0.48f) - - Button( - onClick = onClick, - enabled = enabled, - modifier = modifier.height(40.dp).widthIn(min = 96.dp), - colors = - ButtonDefaults.buttonColors( - containerColor = DownloadButtonBlack, - contentColor = contentColor, - disabledContainerColor = DownloadButtonBlack.copy(alpha = 0.18f), - disabledContentColor = TextSecondary.copy(alpha = 0.48f), - ), - border = BorderStroke(1.dp, contentColor.copy(alpha = if (enabled) 0.55f else 0.24f)), - contentPadding = PaddingValues(horizontal = 8.dp), - shape = RoundedCornerShape(8.dp), - ) { - Text( - label, - color = contentColor, - fontSize = 12.sp, - fontWeight = FontWeight.SemiBold, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - } - } - - @Composable - private fun AnimatedDownloadProgressFill( - modifier: Modifier, - widthPx: Float, - ) { - val infiniteTransition = rememberInfiniteTransition(label = "downloadProgressGradient") - val gradientOffset by infiniteTransition.animateFloat( - initialValue = -widthPx, - targetValue = 0f, - animationSpec = - infiniteRepeatable( - animation = tween(durationMillis = 5000, easing = LinearEasing), - repeatMode = RepeatMode.Restart, - ), - label = "downloadProgressGradientOffset", - ) - - Box( - modifier.background( - Brush.horizontalGradient( - colorStops = DownloadChaseGradientStops, - startX = gradientOffset, - endX = gradientOffset + (widthPx * 2f), - tileMode = TileMode.Repeated, - ), - ), - ) - } - - @Composable - private fun DownloadChasingProgressBar( - progress: Float, - status: DownloadPhase, - animationsActive: Boolean, - modifier: Modifier = Modifier, - ) { - val clampedProgress = progress.coerceIn(0f, 1f) - val shouldUseActiveGradient = - when (status) { - DownloadPhase.DOWNLOADING, - DownloadPhase.QUEUED, - DownloadPhase.PREPARING, - DownloadPhase.VERIFYING, - DownloadPhase.PATCHING, - DownloadPhase.APPLYING_DATA, - DownloadPhase.FINALIZING, - DownloadPhase.UNPACKING, - -> true - else -> false - } - val shouldAnimate = shouldUseActiveGradient && animationsActive - val fillColor = - when (status) { - DownloadPhase.FAILED, - DownloadPhase.CANCELLED, - -> DangerRed - DownloadPhase.COMPLETE -> StatusOnline - DownloadPhase.PAUSED -> TextSecondary - else -> Accent - } - - BoxWithConstraints( - modifier = - modifier - .clip(CircleShape) - .background(Color.Black.copy(alpha = 0.34f)), - ) { - val density = LocalDensity.current - val widthPx = with(density) { maxWidth.toPx().coerceAtLeast(1f) } - - if (clampedProgress > 0f) { - val fillModifier = - Modifier - .fillMaxHeight() - .fillMaxWidth(clampedProgress) - .clip(RectangleShape) - - if (shouldUseActiveGradient) { - if (shouldAnimate) { - AnimatedDownloadProgressFill(fillModifier, widthPx) - } else { - Box( - fillModifier.background( - Brush.horizontalGradient( - colorStops = DownloadChaseGradientStops, - endX = widthPx * 2f, - tileMode = TileMode.Repeated, - ), - ), - ) - } - } else { - Box(fillModifier.background(fillColor)) - } - } - } - } - - @Composable - fun DownloadItemDeck( - id: String, - info: DownloadInfo, - isSelected: Boolean, - animationsActive: Boolean, - onClick: () -> Unit, - ) { - var progress by remember { mutableFloatStateOf(info.getProgress()) } - var showDeleteDialog by remember { mutableStateOf(false) } - - DisposableEffect(info) { - val listener: (Float) -> Unit = { progress = it } - info.addProgressListener(listener) - onDispose { info.removeProgressListener(listener) } - } - val status by info.getStatusFlow().collectAsState() - val statusMessage by info.getStatusMessageFlow().collectAsState() - var previousStatus by remember { mutableStateOf(status) } - var showCompletedProgressBar by remember { mutableStateOf(status != DownloadPhase.COMPLETE) } - val isSteam = id.startsWith("STEAM_") - val isEpic = id.startsWith("EPIC_") - val isGog = id.startsWith("GOG_") - val appId = - if (isSteam) { - id.removePrefix("STEAM_").toIntOrNull() ?: 0 - } else if (isEpic) { - id.removePrefix("EPIC_").toIntOrNull() ?: 0 - } else { - 0 - } - val gogId = if (isGog) id.removePrefix("GOG_") else "" - - var steamApp by remember(appId) { mutableStateOf(null) } - var epicGame by remember(appId) { mutableStateOf(null) } - var gogGame by remember(gogId) { mutableStateOf(null) } - val context = LocalContext.current - var isFocused by remember { mutableStateOf(false) } - val clickInteractionSource = remember { MutableInteractionSource() } - val animatedProgress by animateFloatAsState( - targetValue = if (status == DownloadPhase.COMPLETE) 1f else progress.coerceIn(0f, 1f), - animationSpec = tween(durationMillis = 650, easing = FastOutSlowInEasing), - label = "downloadItemProgress", - ) - - LaunchedEffect(status) { - if (status == DownloadPhase.COMPLETE) { - if (previousStatus != DownloadPhase.COMPLETE) { - showCompletedProgressBar = true - delay(900) - } - showCompletedProgressBar = false - } else { - showCompletedProgressBar = true - } - previousStatus = status - } - - LaunchedEffect(appId, gogId, isSteam, isEpic, isGog) { - withContext(Dispatchers.IO) { - if (isSteam) { - steamApp = db.steamAppDao().findApp(appId) - } else if (isEpic) { - epicGame = EpicService.getEpicGameOf(appId) - } else if (isGog) { - gogGame = GOGService.getGOGGameOf(gogId) - } - } - } - - val unknownGameLabel = stringResource(R.string.library_games_unknown_game) - val displayName = - if (isSteam) { - steamApp?.name - } else if (isEpic) { - epicGame?.title - } else if (isGog) { - gogGame?.title - } else { - unknownGameLabel - } - val displayImage = - if (isSteam) { - steamApp?.getHeaderImageUrl() - } else if (isEpic) { - epicGame?.primaryImageUrl ?: epicGame?.iconUrl - } else if (isGog) { - gogGame?.imageUrl ?: gogGame?.iconUrl - } else { - null - } - - Surface( - color = if (isSelected) DownloadCardSelectedBlack else DownloadCardBlack, - shape = RoundedCornerShape(12.dp), - modifier = - Modifier - .fillMaxWidth() - .chasingBorder( - isFocused = isFocused || isSelected, - paused = chasingBordersPaused.value || !animationsActive, - cornerRadius = 12.dp, - borderWidth = 2.dp, - animationDurationMs = 8000, - ) - .onFocusChanged { isFocused = it.isFocused } - .focusable() - .clickable( - interactionSource = clickInteractionSource, - indication = null, - onClick = onClick, - ), - ) { - Row(Modifier.padding(12.dp), verticalAlignment = Alignment.CenterVertically) { - AsyncImage( - model = - ImageRequest - .Builder(context) - .data(displayImage) - .crossfade(300) - .build(), - contentDescription = null, - modifier = Modifier.size(120.dp, 68.dp).clip(RoundedCornerShape(4.dp)), - contentScale = ContentScale.Crop, - ) - - Spacer(Modifier.width(16.dp)) - - Column(Modifier.weight(1f)) { - val currentFile by info.getCurrentFileNameFlow().collectAsState() - val (downloadedBytes, totalBytes) = info.getBytesProgress() - val speed = info.getCurrentDownloadSpeed() ?: 0L - val percentage = (animatedProgress * 100).roundToInt() - val showDownloadSpeed = - status == DownloadPhase.DOWNLOADING && - progress < 1f && - speed > 0 - - Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) { - Text( - displayName ?: unknownGameLabel, - fontWeight = FontWeight.Bold, - color = TextPrimary, - modifier = Modifier.weight(1f), - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - - // Centered Size Info - Text( - text = "${StorageUtils.formatBinarySize(downloadedBytes)} / ${StorageUtils.formatBinarySize(totalBytes)}", - style = MaterialTheme.typography.labelMedium, - color = TextSecondary, - modifier = Modifier.weight(1f), - textAlign = TextAlign.Center, - ) - - Box(modifier = Modifier.weight(1f), contentAlignment = Alignment.CenterEnd) { - if (showDownloadSpeed) { - Text( - text = "${StorageUtils.formatBinarySize(speed)}/s", - style = MaterialTheme.typography.labelMedium, - color = Accent, - fontWeight = FontWeight.Bold, - ) - } - } - } - - val statusText = - when (status) { - DownloadPhase.DOWNLOADING -> { - currentFile?.let { - stringResource(R.string.downloads_queue_phase_downloading_file, it.take(10)) - } ?: stringResource(R.string.downloads_queue_phase_downloading) - } - - DownloadPhase.PAUSED -> { - stringResource(R.string.downloads_queue_phase_paused) - } - - DownloadPhase.QUEUED -> { - stringResource(R.string.downloads_queue_phase_queued) - } - - DownloadPhase.PREPARING -> { - stringResource(R.string.downloads_queue_phase_preparing) - } - - DownloadPhase.VERIFYING -> { - currentFile?.let { - stringResource(R.string.downloads_queue_phase_verifying_file, it.take(10)) - } ?: stringResource(R.string.downloads_queue_phase_verifying) - } - - DownloadPhase.PATCHING -> { - stringResource(R.string.downloads_queue_phase_patching) - } - - DownloadPhase.APPLYING_DATA -> { - stringResource(R.string.downloads_queue_phase_applying_data) - } - - DownloadPhase.FINALIZING -> { - stringResource(R.string.downloads_queue_phase_finalizing) - } - - DownloadPhase.UNPACKING -> { - stringResource(R.string.downloads_queue_phase_unpacking) - } - - DownloadPhase.COMPLETE -> { - stringResource(R.string.downloads_queue_phase_complete) - } - - DownloadPhase.CANCELLED -> { - stringResource(R.string.downloads_queue_phase_cancelled) - } - - DownloadPhase.FAILED -> { - stringResource( - R.string.downloads_queue_phase_failed, - if (statusMessage != null && - statusMessage != "null" - ) { - statusMessage!! - } else { - stringResource(R.string.common_ui_unknown_error) - }, - ) - } - - else -> { - stringResource(R.string.downloads_queue_phase_unknown) - } - } - val statusColor = - when (status) { - DownloadPhase.COMPLETE -> StatusOnline - DownloadPhase.FAILED, - DownloadPhase.CANCELLED, - -> DangerRed - DownloadPhase.PAUSED, - DownloadPhase.QUEUED, - -> StatusAway - DownloadPhase.DOWNLOADING, - DownloadPhase.PREPARING, - DownloadPhase.VERIFYING, - DownloadPhase.PATCHING, - DownloadPhase.APPLYING_DATA, - DownloadPhase.FINALIZING, - DownloadPhase.UNPACKING, - -> Accent - else -> TextSecondary - } - - Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) { - Text( - stringResource(R.string.downloads_queue_status_label), - style = MaterialTheme.typography.bodySmall, - color = TextSecondary, - maxLines = 1, - ) - Spacer(Modifier.width(4.dp)) - Text( - statusText, - style = MaterialTheme.typography.bodySmall, - color = statusColor, - modifier = Modifier.weight(1f), - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - } - - AnimatedVisibility( - visible = status != DownloadPhase.COMPLETE || showCompletedProgressBar, - exit = fadeOut(tween(180)) + shrinkVertically(tween(180)), - ) { - Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(top = 8.dp)) { - DownloadChasingProgressBar( - progress = if (status == DownloadPhase.COMPLETE) 1f else animatedProgress, - status = status, - animationsActive = animationsActive, - modifier = Modifier.weight(1f).height(9.dp).padding(end = 10.dp), - ) - Text( - text = "$percentage%", - style = MaterialTheme.typography.labelMedium, - color = if (status == DownloadPhase.COMPLETE) StatusOnline else TextPrimary, - modifier = Modifier.width(40.dp), - ) - } - } - } - - IconButton( - onClick = { showDeleteDialog = true }, - enabled = status != DownloadPhase.COMPLETE && status != DownloadPhase.CANCELLED, - ) { - Icon( - Icons.Outlined.Close, - contentDescription = stringResource(R.string.downloads_queue_cancel_download), - tint = - if (status != DownloadPhase.COMPLETE && - status != DownloadPhase.CANCELLED - ) { - Color(0xFFFF6B6B) - } else { - TextSecondary - }, - ) - } - if (ControllerHelper.isControllerConnected()) { - Spacer(Modifier.width(8.dp)) - ControllerBadge(if (ControllerHelper.isPlayStationController()) "\u2715" else "A") - } - } - } - - if (showDeleteDialog) { - val gameName = - if (id.startsWith("STEAM_")) { - steamApp?.name - } else if (id.startsWith("EPIC_")) { - epicGame?.title - } else if (id.startsWith("GOG_")) { - gogGame?.title - } else { - null - } - AlertDialog( - onDismissRequest = { showDeleteDialog = false }, - containerColor = SurfaceDark, - title = { Text(stringResource(R.string.downloads_queue_cancel_download), color = TextPrimary) }, - text = { - Text( - stringResource( - R.string.downloads_queue_cancel_download_confirm, - gameName ?: stringResource(R.string.downloads_queue_this_game), - ), - color = TextSecondary, - ) - }, - confirmButton = { - Button( - onClick = { - showDeleteDialog = false - DownloadService.cancelDownload(id) - }, - colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFFF6B6B)), - ) { - Text(stringResource(R.string.downloads_queue_cancel_download), color = Color.White) - } - }, - dismissButton = { - Button( - onClick = { showDeleteDialog = false }, - colors = ButtonDefaults.buttonColors(containerColor = CardDark), - ) { - Text(stringResource(R.string.common_ui_cancel), color = TextPrimary) - } - }, - ) - } - } - - // Game Manager Dialog - @Composable - fun GameManagerDialog( - app: SteamApp, - onDismissRequest: () -> Unit, - ) { - val context = LocalContext.current - var isLoading by remember { mutableStateOf(true) } - var manifestSizes by remember { mutableStateOf(SteamService.ManifestSizes()) } - var dlcApps by remember { mutableStateOf>(emptyList()) } - var installed by remember(app.id) { mutableStateOf(null) } - val selectedDlcIds = remember { mutableStateListOf() } - var customPath by remember { mutableStateOf(null) } - var showCustomPathWarning by remember { mutableStateOf(false) } - var showDlcDialog by remember { mutableStateOf(false) } - val scope = rememberCoroutineScope() - - if (showCustomPathWarning) { - CustomPathWarningDialog( - onDismiss = { showCustomPathWarning = false }, - onProceed = { - showCustomPathWarning = false - DirectoryPickerDialog.show( - activity = this@UnifiedActivity, - initialPath = customPath ?: SteamService.defaultAppInstallPath, - title = getString(R.string.settings_content_install_directory), - ) { path -> customPath = path } - }, - ) - } - - if (showDlcDialog && dlcApps.isNotEmpty()) { - GameSettingsDialogFrame( - title = stringResource(R.string.library_games_dlcs), - onDismissRequest = { showDlcDialog = false }, - ) { - Column( - modifier = - Modifier - .heightIn(max = 300.dp) - .verticalScroll(rememberScrollState()), - ) { - dlcApps.forEachIndexed { index, dlc -> - if (index > 0) { - HorizontalDivider( - color = CardBorder.copy(alpha = 0.5f), - thickness = 0.5.dp, - modifier = Modifier.padding(horizontal = 16.dp), - ) - } - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = - Modifier - .fillMaxWidth() - .clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = null, - ) { - if (selectedDlcIds.contains(dlc.id)) { - selectedDlcIds.remove(dlc.id) - } else { - selectedDlcIds.add(dlc.id) - } - }.padding(horizontal = 16.dp, vertical = 2.dp), - ) { - Checkbox( - checked = selectedDlcIds.contains(dlc.id), - onCheckedChange = { if (it) selectedDlcIds.add(dlc.id) else selectedDlcIds.remove(dlc.id) }, - colors = - CheckboxDefaults.colors( - checkedColor = Accent, - uncheckedColor = TextSecondary, - checkmarkColor = Color.White, - ), - ) - Text(dlc.name, color = TextPrimary, fontSize = 13.sp) - } - } - } - } - } - - val selectedDlcIdsKey = selectedDlcIds.toList().sorted().joinToString(",") - - LaunchedEffect(app.id) { - val (downloadableDlcApps, sizes, isInstalled) = - withContext(Dispatchers.IO) { - Triple( - db.steamAppDao().findDownloadableDLCApps(app.id) ?: emptyList(), - SteamService.getSelectedManifestSizes(app.id), - SteamService.isAppInstalled(app.id), - ) - } - dlcApps = downloadableDlcApps - manifestSizes = sizes - installed = isInstalled - isLoading = false - } - - LaunchedEffect(app.id, selectedDlcIdsKey) { - if (isLoading) return@LaunchedEffect - manifestSizes = - withContext(Dispatchers.IO) { - SteamService.getSelectedManifestSizes(app.id, selectedDlcIds.toList()) - } - } - - val totalInstallSize = manifestSizes.installSize - val totalDownloadSize = manifestSizes.downloadSize - val defaultPathSet = - if (PrefManager.useSingleDownloadFolder) { - PrefManager.defaultDownloadFolder.isNotEmpty() - } else { - PrefManager.steamDownloadFolder - .isNotEmpty() - } - val effectivePath = customPath ?: SteamService.defaultAppInstallPath - val availableBytes = - try { - StorageUtils.getAvailableSpace(effectivePath) - } catch (e: Exception) { - 0L - } - val isInstallEnabled = availableBytes >= totalInstallSize - val installPathDisplay = customPath ?: SteamService.defaultAppInstallPath - - StoreInstallDialogShell( - title = app.name, - heroImageUrl = app.getHeroUrl(), - subtitle = - listOfNotNull( - app.developer.takeIf { it.isNotBlank() }, - app.publisher.takeIf { it.isNotBlank() }, - ).joinToString(" • "), - sourceLabel = "Steam", - onDismissRequest = onDismissRequest, - infoContent = { - if (isLoading) { - Spacer(Modifier.height(18.dp)) - CircularProgressIndicator(color = Accent) - } else { - DetailCard( - label = stringResource(R.string.library_games_install_path), - value = installPathDisplay, - ) - DetailCard( - stringResource(R.string.library_games_download_slash_install), - stringResource( - R.string.library_games_download_install_available, - StorageUtils.formatBinarySize(totalDownloadSize), - StorageUtils.formatBinarySize(totalInstallSize), - StorageUtils.formatBinarySize(availableBytes), - ), - valueColor = if (!isInstallEnabled) DangerRed else null, - ) - } - }, - ) { - if (installed == false) { - InstallButton( - loading = isLoading, - onClick = { - scope.launch(Dispatchers.IO) { - SteamService.downloadApp(app.id, selectedDlcIds.toList(), false, customPath) - withContext(Dispatchers.Main) { onDismissRequest() } - } - }, - ) - } - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(6.dp), - ) { - CompactActionButton( - icon = Icons.Outlined.Folder, - label = - if (customPath != - null - ) { - stringResource(R.string.common_ui_custom) - } else if (defaultPathSet) { - stringResource(R.string.common_ui_already_set) - } else { - stringResource(R.string.common_ui_custom) - }, - modifier = Modifier.weight(1f), - onClick = { - if (customPath == null && defaultPathSet) { - showCustomPathWarning = true - } else { - DirectoryPickerDialog.show( - activity = this@UnifiedActivity, - initialPath = customPath ?: SteamService.defaultAppInstallPath, - title = getString(R.string.settings_content_install_directory), - ) { path -> customPath = path } - } - }, - ) - if (dlcApps.isNotEmpty()) { - CompactActionButton( - icon = Icons.Outlined.Extension, - label = stringResource(R.string.library_games_dlcs), - modifier = Modifier.weight(1f), - onClick = { showDlcDialog = true }, - ) - } - } - } - } - - private fun findLibraryShortcutForGame( - containerManager: ContainerManager, - app: SteamApp, - isCustom: Boolean, - isEpic: Boolean, - epicId: Int, - ): Shortcut? = findShortcutForGame(containerManager.loadShortcuts(), app, isCustom, isEpic, epicId) - - private fun findShortcutForGame( - shortcuts: List, - app: SteamApp, - isCustom: Boolean, - isEpic: Boolean, - epicId: Int, - ): Shortcut? = - when { - isEpic -> { - shortcuts.find { - it.getExtra("game_source") == "EPIC" && it.getExtra("app_id") == epicId.toString() - } - } - - else -> { - shortcuts.find { - it.getExtra("app_id") == app.id.toString() || it.getExtra("custom_name") == app.name || it.name == app.name - } - } - } - - private fun isShortcutCloudSyncEnabled(shortcut: Shortcut?): Boolean = - shortcut == null || shortcut.getExtra("cloud_sync_disabled", "0") != "1" - - private fun setShortcutCloudSyncEnabled( - shortcut: Shortcut?, - enabled: Boolean, - ) { - if (shortcut == null) return - shortcut.putExtra("cloud_sync_disabled", if (enabled) null else "1") - if (enabled) { - shortcut.putExtra("cloud_force_download", null) - } - shortcut.saveData() - } - - private fun isShortcutOfflineMode(shortcut: Shortcut?): Boolean = - shortcut != null && shortcut.getExtra("offline_mode", "0") == "1" - - private fun setShortcutOfflineMode( - shortcut: Shortcut?, - enabled: Boolean, - ) { - if (shortcut == null) return - shortcut.putExtra("offline_mode", if (enabled) "1" else null) - shortcut.saveData() - } - - @Composable - private fun CloudSavesContent( - isWorking: Boolean, - cloudSyncEnabled: Boolean, - offlineModeEnabled: Boolean, - gameSource: GameSaveBackupManager.GameSource, - gameId: String, - gameName: String, - shortcut: Shortcut?, - onCloudSyncToggle: (Boolean) -> Unit, - onOfflineModeToggle: (Boolean) -> Unit, - onBackup: () -> Unit, - onRestore: () -> Unit, - onSyncFromCloud: () -> Unit, - onBack: () -> Unit, - ) { - val scope = rememberCoroutineScope() - val context = LocalContext.current - var historyRefreshKey by remember { mutableStateOf(0) } - var historyLoading by remember { mutableStateOf(true) } - var historyEntries by remember { mutableStateOf>(emptyList()) } - var entryPendingRestore by remember { - mutableStateOf(null) - } - var entryPendingRename by remember { - mutableStateOf(null) - } - var entryPendingDelete by remember { - mutableStateOf(null) - } - - LaunchedEffect(gameSource, gameId, historyRefreshKey) { - historyLoading = true - historyEntries = - GameSaveBackupManager.listBackupHistory( - this@UnifiedActivity, - gameSource, - gameId, - gameName, - ) - historyLoading = false - } - - // Auto-refresh the history list whenever a backup/restore finishes. - var wasWorking by remember { mutableStateOf(false) } - LaunchedEffect(isWorking) { - if (wasWorking && !isWorking) historyRefreshKey++ - wasWorking = isWorking - } - - Column( - modifier = - Modifier - .fillMaxWidth() - .padding(horizontal = 24.dp, vertical = 20.dp), - verticalArrangement = Arrangement.spacedBy(12.dp), - ) { - Text( - stringResource(R.string.cloud_saves_title), - style = MaterialTheme.typography.labelMedium, - color = TextSecondary, - fontWeight = FontWeight.Bold, - letterSpacing = 1.1.sp, - ) - - TogglePairCard( - cloudSyncEnabled = cloudSyncEnabled, - offlineModeEnabled = offlineModeEnabled, - onCloudSyncToggle = onCloudSyncToggle, - onOfflineModeToggle = onOfflineModeToggle, - ) - - if (isWorking) { - LinearProgressIndicator( - modifier = Modifier.fillMaxWidth(), - color = Accent, - trackColor = CardBorder, - ) - } - - val providerLabel = - when (gameSource) { - GameSaveBackupManager.GameSource.STEAM -> stringResource(R.string.preloader_platform_steam) - GameSaveBackupManager.GameSource.EPIC -> stringResource(R.string.preloader_platform_epic) - GameSaveBackupManager.GameSource.GOG -> stringResource(R.string.preloader_platform_gog) - } - - ActionWithHelper( - icon = Icons.Outlined.CloudSync, - label = stringResource(R.string.cloud_saves_sync_from_provider, providerLabel), - helper = stringResource(R.string.cloud_saves_sync_summary, providerLabel), - onClick = { if (!isWorking) onSyncFromCloud() }, - ) - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(10.dp), - ) { - ActionWithHelper( - icon = Icons.Outlined.CloudUpload, - label = stringResource(R.string.cloud_saves_backup), - helper = stringResource(R.string.cloud_saves_backup_summary), - modifier = Modifier.weight(1f), - onClick = onBackup, - ) - ActionWithHelper( - icon = Icons.Outlined.CloudDownload, - label = stringResource(R.string.cloud_saves_restore), - helper = stringResource(R.string.cloud_saves_restore_summary), - modifier = Modifier.weight(1f), - onClick = onRestore, - ) - } - - SaveHistorySection( - loading = historyLoading, - entries = historyEntries, - onRefresh = { historyRefreshKey++ }, - onRestore = { entry -> entryPendingRestore = entry }, - onRename = { entry -> entryPendingRename = entry }, - onDelete = { entry -> entryPendingDelete = entry }, - ) - - Spacer(Modifier.height(4.dp)) - TextButton(onClick = onBack) { - Icon( - Icons.AutoMirrored.Outlined.ArrowBack, - contentDescription = null, - tint = TextSecondary, - modifier = Modifier.size(18.dp), - ) - Spacer(Modifier.width(6.dp)) - Text(stringResource(R.string.common_ui_back), color = TextSecondary) - } - } - - entryPendingRestore?.let { entry -> - val whenLabel = - remember(entry.timestampMs) { - android.text.format.DateUtils - .getRelativeTimeSpanString( - entry.timestampMs, - System.currentTimeMillis(), - android.text.format.DateUtils.MINUTE_IN_MILLIS, - ).toString() - } - AlertDialog( - onDismissRequest = { entryPendingRestore = null }, - title = { - Text( - stringResource(R.string.cloud_saves_history_restore_confirm_title), - color = TextPrimary, - ) - }, - text = { - Text( - stringResource(R.string.cloud_saves_history_restore_confirm_body, whenLabel), - color = TextSecondary, - ) - }, - confirmButton = { - TextButton(onClick = { - val target = entryPendingRestore ?: return@TextButton - entryPendingRestore = null - scope.launch { - val result = - GameSaveBackupManager.restoreFromHistoryEntry( - this@UnifiedActivity, - gameSource, - gameId, - target, - ) - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - if (result.success) { - context.getString(R.string.cloud_saves_history_restore_success) - } else { - context.getString(R.string.cloud_saves_history_restore_failed) - }, - android.widget.Toast.LENGTH_SHORT, - ) - historyRefreshKey++ - } - }) { Text(stringResource(R.string.cloud_saves_history_restore), color = Accent) } - }, - dismissButton = { - TextButton(onClick = { entryPendingRestore = null }) { - Text(stringResource(R.string.common_ui_cancel), color = TextSecondary) - } - }, - containerColor = SurfaceDark, - ) - } - - entryPendingRename?.let { entry -> - var labelInput by remember(entry.fileId) { mutableStateOf(entry.label.orEmpty()) } - AlertDialog( - onDismissRequest = { entryPendingRename = null }, - title = { - Text( - stringResource(R.string.cloud_saves_history_rename_title), - color = TextPrimary, - ) - }, - text = { - OutlinedTextField( - value = labelInput, - onValueChange = { v -> - labelInput = v.take(GameSaveBackupManager.MAX_HISTORY_LABEL_LENGTH) - }, - singleLine = true, - placeholder = { - Text( - stringResource(R.string.cloud_saves_history_rename_hint), - color = TextSecondary, - ) - }, - textStyle = MaterialTheme.typography.bodyMedium.copy(color = TextPrimary), - colors = - OutlinedTextFieldDefaults.colors( - focusedBorderColor = Accent, - unfocusedBorderColor = CardBorder, - focusedTextColor = TextPrimary, - unfocusedTextColor = TextPrimary, - cursorColor = Accent, - ), - modifier = Modifier.fillMaxWidth(), - ) - }, - confirmButton = { - TextButton(onClick = { - val target = entryPendingRename ?: return@TextButton - val newLabel = labelInput - entryPendingRename = null - scope.launch { - val result = - GameSaveBackupManager.renameBackupHistoryEntry( - this@UnifiedActivity, - target, - newLabel, - ) - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - if (result.success) { - context.getString(R.string.cloud_saves_history_rename_success) - } else { - context.getString(R.string.cloud_saves_history_rename_failed) - }, - android.widget.Toast.LENGTH_SHORT, - ) - historyRefreshKey++ - } - }) { - Text(stringResource(R.string.cloud_saves_history_rename_save), color = Accent) - } - }, - dismissButton = { - Row { - if (!entry.label.isNullOrBlank()) { - TextButton(onClick = { - val target = entryPendingRename ?: return@TextButton - entryPendingRename = null - scope.launch { - GameSaveBackupManager.renameBackupHistoryEntry( - this@UnifiedActivity, - target, - null, - ) - historyRefreshKey++ - } - }) { - Text(stringResource(R.string.cloud_saves_history_rename_clear), color = TextSecondary) - } - } - TextButton(onClick = { entryPendingRename = null }) { - Text(stringResource(R.string.common_ui_cancel), color = TextSecondary) - } - } - }, - containerColor = SurfaceDark, - ) - } - - entryPendingDelete?.let { entry -> - AlertDialog( - onDismissRequest = { entryPendingDelete = null }, - title = { - Text( - stringResource(R.string.cloud_saves_history_delete_confirm_title), - color = TextPrimary, - ) - }, - text = { - Text( - stringResource(R.string.cloud_saves_history_delete_confirm_body), - color = TextSecondary, - ) - }, - confirmButton = { - TextButton(onClick = { - val target = entryPendingDelete ?: return@TextButton - entryPendingDelete = null - scope.launch { - val result = - GameSaveBackupManager.deleteBackupHistoryEntry( - this@UnifiedActivity, - target, - ) - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - if (result.success) { - context.getString(R.string.cloud_saves_history_delete_success) - } else { - context.getString(R.string.cloud_saves_history_delete_failed) - }, - android.widget.Toast.LENGTH_SHORT, - ) - historyRefreshKey++ - } - }) { Text(stringResource(R.string.cloud_saves_history_delete), color = DangerRed) } - }, - dismissButton = { - TextButton(onClick = { entryPendingDelete = null }) { - Text(stringResource(R.string.common_ui_cancel), color = TextSecondary) - } - }, - containerColor = SurfaceDark, - ) - } - } - - @Composable - private fun SaveHistorySection( - loading: Boolean, - entries: List, - onRefresh: () -> Unit, - onRestore: (GameSaveBackupManager.BackupHistoryEntry) -> Unit, - onRename: (GameSaveBackupManager.BackupHistoryEntry) -> Unit, - onDelete: (GameSaveBackupManager.BackupHistoryEntry) -> Unit, - ) { - Row( - modifier = Modifier.fillMaxWidth().padding(top = 4.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - stringResource(R.string.cloud_saves_history_title), - style = MaterialTheme.typography.labelMedium, - color = TextSecondary, - fontWeight = FontWeight.Bold, - letterSpacing = 1.1.sp, - modifier = Modifier.weight(1f), - ) - IconButton(onClick = onRefresh, modifier = Modifier.size(28.dp)) { - Icon( - Icons.Outlined.Refresh, - contentDescription = stringResource(R.string.cloud_saves_history_refresh), - tint = TextSecondary, - modifier = Modifier.size(18.dp), - ) - } - } - - Surface( - shape = RoundedCornerShape(14.dp), - color = SurfaceDark, - border = BorderStroke(1.dp, CardBorder), - modifier = Modifier.fillMaxWidth(), - ) { - Column(modifier = Modifier.padding(vertical = 6.dp)) { - when { - loading -> { - Text( - stringResource(R.string.cloud_saves_history_loading), - color = TextSecondary, - fontSize = 12.sp, - modifier = Modifier.padding(horizontal = 16.dp, vertical = 14.dp), - ) - } - - entries.isEmpty() -> { - Text( - stringResource(R.string.cloud_saves_history_empty), - color = TextSecondary, - fontSize = 12.sp, - modifier = Modifier.padding(horizontal = 16.dp, vertical = 14.dp), - ) - } - - else -> { - entries.forEachIndexed { index, entry -> - SaveHistoryRow( - entry = entry, - onRestore = { onRestore(entry) }, - onRename = { onRename(entry) }, - onDelete = { onDelete(entry) }, - ) - if (index < entries.lastIndex) { - androidx.compose.material3.HorizontalDivider( - color = CardBorder, - modifier = Modifier.padding(horizontal = 12.dp), - ) - } - } - } - } - } - } - } - - @Composable - private fun SaveHistoryRow( - entry: GameSaveBackupManager.BackupHistoryEntry, - onRestore: () -> Unit, - onRename: () -> Unit, - onDelete: () -> Unit, - ) { - val whenLabel = - remember(entry.timestampMs) { - android.text.format.DateUtils - .getRelativeTimeSpanString( - entry.timestampMs, - System.currentTimeMillis(), - android.text.format.DateUtils.MINUTE_IN_MILLIS, - ).toString() - } - val originLabel = - when (entry.origin) { - GameSaveBackupManager.BackupOrigin.LOCAL -> stringResource(R.string.cloud_saves_history_origin_local) - GameSaveBackupManager.BackupOrigin.CLOUD -> stringResource(R.string.cloud_saves_history_origin_cloud) - GameSaveBackupManager.BackupOrigin.MANUAL -> stringResource(R.string.cloud_saves_history_origin_manual) - GameSaveBackupManager.BackupOrigin.AUTO -> stringResource(R.string.cloud_saves_history_origin_auto) - } - Row( - modifier = - Modifier - .fillMaxWidth() - .padding(horizontal = 12.dp, vertical = 10.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - Icon( - Icons.Outlined.History, - contentDescription = null, - tint = TextSecondary, - modifier = Modifier.size(18.dp), - ) - Spacer(Modifier.width(10.dp)) - Column(modifier = Modifier.weight(1f)) { - val title = entry.label?.takeIf { it.isNotBlank() } ?: whenLabel - Text( - text = title, - color = TextPrimary, - fontSize = 13.sp, - fontWeight = FontWeight.Medium, - ) - Spacer(Modifier.height(2.dp)) - Row(verticalAlignment = Alignment.CenterVertically) { - Box( - modifier = - Modifier - .clip(RoundedCornerShape(999.dp)) - .background(CardBorder) - .padding(horizontal = 8.dp, vertical = 2.dp), - ) { - Text(originLabel, color = TextSecondary, fontSize = 10.sp, fontWeight = FontWeight.SemiBold) - } - Spacer(Modifier.width(6.dp)) - Text( - text = formatBytes(entry.sizeBytes), - color = TextSecondary, - fontSize = 11.sp, - ) - if (!entry.label.isNullOrBlank()) { - Spacer(Modifier.width(6.dp)) - Text( - text = "\u2022 $whenLabel", - color = TextSecondary, - fontSize = 11.sp, - ) - } - } - } - TextButton( - onClick = onRename, - contentPadding = PaddingValues(horizontal = 8.dp, vertical = 2.dp), - ) { - Text(stringResource(R.string.cloud_saves_history_rename), color = TextSecondary, fontSize = 12.sp) - } - TextButton( - onClick = onDelete, - contentPadding = PaddingValues(horizontal = 8.dp, vertical = 2.dp), - ) { - Text(stringResource(R.string.cloud_saves_history_delete), color = DangerRed, fontSize = 12.sp) - } - TextButton( - onClick = onRestore, - contentPadding = PaddingValues(horizontal = 8.dp, vertical = 2.dp), - ) { - Text(stringResource(R.string.cloud_saves_history_restore), color = Accent, fontSize = 12.sp) - } - } - } - - private fun formatBytes(bytes: Long): String = - when { - bytes <= 0 -> "0 B" - bytes < 1024 -> "$bytes B" - bytes < 1024 * 1024 -> "%.1f KB".format(bytes / 1024.0) - bytes < 1024L * 1024 * 1024 -> "%.1f MB".format(bytes / (1024.0 * 1024.0)) - else -> "%.2f GB".format(bytes / (1024.0 * 1024.0 * 1024.0)) - } - - @Composable - private fun TogglePairCard( - cloudSyncEnabled: Boolean, - offlineModeEnabled: Boolean, - onCloudSyncToggle: (Boolean) -> Unit, - onOfflineModeToggle: (Boolean) -> Unit, - ) { - BoxWithConstraints(modifier = Modifier.fillMaxWidth()) { - val stacked = maxWidth < 380.dp - val cloudSyncCell: @Composable (Modifier) -> Unit = { mod -> - TogglePaneCell( - modifier = mod, - title = stringResource(R.string.cloud_sync_title), - summary = - if (cloudSyncEnabled) { - stringResource(R.string.cloud_sync_enabled_summary) - } else { - stringResource(R.string.cloud_sync_disabled_summary) - }, - checked = cloudSyncEnabled && !offlineModeEnabled, - enabled = !offlineModeEnabled, - onCheckedChange = onCloudSyncToggle, - ) - } - val offlineCell: @Composable (Modifier) -> Unit = { mod -> - TogglePaneCell( - modifier = mod, - title = stringResource(R.string.cloud_saves_offline_mode), - summary = stringResource(R.string.cloud_saves_offline_mode_summary), - checked = offlineModeEnabled, - enabled = true, - onCheckedChange = onOfflineModeToggle, - ) - } - if (stacked) { - Column( - modifier = Modifier.fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(10.dp), - ) { - cloudSyncCell(Modifier.fillMaxWidth()) - offlineCell(Modifier.fillMaxWidth()) - } - } else { - Row( - modifier = Modifier.fillMaxWidth().height(IntrinsicSize.Min), - horizontalArrangement = Arrangement.spacedBy(10.dp), - ) { - cloudSyncCell(Modifier.weight(1f).fillMaxHeight()) - offlineCell(Modifier.weight(1f).fillMaxHeight()) - } - } - } - } - - @Composable - private fun TogglePaneCell( - modifier: Modifier = Modifier, - title: String, - summary: String, - checked: Boolean, - enabled: Boolean, - onCheckedChange: (Boolean) -> Unit, - ) { - Column( - modifier = - modifier - .clip(RoundedCornerShape(14.dp)) - .background(SurfaceDark) - .border(1.dp, CardBorder, RoundedCornerShape(14.dp)) - .padding(horizontal = 12.dp, vertical = 10.dp), - verticalArrangement = Arrangement.spacedBy(6.dp), - ) { - Row(verticalAlignment = Alignment.CenterVertically) { - Text( - title, - style = MaterialTheme.typography.titleSmall, - color = if (enabled) TextPrimary else TextSecondary, - fontWeight = FontWeight.Bold, - modifier = Modifier.weight(1f), - ) - Switch( - checked = checked, - onCheckedChange = if (enabled) onCheckedChange else { _ -> }, - enabled = enabled, - colors = - outlinedSwitchColors( - accentColor = Accent, - textSecondaryColor = TextSecondary, - checkedThumbColor = TextPrimary, - ), - ) - } - Text( - summary, - style = MaterialTheme.typography.bodySmall, - color = TextSecondary, - lineHeight = 14.sp, - ) - } - } - - @Composable - private fun ActionWithHelper( - icon: ImageVector, - label: String, - helper: String, - modifier: Modifier = Modifier.fillMaxWidth(), - onClick: () -> Unit, - ) { - Column(modifier = modifier, verticalArrangement = Arrangement.spacedBy(2.dp)) { - CompactActionButton( - icon = icon, - label = label, - modifier = Modifier.fillMaxWidth(), - onClick = onClick, - ) - Text( - helper, - style = MaterialTheme.typography.bodySmall, - color = TextSecondary, - fontSize = 11.sp, - modifier = Modifier.padding(start = 10.dp), - ) - } - } - - private fun resolveLibraryShortcutArtworkModel( - context: android.content.Context, - app: SteamApp, - isCustom: Boolean, - isEpic: Boolean, - epicArtworkUrl: String?, - ): Any? = - when { - isCustom -> { - val safeName = app.name.replace("/", "_").replace("\\", "_") - val iconFile = java.io.File(context.filesDir, "custom_icons/$safeName.png") - if (iconFile.exists()) iconFile else null - } - - isEpic -> { - epicArtworkUrl?.takeIf { it.isNotBlank() } - } - - else -> { - app.getCapsuleUrl() - } - } - - private suspend fun loadArtworkBitmap( - context: android.content.Context, - artworkModel: Any?, - ): Bitmap? { - if (artworkModel == null) return null - return try { - val request = - ImageRequest - .Builder(context) - .data(artworkModel) - .allowHardware(false) - .size(192, 192) - .build() - val result = context.imageLoader.execute(request) - val drawable = result.drawable ?: return null - if (drawable is BitmapDrawable) { - drawable.bitmap - } else { - val width = if (drawable.intrinsicWidth > 0) drawable.intrinsicWidth else 192 - val height = if (drawable.intrinsicHeight > 0) drawable.intrinsicHeight else 192 - val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) - val canvas = Canvas(bitmap) - drawable.setBounds(0, 0, width, height) - drawable.draw(canvas) - bitmap - } - } catch (_: Exception) { - null - } - } - - private suspend fun requestPinnedHomeShortcut( - context: android.content.Context, - shortcut: Shortcut, - artworkModel: Any? = null, - ): Boolean { - if (shortcut.getExtra("uuid").isEmpty()) { - shortcut.genUUID() - } - val shortcutId = shortcut.getExtra("uuid") - if (shortcutId.isEmpty()) return false - val canonicalShortcutPath = shortcut.file.absolutePath - val shortcutPathHash = canonicalShortcutPath.hashCode() - val containerIdForLaunch = shortcut.getExtra("container_id").toIntOrNull() ?: shortcut.container.id - val pinShortcutId = "shortcut_${shortcut.container.id}_${shortcutId}_${shortcutPathHash.toUInt().toString(16)}" - - val shortcutManager = context.getSystemService(android.content.pm.ShortcutManager::class.java) ?: return false - if (!shortcutManager.isRequestPinShortcutSupported) return false - - val launchIntent = - Intent(context, XServerDisplayActivity::class.java).apply { - val launchData = - Uri - .Builder() - .scheme("winnative") - .authority(BuildConfig.APPLICATION_ID) - .appendPath("shortcut") - .appendQueryParameter("uuid", shortcutId) - .appendQueryParameter("container", containerIdForLaunch.toString()) - .appendQueryParameter("hash", shortcutPathHash.toString()) - .build() - action = Intent.ACTION_VIEW - data = launchData - addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP) - putExtra("container_id", containerIdForLaunch) - putExtra("shortcut_path", canonicalShortcutPath) - putExtra("shortcut_name", shortcut.name) - putExtra("shortcut_uuid", shortcutId) - putExtra("shortcut_path_hash", shortcutPathHash) - putExtra(XServerDisplayActivity.EXTRA_LAUNCHED_FROM_PINNED_SHORTCUT, true) - } - - val customIconPath = - shortcut - .getExtra("customLibraryIconPath") - .ifBlank { shortcut.getExtra("customCoverArtPath") } - val customArtworkModel = - customIconPath - .takeIf { it.isNotBlank() } - ?.let { java.io.File(it) } - ?.takeIf { it.exists() } - - val artworkBitmap = loadArtworkBitmap(context, customArtworkModel) ?: loadArtworkBitmap(context, artworkModel) - val shortcutIcon = - artworkBitmap?.let { - android.graphics.drawable.Icon - .createWithBitmap(it) - } - ?: shortcut.icon?.let { - android.graphics.drawable.Icon - .createWithBitmap(it) - } - ?: android.graphics.drawable.Icon - .createWithResource(context, R.drawable.icon_shortcut) - - val pinShortcutInfo = - android.content.pm.ShortcutInfo - .Builder(context, pinShortcutId) - .setShortLabel(shortcut.name) - .setLongLabel(shortcut.name) - .setIcon(shortcutIcon) - .setIntent(launchIntent) - .build() - - val callbackIntent = - Intent(context, ShortcutBroadcastReceiver::class.java).apply { - action = ShortcutBroadcastReceiver.ACTION_PIN_SHORTCUT_RESULT - putExtra("shortcut_path", canonicalShortcutPath) - putExtra("shortcut_name", shortcut.name) - } - val callbackFlags = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE - val callback = - PendingIntent.getBroadcast( - context, - pinShortcutId.hashCode(), - callbackIntent, - callbackFlags, - ) - - val result = - ShortcutsFragment.pinOrUpdateShortcut( - shortcutManager, - pinShortcutInfo, - ShortcutsFragment.buildPinnedShortcutIds(containerIdForLaunch, shortcutId, canonicalShortcutPath), - callback.intentSender, - ) - if (result == ShortcutsFragment.PinShortcutResult.REUSED_EXISTING) { - val toastIcon = artworkBitmap ?: shortcut.icon - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - R.string.shortcuts_list_readded_existing, - toastIcon, - ) - } - return result != ShortcutsFragment.PinShortcutResult.FAILED - } - - private suspend fun addLibraryShortcutToHomeScreen( - context: android.content.Context, - app: SteamApp, - isCustom: Boolean, - isEpic: Boolean, - epicId: Int, - epicArtworkUrl: String? = null, - ): Boolean { - val containerManager = ContainerManager(context) - val shortcut = findLibraryShortcutForGame(containerManager, app, isCustom, isEpic, epicId) ?: return false - val artworkModel = resolveLibraryShortcutArtworkModel(context, app, isCustom, isEpic, epicArtworkUrl) - return requestPinnedHomeShortcut(context, shortcut, artworkModel) - } - - private suspend fun addGogShortcutToHomeScreen( - context: android.content.Context, - app: GOGGame, - artworkUrl: String?, - ): Boolean { - val shortcut = - ContainerManager(context).loadShortcuts().find { - it.getExtra("game_source") == "GOG" && it.getExtra("gog_id") == app.id - } ?: return false - val artworkModel = artworkUrl?.takeIf { it.isNotBlank() } - return requestPinnedHomeShortcut(context, shortcut, artworkModel) - } - - // Game launch with drive-aware mapping - private fun launchSteamGame( - context: android.content.Context, - containerManager: ContainerManager, - app: SteamApp, - ) { - lifecycleScope.launch(Dispatchers.IO) { - val gameInstallPath = SteamService.getAppDirPath(app.id) - val gameDir = java.io.File(gameInstallPath) - if (!gameDir.exists()) { - withContext(Dispatchers.Main) { - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - "Game not installed: ${app.name}", - android.widget.Toast.LENGTH_SHORT, - ) - } - return@launch - } - - val shortcut = - containerManager.loadShortcuts().find { - it.getExtra("game_source") == "STEAM" && it.getExtra("app_id") == app.id.toString() - } - val detectedLaunchExecutable = SteamService.getInstalledExe(app.id) - - if (shortcut != null) { - if (!SetupWizardActivity.isContainerUsable(context, shortcut.container)) { - withContext(Dispatchers.Main) { - SetupWizardActivity.promptToInstallWineOrCreateContainer( - context, - shortcut.container.wineVersion, - ) - } - return@launch - } - normalizeContainerDrives(shortcut.container) - shortcut.putExtra("game_source", "STEAM") - shortcut.putExtra("game_install_path", gameInstallPath) - val existingLaunchExecutable = shortcut.getExtra("launch_exe_path") - if (existingLaunchExecutable.isNullOrBlank() && detectedLaunchExecutable.isNotBlank()) { - shortcut.putExtra("launch_exe_path", detectedLaunchExecutable) - } - val loaderExec = "wine \"C:\\\\Program Files (x86)\\\\Steam\\\\steamclient_loader_x64.exe\"" - val lines = - com.winlator.cmod.shared.io.FileUtils - .readLines(shortcut.file) - val rewritten = StringBuilder() - var execUpdated = false - for (line in lines) { - if (line.startsWith("Exec=")) { - rewritten.append("Exec=").append(loaderExec).append("\n") - execUpdated = true - } else { - rewritten.append(line).append("\n") - } - } - if (!execUpdated) { - rewritten.append("Exec=").append(loaderExec).append("\n") - } - com.winlator.cmod.shared.io.FileUtils - .writeString(shortcut.file, rewritten.toString()) - shortcut.saveData() - val intent = Intent(context, XServerDisplayActivity::class.java) - intent.putExtra("container_id", shortcut.container.id) - intent.putExtra("shortcut_path", shortcut.file.path) - intent.putExtra("shortcut_name", shortcut.name) - withContext(Dispatchers.Main) { - launchGame(context, intent) - } - } else { - val container = SetupWizardActivity.getPreferredGameContainer(context, containerManager) - - if (container == null) { - withContext(Dispatchers.Main) { - SetupWizardActivity.promptToInstallWineOrCreateContainer(context) - } - return@launch - } - - normalizeContainerDrives(container) - - val execPath = "wine \"C:\\\\Program Files (x86)\\\\Steam\\\\steamclient_loader_x64.exe\"" - - // Generate a shortcut dynamically - val desktopDir = container.getDesktopDir() - if (!desktopDir.exists()) desktopDir.mkdirs() - val shortcutFile = java.io.File(desktopDir, "${app.name.replace("/", "_")}.desktop") - val content = java.lang.StringBuilder() - content.append("[Desktop Entry]\n") - content.append("Type=Application\n") - content.append("Name=${app.name}\n") - content.append("Exec=$execPath\n") - content.append("Icon=steam_icon_${app.id}\n") - content.append("\n[Extra Data]\n") - content.append("game_source=STEAM\n") - content.append("app_id=${app.id}\n") - content.append("container_id=${container.id}\n") - content.append("game_install_path=${gameInstallPath}\n") - content.append("launch_exe_path=${detectedLaunchExecutable}\n") - content.append("use_container_defaults=1\n") - - com.winlator.cmod.shared.io.FileUtils - .writeString(shortcutFile, content.toString()) - - container.saveData() - - val intent = Intent(context, XServerDisplayActivity::class.java) - intent.putExtra("container_id", container.id) - intent.putExtra("shortcut_path", shortcutFile.path) - intent.putExtra("shortcut_name", app.name) - withContext(Dispatchers.Main) { - launchGame(context, intent) - } - } - } - } - - private fun launchEpicGame( - context: android.content.Context, - containerManager: ContainerManager, - app: EpicGame, - ) { - lifecycleScope.launch(Dispatchers.IO) { - val gameInstallPath = app.installPath.takeIf { it.isNotEmpty() } ?: EpicConstants.getGameInstallPath(context, app.appName) - val gameDir = java.io.File(gameInstallPath) - if (!gameDir.exists()) { - withContext(Dispatchers.Main) { - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - "Game not installed: ${app.title}", - android.widget.Toast.LENGTH_SHORT, - ) - } - return@launch - } - - // Try to find an existing shortcut first (preserves per-game settings) - val existingShortcut = - containerManager.loadShortcuts().find { - it.getExtra("game_source") == "EPIC" && it.getExtra("app_id") == app.id.toString() - } - - if (existingShortcut != null) { - if (!SetupWizardActivity.isContainerUsable(context, existingShortcut.container)) { - withContext(Dispatchers.Main) { - SetupWizardActivity.promptToInstallWineOrCreateContainer( - context, - existingShortcut.container.wineVersion, - ) - } - return@launch - } - // Existing shortcut found: preserve per-game settings and update the mapped install path - val shortcut = existingShortcut - // Ensure game_install_path is always up-to-date - shortcut.putExtra("game_install_path", gameInstallPath) - normalizeContainerDrives(shortcut.container) - - // Repair broken Exec line if the executable is missing or still points at a legacy placeholder mapping. - val currentPath = shortcut.path - if (currentPath == null || currentPath == "D:\\" || currentPath == "D:\\\\" || - currentPath == "A:\\" || currentPath == "A:\\\\" || - currentPath.startsWith("A:\\") - ) { - val newExecCmd = - buildStoreWineExecCommandForSelectedExe( - shortcut.container, - "EPIC", - gameInstallPath, - shortcut.getExtra("launch_exe_path"), - ) ?: run { - val exePath = EpicService.getInstalledExe(app.id) - if (exePath.isNotEmpty()) { - shortcut.putExtra("launch_exe_path", exePath) - buildStoreWineExecCommand( - shortcut.container, - "EPIC", - gameInstallPath, - java.io.File(gameInstallPath, exePath.replace("\\", "/")), - ) - } else { - val exeFile = findGameExe(gameDir) - if (exeFile != null) { - shortcut.putExtra("launch_exe_path", exeFile.absolutePath) - buildStoreWineExecCommand(shortcut.container, "EPIC", gameInstallPath, exeFile) - } else { - null - } - } - } - if (newExecCmd != null) { - // Rewrite the Exec line in the .desktop file while preserving all other content - val lines = - com.winlator.cmod.shared.io.FileUtils - .readLines(shortcut.file) - val sb = StringBuilder() - for (line in lines) { - if (line.startsWith("Exec=")) { - sb.append("Exec=$newExecCmd\n") - } else { - sb.append(line).append("\n") - } - } - com.winlator.cmod.shared.io.FileUtils - .writeString(shortcut.file, sb.toString()) - } - } - - shortcut.saveData() - - // Provision the EOS overlay into this container. Best-effort — failures are - // non-fatal (games without the EOS SDK ignore it; games with the SDK still run - // without the in-game HUD). Tokens must be staged inside the prefix because - // the dosdevices map doesn't expose the app cache dir on any drive letter. - runCatching { - EpicService.installOverlay(context, shortcut.container) - }.onFailure { - Log.w("EPIC", "EOS overlay install failed for ${app.appName}; launching anyway", it) - } - - val launchArgsResult = - EpicGameLauncher.buildLaunchParameters( - context = context, - game = app, - container = shortcut.container, - ) - launchArgsResult.exceptionOrNull()?.let { err -> - // The launch can still proceed (offline-tolerant titles, single-player non-DRM - // games), so we don't abort — but surface the failure prominently so users - // know why a DRM/online title may bounce to its login screen. - Log.e("EPIC", "Failed to build Epic launch parameters for ${app.appName}: ${err.message}", err) - withContext(Dispatchers.Main) { - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - "Could not refresh Epic launch token: ${err.message ?: "unknown error"}", - android.widget.Toast.LENGTH_LONG, - ) - } - } - val args = launchArgsResult.getOrNull()?.joinToString(" ") ?: "" - - val intent = Intent(context, XServerDisplayActivity::class.java) - intent.putExtra("container_id", shortcut.container.id) - intent.putExtra("shortcut_path", shortcut.file.path) - intent.putExtra("shortcut_name", shortcut.name) - intent.putExtra("extra_exec_args", args) // Pass fresh tokens - withContext(Dispatchers.Main) { - launchGame(context, intent) - } - } else { - // No existing shortcut — create a new one - val exePath = EpicService.getInstalledExe(app.id) - val container = SetupWizardActivity.getPreferredGameContainer(context, containerManager) - - if (container == null) { - withContext(Dispatchers.Main) { - SetupWizardActivity.promptToInstallWineOrCreateContainer(context) - } - return@launch - } - - normalizeContainerDrives(container) - val execCmd = - if (exePath.isNotEmpty()) { - buildStoreWineExecCommand( - container, - "EPIC", - gameInstallPath, - java.io.File(gameInstallPath, exePath.replace("\\", "/")), - ) - } else { - val exeFile = findGameExe(gameDir) - if (exeFile != null) { - buildStoreWineExecCommand(container, "EPIC", gameInstallPath, exeFile) - } else { - "wine \"explorer.exe\"" - } - } - - val desktopDir = container.getDesktopDir() - if (!desktopDir.exists()) desktopDir.mkdirs() - val shortcutFile = java.io.File(desktopDir, "${app.appName}.desktop") - val content = java.lang.StringBuilder() - content.append("[Desktop Entry]\n") - content.append("Type=Application\n") - content.append("Name=${app.title}\n") - content.append("Exec=$execCmd\n") - content.append("Icon=epic_icon_${app.id}\n") - content.append("\n[Extra Data]\n") - content.append("game_source=EPIC\n") - content.append("app_id=${app.id}\n") - if (app.catalogId.isNotEmpty()) { - // Persist catalog_id so EpicGameFixHelper / GameFixes can dispatch the - // per-catalog registry/env/folder fixes without a DB round-trip on launch. - content.append("catalog_id=${app.catalogId}\n") - } - content.append("container_id=${container.id}\n") - content.append("game_install_path=${gameInstallPath}\n") - if (exePath.isNotEmpty()) { - content.append("launch_exe_path=${exePath}\n") - } - content.append("use_container_defaults=1\n") - - com.winlator.cmod.shared.io.FileUtils - .writeString(shortcutFile, content.toString()) - - container.saveData() - - // Best-effort EOS overlay provisioning — see existing-shortcut branch above. - runCatching { - EpicService.installOverlay(context, container) - }.onFailure { - Log.w("EPIC", "EOS overlay install failed for ${app.appName}; launching anyway", it) - } - - val launchArgsResult = - EpicGameLauncher.buildLaunchParameters( - context = context, - game = app, - container = container, - ) - launchArgsResult.exceptionOrNull()?.let { err -> - Log.e("EPIC", "Failed to build Epic launch parameters for ${app.appName}: ${err.message}", err) - withContext(Dispatchers.Main) { - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - "Could not refresh Epic launch token: ${err.message ?: "unknown error"}", - android.widget.Toast.LENGTH_LONG, - ) - } - } - val args = launchArgsResult.getOrNull()?.joinToString(" ") ?: "" - - val intent = Intent(context, XServerDisplayActivity::class.java) - intent.putExtra("container_id", container.id) - intent.putExtra("shortcut_path", shortcutFile.path) - intent.putExtra("shortcut_name", app.title) - intent.putExtra("extra_exec_args", args) // Pass fresh tokens - withContext(Dispatchers.Main) { - launchGame(context, intent) - } - } - } - } - - private fun launchGogGame( - context: android.content.Context, - containerManager: ContainerManager, - app: GOGGame, - ) { - lifecycleScope.launch(Dispatchers.IO) { - val gameInstallPath = app.installPath.takeIf { it.isNotEmpty() } ?: GOGConstants.getGameInstallPath(app.title) - val gameDir = java.io.File(gameInstallPath) - if (!gameDir.exists()) { - withContext(Dispatchers.Main) { - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - "Game not installed: ${app.title}", - android.widget.Toast.LENGTH_SHORT, - ) - } - return@launch - } - - val existingShortcut = - containerManager.loadShortcuts().find { - it.getExtra("game_source") == "GOG" && it.getExtra("gog_id") == app.id - } - - val gogAppId = "GOG_${app.id}" - GOGService.syncCloudSaves(context, gogAppId) - - if (existingShortcut != null) { - val shortcut = existingShortcut - if (!SetupWizardActivity.isContainerUsable(context, shortcut.container)) { - withContext(Dispatchers.Main) { - SetupWizardActivity.promptToInstallWineOrCreateContainer( - context, - shortcut.container.wineVersion, - ) - } - return@launch - } - shortcut.putExtra("game_install_path", gameInstallPath) - normalizeContainerDrives(shortcut.container) - - // Repair broken Exec line if the executable is missing or still points at a legacy placeholder mapping. - val currentPath = shortcut.path - if (currentPath == null || currentPath == "D:\\" || currentPath == "D:\\\\" || - currentPath == "A:\\" || currentPath == "A:\\\\" || - currentPath.startsWith("A:\\") - ) { - val newExecCmd = - buildStoreWineExecCommandForSelectedExe( - shortcut.container, - "GOG", - gameInstallPath, - shortcut.getExtra("launch_exe_path"), - ) ?: run { - val libraryItem = - LibraryItem("GOG_${app.id}", app.title, com.winlator.cmod.feature.stores.steam.enums.GameSource.GOG) - val exePath = GOGService.getInstalledExe(libraryItem) - if (exePath.isNotEmpty()) { - shortcut.putExtra("launch_exe_path", exePath) - buildStoreWineExecCommand( - shortcut.container, - "GOG", - gameInstallPath, - java.io.File(gameInstallPath, exePath.replace("\\", "/")), - ) - } else { - val exeFile = findGameExe(gameDir) - if (exeFile != null) { - shortcut.putExtra("launch_exe_path", exeFile.absolutePath) - buildStoreWineExecCommand(shortcut.container, "GOG", gameInstallPath, exeFile) - } else { - null - } - } - } - if (newExecCmd != null) { - val lines = - com.winlator.cmod.shared.io.FileUtils - .readLines(shortcut.file) - val sb = StringBuilder() - for (line in lines) { - if (line.startsWith("Exec=")) { - sb.append("Exec=$newExecCmd\n") - } else { - sb.append(line).append("\n") - } - } - com.winlator.cmod.shared.io.FileUtils - .writeString(shortcut.file, sb.toString()) - } - } - - shortcut.saveData() - - val intent = Intent(context, XServerDisplayActivity::class.java) - intent.putExtra("container_id", shortcut.container.id) - intent.putExtra("shortcut_path", shortcut.file.path) - intent.putExtra("shortcut_name", shortcut.name) - withContext(Dispatchers.Main) { - launchGame(context, intent) - } - return@launch - } - - val libraryItem = LibraryItem("GOG_${app.id}", app.title, com.winlator.cmod.feature.stores.steam.enums.GameSource.GOG) - val exePath = GOGService.getInstalledExe(libraryItem) - - val container = SetupWizardActivity.getPreferredGameContainer(context, containerManager) - - if (container == null) { - withContext(Dispatchers.Main) { - SetupWizardActivity.promptToInstallWineOrCreateContainer(context) - } - return@launch - } - - normalizeContainerDrives(container) - val execCmd = - if (exePath.isNotEmpty()) { - buildStoreWineExecCommand( - container, - "GOG", - gameInstallPath, - java.io.File(gameInstallPath, exePath.replace("\\", "/")), - ) - } else { - val exeFile = findGameExe(gameDir) - if (exeFile != null) { - buildStoreWineExecCommand(container, "GOG", gameInstallPath, exeFile) - } else { - "wine \"explorer.exe\"" - } - } - - val desktopDir = container.getDesktopDir() - if (!desktopDir.exists()) desktopDir.mkdirs() - val shortcutFile = java.io.File(desktopDir, "${app.title.replace("/", "_")}.desktop") - val content = java.lang.StringBuilder() - content.append("[Desktop Entry]\n") - content.append("Type=Application\n") - content.append("Name=${app.title}\n") - content.append("Exec=$execCmd\n") - content.append("Icon=gog_icon_${app.id}\n") - content.append("\n[Extra Data]\n") - content.append("game_source=GOG\n") - content.append("gog_id=${app.id}\n") - content.append("app_id=${gogPseudoId(app.id)}\n") - content.append("container_id=${container.id}\n") - content.append("game_install_path=${gameInstallPath}\n") - if (exePath.isNotEmpty()) { - content.append("launch_exe_path=${exePath}\n") - } - content.append("use_container_defaults=1\n") - - com.winlator.cmod.shared.io.FileUtils - .writeString(shortcutFile, content.toString()) - container.saveData() - - val intent = Intent(context, XServerDisplayActivity::class.java) - intent.putExtra("container_id", container.id) - intent.putExtra("shortcut_path", shortcutFile.path) - intent.putExtra("shortcut_name", app.title) - withContext(Dispatchers.Main) { - launchGame(context, intent) - } - } - } - - private fun normalizeContainerDrives(container: com.winlator.cmod.runtime.container.Container) { - container.drives = - com.winlator.cmod.runtime.wine.WineUtils.normalizePersistentDrives( - this, - container.drives ?: com.winlator.cmod.runtime.container.Container.DEFAULT_DRIVES, - ) - } - - private fun buildWineExecCommand( - container: com.winlator.cmod.runtime.container.Container?, - gameInstallPath: String, - relativeExePath: String, - ): String { - val exeFile = java.io.File(gameInstallPath, relativeExePath.replace("\\", "/")) - return buildWineExecCommand(container, gameInstallPath, exeFile) - } - - private fun buildWineExecCommand( - container: com.winlator.cmod.runtime.container.Container?, - gameInstallPath: String, - exeFile: java.io.File, - ): String { - val windowsPath = - container?.let { - com.winlator.cmod.runtime.wine.WineUtils - .getDriveCGameWindowsPath( - it, - "CUSTOM", - gameInstallPath, - exeFile.absolutePath, - ) ?: com.winlator.cmod.runtime.wine.WineUtils - .getWindowsPath(it, exeFile.absolutePath) - } ?: run { - com.winlator.cmod.runtime.wine.WineUtils.getDosPath(exeFile.absolutePath) - } - return "wine \"$windowsPath\"" - } - - private fun buildStoreWineExecCommand( - container: com.winlator.cmod.runtime.container.Container?, - source: String, - gameInstallPath: String, - exeFile: java.io.File, - ): String { - val windowsPath = - container?.let { - com.winlator.cmod.runtime.wine.WineUtils.getDriveCGameWindowsPath( - it, - source, - gameInstallPath, - exeFile.absolutePath, - ) - } ?: run { - val relativePath = - try { - exeFile.relativeTo(java.io.File(gameInstallPath)).path.replace("/", "\\") - } catch (_: Exception) { - exeFile.name - } - val linkName = - com.winlator.cmod.runtime.wine.WineUtils.getDriveCGameLinkName(gameInstallPath) - "C:\\WinNative\\Games\\$source\\$linkName\\$relativePath" - } - return "wine \"$windowsPath\"" - } - - private fun buildStoreWineExecCommandForSelectedExe( - container: com.winlator.cmod.runtime.container.Container?, - source: String, - gameInstallPath: String, - selectedExePath: String?, - ): String? { - if (selectedExePath.isNullOrBlank()) return null - - val selectedExe = java.io.File(selectedExePath) - if (!selectedExe.isFile) return null - - val normalizedBaseDir = - java.io - .File(gameInstallPath) - .absolutePath - .removeSuffix("/") - val normalizedExePath = selectedExe.absolutePath - return if (normalizedExePath == normalizedBaseDir || normalizedExePath.startsWith("$normalizedBaseDir/")) { - buildStoreWineExecCommand(container, source, gameInstallPath, selectedExe) - } else { - val hostPath = normalizedExePath.replace("/", "\\\\").let { if (it.startsWith("\\")) it else "\\$it" } - "wine \"Z:${hostPath}\"" - } - } - - // Launch custom game by shortcut name - private fun launchCustomGame( - context: android.content.Context, - containerManager: ContainerManager, - gameName: String, - ) { - lifecycleScope.launch(Dispatchers.IO) { - val allShortcuts = containerManager.loadShortcuts() - - // Try matching by app_id (for non-official Steam/Epic), custom_name, or filename - var shortcut = - allShortcuts.find { it.getExtra("app_id") == gameName } - ?: allShortcuts.find { it.getExtra("custom_name") == gameName } - ?: allShortcuts.find { it.name == gameName } - ?: allShortcuts.find { it.name == gameName.replace("/", "_").replace("\\", "_") } - - // If still not found, try matching by looking at the safe filename directly - if (shortcut == null) { - val safeName = gameName.replace("/", "_").replace("\\", "_") - for (container in containerManager.containers) { - val desktopFile = java.io.File(container.getDesktopDir(), "$safeName.desktop") - if (desktopFile.exists()) { - shortcut = - com.winlator.cmod.runtime.container - .Shortcut(container, desktopFile) - break - } - } - } - - if (shortcut == null) { - withContext(Dispatchers.Main) { - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - "Custom game shortcut not found: $gameName", - android.widget.Toast.LENGTH_SHORT, - ) - } - return@launch - } - - // Backfill custom_name if missing (legacy shortcuts) - if (shortcut.getExtra("custom_name").isEmpty()) { - shortcut.putExtra("custom_name", gameName) - shortcut.saveData() - } - - // Refresh storage-root mappings; custom game paths launch through the drive_c game symlink. - val gameFolder = shortcut.getExtra("custom_game_folder", "") - if (gameFolder.isNotEmpty()) { - normalizeContainerDrives(shortcut.container) - shortcut.container.saveData() - } - val intent = Intent(context, XServerDisplayActivity::class.java) - intent.putExtra("container_id", shortcut.container.id) - intent.putExtra("shortcut_path", shortcut.file.path) - intent.putExtra("shortcut_name", gameName) - withContext(Dispatchers.Main) { - launchGame(context, intent) - } - } - } - - private fun launchGame( - context: android.content.Context, - intent: Intent, - ) { - DownloadService.clearCompletedDownloads() - context.startActivity(intent) - // Suppress the default activity transition so the preloader stays seamless - if (context is android.app.Activity) { - com.winlator.cmod.shared.android.AppUtils - .applyOpenActivityTransition(context, 0, 0) - } - } - - private fun findGameExe(dir: java.io.File): java.io.File? { - // BFS: check each directory level fully before going deeper - val exclusions = - listOf( - "unins", - "redist", - "setup", - "dotnet", - "vcredist", - "dxsetup", - "helper", - "crash", - "ue4prereq", - "dxwebsetup", - "launcher", - ) - - var currentDirs = listOf(dir) - var depth = 0 - var fallbackExe: java.io.File? = null - - while (currentDirs.isNotEmpty() && depth <= 4) { - val nextDirs = mutableListOf() - val candidates = mutableListOf() - - for (d in currentDirs) { - val children = d.listFiles() ?: continue - for (f in children) { - if (f.isDirectory) { - nextDirs.add(f) - } else if (f.extension.equals("exe", ignoreCase = true)) { - val name = f.name.lowercase() - if (exclusions.none { name.contains(it) }) { - candidates.add(f) - } - } - } - } - - // Prefer 64-bit executable candidates at the current depth - val exe64 = - candidates.find { - it.name.lowercase().contains("64") || - it.parentFile - ?.name - ?.lowercase() - ?.contains("64") == true - } - if (exe64 != null) return exe64 - - // Collect the first valid candidate as a fallback - if (fallbackExe == null && candidates.isNotEmpty()) { - fallbackExe = candidates.first() - } - - currentDirs = nextDirs - depth++ - } - return fallbackExe - } - - @Composable - fun EmptyStateMessage(message: String) { - Text(message, color = TextSecondary, modifier = Modifier.padding(16.dp)) - } - - @Composable - fun LoginRequiredScreen( - storeName: String, - onLoginClick: () -> Unit, - ) { - val message = - if (storeName == - "Library" - ) { - stringResource(R.string.library_games_sign_in_prompt) - } else { - stringResource(R.string.stores_accounts_sign_in_store_prompt, storeName) - } - val buttonText = - if (storeName == - "Library" - ) { - stringResource(R.string.stores_accounts_manage) - } else { - stringResource(R.string.stores_accounts_sign_into_store, storeName) - } - - Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier.padding(horizontal = 48.dp), - ) { - Icon( - Icons.Outlined.Person, - contentDescription = null, - tint = TextSecondary.copy(alpha = 0.5f), - modifier = Modifier.size(48.dp), - ) - Spacer(Modifier.height(16.dp)) - Text( - message, - color = TextSecondary, - style = MaterialTheme.typography.bodyMedium, - textAlign = androidx.compose.ui.text.style.TextAlign.Center, - lineHeight = 20.sp, - ) - Spacer(Modifier.height(20.dp)) - val interactionSource = - remember { - androidx.compose.foundation.interaction - .MutableInteractionSource() - } - val isPressed by interactionSource.collectIsPressedAsState() - val btnScale by animateFloatAsState( - targetValue = if (isPressed) 0.95f else 1f, - animationSpec = tween(100), - label = "btnScale", - ) - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = - Modifier - .graphicsLayer { - scaleX = btnScale - scaleY = btnScale - }.clickable( - interactionSource = interactionSource, - indication = null, - onClick = onLoginClick, - ).border(1.dp, Accent.copy(alpha = 0.5f), RoundedCornerShape(20.dp)) - .padding(horizontal = 20.dp, vertical = 10.dp), - ) { - Text(buttonText, color = Accent, fontSize = 13.sp, fontWeight = FontWeight.Medium) - } - } - } - } - - // Drawer content: avatar card + filters - @Composable - private fun DrawerContent( - persona: com.winlator.cmod.feature.stores.steam.data.SteamFriend?, - context: android.content.Context, - scope: kotlinx.coroutines.CoroutineScope, - storeVisible: SnapshotStateMap, - contentFilters: SnapshotStateMap, - libraryLayoutMode: LibraryLayoutMode, - onLibraryLayoutSelected: (LibraryLayoutMode) -> Unit, - onStoreVisibleChanged: (String, Boolean) -> Unit, - onContentFiltersChanged: (String, Boolean) -> Unit, - onClose: () -> Unit, - ) { - val currentState = persona?.state ?: EPersonaState.Online - var statusExpanded by remember { mutableStateOf(false) } - - ModalDrawerSheet( - drawerShape = RectangleShape, - drawerContainerColor = BgDark, - drawerContentColor = TextPrimary, - windowInsets = WindowInsets(0, 0, 0, 0), - modifier = Modifier.width(324.dp), - ) { - Column( - Modifier - .fillMaxHeight() - .navigationBarsPadding() - .verticalScroll(rememberScrollState()) - .padding(20.dp), - ) { - // ── Avatar Card ── - Surface( - shape = RoundedCornerShape(16.dp), - color = SurfaceDark, - border = BorderStroke(1.dp, CardBorder), - modifier = - Modifier - .fillMaxWidth() - .clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = null, - ) { statusExpanded = !statusExpanded }, - ) { - Column(Modifier.padding(16.dp)) { - Row(verticalAlignment = Alignment.CenterVertically) { - val avatarUrl = - persona?.avatarHash?.getAvatarURL() - ?: "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/fe/fef49e7fa7e1997310d705b2a6158ff8dc1cdfeb_full.jpg" - - Box( - modifier = - Modifier - .size(48.dp) - .clip(CircleShape), - ) { - AsyncImage( - model = - ImageRequest - .Builder(context) - .data(avatarUrl) - .crossfade(true) - .build(), - contentDescription = "Profile", - modifier = Modifier.fillMaxSize(), - contentScale = ContentScale.Crop, - ) - } - - Spacer(Modifier.width(12.dp)) - - Column(Modifier.weight(1f)) { - Text( - text = persona?.name ?: stringResource(R.string.stores_accounts_not_signed_in), - style = MaterialTheme.typography.titleSmall, - fontWeight = FontWeight.Bold, - color = TextPrimary, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - val statusLabel = - when (currentState) { - EPersonaState.Online -> stringResource(R.string.stores_accounts_status_online) - EPersonaState.Away -> stringResource(R.string.stores_accounts_status_away) - else -> stringResource(R.string.stores_accounts_status_offline) - } - val statusColor = - when (currentState) { - EPersonaState.Online -> StatusOnline - EPersonaState.Away -> StatusAway - else -> StatusOffline - } - Row(verticalAlignment = Alignment.CenterVertically) { - Box(Modifier.size(8.dp).background(statusColor, CircleShape)) - Spacer(Modifier.width(6.dp)) - Text(statusLabel, style = MaterialTheme.typography.bodySmall, color = TextSecondary) - } - } - - val chevronRotation by animateFloatAsState( - targetValue = if (statusExpanded) 90f else 0f, - animationSpec = tween(250), - label = "chevronRotation", - ) - Icon( - Icons.Outlined.ChevronRight, - contentDescription = "Toggle status", - tint = TextSecondary, - modifier = - Modifier - .size(20.dp) - .graphicsLayer { rotationZ = chevronRotation }, - ) - } - - // Expandable status options - AnimatedVisibility(visible = statusExpanded) { - Column(Modifier.padding(top = 12.dp)) { - HorizontalDivider(color = TextSecondary.copy(alpha = 0.2f)) - Spacer(Modifier.height(8.dp)) - Text( - stringResource(R.string.stores_accounts_status_header), - style = MaterialTheme.typography.labelSmall, - color = TextSecondary, - ) - Spacer(Modifier.height(8.dp)) - - listOf( - Triple(EPersonaState.Online, stringResource(R.string.stores_accounts_status_online), StatusOnline), - Triple(EPersonaState.Away, stringResource(R.string.stores_accounts_status_away), StatusAway), - Triple( - EPersonaState.Invisible, - stringResource(R.string.stores_accounts_status_invisible), - StatusOffline, - ), - ).forEach { (state, label, color) -> - val isSelected = currentState == state - val rowBg by animateColorAsState( - targetValue = if (isSelected) Accent.copy(alpha = 0.12f) else Color.Transparent, - animationSpec = tween(250), - label = "statusRowBg", - ) - val borderAlpha by animateFloatAsState( - targetValue = if (isSelected) 1f else 0f, - animationSpec = tween(250), - label = "statusBorder", - ) - val checkScale by animateFloatAsState( - targetValue = if (isSelected) 1f else 0f, - animationSpec = - spring( - dampingRatio = Spring.DampingRatioMediumBouncy, - stiffness = Spring.StiffnessMedium, - ), - label = "checkScale", - ) - Row( - modifier = - Modifier - .fillMaxWidth() - .clip(RoundedCornerShape(8.dp)) - .background(rowBg) - .border(1.dp, Accent.copy(alpha = 0.4f * borderAlpha), RoundedCornerShape(8.dp)) - .clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = null, - ) { - scope.launch { - SteamService.setPersonaState(state) - statusExpanded = false - } - }.padding(horizontal = 12.dp, vertical = 10.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(10.dp), - ) { - Box(Modifier.size(10.dp).background(color, CircleShape)) - Text(label, color = TextPrimary, style = MaterialTheme.typography.bodyMedium) - Spacer(Modifier.weight(1f)) - Icon( - Icons.Outlined.Check, - contentDescription = null, - tint = Accent, - modifier = - Modifier - .size(16.dp) - .graphicsLayer { - scaleX = checkScale - scaleY = checkScale - alpha = checkScale - }, - ) - } - } - } - } - } - } - - Spacer(Modifier.height(20.dp)) - HorizontalDivider(color = TextSecondary.copy(alpha = 0.15f)) - Spacer(Modifier.height(20.dp)) - - // ── Layouts ── - Text( - stringResource(R.string.library_games_layouts_header), - color = TextSecondary, - fontSize = 10.sp, - fontWeight = FontWeight.Bold, - letterSpacing = 1.4.sp, - modifier = Modifier.padding(bottom = 4.dp), - ) - Spacer(Modifier.height(8.dp)) - - Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) { - DrawerFilterButton( - label = "4-Grid", - checked = libraryLayoutMode == LibraryLayoutMode.GRID_4, - modifier = Modifier.weight(1f), - ) { if (it) onLibraryLayoutSelected(LibraryLayoutMode.GRID_4) } - DrawerFilterButton( - label = stringResource(R.string.library_games_layout_carousel), - checked = libraryLayoutMode == LibraryLayoutMode.CAROUSEL, - modifier = Modifier.weight(1f), - ) { if (it) onLibraryLayoutSelected(LibraryLayoutMode.CAROUSEL) } - DrawerFilterButton( - label = stringResource(R.string.library_games_layout_list), - checked = libraryLayoutMode == LibraryLayoutMode.LIST, - modifier = Modifier.weight(1f), - ) { if (it) onLibraryLayoutSelected(LibraryLayoutMode.LIST) } - } - - Spacer(Modifier.height(16.dp)) - - // ── Stores ── - Text( - stringResource(R.string.stores_accounts_stores_header), - color = TextSecondary, - fontSize = 10.sp, - fontWeight = FontWeight.Bold, - letterSpacing = 1.4.sp, - modifier = Modifier.padding(bottom = 4.dp), - ) - Spacer(Modifier.height(8.dp)) - - Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) { - DrawerFilterButton("Steam", storeVisible["steam"] == true, Modifier.weight(1f)) { onStoreVisibleChanged("steam", it) } - DrawerFilterButton("Epic", storeVisible["epic"] == true, Modifier.weight(1f)) { onStoreVisibleChanged("epic", it) } - } - Spacer(Modifier.height(8.dp)) - Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) { - DrawerFilterButton("GOG", storeVisible["gog"] == true, Modifier.weight(1f)) { onStoreVisibleChanged("gog", it) } - Spacer(Modifier.weight(1f)) - } - - Spacer(Modifier.height(16.dp)) - - // ── Content Types ── - Text( - stringResource(R.string.settings_content_types_header), - color = TextSecondary, - fontSize = 10.sp, - fontWeight = FontWeight.Bold, - letterSpacing = 1.4.sp, - modifier = Modifier.padding(bottom = 4.dp), - ) - Spacer(Modifier.height(8.dp)) - - Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) { - DrawerFilterButton("Games", contentFilters["games"] == true, Modifier.weight(1f)) { onContentFiltersChanged("games", it) } - DrawerFilterButton("DLC", contentFilters["dlc"] == true, Modifier.weight(1f)) { onContentFiltersChanged("dlc", it) } - } - Spacer(Modifier.height(8.dp)) - Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) { - DrawerFilterButton("Applications", contentFilters["applications"] == true, Modifier.weight(1f)) { onContentFiltersChanged("applications", it) } - DrawerFilterButton("Tools", contentFilters["tools"] == true, Modifier.weight(1f)) { onContentFiltersChanged("tools", it) } - } - } - } - } - - @Composable - private fun DrawerFilterButton( - label: String, - checked: Boolean, - modifier: Modifier = Modifier, - onToggle: (Boolean) -> Unit, - ) { - val interactionSource = remember { MutableInteractionSource() } - val isPressed by interactionSource.collectIsPressedAsState() - - val bgColor by animateColorAsState( - targetValue = if (checked) Accent.copy(alpha = 0.2f) else CardDark, - animationSpec = tween(200), - label = "filterBg", - ) - val borderColor by animateColorAsState( - targetValue = if (checked) Accent else CardBorder, - animationSpec = tween(200), - label = "filterBorder", - ) - val textColor by animateColorAsState( - targetValue = if (checked) Accent else TextSecondary, - animationSpec = tween(200), - label = "filterText", - ) - val scale by animateFloatAsState( - targetValue = if (isPressed) 0.92f else 1f, - animationSpec = spring(dampingRatio = Spring.DampingRatioMediumBouncy, stiffness = Spring.StiffnessHigh), - label = "filterScale", - ) - - Box( - modifier = - modifier - .graphicsLayer { - scaleX = scale - scaleY = scale - }.clip(RoundedCornerShape(8.dp)) - .background(bgColor) - .border(1.dp, borderColor, RoundedCornerShape(8.dp)) - .clickable( - interactionSource = interactionSource, - indication = null, - ) { onToggle(!checked) } - .padding(vertical = 10.dp, horizontal = 10.dp), - contentAlignment = Alignment.Center, - ) { - Text( - text = label, - style = MaterialTheme.typography.labelMedium, - color = textColor, - fontWeight = FontWeight.Bold, - maxLines = 1, - ) - } - } - - // Add Custom Game Dialog - @Composable - private fun AddCustomGameDialog(onDismiss: () -> Unit) { - val context = LocalContext.current - val scope = rememberCoroutineScope() - var selectedExePath by remember { mutableStateOf(null) } - var gameName by remember { mutableStateOf("") } - var gameFolder by remember { mutableStateOf(null) } - var isAdding by remember { mutableStateOf(false) } - - fun selectExecutable(path: String) { - if (!path.endsWith(".exe", ignoreCase = true) || !java.io.File(path).isFile) { - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - R.string.common_ui_select_valid_exe_file, - android.widget.Toast.LENGTH_SHORT, - ) - return - } - - selectedExePath = path - gameFolder = LibraryShortcutUtils.detectCustomGameFolder(path) - // Auto-generate a game name from the EXE name (without extension) - if (gameName.isBlank()) { - gameName = - java.io - .File(path) - .nameWithoutExtension - .replace("_", " ") - .replace("-", " ") - } - } - - val defaultDensity = LocalDensity.current - Dialog( - onDismissRequest = onDismiss, - properties = DialogProperties(usePlatformDefaultWidth = false), - ) { - CompositionLocalProvider( - LocalDensity provides Density(defaultDensity.density, fontScale = 1f), - ) { - Surface( - modifier = - Modifier - .widthIn(max = 360.dp) - .fillMaxWidth(0.9f), - shape = RoundedCornerShape(20.dp), - color = Color(0xFF141B24), - ) { - Column(Modifier.padding(horizontal = 16.dp, vertical = 14.dp)) { - // Title - Text( - stringResource(R.string.library_games_add_custom_game), - color = TextPrimary, - fontWeight = FontWeight.SemiBold, - fontSize = 15.sp, - ) - - Spacer(Modifier.height(10.dp)) - - // Scrollable content area - Column( - modifier = - Modifier - .weight(1f, fill = false) - .verticalScroll(rememberScrollState()), - ) { - // Pick EXE button - Row( - modifier = - Modifier - .fillMaxWidth() - .clip(RoundedCornerShape(12.dp)) - .background(Color.White.copy(alpha = 0.05f)) - .clickable { - DirectoryPickerDialog.showFile( - activity = this@UnifiedActivity, - initialPath = selectedExePath ?: gameFolder, - title = getString(R.string.common_ui_select_exe), - allowedExtensions = setOf("exe"), - dimAmount = 0.5f, - preserveBackdropBlur = true, - onSelected = ::selectExecutable, - ) - }.padding(horizontal = 12.dp, vertical = 10.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - Icon(Icons.Outlined.FolderOpen, contentDescription = null, tint = Accent, modifier = Modifier.size(16.dp)) - Spacer(Modifier.width(8.dp)) - Text( - if (selectedExePath == null) "Select Executable (.exe)" else java.io.File(selectedExePath!!).name, - color = if (selectedExePath == null) TextSecondary else TextPrimary, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - fontSize = 12.sp, - ) - } - - if (selectedExePath != null) { - Spacer(Modifier.height(4.dp)) - Text( - selectedExePath!!, - color = TextSecondary.copy(alpha = 0.6f), - fontSize = 9.sp, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - - Spacer(Modifier.height(8.dp)) - - // Game name text field — compact - OutlinedTextField( - value = gameName, - onValueChange = { gameName = it }, - label = { Text(stringResource(R.string.library_games_game_name), fontSize = 11.sp) }, - singleLine = true, - modifier = Modifier.fillMaxWidth(), - textStyle = MaterialTheme.typography.bodySmall.copy(color = TextPrimary), - colors = - OutlinedTextFieldDefaults.colors( - focusedBorderColor = Accent, - unfocusedBorderColor = TextSecondary.copy(alpha = 0.3f), - focusedTextColor = TextPrimary, - unfocusedTextColor = TextPrimary, - cursorColor = Accent, - focusedLabelColor = Accent, - unfocusedLabelColor = TextSecondary, - ), - shape = RoundedCornerShape(10.dp), - ) - - Spacer(Modifier.height(8.dp)) - - // Game folder — single compact row - Row( - modifier = - Modifier - .fillMaxWidth() - .clip(RoundedCornerShape(10.dp)) - .background(Color.White.copy(alpha = 0.05f)) - .padding(horizontal = 10.dp, vertical = 6.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - Icon( - Icons.Outlined.Folder, - contentDescription = null, - tint = StatusOnline.copy(alpha = 0.7f), - modifier = Modifier.size(14.dp), - ) - Spacer(Modifier.width(6.dp)) - Column(Modifier.weight(1f)) { - Text( - stringResource(R.string.library_games_game_folder_mapped_drive), - color = TextSecondary, - fontSize = 9.sp, - ) - Text( - gameFolder ?: stringResource(R.string.common_ui_auto_detected), - color = if (gameFolder != null) TextPrimary else TextSecondary, - fontSize = 10.sp, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - } - IconButton(onClick = { - if (!ensureAllFilesAccessForImports(context)) return@IconButton - DirectoryPickerDialog.show( - activity = this@UnifiedActivity, - initialPath = gameFolder, - title = getString(R.string.common_ui_select_folder), - dimAmount = 0.5f, - preserveBackdropBlur = true, - ) { path -> gameFolder = path } - }, modifier = Modifier.size(28.dp)) { - Icon( - Icons.Outlined.Edit, - contentDescription = stringResource(R.string.common_ui_change), - tint = Accent, - modifier = Modifier.size(14.dp), - ) - } - } - } - } - - Spacer(Modifier.height(12.dp)) - - // Action buttons - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.End, - ) { - OutlinedButton( - onClick = onDismiss, - shape = RoundedCornerShape(10.dp), - border = androidx.compose.foundation.BorderStroke(1.dp, TextSecondary.copy(alpha = 0.3f)), - colors = ButtonDefaults.outlinedButtonColors(contentColor = TextSecondary), - contentPadding = PaddingValues(horizontal = 14.dp, vertical = 0.dp), - modifier = Modifier.height(34.dp).widthIn(min = 72.dp), - ) { - Text(stringResource(R.string.common_ui_cancel), fontSize = 12.sp) - } - Spacer(Modifier.width(8.dp)) - val addEnabled = selectedExePath != null && gameName.isNotBlank() && gameFolder != null && !isAdding - OutlinedButton( - onClick = { - if (selectedExePath == null || gameName.isBlank() || gameFolder == null) { - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - context.getString(R.string.library_games_select_exe_provide_name), - android.widget.Toast.LENGTH_SHORT, - ) - return@OutlinedButton - } - isAdding = true - scope.launch(Dispatchers.IO) { - addCustomGame(context, gameName.trim(), selectedExePath!!, gameFolder!!) - withContext(Dispatchers.Main) { - isAdding = false - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - "$gameName added!", - android.widget.Toast.LENGTH_SHORT, - ) - onDismiss() - } - } - }, - enabled = addEnabled, - shape = RoundedCornerShape(10.dp), - border = - androidx.compose.foundation.BorderStroke( - 1.dp, - if (addEnabled) Accent.copy(alpha = 0.5f) else TextSecondary.copy(alpha = 0.2f), - ), - colors = ButtonDefaults.outlinedButtonColors(contentColor = Accent), - contentPadding = PaddingValues(horizontal = 14.dp, vertical = 0.dp), - modifier = Modifier.height(34.dp).widthIn(min = 72.dp), - ) { - if (isAdding) { - CircularProgressIndicator(color = Accent, modifier = Modifier.size(12.dp), strokeWidth = 2.dp) - } else { - Text(stringResource(R.string.common_ui_add), fontWeight = FontWeight.Medium, fontSize = 12.sp) - } - } - } - } - } - } - } - } - - private fun ensureAllFilesAccessForImports(context: android.content.Context): Boolean { - if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.R || android.os.Environment.isExternalStorageManager()) { - return true - } - - com.winlator.cmod.shared.ui.toast.WinToast.show( - context, - "Grant All files access to browse Downloads directly.", - android.widget.Toast.LENGTH_LONG, - ) - - val intent = - android.content.Intent(android.provider.Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION).apply { - data = android.net.Uri.parse("package:$packageName") - } - startActivity(intent) - return false - } - - // Create custom game shortcut + container - private fun addCustomGame( - context: android.content.Context, - name: String, - exePath: String, - gameFolderPath: String, - ) { - val containerManager = ContainerManager(context) - var container = SetupWizardActivity.getPreferredGameContainer(context, containerManager) - if (container == null) { - SetupWizardActivity.promptToInstallWineOrCreateContainer(context) - return - } - - val exeFile = java.io.File(exePath) - normalizeContainerDrives(container) - val execCmd = buildWineExecCommand(container, gameFolderPath, exeFile) - - // Write .desktop shortcut - val desktopDir = container.getDesktopDir() - if (!desktopDir.exists()) desktopDir.mkdirs() - val safeName = name.replace("/", "_").replace("\\", "_") - val shortcutFile = java.io.File(desktopDir, "$safeName.desktop") - val shortcutUuid = java.util.UUID.randomUUID().toString() - val iconOutFile = LibraryShortcutArtwork.buildManagedCustomGameArtworkFile(context, shortcutUuid) - val extractedArtworkPath = - try { - if (PeIconExtractor.extractAndSave(java.io.File(exePath), iconOutFile)) { - iconOutFile.absolutePath - } else { - null - } - } catch (_: Exception) { - null - } - val content = StringBuilder() - content.append("[Desktop Entry]\n") - content.append("Type=Application\n") - content.append("Name=$name\n") - content.append("Exec=$execCmd\n") - content.append("Icon=custom_game\n") - content.append("\n[Extra Data]\n") - content.append("game_source=CUSTOM\n") - content.append("custom_name=$name\n") - content.append("custom_exe=$exePath\n") - content.append("custom_game_folder=$gameFolderPath\n") - content.append("uuid=$shortcutUuid\n") - extractedArtworkPath?.let { content.append("customCoverArtPath=$it\n") } - content.append("container_id=${container.id}\n") - content.append("use_container_defaults=1\n") - com.winlator.cmod.shared.io.FileUtils - .writeString(shortcutFile, content.toString()) - container.saveData() - } - - @Composable - fun CustomPathWarningDialog( - onDismiss: () -> Unit, - onProceed: () -> Unit, - ) { - Dialog(onDismissRequest = onDismiss) { - Surface( - shape = RoundedCornerShape(16.dp), - color = CardDark, - modifier = Modifier.padding(16.dp), - ) { - Column(modifier = Modifier.padding(24.dp)) { - Text( - text = stringResource(R.string.stores_accounts_custom_download_path), - style = MaterialTheme.typography.titleLarge, - color = TextPrimary, - fontWeight = FontWeight.Bold, - ) - Spacer(Modifier.height(16.dp)) - Text( - text = stringResource(R.string.stores_accounts_custom_download_path_description), - style = MaterialTheme.typography.bodyMedium, - color = TextSecondary, - ) - Spacer(Modifier.height(24.dp)) - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.Center, - ) { - TextButton(onClick = onDismiss) { - Text(stringResource(R.string.common_ui_close), color = TextSecondary) - } - Spacer(Modifier.width(8.dp)) - Button( - onClick = onProceed, - colors = ButtonDefaults.buttonColors(containerColor = Accent), - shape = RoundedCornerShape(8.dp), - ) { - Text(stringResource(R.string.common_ui_proceed)) - } - } - } - } - } - } - - @Composable - private fun rememberControllerConnectionState(): ControllerConnectionState { - val context = LocalContext.current - val inputManager = remember(context) { context.getSystemService(InputManager::class.java) } - var controllerState by remember { mutableStateOf(ControllerConnectionState()) } - - DisposableEffect(inputManager) { - fun refreshState() { - controllerState = - ControllerConnectionState( - isConnected = ControllerHelper.isControllerConnected(), - isPlayStation = ControllerHelper.isPlayStationController(), - ) - } - - val listener = - object : InputManager.InputDeviceListener { - override fun onInputDeviceAdded(deviceId: Int) = refreshState() - - override fun onInputDeviceRemoved(deviceId: Int) = refreshState() - - override fun onInputDeviceChanged(deviceId: Int) = refreshState() - } - - refreshState() - inputManager?.registerInputDeviceListener(listener, null) - onDispose { - inputManager?.unregisterInputDeviceListener(listener) - } - } - - return controllerState - } } @Composable fun ControllerBadge( text: String, modifier: Modifier = Modifier, + compact: Boolean = false, ) { + val corner = if (compact) 11.dp else 15.dp Box( modifier = modifier - .defaultMinSize(minHeight = 22.dp) - .background(Color(0xFF394048), RoundedCornerShape(15.dp)) - .border(1.dp, Color(0xFF8B949E).copy(alpha = 0.5f), RoundedCornerShape(15.dp)) - .padding(horizontal = 10.dp, vertical = 3.dp), + .defaultMinSize(minHeight = if (compact) 16.dp else 22.dp) + .background(Color(0xFF394048), RoundedCornerShape(corner)) + .border(1.dp, Color(0xFF8B949E).copy(alpha = 0.5f), RoundedCornerShape(corner)) + .padding(horizontal = if (compact) 5.dp else 10.dp, vertical = if (compact) 1.dp else 3.dp), contentAlignment = Alignment.Center, ) { Text( text = text, color = Color(0xFFE6EDF3), - fontSize = 12.sp, + fontSize = if (compact) 9.sp else 12.sp, fontWeight = FontWeight.Bold, - lineHeight = 15.sp, + lineHeight = if (compact) 11.sp else 15.sp, style = MaterialTheme.typography.labelSmall, ) } } + +internal inline fun android.content.Context.runIfOnlineOrToast(action: () -> Unit) { + if (com.winlator.cmod.app.service.NetworkMonitor.hasInternet.value) { + action() + } else { + com.winlator.cmod.shared.ui.toast.WinToast.show( + this, + getString(R.string.downloads_no_internet), + android.widget.Toast.LENGTH_SHORT, + ) + } +} diff --git a/app/src/main/app/shell/UnifiedActivityDownloads.kt b/app/src/main/app/shell/UnifiedActivityDownloads.kt new file mode 100644 index 000000000..60e18c020 --- /dev/null +++ b/app/src/main/app/shell/UnifiedActivityDownloads.kt @@ -0,0 +1,1839 @@ +package com.winlator.cmod.app.shell +import com.winlator.cmod.app.shell.UnifiedActivity.DownloadCancelRequest + +import android.app.Activity +import android.app.PendingIntent +import android.content.Intent +import android.content.res.Configuration +import android.hardware.input.InputManager +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.drawable.BitmapDrawable +import android.net.Uri +import android.os.Bundle +import android.provider.DocumentsContract +import android.util.Log +import androidx.activity.SystemBarStyle +import androidx.activity.compose.BackHandler +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AppCompatActivity +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.EnterTransition +import androidx.compose.animation.ExitTransition +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.FastOutSlowInEasing +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.spring +import androidx.compose.animation.core.tween +import androidx.compose.animation.expandVertically +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.shrinkVertically +import androidx.compose.animation.slideInHorizontally +import androidx.compose.animation.slideOutHorizontally +import androidx.compose.animation.togetherWith +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background +import androidx.compose.foundation.basicMarquee +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.focusable +import androidx.compose.foundation.gestures.detectHorizontalDragGestures +import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsPressedAsState +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items +import androidx.compose.foundation.lazy.grid.itemsIndexed +import androidx.compose.foundation.lazy.grid.rememberLazyGridState +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.outlined.ArrowBack +import androidx.compose.material.icons.automirrored.outlined.ExitToApp +import androidx.compose.material.icons.automirrored.outlined.OpenInNew +import androidx.compose.material.icons.outlined.* +import androidx.compose.material3.* +import androidx.compose.material3.pulltorefresh.PullToRefreshBox +import androidx.compose.runtime.* +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.snapshotFlow +import androidx.compose.runtime.snapshots.SnapshotStateMap +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.zIndex +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusProperties +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.focus.focusTarget +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.geometry.CornerRadius +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.TileMode +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.input.pointer.PointerEventPass +import androidx.compose.ui.input.pointer.PointerEventType +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties +import androidx.lifecycle.lifecycleScope +import androidx.navigation.NavHostController +import androidx.navigation.NavType +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import androidx.navigation.navArgument +import coil.compose.AsyncImage +import coil.imageLoader +import coil.request.ImageRequest +import com.winlator.cmod.BuildConfig +import com.winlator.cmod.R +import com.winlator.cmod.app.PluviaApp +import com.winlator.cmod.app.db.PluviaDatabase +import com.winlator.cmod.app.service.DownloadService +import com.winlator.cmod.app.service.download.DownloadCoordinator +import com.winlator.cmod.app.update.UpdateChecker +import com.winlator.cmod.feature.settings.InputControlsFragment +import com.winlator.cmod.feature.settings.SettingsFocusZone +import com.winlator.cmod.feature.settings.SettingsHost +import com.winlator.cmod.feature.settings.SettingsNavBridge +import com.winlator.cmod.feature.settings.SettingsNavItem +import com.winlator.cmod.feature.setup.SetupWizardActivity +import com.winlator.cmod.feature.shortcuts.LibraryShortcutUtils +import com.winlator.cmod.feature.shortcuts.LibraryShortcutArtwork +import com.winlator.cmod.feature.shortcuts.ShortcutBroadcastReceiver +import com.winlator.cmod.feature.shortcuts.ShortcutSettingsComposeDialog +import com.winlator.cmod.feature.shortcuts.ShortcutsFragment +import com.winlator.cmod.feature.stores.common.StoreArtworkCache +import com.winlator.cmod.feature.stores.epic.data.EpicCredentials +import com.winlator.cmod.feature.stores.epic.data.EpicGame +import com.winlator.cmod.feature.stores.epic.data.EpicGameToken +import com.winlator.cmod.feature.stores.epic.service.EpicAuthManager +import com.winlator.cmod.feature.stores.epic.service.EpicCloudSavesManager +import com.winlator.cmod.feature.stores.epic.service.EpicConstants +import com.winlator.cmod.feature.stores.epic.service.EpicDownloadManager +import com.winlator.cmod.feature.stores.epic.service.EpicGameLauncher +import com.winlator.cmod.feature.stores.epic.service.EpicManager +import com.winlator.cmod.feature.stores.epic.service.EpicService +import com.winlator.cmod.feature.stores.epic.service.EpicUpdateInfo +import com.winlator.cmod.feature.stores.epic.ui.auth.EpicOAuthActivity +import com.winlator.cmod.feature.stores.gog.data.GOGDlcInfo +import com.winlator.cmod.feature.stores.gog.data.GOGGame +import com.winlator.cmod.feature.stores.gog.data.LibraryItem +import com.winlator.cmod.feature.stores.gog.service.GOGAuthManager +import com.winlator.cmod.feature.stores.gog.service.GOGConstants +import com.winlator.cmod.feature.stores.gog.service.GOGManifestSizes +import com.winlator.cmod.feature.stores.gog.service.GOGService +import com.winlator.cmod.feature.stores.gog.service.GOGUpdateInfo +import com.winlator.cmod.feature.stores.gog.ui.auth.GOGOAuthActivity +import com.winlator.cmod.feature.stores.steam.SteamLoginActivity +import com.winlator.cmod.feature.stores.steam.data.DepotInfo +import com.winlator.cmod.feature.stores.steam.data.DownloadInfo +import com.winlator.cmod.feature.stores.steam.data.SteamApp +import com.winlator.cmod.feature.stores.steam.enums.DownloadPhase +import com.winlator.cmod.feature.stores.steam.events.AndroidEvent +import com.winlator.cmod.feature.stores.steam.events.EventDispatcher +import com.winlator.cmod.feature.stores.steam.service.SteamService +import com.winlator.cmod.feature.stores.steam.utils.PrefManager +import com.winlator.cmod.feature.stores.steam.utils.getAvatarURL +import com.winlator.cmod.feature.sync.CloudSyncHelper +import com.winlator.cmod.feature.sync.google.CloudSyncManager +import com.winlator.cmod.feature.sync.google.GameSaveBackupManager +import com.winlator.cmod.feature.sync.ui.CloudSavesContent +import com.winlator.cmod.runtime.container.ContainerManager +import com.winlator.cmod.runtime.container.Shortcut +import com.winlator.cmod.runtime.display.XServerDisplayActivity +import com.winlator.cmod.runtime.display.environment.ImageFs +import com.winlator.cmod.runtime.input.ControllerHelper +import com.winlator.cmod.runtime.wine.PeIconExtractor +import com.winlator.cmod.shared.android.ActivityResultHost +import com.winlator.cmod.shared.android.AppTerminationHelper +import com.winlator.cmod.shared.android.DirectoryPickerDialog +import com.winlator.cmod.shared.android.FixedFontScaleAppCompatActivity +import com.winlator.cmod.shared.android.RefreshRateUtils +import com.winlator.cmod.shared.io.StorageUtils +import com.winlator.cmod.shared.io.FileUtils +import com.winlator.cmod.shared.ui.CarouselView +import com.winlator.cmod.shared.ui.dialog.PopupDialog +import com.winlator.cmod.shared.ui.dialog.PopupTextAction +import androidx.compose.foundation.focusGroup +import com.winlator.cmod.shared.ui.focus.controllerFocusGlow +import com.winlator.cmod.shared.ui.focus.controllerMenuInput +import com.winlator.cmod.shared.ui.focus.controllerTextFieldEscape +import com.winlator.cmod.shared.ui.nav.DialogPaneNav +import com.winlator.cmod.shared.ui.nav.LocalPaneNav +import com.winlator.cmod.shared.ui.nav.PANE_DIR_ACTIVATE +import com.winlator.cmod.shared.ui.nav.PANE_DIR_DOWN +import com.winlator.cmod.shared.ui.nav.PANE_DIR_LEFT +import com.winlator.cmod.shared.ui.nav.PANE_DIR_RIGHT +import com.winlator.cmod.shared.ui.nav.PANE_DIR_SECONDARY +import com.winlator.cmod.shared.ui.nav.PANE_DIR_UP +import com.winlator.cmod.shared.ui.nav.PaneNavRegistry +import com.winlator.cmod.shared.ui.nav.paneNavItem +import com.winlator.cmod.shared.ui.FourByTwoGridView +import com.winlator.cmod.shared.ui.JoystickGridScroll +import com.winlator.cmod.shared.ui.JoystickListScroll +import com.winlator.cmod.shared.ui.ListView +import com.winlator.cmod.shared.ui.widget.chasingBorder +import com.winlator.cmod.shared.theme.WinNativeTheme +import dagger.hilt.android.AndroidEntryPoint +import dagger.Lazy +import com.winlator.cmod.feature.stores.steam.enums.EPersonaState +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import javax.inject.Inject +import kotlin.math.abs +import kotlin.math.roundToInt + +// Downloads tab + queue/progress UI + game/workshop managers, split out of UnifiedActivity.kt (behavior-identical). + +// Downloads Tab +@Composable +internal fun UnifiedActivity.DownloadsTab( + selectedId: String?, + animationsActive: Boolean = true, + onSelectDownload: (String?) -> Unit, +) { + val downloads = remember { mutableStateListOf>() } + var tick by remember { mutableIntStateOf(0) } + val scope = rememberCoroutineScope() + var cancelWarningRequest by remember { mutableStateOf(null) } + + val downloadsActivity = LocalContext.current as? UnifiedActivity + val bridge = downloadsActivity?.downloadsNavBridge + val navRegistry = remember(bridge) { PaneNavRegistry(initialSignal = bridge?.navSignal ?: -1) } + navRegistry.controllerActive = bridge?.controllerActive ?: false + LaunchedEffect(navRegistry, bridge?.navSignal) { + navRegistry.processNav(bridge?.navSignal ?: 0, bridge?.navDir ?: 0) + } + + val syncDownloads = + remember(selectedId, onSelectDownload) { + { + val currentDownloads = DownloadService.getAllDownloads() + downloads.clear() + downloads.addAll(currentDownloads) + if (selectedId != null && currentDownloads.none { it.first == selectedId }) { + onSelectDownload(null) + } + } + } + val latestSyncDownloads by rememberUpdatedState(syncDownloads) + + val downloadStatusListener = + remember { + object : EventDispatcher.JavaEventListener { + override fun onEvent(event: Any) { + if (event is AndroidEvent.DownloadStatusChanged) { + scope.launch { + latestSyncDownloads() + } + } + } + } + } + + DisposableEffect(downloadStatusListener, syncDownloads) { + syncDownloads() + PluviaApp.events.onJava(AndroidEvent.DownloadStatusChanged::class, downloadStatusListener) + onDispose { + PluviaApp.events.offJava(AndroidEvent.DownloadStatusChanged::class, downloadStatusListener) + } + } + + // Re-sync the list whenever the cross-store DownloadCoordinator records change. This + // is what makes PAUSED records (loaded from DB after app restart) appear in the tab, + // and what removes COMPLETE/CANCELLED/FAILED rows after Clear. + LaunchedEffect(syncDownloads) { + DownloadCoordinator.changes.collect { + latestSyncDownloads() + } + } + + downloads.forEach { (_, info) -> + LaunchedEffect(info) { + info.getStatusFlow().collect { + tick++ + } + } + // Also recompose on status-message changes. Active downloads push + // a changing message every progress tick, so this is what keeps + // the byte count / speed / progress bar refreshing live (the + // phase flow dedups to a single DOWNLOADING emission). + LaunchedEffect(info) { + info.getStatusMessageFlow().collect { + tick++ + } + } + } + + CompositionLocalProvider(LocalPaneNav provides navRegistry) { + Column( + Modifier + .fillMaxSize() + .windowInsetsPadding(WindowInsets.navigationBars.only(WindowInsetsSides.Bottom)) + .tabScreenPadding(top = DownloadsHeaderTopPadding) + .pointerInput(Unit) { + awaitPointerEventScope { + while (true) { + val ev = awaitPointerEvent(PointerEventPass.Initial) + if (ev.type == PointerEventType.Press) { + bridge?.controllerActive = false + } + } + } + }, + ) { + @Suppress("UNUSED_EXPRESSION") + tick + + Row( + modifier = Modifier.fillMaxWidth().padding(bottom = 10.dp), + horizontalArrangement = Arrangement.spacedBy(10.dp, Alignment.CenterHorizontally), + verticalAlignment = Alignment.CenterVertically, + ) { + val selectedInfo = downloads.find { it.first == selectedId }?.second + val selectedStatus = selectedInfo?.getStatusFlow()?.value + // FAILED is resumable: the dispatcher preserves every breadcrumb + // on failure, so one click continues from where it left off. + val isResumable = + selectedStatus == DownloadPhase.PAUSED || selectedStatus == DownloadPhase.FAILED + val isComplete = selectedStatus == DownloadPhase.COMPLETE + val isCancelled = selectedStatus == DownloadPhase.CANCELLED + val pausableDownloads = + downloads.filter { + val status = it.second.getStatusFlow().value + status != DownloadPhase.COMPLETE && status != DownloadPhase.CANCELLED + } + val allPausableDownloadsPaused = + pausableDownloads.isNotEmpty() && + pausableDownloads.all { + val s = it.second.getStatusFlow().value + s == DownloadPhase.PAUSED || s == DownloadPhase.FAILED + } + + val pauseResumeLabel = + if (selectedId == null) { + if (allPausableDownloadsPaused) { + stringResource( + R.string.downloads_queue_resume_all, + ) + } else { + stringResource(R.string.downloads_queue_pause_all) + } + } else { + when { + selectedStatus == DownloadPhase.FAILED -> stringResource(R.string.session_drawer_retry) + isResumable -> stringResource(R.string.session_drawer_resume) + else -> stringResource(R.string.session_drawer_pause) + } + } + + val cancelLabel = + if (selectedId == null) { + stringResource(R.string.downloads_queue_cancel_all) + } else { + stringResource(R.string.common_ui_cancel) + } + + // Disable pause/resume for completed or cancelled downloads + val pauseResumeEnabled = + if (selectedId != null) { + !isComplete && !isCancelled + } else { + pausableDownloads.isNotEmpty() + } + + val cancelEnabled = + if (selectedId != null) { + !isComplete && !isCancelled + } else { + pausableDownloads.isNotEmpty() + } + + DownloadsQueueButton( + label = pauseResumeLabel, + accentColor = Accent, + onClick = { + val isResumeAction = + if (selectedId == null) allPausableDownloadsPaused else isResumable + val run = { + when { + selectedId == null && allPausableDownloadsPaused -> DownloadService.resumeAll() + selectedId == null -> DownloadService.pauseAll() + isResumable -> DownloadService.resumeDownload(selectedId) + else -> DownloadService.pauseDownload(selectedId) + } + } + if (isResumeAction) this@DownloadsTab.runIfOnlineOrToast(run) else run() + }, + enabled = pauseResumeEnabled, + ) + + Box { + DownloadsQueueButton( + label = cancelLabel, + accentColor = DangerRed, + onClick = { + if (selectedId == null) { + cancelWarningRequest = + DownloadCancelRequest( + ids = pausableDownloads.map { it.first }, + isCancelAll = true, + ) + } else { + cancelWarningRequest = + DownloadCancelRequest( + ids = listOf(selectedId), + isCancelAll = false, + ) + } + }, + enabled = cancelEnabled, + ) + + cancelWarningRequest?.let { request -> + DownloadCancelWarningMenu( + expanded = true, + onDismissRequest = { cancelWarningRequest = null }, + onConfirm = { + val activeRequest = cancelWarningRequest + cancelWarningRequest = null + val ids = activeRequest?.ids.orEmpty() + if (activeRequest?.isCancelAll == true) { + DownloadService.cancelAll() + } else { + ids.forEach(DownloadService::cancelDownload) + } + onSelectDownload(null) + }, + isCancelAll = request.isCancelAll, + ) + } + } + + // Clear button - clears completed, cancelled, and failed downloads + val hasCompletedOrCancelled = + downloads.any { + val s = it.second.getStatusFlow().value + s == DownloadPhase.COMPLETE || s == DownloadPhase.CANCELLED || s == DownloadPhase.FAILED + } + + DownloadsQueueButton( + label = stringResource(R.string.downloads_queue_clear), + accentColor = Accent, + onClick = { + DownloadService.clearCompletedDownloads() + }, + enabled = hasCompletedOrCancelled, + ) + } + + val listState = rememberLazyListState() + val activity = LocalContext.current as? UnifiedActivity + val density = LocalContext.current.resources.displayMetrics.density + + LaunchedEffect(listState) { + activity?.rightStickScrollState?.collect { rz -> + if (kotlin.math.abs(rz) > 0.1f) { + // Max scroll speed is 20 rows per second (approx 20 * 100dp / 60fps ~ 32dp per frame) + // Min scroll speed is 0.75 rows per second (approx 0.75 * 100dp / 60fps ~ 1.25dp per frame) + // Use a square curve for more gradual acceleration + val speedFactor = kotlin.math.abs(rz) + val curveFactor = speedFactor * speedFactor + val baseSpeed = 1.25f + (curveFactor * (32f - 1.25f)) + val direction = if (rz > 0) 1f else -1f + + // Using a loop while the stick is held + while (kotlin.math.abs(activity.rightStickScrollState.value) > 0.1f) { + val currentRz = activity.rightStickScrollState.value + val currentSpeedFactor = kotlin.math.abs(currentRz) + val currentCurveFactor = currentSpeedFactor * currentSpeedFactor + val currentBaseSpeed = 1.25f + (currentCurveFactor * (32f - 1.25f)) + val currentDirection = if (currentRz > 0) 1f else -1f + + val pixelsToScroll = currentBaseSpeed * currentDirection * density + listState.dispatchRawDelta(pixelsToScroll) + kotlinx.coroutines.delay(16) // roughly 60fps + } + } + } + } + + // Sort so the user always sees what's actually running first, then everything + // they can resume, then finished items, with cancelled at the very bottom. + // The list re-sorts on phase transitions because `tick` (incremented by the + // status flow collectors above) is read here, forcing recomposition. + @Suppress("UNUSED_EXPRESSION") + tick + val sortedDownloads = + downloads.sortedBy { (_, info) -> + when (info.getStatusFlow().value) { + // In-progress states grouped together at the top. + DownloadPhase.DOWNLOADING, + DownloadPhase.PREPARING, + DownloadPhase.VERIFYING, + DownloadPhase.PATCHING, + DownloadPhase.APPLYING_DATA, + DownloadPhase.FINALIZING, + DownloadPhase.UNPACKING, + DownloadPhase.UNKNOWN, + -> 0 + // FAILED sorts with PAUSED — both are user-resumable; + // don't bury them under finished downloads. + DownloadPhase.PAUSED -> 1 + DownloadPhase.FAILED -> 1 + DownloadPhase.QUEUED -> 2 + DownloadPhase.COMPLETE -> 3 + DownloadPhase.CANCELLED -> 5 + } + } + + if (sortedDownloads.isEmpty()) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center, + ) { + EmptyStateMessage(stringResource(R.string.downloads_queue_empty)) + } + } else { + LazyColumn(state = listState, modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.spacedBy(8.dp)) { + items(sortedDownloads, key = { it.first }) { (id, info) -> + DownloadItemDeck( + id, + info, + isSelected = selectedId == id, + animationsActive = animationsActive, + onClick = { + if (selectedId == id) onSelectDownload(null) else onSelectDownload(id) + }, + ) + } + } + } + } + } +} + +@Composable +internal fun UnifiedActivity.DownloadsQueueButton( + label: String, + accentColor: Color, + enabled: Boolean, + modifier: Modifier = Modifier, + onClick: () -> Unit, +) { + val contentColor = if (enabled) accentColor else TextSecondary.copy(alpha = 0.48f) + + Button( + onClick = onClick, + enabled = enabled, + modifier = + modifier + .height(40.dp) + .widthIn(min = 96.dp) + .paneNavItem(cornerRadius = 8.dp, onActivate = { if (enabled) onClick() }), + colors = + ButtonDefaults.buttonColors( + containerColor = DownloadButtonBlack, + contentColor = contentColor, + disabledContainerColor = DownloadButtonBlack.copy(alpha = 0.18f), + disabledContentColor = TextSecondary.copy(alpha = 0.48f), + ), + border = BorderStroke(1.dp, contentColor.copy(alpha = if (enabled) 0.55f else 0.24f)), + contentPadding = PaddingValues(horizontal = 8.dp), + shape = RoundedCornerShape(8.dp), + ) { + Text( + label, + color = contentColor, + fontSize = 12.sp, + fontWeight = FontWeight.SemiBold, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } +} + +@Composable +internal fun UnifiedActivity.AnimatedDownloadProgressFill( + modifier: Modifier, + widthPx: Float, +) { + val infiniteTransition = rememberInfiniteTransition(label = "downloadProgressGradient") + val gradientOffset by infiniteTransition.animateFloat( + initialValue = -widthPx, + targetValue = 0f, + animationSpec = + infiniteRepeatable( + animation = tween(durationMillis = 5000, easing = LinearEasing), + repeatMode = RepeatMode.Restart, + ), + label = "downloadProgressGradientOffset", + ) + + Box( + modifier.background( + Brush.horizontalGradient( + colorStops = DownloadChaseGradientStops, + startX = gradientOffset, + endX = gradientOffset + (widthPx * 2f), + tileMode = TileMode.Repeated, + ), + ), + ) +} + +@Composable +internal fun UnifiedActivity.DownloadChasingProgressBar( + progress: Float, + status: DownloadPhase, + animationsActive: Boolean, + modifier: Modifier = Modifier, +) { + val clampedProgress = progress.coerceIn(0f, 1f) + val shouldUseActiveGradient = + when (status) { + DownloadPhase.DOWNLOADING, + DownloadPhase.QUEUED, + DownloadPhase.PREPARING, + DownloadPhase.VERIFYING, + DownloadPhase.PATCHING, + DownloadPhase.APPLYING_DATA, + DownloadPhase.FINALIZING, + DownloadPhase.UNPACKING, + -> true + else -> false + } + val shouldAnimate = shouldUseActiveGradient && animationsActive + val fillColor = + when (status) { + DownloadPhase.FAILED, + DownloadPhase.CANCELLED, + -> DangerRed + DownloadPhase.COMPLETE -> StatusOnline + DownloadPhase.PAUSED -> TextSecondary + else -> Accent + } + + BoxWithConstraints( + modifier = + modifier + .clip(CircleShape) + .background(Color.Black.copy(alpha = 0.34f)), + ) { + val density = LocalDensity.current + val widthPx = with(density) { maxWidth.toPx().coerceAtLeast(1f) } + + if (clampedProgress > 0f) { + val fillModifier = + Modifier + .fillMaxHeight() + .fillMaxWidth(clampedProgress) + .clip(RectangleShape) + + if (shouldUseActiveGradient) { + if (shouldAnimate) { + AnimatedDownloadProgressFill(fillModifier, widthPx) + } else { + Box( + fillModifier.background( + Brush.horizontalGradient( + colorStops = DownloadChaseGradientStops, + endX = widthPx * 2f, + tileMode = TileMode.Repeated, + ), + ), + ) + } + } else { + Box(fillModifier.background(fillColor)) + } + } + } +} + +/** + * The live progress body (phase label, bar, percentage, byte counts) for a + * game's in-flight download / verify. Observes [info] directly so it + * refreshes live. Rendered inside [SteamTaskProgressDialog]. + */ +@Composable +internal fun UnifiedActivity.SteamTaskProgressBody(info: DownloadInfo) { + var progress by remember(info) { mutableFloatStateOf(info.getProgress()) } + DisposableEffect(info) { + val listener: (Float) -> Unit = { progress = it } + info.addProgressListener(listener) + onDispose { info.removeProgressListener(listener) } + } + val status by info.getStatusFlow().collectAsState() + // The status message carries a unique suffix every progress tick; + // keying the byte sample on it (and on `progress`) keeps the card + // refreshing live — the Downloads-tab row relies on the same. + val statusMessage by info.getStatusMessageFlow().collectAsState() + val fraction = progress.coerceIn(0f, 1f) + val animatedFraction by animateFloatAsState( + targetValue = fraction, + animationSpec = tween(durationMillis = 400), + label = "steamTaskProgress", + ) + val (doneBytes, totalBytes) = + remember(progress, statusMessage) { info.getDisplayBytesProgress() } + + val phaseText = + when (status) { + DownloadPhase.VERIFYING -> stringResource(R.string.downloads_queue_phase_verifying) + DownloadPhase.DOWNLOADING -> stringResource(R.string.downloads_queue_phase_downloading) + DownloadPhase.PAUSED -> stringResource(R.string.downloads_queue_phase_paused) + DownloadPhase.QUEUED -> stringResource(R.string.downloads_queue_phase_queued) + DownloadPhase.PREPARING -> stringResource(R.string.downloads_queue_phase_preparing) + DownloadPhase.PATCHING -> stringResource(R.string.downloads_queue_phase_patching) + DownloadPhase.APPLYING_DATA -> stringResource(R.string.downloads_queue_phase_applying_data) + DownloadPhase.UNPACKING -> stringResource(R.string.downloads_queue_phase_unpacking) + DownloadPhase.FINALIZING -> stringResource(R.string.downloads_queue_phase_finalizing) + DownloadPhase.COMPLETE -> stringResource(R.string.downloads_queue_phase_complete) + DownloadPhase.FAILED -> stringResource(R.string.common_ui_failed) + DownloadPhase.CANCELLED -> stringResource(R.string.downloads_queue_phase_cancelled) + else -> stringResource(R.string.common_ui_working) + } + val phaseColor = + when (status) { + DownloadPhase.COMPLETE -> StatusOnline + DownloadPhase.FAILED, DownloadPhase.CANCELLED -> DangerRed + DownloadPhase.PAUSED, DownloadPhase.QUEUED -> StatusAway + else -> Accent + } + + Column(verticalArrangement = Arrangement.spacedBy(9.dp)) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Text(phaseText, color = phaseColor, fontSize = 14.sp, fontWeight = FontWeight.Bold) + Text( + "${(fraction * 100).toInt()}%", + color = TextPrimary, + fontSize = 14.sp, + fontWeight = FontWeight.Bold, + ) + } + DownloadChasingProgressBar( + progress = animatedFraction, + status = status, + animationsActive = true, + modifier = Modifier.fillMaxWidth().height(10.dp), + ) + Text( + if (totalBytes > 0L) { + "${StorageUtils.formatDecimalSize(doneBytes)} / " + + StorageUtils.formatDecimalSize(totalBytes) + } else { + " " + }, + color = TextSecondary, + fontSize = 11.sp, + ) + } +} + +/** + * Activity-root host for the Verify Files progress pop-up + completion + * notice. Rendered once near the NavHost so it outlives the game-detail + * dialogs that start the task — the verify-completion library refresh + * tears those down, and a dialog-scoped watcher would miss COMPLETE. + * + * Reads the `taskProgress*` activity fields; [showTaskProgressPopup] + * populates them. The watcher is keyed on [taskProgressInfo] so it + * re-attaches if recomposed and (because the status is a StateFlow) + * still observes a terminal phase that landed in between. + */ +@Composable +internal fun UnifiedActivity.TaskProgressHost() { + val info = taskProgressInfo + LaunchedEffect(info) { + if (info == null) return@LaunchedEffect + info.getStatusFlow().collect { st -> + when (st) { + DownloadPhase.COMPLETE -> { + // Snapshot first: `taskProgressShown = false` can trigger + // a follow-up task that overwrites these fields before we read. + val msg = taskProgressCompleteMsg + val asToast = taskProgressCompleteAsToast + taskProgressShown = false + taskProgressInfo = null + if (asToast) { + com.winlator.cmod.shared.ui.toast.WinToast.show( + this@TaskProgressHost, + msg, + android.widget.Toast.LENGTH_SHORT, + ) + } else { + taskDoneFailed = false + taskDoneMessage = msg + } + } + DownloadPhase.FAILED -> { + taskProgressShown = false + taskDoneFailed = true + taskDoneMessage = taskProgressFailedMsg + taskProgressInfo = null + } + DownloadPhase.CANCELLED -> { + taskProgressShown = false + taskProgressInfo = null + } + else -> Unit + } + } + } + if (taskCheckingShown) { + TaskCheckingDialog( + gameName = taskCheckingGameName, + onDismissRequest = { taskCheckingShown = false }, + ) + } + if (info != null && taskProgressShown) { + SteamTaskProgressDialog( + info = info, + gameName = taskProgressGameName, + onDismissRequest = { taskProgressShown = false }, + ) + } + taskDoneMessage?.let { msg -> + TaskCompleteDialog( + message = msg, + failed = taskDoneFailed, + onClose = { taskDoneMessage = null }, + ) + } +} + +/** + * Indeterminate "Checking for updates" pop-up — same frame as + * [SteamTaskProgressDialog] but without a known task to track. Dismissable; + * the underlying check keeps running and the host shows the result. + */ +@Composable +internal fun UnifiedActivity.TaskCheckingDialog( + gameName: String, + onDismissRequest: () -> Unit, +) { + Dialog(onDismissRequest = onDismissRequest) { + PopupDialog( + title = gameName, + message = stringResource(R.string.store_game_checking_updates), + icon = Icons.Outlined.Sync, + accentColor = Accent, + modifier = Modifier.widthIn(min = 280.dp, max = 360.dp), + content = { + LinearProgressIndicator( + modifier = Modifier + .fillMaxWidth() + .height(6.dp) + .clip(RoundedCornerShape(3.dp)), + color = Accent, + ) + }, + footer = { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.End, + ) { + PopupTextAction( + label = stringResource(R.string.common_ui_close), + textColor = Accent, + onClick = onDismissRequest, + ) + } + }, + ) + } +} + +/** + * Dismissable pop-up showing live progress for a Steam task (verify / + * update). Tapping outside closes it — the task keeps running and stays + * visible in the Downloads tab. The host watches the task to completion + * separately and shows [TaskCompleteDialog] when it finishes. + */ +@Composable +internal fun UnifiedActivity.SteamTaskProgressDialog( + info: DownloadInfo, + gameName: String, + onDismissRequest: () -> Unit, +) { + Dialog(onDismissRequest = onDismissRequest) { + PopupDialog( + title = gameName, + icon = Icons.Outlined.Download, + accentColor = Accent, + modifier = Modifier.widthIn(min = 280.dp, max = 360.dp), + content = { + SteamTaskProgressBody(info) + Text( + stringResource(R.string.store_game_progress_background_hint), + color = TextSecondary, + fontSize = 11.sp, + ) + }, + footer = { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.End, + ) { + PopupTextAction( + label = stringResource(R.string.common_ui_close), + textColor = Accent, + onClick = onDismissRequest, + ) + } + }, + ) + } +} + +/** + * Small completion notice ("Verify Files Complete" / " Failed") with a + * single Close button. Shown by the host once a watched task finishes. + */ +@Composable +internal fun UnifiedActivity.TaskCompleteDialog(message: String, failed: Boolean, onClose: () -> Unit) { + Dialog(onDismissRequest = onClose) { + PopupDialog( + title = message, + icon = if (failed) Icons.Outlined.Warning else Icons.Outlined.CheckCircle, + accentColor = if (failed) DangerRed else StatusOnline, + confirmButtonColor = Accent, + confirmLabel = stringResource(R.string.common_ui_close), + onConfirm = onClose, + modifier = Modifier.widthIn(min = 280.dp, max = 360.dp), + ) + } +} + +@Composable +internal fun UnifiedActivity.DownloadCancelWarningMenu( + expanded: Boolean, + onDismissRequest: () -> Unit, + onConfirm: () -> Unit, + isCancelAll: Boolean = false, +) { + val titleRes = if (isCancelAll) R.string.downloads_queue_cancel_all_title else R.string.downloads_queue_cancel_download_title + val messageRes = if (isCancelAll) R.string.downloads_queue_cancel_all_warning else R.string.downloads_queue_cancel_download_warning + val confirmRes = if (isCancelAll) R.string.downloads_queue_cancel_all else R.string.downloads_queue_cancel_download + LaunchDangerConfirmDialog( + visible = expanded, + title = stringResource(titleRes), + message = stringResource(messageRes), + confirmLabel = stringResource(confirmRes), + onDismissRequest = onDismissRequest, + onConfirm = onConfirm, + icon = Icons.Outlined.Warning, + titleTextAlign = TextAlign.Center, + messageTextAlign = TextAlign.Center, + ) +} + +@Composable +internal fun UnifiedActivity.DownloadItemDeck( + id: String, + info: DownloadInfo, + isSelected: Boolean, + animationsActive: Boolean, + onClick: () -> Unit, +) { + var progress by remember { mutableFloatStateOf(info.getProgress()) } + var showDeleteDialog by remember { mutableStateOf(false) } + + DisposableEffect(info) { + val listener: (Float) -> Unit = { progress = it } + info.addProgressListener(listener) + onDispose { info.removeProgressListener(listener) } + } + val status by info.getStatusFlow().collectAsState() + val statusMessage by info.getStatusMessageFlow().collectAsState() + var previousStatus by remember { mutableStateOf(status) } + var showCompletedProgressBar by remember { mutableStateOf(status != DownloadPhase.COMPLETE) } + val isSteam = id.startsWith("STEAM_") + val isEpic = id.startsWith("EPIC_") + val isGog = id.startsWith("GOG_") + val appId = + if (isSteam) { + id.removePrefix("STEAM_").toIntOrNull() ?: 0 + } else if (isEpic) { + id.removePrefix("EPIC_").toIntOrNull() ?: 0 + } else { + 0 + } + val gogId = if (isGog) id.removePrefix("GOG_") else "" + + var steamApp by remember(appId) { mutableStateOf(null) } + var epicGame by remember(appId) { mutableStateOf(null) } + var gogGame by remember(gogId) { mutableStateOf(null) } + val context = LocalContext.current + val clickInteractionSource = remember { MutableInteractionSource() } + val animatedProgress by animateFloatAsState( + targetValue = if (status == DownloadPhase.COMPLETE) 1f else progress.coerceIn(0f, 1f), + animationSpec = tween(durationMillis = 650, easing = FastOutSlowInEasing), + label = "downloadItemProgress", + ) + + LaunchedEffect(status) { + if (status == DownloadPhase.COMPLETE) { + if (previousStatus != DownloadPhase.COMPLETE) { + showCompletedProgressBar = true + delay(900) + } + showCompletedProgressBar = false + } else { + showCompletedProgressBar = true + } + previousStatus = status + } + + LaunchedEffect(appId, gogId, isSteam, isEpic, isGog) { + withContext(Dispatchers.IO) { + if (isSteam) { + steamApp = db.steamAppDao().findApp(appId) + } else if (isEpic) { + epicGame = EpicService.getEpicGameOf(appId) + } else if (isGog) { + gogGame = GOGService.getGOGGameOf(gogId) + } + } + } + + val unknownGameLabel = stringResource(R.string.library_games_unknown_game) + val displayName = + if (isSteam) { + steamApp?.name + } else if (isEpic) { + epicGame?.title + } else if (isGog) { + gogGame?.title + } else { + unknownGameLabel + } + val displayImage = + if (isSteam) { + steamApp?.getHeaderImageUrl() + } else if (isEpic) { + epicGame?.primaryImageUrl ?: epicGame?.iconUrl + } else if (isGog) { + gogGame?.imageUrl ?: gogGame?.iconUrl + } else { + null + } + + Surface( + color = if (isSelected) DownloadCardSelectedBlack else DownloadCardBlack, + shape = RoundedCornerShape(12.dp), + modifier = + Modifier + .fillMaxWidth() + .chasingBorder( + isFocused = isSelected, + paused = chasingBordersPaused.value || !animationsActive, + cornerRadius = 12.dp, + borderWidth = 2.dp, + animationDurationMs = 8000, + ) + .paneNavItem( + cornerRadius = 12.dp, + onActivate = onClick, + onSecondary = { + if (status != DownloadPhase.COMPLETE && status != DownloadPhase.CANCELLED) { + showDeleteDialog = true + } + }, + ) + .clickable( + interactionSource = clickInteractionSource, + indication = null, + onClick = onClick, + ), + ) { + Row(Modifier.padding(12.dp), verticalAlignment = Alignment.CenterVertically) { + AsyncImage( + model = + ImageRequest + .Builder(context) + .data(displayImage) + .crossfade(300) + .build(), + contentDescription = null, + modifier = Modifier.size(120.dp, 68.dp).clip(RoundedCornerShape(4.dp)), + contentScale = ContentScale.Crop, + ) + + Spacer(Modifier.width(16.dp)) + + Column(Modifier.weight(1f)) { + val currentFile by info.getCurrentFileNameFlow().collectAsState() + val (downloadedBytes, totalBytes) = info.getDisplayBytesProgress() + val speed = info.getCurrentDownloadSpeed() ?: 0L + val percentage = (animatedProgress * 100).roundToInt() + val showDownloadSpeed = + status == DownloadPhase.DOWNLOADING && + progress < 1f && + speed > 0 + + Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) { + Text( + displayName ?: unknownGameLabel, + fontWeight = FontWeight.Bold, + color = TextPrimary, + modifier = Modifier.weight(1f), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + + // Centered Size Info + Text( + text = "${StorageUtils.formatDecimalSize(downloadedBytes)} / ${StorageUtils.formatDecimalSize(totalBytes)}", + style = MaterialTheme.typography.labelMedium, + color = TextSecondary, + modifier = Modifier.weight(1f), + textAlign = TextAlign.Center, + ) + + Box(modifier = Modifier.weight(1f), contentAlignment = Alignment.CenterEnd) { + if (showDownloadSpeed) { + Text( + text = StorageUtils.formatBitsPerSecond(speed), + style = MaterialTheme.typography.labelMedium, + color = Accent, + fontWeight = FontWeight.Bold, + ) + } + } + } + + val statusText = + when (status) { + DownloadPhase.DOWNLOADING -> { + currentFile?.let { + stringResource(R.string.downloads_queue_phase_downloading_file, it.take(10)) + } ?: stringResource(R.string.downloads_queue_phase_downloading) + } + + DownloadPhase.PAUSED -> { + stringResource(R.string.downloads_queue_phase_paused) + } + + DownloadPhase.QUEUED -> { + stringResource(R.string.downloads_queue_phase_queued) + } + + DownloadPhase.PREPARING -> { + stringResource(R.string.downloads_queue_phase_preparing) + } + + DownloadPhase.VERIFYING -> { + currentFile?.let { + stringResource(R.string.downloads_queue_phase_verifying_file, it.take(10)) + } ?: stringResource(R.string.downloads_queue_phase_verifying) + } + + DownloadPhase.PATCHING -> { + stringResource(R.string.downloads_queue_phase_patching) + } + + DownloadPhase.APPLYING_DATA -> { + stringResource(R.string.downloads_queue_phase_applying_data) + } + + DownloadPhase.FINALIZING -> { + stringResource(R.string.downloads_queue_phase_finalizing) + } + + DownloadPhase.UNPACKING -> { + stringResource(R.string.downloads_queue_phase_unpacking) + } + + DownloadPhase.COMPLETE -> { + stringResource(R.string.downloads_queue_phase_complete) + } + + DownloadPhase.CANCELLED -> { + stringResource(R.string.downloads_queue_phase_cancelled) + } + + DownloadPhase.FAILED -> { + stringResource( + R.string.downloads_queue_phase_failed, + if (statusMessage != null && + statusMessage != "null" + ) { + statusMessage!! + } else { + stringResource(R.string.common_ui_unknown_error) + }, + ) + } + + else -> { + stringResource(R.string.downloads_queue_phase_unknown) + } + } + val statusColor = + when (status) { + DownloadPhase.COMPLETE -> StatusOnline + DownloadPhase.FAILED, + DownloadPhase.CANCELLED, + -> DangerRed + DownloadPhase.PAUSED, + DownloadPhase.QUEUED, + -> StatusAway + DownloadPhase.DOWNLOADING, + DownloadPhase.PREPARING, + DownloadPhase.VERIFYING, + DownloadPhase.PATCHING, + DownloadPhase.APPLYING_DATA, + DownloadPhase.FINALIZING, + DownloadPhase.UNPACKING, + -> Accent + else -> TextSecondary + } + + Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) { + Text( + stringResource(R.string.downloads_queue_status_label), + style = MaterialTheme.typography.bodySmall, + color = TextSecondary, + maxLines = 1, + ) + Spacer(Modifier.width(4.dp)) + Text( + statusText, + style = MaterialTheme.typography.bodySmall, + color = statusColor, + modifier = Modifier.weight(1f), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + + AnimatedVisibility( + visible = status != DownloadPhase.COMPLETE || showCompletedProgressBar, + exit = fadeOut(tween(180)) + shrinkVertically(tween(180)), + ) { + Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(top = 8.dp)) { + DownloadChasingProgressBar( + progress = if (status == DownloadPhase.COMPLETE) 1f else animatedProgress, + status = status, + animationsActive = animationsActive, + modifier = Modifier.weight(1f).height(9.dp).padding(end = 10.dp), + ) + Text( + text = "$percentage%", + style = MaterialTheme.typography.labelMedium, + color = if (status == DownloadPhase.COMPLETE) StatusOnline else TextPrimary, + modifier = Modifier.width(40.dp), + ) + } + } + } + + Box(contentAlignment = Alignment.Center) { + IconButton( + onClick = { showDeleteDialog = true }, + enabled = status != DownloadPhase.COMPLETE && status != DownloadPhase.CANCELLED, + ) { + Icon( + Icons.Outlined.Close, + contentDescription = stringResource(R.string.downloads_queue_cancel_download), + tint = + if (status != DownloadPhase.COMPLETE && + status != DownloadPhase.CANCELLED + ) { + Color(0xFFFF6B6B) + } else { + TextSecondary + }, + ) + } + if (showDeleteDialog) { + DownloadCancelWarningMenu( + expanded = true, + onDismissRequest = { showDeleteDialog = false }, + onConfirm = { + showDeleteDialog = false + DownloadService.cancelDownload(id) + }, + ) + } + } + if (ControllerHelper.isControllerConnected()) { + Spacer(Modifier.width(8.dp)) + ControllerBadge(if (ControllerHelper.isPlayStationController()) "\u2715" else "A") + } + } + } +} + +// Game Manager Dialog +@Composable +internal fun UnifiedActivity.GameManagerDialog( + app: SteamApp, + onDismissRequest: () -> Unit, +) { + val context = LocalContext.current + var isLoading by remember { mutableStateOf(true) } + var selectedManifestSizes by remember { mutableStateOf(SteamService.ManifestSizes()) } + var baseInstallSize by remember(app.id) { mutableStateOf(0L) } + var dlcApps by remember { mutableStateOf>(emptyList()) } + var dlcSizes by remember { mutableStateOf>(emptyMap()) } + var installedDlcIds by remember(app.id) { mutableStateOf>(emptySet()) } + var installed by remember(app.id) { mutableStateOf(null) } + val selectedDlcIds = remember { mutableStateListOf() } + var customPath by remember { mutableStateOf(null) } + var showCustomPathWarning by remember { mutableStateOf(false) } + var isCheckingForUpdate by remember(app.id) { mutableStateOf(false) } + var isUpdateCheckCoolingDown by remember(app.id) { mutableStateOf(false) } + var showWorkshopDialog by remember(app.id) { mutableStateOf(false) } + var updateInfo by remember(app.id) { mutableStateOf(null) } + var updateStatusText by remember(app.id) { mutableStateOf(null) } + val downloadRecords by com.winlator.cmod.app.service.download.DownloadCoordinator.records.collectAsState( + initial = com.winlator.cmod.app.service.download.DownloadCoordinator.snapshotRecords(), + ) + val scope = rememberCoroutineScope() + + if (showCustomPathWarning) { + CustomPathWarningDialog( + onDismiss = { showCustomPathWarning = false }, + onProceed = { + showCustomPathWarning = false + DirectoryPickerDialog.show( + activity = this@GameManagerDialog, + initialPath = customPath ?: SteamService.defaultAppInstallPath, + title = getString(R.string.settings_content_install_directory), + extraRoots = driveRoots(includeInternal = true), + ) { path -> customPath = path } + }, + ) + } + + data class SteamInstallLoadData( + val dlcApps: List, + val dlcSizes: Map, + val installedDlcIds: Set, + val baseManifestSizes: SteamService.ManifestSizes, + val installed: Boolean, + ) + + LaunchedEffect(app.id, downloadRecords) { + val loadData = + withContext(Dispatchers.IO) { + val selectableDlcApps = SteamService.getSelectableDlcAppsOf(app.id) + val perDlcSizes = + selectableDlcApps.associate { dlc -> + dlc.id to SteamService.getDlcOnlyManifestSizes(app.id, dlc.id) + } + val installedDlcIds = + SteamService.getInstalledDlcDepotsOf(app.id) + .orEmpty() + .toSet() + SteamInstallLoadData( + dlcApps = selectableDlcApps, + dlcSizes = perDlcSizes, + installedDlcIds = installedDlcIds, + baseManifestSizes = SteamService.getInstallableSelectedManifestSizes(app.id), + installed = SteamService.isAppInstalled(app.id), + ) + } + dlcApps = loadData.dlcApps + dlcSizes = loadData.dlcSizes + installedDlcIds = loadData.installedDlcIds + selectedDlcIds.removeAll(loadData.installedDlcIds) + selectedManifestSizes = loadData.baseManifestSizes + baseInstallSize = loadData.baseManifestSizes.installSize + installed = loadData.installed + isLoading = false + } + + LaunchedEffect(app.id, selectedDlcIds.toList()) { + selectedManifestSizes = + withContext(Dispatchers.IO) { + SteamService.getInstallableSelectedManifestSizes(app.id, selectedDlcIds.toList()) + } + } + + val totalDownloadSize = selectedManifestSizes.downloadSize + val totalInstallSize = selectedManifestSizes.installSize + val defaultPathSet = + if (PrefManager.useSingleDownloadFolder) { + PrefManager.defaultDownloadFolder.isNotEmpty() + } else { + PrefManager.steamDownloadFolder + .isNotEmpty() + } + val effectivePath = customPath ?: SteamService.defaultAppInstallPath + val availableBytes = + try { + StorageUtils.getAvailableSpace(effectivePath) + } catch (e: Exception) { + 0L + } + // For an already-installed game the base content is on disk, so only require free + // space for the newly-selected DLC (already-installed DLC is excluded from selection). + val requiredBytes = + if (installed == true) (totalInstallSize - baseInstallSize).coerceAtLeast(0L) else totalInstallSize + val isInstallEnabled = requiredBytes == 0L || availableBytes >= requiredBytes + val installPathDisplay = customPath ?: SteamService.defaultAppInstallPath + + val dlcItems = + remember(dlcApps, dlcSizes, installedDlcIds) { + dlcApps.map { dlc -> + val sizes = dlcSizes[dlc.id] + val size = + sizes + ?.downloadSize + ?.takeIf { it > 0L } + ?: sizes?.installSize + ?: 0L + StoreDlcItem( + id = dlc.id, + name = dlc.name, + downloadSize = size, + isInstalled = dlc.id in installedDlcIds, + ) + } + } + val customPathLabel = + when { + customPath != null -> stringResource(R.string.common_ui_custom) + defaultPathSet -> stringResource(R.string.common_ui_already_set) + else -> stringResource(R.string.common_ui_custom) + } + val isReallyInstalled = installed == true + val steamDownloadRecord = + downloadRecords.firstOrNull { + it.store == com.winlator.cmod.app.db.download.DownloadRecord.STORE_STEAM && + it.storeGameId == app.id.toString() && + it.status in setOf( + com.winlator.cmod.app.db.download.DownloadRecord.STATUS_QUEUED, + com.winlator.cmod.app.db.download.DownloadRecord.STATUS_DOWNLOADING, + com.winlator.cmod.app.db.download.DownloadRecord.STATUS_PAUSED, + ) + } + val hasBlockingSteamDownload = + downloadRecords.any { + it.store == com.winlator.cmod.app.db.download.DownloadRecord.STORE_STEAM && + it.storeGameId == app.id.toString() && + it.status in setOf( + com.winlator.cmod.app.db.download.DownloadRecord.STATUS_QUEUED, + com.winlator.cmod.app.db.download.DownloadRecord.STATUS_DOWNLOADING, + com.winlator.cmod.app.db.download.DownloadRecord.STATUS_PAUSED, + com.winlator.cmod.app.db.download.DownloadRecord.STATUS_FAILED, + ) + } + val updateActionEnabled = steamDownloadRecord == null + val installActionEnabled = isInstallEnabled && steamDownloadRecord == null + val activeSteamDownloadText = stringResource(R.string.store_game_download_already_active) + val noUpdateAvailableText = stringResource(R.string.store_game_no_update_available) + val updateAvailableText = stringResource(R.string.store_game_update_available) + val updateFailedText = stringResource(R.string.store_game_update_check_failed) + + Dialog( + onDismissRequest = onDismissRequest, + properties = + DialogProperties( + usePlatformDefaultWidth = false, + decorFitsSystemWindows = false, + ), + ) { + Surface( + modifier = Modifier.fillMaxSize(), + shape = RectangleShape, + color = Color.Black, + ) { + StoreGameDetailScreen( + title = app.name, + subtitle = + listOfNotNull( + app.developer.takeIf { it.isNotBlank() }, + app.publisher.takeIf { + it.isNotBlank() && !it.equals(app.developer, ignoreCase = true) + }, + ).joinToString(" • "), + sourceLabel = "Steam", + heroImageUrl = StoreArtworkCache.imageModel(context, StoreArtworkCache.steamRef(app, "hero", app.getHeroUrl())), + isLoading = isLoading, + isInstalled = isReallyInstalled, + installPathDisplay = installPathDisplay, + downloadSize = totalDownloadSize, + installSize = totalInstallSize, + availableBytes = availableBytes, + isInstallEnabled = isInstallEnabled, + isDownloadActionEnabled = installActionEnabled, + customPathLabel = customPathLabel, + showCustomPath = true, + showCloudSync = false, + showUninstall = false, + showUpdateCheck = true, + isCheckingForUpdate = isCheckingForUpdate, + isUpdateAvailable = updateInfo?.hasUpdate == true, + updateDownloadSize = updateInfo?.downloadSize ?: 0L, + updateStatusText = updateStatusText, + isUpdateActionEnabled = updateActionEnabled, + isUpdateCheckCoolingDown = isUpdateCheckCoolingDown, + // Shown for any installed game; titles without UGC simply + // open to an empty Workshop window (handled gracefully). + showWorkshop = isReallyInstalled, + showVerifyFiles = isReallyInstalled, + areSteamActionsEnabled = !hasBlockingSteamDownload, + dlcs = dlcItems, + selectedDlcIds = selectedDlcIds.toSet(), + isDlcSelectionEnabled = steamDownloadRecord == null, + onBack = onDismissRequest, + onInstall = { + if (steamDownloadRecord != null) { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + activeSteamDownloadText, + android.widget.Toast.LENGTH_SHORT, + ) + return@StoreGameDetailScreen + } + context.runIfOnlineOrToast { + scope.launch(Dispatchers.IO) { + val installableDlcIds = dlcItems + .filter { !it.isInstalled && it.id in selectedDlcIds } + .map { it.id } + SteamService.downloadApp(app.id, installableDlcIds, false, customPath) + withContext(Dispatchers.Main) { onDismissRequest() } + } + } + }, + onCheckForUpdate = { startUpdateCheck(app.id, app.name) }, + onWorkshop = { showWorkshopDialog = true }, + onVerifyFiles = { + if (steamDownloadRecord != null) { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + activeSteamDownloadText, + android.widget.Toast.LENGTH_SHORT, + ) + return@StoreGameDetailScreen + } + context.runIfOnlineOrToast { + scope.launch { + val started = + withContext(Dispatchers.IO) { + SteamService.downloadAppForVerify(app.id) + } + if (started != null) { + // Hand off to the activity-root host so the + // pop-up + completion notice outlive this dialog. + showTaskProgressPopup( + started, + app.name, + getString(R.string.store_game_verify_complete), + getString(R.string.store_game_verify_failed_notice), + completeAsToast = true, + ) + } + } + } + }, + onDownloadUpdate = { + if (!updateActionEnabled || updateInfo?.hasUpdate != true) return@StoreGameDetailScreen + context.runIfOnlineOrToast { + scope.launch(Dispatchers.IO) { + try { + val latest = SteamService.checkForAppUpdate(app.id) + withContext(Dispatchers.Main) { + updateInfo = latest + updateStatusText = + when { + latest.hasUpdate -> updateAvailableText + latest.message != null -> updateFailedText + else -> null + } + } + if (!latest.hasUpdate) { + withContext(Dispatchers.Main) { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + noUpdateAvailableText, + android.widget.Toast.LENGTH_SHORT, + ) + } + return@launch + } + + SteamService.downloadAppForUpdate(app.id, latest.depotIds) + withContext(Dispatchers.Main) { onDismissRequest() } + } catch (e: Exception) { + Log.w("UnifiedActivity", "Steam update download failed to start for appId=${app.id}", e) + withContext(Dispatchers.Main) { + updateStatusText = updateFailedText + } + } + } + } + }, + onCustomPath = { + if (customPath == null && defaultPathSet) { + showCustomPathWarning = true + } else { + DirectoryPickerDialog.show( + activity = this@GameManagerDialog, + initialPath = customPath ?: SteamService.defaultAppInstallPath, + title = getString(R.string.settings_content_install_directory), + extraRoots = driveRoots(includeInternal = true), + ) { path -> customPath = path } + } + }, + onToggleDlc = { id -> + if (steamDownloadRecord != null) { + return@StoreGameDetailScreen + } + if (dlcItems.any { it.id == id && it.isInstalled }) { + return@StoreGameDetailScreen + } + if (selectedDlcIds.contains(id)) { + selectedDlcIds.remove(id) + } else { + selectedDlcIds.add(id) + } + }, + onToggleSelectAllDlcs = { + if (steamDownloadRecord != null) { + return@StoreGameDetailScreen + } + val selectableDlcItems = dlcItems.filterNot { it.isInstalled } + val all = selectableDlcItems.isNotEmpty() && selectableDlcItems.all { it.id in selectedDlcIds } + if (all) { + selectedDlcIds.removeAll(selectableDlcItems.map { it.id }.toSet()) + } else { + selectableDlcItems.forEach { if (it.id !in selectedDlcIds) selectedDlcIds.add(it.id) } + } + }, + ) + } + } + + if (showWorkshopDialog) { + WorkshopDialog( + appId = app.id, + gameTitle = app.name, + onDismissRequest = { showWorkshopDialog = false }, + ) + } +} + +@Composable +internal fun UnifiedActivity.WorkshopDialog( + appId: Int, + gameTitle: String, + onDismissRequest: () -> Unit, +) { + var loadState by remember(appId) { mutableStateOf(WorkshopLoadState.LOADING) } + var errorMessage by remember(appId) { mutableStateOf(null) } + var allItems by remember(appId) { mutableStateOf>(emptyList()) } + var query by remember(appId) { mutableStateOf("") } + // Published-file-ids with an install OR uninstall in flight. + val busyIds = remember(appId) { mutableStateListOf() } + var reloadKey by remember(appId) { mutableStateOf(0) } + val scope = rememberCoroutineScope() + + LaunchedEffect(appId, reloadKey) { + loadState = WorkshopLoadState.LOADING + errorMessage = null + // Drop any in-flight spinners — a reload re-fetches the list. + busyIds.clear() + try { + val items = + withContext(Dispatchers.IO) { + val json = SteamService.getSubscribedWorkshopItems(appId) + if (json == null) { + null + } else { + val installed = + com.winlator.cmod.feature.stores.steam.workshop.WorkshopModsGenerator + .installedItemIds(applicationContext, appId) + parseWorkshopItemsJson(json, installed) + } + } + if (items == null) { + errorMessage = + "Couldn't load your Workshop subscriptions. " + + "Make sure you're signed in to Steam and online." + loadState = WorkshopLoadState.ERROR + } else { + allItems = items + loadState = WorkshopLoadState.READY + } + } catch (e: Exception) { + Log.w("UnifiedActivity", "Workshop load failed for appId=$appId", e) + errorMessage = e.message + loadState = WorkshopLoadState.ERROR + } + } + + val filtered = + remember(allItems, query) { + val q = query.trim() + if (q.isBlank()) { + allItems + } else { + allItems.filter { + it.title.contains(q, ignoreCase = true) || + it.author.contains(q, ignoreCase = true) + } + } + } + + Dialog( + onDismissRequest = onDismissRequest, + properties = + DialogProperties( + usePlatformDefaultWidth = false, + decorFitsSystemWindows = false, + ), + ) { + StoreWorkshopScreen( + gameTitle = gameTitle, + loadState = loadState, + errorMessage = errorMessage, + items = filtered, + query = query, + // Snapshotted here inside the Dialog content lambda: a mutation of + // the SnapshotStateList invalidates this scope, so .toSet() re-runs. + busyIds = busyIds.toSet(), + onQueryChange = { query = it }, + onInstall = { id -> + val item = allItems.firstOrNull { it.publishedFileId == id } + if (item != null && id !in busyIds) { + if (item.manifestId == 0L) { + com.winlator.cmod.shared.ui.toast.WinToast.show( + this@WorkshopDialog, + "This Workshop item has no downloadable content", + android.widget.Toast.LENGTH_SHORT, + ) + } else { + busyIds.add(id) + scope.launch { + val ok = + SteamService.installWorkshopItem( + appId = appId, + publishedFileId = item.publishedFileId, + manifestId = item.manifestId, + title = item.title, + fileSizeBytes = item.fileSizeBytes, + timeUpdated = item.timeUpdated, + previewUrl = item.previewImageUrl ?: "", + ) + if (ok) { + allItems = + allItems.map { + if (it.publishedFileId == id) it.copy(isInstalled = true) else it + } + } else { + com.winlator.cmod.shared.ui.toast.WinToast.show( + this@WorkshopDialog, + "Workshop download failed — check your Steam connection", + android.widget.Toast.LENGTH_LONG, + ) + } + busyIds.remove(id) + } + } + } + }, + onUninstall = { id -> + if (id !in busyIds) { + busyIds.add(id) + scope.launch { + val ok = + withContext(Dispatchers.IO) { + com.winlator.cmod.feature.stores.steam.workshop.WorkshopModsGenerator + .uninstall(applicationContext, appId, id) + } + if (ok) { + allItems = + allItems.map { + if (it.publishedFileId == id) it.copy(isInstalled = false) else it + } + } else { + com.winlator.cmod.shared.ui.toast.WinToast.show( + this@WorkshopDialog, + "Couldn't uninstall this Workshop item", + android.widget.Toast.LENGTH_SHORT, + ) + } + busyIds.remove(id) + } + } + }, + onRetry = { reloadKey++ }, + onClose = onDismissRequest, + ) + } +} diff --git a/app/src/main/app/shell/UnifiedActivityDrawer.kt b/app/src/main/app/shell/UnifiedActivityDrawer.kt new file mode 100644 index 000000000..0fd85a29f --- /dev/null +++ b/app/src/main/app/shell/UnifiedActivityDrawer.kt @@ -0,0 +1,1381 @@ +package com.winlator.cmod.app.shell +import com.winlator.cmod.app.shell.UnifiedActivity.ControllerConnectionState + +import android.content.SharedPreferences +import androidx.preference.PreferenceManager +import android.app.Activity +import android.app.PendingIntent +import android.content.Intent +import android.content.res.Configuration +import android.hardware.input.InputManager +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.drawable.BitmapDrawable +import android.net.Uri +import android.os.Bundle +import android.provider.DocumentsContract +import android.util.Log +import androidx.activity.SystemBarStyle +import androidx.activity.compose.BackHandler +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AppCompatActivity +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.EnterTransition +import androidx.compose.animation.ExitTransition +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.FastOutSlowInEasing +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.spring +import androidx.compose.animation.core.tween +import androidx.compose.animation.expandVertically +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.shrinkVertically +import androidx.compose.animation.slideInHorizontally +import androidx.compose.animation.slideOutHorizontally +import androidx.compose.animation.togetherWith +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background +import androidx.compose.foundation.basicMarquee +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.focusable +import androidx.compose.foundation.gestures.awaitEachGesture +import androidx.compose.foundation.gestures.awaitFirstDown +import androidx.compose.foundation.gestures.detectHorizontalDragGestures +import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsPressedAsState +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items +import androidx.compose.foundation.lazy.grid.itemsIndexed +import androidx.compose.foundation.lazy.grid.rememberLazyGridState +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.outlined.ArrowBack +import androidx.compose.material.icons.automirrored.outlined.ExitToApp +import androidx.compose.material.icons.automirrored.outlined.OpenInNew +import androidx.compose.material.icons.outlined.* +import androidx.compose.material3.* +import androidx.compose.material3.pulltorefresh.PullToRefreshBox +import androidx.compose.runtime.* +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.snapshotFlow +import androidx.compose.runtime.snapshots.SnapshotStateMap +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.zIndex +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusProperties +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.focus.focusTarget +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.geometry.CornerRadius +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.TileMode +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.input.pointer.PointerEventPass +import androidx.compose.ui.input.pointer.PointerEventType +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties +import androidx.core.net.toUri +import androidx.lifecycle.lifecycleScope +import androidx.navigation.NavHostController +import androidx.navigation.NavType +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import androidx.navigation.navArgument +import coil.compose.AsyncImage +import coil.imageLoader +import coil.request.ImageRequest +import com.winlator.cmod.BuildConfig +import com.winlator.cmod.R +import com.winlator.cmod.app.PluviaApp +import com.winlator.cmod.app.db.PluviaDatabase +import com.winlator.cmod.app.service.DownloadService +import com.winlator.cmod.app.service.download.DownloadCoordinator +import com.winlator.cmod.app.update.UpdateChecker +import com.winlator.cmod.feature.settings.InputControlsFragment +import com.winlator.cmod.feature.settings.SettingsFocusZone +import com.winlator.cmod.feature.settings.SettingsHost +import com.winlator.cmod.feature.settings.SettingsNavBridge +import com.winlator.cmod.feature.settings.SettingsNavItem +import com.winlator.cmod.feature.setup.SetupWizardActivity +import com.winlator.cmod.feature.shortcuts.LibraryShortcutUtils +import com.winlator.cmod.feature.shortcuts.LibraryShortcutArtwork +import com.winlator.cmod.feature.artwork.SteamArtworkScraper +import com.winlator.cmod.runtime.container.Container +import com.winlator.cmod.feature.shortcuts.ShortcutBroadcastReceiver +import com.winlator.cmod.feature.shortcuts.ShortcutSettingsComposeDialog +import com.winlator.cmod.feature.shortcuts.ShortcutsFragment +import com.winlator.cmod.feature.stores.common.StoreArtworkCache +import com.winlator.cmod.feature.stores.epic.data.EpicCredentials +import com.winlator.cmod.feature.stores.epic.data.EpicGame +import com.winlator.cmod.feature.stores.epic.data.EpicGameToken +import com.winlator.cmod.feature.stores.epic.service.EpicAuthManager +import com.winlator.cmod.feature.stores.epic.service.EpicCloudSavesManager +import com.winlator.cmod.feature.stores.epic.service.EpicConstants +import com.winlator.cmod.feature.stores.epic.service.EpicDownloadManager +import com.winlator.cmod.feature.stores.epic.service.EpicGameLauncher +import com.winlator.cmod.feature.stores.epic.service.EpicManager +import com.winlator.cmod.feature.stores.epic.service.EpicService +import com.winlator.cmod.feature.stores.epic.service.EpicUpdateInfo +import com.winlator.cmod.feature.stores.epic.ui.auth.EpicOAuthActivity +import com.winlator.cmod.feature.stores.gog.data.GOGDlcInfo +import com.winlator.cmod.feature.stores.gog.data.GOGGame +import com.winlator.cmod.feature.stores.gog.data.LibraryItem +import com.winlator.cmod.feature.stores.gog.service.GOGAuthManager +import com.winlator.cmod.feature.stores.gog.service.GOGConstants +import com.winlator.cmod.feature.stores.gog.service.GOGManifestSizes +import com.winlator.cmod.feature.stores.gog.service.GOGService +import com.winlator.cmod.feature.stores.gog.service.GOGUpdateInfo +import com.winlator.cmod.feature.stores.gog.ui.auth.GOGOAuthActivity +import com.winlator.cmod.feature.stores.steam.SteamLoginActivity +import com.winlator.cmod.feature.stores.steam.data.DepotInfo +import com.winlator.cmod.feature.stores.steam.data.DownloadInfo +import com.winlator.cmod.feature.stores.steam.data.SteamApp +import com.winlator.cmod.feature.stores.steam.enums.DownloadPhase +import com.winlator.cmod.feature.stores.steam.events.AndroidEvent +import com.winlator.cmod.feature.stores.steam.events.EventDispatcher +import com.winlator.cmod.feature.stores.steam.service.SteamService +import com.winlator.cmod.feature.stores.steam.utils.PrefManager +import com.winlator.cmod.feature.stores.steam.utils.getAvatarURL +import com.winlator.cmod.feature.sync.CloudSyncHelper +import com.winlator.cmod.feature.sync.google.CloudSyncManager +import com.winlator.cmod.feature.sync.google.GameSaveBackupManager +import com.winlator.cmod.feature.sync.ui.CloudSavesContent +import com.winlator.cmod.runtime.container.ContainerManager +import com.winlator.cmod.runtime.container.Shortcut +import com.winlator.cmod.runtime.display.XServerDisplayActivity +import com.winlator.cmod.runtime.display.environment.ImageFs +import com.winlator.cmod.runtime.input.ControllerHelper +import com.winlator.cmod.runtime.wine.PeIconExtractor +import com.winlator.cmod.shared.android.ActivityResultHost +import com.winlator.cmod.shared.android.AppTerminationHelper +import com.winlator.cmod.shared.android.DirectoryPickerDialog +import com.winlator.cmod.shared.android.FixedFontScaleAppCompatActivity +import com.winlator.cmod.shared.android.RefreshRateUtils +import com.winlator.cmod.shared.io.StorageUtils +import com.winlator.cmod.shared.io.FileUtils +import com.winlator.cmod.shared.ui.CarouselView +import com.winlator.cmod.shared.ui.dialog.PopupDialog +import com.winlator.cmod.shared.ui.dialog.PopupTextAction +import androidx.compose.foundation.focusGroup +import com.winlator.cmod.shared.ui.focus.controllerFocusGlow +import com.winlator.cmod.shared.ui.focus.controllerMenuInput +import com.winlator.cmod.shared.ui.focus.controllerTextFieldEscape +import com.winlator.cmod.shared.ui.nav.DialogPaneNav +import com.winlator.cmod.shared.ui.nav.LocalPaneNav +import com.winlator.cmod.shared.ui.nav.PANE_DIR_ACTIVATE +import com.winlator.cmod.shared.ui.nav.PANE_DIR_DOWN +import com.winlator.cmod.shared.ui.nav.PANE_DIR_LEFT +import com.winlator.cmod.shared.ui.nav.PANE_DIR_RIGHT +import com.winlator.cmod.shared.ui.nav.PANE_DIR_SECONDARY +import com.winlator.cmod.shared.ui.nav.PANE_DIR_UP +import com.winlator.cmod.shared.ui.nav.PaneNavRegistry +import com.winlator.cmod.shared.ui.nav.paneNavItem +import kotlinx.coroutines.CoroutineScope +import com.winlator.cmod.shared.ui.FourByTwoGridView +import com.winlator.cmod.shared.ui.JoystickGridScroll +import com.winlator.cmod.shared.ui.JoystickListScroll +import com.winlator.cmod.shared.ui.ListView +import com.winlator.cmod.shared.ui.widget.chasingBorder +import com.winlator.cmod.shared.theme.WinNativeTheme +import dagger.hilt.android.AndroidEntryPoint +import dagger.Lazy +import com.winlator.cmod.feature.stores.steam.enums.EPersonaState +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import javax.inject.Inject +import kotlin.math.abs +import kotlin.math.roundToInt + +// Navigation drawer + add-custom-game dialog + empty/login states, split out of UnifiedActivity.kt (behavior-identical). + +@Composable +internal fun UnifiedActivity.EmptyStateMessage(message: String) { + Text(message, color = TextSecondary, modifier = Modifier.padding(16.dp)) +} + +@Composable +internal fun UnifiedActivity.LoginRequiredScreen( + storeName: String, + onLoginClick: () -> Unit, +) { + val message = + if (storeName == + "Library" + ) { + stringResource(R.string.library_games_sign_in_prompt) + } else { + stringResource(R.string.stores_accounts_sign_in_store_prompt, storeName) + } + val buttonText = + if (storeName == + "Library" + ) { + stringResource(R.string.stores_accounts_manage) + } else { + stringResource(R.string.stores_accounts_sign_into_store, storeName) + } + + Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.padding(horizontal = 48.dp), + ) { + Icon( + Icons.Outlined.Person, + contentDescription = null, + tint = Accent, + modifier = Modifier.size(48.dp), + ) + Spacer(Modifier.height(16.dp)) + Text( + message, + color = TextSecondary, + style = MaterialTheme.typography.bodyMedium, + textAlign = androidx.compose.ui.text.style.TextAlign.Center, + lineHeight = 20.sp, + ) + Spacer(Modifier.height(20.dp)) + val interactionSource = + remember { + androidx.compose.foundation.interaction + .MutableInteractionSource() + } + val isPressed by interactionSource.collectIsPressedAsState() + val btnScale by animateFloatAsState( + targetValue = if (isPressed) 0.95f else 1f, + animationSpec = tween(100), + label = "btnScale", + ) + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = + Modifier + .graphicsLayer { + scaleX = btnScale + scaleY = btnScale + }.clickable( + interactionSource = interactionSource, + indication = null, + onClick = onLoginClick, + ).border(1.dp, Accent.copy(alpha = 0.5f), RoundedCornerShape(20.dp)) + .padding(horizontal = 20.dp, vertical = 10.dp), + ) { + Text(buttonText, color = Accent, fontSize = 13.sp, fontWeight = FontWeight.Medium) + } + } + } +} + +// Drawer content: avatar card + filters +@Composable +internal fun UnifiedActivity.DrawerContent( + persona: com.winlator.cmod.feature.stores.steam.data.SteamFriend?, + isOpen: Boolean, + context: android.content.Context, + scope: kotlinx.coroutines.CoroutineScope, + storeVisible: SnapshotStateMap, + contentFilters: SnapshotStateMap, + libraryLayoutMode: LibraryLayoutMode, + immersiveMode: Boolean, + immersiveBlur: Boolean, + onLibraryLayoutSelected: (LibraryLayoutMode) -> Unit, + onStoreVisibleChanged: (String, Boolean) -> Unit, + onContentFiltersChanged: (String, Boolean) -> Unit, + onImmersiveModeChanged: (Boolean) -> Unit, + onImmersiveBlurChanged: (Boolean) -> Unit, + onExportAll: () -> Unit, + onExitApp: () -> Unit, +) { + val drawerBridge = (context as? UnifiedActivity)?.drawerNavBridge + val navRegistry = remember(drawerBridge) { PaneNavRegistry(initialSignal = drawerBridge?.navSignal ?: -1) } + navRegistry.controllerActive = drawerBridge?.controllerActive ?: false + LaunchedEffect(navRegistry, drawerBridge?.navSignal) { + navRegistry.processNav(drawerBridge?.navSignal ?: 0, drawerBridge?.navDir ?: 0) + } + LaunchedEffect(isOpen) { if (isOpen) navRegistry.reset() } + + ModalDrawerSheet( + drawerShape = RectangleShape, + drawerContainerColor = Color(0xFF12121B), + drawerContentColor = TextPrimary, + windowInsets = WindowInsets(0, 0, 0, 0), + modifier = Modifier.width(324.dp), + ) { + CompositionLocalProvider(LocalPaneNav provides navRegistry) { + Column( + Modifier + .fillMaxHeight() + .navigationBarsPadding() + .verticalScroll(rememberScrollState()) + .padding(20.dp), + ) { + + // ── Layouts ── + Text( + stringResource(R.string.library_games_layouts_header), + color = TextSecondary, + fontSize = 10.sp, + fontWeight = FontWeight.Bold, + letterSpacing = 1.4.sp, + modifier = Modifier.padding(bottom = 4.dp), + ) + Spacer(Modifier.height(8.dp)) + + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) { + DrawerFilterButton( + label = "4-Grid", + checked = libraryLayoutMode == LibraryLayoutMode.GRID_4, + modifier = Modifier.weight(1f), + ) { if (it) onLibraryLayoutSelected(LibraryLayoutMode.GRID_4) } + DrawerFilterButton( + label = stringResource(R.string.library_games_layout_carousel), + checked = libraryLayoutMode == LibraryLayoutMode.CAROUSEL, + modifier = Modifier.weight(1f), + fontSize = 11.sp, + ) { if (it) onLibraryLayoutSelected(LibraryLayoutMode.CAROUSEL) } + DrawerFilterButton( + label = stringResource(R.string.library_games_layout_list), + checked = libraryLayoutMode == LibraryLayoutMode.LIST, + modifier = Modifier.weight(1f), + ) { if (it) onLibraryLayoutSelected(LibraryLayoutMode.LIST) } + } + + Spacer(Modifier.height(16.dp)) + + // ── View Options ── + Text( + stringResource(R.string.library_games_view_options_header), + color = TextSecondary, + fontSize = 10.sp, + fontWeight = FontWeight.Bold, + letterSpacing = 1.4.sp, + modifier = Modifier.padding(bottom = 4.dp), + ) + Spacer(Modifier.height(8.dp)) + + DrawerSwitchCard( + label = stringResource(R.string.library_games_immersive_mode), + description = stringResource(R.string.library_games_immersive_mode_description), + checked = immersiveMode, + onCheckedChange = onImmersiveModeChanged, + ) + + AnimatedVisibility(visible = immersiveMode) { + Column { + Spacer(Modifier.height(8.dp)) + DrawerSwitchCard( + label = stringResource(R.string.library_games_immersive_blur), + description = stringResource(R.string.library_games_immersive_blur_description), + checked = immersiveBlur, + onCheckedChange = onImmersiveBlurChanged, + ) + } + } + + Spacer(Modifier.height(12.dp)) + DrawerActionCard( + icon = Icons.Outlined.IosShare, + label = stringResource(R.string.shortcuts_export_to_frontend), + onClick = onExportAll, + ) + + Spacer(Modifier.height(16.dp)) + + // ── Stores ── + Text( + stringResource(R.string.stores_accounts_stores_header), + color = TextSecondary, + fontSize = 10.sp, + fontWeight = FontWeight.Bold, + letterSpacing = 1.4.sp, + modifier = Modifier.padding(bottom = 4.dp), + ) + Spacer(Modifier.height(8.dp)) + + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) { + DrawerFilterButton("Steam", storeVisible["steam"] == true, Modifier.weight(1f)) { onStoreVisibleChanged("steam", it) } + DrawerFilterButton("Epic", storeVisible["epic"] == true, Modifier.weight(1f)) { onStoreVisibleChanged("epic", it) } + } + Spacer(Modifier.height(8.dp)) + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) { + DrawerFilterButton("GOG", storeVisible["gog"] == true, Modifier.weight(1f)) { onStoreVisibleChanged("gog", it) } + Spacer(Modifier.weight(1f)) + } + + Spacer(Modifier.height(16.dp)) + + // ── Content Types ── + Text( + stringResource(R.string.settings_content_types_header), + color = TextSecondary, + fontSize = 10.sp, + fontWeight = FontWeight.Bold, + letterSpacing = 1.4.sp, + modifier = Modifier.padding(bottom = 4.dp), + ) + Spacer(Modifier.height(8.dp)) + + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) { + DrawerFilterButton("Games", contentFilters["games"] == true, Modifier.weight(1f)) { onContentFiltersChanged("games", it) } + DrawerFilterButton("DLC", contentFilters["dlc"] == true, Modifier.weight(1f)) { onContentFiltersChanged("dlc", it) } + } + Spacer(Modifier.height(8.dp)) + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) { + DrawerFilterButton("Applications", contentFilters["applications"] == true, Modifier.weight(1f)) { onContentFiltersChanged("applications", it) } + DrawerFilterButton("Tools", contentFilters["tools"] == true, Modifier.weight(1f)) { onContentFiltersChanged("tools", it) } + } + + Spacer(Modifier.height(20.dp)) + HorizontalDivider(color = TextSecondary.copy(alpha = 0.15f)) + Spacer(Modifier.height(16.dp)) + + DrawerExitAppCard(onClick = onExitApp) + } + } + } +} + +@Composable +internal fun UnifiedActivity.DrawerExitAppCard(onClick: () -> Unit) { + val interactionSource = remember { MutableInteractionSource() } + val isPressed by interactionSource.collectIsPressedAsState() + val scale by animateFloatAsState( + targetValue = if (isPressed) 0.97f else 1f, + animationSpec = tween(100), + label = "exitAppCardScale", + ) + + Row( + modifier = + Modifier + .fillMaxWidth() + .graphicsLayer { + scaleX = scale + scaleY = scale + } + .clip(RoundedCornerShape(12.dp)) + .background(DangerRed.copy(alpha = 0.16f)) + .border(1.dp, DangerRed.copy(alpha = 0.5f), RoundedCornerShape(12.dp)) + .paneNavItem(cornerRadius = 12.dp, onActivate = onClick) + .clickable( + interactionSource = interactionSource, + indication = null, + onClick = onClick, + ) + .padding(horizontal = 14.dp, vertical = 13.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Box( + modifier = + Modifier + .size(34.dp) + .clip(RoundedCornerShape(8.dp)) + .background(DangerRed.copy(alpha = 0.22f)), + contentAlignment = Alignment.Center, + ) { + Icon( + Icons.AutoMirrored.Outlined.ExitToApp, + contentDescription = null, + tint = Color(0xFFFFB4B4), + modifier = Modifier.size(20.dp), + ) + } + Spacer(Modifier.width(12.dp)) + Text( + text = stringResource(R.string.common_ui_exit_app), + color = Color(0xFFFFD6D6), + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.Bold, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } +} + +@Composable +internal fun UnifiedActivity.DrawerActionCard( + icon: androidx.compose.ui.graphics.vector.ImageVector, + label: String, + onClick: () -> Unit, +) { + val interactionSource = remember { MutableInteractionSource() } + val isPressed by interactionSource.collectIsPressedAsState() + val scale by animateFloatAsState( + targetValue = if (isPressed) 0.97f else 1f, + animationSpec = tween(100), + label = "drawerActionCardScale", + ) + + Row( + modifier = + Modifier + .fillMaxWidth() + .graphicsLayer { + scaleX = scale + scaleY = scale + } + .clip(RoundedCornerShape(12.dp)) + .background(Accent.copy(alpha = 0.14f)) + .border(1.dp, Accent.copy(alpha = 0.45f), RoundedCornerShape(12.dp)) + .paneNavItem(cornerRadius = 12.dp, onActivate = onClick) + .clickable( + interactionSource = interactionSource, + indication = null, + onClick = onClick, + ) + .padding(horizontal = 14.dp, vertical = 13.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Box( + modifier = + Modifier + .size(34.dp) + .clip(RoundedCornerShape(8.dp)) + .background(Accent.copy(alpha = 0.22f)), + contentAlignment = Alignment.Center, + ) { + Icon( + icon, + contentDescription = null, + tint = Accent, + modifier = Modifier.size(20.dp), + ) + } + Spacer(Modifier.width(12.dp)) + Text( + text = label, + color = TextPrimary, + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.Bold, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } +} + +@Composable +internal fun UnifiedActivity.DrawerFilterButton( + label: String, + checked: Boolean, + modifier: Modifier = Modifier, + fontSize: TextUnit = TextUnit.Unspecified, + onToggle: (Boolean) -> Unit, +) { + val interactionSource = remember { MutableInteractionSource() } + val isPressed by interactionSource.collectIsPressedAsState() + + val bgColor by animateColorAsState( + targetValue = if (checked) Accent.copy(alpha = 0.2f) else CardDark, + animationSpec = tween(200), + label = "filterBg", + ) + val borderColor by animateColorAsState( + targetValue = if (checked) Accent else CardBorder, + animationSpec = tween(200), + label = "filterBorder", + ) + val textColor by animateColorAsState( + targetValue = if (checked) Accent else TextSecondary, + animationSpec = tween(200), + label = "filterText", + ) + val scale by animateFloatAsState( + targetValue = if (isPressed) 0.92f else 1f, + animationSpec = spring(dampingRatio = Spring.DampingRatioMediumBouncy, stiffness = Spring.StiffnessHigh), + label = "filterScale", + ) + + Box( + modifier = + modifier + .graphicsLayer { + scaleX = scale + scaleY = scale + }.clip(RoundedCornerShape(8.dp)) + .background(bgColor) + .border(1.dp, borderColor, RoundedCornerShape(8.dp)) + .paneNavItem(cornerRadius = 8.dp, onActivate = { onToggle(!checked) }) + .clickable( + interactionSource = interactionSource, + indication = null, + ) { onToggle(!checked) } + .padding(vertical = 10.dp, horizontal = 10.dp), + contentAlignment = Alignment.Center, + ) { + Text( + text = label, + style = MaterialTheme.typography.labelMedium, + fontSize = fontSize, + color = textColor, + fontWeight = FontWeight.Bold, + maxLines = 1, + ) + } +} + +@Composable +internal fun UnifiedActivity.DrawerSwitchCard( + label: String, + description: String?, + checked: Boolean, + onCheckedChange: (Boolean) -> Unit, + modifier: Modifier = Modifier, +) { + val interactionSource = remember { MutableInteractionSource() } + val isPressed by interactionSource.collectIsPressedAsState() + + val bgColor by animateColorAsState( + targetValue = if (checked) Accent.copy(alpha = 0.18f) else CardDark, + animationSpec = tween(200), + label = "switchCardBg", + ) + val borderColor by animateColorAsState( + targetValue = if (checked) Accent else CardBorder, + animationSpec = tween(200), + label = "switchCardBorder", + ) + val labelColor by animateColorAsState( + targetValue = if (checked) Accent else TextPrimary, + animationSpec = tween(200), + label = "switchCardLabel", + ) + val scale by animateFloatAsState( + targetValue = if (isPressed) 0.97f else 1f, + animationSpec = tween(120), + label = "switchCardScale", + ) + + Row( + modifier = + modifier + .fillMaxWidth() + .graphicsLayer { + scaleX = scale + scaleY = scale + }.clip(RoundedCornerShape(10.dp)) + .background(bgColor) + .border(1.dp, borderColor, RoundedCornerShape(10.dp)) + .paneNavItem(cornerRadius = 10.dp, onActivate = { onCheckedChange(!checked) }) + .clickable( + interactionSource = interactionSource, + indication = null, + ) { onCheckedChange(!checked) } + .padding(horizontal = 12.dp, vertical = 10.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Column(Modifier.weight(1f)) { + Text( + text = label, + style = MaterialTheme.typography.bodyMedium, + color = labelColor, + fontWeight = FontWeight.SemiBold, + maxLines = 1, + ) + if (!description.isNullOrBlank()) { + Spacer(Modifier.height(2.dp)) + Text( + text = description, + style = MaterialTheme.typography.labelSmall, + color = TextSecondary, + maxLines = 2, + ) + } + } + Spacer(Modifier.width(10.dp)) + Switch( + checked = checked, + onCheckedChange = onCheckedChange, + modifier = Modifier.focusProperties { canFocus = false }, + colors = + SwitchDefaults.colors( + checkedThumbColor = Color.White, + checkedTrackColor = Accent, + checkedBorderColor = Accent, + uncheckedThumbColor = TextSecondary, + uncheckedTrackColor = CardDark, + uncheckedBorderColor = CardBorder, + ), + ) + } +} + +@Composable +internal fun UnifiedActivity.AddCustomGameDialog(onDismiss: () -> Unit) { + val context = LocalContext.current + val scope = rememberCoroutineScope() + var selectedExePath by remember { mutableStateOf(null) } + var gameName by remember { mutableStateOf("") } + var gameFolder by remember { mutableStateOf(null) } + var retroSystem by remember { mutableStateOf(null) } + var isAdding by remember { mutableStateOf(false) } + var nameEditing by remember { mutableStateOf(false) } + val nameFocus = remember { FocusRequester() } + val nameKeyboard = LocalSoftwareKeyboardController.current + LaunchedEffect(nameEditing) { + if (nameEditing) { + runCatching { nameFocus.requestFocus() } + nameKeyboard?.show() + } + } + val registry = remember { PaneNavRegistry() } + val addEnabled = + selectedExePath != null && gameName.isNotBlank() && !isAdding && + (retroSystem != null || gameFolder != null) + val doAdd: () -> Unit = { + isAdding = true + val chosenRetro = retroSystem + scope.launch(Dispatchers.IO) { + val added = + if (chosenRetro != null) { + com.winlator.cmod.feature.retro.RetroShortcuts + .create(context, gameName.trim(), selectedExePath!!, chosenRetro) + } else { + addCustomGame(context, gameName.trim(), selectedExePath!!, gameFolder!!) + true + } + withContext(Dispatchers.Main) { + isAdding = false + if (added) { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + "$gameName added!", + android.widget.Toast.LENGTH_SHORT, + ) + onDismiss() + } else { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + "Could not add game", + android.widget.Toast.LENGTH_SHORT, + ) + } + } + } + } + + fun selectExecutable(path: String) { + val detectedRetro = com.winlator.cmod.feature.retro.RetroSystems.detectForFile(path) + val file = java.io.File(path) + val launchable = file.extension.lowercase() in DirectoryPickerDialog.ExecutableExtensions + if (!file.isFile || (!launchable && detectedRetro == null)) { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + R.string.common_ui_select_valid_exe_file, + android.widget.Toast.LENGTH_SHORT, + ) + return + } + + selectedExePath = path + retroSystem = detectedRetro + gameFolder = + if (detectedRetro != null) { + java.io.File(path).parent + } else { + LibraryShortcutUtils.detectCustomGameFolder(path) + } + // Auto-generate a game name from the EXE name (without extension) + if (gameName.isBlank()) { + gameName = + java.io + .File(path) + .nameWithoutExtension + .replace("_", " ") + .replace("-", " ") + } + } + + val defaultDensity = LocalDensity.current + Dialog( + onDismissRequest = onDismiss, + properties = DialogProperties(usePlatformDefaultWidth = false), + ) { + CompositionLocalProvider( + LocalDensity provides Density(defaultDensity.density, fontScale = 1f), + androidx.compose.material3.LocalMinimumInteractiveComponentSize provides androidx.compose.ui.unit.Dp.Unspecified, + LocalPaneNav provides registry, + ) { + DialogPaneNav(registry, onDismiss = onDismiss, onStart = { if (addEnabled) doAdd() }) + Surface( + modifier = + Modifier + .widthIn(max = 360.dp) + .fillMaxWidth(0.9f), + shape = RoundedCornerShape(20.dp), + color = Color(0xFF141B24), + ) { + Column(Modifier.padding(horizontal = 16.dp, vertical = 14.dp)) { + // Title + Text( + stringResource(R.string.library_games_add_custom_game), + color = TextPrimary, + fontWeight = FontWeight.SemiBold, + fontSize = 15.sp, + ) + + Spacer(Modifier.height(10.dp)) + + // Scrollable content area + Column( + modifier = + Modifier + .weight(1f, fill = false) + .verticalScroll(rememberScrollState()), + ) { + // Pick EXE button + Row( + modifier = + Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(12.dp)) + .background(Color.White.copy(alpha = 0.05f)) + .paneNavItem( + cornerRadius = 12.dp, + tapToSelect = true, + isEntry = true, + onActivate = { + DirectoryPickerDialog.showFile( + activity = this@AddCustomGameDialog, + initialPath = + selectedExePath ?: gameFolder + ?: android.os.Environment + .getExternalStoragePublicDirectory( + android.os.Environment.DIRECTORY_DOWNLOADS, + ).absolutePath, + title = getString(R.string.common_ui_select_exe), + allowedExtensions = DirectoryPickerDialog.ExecutableExtensions + + com.winlator.cmod.feature.retro.RetroSystems.allExtensions, + dimAmount = 0.5f, + preserveBackdropBlur = true, + extraRoots = driveRoots(includeInternal = true), + onSelected = ::selectExecutable, + ) + }, + ).padding(horizontal = 12.dp, vertical = 10.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon(Icons.Outlined.FolderOpen, contentDescription = null, tint = Accent, modifier = Modifier.size(16.dp)) + Spacer(Modifier.width(8.dp)) + Text( + selectedExePath ?: "Select Executable or Console ROM", + color = if (selectedExePath == null) TextSecondary else TextPrimary, + maxLines = if (selectedExePath == null) 1 else Int.MAX_VALUE, + overflow = if (selectedExePath == null) TextOverflow.Ellipsis else TextOverflow.Visible, + fontSize = if (selectedExePath == null) 12.sp else 10.sp, + modifier = Modifier.weight(1f), + ) + } + + if (selectedExePath != null) { + + Spacer(Modifier.height(8.dp)) + + // Game name text field — compact + OutlinedTextField( + value = gameName, + onValueChange = { gameName = it }, + label = { Text(stringResource(R.string.library_games_game_name), fontSize = 11.sp) }, + singleLine = true, + modifier = Modifier + .fillMaxWidth() + .paneNavItem(cornerRadius = 10.dp, onActivate = { nameEditing = true }) + .pointerInput(Unit) { + awaitEachGesture { + awaitFirstDown(requireUnconsumed = false) + nameEditing = true + } + } + .focusRequester(nameFocus) + .focusProperties { canFocus = nameEditing } + .onFocusChanged { if (!it.isFocused) nameEditing = false } + .controllerTextFieldEscape(), + textStyle = MaterialTheme.typography.bodySmall.copy(color = TextPrimary), + colors = + OutlinedTextFieldDefaults.colors( + focusedBorderColor = Accent, + unfocusedBorderColor = TextSecondary.copy(alpha = 0.3f), + focusedTextColor = TextPrimary, + unfocusedTextColor = TextPrimary, + cursorColor = Accent, + focusedLabelColor = Accent, + unfocusedLabelColor = TextSecondary, + ), + shape = RoundedCornerShape(10.dp), + ) + + Spacer(Modifier.height(8.dp)) + + if (retroSystem != null) { + val activeRetroSystem = retroSystem + var consoleMenuOpen by remember { mutableStateOf(false) } + Box { + Row( + modifier = + Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(10.dp)) + .background(Color.White.copy(alpha = 0.05f)) + .paneNavItem( + cornerRadius = 10.dp, + tapToSelect = true, + onActivate = { consoleMenuOpen = true }, + ).clickable { consoleMenuOpen = true } + .padding(horizontal = 10.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + Icons.Outlined.SportsEsports, + contentDescription = null, + tint = StatusOnline.copy(alpha = 0.7f), + modifier = Modifier.size(14.dp), + ) + Spacer(Modifier.width(6.dp)) + Column(Modifier.weight(1f)) { + Text("Console", color = TextSecondary, fontSize = 9.sp) + Text( + activeRetroSystem?.displayName ?: "", + color = TextPrimary, + fontSize = 10.sp, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + Icon( + Icons.Outlined.Edit, + contentDescription = stringResource(R.string.common_ui_change), + tint = Accent, + modifier = Modifier.size(14.dp), + ) + } + DropdownMenu( + expanded = consoleMenuOpen, + onDismissRequest = { consoleMenuOpen = false }, + containerColor = Color(0xFF1C232E), + ) { + com.winlator.cmod.feature.retro.RetroSystems.ALL.forEach { candidate -> + DropdownMenuItem( + text = { + Text( + candidate.displayName, + color = + if (candidate.id == activeRetroSystem?.id) Accent else TextPrimary, + fontSize = 12.sp, + ) + }, + onClick = { + retroSystem = candidate + consoleMenuOpen = false + }, + ) + } + } + } + } else { + // Game folder — single compact row + Row( + modifier = + Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(10.dp)) + .background(Color.White.copy(alpha = 0.05f)) + .padding(horizontal = 10.dp, vertical = 6.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + Icons.Outlined.Folder, + contentDescription = null, + tint = StatusOnline.copy(alpha = 0.7f), + modifier = Modifier.size(14.dp), + ) + Spacer(Modifier.width(6.dp)) + Column(Modifier.weight(1f)) { + Text( + stringResource(R.string.library_games_game_folder_mapped_drive), + color = TextSecondary, + fontSize = 9.sp, + ) + Text( + gameFolder ?: stringResource(R.string.common_ui_auto_detected), + color = if (gameFolder != null) TextPrimary else TextSecondary, + fontSize = 10.sp, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + val openFolderPicker = { + if (ensureAllFilesAccessForImports(context)) { + DirectoryPickerDialog.show( + activity = this@AddCustomGameDialog, + initialPath = gameFolder, + title = getString(R.string.common_ui_select_folder), + dimAmount = 0.5f, + preserveBackdropBlur = true, + extraRoots = driveRoots(includeInternal = true), + ) { path -> gameFolder = path } + } + } + IconButton( + onClick = openFolderPicker, + modifier = + Modifier.size(28.dp).paneNavItem( + cornerRadius = 8.dp, + onActivate = openFolderPicker, + ), + ) { + Icon( + Icons.Outlined.Edit, + contentDescription = stringResource(R.string.common_ui_change), + tint = Accent, + modifier = Modifier.size(14.dp), + ) + } + } + } + } + } + + Spacer(Modifier.height(12.dp)) + + // Action buttons + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.End, + ) { + OutlinedButton( + onClick = onDismiss, + shape = RoundedCornerShape(10.dp), + border = androidx.compose.foundation.BorderStroke(1.dp, TextSecondary.copy(alpha = 0.3f)), + colors = ButtonDefaults.outlinedButtonColors(contentColor = TextSecondary), + contentPadding = PaddingValues(horizontal = 14.dp, vertical = 0.dp), + modifier = + Modifier.height(34.dp).widthIn(min = 72.dp) + .paneNavItem(cornerRadius = 10.dp, onActivate = onDismiss), + ) { + Text(stringResource(R.string.common_ui_cancel), fontSize = 12.sp) + } + Spacer(Modifier.width(8.dp)) + OutlinedButton( + onClick = doAdd, + enabled = addEnabled, + shape = RoundedCornerShape(10.dp), + border = + androidx.compose.foundation.BorderStroke( + 1.dp, + if (addEnabled) Accent.copy(alpha = 0.5f) else TextSecondary.copy(alpha = 0.2f), + ), + colors = ButtonDefaults.outlinedButtonColors(contentColor = Accent), + contentPadding = PaddingValues(horizontal = 14.dp, vertical = 0.dp), + modifier = + Modifier.height(34.dp).widthIn(min = 72.dp) + .paneNavItem(cornerRadius = 10.dp, onActivate = { if (addEnabled) doAdd() }), + ) { + if (isAdding) { + CircularProgressIndicator(color = Accent, modifier = Modifier.size(12.dp), strokeWidth = 2.dp) + } else { + Text(stringResource(R.string.common_ui_add), fontWeight = FontWeight.Medium, fontSize = 12.sp) + } + } + } + } + } + } + } +} + +internal fun UnifiedActivity.ensureAllFilesAccessForImports(context: android.content.Context): Boolean { + if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.R || android.os.Environment.isExternalStorageManager()) { + return true + } + + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + "Grant All files access to browse Downloads directly.", + android.widget.Toast.LENGTH_LONG, + ) + + val intent = + android.content.Intent(android.provider.Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION).apply { + data = android.net.Uri.parse("package:$packageName") + } + startActivity(intent) + return false +} + +internal fun UnifiedActivity.driveRoots(includeInternal: Boolean): List { + val imagefsRoot = + com.winlator.cmod.runtime.display.environment.ImageFs.find(this).getRootDir() + val roots = + mutableListOf( + DirectoryPickerDialog.ManagedRoot("C:", java.io.File(imagefsRoot, "home").absolutePath), + DirectoryPickerDialog.ManagedRoot("Z:", imagefsRoot.absolutePath), + DirectoryPickerDialog.ManagedRoot( + "D:", + android.os.Environment + .getExternalStoragePublicDirectory(android.os.Environment.DIRECTORY_DOWNLOADS) + .absolutePath, + ), + ) + if (includeInternal) { + roots += + DirectoryPickerDialog.ManagedRoot( + "Internal", + android.os.Environment.getExternalStorageDirectory().absolutePath, + ) + } + return roots +} + +internal suspend fun scrapeCustomGameArtwork( + context: android.content.Context, + gameName: String, + shortcutUuid: String, + container: Container, + shortcutFile: java.io.File +) { + withContext(Dispatchers.Main) { + com.winlator.cmod.shared.ui.toast.WinToast.show(context, R.string.library_games_scraping_artwork, android.widget.Toast.LENGTH_LONG) + } + val shortcut = Shortcut(container, shortcutFile) + val artworkInfo = SteamArtworkScraper(context).getGameArtwork(gameName) + val dir = java.io.File(context.filesDir, "library_view_artwork") + if (!dir.exists()) dir.mkdirs() + var saved = false + artworkInfo.forEach { (slotSuffix, file) -> + val librarySlot = + LibraryShortcutArtwork.LibraryArtworkSlot.entries.find { it.fileSuffix == slotSuffix } + val bitmap = + if (librarySlot != null && file.isFile) { + com.winlator.cmod.shared.android.ImageUtils.getBitmapFromUri(context, file.toUri(), 1024) + } else { + null + } + if (librarySlot != null && bitmap != null) { + val outputFile = java.io.File(dir, "${shortcutUuid}_${librarySlot.fileSuffix}.png") + if (com.winlator.cmod.shared.io.FileUtils.saveBitmapToFile(bitmap, outputFile)) { + shortcut.putExtra(librarySlot.extraKey, outputFile.absolutePath) + saved = true + } + } + file.delete() + } + if (saved) shortcut.saveData() + withContext(Dispatchers.Main) { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + if (saved) R.string.common_ui_done else R.string.common_ui_failed, + android.widget.Toast.LENGTH_LONG, + ) + if (saved) { + com.winlator.cmod.app.PluviaApp.events.emit(com.winlator.cmod.feature.stores.steam.events.AndroidEvent.LibraryArtworkChanged) + } + } +} + +internal fun UnifiedActivity.addCustomGame( + context: android.content.Context, + name: String, + exePath: String, + gameFolderPath: String, +) { + val containerManager = ContainerManager(context) + var container = SetupWizardActivity.getPreferredGameContainer(context, containerManager) + if (container == null) { + SetupWizardActivity.promptToInstallWineOrCreateContainer(context) + return + } + + val exeFile = java.io.File(exePath) + normalizeContainerDrives(container) + val execCmd = buildWineExecCommand(container, gameFolderPath, exeFile) + + val desktopDir = container.getDesktopDir() + if (!desktopDir.exists()) desktopDir.mkdirs() + val safeName = name.replace("/", "_").replace("\\", "_") + val shortcutFile = java.io.File(desktopDir, "$safeName.desktop") + val shortcutUuid = java.util.UUID.randomUUID().toString() + val iconOutFile = LibraryShortcutArtwork.buildManagedCustomGameArtworkFile(context, shortcutUuid) + val preferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) + val extractedArtworkPath = + try { + if (PeIconExtractor.extractAndSave(java.io.File(exePath), iconOutFile)) { + iconOutFile.absolutePath + } else { + null + } + } catch (_: Exception) { + null + } + val content = StringBuilder() + content.append("[Desktop Entry]\n") + content.append("Type=Application\n") + content.append("Name=$name\n") + content.append("Exec=$execCmd\n") + content.append("Icon=custom_game\n") + content.append("\n[Extra Data]\n") + content.append("game_source=CUSTOM\n") + content.append("custom_name=$name\n") + content.append("custom_exe=$exePath\n") + content.append("custom_game_folder=$gameFolderPath\n") + content.append("uuid=$shortcutUuid\n") + extractedArtworkPath?.let { content.append("customCoverArtPath=$it\n") } + content.append("container_id=${container.id}\n") + content.append("use_container_defaults=1\n") + com.winlator.cmod.shared.io.FileUtils + .writeString(shortcutFile, content.toString()) + container.saveData() + if (preferences.getBoolean("enable_auto_scraping", false)) { + CoroutineScope(Dispatchers.IO).launch { + scrapeCustomGameArtwork(context, name, shortcutUuid, container, shortcutFile) + } + } +} + +@Composable +internal fun UnifiedActivity.CustomPathWarningDialog( + onDismiss: () -> Unit, + onProceed: () -> Unit, +) { + Dialog(onDismissRequest = onDismiss) { + Surface( + shape = RoundedCornerShape(16.dp), + color = CardDark, + modifier = Modifier.padding(16.dp), + ) { + Column(modifier = Modifier.padding(24.dp)) { + Text( + text = stringResource(R.string.stores_accounts_custom_download_path), + style = MaterialTheme.typography.titleLarge, + color = TextPrimary, + fontWeight = FontWeight.Bold, + ) + Spacer(Modifier.height(16.dp)) + Text( + text = stringResource(R.string.stores_accounts_custom_download_path_description), + style = MaterialTheme.typography.bodyMedium, + color = TextSecondary, + ) + Spacer(Modifier.height(24.dp)) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center, + ) { + TextButton(onClick = onDismiss) { + Text(stringResource(R.string.common_ui_close), color = TextSecondary) + } + Spacer(Modifier.width(8.dp)) + Button( + onClick = onProceed, + colors = ButtonDefaults.buttonColors(containerColor = Accent), + shape = RoundedCornerShape(8.dp), + ) { + Text(stringResource(R.string.common_ui_proceed)) + } + } + } + } + } +} + +@Composable +internal fun UnifiedActivity.rememberControllerConnectionState(): ControllerConnectionState { + val context = LocalContext.current + val inputManager = remember(context) { context.getSystemService(InputManager::class.java) } + var controllerState by remember { mutableStateOf(ControllerConnectionState()) } + + DisposableEffect(inputManager) { + fun refreshState() { + controllerState = + ControllerConnectionState( + isConnected = ControllerHelper.isControllerConnected(), + isPlayStation = ControllerHelper.isPlayStationController(), + ) + } + + val listener = + object : InputManager.InputDeviceListener { + override fun onInputDeviceAdded(deviceId: Int) = refreshState() + + override fun onInputDeviceRemoved(deviceId: Int) = refreshState() + + override fun onInputDeviceChanged(deviceId: Int) = refreshState() + } + + refreshState() + inputManager?.registerInputDeviceListener(listener, null) + onDispose { + inputManager?.unregisterInputDeviceListener(listener) + } + } + + return controllerState +} diff --git a/app/src/main/app/shell/UnifiedActivityGameDialogs.kt b/app/src/main/app/shell/UnifiedActivityGameDialogs.kt new file mode 100644 index 000000000..4f8606c4e --- /dev/null +++ b/app/src/main/app/shell/UnifiedActivityGameDialogs.kt @@ -0,0 +1,3168 @@ +package com.winlator.cmod.app.shell +import com.winlator.cmod.app.shell.UnifiedActivity.GameSettingsActionItem +import com.winlator.cmod.app.shell.UnifiedActivity.GameSettingsScreen +import com.winlator.cmod.app.shell.UnifiedActivity.HeroBootChoice +import com.winlator.cmod.app.shell.UnifiedActivity.HeroLaunchPopup +import com.winlator.cmod.app.shell.UnifiedActivity.HomeShortcutUiState +import com.winlator.cmod.app.shell.UnifiedActivity.LibraryDetailPopup +import com.winlator.cmod.app.shell.UnifiedActivity.LibraryDetailScreen + +import android.app.Activity +import android.app.PendingIntent +import android.content.Intent +import android.content.res.Configuration +import android.hardware.input.InputManager +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.drawable.BitmapDrawable +import android.net.Uri +import android.os.Bundle +import android.provider.DocumentsContract +import android.util.Log +import androidx.activity.SystemBarStyle +import androidx.activity.compose.BackHandler +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AppCompatActivity +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.EnterTransition +import androidx.compose.animation.ExitTransition +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.FastOutSlowInEasing +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.spring +import androidx.compose.animation.core.tween +import androidx.compose.animation.expandVertically +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.shrinkVertically +import androidx.compose.animation.slideInHorizontally +import androidx.compose.animation.slideOutHorizontally +import androidx.compose.animation.togetherWith +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background +import androidx.compose.foundation.basicMarquee +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.focusable +import androidx.compose.foundation.gestures.detectHorizontalDragGestures +import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsPressedAsState +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items +import androidx.compose.foundation.lazy.grid.itemsIndexed +import androidx.compose.foundation.lazy.grid.rememberLazyGridState +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.outlined.ArrowBack +import androidx.compose.material.icons.automirrored.outlined.ExitToApp +import androidx.compose.material.icons.automirrored.outlined.OpenInNew +import androidx.compose.material.icons.outlined.* +import androidx.compose.material3.* +import androidx.compose.material3.pulltorefresh.PullToRefreshBox +import androidx.compose.runtime.* +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.snapshotFlow +import androidx.compose.runtime.snapshots.SnapshotStateMap +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.zIndex +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusProperties +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.focus.focusTarget +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.geometry.CornerRadius +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.TileMode +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.input.pointer.PointerEventPass +import androidx.compose.ui.input.pointer.PointerEventType +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties +import androidx.lifecycle.lifecycleScope +import androidx.navigation.NavHostController +import androidx.navigation.NavType +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import androidx.navigation.navArgument +import coil.compose.AsyncImage +import coil.imageLoader +import coil.request.ImageRequest +import com.winlator.cmod.BuildConfig +import com.winlator.cmod.R +import com.winlator.cmod.app.PluviaApp +import com.winlator.cmod.app.db.PluviaDatabase +import com.winlator.cmod.app.service.DownloadService +import com.winlator.cmod.app.service.download.DownloadCoordinator +import com.winlator.cmod.app.update.UpdateChecker +import com.winlator.cmod.feature.settings.InputControlsFragment +import com.winlator.cmod.feature.settings.SettingsFocusZone +import com.winlator.cmod.feature.settings.SettingsHost +import com.winlator.cmod.feature.settings.SettingsNavBridge +import com.winlator.cmod.feature.settings.SettingsNavItem +import com.winlator.cmod.feature.setup.SetupWizardActivity +import com.winlator.cmod.feature.shortcuts.LibraryShortcutUtils +import com.winlator.cmod.feature.shortcuts.LibraryShortcutArtwork +import com.winlator.cmod.feature.shortcuts.ShortcutBroadcastReceiver +import com.winlator.cmod.feature.shortcuts.ShortcutSettingsComposeDialog +import com.winlator.cmod.feature.shortcuts.ShortcutsFragment +import com.winlator.cmod.feature.stores.common.StoreArtworkCache +import com.winlator.cmod.feature.stores.epic.data.EpicCredentials +import com.winlator.cmod.feature.stores.epic.data.EpicGame +import com.winlator.cmod.feature.stores.epic.data.EpicGameToken +import com.winlator.cmod.feature.stores.epic.service.EpicAuthManager +import com.winlator.cmod.feature.stores.epic.service.EpicCloudSavesManager +import com.winlator.cmod.feature.stores.epic.service.EpicConstants +import com.winlator.cmod.feature.stores.epic.service.EpicDownloadManager +import com.winlator.cmod.feature.stores.epic.service.EpicGameLauncher +import com.winlator.cmod.feature.stores.epic.service.EpicManager +import com.winlator.cmod.feature.stores.epic.service.EpicService +import com.winlator.cmod.feature.stores.epic.service.EpicUpdateInfo +import com.winlator.cmod.feature.stores.epic.ui.auth.EpicOAuthActivity +import com.winlator.cmod.feature.stores.gog.data.GOGDlcInfo +import com.winlator.cmod.feature.stores.gog.data.GOGGame +import com.winlator.cmod.feature.stores.gog.data.LibraryItem +import com.winlator.cmod.feature.stores.gog.service.GOGAuthManager +import com.winlator.cmod.feature.stores.gog.service.GOGConstants +import com.winlator.cmod.feature.stores.gog.service.GOGManifestSizes +import com.winlator.cmod.feature.stores.gog.service.GOGService +import com.winlator.cmod.feature.stores.gog.service.GOGUpdateInfo +import com.winlator.cmod.feature.stores.gog.ui.auth.GOGOAuthActivity +import com.winlator.cmod.feature.stores.steam.SteamLoginActivity +import com.winlator.cmod.feature.stores.steam.data.DepotInfo +import com.winlator.cmod.feature.stores.steam.data.DownloadInfo +import com.winlator.cmod.feature.stores.steam.data.SteamApp +import com.winlator.cmod.feature.stores.steam.enums.DownloadPhase +import com.winlator.cmod.feature.stores.steam.events.AndroidEvent +import com.winlator.cmod.feature.stores.steam.events.EventDispatcher +import com.winlator.cmod.feature.stores.steam.service.SteamService +import com.winlator.cmod.feature.stores.steam.utils.PrefManager +import com.winlator.cmod.feature.stores.steam.utils.getAvatarURL +import com.winlator.cmod.feature.sync.CloudSyncHelper +import com.winlator.cmod.feature.sync.google.CloudSyncManager +import com.winlator.cmod.feature.sync.google.GameSaveBackupManager +import com.winlator.cmod.feature.sync.ui.CloudSavesContent +import com.winlator.cmod.runtime.container.ContainerManager +import com.winlator.cmod.runtime.container.Shortcut +import com.winlator.cmod.runtime.display.XServerDisplayActivity +import com.winlator.cmod.runtime.display.environment.ImageFs +import com.winlator.cmod.runtime.input.ControllerHelper +import com.winlator.cmod.runtime.wine.PeIconExtractor +import com.winlator.cmod.shared.android.ActivityResultHost +import com.winlator.cmod.shared.android.AppTerminationHelper +import com.winlator.cmod.shared.android.DirectoryPickerDialog +import com.winlator.cmod.shared.android.FixedFontScaleAppCompatActivity +import com.winlator.cmod.shared.android.RefreshRateUtils +import com.winlator.cmod.shared.io.StorageUtils +import com.winlator.cmod.shared.io.FileUtils +import com.winlator.cmod.shared.ui.CarouselView +import com.winlator.cmod.shared.ui.dialog.PopupDialog +import com.winlator.cmod.shared.ui.dialog.PopupTextAction +import androidx.compose.foundation.focusGroup +import com.winlator.cmod.shared.ui.focus.controllerFocusGlow +import com.winlator.cmod.shared.ui.focus.controllerMenuInput +import com.winlator.cmod.shared.ui.focus.controllerTextFieldEscape +import com.winlator.cmod.shared.ui.nav.DialogPaneNav +import com.winlator.cmod.shared.ui.nav.LocalPaneNav +import com.winlator.cmod.shared.ui.nav.PANE_DIR_ACTIVATE +import com.winlator.cmod.shared.ui.nav.PANE_DIR_DOWN +import com.winlator.cmod.shared.ui.nav.PANE_DIR_LEFT +import com.winlator.cmod.shared.ui.nav.PANE_DIR_RIGHT +import com.winlator.cmod.shared.ui.nav.PANE_DIR_SECONDARY +import com.winlator.cmod.shared.ui.nav.PANE_DIR_UP +import com.winlator.cmod.shared.ui.nav.PaneNavRegistry +import com.winlator.cmod.shared.ui.nav.paneNavItem +import com.winlator.cmod.shared.ui.FourByTwoGridView +import com.winlator.cmod.shared.ui.JoystickGridScroll +import com.winlator.cmod.shared.ui.JoystickListScroll +import com.winlator.cmod.shared.ui.ListView +import com.winlator.cmod.shared.ui.widget.chasingBorder +import com.winlator.cmod.shared.theme.WinNativeTheme +import dagger.hilt.android.AndroidEntryPoint +import dagger.Lazy +import com.winlator.cmod.feature.stores.steam.enums.EPersonaState +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import javax.inject.Inject +import kotlin.math.abs +import kotlin.math.roundToInt + +// Game settings/detail dialogs, split out of UnifiedActivity.kt (behavior-identical). + +@Composable +internal fun UnifiedActivity.LibraryDetailPopupFrame( + title: String, + onDismissRequest: () -> Unit, + wide: Boolean = false, + content: @Composable ColumnScope.() -> Unit, +) { + val dismissInteractionSource = remember { MutableInteractionSource() } + val panelInteractionSource = remember { MutableInteractionSource() } + val registry = remember { PaneNavRegistry() } + + CompositionLocalProvider(LocalPaneNav provides registry) { + DialogPaneNav(registry, onDismiss = onDismissRequest) + Box( + modifier = + Modifier + .fillMaxSize() + .background(Color.Black.copy(alpha = 0.58f)) + .clickable( + interactionSource = dismissInteractionSource, + indication = null, + onClick = onDismissRequest, + ), + ) { + BoxWithConstraints( + modifier = + Modifier + .fillMaxSize() + .windowInsetsPadding(WindowInsets.navigationBars) + .padding(16.dp), + contentAlignment = Alignment.Center, + ) { + val panelMaxWidth = if (wide) 440.dp else 360.dp + val panelWidthFraction = if (wide) 0.72f else 0.58f + val panelMaxHeight = (maxHeight - 16.dp).coerceAtLeast(240.dp) + + Surface( + modifier = + Modifier + .fillMaxWidth(panelWidthFraction) + .widthIn(max = panelMaxWidth) + .heightIn(max = panelMaxHeight) + .clickable( + interactionSource = panelInteractionSource, + indication = null, + onClick = {}, + ), + shape = RoundedCornerShape(16.dp), + color = CardDark, + border = BorderStroke(1.dp, CardBorder), + tonalElevation = 8.dp, + shadowElevation = 12.dp, + ) { + Column { + Row( + modifier = + Modifier + .fillMaxWidth() + .padding(start = 16.dp, top = 8.dp, end = 8.dp, bottom = 8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = title, + style = MaterialTheme.typography.titleSmall, + fontSize = 13.sp, + color = TextPrimary, + fontWeight = FontWeight.Bold, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.weight(1f), + ) + IconButton( + onClick = onDismissRequest, + modifier = + Modifier + .size(34.dp) + .paneNavItem(cornerRadius = 8.dp, onActivate = onDismissRequest), + ) { + Icon( + Icons.Outlined.Close, + contentDescription = stringResource(R.string.common_ui_close), + tint = TextSecondary, + modifier = Modifier.size(20.dp), + ) + } + } + HorizontalDivider(color = CardBorder, thickness = 0.5.dp) + Column( + modifier = + Modifier + .weight(1f, fill = false) + .verticalScroll(rememberScrollState()), + ) { + content() + } + } + } + } + } + } +} + +@Composable +internal fun UnifiedActivity.GameSettingsDialogFrame( + title: String, + onDismissRequest: () -> Unit, + wide: Boolean = false, + contentKey: Any? = null, + content: @Composable ColumnScope.() -> Unit, +) { + val registry = remember { PaneNavRegistry() } + LaunchedEffect(contentKey) { registry.reset() } + Dialog( + onDismissRequest = onDismissRequest, + properties = + DialogProperties( + usePlatformDefaultWidth = false, + decorFitsSystemWindows = false, + ), + ) { + CompositionLocalProvider(LocalPaneNav provides registry) { + DialogPaneNav(registry, onDismiss = onDismissRequest) + BoxWithConstraints( + modifier = + Modifier + .fillMaxSize() + .windowInsetsPadding(WindowInsets.navigationBars), + contentAlignment = Alignment.Center, + ) { + val widthModifier = + if (wide) { + Modifier.widthIn(min = 320.dp, max = (maxWidth - 32.dp).coerceAtMost(560.dp)) + } else { + Modifier.widthIn(min = 200.dp, max = 280.dp) + } + val maxContentHeight = (maxHeight - 48.dp).coerceAtLeast(320.dp) + Surface( + modifier = widthModifier.heightIn(max = maxContentHeight), + shape = RoundedCornerShape(14.dp), + color = CardDark, + border = BorderStroke(1.dp, CardBorder), + tonalElevation = 8.dp, + ) { + Column( + modifier = + Modifier + .padding(vertical = 6.dp), + ) { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = title, + modifier = Modifier + .weight(1f) + .padding(start = 16.dp, top = 8.dp, bottom = 8.dp), + style = MaterialTheme.typography.titleSmall, + color = TextPrimary, + fontWeight = FontWeight.Bold, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + IconButton( + onClick = onDismissRequest, + modifier = Modifier + .padding(end = 4.dp) + .size(34.dp) + .paneNavItem(cornerRadius = 17.dp, onActivate = onDismissRequest, pinTop = true), + ) { + Icon( + Icons.Outlined.Close, + contentDescription = stringResource(R.string.common_ui_close), + tint = TextSecondary, + modifier = Modifier.size(20.dp), + ) + } + } + HorizontalDivider(color = CardBorder, thickness = 0.5.dp) + Column( + modifier = + Modifier + .weight(1f, fill = false) + .verticalScroll(rememberScrollState()), + ) { + content() + } + } + } + } + } + } +} + +@Composable +internal fun UnifiedActivity.GameSettingsActionGrid( + actions: List, + modifier: Modifier = Modifier, +) { + Column(modifier = modifier) { + actions.forEachIndexed { index, action -> + if (index > 0) { + HorizontalDivider( + color = CardBorder.copy(alpha = 0.5f), + thickness = 0.5.dp, + modifier = Modifier.padding(horizontal = 16.dp), + ) + } + GameSettingsActionCard(action = action, isEntry = index == 0) + } + } +} + +@Composable +internal fun UnifiedActivity.GameSettingsActionCard( + action: GameSettingsActionItem, + modifier: Modifier = Modifier, + isEntry: Boolean = false, +) { + val isDanger = action.accentColor == DangerRed + val iconColor = if (isDanger) DangerRed else TextSecondary + val textColor = if (isDanger) DangerRed else TextPrimary + + val interactionSource = remember { MutableInteractionSource() } + val isPressed by interactionSource.collectIsPressedAsState() + val scale by animateFloatAsState( + targetValue = if (isPressed) 0.96f else 1f, + animationSpec = spring(stiffness = Spring.StiffnessMediumLow), + label = "actionCardScale", + ) + Row( + modifier = + modifier + .fillMaxWidth() + .graphicsLayer { + scaleX = scale + scaleY = scale + }.paneNavItem(cornerRadius = 0.dp, onActivate = action.onClick, isEntry = isEntry) + .clickable( + interactionSource = interactionSource, + indication = null, + onClick = action.onClick, + ).padding(horizontal = 16.dp, vertical = 11.dp), + horizontalArrangement = Arrangement.spacedBy(12.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + imageVector = action.icon, + contentDescription = null, + tint = iconColor, + modifier = Modifier.size(18.dp), + ) + Text( + text = action.title, + style = MaterialTheme.typography.bodyMedium, + color = textColor, + fontWeight = FontWeight.Medium, + maxLines = 1, + ) + } +} + +@Composable +internal fun UnifiedActivity.GameSettingsInfoCard( + message: String, + accentColor: Color = Accent, +) { + Column( + modifier = + Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 12.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(6.dp), + ) { + Icon( + imageVector = Icons.Outlined.Warning, + contentDescription = null, + tint = accentColor.copy(alpha = 0.7f), + modifier = Modifier.size(18.dp), + ) + Text( + text = message, + style = MaterialTheme.typography.bodySmall, + color = TextSecondary, + lineHeight = 18.sp, + textAlign = TextAlign.Center, + ) + } +} + +/** + * Shared uninstall/remove confirmation UI used by GameSettingsDialog, + * GOGGameSettingsDialog, and LibraryGameDetailDialog. + */ +@Composable +internal fun UnifiedActivity.UninstallConfirmation( + message: String, + confirmLabel: String = stringResource(R.string.common_ui_uninstall), + onConfirm: () -> Unit, + onCancel: () -> Unit, +) { + var isUninstalling by remember { mutableStateOf(false) } + + GameSettingsInfoCard(message = message, accentColor = DangerRed) + + if (isUninstalling) { + Box( + modifier = Modifier.fillMaxWidth().padding(vertical = 12.dp), + contentAlignment = Alignment.Center, + ) { + CircularProgressIndicator(color = DangerRed) + } + } else { + Row( + modifier = + Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 8.dp), + horizontalArrangement = Arrangement.End, + verticalAlignment = Alignment.CenterVertically, + ) { + OutlinedButton( + onClick = { + isUninstalling = true + onConfirm() + }, + modifier = Modifier.paneNavItem( + cornerRadius = 8.dp, + onActivate = { isUninstalling = true; onConfirm() }, + isEntry = true, + ), + border = BorderStroke(1.dp, DangerRed.copy(alpha = 0.5f)), + shape = RoundedCornerShape(8.dp), + colors = ButtonDefaults.outlinedButtonColors(contentColor = DangerRed), + ) { + Text( + confirmLabel, + style = MaterialTheme.typography.bodySmall, + fontWeight = FontWeight.Medium, + ) + } + Spacer(Modifier.width(8.dp)) + TextButton( + onClick = onCancel, + modifier = Modifier.paneNavItem(cornerRadius = 8.dp, onActivate = onCancel), + ) { + Text(stringResource(R.string.common_ui_cancel), color = TextSecondary, style = MaterialTheme.typography.bodySmall) + } + } + } +} + +@Composable +internal fun UnifiedActivity.ShortcutRemovalConfirmation( + message: String, + onConfirm: () -> Unit, + onCancel: () -> Unit, +) { + var isRemoving by remember { mutableStateOf(false) } + + GameSettingsInfoCard(message = message, accentColor = DangerRed) + + if (isRemoving) { + Box( + modifier = Modifier.fillMaxWidth().padding(vertical = 12.dp), + contentAlignment = Alignment.Center, + ) { + CircularProgressIndicator(color = DangerRed) + } + } else { + Row( + modifier = + Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 8.dp), + horizontalArrangement = Arrangement.End, + verticalAlignment = Alignment.CenterVertically, + ) { + OutlinedButton( + onClick = { + isRemoving = true + onConfirm() + }, + modifier = Modifier.paneNavItem( + cornerRadius = 8.dp, + onActivate = { isRemoving = true; onConfirm() }, + isEntry = true, + ), + border = BorderStroke(1.dp, DangerRed.copy(alpha = 0.5f)), + shape = RoundedCornerShape(8.dp), + colors = ButtonDefaults.outlinedButtonColors(contentColor = DangerRed), + ) { + Text( + stringResource(R.string.common_ui_remove), + style = MaterialTheme.typography.bodySmall, + fontWeight = FontWeight.Medium, + ) + } + Spacer(Modifier.width(8.dp)) + TextButton( + onClick = onCancel, + modifier = Modifier.paneNavItem(cornerRadius = 8.dp, onActivate = onCancel), + ) { + Text(stringResource(R.string.common_ui_cancel), color = TextSecondary, style = MaterialTheme.typography.bodySmall) + } + } + } +} + +@Composable +internal fun UnifiedActivity.HeroLaunchConfirmFooter( + onCancel: () -> Unit, + onContinue: () -> Unit, +) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + PaneFooterAction( + label = stringResource(R.string.common_ui_cancel), + textColor = DangerRed, + onClick = onCancel, + ) + PaneFooterAction( + label = stringResource(R.string.common_ui_continue), + textColor = StatusOnline, + onClick = onContinue, + isEntry = true, + ) + } +} + +@Composable +internal fun UnifiedActivity.PaneFooterAction( + label: String, + textColor: Color, + onClick: () -> Unit, + isEntry: Boolean = false, +) { + Box( + modifier = + Modifier + .clip(RoundedCornerShape(8.dp)) + .paneNavItem( + cornerRadius = 8.dp, + onActivate = onClick, + tapToSelect = true, + isEntry = isEntry, + ).padding(horizontal = 10.dp, vertical = 7.dp), + contentAlignment = Alignment.Center, + ) { + Text( + text = label, + color = textColor, + fontSize = 12.sp, + fontWeight = FontWeight.SemiBold, + maxLines = 1, + ) + } +} + +@Composable +internal fun UnifiedActivity.HeroBootDialog( + onConfirm: (HeroBootChoice) -> Unit, + onDismissRequest: () -> Unit, +) { + var choice by remember { mutableStateOf(HeroBootChoice.Desktop) } + val graphicsTest = stringResource(R.string.hero_graphics_tests_title) + val inputTest = stringResource(R.string.hero_input_tests_title) + val bits32 = stringResource(R.string.hero_graphics_test_32) + val bits64 = stringResource(R.string.hero_graphics_test_64) + val test32 = "$graphicsTest $bits32" + val test64 = "$graphicsTest $bits64" + val input32 = "$inputTest $bits32" + val input64 = "$inputTest $bits64" + val title = + when (choice) { + HeroBootChoice.Desktop -> stringResource(R.string.hero_boot_to_desktop_title) + HeroBootChoice.Cube32 -> test32 + HeroBootChoice.Cube64 -> test64 + HeroBootChoice.Input32 -> input32 + HeroBootChoice.Input64 -> input64 + } + val registry = remember { PaneNavRegistry() } + Dialog(onDismissRequest = onDismissRequest) { + CompositionLocalProvider(LocalPaneNav provides registry) { + DialogPaneNav(registry, onDismiss = onDismissRequest, onStart = { onConfirm(choice) }) + PopupDialog( + title = title, + icon = Icons.Outlined.DesktopWindows, + accentColor = Accent, + modifier = Modifier.widthIn(min = 220.dp, max = 290.dp), + content = { + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(6.dp), + ) { + HeroBootOptionRow( + label = stringResource(R.string.hero_boot_to_desktop_title), + selected = choice == HeroBootChoice.Desktop, + onClick = { choice = HeroBootChoice.Desktop }, + ) + HeroBootOptionRow( + label = test32, + selected = choice == HeroBootChoice.Cube32, + onClick = { choice = HeroBootChoice.Cube32 }, + ) + HeroBootOptionRow( + label = test64, + selected = choice == HeroBootChoice.Cube64, + onClick = { choice = HeroBootChoice.Cube64 }, + ) + HeroBootOptionRow( + label = input32, + selected = choice == HeroBootChoice.Input32, + onClick = { choice = HeroBootChoice.Input32 }, + ) + HeroBootOptionRow( + label = input64, + selected = choice == HeroBootChoice.Input64, + onClick = { choice = HeroBootChoice.Input64 }, + ) + } + }, + footer = { + HeroLaunchConfirmFooter(onCancel = onDismissRequest, onContinue = { onConfirm(choice) }) + }, + ) + } + } +} + +@Composable +internal fun UnifiedActivity.HeroBootOptionRow( + label: String, + selected: Boolean, + onClick: () -> Unit, +) { + val glassBlue = Accent + Box( + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(8.dp)) + .background(glassBlue.copy(alpha = if (selected) 0.26f else 0.05f)) + .border(1.dp, glassBlue.copy(alpha = if (selected) 0.65f else 0.12f), RoundedCornerShape(8.dp)) + .paneNavItem(cornerRadius = 8.dp, onActivate = onClick, tapToSelect = true) + .padding(horizontal = 12.dp, vertical = 8.dp), + contentAlignment = Alignment.Center, + ) { + Text( + text = label, + color = if (selected) Color.White else glassBlue.copy(alpha = 0.5f), + fontSize = 12.sp, + fontWeight = FontWeight.SemiBold, + ) + } +} + +@Composable +internal fun UnifiedActivity.HeroRemoveShortcutDialog( + gameName: String, + onConfirm: () -> Unit, + onDismissRequest: () -> Unit, +) { + val registry = remember { PaneNavRegistry() } + var isRemoving by remember { mutableStateOf(false) } + Dialog(onDismissRequest = onDismissRequest) { + CompositionLocalProvider(LocalPaneNav provides registry) { + DialogPaneNav(registry, onDismiss = onDismissRequest) + PopupDialog( + title = stringResource(R.string.common_ui_shortcut), + message = stringResource(R.string.shortcuts_list_remove_game_shortcut_message, gameName), + icon = Icons.Outlined.Home, + accentColor = DangerRed, + confirmButtonColor = DangerRed, + progressLabel = stringResource(R.string.common_ui_working), + modifier = Modifier.widthIn(min = 280.dp, max = 360.dp), + footer = { + if (isRemoving) { + Row( + modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp), + horizontalArrangement = Arrangement.spacedBy(10.dp, Alignment.CenterHorizontally), + verticalAlignment = Alignment.CenterVertically, + ) { + CircularProgressIndicator(color = DangerRed, strokeWidth = 2.dp, modifier = Modifier.size(20.dp)) + Text( + stringResource(R.string.common_ui_working), + color = TextPrimary, + fontSize = 13.sp, + fontWeight = FontWeight.SemiBold, + ) + } + } else { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + PaneFooterAction( + label = stringResource(R.string.common_ui_cancel), + textColor = TextSecondary, + onClick = onDismissRequest, + ) + PaneFooterAction( + label = stringResource(R.string.common_ui_remove), + textColor = DangerRed, + onClick = { + isRemoving = true + onConfirm() + }, + isEntry = true, + ) + } + } + }, + ) + } + } +} + +@Composable +internal fun UnifiedActivity.GameSettingsDialog( + app: SteamApp, + onDismissRequest: () -> Unit, +) { + val context = LocalContext.current + var currentTab by remember { mutableStateOf(GameSettingsScreen.Menu) } + val scope = rememberCoroutineScope() + val isCustom = app.id < 0 + val isEpic = app.id >= 2000000000 + val epicId = if (isEpic) app.id - 2000000000 else 0 + var shortcutRefreshKey by remember(app.id, isCustom, isEpic, epicId) { mutableStateOf(0) } + var pinnedShortcutOverride by remember(app.id, isCustom, isEpic, epicId) { mutableStateOf(null) } + val epicArtworkUrl by produceState(initialValue = null, key1 = isEpic, key2 = epicId) { + value = + if (isEpic) { + val epicGame = db.epicGameDao().getById(epicId) + epicGame?.primaryImageUrl ?: epicGame?.iconUrl + } else { + null + } + } + val currentRefreshSignal = this@GameSettingsDialog.libraryRefreshSignal + val homeShortcutState by produceState( + HomeShortcutUiState(), + app.id, + isCustom, + isEpic, + epicId, + currentRefreshSignal, + shortcutRefreshKey, + ) { + value = + withContext(Dispatchers.IO) { + val shortcut = findLibraryShortcutForGame(ContainerManager(context), app, isCustom, isEpic, epicId) + HomeShortcutUiState( + shortcut = shortcut, + isPinned = shortcut?.let { LibraryShortcutUtils.hasPinnedHomeShortcut(context, it) } == true, + loaded = true, + ) + } + } + val artworkRefreshListener = + remember(app.id, isCustom, isEpic, epicId) { + object : EventDispatcher.JavaEventListener { + override fun onEvent(event: Any) { + if (event is AndroidEvent.LibraryArtworkChanged) { + shortcutRefreshKey++ + } + } + } + } + DisposableEffect(artworkRefreshListener) { + PluviaApp.events.onJava(AndroidEvent.LibraryArtworkChanged::class, artworkRefreshListener) + onDispose { + PluviaApp.events.offJava(AndroidEvent.LibraryArtworkChanged::class, artworkRefreshListener) + } + } + val hasPinnedShortcut = pinnedShortcutOverride ?: homeShortcutState.isPinned + + val exportLauncher = + rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument("application/zip")) { uri -> + if (uri != null) { + scope.launch(kotlinx.coroutines.Dispatchers.IO) { + try { + val os = context.contentResolver.openOutputStream(uri) ?: return@launch + val zos = java.util.zip.ZipOutputStream(java.io.BufferedOutputStream(os)) + + val containerManager = + com.winlator.cmod.runtime.container + .ContainerManager(context) + val shortcut = findLibraryShortcutForGame(containerManager, app, isCustom, isEpic, epicId) + + val dirsToZip = mutableListOf() + + val goldbergSaves = java.io.File(SteamService.getAppDirPath(app.id), "steam_settings/saves") + if (goldbergSaves.exists() && goldbergSaves.isDirectory) { + dirsToZip.add(goldbergSaves) + } + + if (shortcut != null) { + val prefixDir = java.io.File(shortcut.container.getRootDir(), ".wine/drive_c/users/xuser") + val docs = java.io.File(prefixDir, "Documents") + val savedGames = java.io.File(prefixDir, "Saved Games") + val appData = java.io.File(prefixDir, "AppData") + if (docs.exists()) dirsToZip.add(docs) + if (savedGames.exists()) dirsToZip.add(savedGames) + if (appData.exists()) dirsToZip.add(appData) + } + + fun zipDir( + dir: java.io.File, + baseName: String, + ) { + val children = dir.listFiles() ?: return + for (child in children) { + val name = if (baseName.isEmpty()) child.name else "$baseName/${child.name}" + if (child.isDirectory) { + zos.putNextEntry(java.util.zip.ZipEntry("$name/")) + zos.closeEntry() + zipDir(child, name) + } else { + zos.putNextEntry(java.util.zip.ZipEntry(name)) + val fis = java.io.FileInputStream(child) + val buf = ByteArray(1024 * 8) + var len: Int + while (fis.read(buf).also { len = it } > 0) { + zos.write(buf, 0, len) + } + fis.close() + zos.closeEntry() + } + } + } + + for (dir in dirsToZip) { + val baseName = dir.name + zos.putNextEntry(java.util.zip.ZipEntry("$baseName/")) + zos.closeEntry() + zipDir(dir, baseName) + } + + zos.close() + withContext(kotlinx.coroutines.Dispatchers.Main) { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + R.string.saves_import_export_exported, + android.widget.Toast.LENGTH_SHORT, + ) + onDismissRequest() + } + } catch (e: Exception) { + e.printStackTrace() + withContext(kotlinx.coroutines.Dispatchers.Main) { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + getString(R.string.saves_import_export_exported_failed, e.message), + android.widget.Toast.LENGTH_SHORT, + ) + } + } + } + } + } + + val importLauncher = + rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) { uri -> + if (uri != null) { + scope.launch(kotlinx.coroutines.Dispatchers.IO) { + try { + val `is` = context.contentResolver.openInputStream(uri) ?: return@launch + val zis = java.util.zip.ZipInputStream(java.io.BufferedInputStream(`is`)) + + val containerManager = + com.winlator.cmod.runtime.container + .ContainerManager(context) + val shortcut = findLibraryShortcutForGame(containerManager, app, isCustom, isEpic, epicId) + + val goldbergSavesParent = + java.io.File( + if (isEpic) app.gameDir else SteamService.getAppDirPath(app.id), + if (isEpic) "" else "steam_settings", + ) + val prefixDir = shortcut?.let { java.io.File(it.container.getRootDir(), ".wine/drive_c/users/xuser") } + + var ze: java.util.zip.ZipEntry? + while (zis.nextEntry.also { ze = it } != null) { + val entry = ze!! + val name = entry.name + var destFile: java.io.File? = null + if (name.startsWith("saves/")) { + destFile = java.io.File(goldbergSavesParent, name) + } else if (prefixDir != null) { + if (name.startsWith("Documents/") || name.startsWith("Saved Games/") || name.startsWith("AppData/")) { + destFile = java.io.File(prefixDir, name) + } + } + + if (destFile != null) { + if (entry.isDirectory) { + destFile.mkdirs() + } else { + destFile.parentFile?.mkdirs() + val fos = java.io.FileOutputStream(destFile) + val buf = ByteArray(1024 * 8) + var len: Int + while (zis.read(buf).also { len = it } > 0) { + fos.write(buf, 0, len) + } + fos.close() + } + } + zis.closeEntry() + } + zis.close() + withContext(kotlinx.coroutines.Dispatchers.Main) { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + R.string.saves_import_export_imported, + android.widget.Toast.LENGTH_SHORT, + ) + onDismissRequest() + } + } catch (e: Exception) { + e.printStackTrace() + withContext(kotlinx.coroutines.Dispatchers.Main) { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + getString(R.string.saves_import_export_imported_failed, e.message), + android.widget.Toast.LENGTH_SHORT, + ) + } + } + } + } + } + + GameSettingsDialogFrame( + title = app.name, + onDismissRequest = onDismissRequest, + wide = currentTab == GameSettingsScreen.CloudSaves, + contentKey = currentTab, + ) { + when (currentTab) { + GameSettingsScreen.Menu -> { + val actions = + listOf( + GameSettingsActionItem( + title = stringResource(R.string.common_ui_settings), + icon = Icons.Outlined.Settings, + onClick = { + val containerManager = ContainerManager(context) + val shortcut = + findLibraryShortcutForGame(containerManager, app, isCustom, isEpic, epicId) + ?: if (isCustom) { + null + } else { + ShortcutSettingsComposeDialog.createLibraryShortcut( + context = context, + containerManager = containerManager, + source = if (isEpic) "EPIC" else "STEAM", + appId = if (isEpic) epicId else app.id, + gogId = null, + appName = app.name, + ) + } + if (shortcut != null) { + ShortcutSettingsComposeDialog(this@GameSettingsDialog, shortcut).show() + } + onDismissRequest() + }, + ), + GameSettingsActionItem( + title = stringResource(R.string.hero_boot_to_desktop_title), + icon = Icons.Outlined.DesktopWindows, + onClick = { + val shortcut = + findLibraryShortcutForGame(ContainerManager(context), app, isCustom, isEpic, epicId) + if (shortcut != null) { + context.startActivity( + Intent(context, XServerDisplayActivity::class.java) + .putExtra("container_id", shortcut.container.id), + ) + } else { + com.winlator.cmod.shared.ui.toast.WinToast.show(context, R.string.shortcuts_list_not_available) + } + onDismissRequest() + }, + ), + GameSettingsActionItem( + title = + stringResource( + if (hasPinnedShortcut) { + R.string.common_ui_remove + } else { + R.string.common_ui_shortcut + }, + ), + icon = Icons.Outlined.Home, + accentColor = if (hasPinnedShortcut) DangerRed else Accent, + onClick = { + if (hasPinnedShortcut) { + currentTab = GameSettingsScreen.Shortcut + } else { + scope.launch { + val created = + withContext(Dispatchers.IO) { + addLibraryShortcutToHomeScreen( + context, + app, + isCustom, + isEpic, + epicId, + epicArtworkUrl, + ) + } + if (!created) { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + context.getString( + R.string.library_games_failed_to_create_shortcut, + app.name, + ), + ) + } + } + } + }, + ), + GameSettingsActionItem( + title = stringResource(R.string.cloud_saves_title), + icon = Icons.Outlined.CloudSync, + onClick = { currentTab = GameSettingsScreen.CloudSaves }, + ), + + GameSettingsActionItem( + title = + if (isCustom) { + stringResource( + R.string.common_ui_remove, + ) + } else { + stringResource(R.string.common_ui_uninstall) + }, + icon = Icons.Outlined.Delete, + accentColor = DangerRed, + onClick = { currentTab = GameSettingsScreen.Uninstall }, + ), + ) + + GameSettingsActionGrid(actions = actions) + } + + GameSettingsScreen.Shortcut -> { + ShortcutRemovalConfirmation( + message = stringResource(R.string.shortcuts_list_remove_game_shortcut_message, app.name), + onConfirm = { + scope.launch { + val removed = + withContext(Dispatchers.IO) { + homeShortcutState.shortcut?.let { + LibraryShortcutUtils.disablePinnedHomeShortcut(context, it) + } == true + } + pinnedShortcutOverride = if (removed) false else hasPinnedShortcut + shortcutRefreshKey++ + currentTab = GameSettingsScreen.Menu + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + if (removed) { + context.getString(R.string.shortcuts_list_removed) + } else { + context.getString(R.string.common_ui_unknown_error) + }, + ) + } + }, + onCancel = { currentTab = GameSettingsScreen.Menu }, + ) + } + + GameSettingsScreen.CloudSaves -> { + var isWorking by remember { mutableStateOf(false) } + val shortcut = + remember(app.id, epicId, isCustom, isEpic) { + findLibraryShortcutForGame(ContainerManager(context), app, isCustom, isEpic, epicId) + } + var cloudSyncEnabled by remember(shortcut?.file?.absolutePath) { + mutableStateOf(isShortcutCloudSyncEnabled(shortcut)) + } + var offlineModeEnabled by remember(shortcut?.file?.absolutePath) { + mutableStateOf(isShortcutOfflineMode(shortcut)) + } + + val gameSource = + when { + isEpic -> GameSaveBackupManager.GameSource.EPIC + isCustom -> GameSaveBackupManager.GameSource.CUSTOM + else -> GameSaveBackupManager.GameSource.STEAM + } + val gameIdStr = + when { + isEpic -> epicId.toString() + isCustom -> shortcut?.let { GameSaveBackupManager.customGameId(it) } ?: app.name + else -> app.id.toString() + } + val providerLabel = + when (gameSource) { + GameSaveBackupManager.GameSource.EPIC -> + stringResource(R.string.preloader_platform_epic) + GameSaveBackupManager.GameSource.CUSTOM -> + stringResource(R.string.preloader_platform_custom) + else -> + stringResource(R.string.preloader_platform_steam) + } + + CloudSavesContent( + activity = this@GameSettingsDialog, + isWorking = isWorking, + cloudSyncEnabled = cloudSyncEnabled, + offlineModeEnabled = offlineModeEnabled, + gameSource = gameSource, + gameId = gameIdStr, + gameName = app.name, + shortcut = shortcut, + retroSaveDir = com.winlator.cmod.feature.sync.google.GameSaveBackupManager.retroSaveDir(context, shortcut, gameIdStr), + onCloudSyncToggle = { enabled -> + cloudSyncEnabled = enabled + setShortcutCloudSyncEnabled(shortcut, enabled) + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + if (enabled) { + context.getString(R.string.cloud_sync_enabled_summary) + } else { + context.getString(R.string.cloud_sync_disabled_summary) + }, + android.widget.Toast.LENGTH_SHORT, + ) + }, + onOfflineModeToggle = { enabled -> + offlineModeEnabled = enabled + setShortcutOfflineMode(shortcut, enabled) + }, + onSyncFromCloud = { + if (!isWorking) { + isWorking = true + scope.launch(Dispatchers.IO) { + val ok = + CloudSyncHelper.downloadCloudSaves( + context, + gameSource, + gameIdStr, + shortcut, + ) + withContext(Dispatchers.Main) { + isWorking = false + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + if (ok) { + context.getString( + R.string.cloud_saves_sync_from_provider_success, + providerLabel, + ) + } else { + context.getString( + R.string.cloud_saves_sync_from_provider_failed, + providerLabel, + ) + }, + android.widget.Toast.LENGTH_SHORT, + ) + } + } + } + }, + onBack = { currentTab = GameSettingsScreen.Menu }, + ) + } + + GameSettingsScreen.Uninstall -> { + UninstallConfirmation( + message = + if (isCustom) { + getString(R.string.library_games_remove_confirm, app.name) + } else { + getString(R.string.library_games_uninstall_confirm, app.name) + }, + confirmLabel = + if (isCustom) { + stringResource( + R.string.common_ui_remove, + ) + } else { + stringResource(R.string.common_ui_uninstall) + }, + onConfirm = { + if (isCustom) { + scope.launch(Dispatchers.IO) { + val cm = ContainerManager(context) + val sc = findLibraryShortcutForGame(cm, app, isCustom, isEpic, epicId) + sc?.let { LibraryShortcutUtils.deleteShortcutArtifacts(context, it) } + PluviaApp.events.emit(AndroidEvent.LibraryInstallStatusChanged(app.id)) + withContext(Dispatchers.Main) { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + getString(R.string.library_games_game_removed, app.name), + android.widget.Toast.LENGTH_SHORT, + ) + onDismissRequest() + } + } + } else if (isEpic) { + scope.launch(Dispatchers.IO) { + val result = EpicService.deleteGame(context, epicId) + withContext(Dispatchers.Main) { + if (result.isSuccess) { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + getString(R.string.library_games_game_uninstalled, app.name), + android.widget.Toast.LENGTH_SHORT, + ) + } else { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + getString( + R.string.library_games_failed_to_uninstall_reason, + result.exceptionOrNull()?.message + ?: getString(R.string.common_ui_unknown_error), + ), + android.widget.Toast.LENGTH_LONG, + ) + } + onDismissRequest() + } + } + } else { + SteamService.uninstallApp(app.id) { success -> + if (success) { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + getString(R.string.library_games_game_uninstalled, app.name), + android.widget.Toast.LENGTH_SHORT, + ) + } else { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + getString(R.string.library_games_failed_to_uninstall), + android.widget.Toast.LENGTH_SHORT, + ) + } + onDismissRequest() + } + } + }, + onCancel = { currentTab = GameSettingsScreen.Menu }, + ) + } + } + } +} + +@Composable +internal fun UnifiedActivity.GOGGameSettingsDialog( + app: GOGGame, + onDismissRequest: () -> Unit, +) { + val context = LocalContext.current + var currentTab by remember { mutableStateOf(GameSettingsScreen.Menu) } + val scope = rememberCoroutineScope() + var shortcutRefreshKey by remember(app.id) { mutableStateOf(0) } + var pinnedShortcutOverride by remember(app.id) { mutableStateOf(null) } + val currentRefreshSignal = this@GOGGameSettingsDialog.libraryRefreshSignal + val homeShortcutState by produceState( + HomeShortcutUiState(), + app.id, + currentRefreshSignal, + shortcutRefreshKey, + ) { + value = + withContext(Dispatchers.IO) { + val shortcut = + ContainerManager(context).loadShortcuts().find { + it.getExtra("game_source") == "GOG" && it.getExtra("gog_id") == app.id + } + HomeShortcutUiState( + shortcut = shortcut, + isPinned = shortcut?.let { LibraryShortcutUtils.hasPinnedHomeShortcut(context, it) } == true, + loaded = true, + ) + } + } + val hasPinnedShortcut = pinnedShortcutOverride ?: homeShortcutState.isPinned + + GameSettingsDialogFrame( + title = app.title, + onDismissRequest = onDismissRequest, + wide = currentTab == GameSettingsScreen.CloudSaves, + contentKey = currentTab, + ) { + when (currentTab) { + GameSettingsScreen.Menu -> { + GameSettingsActionGrid( + actions = + listOf( + GameSettingsActionItem( + title = stringResource(R.string.common_ui_settings), + icon = Icons.Outlined.Settings, + onClick = { + val containerManager = ContainerManager(context) + val shortcut = + containerManager.loadShortcuts().find { + it.getExtra("game_source") == "GOG" && it.getExtra("gog_id") == app.id + } ?: ShortcutSettingsComposeDialog.createLibraryShortcut( + context = context, + containerManager = containerManager, + source = "GOG", + appId = gogPseudoId(app.id), + gogId = app.id, + appName = app.title, + ) + if (shortcut != null) { + ShortcutSettingsComposeDialog(this@GOGGameSettingsDialog, shortcut).show() + } + onDismissRequest() + }, + ), + GameSettingsActionItem( + title = + stringResource( + if (hasPinnedShortcut) { + R.string.common_ui_remove + } else { + R.string.common_ui_shortcut + }, + ), + icon = Icons.Outlined.Home, + accentColor = if (hasPinnedShortcut) DangerRed else Accent, + onClick = { + if (hasPinnedShortcut) { + currentTab = GameSettingsScreen.Shortcut + } else { + scope.launch { + val artworkUrl = app.imageUrl.ifEmpty { app.iconUrl } + val created = + withContext(Dispatchers.IO) { + addGogShortcutToHomeScreen(context, app, artworkUrl) + } + if (!created) { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + context.getString( + R.string.library_games_failed_to_create_shortcut, + app.title, + ), + ) + } + } + } + }, + ), + GameSettingsActionItem( + title = stringResource(R.string.cloud_saves_title), + icon = Icons.Outlined.CloudSync, + onClick = { currentTab = GameSettingsScreen.CloudSaves }, + ), + GameSettingsActionItem( + title = stringResource(R.string.common_ui_uninstall), + icon = Icons.Outlined.Delete, + accentColor = DangerRed, + onClick = { currentTab = GameSettingsScreen.Uninstall }, + ), + ), + ) + } + + GameSettingsScreen.Shortcut -> { + ShortcutRemovalConfirmation( + message = stringResource(R.string.shortcuts_list_remove_game_shortcut_message, app.title), + onConfirm = { + scope.launch { + val removed = + withContext(Dispatchers.IO) { + homeShortcutState.shortcut?.let { + LibraryShortcutUtils.disablePinnedHomeShortcut(context, it) + } == true + } + pinnedShortcutOverride = if (removed) false else hasPinnedShortcut + shortcutRefreshKey++ + currentTab = GameSettingsScreen.Menu + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + if (removed) { + context.getString(R.string.shortcuts_list_removed) + } else { + context.getString(R.string.common_ui_unknown_error) + }, + android.widget.Toast.LENGTH_SHORT, + ) + } + }, + onCancel = { currentTab = GameSettingsScreen.Menu }, + ) + } + + GameSettingsScreen.CloudSaves -> { + var isWorking by remember { mutableStateOf(false) } + val shortcut = + remember(app.id) { + ContainerManager(context).loadShortcuts().find { + it.getExtra("game_source") == "GOG" && it.getExtra("gog_id") == app.id + } + } + var cloudSyncEnabled by remember(shortcut?.file?.absolutePath) { + mutableStateOf(isShortcutCloudSyncEnabled(shortcut)) + } + var offlineModeEnabled by remember(shortcut?.file?.absolutePath) { + mutableStateOf(isShortcutOfflineMode(shortcut)) + } + + val gogProviderLabel = stringResource(R.string.preloader_platform_gog) + + CloudSavesContent( + activity = this@GOGGameSettingsDialog, + isWorking = isWorking, + cloudSyncEnabled = cloudSyncEnabled, + offlineModeEnabled = offlineModeEnabled, + gameSource = GameSaveBackupManager.GameSource.GOG, + gameId = app.id, + gameName = app.title, + shortcut = shortcut, + onCloudSyncToggle = { enabled -> + cloudSyncEnabled = enabled + setShortcutCloudSyncEnabled(shortcut, enabled) + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + if (enabled) { + context.getString(R.string.cloud_sync_enabled_summary) + } else { + context.getString(R.string.cloud_sync_disabled_summary) + }, + android.widget.Toast.LENGTH_SHORT, + ) + }, + onOfflineModeToggle = { enabled -> + offlineModeEnabled = enabled + setShortcutOfflineMode(shortcut, enabled) + }, + onSyncFromCloud = { + if (!isWorking) { + isWorking = true + scope.launch(Dispatchers.IO) { + val ok = + CloudSyncHelper.downloadCloudSaves( + context, + GameSaveBackupManager.GameSource.GOG, + app.id, + shortcut, + ) + withContext(Dispatchers.Main) { + isWorking = false + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + if (ok) { + context.getString( + R.string.cloud_saves_sync_from_provider_success, + gogProviderLabel, + ) + } else { + context.getString( + R.string.cloud_saves_sync_from_provider_failed, + gogProviderLabel, + ) + }, + android.widget.Toast.LENGTH_SHORT, + ) + } + } + } + }, + onBack = { currentTab = GameSettingsScreen.Menu }, + ) + } + + GameSettingsScreen.Uninstall -> { + UninstallConfirmation( + message = getString(R.string.library_games_uninstall_confirm, app.title), + onConfirm = { + scope.launch(Dispatchers.IO) { + val result = GOGService.deleteGame( + context, + LibraryItem("GOG_${app.id}", app.title, com.winlator.cmod.feature.stores.steam.enums.GameSource.GOG), + ) + withContext(Dispatchers.Main) { + if (result.isSuccess) { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + getString(R.string.library_games_game_uninstalled, app.title), + android.widget.Toast.LENGTH_SHORT, + ) + } else { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + getString( + R.string.library_games_failed_to_uninstall_reason, + result.exceptionOrNull()?.message + ?: getString(R.string.common_ui_unknown_error), + ), + android.widget.Toast.LENGTH_LONG, + ) + } + onDismissRequest() + } + } + }, + onCancel = { currentTab = GameSettingsScreen.Menu }, + ) + } + } + } +} + +@Composable +internal fun UnifiedActivity.LibraryGameDetailDialog( + app: SteamApp, + gogGame: GOGGame? = null, + onDismissRequest: () -> Unit, +) { + val context = LocalContext.current + val scope = rememberCoroutineScope() + var currentScreen by remember { mutableStateOf(LibraryDetailScreen.Main) } + var activePopup by remember { mutableStateOf(null) } + var showAchievements by remember(app.id) { mutableStateOf(false) } + var shortcutRefreshKey by remember(app.id, gogGame?.id) { mutableStateOf(0) } + var pinnedShortcutOverride by remember(app.id, gogGame?.id) { mutableStateOf(null) } + var showWorkshopDialog by remember(app.id) { mutableStateOf(false) } + + val isCustom = app.id < 0 + val isEpic = app.id >= 2000000000 + val isGog = gogGame != null + val epicId = if (isEpic) app.id - 2000000000 else 0 + + val libraryDownloadRecords by com.winlator.cmod.app.service.download.DownloadCoordinator.records.collectAsState( + initial = com.winlator.cmod.app.service.download.DownloadCoordinator.snapshotRecords(), + ) + val hasBlockingSteamDownloadForLibrary = + !isCustom && !isEpic && !isGog && + libraryDownloadRecords.any { + it.store == com.winlator.cmod.app.db.download.DownloadRecord.STORE_STEAM && + it.storeGameId == app.id.toString() && + it.status in setOf( + com.winlator.cmod.app.db.download.DownloadRecord.STATUS_QUEUED, + com.winlator.cmod.app.db.download.DownloadRecord.STATUS_DOWNLOADING, + com.winlator.cmod.app.db.download.DownloadRecord.STATUS_PAUSED, + com.winlator.cmod.app.db.download.DownloadRecord.STATUS_FAILED, + ) + } + val hasBlockingEpicDownloadForLibrary = + isEpic && + libraryDownloadRecords.any { + it.store == com.winlator.cmod.app.db.download.DownloadRecord.STORE_EPIC && + it.storeGameId == epicId.toString() && + it.status in setOf( + com.winlator.cmod.app.db.download.DownloadRecord.STATUS_QUEUED, + com.winlator.cmod.app.db.download.DownloadRecord.STATUS_DOWNLOADING, + com.winlator.cmod.app.db.download.DownloadRecord.STATUS_PAUSED, + com.winlator.cmod.app.db.download.DownloadRecord.STATUS_FAILED, + ) + } + val hasBlockingGogDownloadForLibrary = + isGog && + libraryDownloadRecords.any { + it.store == com.winlator.cmod.app.db.download.DownloadRecord.STORE_GOG && + it.storeGameId == gogGame?.id && + it.status in setOf( + com.winlator.cmod.app.db.download.DownloadRecord.STATUS_QUEUED, + com.winlator.cmod.app.db.download.DownloadRecord.STATUS_DOWNLOADING, + com.winlator.cmod.app.db.download.DownloadRecord.STATUS_PAUSED, + com.winlator.cmod.app.db.download.DownloadRecord.STATUS_FAILED, + ) + } + + val epicGame by produceState(initialValue = null, key1 = epicId) { + value = if (isEpic) db.epicGameDao().getById(epicId) else null + } + + val epicArtworkUrl by produceState(initialValue = null, key1 = isEpic, key2 = epicId) { + value = + if (isEpic) { + val eg = db.epicGameDao().getById(epicId) + eg?.primaryImageUrl ?: eg?.iconUrl + } else { + null + } + } + val currentRefreshSignal = this@LibraryGameDetailDialog.libraryRefreshSignal + val homeShortcutState by produceState( + HomeShortcutUiState(), + app.id, + gogGame?.id, + isCustom, + isEpic, + isGog, + epicId, + currentRefreshSignal, + shortcutRefreshKey, + ) { + value = + withContext(Dispatchers.IO) { + val shortcut = + when { + isGog -> { + ContainerManager(context).loadShortcuts().find { + it.getExtra("game_source") == "GOG" && it.getExtra("gog_id") == gogGame!!.id + } + } + + else -> { + findLibraryShortcutForGame(ContainerManager(context), app, isCustom, isEpic, epicId) + } + } + HomeShortcutUiState( + shortcut = shortcut, + isPinned = shortcut?.let { LibraryShortcutUtils.hasPinnedHomeShortcut(context, it) } == true, + loaded = true, + ) + } + } + val artworkRefreshListener = + remember(app.id, gogGame?.id) { + object : EventDispatcher.JavaEventListener { + override fun onEvent(event: Any) { + if (event is AndroidEvent.LibraryArtworkChanged) { + shortcutRefreshKey++ + } + } + } + } + DisposableEffect(artworkRefreshListener) { + PluviaApp.events.onJava(AndroidEvent.LibraryArtworkChanged::class, artworkRefreshListener) + onDispose { + PluviaApp.events.offJava(AndroidEvent.LibraryArtworkChanged::class, artworkRefreshListener) + } + } + val hasPinnedShortcut = pinnedShortcutOverride ?: homeShortcutState.isPinned + val librarySystemIdHint = if (isCustom) retroLibrarySystemIds.value[app.id] else null + val retroCaps = + remember(homeShortcutState.shortcut, homeShortcutState.loaded, isCustom, librarySystemIdHint) { + when { + !isCustom -> com.winlator.cmod.feature.retro.RetroShortcuts.LibraryCapabilities() + homeShortcutState.loaded -> + com.winlator.cmod.feature.retro.RetroShortcuts.libraryCapabilities(homeShortcutState.shortcut) + else -> + com.winlator.cmod.feature.retro.RetroShortcuts.libraryCapabilitiesForSystemId(librarySystemIdHint) + } + } + val isRetro = retroCaps.isRetro + val isExternalRetro = retroCaps.isExternal + val retroSystemId = retroCaps.systemId + val retroRomPath = + retroCaps.romPath + ?: homeShortcutState.shortcut + ?.getExtra(com.winlator.cmod.feature.retro.RetroShortcuts.KEY_ROM) + ?.takeIf { it.isNotEmpty() } + LaunchedEffect(retroSystemId, retroRomPath, isExternalRetro) { + val sid = retroSystemId + val rp = retroRomPath + if (sid != null && rp != null && !isExternalRetro) { + com.winlator.cmod.feature.retro.RetroAchievementsManager.prefetch(context, sid, rp) + } + } + + BackHandler(enabled = activePopup != null) { + activePopup = null + } + + // Hero image + val customHeroImageFile = + homeShortcutState.shortcut + ?.getExtra("customLibraryHeroArtPath") + ?.takeIf { it.isNotBlank() } + ?.let { java.io.File(it) } + ?.takeIf { it.exists() } + val customHeroImageCacheKey = + customHeroImageFile?.let { + "library_custom_hero:${it.absolutePath}:${it.lastModified()}" + } + val heroImageUrl: Any? = + customHeroImageFile ?: when { + isGog -> { + StoreArtworkCache.imageModel(context, StoreArtworkCache.gogHeroRef(gogGame!!)) + } + + isEpic -> { + epicGame?.let { StoreArtworkCache.imageModel(context, StoreArtworkCache.epicHeroRef(it)) } + } + + isCustom -> { + val customCoverArt = + homeShortcutState.shortcut + ?.getExtra("customCoverArtPath") + ?.takeIf { it.isNotBlank() } + ?.let { java.io.File(it) } + ?.takeIf { it.exists() } + customCoverArt ?: run { + val safeName = app.name.replace("/", "_").replace("\\", "_") + val iconFile = java.io.File(context.filesDir, "custom_icons/$safeName.png") + if (iconFile.exists()) iconFile else null + } + } + + else -> { + val heroUrl = app.getHeroUrl() + StoreArtworkCache.imageModel(context, StoreArtworkCache.steamRef(app, "hero", heroUrl)) + } + } + + val subtitle = + when { + isGog -> { + gogGame!!.developer + } + + isCustom -> { + stringResource(R.string.library_games_custom_game) + } + + isEpic -> { + epicGame?.developer ?: "" + } + + else -> { + listOfNotNull( + app.developer.takeIf { it.isNotBlank() }, + app.publisher.takeIf { it.isNotBlank() }, + ).distinctBy { it.trim().lowercase() }.joinToString(" • ") + } + } + + // Playtime info + val playtimePrefs = + remember { + context.getSharedPreferences("playtime_stats", android.content.Context.MODE_PRIVATE) + } + val searchKey = + remember(app) { + if (app.id >= 2000000000 || app.id < 0) { + app.name + } else { + app.name.replace(LIBRARY_NAME_SANITIZE_REGEX, "") + } + } + val lastPlayed = playtimePrefs.getLong("${searchKey}_last_played", 0L) + val totalPlaytime = playtimePrefs.getLong("${searchKey}_playtime", 0L) + val playCount = playtimePrefs.getInt("${searchKey}_play_count", 0) + + val sourceLabel = + when { + isGog -> "GOG" + isEpic -> "Epic Games" + isCustom -> + retroCaps.sourceLabel + ?: librarySystemIdHint?.let { + com.winlator.cmod.feature.retro.RetroSystems + .fromId(it) + ?.badgeLabel + } + ?: "Custom" + else -> "Steam" + } + + // Install path + val installPath = + remember(app, gogGame) { + when { + isGog -> { + gogGame!!.installPath + } + + isEpic -> { + epicGame?.installPath ?: "" + } + + isCustom -> { + app.gameDir + } + + else -> { + try { + SteamService.getAppDirPath(app.id) + } catch (_: Exception) { + "" + } + } + } + } + + // Install size (computed async) + val installSizeText by produceState(initialValue = null, key1 = installPath) { + value = + if (installPath.isNotBlank()) { + withContext(Dispatchers.IO) { + try { + val bytes = StorageUtils.getFolderSize(installPath) + if (bytes > 0) StorageUtils.formatBinarySize(bytes) else null + } catch (_: Exception) { + null + } + } + } else { + null + } + } + + // Export / Import launchers (reuse GameSettingsDialog pattern) + + val exportLauncher = + rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument("application/zip")) { uri -> + if (uri != null) { + scope.launch(Dispatchers.IO) { + try { + val os = context.contentResolver.openOutputStream(uri) ?: return@launch + val zos = java.util.zip.ZipOutputStream(java.io.BufferedOutputStream(os)) + val containerManager = ContainerManager(context) + val shortcut = findLibraryShortcutForGame(containerManager, app, isCustom, isEpic, epicId) + val dirsToZip = mutableListOf() + val goldbergSaves = java.io.File(SteamService.getAppDirPath(app.id), "steam_settings/saves") + if (goldbergSaves.exists() && goldbergSaves.isDirectory) dirsToZip.add(goldbergSaves) + if (shortcut != null) { + val prefixDir = java.io.File(shortcut.container.getRootDir(), ".wine/drive_c/users/xuser") + listOf("Documents", "Saved Games", "AppData").forEach { name -> + val dir = java.io.File(prefixDir, name) + if (dir.exists()) dirsToZip.add(dir) + } + } + + fun zipDir( + dir: java.io.File, + baseName: String, + ) { + val children = dir.listFiles() ?: return + for (child in children) { + val name = if (baseName.isEmpty()) child.name else "$baseName/${child.name}" + if (child.isDirectory) { + zos.putNextEntry(java.util.zip.ZipEntry("$name/")) + zos.closeEntry() + zipDir(child, name) + } else { + zos.putNextEntry(java.util.zip.ZipEntry(name)) + child.inputStream().use { it.copyTo(zos) } + zos.closeEntry() + } + } + } + for (dir in dirsToZip) { + zos.putNextEntry(java.util.zip.ZipEntry("${dir.name}/")) + zos.closeEntry() + zipDir(dir, dir.name) + } + zos.close() + withContext(Dispatchers.Main) { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + R.string.saves_import_export_exported, + android.widget.Toast.LENGTH_SHORT, + ) + } + } catch (e: Exception) { + e.printStackTrace() + withContext(Dispatchers.Main) { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + getString(R.string.saves_import_export_exported_failed, e.message), + android.widget.Toast.LENGTH_SHORT, + ) + } + } + } + } + } + + val importLauncher = + rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) { uri -> + if (uri != null) { + scope.launch(Dispatchers.IO) { + try { + val inputStream = context.contentResolver.openInputStream(uri) ?: return@launch + val zis = java.util.zip.ZipInputStream(java.io.BufferedInputStream(inputStream)) + val containerManager = ContainerManager(context) + val shortcut = findLibraryShortcutForGame(containerManager, app, isCustom, isEpic, epicId) + val goldbergSavesParent = + java.io.File( + if (isEpic) app.gameDir else SteamService.getAppDirPath(app.id), + if (isEpic) "" else "steam_settings", + ) + val prefixDir = shortcut?.let { java.io.File(it.container.getRootDir(), ".wine/drive_c/users/xuser") } + var ze: java.util.zip.ZipEntry? + while (zis.nextEntry.also { ze = it } != null) { + val entry = ze!! + val name = entry.name + var destFile: java.io.File? = null + if (name.startsWith("saves/")) { + destFile = java.io.File(goldbergSavesParent, name) + } else if (prefixDir != null && + (name.startsWith("Documents/") || name.startsWith("Saved Games/") || name.startsWith("AppData/")) + ) { + destFile = java.io.File(prefixDir, name) + } + if (destFile != null) { + if (entry.isDirectory) { + destFile.mkdirs() + } else { + destFile.parentFile?.mkdirs() + java.io.FileOutputStream(destFile).use { fos -> zis.copyTo(fos) } + } + } + zis.closeEntry() + } + zis.close() + withContext(Dispatchers.Main) { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + R.string.saves_import_export_imported, + android.widget.Toast.LENGTH_SHORT, + ) + } + } catch (e: Exception) { + e.printStackTrace() + withContext(Dispatchers.Main) { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + getString(R.string.saves_import_export_imported_failed, e.message), + android.widget.Toast.LENGTH_SHORT, + ) + } + } + } + } + } + + val uninstallGame: () -> Unit = { + if (isGog) { + scope.launch(Dispatchers.IO) { + val result = GOGService.deleteGame( + context, + LibraryItem( + "GOG_${gogGame!!.id}", + gogGame.title, + com.winlator.cmod.feature.stores.steam.enums.GameSource.GOG, + ), + ) + withContext(Dispatchers.Main) { + if (result.isSuccess) { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + getString(R.string.library_games_game_uninstalled, app.name), + android.widget.Toast.LENGTH_SHORT, + ) + } else { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + getString( + R.string.library_games_failed_to_uninstall_reason, + result.exceptionOrNull()?.message ?: getString(R.string.common_ui_unknown_error), + ), + android.widget.Toast.LENGTH_LONG, + ) + } + onDismissRequest() + } + } + } else if (isCustom) { + scope.launch(Dispatchers.IO) { + val cm = ContainerManager(context) + val sc = findLibraryShortcutForGame(cm, app, isCustom, isEpic, epicId) + sc?.let { LibraryShortcutUtils.deleteShortcutArtifacts(context, it) } + java.io + .File( + context.filesDir, + "custom_icons/${app.name.replace("/", "_")}.png", + ).delete() + PluviaApp.events.emit(AndroidEvent.LibraryInstallStatusChanged(app.id)) + withContext(Dispatchers.Main) { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + getString(R.string.library_games_game_removed, app.name), + android.widget.Toast.LENGTH_SHORT, + ) + onDismissRequest() + } + } + } else if (isEpic) { + scope.launch(Dispatchers.IO) { + val result = EpicService.deleteGame(context, epicId) + withContext(Dispatchers.Main) { + if (result.isSuccess) { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + getString(R.string.library_games_game_uninstalled, app.name), + android.widget.Toast.LENGTH_SHORT, + ) + } else { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + getString( + R.string.library_games_failed_to_uninstall_reason, + result.exceptionOrNull()?.message ?: "", + ), + android.widget.Toast.LENGTH_LONG, + ) + } + onDismissRequest() + } + } + } else { + SteamService.uninstallApp(app.id) { success -> + if (success) { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + getString(R.string.library_games_game_uninstalled, app.name), + android.widget.Toast.LENGTH_SHORT, + ) + } else { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + getString(R.string.library_games_failed_to_uninstall), + android.widget.Toast.LENGTH_SHORT, + ) + } + onDismissRequest() + } + } + } + + Dialog( + onDismissRequest = onDismissRequest, + properties = + DialogProperties( + usePlatformDefaultWidth = false, + decorFitsSystemWindows = false, + ), + ) { + Surface( + modifier = Modifier.fillMaxSize(), + shape = RectangleShape, + color = Color.Black, + ) { + Box(Modifier.fillMaxSize()) { + Column(Modifier.fillMaxSize()) { + val showHero = currentScreen == LibraryDetailScreen.Main + val subScreenTitle = + when (currentScreen) { + LibraryDetailScreen.Shortcut -> stringResource(R.string.common_ui_shortcut) + LibraryDetailScreen.Uninstall -> + stringResource( + if (isCustom) R.string.common_ui_remove else R.string.common_ui_uninstall, + ) + else -> "" + } + // Sub-screens get a compact title bar. The main launch view owns the full + // screen and draws artwork edge-to-edge in its content branch. + if (!showHero) { + Row( + modifier = + Modifier + .fillMaxWidth() + .background(SurfaceDark) + .padding(horizontal = 8.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + IconButton(onClick = { currentScreen = LibraryDetailScreen.Main }) { + Icon( + Icons.AutoMirrored.Outlined.ArrowBack, + contentDescription = stringResource(R.string.common_ui_back), + tint = TextPrimary, + ) + } + Text( + subScreenTitle, + style = MaterialTheme.typography.titleMedium, + color = TextPrimary, + fontWeight = FontWeight.Bold, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.weight(1f).padding(start = 4.dp), + ) + Text( + app.name, + style = MaterialTheme.typography.bodySmall, + color = TextSecondary, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.padding(end = 16.dp), + ) + } + HorizontalDivider(color = CardBorder, thickness = 0.5.dp) + } + + // Bottom content + when (currentScreen) { + LibraryDetailScreen.Main -> { + // Lock Play while VERIFY / UPDATE is rewriting depots in place + // for this game — launching mid-write can corrupt the install. + val activePlayBlockingTask = + if (isCustom) { + null + } else if (isGog) { + val gogIdStr = gogGame!!.id + libraryDownloadRecords.firstOrNull { rec -> + rec.store == com.winlator.cmod.app.db.download + .DownloadRecord.STORE_GOG && + rec.storeGameId == gogIdStr && + rec.status == + com.winlator.cmod.app.db.download + .DownloadRecord.STATUS_DOWNLOADING && + ( + rec.taskType == + com.winlator.cmod.app.db.download + .DownloadRecord.TASK_VERIFY || + rec.taskType == + com.winlator.cmod.app.db.download + .DownloadRecord.TASK_UPDATE + ) + }?.taskType + } else if (isEpic) { + val appIdStr = epicId.toString() + libraryDownloadRecords.firstOrNull { rec -> + rec.store == com.winlator.cmod.app.db.download + .DownloadRecord.STORE_EPIC && + rec.storeGameId == appIdStr && + rec.status == + com.winlator.cmod.app.db.download + .DownloadRecord.STATUS_DOWNLOADING && + ( + rec.taskType == + com.winlator.cmod.app.db.download + .DownloadRecord.TASK_VERIFY || + rec.taskType == + com.winlator.cmod.app.db.download + .DownloadRecord.TASK_UPDATE + ) + }?.taskType + } else { + val appIdStr = app.id.toString() + libraryDownloadRecords.firstOrNull { rec -> + rec.store == com.winlator.cmod.app.db.download + .DownloadRecord.STORE_STEAM && + rec.storeGameId == appIdStr && + rec.status == + com.winlator.cmod.app.db.download + .DownloadRecord.STATUS_DOWNLOADING && + ( + rec.taskType == + com.winlator.cmod.app.db.download + .DownloadRecord.TASK_VERIFY || + rec.taskType == + com.winlator.cmod.app.db.download + .DownloadRecord.TASK_UPDATE + ) + }?.taskType + } + val playEnabled = activePlayBlockingTask == null + val playDisabledLabel = + when (activePlayBlockingTask) { + com.winlator.cmod.app.db.download.DownloadRecord.TASK_VERIFY -> + stringResource(R.string.downloads_queue_phase_verifying) + com.winlator.cmod.app.db.download.DownloadRecord.TASK_UPDATE -> + stringResource(R.string.downloads_queue_phase_updating) + else -> null + } + val launchAppName = + homeShortcutState.shortcut + ?.getExtra("custom_name", "") + ?.takeIf { it.isNotBlank() } + ?: when { + isEpic -> epicGame?.title?.takeIf { it.isNotBlank() } ?: app.name + isGog -> gogGame?.title?.takeIf { it.isNotBlank() } ?: app.name + else -> app.name + } + val heroToastAnchor = LocalView.current + var heroPopup by remember { mutableStateOf(null) } + var bootShortcut by remember { mutableStateOf(null) } + val resolveOrCreateShortcut: () -> com.winlator.cmod.runtime.container.Shortcut? = { + val containerManager = ContainerManager(context) + when { + isGog -> + containerManager.loadShortcuts().find { + it.getExtra("game_source") == "GOG" && + it.getExtra("gog_id") == gogGame!!.id + } ?: ShortcutSettingsComposeDialog.createLibraryShortcut( + context = context, + containerManager = containerManager, + source = "GOG", + appId = gogPseudoId(gogGame!!.id), + gogId = gogGame.id, + appName = app.name, + ) + isCustom -> findLibraryShortcutForGame(containerManager, app, isCustom, isEpic, epicId) + else -> + findLibraryShortcutForGame(containerManager, app, isCustom, isEpic, epicId) + ?: ShortcutSettingsComposeDialog.createLibraryShortcut( + context = context, + containerManager = containerManager, + source = if (isEpic) "EPIC" else "STEAM", + appId = if (isEpic) epicId else app.id, + gogId = null, + appName = app.name, + ) + } + } + var showSaveTransfer by remember(app.id) { mutableStateOf(false) } + val retroSaveImportLauncher = + rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) { uri -> + if (uri != null) { + val sourceName = + context.contentResolver.query(uri, arrayOf(android.provider.OpenableColumns.DISPLAY_NAME), null, null, null)?.use { + if (it.moveToFirst()) it.getString(0) else null + } ?: "save" + val result = + runCatching { + val bytes = + context.contentResolver.openInputStream(uri)?.use { it.readBytes() } + ?: return@runCatching com.winlator.cmod.feature.retro.RetroSaveImport.Result.Invalid("Could not read the file.") + com.winlator.cmod.feature.retro.RetroSaveImport.import(context, app.name, sourceName, bytes) + }.getOrElse { com.winlator.cmod.feature.retro.RetroSaveImport.Result.Invalid("Could not read the file.") } + val message = + when (result) { + is com.winlator.cmod.feature.retro.RetroSaveImport.Result.Success -> + "Imported save (${result.name}, ${result.bytes / 1024} KB)" + is com.winlator.cmod.feature.retro.RetroSaveImport.Result.Invalid -> + "Import failed: ${result.reason}" + } + android.widget.Toast.makeText(context, message, android.widget.Toast.LENGTH_LONG).show() + } + } + val retroSaveExportLauncher = + rememberLauncherForActivityResult( + ActivityResultContracts.CreateDocument("application/octet-stream"), + ) { uri -> + if (uri != null) { + val ok = + runCatching { + val sram = + com.winlator.cmod.feature.retro.RetroSaveStates.sramFile(context, app.name) + if (!sram.isFile) return@runCatching false + context.contentResolver.openOutputStream(uri)?.use { it.write(sram.readBytes()) } + true + }.getOrDefault(false) + android.widget.Toast.makeText( + context, + context.getString( + if (ok) R.string.retro_save_transfer_export_ok else R.string.retro_save_transfer_export_failed, + ), + android.widget.Toast.LENGTH_SHORT, + ).show() + } + } + if (showSaveTransfer) { + androidx.compose.material3.AlertDialog( + onDismissRequest = { showSaveTransfer = false }, + title = { androidx.compose.material3.Text(stringResource(R.string.retro_save_transfer_title)) }, + text = { androidx.compose.material3.Text(stringResource(R.string.retro_save_transfer_message)) }, + confirmButton = { + androidx.compose.material3.TextButton(onClick = { + showSaveTransfer = false + retroSaveImportLauncher.launch(arrayOf("*/*")) + }) { androidx.compose.material3.Text(stringResource(R.string.retro_save_transfer_import)) } + }, + dismissButton = { + androidx.compose.material3.TextButton(onClick = { + val sram = com.winlator.cmod.feature.retro.RetroSaveStates.sramFile(context, app.name) + showSaveTransfer = false + if (!sram.isFile) { + android.widget.Toast.makeText( + context, + context.getString(R.string.retro_save_transfer_none), + android.widget.Toast.LENGTH_SHORT, + ).show() + } else { + retroSaveExportLauncher.launch("${app.name}.srm") + } + }) { androidx.compose.material3.Text(stringResource(R.string.retro_save_transfer_export)) } + }, + ) + } + // The 3D engine offers itself only for the few Gen 1 + // titles it supports, and only when its files are + // actually in the retro bundle. Compatibility is the + // ROM's SHA-1, so deciding it means hashing a file: + // done once off the composition thread, keyed on the + // shortcut, rather than on every recomposition. + val engine3dShortcut = homeShortcutState.shortcut + val engine3dKey = engine3dShortcut?.file?.absolutePath + var engine3dSupported by remember(engine3dKey) { mutableStateOf(false) } + var engine3dOn by remember(engine3dKey) { + mutableStateOf( + engine3dShortcut?.let { + com.winlator.cmod.feature.retro.Gen1EmbedLaunch.isEnabled(it) + } ?: false, + ) + } + LaunchedEffect(engine3dKey) { + engine3dSupported = + engine3dShortcut != null && + withContext(Dispatchers.IO) { + runCatching { + com.winlator.cmod.feature.retro.Gen1EmbedLaunch + .isCompatible(context, engine3dShortcut) + }.getOrDefault(false) + } + } + LibraryGameLaunchScreen( + appName = launchAppName, + subtitle = subtitle, + sourceLabel = sourceLabel, + heroImageUrl = heroImageUrl, + customHeroImageCacheKey = customHeroImageCacheKey, + releaseDateEpochSeconds = app.releaseDate, + totalPlaytimeMillis = totalPlaytime, + playCount = playCount, + lastPlayedMillis = lastPlayed, + installSizeText = installSizeText, + isCustom = isCustom, + isRetro = isRetro, + showBootToDesktop = retroCaps.showBootToDesktop, + showSaveTransfer = retroCaps.showSaveTransfer, + hasPinnedShortcut = hasPinnedShortcut, + playEnabled = playEnabled, + playDisabledLabel = playDisabledLabel, + altEngineLabel = + if (engine3dSupported) stringResource(R.string.retro_gs_engine_3d) else null, + altEngineEnabled = engine3dOn, + onAltEngineChange = + if (engine3dSupported && engine3dShortcut != null) { + { on -> + engine3dOn = on + // Written straight to the shortcut, so + // Play uses it immediately and the + // Graphics pane agrees with it. + engine3dShortcut.putExtra( + com.winlator.cmod.feature.retro.Gen1EmbedLaunch.KEY_ENGINE_3D, + if (on) "1" else "0", + ) + engine3dShortcut.saveData() + } + } else { + null + }, + onBack = onDismissRequest, + onPlay = { + val containerManager = ContainerManager(context) + if (isCustom) { + launchCustomGame(context, containerManager, app.name) + } else if (isGog) { + launchGogGame(context, containerManager, gogGame!!) + } else if (isEpic) { + epicGame?.let { launchEpicGame(context, containerManager, it) } + } else { + launchSteamGame(context, containerManager, app) + } + onDismissRequest() + }, + onSettings = { + val shortcut = resolveOrCreateShortcut() + if (shortcut != null) { + ShortcutSettingsComposeDialog(this@LibraryGameDetailDialog, shortcut).show() + } + }, + onBootToDesktop = { + val shortcut = resolveOrCreateShortcut() + if (shortcut != null) { + bootShortcut = shortcut + heroPopup = HeroLaunchPopup.BootToDesktop + } else { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + R.string.shortcuts_list_not_available, + heroToastAnchor, + ) + } + }, + onAchievements = + when { + isRetro -> { + val sysId = retroSystemId + val rom = retroRomPath + if (sysId != null && rom != null && retroCaps.showAchievements) { + { + context.startActivity( + android.content.Intent( + context, + com.winlator.cmod.feature.retro.RetroAchievementsActivity::class.java, + ).apply { + putExtra( + com.winlator.cmod.feature.retro.RetroAchievementsActivity.EXTRA_SYSTEM_ID, + sysId, + ) + putExtra( + com.winlator.cmod.feature.retro.RetroAchievementsActivity.EXTRA_ROM_PATH, + rom, + ) + putExtra( + com.winlator.cmod.feature.retro.RetroAchievementsActivity.EXTRA_GAME_NAME, + app.name, + ) + putExtra( + com.winlator.cmod.feature.retro.RetroAchievementsActivity.EXTRA_IN_SESSION, + false, + ) + }, + ) + } + } else { + null + } + } + !isCustom && !isEpic && !isGog -> { + { showAchievements = true } + } + else -> null + }, + onShortcut = { + if (hasPinnedShortcut) { + heroPopup = HeroLaunchPopup.RemoveShortcut + } else { + scope.launch { + val created = + withContext(Dispatchers.IO) { + if (isGog) { + val artworkUrl = gogGame!!.imageUrl.ifEmpty { gogGame.iconUrl } + addGogShortcutToHomeScreen(context, gogGame, artworkUrl) + } else { + addLibraryShortcutToHomeScreen( + context, + app, + isCustom, + isEpic, + epicId, + epicArtworkUrl, + ) + } + } + if (!created) { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + context.getString( + R.string.library_games_failed_to_create_shortcut, + app.name, + ), + ) + } + } + } + }, + onCloudSaves = { activePopup = LibraryDetailPopup.CloudSaves }, + onSaveTransfer = + if (retroCaps.showSaveTransfer) { + { showSaveTransfer = true } + } else { + null + }, + onCheats = + if (retroCaps.showCheats && retroSystemId != null) { + { + context.startActivity( + android.content.Intent( + context, + com.winlator.cmod.feature.retro.RetroCheatsActivity::class.java, + ).apply { + putExtra( + com.winlator.cmod.feature.retro.RetroCheatsActivity.EXTRA_SYSTEM_ID, + retroSystemId, + ) + putExtra( + com.winlator.cmod.feature.retro.RetroCheatsActivity.EXTRA_GAME_NAME, + app.name, + ) + }, + ) + } + } else { + null + }, + cheatsEnabled = + !( + com.winlator.cmod.feature.retro.RetroAchievementsManager.isEnabled(context) && + com.winlator.cmod.feature.retro.RetroAchievementsManager.isLoggedIn(context) && + com.winlator.cmod.feature.retro.RetroAchievementsManager.isHardcorePreferred(context) + ), + onUninstall = uninstallGame, + steamMenuEnabled = !isCustom && + (!isEpic || epicGame?.isInstalled == true) && + (!isGog || gogGame?.isInstalled == true), + showVerifyFiles = !isCustom && + (!isEpic || epicGame?.isInstalled == true) && + (!isGog || gogGame?.isInstalled == true), + showCheckForUpdate = !isCustom && + (!isEpic || epicGame?.isInstalled == true) && + (!isGog || gogGame?.isInstalled == true), + showWorkshop = !isCustom && !isEpic && !isGog, + areSteamActionsEnabled = + when { + isEpic -> !hasBlockingEpicDownloadForLibrary + isGog -> !hasBlockingGogDownloadForLibrary + else -> !hasBlockingSteamDownloadForLibrary + }, + onVerifyFiles = { + context.runIfOnlineOrToast { + scope.launch { + val started = + withContext(Dispatchers.IO) { + when { + isEpic -> EpicService.verifyGameFiles(context, epicId) + isGog -> GOGService.verifyGameFiles(context, gogGame!!.id) + else -> SteamService.downloadAppForVerify(app.id) + } + } + if (started != null) { + showTaskProgressPopup( + started, + if (isGog) gogGame!!.title else app.name, + getString(R.string.store_game_verify_complete), + getString(R.string.store_game_verify_failed_notice), + completeAsToast = true, + ) + } + if (started == null) { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + getString(R.string.store_game_download_already_active), + android.widget.Toast.LENGTH_SHORT, + ) + } + } + } + }, + onCheckForUpdate = { + when { + isEpic -> startEpicUpdateCheck(epicId, app.name) + isGog -> startGogUpdateCheck(gogGame!!.id, gogGame.title) + else -> startUpdateCheck(app.id, app.name) + } + }, + onWorkshop = { if (!isEpic && !isGog && !isCustom) showWorkshopDialog = true }, + ) + + when (heroPopup) { + HeroLaunchPopup.BootToDesktop -> + HeroBootDialog( + onConfirm = { choice -> + heroPopup = null + bootShortcut?.let { sc -> + val intent = + Intent(context, XServerDisplayActivity::class.java) + .putExtra("container_id", sc.container.id) + when (choice) { + HeroBootChoice.Desktop -> {} + HeroBootChoice.Cube32 -> + intent + .putExtra("shortcut_path", sc.file.absolutePath) + .putExtra("boot_exe", "C:\\ProgramData\\Microsoft\\Windows\\Graphics-Test-32bit.exe") + HeroBootChoice.Cube64 -> + intent + .putExtra("shortcut_path", sc.file.absolutePath) + .putExtra("boot_exe", "C:\\ProgramData\\Microsoft\\Windows\\Graphics-Test-64bit.exe") + HeroBootChoice.Input32 -> + intent + .putExtra("shortcut_path", sc.file.absolutePath) + .putExtra("boot_exe", "C:\\ProgramData\\Microsoft\\Windows\\InputControl32.exe") + HeroBootChoice.Input64 -> + intent + .putExtra("shortcut_path", sc.file.absolutePath) + .putExtra("boot_exe", "C:\\ProgramData\\Microsoft\\Windows\\InputControl64.exe") + } + context.startActivity(intent) + onDismissRequest() + } + }, + onDismissRequest = { heroPopup = null }, + ) + HeroLaunchPopup.RemoveShortcut -> + HeroRemoveShortcutDialog( + gameName = if (isGog) gogGame!!.title else app.name, + onConfirm = { + scope.launch { + val removed = + withContext(Dispatchers.IO) { + homeShortcutState.shortcut?.let { + LibraryShortcutUtils.disablePinnedHomeShortcut(context, it) + } == true + } + pinnedShortcutOverride = if (removed) false else hasPinnedShortcut + shortcutRefreshKey++ + heroPopup = null + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + if (removed) { + context.getString(R.string.shortcuts_list_removed) + } else { + context.getString(R.string.common_ui_unknown_error) + }, + ) + } + }, + onDismissRequest = { heroPopup = null }, + ) + null -> {} + } + } + + LibraryDetailScreen.Shortcut -> { + Column( + modifier = + Modifier + .fillMaxSize() + .padding(horizontal = 24.dp, vertical = 20.dp), + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + Text( + stringResource(R.string.common_ui_shortcut), + style = MaterialTheme.typography.labelMedium, + color = TextSecondary, + fontWeight = FontWeight.Bold, + letterSpacing = 1.1.sp, + ) + + Spacer(Modifier.weight(1f)) + + ShortcutRemovalConfirmation( + message = + stringResource( + R.string.shortcuts_list_remove_game_shortcut_message, + if (isGog) gogGame!!.title else app.name, + ), + onConfirm = { + scope.launch { + val removed = + withContext(Dispatchers.IO) { + homeShortcutState.shortcut?.let { + LibraryShortcutUtils.disablePinnedHomeShortcut(context, it) + } == true + } + pinnedShortcutOverride = if (removed) false else hasPinnedShortcut + shortcutRefreshKey++ + currentScreen = LibraryDetailScreen.Main + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + if (removed) { + context.getString(R.string.shortcuts_list_removed) + } else { + context.getString(R.string.common_ui_unknown_error) + }, + ) + } + }, + onCancel = { currentScreen = LibraryDetailScreen.Main }, + ) + } + } + + LibraryDetailScreen.CloudSaves -> { + Column( + modifier = + Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()) + .navigationBarsPadding(), + ) { + var isWorking by remember { mutableStateOf(false) } + + val detailGameSource = + when { + isGog -> GameSaveBackupManager.GameSource.GOG + isEpic -> GameSaveBackupManager.GameSource.EPIC + else -> GameSaveBackupManager.GameSource.STEAM + } + val detailGameId = + when { + isGog -> gogGame!!.id + isEpic -> epicId.toString() + else -> app.id.toString() + } + val detailShortcut = + remember(app.id, gogGame?.id, epicId, isGog, isEpic, isCustom) { + val containerManager = ContainerManager(context) + when { + isGog -> { + containerManager.loadShortcuts().find { + it.getExtra("game_source") == "GOG" && it.getExtra("gog_id") == gogGame!!.id + } + } + + else -> { + findLibraryShortcutForGame(containerManager, app, isCustom, isEpic, epicId) + } + } + } + var cloudSyncEnabled by remember(detailShortcut?.file?.absolutePath) { + mutableStateOf(isShortcutCloudSyncEnabled(detailShortcut)) + } + var offlineModeEnabled by remember(detailShortcut?.file?.absolutePath) { + mutableStateOf(isShortcutOfflineMode(detailShortcut)) + } + + val detailProviderLabel = + when (detailGameSource) { + GameSaveBackupManager.GameSource.GOG -> + stringResource(R.string.preloader_platform_gog) + GameSaveBackupManager.GameSource.EPIC -> + stringResource(R.string.preloader_platform_epic) + GameSaveBackupManager.GameSource.CUSTOM -> + stringResource(R.string.preloader_platform_custom) + GameSaveBackupManager.GameSource.STEAM -> + stringResource(R.string.preloader_platform_steam) + } + + CloudSavesContent( + activity = this@LibraryGameDetailDialog, + isWorking = isWorking, + cloudSyncEnabled = cloudSyncEnabled, + offlineModeEnabled = offlineModeEnabled, + gameSource = detailGameSource, + gameId = detailGameId, + gameName = app.name, + shortcut = detailShortcut, + retroSaveDir = com.winlator.cmod.feature.sync.google.GameSaveBackupManager.retroSaveDir(context, detailShortcut, detailGameId), + onCloudSyncToggle = { enabled -> + cloudSyncEnabled = enabled + setShortcutCloudSyncEnabled(detailShortcut, enabled) + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + if (enabled) { + context.getString(R.string.cloud_sync_enabled_summary) + } else { + context.getString(R.string.cloud_sync_disabled_summary) + }, + android.widget.Toast.LENGTH_SHORT, + ) + }, + onOfflineModeToggle = { enabled -> + offlineModeEnabled = enabled + setShortcutOfflineMode(detailShortcut, enabled) + }, + onSyncFromCloud = { + if (!isWorking) { + isWorking = true + scope.launch(Dispatchers.IO) { + val ok = + CloudSyncHelper.downloadCloudSaves( + context, + detailGameSource, + detailGameId, + detailShortcut, + ) + withContext(Dispatchers.Main) { + isWorking = false + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + if (ok) { + context.getString( + R.string.cloud_saves_sync_from_provider_success, + detailProviderLabel, + ) + } else { + context.getString( + R.string.cloud_saves_sync_from_provider_failed, + detailProviderLabel, + ) + }, + android.widget.Toast.LENGTH_SHORT, + ) + } + } + } + }, + showBottomBack = false, + onBack = { currentScreen = LibraryDetailScreen.Main }, + ) + } + } + + LibraryDetailScreen.Uninstall -> { + Column( + modifier = + Modifier + .fillMaxSize() + .padding(horizontal = 24.dp, vertical = 20.dp), + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + Text( + stringResource( + if (isCustom) R.string.library_games_remove_game else R.string.library_games_uninstall_game, + ), + style = MaterialTheme.typography.labelMedium, + color = TextSecondary, + fontWeight = FontWeight.Bold, + letterSpacing = 1.1.sp, + ) + + Spacer(Modifier.weight(1f)) + + UninstallConfirmation( + message = + if (isCustom) { + getString(R.string.library_games_remove_confirm, app.name) + } else { + getString(R.string.library_games_uninstall_confirm, app.name) + }, + confirmLabel = + stringResource( + if (isCustom) R.string.common_ui_remove else R.string.common_ui_uninstall, + ), + onConfirm = uninstallGame, + onCancel = { currentScreen = LibraryDetailScreen.Main }, + ) + } + } + } + } + + if (showAchievements) { + Dialog( + onDismissRequest = { showAchievements = false }, + properties = DialogProperties( + usePlatformDefaultWidth = false, + dismissOnClickOutside = false, + decorFitsSystemWindows = false, + ), + ) { + com.winlator.cmod.feature.stores.steam.achievements.SteamAchievementsScreen( + appId = app.id, + appName = app.name, + onClose = { showAchievements = false }, + ) + } + } + + activePopup?.let { popup -> + LibraryDetailPopupFrame( + title = + when (popup) { + LibraryDetailPopup.CloudSaves -> + stringResource( + R.string.cloud_saves_title_for_provider, + when { + isGog -> stringResource(R.string.preloader_platform_gog) + isEpic -> stringResource(R.string.preloader_platform_epic) + isCustom -> stringResource(R.string.preloader_platform_custom) + else -> stringResource(R.string.preloader_platform_steam) + }, + app.name, + ) + }, + wide = popup == LibraryDetailPopup.CloudSaves, + onDismissRequest = { activePopup = null }, + ) { + when (popup) { + LibraryDetailPopup.CloudSaves -> { + var isWorking by remember { mutableStateOf(false) } + + val detailGameSource = + when { + isGog -> GameSaveBackupManager.GameSource.GOG + isEpic -> GameSaveBackupManager.GameSource.EPIC + isCustom -> GameSaveBackupManager.GameSource.CUSTOM + else -> GameSaveBackupManager.GameSource.STEAM + } + val detailShortcut = + remember(app.id, gogGame?.id, epicId, isGog, isEpic, isCustom) { + val containerManager = ContainerManager(context) + when { + isGog -> { + containerManager.loadShortcuts().find { + it.getExtra("game_source") == "GOG" && it.getExtra("gog_id") == gogGame!!.id + } + } + + else -> { + findLibraryShortcutForGame(containerManager, app, isCustom, isEpic, epicId) + } + } + } + val detailGameId = + when { + isGog -> gogGame!!.id + isEpic -> epicId.toString() + isCustom -> + detailShortcut?.let { GameSaveBackupManager.customGameId(it) } + ?: app.name + else -> app.id.toString() + } + var cloudSyncEnabled by remember(detailShortcut?.file?.absolutePath) { + mutableStateOf(isShortcutCloudSyncEnabled(detailShortcut)) + } + var offlineModeEnabled by remember(detailShortcut?.file?.absolutePath) { + mutableStateOf(isShortcutOfflineMode(detailShortcut)) + } + + val detailProviderLabel = + when (detailGameSource) { + GameSaveBackupManager.GameSource.GOG -> + stringResource(R.string.preloader_platform_gog) + GameSaveBackupManager.GameSource.EPIC -> + stringResource(R.string.preloader_platform_epic) + GameSaveBackupManager.GameSource.CUSTOM -> + stringResource(R.string.preloader_platform_custom) + GameSaveBackupManager.GameSource.STEAM -> + stringResource(R.string.preloader_platform_steam) + } + + CloudSavesContent( + activity = this@LibraryGameDetailDialog, + isWorking = isWorking, + cloudSyncEnabled = cloudSyncEnabled, + offlineModeEnabled = offlineModeEnabled, + gameSource = detailGameSource, + gameId = detailGameId, + gameName = app.name, + shortcut = detailShortcut, + retroSaveDir = com.winlator.cmod.feature.sync.google.GameSaveBackupManager.retroSaveDir(context, detailShortcut, detailGameId), + onCloudSyncToggle = { enabled -> + cloudSyncEnabled = enabled + setShortcutCloudSyncEnabled(detailShortcut, enabled) + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + if (enabled) { + context.getString(R.string.cloud_sync_enabled_summary) + } else { + context.getString(R.string.cloud_sync_disabled_summary) + }, + android.widget.Toast.LENGTH_SHORT, + ) + }, + onOfflineModeToggle = { enabled -> + offlineModeEnabled = enabled + setShortcutOfflineMode(detailShortcut, enabled) + }, + onSyncFromCloud = { + if (!isWorking) { + isWorking = true + scope.launch(Dispatchers.IO) { + val ok = + CloudSyncHelper.downloadCloudSaves( + context, + detailGameSource, + detailGameId, + detailShortcut, + ) + withContext(Dispatchers.Main) { + isWorking = false + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + if (ok) { + context.getString( + R.string.cloud_saves_sync_from_provider_success, + detailProviderLabel, + ) + } else { + context.getString( + R.string.cloud_saves_sync_from_provider_failed, + detailProviderLabel, + ) + }, + android.widget.Toast.LENGTH_SHORT, + ) + } + } + } + }, + showTitle = false, + showBottomBack = false, + onBack = { activePopup = null }, + ) + } + } + } + } + + if ( + currentScreen != LibraryDetailScreen.Main && + currentScreen != LibraryDetailScreen.CloudSaves + ) { + // Close button overlay + IconButton( + onClick = onDismissRequest, + modifier = + Modifier + .align(Alignment.TopEnd) + .padding(16.dp) + .size(42.dp) + .shadow(8.dp, CircleShape, spotColor = Color.Black.copy(alpha = 0.35f)) + .clip(CircleShape) + .background(BgDark.copy(alpha = 0.7f)), + ) { + Icon(Icons.Outlined.Close, contentDescription = "Close", tint = TextPrimary) + } + } + } + + if (showWorkshopDialog) { + WorkshopDialog( + appId = app.id, + gameTitle = app.name, + onDismissRequest = { showWorkshopDialog = false }, + ) + } + } + } +} diff --git a/app/src/main/app/shell/UnifiedActivityHub.kt b/app/src/main/app/shell/UnifiedActivityHub.kt new file mode 100644 index 000000000..6f3744af4 --- /dev/null +++ b/app/src/main/app/shell/UnifiedActivityHub.kt @@ -0,0 +1,2644 @@ +package com.winlator.cmod.app.shell +import com.winlator.cmod.app.shell.UnifiedActivity.TabDef + +import android.app.Activity +import android.app.PendingIntent +import android.content.Intent +import android.content.res.Configuration +import android.hardware.input.InputManager +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.drawable.BitmapDrawable +import android.net.Uri +import android.os.Bundle +import android.provider.DocumentsContract +import android.util.Log +import androidx.activity.SystemBarStyle +import androidx.activity.compose.BackHandler +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AppCompatActivity +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.EnterTransition +import androidx.compose.animation.ExitTransition +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.FastOutSlowInEasing +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.spring +import androidx.compose.animation.core.tween +import androidx.compose.animation.expandVertically +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.shrinkVertically +import androidx.compose.animation.slideInHorizontally +import androidx.compose.animation.slideOutHorizontally +import androidx.compose.animation.togetherWith +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background +import androidx.compose.foundation.basicMarquee +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.focusable +import androidx.compose.foundation.gestures.detectHorizontalDragGestures +import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsPressedAsState +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items +import androidx.compose.foundation.lazy.grid.itemsIndexed +import androidx.compose.foundation.lazy.grid.rememberLazyGridState +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.outlined.ArrowBack +import androidx.compose.material.icons.automirrored.outlined.ExitToApp +import androidx.compose.material.icons.automirrored.outlined.OpenInNew +import androidx.compose.material.icons.outlined.* +import androidx.compose.material3.* +import androidx.compose.material3.pulltorefresh.PullToRefreshBox +import androidx.compose.runtime.* +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.snapshotFlow +import androidx.compose.runtime.snapshots.SnapshotStateMap +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.zIndex +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusProperties +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.focus.focusTarget +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.geometry.CornerRadius +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.TileMode +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.input.pointer.PointerEventPass +import androidx.compose.ui.input.pointer.PointerEventType +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties +import androidx.lifecycle.lifecycleScope +import androidx.navigation.NavHostController +import androidx.navigation.NavType +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import androidx.navigation.navArgument +import coil.compose.AsyncImage +import coil.imageLoader +import coil.request.ImageRequest +import com.winlator.cmod.BuildConfig +import com.winlator.cmod.R +import com.winlator.cmod.app.PluviaApp +import com.winlator.cmod.app.db.PluviaDatabase +import com.winlator.cmod.app.service.DownloadService +import com.winlator.cmod.app.service.download.DownloadCoordinator +import com.winlator.cmod.app.update.UpdateChecker +import com.winlator.cmod.feature.settings.InputControlsFragment +import com.winlator.cmod.feature.settings.SettingsFocusZone +import com.winlator.cmod.feature.settings.SettingsHost +import com.winlator.cmod.feature.settings.SettingsNavBridge +import com.winlator.cmod.feature.settings.SettingsNavItem +import com.winlator.cmod.feature.setup.SetupWizardActivity +import com.winlator.cmod.feature.shortcuts.LibraryShortcutUtils +import com.winlator.cmod.feature.shortcuts.LibraryShortcutArtwork +import com.winlator.cmod.feature.shortcuts.ShortcutBroadcastReceiver +import com.winlator.cmod.feature.shortcuts.ShortcutSettingsComposeDialog +import com.winlator.cmod.feature.shortcuts.ShortcutsFragment +import com.winlator.cmod.feature.stores.common.StoreArtworkCache +import com.winlator.cmod.feature.stores.epic.data.EpicCredentials +import com.winlator.cmod.feature.stores.epic.data.EpicGame +import com.winlator.cmod.feature.stores.epic.data.EpicGameToken +import com.winlator.cmod.feature.stores.epic.service.EpicAuthManager +import com.winlator.cmod.feature.stores.epic.service.EpicCloudSavesManager +import com.winlator.cmod.feature.stores.epic.service.EpicConstants +import com.winlator.cmod.feature.stores.epic.service.EpicDownloadManager +import com.winlator.cmod.feature.stores.epic.service.EpicGameLauncher +import com.winlator.cmod.feature.stores.epic.service.EpicManager +import com.winlator.cmod.feature.stores.epic.service.EpicService +import com.winlator.cmod.feature.stores.epic.service.EpicUpdateInfo +import com.winlator.cmod.feature.stores.epic.ui.auth.EpicOAuthActivity +import com.winlator.cmod.feature.stores.gog.data.GOGDlcInfo +import com.winlator.cmod.feature.stores.gog.data.GOGGame +import com.winlator.cmod.feature.stores.gog.data.LibraryItem +import com.winlator.cmod.feature.stores.gog.service.GOGAuthManager +import com.winlator.cmod.feature.stores.gog.service.GOGConstants +import com.winlator.cmod.feature.stores.gog.service.GOGManifestSizes +import com.winlator.cmod.feature.stores.gog.service.GOGService +import com.winlator.cmod.feature.stores.gog.service.GOGUpdateInfo +import com.winlator.cmod.feature.stores.gog.ui.auth.GOGOAuthActivity +import com.winlator.cmod.feature.stores.steam.SteamLoginActivity +import com.winlator.cmod.feature.stores.steam.data.DepotInfo +import com.winlator.cmod.feature.stores.steam.data.DownloadInfo +import com.winlator.cmod.feature.stores.steam.data.SteamApp +import com.winlator.cmod.feature.stores.steam.enums.DownloadPhase +import com.winlator.cmod.feature.stores.steam.events.AndroidEvent +import com.winlator.cmod.feature.stores.steam.events.EventDispatcher +import com.winlator.cmod.feature.stores.steam.service.SteamService +import com.winlator.cmod.feature.stores.steam.utils.PrefManager +import com.winlator.cmod.feature.stores.steam.utils.getAvatarURL +import com.winlator.cmod.feature.sync.CloudSyncHelper +import com.winlator.cmod.feature.sync.google.CloudSyncManager +import com.winlator.cmod.feature.sync.google.GameSaveBackupManager +import com.winlator.cmod.feature.sync.ui.CloudSavesContent +import com.winlator.cmod.runtime.container.ContainerManager +import com.winlator.cmod.runtime.container.Shortcut +import com.winlator.cmod.runtime.display.XServerDisplayActivity +import com.winlator.cmod.runtime.display.environment.ImageFs +import com.winlator.cmod.runtime.input.ControllerHelper +import com.winlator.cmod.runtime.wine.PeIconExtractor +import com.winlator.cmod.shared.android.ActivityResultHost +import com.winlator.cmod.shared.android.AppTerminationHelper +import com.winlator.cmod.shared.android.DirectoryPickerDialog +import com.winlator.cmod.shared.android.FixedFontScaleAppCompatActivity +import com.winlator.cmod.shared.android.RefreshRateUtils +import com.winlator.cmod.shared.io.StorageUtils +import com.winlator.cmod.shared.io.FileUtils +import com.winlator.cmod.shared.ui.CarouselView +import com.winlator.cmod.shared.ui.dialog.PopupDialog +import com.winlator.cmod.shared.ui.dialog.PopupTextAction +import androidx.compose.foundation.focusGroup +import com.winlator.cmod.shared.ui.focus.controllerFocusGlow +import com.winlator.cmod.shared.ui.focus.controllerMenuInput +import com.winlator.cmod.shared.ui.focus.controllerTextFieldEscape +import com.winlator.cmod.shared.ui.nav.DialogPaneNav +import com.winlator.cmod.shared.ui.nav.LocalPaneNav +import com.winlator.cmod.shared.ui.nav.PANE_DIR_ACTIVATE +import com.winlator.cmod.shared.ui.nav.PANE_DIR_DOWN +import com.winlator.cmod.shared.ui.nav.PANE_DIR_LEFT +import com.winlator.cmod.shared.ui.nav.PANE_DIR_RIGHT +import com.winlator.cmod.shared.ui.nav.PANE_DIR_SECONDARY +import com.winlator.cmod.shared.ui.nav.PANE_DIR_UP +import com.winlator.cmod.shared.ui.nav.PaneNavRegistry +import com.winlator.cmod.shared.ui.nav.paneNavItem +import com.winlator.cmod.shared.ui.FourByTwoGridView +import com.winlator.cmod.shared.ui.JoystickGridScroll +import com.winlator.cmod.shared.ui.JoystickListScroll +import com.winlator.cmod.shared.ui.ListView +import com.winlator.cmod.shared.ui.widget.chasingBorder +import com.winlator.cmod.shared.theme.WinNativeTheme +import dagger.hilt.android.AndroidEntryPoint +import dagger.Lazy +import com.winlator.cmod.feature.stores.steam.enums.EPersonaState +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import javax.inject.Inject +import kotlin.math.abs +import kotlin.math.roundToInt + +// Main hub scaffold + top bar + glasses sheet + library carousel, split out of UnifiedActivity.kt (behavior-identical). + +@Composable +internal fun UnifiedActivity.UnifiedHub() { + val horizontalNavigationInsets = + WindowInsets.navigationBars.only(WindowInsetsSides.Horizontal) + val initialLibraryLayoutMode = startupLibraryLayoutMode + val initialStoreVisible = startupStoreVisible ?: mapOf("steam" to true, "epic" to true, "gog" to true) + val initialContentFilters = startupContentFilters ?: mapOf("games" to true, "dlc" to false, "applications" to false, "tools" to false) + if (!startupBootstrapReady || initialLibraryLayoutMode == null) { + Box( + modifier = + Modifier + .fillMaxSize() + .background(BgDark) + .windowInsetsPadding(horizontalNavigationInsets), + contentAlignment = Alignment.Center, + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + CircularProgressIndicator(color = Accent) + Text( + text = stringResource(R.string.common_ui_app_name), + color = TextPrimary, + style = MaterialTheme.typography.titleMedium, + ) + } + } + return + } + + val storeVisible = remember { mutableStateMapOf(*initialStoreVisible.entries.map { it.key to it.value }.toTypedArray()) } + var showAddCustomGame by remember { mutableStateOf(false) } + var showExitDialog by remember { mutableStateOf(false) } + var searchQueryTfv by remember { mutableStateOf(TextFieldValue("")) } + val searchQuery = searchQueryTfv.text + var localLibraryRefreshKey by remember { mutableIntStateOf(0) } + var shortcutDataRefreshKey by remember { mutableIntStateOf(0) } + var iconRefreshKey by remember { mutableIntStateOf(0) } + + val currentRefreshSignal = this@UnifiedHub.libraryRefreshSignal + val libraryRefreshKey = currentRefreshSignal + localLibraryRefreshKey + val shortcutRefreshKey = libraryRefreshKey + shortcutDataRefreshKey + val playtimeRefreshKey = this@UnifiedHub.libraryPlaytimeRefreshSignal + + val contentFilters = remember { mutableStateMapOf(*initialContentFilters.entries.map { it.key to it.value }.toTypedArray()) } + var libraryLayoutMode by remember { + mutableStateOf( + runCatching { LibraryLayoutMode.valueOf(PrefManager.libraryLayoutMode) } + .getOrElse { initialLibraryLayoutMode }, + ) + } + var immersiveMode by remember { mutableStateOf(PrefManager.libraryImmersiveMode) } + var immersiveBlur by remember { mutableStateOf(PrefManager.libraryImmersiveBlur) } + val tabs = remember(storeVisible.toMap()) { buildTabs(storeVisible) } + var selectedIdx by rememberSaveable { mutableIntStateOf(0) } + var selectedDownloadId by remember { mutableStateOf(null) } + val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed) + LaunchedEffect(drawerState.isOpen) { + drawerOpen = drawerState.isOpen + if (!drawerState.isOpen) drawerNavBridge.controllerActive = false + } + val isLoggedIn by SteamService.isLoggedInFlow.collectAsState() + val chatServiceEnabled by SteamService.chatServiceEnabledFlow.collectAsState() + val isEpicLoggedIn by EpicAuthManager.isLoggedInFlow.collectAsState() + val isGogLoggedIn by GOGAuthManager.isLoggedInFlow.collectAsState() + val steamApps by db.steamAppDao().getAllOwnedApps().collectAsState(initial = emptyList()) + val context = LocalContext.current + val persona by SteamService.instance?.localPersona?.collectAsState() + ?: remember { mutableStateOf(null) } + val scope = rememberCoroutineScope() + val rightDrawerState = rememberDrawerState(initialValue = DrawerValue.Closed) + val friends by SteamService.instance?.friendsList?.collectAsState() + ?: remember { mutableStateOf(emptyList()) } + var chatFriend by remember { mutableStateOf(null) } + val friendsDrawerOpen = rightDrawerState.isOpen + LaunchedEffect(rightDrawerState.isOpen) { + rightDrawerOpen = rightDrawerState.isOpen + if (!rightDrawerState.isOpen) friendsDrawerNavBridge.controllerActive = false + } + LaunchedEffect(Unit) { + (context as? UnifiedActivity)?.openFriendsSignal?.collect { + if (rightDrawerState.isOpen) rightDrawerState.close() else rightDrawerState.open() + } + } + var installedFriendGameIds by remember { mutableStateOf>(emptySet()) } + LaunchedEffect(friends) { + val ids = friends.map { it.gameAppId }.filter { it > 0 }.distinct() + installedFriendGameIds = + withContext(Dispatchers.IO) { ids.filter { SteamService.isAppInstalled(it) }.toSet() } + } + LaunchedEffect(isLoggedIn, chatServiceEnabled) { + if (isLoggedIn && chatServiceEnabled) { + while (true) { + runCatching { SteamService.instance?.refreshFriends() } + kotlinx.coroutines.delay(30_000L) + } + } + } + LaunchedEffect(isLoggedIn, friendsDrawerOpen, chatServiceEnabled) { + if (isLoggedIn && friendsDrawerOpen && chatServiceEnabled) { + while (true) { + runCatching { SteamService.instance?.syncFriendsPresence() } + kotlinx.coroutines.delay(5_000L) + } + } + } + LaunchedEffect(isLoggedIn, chatServiceEnabled) { + if (isLoggedIn && chatServiceEnabled) { + runCatching { com.winlator.cmod.feature.stores.steam.chat.ChatOverlayService.start(context) } + } + } + + val epicApps by db.epicGameDao().getAll().collectAsState(initial = emptyList()) + val gogApps by db.gogGameDao().getAll().collectAsState(initial = emptyList()) + + val controllerState = rememberControllerConnectionState() + val isControllerConnected = controllerState.isConnected + val isPS = controllerState.isPlayStation + val isLibraryTab = tabs.getOrNull(selectedIdx)?.key == "library" + + val libraryRefreshListener = + remember { + object : EventDispatcher.JavaEventListener { + override fun onEvent(event: Any) { + when (event) { + is AndroidEvent.LibraryInstallStatusChanged -> { + localLibraryRefreshKey++ + shortcutDataRefreshKey++ + iconRefreshKey++ + } + is AndroidEvent.LibraryArtworkChanged -> { + shortcutDataRefreshKey++ + iconRefreshKey++ + } + } + } + } + } + DisposableEffect(libraryRefreshListener) { + PluviaApp.events.onJava(AndroidEvent.LibraryInstallStatusChanged::class, libraryRefreshListener) + PluviaApp.events.onJava(AndroidEvent.LibraryArtworkChanged::class, libraryRefreshListener) + onDispose { + PluviaApp.events.offJava(AndroidEvent.LibraryInstallStatusChanged::class, libraryRefreshListener) + PluviaApp.events.offJava(AndroidEvent.LibraryArtworkChanged::class, libraryRefreshListener) + } + } + + LaunchedEffect(isEpicLoggedIn) { + if (isEpicLoggedIn) { + EpicService.start(context) + } + } + + LaunchedEffect(isGogLoggedIn) { + if (isGogLoggedIn) { + GOGService.start(context) + } + } + + val epicLoginLauncher = + rememberLauncherForActivityResult( + contract = ActivityResultContracts.StartActivityForResult(), + ) { result -> + if (result.resultCode == android.app.Activity.RESULT_OK) { + val code = result.data?.getStringExtra(EpicOAuthActivity.EXTRA_AUTH_CODE) + if (code != null) { + scope.launch { + val authResult = EpicAuthManager.authenticateWithCode(context, code) + if (authResult.isSuccess) { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + R.string.stores_accounts_logged_in_epic, + android.widget.Toast.LENGTH_SHORT, + ) + } else { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + getString(R.string.stores_accounts_epic_login_failed, authResult.exceptionOrNull()?.message), + android.widget.Toast.LENGTH_LONG, + ) + } + } + } + } + } + + val gogLoginLauncher = + rememberLauncherForActivityResult( + contract = ActivityResultContracts.StartActivityForResult(), + ) { result -> + if (result.resultCode == android.app.Activity.RESULT_OK) { + val code = result.data?.getStringExtra(GOGOAuthActivity.EXTRA_AUTH_CODE) + if (!code.isNullOrBlank()) { + scope.launch { + val authResult = GOGAuthManager.authenticateWithCode(context, code) + if (authResult.isSuccess) { + GOGService.start(context) + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + R.string.stores_accounts_logged_in_gog, + android.widget.Toast.LENGTH_SHORT, + ) + } else { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + getString(R.string.stores_accounts_gog_login_failed, authResult.exceptionOrNull()?.message), + android.widget.Toast.LENGTH_LONG, + ) + } + } + } + } + } + + val filteredSteamApps = + remember(steamApps, contentFilters.toMap()) { + steamApps.filter { app -> + when (app.type) { + com.winlator.cmod.feature.stores.steam.enums.AppType.game -> contentFilters["games"] == true + com.winlator.cmod.feature.stores.steam.enums.AppType.demo -> contentFilters["games"] == true + com.winlator.cmod.feature.stores.steam.enums.AppType.dlc -> contentFilters["dlc"] == true + com.winlator.cmod.feature.stores.steam.enums.AppType.application -> contentFilters["applications"] == true + com.winlator.cmod.feature.stores.steam.enums.AppType.tool -> contentFilters["tools"] == true + com.winlator.cmod.feature.stores.steam.enums.AppType.config -> contentFilters["tools"] == true + else -> contentFilters["games"] == true + } + } + } + + var globalSettingsApp by remember { mutableStateOf(null) } + var globalSettingsGogGame by remember { mutableStateOf(null) } + + LaunchedEffect(tabs.size) { if (selectedIdx >= tabs.size) selectedIdx = 0 } + LaunchedEffect(isLoggedIn, persona) { + if (isLoggedIn && persona == null) { + SteamService.requestUserPersona() + } + } + + val activity = LocalContext.current as? UnifiedActivity + + LaunchedEffect(tabs) { + activity?.keyEventFlow?.collect { event -> + val key = tabs.getOrNull(selectedIdx)?.key ?: "library" + when (event.keyCode) { + android.view.KeyEvent.KEYCODE_BUTTON_L1 -> { + selectedIdx = if (selectedIdx > 0) selectedIdx - 1 else tabs.size - 1 + } + + android.view.KeyEvent.KEYCODE_BUTTON_R1 -> { + selectedIdx = (selectedIdx + 1) % tabs.size + } + + android.view.KeyEvent.KEYCODE_BUTTON_START -> { + navigateToSettings(SettingsNavItem.STORES) + } + + android.view.KeyEvent.KEYCODE_BUTTON_SELECT -> { + if (key != "downloads") { + if (drawerState.isOpen) drawerState.close() else drawerState.open() + } + } + + android.view.KeyEvent.KEYCODE_BUTTON_X -> { + if (key == "library" && (selectedSteamAppId != 0 || selectedGogGameId.isNotEmpty())) { + activity?.openHeroForFocusedSignal?.tryEmit(Unit) + } + } + + android.view.KeyEvent.KEYCODE_BUTTON_THUMBL -> { + if (key == "library") { + activity?.openSearchSignal?.tryEmit(Unit) + } + } + + android.view.KeyEvent.KEYCODE_BUTTON_THUMBR -> { + if (key == "library") { + showAddCustomGame = true + } + } + + android.view.KeyEvent.KEYCODE_BUTTON_B -> { + if (chatFriend != null) { + chatFriend = null + } else if (rightDrawerState.isOpen) { + rightDrawerState.close() + } else if (drawerState.isOpen) { + drawerState.close() + } else if (globalSettingsApp != null) { + globalSettingsApp = null + } else if (globalSettingsGogGame != null) { + globalSettingsGogGame = null + } else if (showAddCustomGame) { + showAddCustomGame = false + } else { + showExitDialog = true + } + } + + android.view.KeyEvent.KEYCODE_BUTTON_Y -> { + if (key == "library" && (selectedSteamAppId != 0 || selectedGogGameId.isNotEmpty())) { + if (selectedLibrarySource == "GOG") { + globalSettingsGogGame = gogApps.find { it.id == selectedGogGameId } + return@collect + } + val isCustom = selectedSteamAppId < 0 + val epicId = if (selectedSteamAppId >= 2000000000) selectedSteamAppId - 2000000000 else 0 + + globalSettingsApp = ( + steamApps.find { it.id == selectedSteamAppId } + ?: if (isCustom) { + SteamApp(id = selectedSteamAppId, name = selectedSteamAppName, developer = "Custom") + } else if (epicId > 0) { + val epic = epicApps.find { it.id == epicId } + SteamApp( + id = selectedSteamAppId, + name = selectedSteamAppName, + developer = epic?.developer ?: "Epic Games", + gameDir = epic?.installPath ?: "", + ) + } else { + null + } + ) + } + } + + android.view.KeyEvent.KEYCODE_BUTTON_A, android.view.KeyEvent.KEYCODE_DPAD_CENTER -> { + if (key == "library" && (selectedSteamAppId != 0 || selectedGogGameId.isNotEmpty())) { + val isCustom = selectedSteamAppId < 0 + val epicId = if (selectedSteamAppId >= 2000000000) selectedSteamAppId - 2000000000 else 0 + val containerManager = ContainerManager(context) + if (isCustom) { + launchCustomGame(context, containerManager, selectedSteamAppName) + } else if (selectedLibrarySource == "GOG") { + gogApps.find { it.id == selectedGogGameId }?.let { + launchGogGame(context, containerManager, it) + } + } else if (epicId > 0) { + val epic = epicApps.find { it.id == epicId } + if (epic != null && epic.isInstalled) { + val dummyApp = + SteamApp(id = selectedSteamAppId, name = selectedSteamAppName, gameDir = epic.installPath) + launchSteamGame(context, containerManager, dummyApp) + } + } else { + val steam = steamApps.find { it.id == selectedSteamAppId } + if (steam != null) { + launchSteamGame(context, containerManager, steam) + } + } + } else if (key != "library" && key != "downloads") { + storeItemClickCallback?.invoke(storeFocusIndex.value) + } + } + + } + } + } + + androidx.compose.runtime.CompositionLocalProvider( + androidx.compose.ui.platform.LocalLayoutDirection provides androidx.compose.ui.unit.LayoutDirection.Rtl, + ) { + ModalNavigationDrawer( + drawerState = rightDrawerState, + drawerContent = { + androidx.compose.runtime.CompositionLocalProvider( + androidx.compose.ui.platform.LocalLayoutDirection provides androidx.compose.ui.unit.LayoutDirection.Ltr, + ) { + com.winlator.cmod.feature.stores.steam.friends.FriendsDrawerContent( + isOpen = rightDrawerState.isOpen, + self = persona ?: com.winlator.cmod.feature.stores.steam.data.SteamFriend(), + friends = friends, + installedGameIds = installedFriendGameIds, + chatEnabled = chatServiceEnabled, + onSetState = { st -> scope.launch { SteamService.setPersonaState(st) } }, + onOpenChat = { f -> chatFriend = f; scope.launch { rightDrawerState.close() } }, + onJoinGame = { f -> + scope.launch { rightDrawerState.close() } + scope.launch { + val app = withContext(Dispatchers.IO) { SteamService.getAppInfoOf(f.gameAppId) } + val installed = withContext(Dispatchers.IO) { SteamService.getInstalledApp(f.gameAppId) } + val label = f.gameName.ifBlank { context.getString(R.string.steam_join_the_game) } + if (app != null && installed != null) { + android.widget.Toast.makeText( + context, context.getString(R.string.steam_join_joining, f.name, label), android.widget.Toast.LENGTH_SHORT, + ).show() + launchSteamGame(context, ContainerManager(context), app, f.connectString) + } else { + android.widget.Toast.makeText( + context, + if (app != null) context.getString(R.string.steam_join_install, label, f.name) + else context.getString(R.string.steam_join_not_owned, label), + android.widget.Toast.LENGTH_LONG, + ).show() + } + } + }, + onPlayGame = { f -> + scope.launch { rightDrawerState.close() } + scope.launch { + val app = withContext(Dispatchers.IO) { SteamService.getAppInfoOf(f.gameAppId) } + if (app != null) { + launchSteamGame(context, ContainerManager(context), app, null) + } + } + }, + ) + } + }, + scrimColor = Color.Black.copy(alpha = 0.5f), + gesturesEnabled = rightDrawerState.isOpen, + ) { + androidx.compose.runtime.CompositionLocalProvider( + androidx.compose.ui.platform.LocalLayoutDirection provides androidx.compose.ui.unit.LayoutDirection.Ltr, + ) { + ModalNavigationDrawer( + drawerState = drawerState, + drawerContent = { + DrawerContent( + persona = persona, + isOpen = drawerState.isOpen, + context = context, + scope = scope, + storeVisible = storeVisible, + contentFilters = contentFilters, + libraryLayoutMode = libraryLayoutMode, + immersiveMode = immersiveMode, + immersiveBlur = immersiveBlur, + onLibraryLayoutSelected = { + libraryLayoutMode = it + PrefManager.libraryLayoutMode = it.name + }, + onStoreVisibleChanged = { key, value -> + storeVisible[key] = value + PrefManager.libraryStoreVisible = storeVisible.entries.filter { it.value }.joinToString(",") { it.key } + }, + onContentFiltersChanged = { key, value -> + contentFilters[key] = value + PrefManager.libraryContentFilters = contentFilters.entries.filter { it.value }.joinToString(",") { it.key } + }, + onImmersiveModeChanged = { + immersiveMode = it + PrefManager.libraryImmersiveMode = it + }, + onImmersiveBlurChanged = { + immersiveBlur = it + PrefManager.libraryImmersiveBlur = it + }, + onExportAll = { + scope.launch { + val count = + withContext(Dispatchers.IO) { + com.winlator.cmod.feature.shortcuts.FrontendExporter.exportAll(context) + } + val dir = com.winlator.cmod.feature.shortcuts.FrontendExporter.resolveExportDir(context) + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + if (count > 0) { + context.getString(R.string.shortcuts_export_all_done, count, dir?.path ?: "") + } else { + context.getString(R.string.shortcuts_export_all_none) + }, + ) + } + }, + onExitApp = { + AppTerminationHelper.exitApplication(this@UnifiedHub, "hub_drawer_exit") + }, + ) + }, + scrimColor = Color.Black.copy(alpha = 0.5f), + gesturesEnabled = drawerState.isOpen, + ) { + Box( + Modifier + .fillMaxSize() + .background(BgDark) + .windowInsetsPadding(horizontalNavigationInsets), + ) { + val currentTabKeyForImmersive = tabs.getOrNull(selectedIdx)?.key ?: "library" + val immersiveActive = immersiveMode && currentTabKeyForImmersive == "library" + DisposableEffect(immersiveActive) { + applyImmersiveSystemBars(immersiveActive) + onDispose { applyImmersiveSystemBars(false) } + } + if (immersiveMode && currentTabKeyForImmersive == "library") { + val immersiveModel by immersiveBackgroundRef.collectAsState() + val immersiveRequest = + remember(immersiveModel, immersiveBlur, context) { + val builder = ImageRequest.Builder(context).data(immersiveModel) + (immersiveModel as? java.io.File)?.takeIf { it.isFile }?.let { file -> + // Custom uploads can be overwritten in place. + val key = "library_immersive_bg:${file.absolutePath}:${file.lastModified()}" + builder.memoryCacheKey(if (immersiveBlur) "$key:blur" else key).diskCacheKey(key) + } + if (immersiveBlur) { + // Blur baked into the bitmap at decode (quarter-res + radius 2 ≈ 8px on screen), so drawing costs the same as a plain image. + val dm = context.resources.displayMetrics + builder + .size(dm.widthPixels / 4, dm.heightPixels / 4) + .scale(coil.size.Scale.FILL) + .transformations(BoxBlurTransformation(radius = 2)) + } + builder.crossfade(400).build() + } + AnimatedVisibility( + visible = immersiveModel != null, + enter = fadeIn(tween(400)), + exit = fadeOut(tween(400)), + modifier = Modifier.matchParentSize(), + ) { + Box(Modifier.matchParentSize()) { + AsyncImage( + model = immersiveRequest, + contentDescription = null, + modifier = Modifier.matchParentSize(), + contentScale = ContentScale.Crop, + ) + Box( + Modifier + .matchParentSize() + .background(BgDark.copy(alpha = 0.5f)), + ) + } + } + } + val scaffoldContainer = if (immersiveMode && currentTabKeyForImmersive == "library") Color.Transparent else BgDark + val openFileManager: () -> Unit = { + val internalPath = android.os.Environment.getExternalStorageDirectory().absolutePath + val managedRoots = driveRoots(includeInternal = true) + val containerManager = com.winlator.cmod.runtime.container.ContainerManager(context) + val containers = + containerManager.getContainers().map { + DirectoryPickerDialog.ManagedContainer(it.id, it.getName()) + } + DirectoryPickerDialog.showManager( + activity = this@UnifiedHub, + initialPath = internalPath, + managedRoots = managedRoots, + containers = containers, + onRunFile = { exePath, containerId -> + val container = containerManager.getContainerById(containerId) + if (container != null) { + val winePath = + com.winlator.cmod.runtime.wine.WineUtils + .hostPathToMappedWinePath(container, exePath) + startActivity( + android.content.Intent( + this@UnifiedHub, + com.winlator.cmod.runtime.display.XServerDisplayActivity::class.java, + ).apply { + putExtra("container_id", container.id) + putExtra("boot_exe", winePath) + addFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK) + }, + ) + } + }, + onCreateShortcut = { exePath -> + val exeFile = java.io.File(exePath) + addCustomGame( + context, + exeFile.nameWithoutExtension, + exePath, + exeFile.parent ?: exePath, + ) + localLibraryRefreshKey++ + }, + ) + } + Scaffold( + containerColor = scaffoldContainer, + contentWindowInsets = WindowInsets(0, 0, 0, 0), + topBar = { + TopBar(tabs, selectedIdx, { + selectedIdx = it + }, persona, context, scope, isControllerConnected, isPS, isLibraryTab, searchQueryTfv, { + searchQueryTfv = + it + }, onFilterClicked = { scope.launch { drawerState.open() } }, onFriendsClicked = { scope.launch { rightDrawerState.open() } }) { + if (selectedLibrarySource == "GOG") { + globalSettingsGogGame = gogApps.find { it.id == selectedGogGameId } + } else { + globalSettingsApp = ( + steamApps.find { it.id == selectedSteamAppId } + ?: if (selectedSteamAppId < 0) { + SteamApp( + id = selectedSteamAppId, + name = selectedSteamAppName, + developer = "Custom", + ) + } else if (selectedSteamAppId >= 2000000000) { + val epicId = selectedSteamAppId - 2000000000 + val epic = epicApps.find { it.id == epicId } + SteamApp( + id = selectedSteamAppId, + name = selectedSteamAppName, + developer = epic?.developer ?: "Epic Games", + gameDir = epic?.installPath ?: "", + ) + } else { + null + } + ) + } + } + }, + ) { padding -> + LaunchedEffect(selectedIdx, tabs) { + currentTabKey = tabs.getOrNull(selectedIdx)?.key ?: "library" + storeFocusIndex.value = 0 + downloadsNavBridge.controllerActive = false + } + + val key = tabs.getOrNull(selectedIdx)?.key ?: "library" + val innerBoxBg = if (immersiveMode && key == "library") Color.Transparent else BgDark + + Box(Modifier.padding(padding).fillMaxSize().background(innerBoxBg)) { + + LaunchedEffect(key) { libraryTabActive.value = (key == "library") } + + // Keep Library composed so its state survives tab switches. + Box( + Modifier.fillMaxSize().let { + if (key == "library") { + it + } else { + it.alpha(0f).pointerInput(Unit) { /* block ghost taps */ } + } + }, + ) { + LibraryCarousel( + isLoggedIn = isLoggedIn, + steamApps = filteredSteamApps, + epicApps = epicApps, + gogApps = gogApps, + layoutMode = libraryLayoutMode, + libraryRefreshKey = libraryRefreshKey, + shortcutRefreshKey = shortcutRefreshKey, + playtimeRefreshKey = playtimeRefreshKey, + iconRefreshKey = iconRefreshKey, + searchQuery = searchQuery, + isControllerConnected = isControllerConnected, + ) + } + + if (key != "library") { + AnimatedContent( + targetState = key, + transitionSpec = { + fadeIn(tween(200)) togetherWith fadeOut(tween(150)) + }, + label = "tabContent", + ) { animatedKey -> + when (animatedKey) { + "downloads" -> { + DownloadsTab( + selectedDownloadId, + animationsActive = key == "downloads", + onSelectDownload = { selectedDownloadId = it }, + ) + } + + "steam" -> { + SteamStoreTab(isLoggedIn, filteredSteamApps, searchQuery, LibraryLayoutMode.GRID_4) + } + + "epic" -> { + EpicStoreTab(isEpicLoggedIn, epicApps, searchQuery, LibraryLayoutMode.GRID_4) { + epicLoginLauncher.launch(Intent(this@UnifiedHub, EpicOAuthActivity::class.java)) + } + } + + "gog" -> { + GOGStoreTab(isGogLoggedIn, gogApps, searchQuery, LibraryLayoutMode.GRID_4) { + gogLoginLauncher.launch(Intent(this@UnifiedHub, GOGOAuthActivity::class.java)) + } + } + + else -> {} + } + } + } + + val configuration = LocalConfiguration.current + val libraryFabBase = minOf(configuration.screenWidthDp, configuration.screenHeightDp) + val addGameFabSize = (libraryFabBase * 0.125f).dp.coerceIn(56.dp, 64.dp) + val addGameFabMargin = (libraryFabBase * 0.035f).dp.coerceIn(12.dp, 20.dp) + val addGameFabIconSize = (libraryFabBase * 0.055f).dp.coerceIn(24.dp, 28.dp) + val fabNavInsets = WindowInsets.navigationBars.asPaddingValues() + val fabEndInset = + (20.dp - fabNavInsets.calculateRightPadding(androidx.compose.ui.unit.LayoutDirection.Ltr)) + .coerceAtLeast(4.dp) + val fabStartInset = + (20.dp - fabNavInsets.calculateLeftPadding(androidx.compose.ui.unit.LayoutDirection.Ltr)) + .coerceAtLeast(4.dp) + + if (drawerState.isClosed) { + DrawerSwipeHotZone( + modifier = Modifier.align(Alignment.CenterStart), + onOpenDrawer = { scope.launch { drawerState.open() } }, + ) + } + if (rightDrawerState.isClosed) { + DrawerSwipeHotZone( + modifier = Modifier.align(Alignment.CenterEnd).padding(end = 22.dp), + isRightSide = true, + onOpenDrawer = { scope.launch { rightDrawerState.open() } }, + ) + } + + // Composed after the hot zones so the FAB stays on top for hit-testing. + if (key == "library") { + Column( + modifier = + Modifier + .align(Alignment.BottomEnd) + .windowInsetsPadding( + WindowInsets.navigationBars.only(WindowInsetsSides.Bottom), + ) + .padding(end = fabEndInset, bottom = addGameFabMargin), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + if (isControllerConnected) { + ControllerBadge("R3") + Spacer(Modifier.height(8.dp)) + } + Box( + modifier = + Modifier + .size(addGameFabSize) + .drawBehind { + drawCircle( + brush = + Brush.radialGradient( + colors = listOf(Accent.copy(alpha = 0.22f), Color.Transparent), + center = center, + radius = size.minDimension * 0.64f, + ), + radius = size.minDimension * 0.64f, + ) + } + .clip(CircleShape) + .background(Color.Transparent, CircleShape) + .border(1.5.dp, Accent.copy(alpha = 0.55f), CircleShape) + .focusProperties { canFocus = false } // No specific button for this, handle via long press or touch + .clickable( + interactionSource = null, + indication = androidx.compose.material3.ripple(color = Accent), + ) { showAddCustomGame = true }, + contentAlignment = Alignment.Center, + ) { + Icon( + Icons.Outlined.Add, + contentDescription = "Add Custom Game", + tint = Accent, + modifier = Modifier.size(addGameFabIconSize), + ) + } + } + } + + if (key == "library" || key == "downloads") { + Box( + modifier = + Modifier + .align(Alignment.BottomStart) + .windowInsetsPadding( + WindowInsets.navigationBars.only(WindowInsetsSides.Bottom), + ) + .padding(start = fabStartInset, bottom = addGameFabMargin) + .size(addGameFabSize) + .drawBehind { + drawCircle( + brush = + Brush.radialGradient( + colors = listOf(Accent.copy(alpha = 0.22f), Color.Transparent), + center = center, + radius = size.minDimension * 0.64f, + ), + radius = size.minDimension * 0.64f, + ) + } + .clip(CircleShape) + .background(Color.Transparent, CircleShape) + .border(1.5.dp, Accent.copy(alpha = 0.55f), CircleShape) + .focusProperties { canFocus = false } + .clickable( + interactionSource = null, + indication = androidx.compose.material3.ripple(color = Accent), + ) { openFileManager() }, + contentAlignment = Alignment.Center, + ) { + Icon( + Icons.Outlined.FolderOpen, + contentDescription = "Files", + tint = Accent, + modifier = Modifier.size(addGameFabIconSize), + ) + } + } + } + } + } + } // end ModalNavigationDrawer + } // end inner LTR + } // end right friends ModalNavigationDrawer + } // end RTL provider + + if (globalSettingsApp != null) { + GameSettingsDialog( + app = globalSettingsApp!!, + onDismissRequest = { globalSettingsApp = null }, + ) + } + if (globalSettingsGogGame != null) { + GOGGameSettingsDialog( + app = globalSettingsGogGame!!, + onDismissRequest = { globalSettingsGogGame = null }, + ) + } + + if (showAddCustomGame) { + AddCustomGameDialog(onDismiss = { + showAddCustomGame = false + localLibraryRefreshKey++ + }) + } + + chatFriend?.let { cf -> + com.winlator.cmod.feature.stores.steam.friends.SteamChatScreen( + friend = friends.firstOrNull { it.steamId == cf.steamId } ?: cf, + onClose = { chatFriend = null }, + ) + } + + BackHandler(enabled = true) { + if (chatFriend != null) { + chatFriend = null + } else if (rightDrawerState.isOpen) { + scope.launch { rightDrawerState.close() } + } else if (drawerState.isOpen) { + scope.launch { drawerState.close() } + } else if (globalSettingsApp != null) { + globalSettingsApp = null + } else if (globalSettingsGogGame != null) { + globalSettingsGogGame = null + } else if (showAddCustomGame) { + showAddCustomGame = false + } else { + showExitDialog = true + } + } + + if (showExitDialog) { + Dialog( + onDismissRequest = { showExitDialog = false }, + properties = DialogProperties(dismissOnBackPress = true, dismissOnClickOutside = true), + ) { + Box( + modifier = + Modifier + .width(320.dp) + .clip(RoundedCornerShape(20.dp)) + .background(SurfaceDark) + .border(1.dp, Accent.copy(alpha = 0.3f), RoundedCornerShape(20.dp)) + .padding(28.dp), + contentAlignment = Alignment.Center, + ) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Text( + text = stringResource(R.string.common_ui_exit_app_confirm), + style = MaterialTheme.typography.titleLarge, + color = TextPrimary, + fontWeight = FontWeight.Bold, + ) + Spacer(Modifier.height(24.dp)) + Row( + horizontalArrangement = Arrangement.spacedBy(16.dp), + ) { + OutlinedButton( + onClick = { showExitDialog = false }, + colors = ButtonDefaults.outlinedButtonColors(contentColor = TextSecondary), + border = androidx.compose.foundation.BorderStroke(1.dp, TextSecondary.copy(alpha = 0.5f)), + shape = RoundedCornerShape(12.dp), + modifier = Modifier.weight(1f), + ) { + Text(stringResource(R.string.common_ui_cancel), fontWeight = FontWeight.Medium) + } + Button( + onClick = { + AppTerminationHelper.exitApplication(this@UnifiedHub, "hub_exit_menu") + }, + colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFE53935)), + shape = RoundedCornerShape(12.dp), + modifier = Modifier.weight(1f), + ) { + Text(stringResource(R.string.common_ui_exit), color = Color.White, fontWeight = FontWeight.Bold) + } + } + } + } + } + } +} + +@Composable +internal fun UnifiedActivity.DrawerSwipeHotZone( + modifier: Modifier = Modifier, + isRightSide: Boolean = false, + onOpenDrawer: () -> Unit, +) { + val density = LocalDensity.current + val openThresholdPx = with(density) { 36.dp.toPx() } + + Box( + modifier = + modifier + .fillMaxHeight() + .width(if (isRightSide) 30.dp else 40.dp) + .pointerInput(openThresholdPx, isRightSide) { + var accumulatedDrag = 0f + var opened = false + + detectHorizontalDragGestures( + onDragStart = { + accumulatedDrag = 0f + opened = false + }, + onHorizontalDrag = { change, dragAmount -> + val delta = if (isRightSide) -dragAmount else dragAmount + if (delta <= 0f || opened) return@detectHorizontalDragGestures + + accumulatedDrag += delta + change.consume() + + if (accumulatedDrag >= openThresholdPx) { + opened = true + onOpenDrawer() + } + }, + ) + }, + ) +} + +@Composable +internal fun UnifiedActivity.GlassesSettingsSheet(onDismiss: () -> Unit) { + val gm = com.winlator.cmod.runtime.display.GlassesManager + val settings by gm.settings.collectAsState() + val brightnessMax = gm.brightnessMax() + val volumeMax = gm.volumeMax() + val brightness = if (settings.brightness < 0) brightnessMax else settings.brightness + val volume = if (settings.volume < 0) volumeMax else settings.volume + val registry = remember { PaneNavRegistry() } + Dialog( + onDismissRequest = onDismiss, + properties = DialogProperties(usePlatformDefaultWidth = false), + ) { + CompositionLocalProvider(LocalPaneNav provides registry) { + DialogPaneNav(registry, onDismiss = onDismiss) + androidx.compose.material3.Surface( + shape = RoundedCornerShape(24.dp), + color = SurfaceDark, + modifier = Modifier.fillMaxWidth(0.82f), + ) { + Column( + modifier = Modifier + .verticalScroll(rememberScrollState()) + .padding(horizontal = 22.dp, vertical = 18.dp), + verticalArrangement = Arrangement.spacedBy(14.dp), + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + Icon(Eyeglasses2Icon, contentDescription = null, tint = Accent, modifier = Modifier.size(22.dp)) + Spacer(Modifier.width(10.dp)) + Text(gm.modelName(), color = TextPrimary, fontSize = 17.sp, fontWeight = FontWeight.SemiBold) + } + Row(horizontalArrangement = Arrangement.spacedBy(24.dp)) { + Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(14.dp)) { + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + GlassesLabel(stringResource(R.string.glasses_panel_refresh)) + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + listOf(60, 90, 120).forEach { hz -> + val selected = settings.refreshHz == hz + Box( + modifier = Modifier + .weight(1f) + .clip(RoundedCornerShape(11.dp)) + .background(if (selected) Accent else TextSecondary.copy(alpha = 0.12f)) + .paneNavItem(cornerRadius = 11.dp, onActivate = { gm.setRefreshHz(hz) }, isEntry = hz == 60) + .clickable { gm.setRefreshHz(hz) } + .padding(vertical = 10.dp), + contentAlignment = Alignment.Center, + ) { + Text("$hz", color = if (selected) SurfaceDark else TextPrimary, + fontSize = 14.sp, fontWeight = FontWeight.SemiBold) + } + } + } + } + Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) { + GlassesToggleTile(stringResource(R.string.glasses_panel_sunblock), + settings.sunblock, Modifier.weight(1f)) { gm.setSunblock(it) } + GlassesToggleTile(stringResource(R.string.session_drawer_output_3d), + settings.threeD, Modifier.weight(1f)) { gm.set3D(it) } + } + } + Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(14.dp)) { + GlassesPercentSlider(stringResource(R.string.session_drawer_output_brightness), + brightness, brightnessMax) { gm.setBrightness(it) } + GlassesPercentSlider(stringResource(R.string.session_drawer_output_volume), + volume, volumeMax) { gm.setVolume(it) } + } + } + } + } + } + } +} + +@Composable +internal fun UnifiedActivity.GlassesLabel(text: String) { + Text(text, color = TextSecondary, fontSize = 13.sp, fontWeight = FontWeight.Medium) +} + +@Composable +internal fun UnifiedActivity.GlassesPercentSlider(label: String, level: Int, max: Int, onChange: (Int) -> Unit) { + val pct = if (max > 0) Math.round(level * 100f / max) else 0 + Column(verticalArrangement = Arrangement.spacedBy(6.dp)) { + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + GlassesLabel(label) + Text("$pct%", color = Accent, fontSize = 13.sp, fontWeight = FontWeight.SemiBold) + } + androidx.compose.material3.Slider( + value = level.toFloat(), + onValueChange = { onChange(it.roundToInt()) }, + valueRange = 0f..max.toFloat(), + steps = (max - 1).coerceAtLeast(0), + modifier = Modifier.paneNavItem( + cornerRadius = 8.dp, + onAdjust = { dir -> onChange((level + dir).coerceIn(0, max)) }, + ), + colors = androidx.compose.material3.SliderDefaults.colors( + thumbColor = Accent, + activeTrackColor = Accent, + inactiveTrackColor = TextSecondary.copy(alpha = 0.2f), + ), + ) + } +} + +@Composable +internal fun UnifiedActivity.GlassesToggleTile(label: String, checked: Boolean, modifier: Modifier = Modifier, onChange: (Boolean) -> Unit) { + Column( + modifier = modifier + .clip(RoundedCornerShape(13.dp)) + .background(if (checked) Accent.copy(alpha = 0.16f) else TextSecondary.copy(alpha = 0.08f)) + .paneNavItem(cornerRadius = 13.dp, onActivate = { onChange(!checked) }) + .clickable { onChange(!checked) } + .padding(vertical = 8.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(2.dp), + ) { + Text(label, color = TextPrimary, fontSize = 13.sp, fontWeight = FontWeight.Medium) + androidx.compose.material3.Switch( + checked = checked, + onCheckedChange = onChange, + colors = androidx.compose.material3.SwitchDefaults.colors( + checkedThumbColor = Color.White, + checkedTrackColor = Accent, + ), + ) + } +} + +@Composable +internal fun UnifiedActivity.TopBar( + tabs: List, + selectedIdx: Int, + onSelect: (Int) -> Unit, + persona: com.winlator.cmod.feature.stores.steam.data.SteamFriend?, + context: android.content.Context, + scope: kotlinx.coroutines.CoroutineScope, + isControllerConnected: Boolean, + isPS: Boolean, + isLibraryTab: Boolean, + searchQuery: TextFieldValue, + onSearchQueryChange: (TextFieldValue) -> Unit, + onFilterClicked: () -> Unit, + onFriendsClicked: () -> Unit = {}, + onGameSettingsClicked: () -> Unit, +) { + var isSearchExpanded by remember { mutableStateOf(false) } + val searchFocusRequester = remember { FocusRequester() } + val keyboardController = LocalSoftwareKeyboardController.current + val isDownloadsTab = tabs.getOrNull(selectedIdx)?.key == "downloads" + val glassesConnected by com.winlator.cmod.runtime.display.GlassesManager.connected.collectAsState() + var showGlassesPanel by remember { mutableStateOf(false) } + + LaunchedEffect(selectedIdx) { + if (isSearchExpanded) { + onSearchQueryChange(TextFieldValue("")) + isSearchExpanded = false + } + } + + // Auto-focus the search field when expanded + LaunchedEffect(isSearchExpanded) { + if (isSearchExpanded) { + kotlinx.coroutines.delay(150) + searchFocusRequester.requestFocus() + } else if (searchQuery.text.isNotEmpty()) { + onSearchQueryChange(TextFieldValue("")) + } + } + + val controllerSearchActivity = LocalContext.current as? UnifiedActivity + LaunchedEffect(Unit) { + controllerSearchActivity?.openSearchSignal?.collect { + if (!isDownloadsTab) { + if (isSearchExpanded) { + onSearchQueryChange(TextFieldValue("")) + isSearchExpanded = false + } else { + isSearchExpanded = true + } + } + } + } + LaunchedEffect(Unit) { + controllerSearchActivity?.openGlassesSignal?.collect { + if (glassesConnected) showGlassesPanel = true + } + } + + Column(modifier = Modifier.fillMaxWidth()) { + Box( + modifier = + Modifier + .fillMaxWidth() + .padding( + start = UnifiedTopBarHorizontalPadding, + end = UnifiedTopBarHorizontalPadding, + top = UnifiedTopBarTopPadding, + ) + .height(UnifiedTopBarHeight), + ) { + // Center Block: Tabs (absolutely centered, unaffected by left/right content) + Row( + modifier = Modifier.align(Alignment.Center).zIndex(1f), + verticalAlignment = Alignment.CenterVertically, + ) { + @Suppress("DEPRECATION") + CompositionLocalProvider( + androidx.compose.material3.LocalRippleConfiguration provides null, + ) { + val tabWidth = 100.dp + val tabSideGutter = 12.dp + val tabBarShape = RoundedCornerShape(18.dp) + val visibleCount = minOf(3, tabs.size) + val tabListState = rememberLazyListState() + val snapFlingBehavior = rememberSnapFlingBehavior(lazyListState = tabListState) + + LaunchedEffect(selectedIdx) { + val scrollTo = maxOf(0, selectedIdx - 1) + tabListState.animateScrollToItem(scrollTo) + } + + Box( + modifier = + Modifier + .width(tabWidth * visibleCount + tabSideGutter * 2) + .height(44.dp) + .shadow(8.dp, tabBarShape, spotColor = Color.Black.copy(alpha = 0.5f)) + .clip(tabBarShape) + .background(CardDark) + .border(1.dp, CardBorder, tabBarShape), + ) { + LazyRow( + state = tabListState, + flingBehavior = snapFlingBehavior, + modifier = + Modifier + .align(Alignment.Center) + .width(tabWidth * visibleCount) + .fillMaxHeight() + .focusProperties { canFocus = !isLibraryTab }, + userScrollEnabled = tabs.size > visibleCount, + ) { + itemsIndexed(tabs) { index, tab -> + val selected = selectedIdx == index + val interactionSource = remember { MutableInteractionSource() } + val isPressed by interactionSource.collectIsPressedAsState() + val tabScale by animateFloatAsState( + targetValue = if (isPressed) 0.92f else 1f, + animationSpec = spring(stiffness = Spring.StiffnessHigh), + label = "tabScale", + ) + val textColor by animateColorAsState( + targetValue = if (selected) Accent else TextSecondary, + animationSpec = tween(280), + label = "tabTextColor", + ) + + Box( + modifier = + Modifier + .width(tabWidth) + .fillMaxHeight() + .focusProperties { canFocus = false } + .graphicsLayer { + scaleX = tabScale + scaleY = tabScale + }.clickable( + interactionSource = interactionSource, + indication = null, + ) { onSelect(index) }, + contentAlignment = Alignment.Center, + ) { + Text( + text = tab.label.uppercase(), + style = MaterialTheme.typography.labelLarge, + fontWeight = if (selected) FontWeight.Bold else FontWeight.Medium, + fontSize = 13.sp, + maxLines = 1, + color = textColor, + ) + } + } + } + if (isControllerConnected) { + ControllerBadge( + "L1", + Modifier.align(Alignment.CenterStart).padding(start = 4.dp), + compact = true, + ) + ControllerBadge( + "R1", + Modifier.align(Alignment.CenterEnd).padding(end = 4.dp), + compact = true, + ) + } + } + } + } + + Row( + modifier = Modifier.align(Alignment.CenterStart).fillMaxHeight(), + verticalAlignment = Alignment.CenterVertically, + ) { + Box( + modifier = + Modifier + .size(44.dp) + .clip(CircleShape) + .background(Color.Transparent) + .border(1.dp, Accent.copy(alpha = 0.5f), CircleShape) + .focusProperties { canFocus = !isLibraryTab }, + contentAlignment = Alignment.Center, + ) { + @Suppress("DEPRECATION") + CompositionLocalProvider( + androidx.compose.material3.LocalRippleConfiguration provides + androidx.compose.material3.RippleConfiguration(color = Accent), + ) { + IconButton(onClick = { + navigateToSettings(SettingsNavItem.STORES) + }, modifier = Modifier.size(44.dp), enabled = true) { + Icon(Icons.Outlined.Settings, contentDescription = "Menu", tint = Accent, modifier = Modifier.size(24.dp)) + } + } + } + if (isControllerConnected) { + Spacer(Modifier.width(4.dp)) + ControllerBadge(if (isPS) "\u2261" else "Start") + } + + Spacer(Modifier.width(6.dp)) + + val searchIconRotation by animateFloatAsState( + targetValue = if (isSearchExpanded) 90f else 0f, + animationSpec = + spring( + dampingRatio = Spring.DampingRatioMediumBouncy, + stiffness = Spring.StiffnessLow, + ), + label = "searchIconRotation", + ) + + Box( + modifier = + Modifier + .size(44.dp) + .clip(CircleShape) + .background( + if (isSearchExpanded) { + Accent.copy(alpha = 0.15f) + } else { + Color.Transparent + }, + ).border( + 1.dp, + Accent.copy(alpha = if (isDownloadsTab) 0.25f else 0.5f), + CircleShape, + ).focusProperties { canFocus = !isLibraryTab }, + contentAlignment = Alignment.Center, + ) { + @Suppress("DEPRECATION") + CompositionLocalProvider( + androidx.compose.material3.LocalRippleConfiguration provides + androidx.compose.material3.RippleConfiguration(color = Accent), + ) { + IconButton( + onClick = { + if (!isDownloadsTab) { + if (isSearchExpanded) { + onSearchQueryChange(TextFieldValue("")) + isSearchExpanded = false + } else { + isSearchExpanded = true + } + } + }, + modifier = Modifier.size(44.dp), + enabled = !isDownloadsTab, + ) { + Icon( + Icons.Outlined.Search, + contentDescription = "Search", + tint = + if (isDownloadsTab) { + TextSecondary.copy(alpha = 0.4f) + } else { + Accent + }, + modifier = + Modifier + .size(24.dp) + .graphicsLayer { rotationZ = searchIconRotation }, + ) + } + } + } + if (isControllerConnected) { + Spacer(Modifier.width(4.dp)) + ControllerBadge("L3") + } + } + + val topBarView = androidx.compose.ui.platform.LocalView.current + val topBarDensity = androidx.compose.ui.platform.LocalDensity.current + val topBarOrientation = androidx.compose.ui.platform.LocalConfiguration.current.orientation + val navRightInset = remember(topBarOrientation, topBarView) { + val px = androidx.core.view.ViewCompat.getRootWindowInsets(topBarView) + ?.getInsets(androidx.core.view.WindowInsetsCompat.Type.navigationBars())?.right ?: 0 + with(topBarDensity) { px.toDp() } + } + Row( + modifier = Modifier.align(Alignment.CenterEnd).fillMaxHeight().zIndex(2f), + horizontalArrangement = Arrangement.End, + verticalAlignment = Alignment.CenterVertically, + ) { + Spacer(Modifier.width(8.dp)) + + if (glassesConnected) { + Box( + modifier = + Modifier + .size(44.dp) + .clip(CircleShape) + .background(Color.Transparent) + .border(1.dp, Accent.copy(alpha = 0.5f), CircleShape) + .clickable { showGlassesPanel = true }, + contentAlignment = Alignment.Center, + ) { + Icon(Eyeglasses2Icon, contentDescription = "Glasses", tint = Accent, modifier = Modifier.size(24.dp)) + } + Spacer(Modifier.width(12.dp)) + } + + Box( + modifier = + Modifier + .size(44.dp) + .clip(CircleShape) + .background(Color.Transparent) + .border(1.dp, Accent.copy(alpha = 0.5f), CircleShape) + .focusProperties { canFocus = !isLibraryTab } + .clickable( + interactionSource = null, + indication = androidx.compose.material3.ripple(color = Accent), + ) { onFilterClicked() }, + contentAlignment = Alignment.Center, + ) { + Icon(Icons.Outlined.FilterList, contentDescription = "Filter", tint = Accent, modifier = Modifier.size(24.dp)) + } + if (isControllerConnected) { + Spacer(Modifier.width(4.dp)) + ControllerBadge("Select") + } + + Spacer(Modifier.width(6.dp)) + + Box( + modifier = + Modifier + .size(44.dp) + .clip(CircleShape) + .background(Color.Transparent) + .border(1.dp, Accent.copy(alpha = 0.5f), CircleShape) + .clickable { onFriendsClicked() }, + contentAlignment = Alignment.Center, + ) { + Icon(Icons.Outlined.People, contentDescription = "Friends", tint = Accent, modifier = Modifier.size(24.dp)) + } + if (isControllerConnected && navRightInset <= 0.dp) { + Spacer(Modifier.width(8.dp)) + Box( + modifier = + Modifier + .background(Color(0xFF394048), RoundedCornerShape(15.dp)) + .border(1.dp, Color(0xFF8B949E).copy(alpha = 0.5f), RoundedCornerShape(15.dp)) + .padding(horizontal = 7.dp, vertical = 3.dp), + contentAlignment = Alignment.Center, + ) { + Icon( + Icons.Outlined.SportsEsports, + contentDescription = "Guide", + tint = Color(0xFFE6EDF3), + modifier = Modifier.size(16.dp), + ) + } + } + } + + if (isControllerConnected && navRightInset > 0.dp) { + Box( + modifier = + Modifier + .align(Alignment.CenterEnd) + .offset(x = 38.dp) + .zIndex(2f) + .background(Color(0xFF394048), RoundedCornerShape(15.dp)) + .border(1.dp, Color(0xFF8B949E).copy(alpha = 0.5f), RoundedCornerShape(15.dp)) + .padding(horizontal = 7.dp, vertical = 3.dp), + contentAlignment = Alignment.Center, + ) { + Icon( + Icons.Outlined.SportsEsports, + contentDescription = "Guide", + tint = Color(0xFFE6EDF3), + modifier = Modifier.size(16.dp), + ) + } + } + } + + if (showGlassesPanel) GlassesSettingsSheet(onDismiss = { showGlassesPanel = false }) + + AnimatedVisibility( + visible = isSearchExpanded && !isDownloadsTab, + enter = + expandVertically( + animationSpec = + spring( + dampingRatio = Spring.DampingRatioNoBouncy, + stiffness = Spring.StiffnessMedium, + ), + expandFrom = Alignment.Top, + ) + fadeIn(animationSpec = tween(200)), + exit = + shrinkVertically( + animationSpec = + spring( + dampingRatio = Spring.DampingRatioNoBouncy, + stiffness = Spring.StiffnessMedium, + ), + shrinkTowards = Alignment.Top, + ) + fadeOut(animationSpec = tween(120)), + ) { + Box( + modifier = + Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 6.dp), + contentAlignment = Alignment.Center, + ) { + Box( + modifier = + Modifier + .widthIn(max = 600.dp) + .fillMaxWidth(0.7f) + .height(44.dp) + .shadow(8.dp, RoundedCornerShape(24.dp), spotColor = Color.Black.copy(alpha = 0.4f)) + .clip(RoundedCornerShape(24.dp)) + .background(SurfaceDark), + contentAlignment = Alignment.CenterStart, + ) { + Row( + modifier = + Modifier + .fillMaxSize() + .padding(horizontal = 16.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + Icons.Outlined.Search, + contentDescription = null, + tint = Accent, + modifier = Modifier.size(22.dp), + ) + Spacer(Modifier.width(12.dp)) + BasicTextField( + value = searchQuery, + onValueChange = onSearchQueryChange, + singleLine = true, + textStyle = + TextStyle( + color = TextPrimary, + fontSize = 15.sp, + ), + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search), + keyboardActions = KeyboardActions(onSearch = { keyboardController?.hide() }), + cursorBrush = Brush.verticalGradient(listOf(Accent, AccentGlow)), + modifier = + Modifier + .weight(1f) + .focusRequester(searchFocusRequester), + decorationBox = { innerTextField -> + Box(contentAlignment = Alignment.CenterStart) { + if (searchQuery.text.isEmpty()) { + Text( + "Search games", + style = + TextStyle( + color = TextSecondary, + fontSize = 15.sp, + ), + ) + } + innerTextField() + } + }, + ) + if (searchQuery.text.isNotEmpty()) { + IconButton( + onClick = { onSearchQueryChange(TextFieldValue("")) }, + modifier = Modifier.size(32.dp), + ) { + Icon( + Icons.Outlined.Close, + contentDescription = "Clear", + tint = TextSecondary, + modifier = Modifier.size(18.dp), + ) + } + } + } + } + } + } + } // end Column +} + +@Composable +internal fun UnifiedActivity.LibraryCarousel( + isLoggedIn: Boolean, + steamApps: List, + epicApps: List, + gogApps: List, + layoutMode: LibraryLayoutMode, + libraryRefreshKey: Int = 0, + shortcutRefreshKey: Int = 0, + playtimeRefreshKey: Int = 0, + iconRefreshKey: Int = 0, + searchQuery: String = "", + isControllerConnected: Boolean = false, +) { + val context = LocalContext.current + + var cachedShortcuts by remember { mutableStateOf>(emptyList()) } + var customApps by remember { mutableStateOf>(emptyList()) } + var localLibraryRefreshKey by remember { mutableIntStateOf(0) } + var shortcutsLoaded by remember { mutableStateOf(false) } + var pullRefreshing by remember { mutableStateOf(false) } + LaunchedEffect(shortcutRefreshKey, localLibraryRefreshKey, com.winlator.cmod.feature.retro.RetroBoxart.artVersion.value) { + shortcutsLoaded = false + + // Pull-to-refresh only: rescan disk so a manually moved game is picked up without faking a re-download. + // Skipped on the initial pass because the scan walks every known app. + if (pullRefreshing) { + runCatching { + withContext(Dispatchers.IO) { SteamService.repairInstalledMetadataFromDisk() } + }.onFailure { Log.w("UnifiedActivity", "Pull-to-refresh install repair failed", it) } + } + + val shortcutScanResult = + runCatching { + withContext(Dispatchers.IO) { + runCatching { com.winlator.cmod.feature.retro.RetroRomScanner.scanConfiguredFolder(context) } + val cm = ContainerManager(context) + cm.upgradeShortcuts { + localLibraryRefreshKey++ + } + val allShortcuts = cm.loadShortcuts() + val badges = HashMap() + val apps = + allShortcuts + .mapNotNull { shortcut -> + if (!LibraryShortcutUtils.isCustomLibraryShortcut(shortcut)) { + return@mapNotNull null + } + + val displayName = + shortcut + .getExtra("custom_name", shortcut.name) + .ifBlank { shortcut.name } + + val uuid = shortcut.getExtra("uuid") + val customId = if (uuid.isNotEmpty()) { + -(uuid.hashCode().and(0x7FFFFFFF) + 1) + } else { + -(displayName.hashCode().and(0x7FFFFFFF) + 1) + } + + com.winlator.cmod.feature.retro.RetroSystems + .fromId( + shortcut.getExtra( + com.winlator.cmod.feature.retro.RetroShortcuts.KEY_SYSTEM, + ), + )?.let { badges[customId] = it.id } + + SteamApp( + id = customId, + name = displayName, + developer = "Custom", + gameDir = + shortcut.getExtra( + "game_install_path", + shortcut.getExtra("custom_game_folder", ""), + ), + ) + } + + Triple(allShortcuts, apps, badges) + } + }.getOrNull() + + if (shortcutScanResult != null) { + cachedShortcuts = shortcutScanResult.first + customApps = shortcutScanResult.second + retroLibrarySystemIds.value = shortcutScanResult.third + } + + shortcutsLoaded = true + } + + // Move library filtering and file checks off the main thread. + var mergedInstalledApps by remember { mutableStateOf>(emptyList()) } + var installedApps by remember { mutableStateOf>(emptyList()) } + var stableInstalledApps by remember { mutableStateOf>(emptyList()) } + var gogByPseudoId by remember { mutableStateOf>(emptyMap()) } + var epicByPseudoId by remember { mutableStateOf>(emptyMap()) } + var stableGogByPseudoId by remember { mutableStateOf>(emptyMap()) } + var stableEpicByPseudoId by remember { mutableStateOf>(emptyMap()) } + var customListArtworkPathByAppId by remember { mutableStateOf>(emptyMap()) } + var customHeroArtworkPathByAppId by remember { mutableStateOf>(emptyMap()) } + var customCarouselArtworkPathByAppId by remember { mutableStateOf>(emptyMap()) } + var customArtworkPathByAppId by remember { mutableStateOf>(emptyMap()) } + var customIconArtworkPathByAppId by remember { mutableStateOf>(emptyMap()) } + var customIconPathByAppId by remember { mutableStateOf>(emptyMap()) } + var stableCustomArtworkPathByAppId by remember { mutableStateOf>(emptyMap()) } + var stableCustomIconArtworkPathByAppId by remember { mutableStateOf>(emptyMap()) } + var stableCustomIconPathByAppId by remember { mutableStateOf>(emptyMap()) } + var stableCustomHeroPathByAppId by remember { mutableStateOf>(emptyMap()) } + var stableCustomCarouselPathByAppId by remember { mutableStateOf>(emptyMap()) } + var stableCustomListPathByAppId by remember { mutableStateOf>(emptyMap()) } + var artworkCacheRefreshKey by remember { mutableIntStateOf(0) } + var libraryLoaded by remember { mutableStateOf(false) } + // Suppress transient empty states before background recomputation starts. + val scanInputToken = + remember(steamApps, epicApps, gogApps, customApps, libraryRefreshKey, localLibraryRefreshKey) { Any() } + var processedScanToken by remember { mutableStateOf(null) } + + LaunchedEffect(scanInputToken) { + withContext(Dispatchers.IO) { + val steamInstalled = steamApps.filter { SteamService.isAppInstalled(it.id) } + + val epicInstalled = epicApps.filter { it.isInstalled } + + // Match Epic's DB-backed install filter during verify/update. + val gogInstalled = gogApps.filter { it.isInstalled } + + val gogMap = gogInstalled.associateBy { gogPseudoId(it.id) } + val epicMap = epicInstalled.associateBy { 2000000000 + it.id } + + val playtimePrefs = context.getSharedPreferences("playtime_stats", android.content.Context.MODE_PRIVATE) + val allPlaytime = playtimePrefs.all + val mappedEpic = + epicInstalled.map { epic -> + SteamApp( + id = 2000000000 + epic.id, + name = epic.title, + developer = epic.developer, + gameDir = epic.installPath, + ) + } + val mappedGog = + gogInstalled.map { gog -> + SteamApp( + id = gogPseudoId(gog.id), + name = gog.title, + developer = gog.developer, + gameDir = gog.installPath, + ) + } + val merged = steamInstalled + customApps + mappedEpic + mappedGog + val sorted = + merged.sortedByDescending { app -> + val searchKey = + if (app.id >= 2000000000 || app.id < 0) { + app.name + } else { + app.name.replace(LIBRARY_NAME_SANITIZE_REGEX, "") + } + (allPlaytime["${searchKey}_last_played"] as? Long) ?: 0L + } + + withContext(Dispatchers.Main) { + gogByPseudoId = gogMap + epicByPseudoId = epicMap + mergedInstalledApps = merged + installedApps = sorted + if (sorted.isNotEmpty()) { + stableInstalledApps = sorted + stableGogByPseudoId = gogMap + stableEpicByPseudoId = epicMap + } + libraryLoaded = true + processedScanToken = scanInputToken + pullRefreshing = false + } + } + } + + LaunchedEffect(installedApps, gogByPseudoId, cachedShortcuts, iconRefreshKey) { + val appsSnapshot = installedApps + val gogSnapshot = gogByPseudoId + val shortcutsSnapshot = cachedShortcuts + + val artworkPaths = + withContext(Dispatchers.IO) { + buildMap { + appsSnapshot.forEach { app -> + val gogGame = gogSnapshot[app.id] + val isCustom = app.id < 0 + val isEpic = app.id >= 2000000000 + val epicId = if (isEpic) app.id - 2000000000 else 0 + val shortcut = + if (gogGame != null) { + shortcutsSnapshot.find { + it.getExtra("game_source") == "GOG" && it.getExtra("gog_id") == gogGame.id + } + } else { + findShortcutForGame(shortcutsSnapshot, app, isCustom, isEpic, epicId) + } + val customPath = + shortcut + ?.getExtra("customLibraryIconPath") + ?.ifBlank { shortcut.getExtra("customCoverArtPath") } + if (!customPath.isNullOrBlank() && java.io.File(customPath).exists()) { + put(app.id, customPath) + } + } + } + } + + val iconArtworkPaths = + withContext(Dispatchers.IO) { + buildMap { + appsSnapshot.forEach { app -> + val gogGame = gogSnapshot[app.id] + val isCustom = app.id < 0 + val isEpic = app.id >= 2000000000 + val epicId = if (isEpic) app.id - 2000000000 else 0 + val shortcut = + if (gogGame != null) { + shortcutsSnapshot.find { + it.getExtra("game_source") == "GOG" && it.getExtra("gog_id") == gogGame.id + } + } else { + findShortcutForGame(shortcutsSnapshot, app, isCustom, isEpic, epicId) + } + val customPath = shortcut?.let(LibraryShortcutArtwork::findIconArtworkPath) + if (customPath != null) { + put(app.id, customPath) + } + } + } + } + + val customHeroPath = + withContext(Dispatchers.IO) { + buildMap { + appsSnapshot.forEach { app -> + if (app.id >= 0) return@forEach + val shortcut = findShortcutForGame(shortcutsSnapshot, app, true, false, 0) ?: return@forEach + val heroPath = shortcut.getExtra("customLibraryHeroArtPath") + if (heroPath.isNullOrBlank() || !java.io.File(heroPath).isFile) + return@forEach + put(app.id, heroPath) + } + } + } + + val customCarouselPath = + withContext(Dispatchers.IO) { + buildMap { + appsSnapshot.forEach { app -> + if (app.id >= 0) return@forEach + val shortcut = findShortcutForGame(shortcutsSnapshot, app, true, false, 0) ?: return@forEach + val carouselPath = shortcut.getExtra("customLibraryCarouselArtPath") + if (carouselPath.isNullOrBlank() || !java.io.File(carouselPath).isFile) + return@forEach + put(app.id, carouselPath) + } + } + } + + val customListPath = + withContext(Dispatchers.IO) { + buildMap { + appsSnapshot.forEach { app -> + if (app.id >= 0) return@forEach + val shortcut = findShortcutForGame(shortcutsSnapshot, app, true, false, 0) ?: return@forEach + val listPath = shortcut.getExtra("customLibraryListArtPath") + if (listPath.isNullOrBlank() || !java.io.File(listPath).isFile) + return@forEach + put(app.id, listPath) + } + } + } + + val customIconPaths = + withContext(Dispatchers.IO) { + buildMap { + appsSnapshot.forEach { app -> + if (app.id >= 0) return@forEach + val safeName = app.name.replace("/", "_").replace("\\", "_") + val iconFile = java.io.File(context.filesDir, "custom_icons/$safeName.png") + if (iconFile.exists()) { + put(app.id, iconFile.absolutePath) + } + } + } + } + + customArtworkPathByAppId = artworkPaths + customIconArtworkPathByAppId = iconArtworkPaths + customIconPathByAppId = customIconPaths + customHeroArtworkPathByAppId = customHeroPath + customCarouselArtworkPathByAppId = customCarouselPath + customListArtworkPathByAppId = customListPath + if (appsSnapshot.isNotEmpty()) { + stableCustomArtworkPathByAppId = artworkPaths + stableCustomIconArtworkPathByAppId = iconArtworkPaths + stableCustomIconPathByAppId = customIconPaths + stableCustomHeroPathByAppId = customHeroPath + stableCustomCarouselPathByAppId = customCarouselPath + stableCustomListPathByAppId = customListPath + } + } + + LaunchedEffect(mergedInstalledApps, playtimeRefreshKey) { + if (mergedInstalledApps.isEmpty()) { + installedApps = emptyList() + return@LaunchedEffect + } + + val sorted = + withContext(Dispatchers.IO) { + val playtimePrefs = context.getSharedPreferences("playtime_stats", android.content.Context.MODE_PRIVATE) + val allPlaytime = playtimePrefs.all + mergedInstalledApps.sortedByDescending { app -> + val searchKey = + if (app.id >= 2000000000 || app.id < 0) { + app.name + } else { + app.name.replace(LIBRARY_NAME_SANITIZE_REGEX, "") + } + (allPlaytime["${searchKey}_last_played"] as? Long) ?: 0L + } + } + + installedApps = sorted + } + + val awaitingShortcutScan = installedApps.isEmpty() && !shortcutsLoaded + val keepPreviousLibraryVisible = + installedApps.isEmpty() && + stableInstalledApps.isNotEmpty() && + (processedScanToken !== scanInputToken || awaitingShortcutScan) + val visibleInstalledApps = if (keepPreviousLibraryVisible) stableInstalledApps else installedApps + val visibleGogByPseudoId = if (keepPreviousLibraryVisible) stableGogByPseudoId else gogByPseudoId + val visibleEpicByPseudoId = if (keepPreviousLibraryVisible) stableEpicByPseudoId else epicByPseudoId + val visibleCustomArtworkPathByAppId = + if (keepPreviousLibraryVisible) stableCustomArtworkPathByAppId else customArtworkPathByAppId + val visibleCustomIconArtworkPathByAppId = + if (keepPreviousLibraryVisible) stableCustomIconArtworkPathByAppId else customIconArtworkPathByAppId + val visibleCustomIconPathByAppId = + if (keepPreviousLibraryVisible) stableCustomIconPathByAppId else customIconPathByAppId + val visibleCustomListPathByAppId = + if (keepPreviousLibraryVisible) stableCustomListPathByAppId else customListArtworkPathByAppId + val visibleCustomHeroPathByAppId = + if (keepPreviousLibraryVisible) stableCustomHeroPathByAppId else customHeroArtworkPathByAppId + val visibleCustomCarouselPathByAppId = + if (keepPreviousLibraryVisible) stableCustomCarouselPathByAppId else customCarouselArtworkPathByAppId + + val displayedApps = + remember(visibleInstalledApps, searchQuery) { + if (searchQuery.isBlank()) { + visibleInstalledApps + } else { + visibleInstalledApps.filter { it.name.contains(searchQuery, ignoreCase = true) } + } + } + + LaunchedEffect( + visibleInstalledApps, + visibleGogByPseudoId, + visibleEpicByPseudoId, + visibleCustomArtworkPathByAppId, + visibleCustomIconArtworkPathByAppId, + cachedShortcuts, + ) { + var deletedCustomOverrides = false + val refs = + visibleInstalledApps.flatMap { app -> + val gogGame = visibleGogByPseudoId[app.id] + val epicGame = visibleEpicByPseudoId[app.id] + val overriddenSlots = + customArtworkOverrideSlots( + app = app, + gogGame = gogGame, + epicGame = epicGame, + hasDefaultCustomArt = visibleCustomArtworkPathByAppId[app.id] != null, + hasIconCustomArt = visibleCustomIconArtworkPathByAppId[app.id] != null, + hasHeroCustomArt = + findLibraryArtworkShortcut(cachedShortcuts, app, gogGame, epicGame) + ?.hasExistingArtwork(LibraryShortcutArtwork.LibraryArtworkSlot.GAME_CARD.extraKey) == true, + ) + + if (overriddenSlots.isNotEmpty()) { + val cacheId = artworkCacheId(app, gogGame, epicGame) + if (cacheId != null) { + val deleted = + withContext(Dispatchers.IO) { + StoreArtworkCache.deleteSlots(context, cacheId.store, cacheId.gameId, overriddenSlots) + } + deletedCustomOverrides = deletedCustomOverrides || deleted + } + } + + StoreArtworkCache + .libraryRefs( + app = app, + gogGame = gogGame, + epicGame = epicGame, + ).filterNot { it.slot in overriddenSlots } + } + val cachedAny = + withContext(Dispatchers.IO) { + StoreArtworkCache.cacheAll(context, refs) + } + if (cachedAny || deletedCustomOverrides) artworkCacheRefreshKey++ + } + + // The startup bootstrap screen already masks the first frame. Do not + // force an extra minimum spinner duration here or the library visibly + // bounces through two loading states on launch. + // A logged-in store whose owned-apps list is still empty hasn't finished + // its initial library fetch yet — keep the spinner up instead of flashing + // "No games installed". This resolves itself once the store populates its + // DB (steamApps/epicApps/gogApps become non-empty) or if other sources + // (custom apps, other stores) already have installed games. + val awaitingStoreSync = + installedApps.isEmpty() && ( + (isLoggedIn && steamApps.isEmpty()) || + (epicApps.isEmpty() && EpicService.hasStoredCredentials(context)) || + (gogApps.isEmpty() && GOGAuthManager.isLoggedIn(context)) + ) + // Only block the surface while the first library result is unresolved. + // After that, keep the current content/empty state visible during + // background refreshes so the UI does not flicker back to a spinner. + val initialLibraryLoadPending = !libraryLoaded + val waitingForFirstEmptyStateResolution = + installedApps.isEmpty() && (processedScanToken !== scanInputToken || awaitingStoreSync || awaitingShortcutScan) + val showLoading = initialLibraryLoadPending || waitingForFirstEmptyStateResolution + if (showLoading) { + Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + val spinAlpha by animateFloatAsState( + targetValue = 1f, + animationSpec = tween(durationMillis = 600), + label = "loaderFade", + ) + CircularProgressIndicator( + color = Accent, + strokeWidth = 3.dp, + modifier = Modifier.size(48.dp).alpha(spinAlpha), + ) + } + return + } + + if (visibleInstalledApps.isEmpty()) { + val epicLoggedIn by EpicAuthManager.isLoggedInFlow.collectAsState() + val gogLoggedIn by GOGAuthManager.isLoggedInFlow.collectAsState() + val anyLoggedIn = isLoggedIn || epicLoggedIn || gogLoggedIn + val hasAnyCredentials = + anyLoggedIn || + SteamService.hasStoredCredentials(context) || + EpicService.hasStoredCredentials(context) || + GOGAuthManager.isLoggedIn(context) + if (!anyLoggedIn && !hasAnyCredentials) { + LoginRequiredScreen("Library") { + navigateToSettings(SettingsNavItem.STORES) + } + } else if (anyLoggedIn) { + PullToRefreshBox( + isRefreshing = pullRefreshing, + onRefresh = { + pullRefreshing = true + localLibraryRefreshKey++ + }, + modifier = Modifier.fillMaxSize(), + ) { + Box( + Modifier.fillMaxSize().verticalScroll(rememberScrollState()), + contentAlignment = Alignment.Center, + ) { + EmptyStateMessage(stringResource(R.string.library_games_no_games_installed)) + } + } + } + return + } + + var selectedAppForSettings by remember { mutableStateOf(null) } + var selectedGogGameForSettings by remember { mutableStateOf(null) } + var detailApp by remember { mutableStateOf(null) } + var detailGogGame by remember { mutableStateOf(null) } + val gridState = rememberLazyGridState() + val carouselState = rememberLazyListState() + val activity = LocalContext.current as? UnifiedActivity + + // Pause chasing borders on library cards while any dialog is open. + LaunchedEffect(selectedAppForSettings, selectedGogGameForSettings, detailApp) { + chasingBordersPaused.value = + selectedAppForSettings != null || selectedGogGameForSettings != null || detailApp != null + } + DisposableEffect(Unit) { + onDispose { chasingBordersPaused.value = false } + } + + LaunchedEffect(layoutMode) { + currentLibraryLayoutMode = layoutMode + } + + // Keep activity's item count in sync + LaunchedEffect(displayedApps.size) { + activity?.libraryItemCount = displayedApps.size + val lastIndex = (displayedApps.size - 1).coerceAtLeast(0) + if (activity != null && displayedApps.isNotEmpty() && activity.libraryFocusIndex.value > lastIndex) { + activity.libraryFocusIndex.value = lastIndex + } + } + + // FocusRequesters for each grid item + val focusRequesters = + remember(displayedApps.size) { + List(displayedApps.size) { FocusRequester() } + } + + // Observe focus index changes from the activity and request focus on the target item + val focusIndex by (activity?.libraryFocusIndex ?: kotlinx.coroutines.flow.MutableStateFlow(0)).collectAsState() + LaunchedEffect(focusIndex, focusRequesters.size, layoutMode) { + if (searchQuery.isEmpty() && + layoutMode == LibraryLayoutMode.GRID_4 && + focusRequesters.isNotEmpty() && + focusIndex in focusRequesters.indices + ) { + gridState.animateScrollToItem(focusIndex) + try { + focusRequesters[focusIndex].requestFocus() + } catch (_: Exception) { + } + } + } + + // Track selected app for the top-right Game Settings button + LaunchedEffect(focusIndex, displayedApps) { + val app = displayedApps.getOrNull(focusIndex) ?: displayedApps.firstOrNull() + selectedSteamAppId = app?.id ?: 0 + selectedSteamAppName = app?.name ?: "" + val gogGame = app?.let { visibleGogByPseudoId[it.id] } + selectedLibrarySource = + when { + gogGame != null -> "GOG" + app == null -> "" + app.id >= 2000000000 -> "EPIC" + app.id < 0 -> "CUSTOM" + else -> "STEAM" + } + selectedGogGameId = gogGame?.id.orEmpty() + } + + val heroApps = rememberUpdatedState(displayedApps) + val heroFocus = rememberUpdatedState(focusIndex) + val heroGogMap = rememberUpdatedState(visibleGogByPseudoId) + LaunchedEffect(Unit) { + activity?.openHeroForFocusedSignal?.collect { + val list = heroApps.value + val app = list.getOrNull(heroFocus.value) ?: list.firstOrNull() + if (app != null) { + detailGogGame = heroGogMap.value[app.id] + detailApp = app + } + } + } + + // Publish the focused game's hero art (custom card > store hero > grid capsule) for the immersive background; shortcuts load once per refresh signal, not per focus move. + var immersiveShortcuts by remember { mutableStateOf?>(null) } + LaunchedEffect(shortcutRefreshKey, libraryRefreshKey, artworkCacheRefreshKey) { + immersiveShortcuts = + withContext(Dispatchers.IO) { ContainerManager(context).loadShortcuts() } + } + + LaunchedEffect(focusIndex, displayedApps, immersiveShortcuts) { + val shortcuts = immersiveShortcuts ?: return@LaunchedEffect + val app = displayedApps.getOrNull(focusIndex) ?: displayedApps.firstOrNull() + if (app == null) { + activity?.immersiveBackgroundRef?.value = null + return@LaunchedEffect + } + // Debounce so scrubbing the grid doesn't decode every intermediate hero. + delay(200) + val gogGame = visibleGogByPseudoId[app.id] + val epicGame = visibleEpicByPseudoId[app.id] + val isCustom = app.id < 0 + val isEpic = app.id >= 2000000000 + val epicId = if (isEpic) app.id - 2000000000 else 0 + + val shortcut = + when { + gogGame != null -> + shortcuts.find { + it.getExtra("game_source") == "GOG" && it.getExtra("gog_id") == gogGame.id + } + else -> findShortcutForGame(shortcuts, app, isCustom, isEpic, epicId) + } + val customHeroFile = + withContext(Dispatchers.IO) { + shortcut + ?.getExtra(LibraryShortcutArtwork.LibraryArtworkSlot.GAME_CARD.extraKey) + ?.takeIf { it.isNotBlank() } + ?.let { java.io.File(it) } + ?.takeIf { it.isFile } + } + + activity?.immersiveBackgroundRef?.value = + customHeroFile + ?: run { + val ref = + StoreArtworkCache.heroRef(app, gogGame, epicGame) + ?: StoreArtworkCache.primaryRef( + app, + gogGame, + epicGame, + useLibraryCapsule = false, + listMode = false, + ) + StoreArtworkCache.imageModel(context, ref) + } + } + + val openSettingsForApp: (Int, SteamApp) -> Unit = { index, app -> + activity?.libraryFocusIndex?.value = index + selectedSteamAppId = app.id + selectedSteamAppName = app.name + val gogGame = visibleGogByPseudoId[app.id] + selectedLibrarySource = + when { + gogGame != null -> "GOG" + app.id >= 2000000000 -> "EPIC" + app.id < 0 -> "CUSTOM" + else -> "STEAM" + } + selectedGogGameId = gogGame?.id.orEmpty() + + if (gogGame != null) { + selectedGogGameForSettings = gogGame + } else { + selectedAppForSettings = app + } + } + + PullToRefreshBox( + isRefreshing = pullRefreshing, + onRefresh = { + pullRefreshing = true + localLibraryRefreshKey++ + }, + modifier = Modifier.fillMaxSize(), + ) { + when (layoutMode) { + LibraryLayoutMode.GRID_4 -> { + FourByTwoGridView( + items = displayedApps, + modifier = Modifier.tabScreenPadding(), + gridState = gridState, + contentPadding = TabGridContentPadding, + clipContent = false, + keyOf = { it.id }, + ) { app, index, rowHeight -> + GameCapsule( + app = app, + gogGame = visibleGogByPseudoId[app.id], + epicGame = visibleEpicByPseudoId[app.id], + iconRefreshKey = iconRefreshKey, + artworkCacheRefreshKey = artworkCacheRefreshKey, + isFocusedOverride = index == focusIndex, + isControllerActive = isControllerConnected, + customArtworkPath = visibleCustomIconArtworkPathByAppId[app.id] ?: visibleCustomArtworkPathByAppId[app.id], + customIconPath = visibleCustomIconPathByAppId[app.id], + customListPath = visibleCustomListPathByAppId[app.id], + customHeroPath = visibleCustomHeroPathByAppId[app.id], + onClick = { + // Keeps the immersive background on the opened game after backing out. + activity?.libraryFocusIndex?.value = index + detailGogGame = visibleGogByPseudoId[app.id] + detailApp = app + }, + onLongClick = { + openSettingsForApp(index, app) + }, + modifier = + Modifier + .height(rowHeight) + .then( + if (index in focusRequesters.indices) { + Modifier.focusRequester(focusRequesters[index]) + } else { + Modifier + }, + ), + ) + } + } + + LibraryLayoutMode.CAROUSEL -> { + // Host the horizontal carousel in a same-height vertical scroll so a downward finger pull reaches the shared PullToRefreshBox. + BoxWithConstraints(Modifier.fillMaxSize()) { + val carouselViewportHeight = maxHeight + Column(Modifier.fillMaxWidth().verticalScroll(rememberScrollState())) { + Box(Modifier.fillMaxWidth().height(carouselViewportHeight)) { + CarouselView( + items = displayedApps, + modifier = Modifier.tabScreenPadding(top = TabCarouselTopPadding, bottom = TabCarouselBottomPadding), + listState = carouselState, + selectedIndex = focusIndex, + onCenteredIndexChanged = { centeredIndex -> + if (activity != null && activity.libraryFocusIndex.value != centeredIndex) { + activity.libraryFocusIndex.value = centeredIndex + } + }, + ) { app, index, isSelected, cardWidth, cardHeight -> + GameCapsule( + app = app, + gogGame = visibleGogByPseudoId[app.id], + epicGame = visibleEpicByPseudoId[app.id], + iconRefreshKey = iconRefreshKey, + artworkCacheRefreshKey = artworkCacheRefreshKey, + isFocusedOverride = isSelected, + isControllerActive = isControllerConnected, + customArtworkPath = visibleCustomIconArtworkPathByAppId[app.id] ?: visibleCustomArtworkPathByAppId[app.id], + customIconPath = visibleCustomIconPathByAppId[app.id], + customListPath = visibleCustomListPathByAppId[app.id], + customCarouselPath = visibleCustomCarouselPathByAppId[app.id], + customHeroPath = visibleCustomHeroPathByAppId[app.id], + onClick = { + detailGogGame = visibleGogByPseudoId[app.id] + detailApp = app + }, + onLongClick = { openSettingsForApp(index, app) }, + useLibraryCapsule = true, + modifier = + Modifier + .fillMaxSize() + .then( + if (index in focusRequesters.indices) { + Modifier.focusRequester(focusRequesters[index]) + } else { + Modifier + }, + ), + ) + } + } + } + } + } + + LibraryLayoutMode.LIST -> { + val listViewState = rememberLazyListState() + ListView( + items = displayedApps, + modifier = Modifier.tabScreenPadding(), + listState = listViewState, + contentPadding = TabListContentPadding, + selectedIndex = focusIndex, + onSelectedIndexChanged = { newIdx -> + activity?.libraryFocusIndex?.value = newIdx + }, + keyOf = { it.id }, + ) { app, index, isSelected -> + GameCapsule( + app = app, + gogGame = visibleGogByPseudoId[app.id], + epicGame = visibleEpicByPseudoId[app.id], + iconRefreshKey = iconRefreshKey, + artworkCacheRefreshKey = artworkCacheRefreshKey, + isFocusedOverride = isSelected, + isControllerActive = isControllerConnected, + customArtworkPath = visibleCustomIconArtworkPathByAppId[app.id] ?: visibleCustomArtworkPathByAppId[app.id], + customIconPath = visibleCustomIconPathByAppId[app.id], + customListPath = visibleCustomListPathByAppId[app.id], + customHeroPath = visibleCustomHeroPathByAppId[app.id], + onClick = { + // Keeps the immersive background on the opened game after backing out. + activity?.libraryFocusIndex?.value = index + detailGogGame = visibleGogByPseudoId[app.id] + detailApp = app + }, + onLongClick = { openSettingsForApp(index, app) }, + listMode = true, + modifier = + Modifier + .then( + if (index in focusRequesters.indices) { + Modifier.focusRequester(focusRequesters[index]) + } else { + Modifier + }, + ), + ) + } + JoystickListScroll( + listState = listViewState, + stickFlow = activity?.rightStickScrollState, + minSpeed = 2.5f, + maxSpeed = 16f, + quadratic = true, + ) + } + } + } + + if (selectedAppForSettings != null) { + GameSettingsDialog( + app = selectedAppForSettings!!, + onDismissRequest = { selectedAppForSettings = null }, + ) + } + if (selectedGogGameForSettings != null) { + GOGGameSettingsDialog( + app = selectedGogGameForSettings!!, + onDismissRequest = { selectedGogGameForSettings = null }, + ) + } + if (detailApp != null) { + LibraryGameDetailDialog( + app = detailApp!!, + gogGame = detailGogGame, + onDismissRequest = { + detailApp = null + detailGogGame = null + }, + ) + } +} diff --git a/app/src/main/app/shell/UnifiedActivityInput.kt b/app/src/main/app/shell/UnifiedActivityInput.kt new file mode 100644 index 000000000..9b08d45ab --- /dev/null +++ b/app/src/main/app/shell/UnifiedActivityInput.kt @@ -0,0 +1,449 @@ +package com.winlator.cmod.app.shell + +import android.app.Activity +import android.app.PendingIntent +import android.content.Intent +import android.content.res.Configuration +import android.hardware.input.InputManager +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.drawable.BitmapDrawable +import android.net.Uri +import android.os.Bundle +import android.provider.DocumentsContract +import android.util.Log +import androidx.activity.SystemBarStyle +import androidx.activity.compose.BackHandler +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AppCompatActivity +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.EnterTransition +import androidx.compose.animation.ExitTransition +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.FastOutSlowInEasing +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.spring +import androidx.compose.animation.core.tween +import androidx.compose.animation.expandVertically +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.shrinkVertically +import androidx.compose.animation.slideInHorizontally +import androidx.compose.animation.slideOutHorizontally +import androidx.compose.animation.togetherWith +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background +import androidx.compose.foundation.basicMarquee +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.focusable +import androidx.compose.foundation.gestures.detectHorizontalDragGestures +import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsPressedAsState +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items +import androidx.compose.foundation.lazy.grid.itemsIndexed +import androidx.compose.foundation.lazy.grid.rememberLazyGridState +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.outlined.ArrowBack +import androidx.compose.material.icons.automirrored.outlined.ExitToApp +import androidx.compose.material.icons.automirrored.outlined.OpenInNew +import androidx.compose.material.icons.outlined.* +import androidx.compose.material3.* +import androidx.compose.material3.pulltorefresh.PullToRefreshBox +import androidx.compose.runtime.* +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.snapshotFlow +import androidx.compose.runtime.snapshots.SnapshotStateMap +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.zIndex +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusProperties +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.focus.focusTarget +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.geometry.CornerRadius +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.TileMode +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.input.pointer.PointerEventPass +import androidx.compose.ui.input.pointer.PointerEventType +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties +import androidx.lifecycle.lifecycleScope +import androidx.navigation.NavHostController +import androidx.navigation.NavType +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import androidx.navigation.navArgument +import coil.compose.AsyncImage +import coil.imageLoader +import coil.request.ImageRequest +import com.winlator.cmod.BuildConfig +import com.winlator.cmod.R +import com.winlator.cmod.app.PluviaApp +import com.winlator.cmod.app.db.PluviaDatabase +import com.winlator.cmod.app.service.DownloadService +import com.winlator.cmod.app.service.download.DownloadCoordinator +import com.winlator.cmod.app.update.UpdateChecker +import com.winlator.cmod.feature.settings.InputControlsFragment +import com.winlator.cmod.feature.settings.SettingsFocusZone +import com.winlator.cmod.feature.settings.SettingsHost +import com.winlator.cmod.feature.settings.SettingsNavBridge +import com.winlator.cmod.feature.settings.SettingsNavItem +import com.winlator.cmod.feature.setup.SetupWizardActivity +import com.winlator.cmod.feature.shortcuts.LibraryShortcutUtils +import com.winlator.cmod.feature.shortcuts.LibraryShortcutArtwork +import com.winlator.cmod.feature.shortcuts.ShortcutBroadcastReceiver +import com.winlator.cmod.feature.shortcuts.ShortcutSettingsComposeDialog +import com.winlator.cmod.feature.shortcuts.ShortcutsFragment +import com.winlator.cmod.feature.stores.common.StoreArtworkCache +import com.winlator.cmod.feature.stores.epic.data.EpicCredentials +import com.winlator.cmod.feature.stores.epic.data.EpicGame +import com.winlator.cmod.feature.stores.epic.data.EpicGameToken +import com.winlator.cmod.feature.stores.epic.service.EpicAuthManager +import com.winlator.cmod.feature.stores.epic.service.EpicCloudSavesManager +import com.winlator.cmod.feature.stores.epic.service.EpicConstants +import com.winlator.cmod.feature.stores.epic.service.EpicDownloadManager +import com.winlator.cmod.feature.stores.epic.service.EpicGameLauncher +import com.winlator.cmod.feature.stores.epic.service.EpicManager +import com.winlator.cmod.feature.stores.epic.service.EpicService +import com.winlator.cmod.feature.stores.epic.service.EpicUpdateInfo +import com.winlator.cmod.feature.stores.epic.ui.auth.EpicOAuthActivity +import com.winlator.cmod.feature.stores.gog.data.GOGDlcInfo +import com.winlator.cmod.feature.stores.gog.data.GOGGame +import com.winlator.cmod.feature.stores.gog.data.LibraryItem +import com.winlator.cmod.feature.stores.gog.service.GOGAuthManager +import com.winlator.cmod.feature.stores.gog.service.GOGConstants +import com.winlator.cmod.feature.stores.gog.service.GOGManifestSizes +import com.winlator.cmod.feature.stores.gog.service.GOGService +import com.winlator.cmod.feature.stores.gog.service.GOGUpdateInfo +import com.winlator.cmod.feature.stores.gog.ui.auth.GOGOAuthActivity +import com.winlator.cmod.feature.stores.steam.SteamLoginActivity +import com.winlator.cmod.feature.stores.steam.data.DepotInfo +import com.winlator.cmod.feature.stores.steam.data.DownloadInfo +import com.winlator.cmod.feature.stores.steam.data.SteamApp +import com.winlator.cmod.feature.stores.steam.enums.DownloadPhase +import com.winlator.cmod.feature.stores.steam.events.AndroidEvent +import com.winlator.cmod.feature.stores.steam.events.EventDispatcher +import com.winlator.cmod.feature.stores.steam.service.SteamService +import com.winlator.cmod.feature.stores.steam.utils.PrefManager +import com.winlator.cmod.feature.stores.steam.utils.getAvatarURL +import com.winlator.cmod.feature.sync.CloudSyncHelper +import com.winlator.cmod.feature.sync.google.CloudSyncManager +import com.winlator.cmod.feature.sync.google.GameSaveBackupManager +import com.winlator.cmod.feature.sync.ui.CloudSavesContent +import com.winlator.cmod.runtime.container.ContainerManager +import com.winlator.cmod.runtime.container.Shortcut +import com.winlator.cmod.runtime.display.XServerDisplayActivity +import com.winlator.cmod.runtime.display.environment.ImageFs +import com.winlator.cmod.runtime.input.ControllerHelper +import com.winlator.cmod.runtime.wine.PeIconExtractor +import com.winlator.cmod.shared.android.ActivityResultHost +import com.winlator.cmod.shared.android.AppTerminationHelper +import com.winlator.cmod.shared.android.DirectoryPickerDialog +import com.winlator.cmod.shared.android.FixedFontScaleAppCompatActivity +import com.winlator.cmod.shared.android.RefreshRateUtils +import com.winlator.cmod.shared.io.StorageUtils +import com.winlator.cmod.shared.io.FileUtils +import com.winlator.cmod.shared.ui.CarouselView +import com.winlator.cmod.shared.ui.dialog.PopupDialog +import com.winlator.cmod.shared.ui.dialog.PopupTextAction +import androidx.compose.foundation.focusGroup +import com.winlator.cmod.shared.ui.focus.controllerFocusGlow +import com.winlator.cmod.shared.ui.focus.controllerMenuInput +import com.winlator.cmod.shared.ui.focus.controllerTextFieldEscape +import com.winlator.cmod.shared.ui.nav.DialogPaneNav +import com.winlator.cmod.shared.ui.nav.LocalPaneNav +import com.winlator.cmod.shared.ui.nav.PANE_DIR_ACTIVATE +import com.winlator.cmod.shared.ui.nav.PANE_DIR_DOWN +import com.winlator.cmod.shared.ui.nav.PANE_DIR_LEFT +import com.winlator.cmod.shared.ui.nav.PANE_DIR_RIGHT +import com.winlator.cmod.shared.ui.nav.PANE_DIR_SECONDARY +import com.winlator.cmod.shared.ui.nav.PANE_DIR_UP +import com.winlator.cmod.shared.ui.nav.PaneNavRegistry +import com.winlator.cmod.shared.ui.nav.paneNavItem +import com.winlator.cmod.shared.ui.FourByTwoGridView +import com.winlator.cmod.shared.ui.JoystickGridScroll +import com.winlator.cmod.shared.ui.JoystickListScroll +import com.winlator.cmod.shared.ui.ListView +import com.winlator.cmod.shared.ui.widget.chasingBorder +import com.winlator.cmod.shared.theme.WinNativeTheme +import dagger.hilt.android.AndroidEntryPoint +import dagger.Lazy +import com.winlator.cmod.feature.stores.steam.enums.EPersonaState +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import javax.inject.Inject +import kotlin.math.abs +import kotlin.math.roundToInt + +// Gamepad/key nav dispatch + glasses combo + immersive bars, split out of UnifiedActivity.kt (behavior-identical). + +internal fun UnifiedActivity.updateGlassesCombo() { + val l2 = l2KeyDown || l2AxisDown + val r2 = r2KeyDown || r2AxisDown + if (l2 && r2) { + if (glassesComboArmed) { + glassesComboArmed = false + if (com.winlator.cmod.runtime.display.GlassesManager.isConnected()) { + openGlassesSignal.tryEmit(Unit) + } + } + } else { + glassesComboArmed = true + } +} + +internal fun UnifiedActivity.applyImmersiveSystemBars(enabled: Boolean) { + window.navigationBarColor = android.graphics.Color.TRANSPARENT + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) { + window.isNavigationBarContrastEnforced = false + } +} + +internal fun UnifiedActivity.moveLibraryFocus( + left: Boolean, + right: Boolean, + up: Boolean, + down: Boolean, +) { + val idx = libraryFocusIndex.value + val count = libraryItemCount + if (count <= 0) return + var newIdx = idx + when (currentLibraryLayoutMode) { + LibraryLayoutMode.GRID_4 -> { + if (left) newIdx = (idx - 1).coerceAtLeast(0) + if (right) newIdx = (idx + 1).coerceAtMost(count - 1) + if (up) newIdx = (idx - 4).coerceAtLeast(0) + if (down) newIdx = (idx + 4).coerceAtMost(count - 1) + } + + LibraryLayoutMode.CAROUSEL -> { + if (left) newIdx = (idx - 1).coerceAtLeast(0) + if (right) newIdx = (idx + 1).coerceAtMost(count - 1) + } + + LibraryLayoutMode.LIST -> { + if (up) newIdx = (idx - 1).coerceAtLeast(0) + if (down) newIdx = (idx + 1).coerceAtMost(count - 1) + } + } + libraryFocusIndex.value = newIdx +} + +internal fun UnifiedActivity.moveStoreFocus( + left: Boolean, + right: Boolean, + up: Boolean, + down: Boolean, +) { + val count = storeItemCount + if (count <= 0) return + val cols = storeColumns + + // Snap to visible content before applying another store-grid move. + var idx = storeFocusIndex.value + val grid = storeGridState + if (grid != null) { + val visibleItems = grid.layoutInfo.visibleItemsInfo + if (visibleItems.isNotEmpty()) { + val firstVisible = visibleItems.first().index + val lastVisible = visibleItems.last().index + if (idx < firstVisible || idx > lastVisible) { + idx = firstVisible + storeFocusIndex.value = idx + return + } + } + } + + var newIdx = idx + if (left) newIdx = (idx - 1).coerceAtLeast(0) + if (right) newIdx = (idx + 1).coerceAtMost(count - 1) + if (up) newIdx = (idx - cols).coerceAtLeast(0) + if (down) newIdx = (idx + cols).coerceAtMost(count - 1) + storeFocusIndex.value = newIdx +} + +internal fun UnifiedActivity.routeDownloadsNav( + left: Boolean, + right: Boolean, + up: Boolean, + down: Boolean, +) { + downloadsNavBridge.controllerActive = true + when { + left -> downloadsNavBridge.left() + right -> downloadsNavBridge.right() + up -> downloadsNavBridge.up() + down -> downloadsNavBridge.down() + } +} + +internal fun UnifiedActivity.gogPseudoId(gameId: String): Int { + val normalized = gameId.hashCode() and 0x1FFFFFFF + return 1_500_000_000 + normalized +} + +internal fun UnifiedActivity.injectKeyEvent(keyCode: Int) { + window.decorView.rootView.dispatchKeyEvent(android.view.KeyEvent(android.view.KeyEvent.ACTION_DOWN, keyCode)) + window.decorView.rootView.dispatchKeyEvent(android.view.KeyEvent(android.view.KeyEvent.ACTION_UP, keyCode)) +} + +internal fun UnifiedActivity.hideImeIfVisible(): Boolean { + val decor = window.decorView + val insets = androidx.core.view.ViewCompat.getRootWindowInsets(decor) ?: return false + if (!insets.isVisible(androidx.core.view.WindowInsetsCompat.Type.ime())) return false + val target = currentFocus ?: decor + androidx.core.view.WindowInsetsControllerCompat(window, target) + .hide(androidx.core.view.WindowInsetsCompat.Type.ime()) + return true +} + +internal fun UnifiedActivity.applySettingsSidebarNav(keyCode: Int) { + when (keyCode) { + android.view.KeyEvent.KEYCODE_DPAD_UP -> moveSettingsItem(-1) + android.view.KeyEvent.KEYCODE_DPAD_DOWN -> moveSettingsItem(1) + android.view.KeyEvent.KEYCODE_DPAD_RIGHT -> enterSettingsContent() + } +} + +internal fun UnifiedActivity.moveSettingsItem(delta: Int) { + val items = SettingsNavItem.entries + val index = items.indexOf(settingsNavBridge.selectedItem) + val next = index + delta + if (next in items.indices) settingsNavBridge.onSelectItem?.invoke(items[next]) +} + +internal fun UnifiedActivity.enterSettingsContent() { + settingsNavBridge.zone = SettingsFocusZone.CONTENT + settingsNavBridge.contentControllerActive = true +} + +internal fun UnifiedActivity.navigateSettingsContent(code: Int) { + settingsNavBridge.contentControllerActive = true + when (code) { + android.view.KeyEvent.KEYCODE_DPAD_LEFT -> settingsNavBridge.contentNavLeft() + android.view.KeyEvent.KEYCODE_DPAD_RIGHT -> settingsNavBridge.contentNavRight() + android.view.KeyEvent.KEYCODE_DPAD_UP -> settingsNavBridge.contentNavUp() + android.view.KeyEvent.KEYCODE_DPAD_DOWN -> settingsNavBridge.contentNavDown() + } +} + +internal fun UnifiedActivity.findVisibleFragmentContainer(view: android.view.View): android.view.View? { + if (view is androidx.fragment.app.FragmentContainerView && view.isShown && view.childCount > 0) { + return view + } + if (view is android.view.ViewGroup) { + for (i in 0 until view.childCount) { + findVisibleFragmentContainer(view.getChildAt(i))?.let { return it } + } + } + return null +} + +internal fun UnifiedActivity.handleSettingsStick(code: Int) { + if (settingsNavBridge.zone == SettingsFocusZone.SIDEBAR) { + applySettingsSidebarNav(code) + return + } + navigateSettingsContent(code) +} + +internal fun UnifiedActivity.handleGuideButton(action: Int, repeatCount: Int) { + when (action) { + android.view.KeyEvent.ACTION_DOWN -> { + if (repeatCount != 0) return + guideHoldRunnable?.let { guideHandler.removeCallbacks(it) } + guideHoldRunnable = null + if (rightDrawerOpen) { + val r = Runnable { openFriendsSignal.tryEmit(Unit) } + guideHoldRunnable = r + guideHandler.postDelayed(r, 400L) + } else if (!menuNavActive && !drawerOpen) { + openFriendsSignal.tryEmit(Unit) + } + } + + android.view.KeyEvent.ACTION_UP -> { + guideHoldRunnable?.let { guideHandler.removeCallbacks(it) } + guideHoldRunnable = null + } + } +} + +internal fun UnifiedActivity.reapplyPreferredRefreshRate() { + if (isFinishing || isDestroyed) return + RefreshRateUtils.applyPreferredRefreshRate(this) +} diff --git a/app/src/main/app/shell/UnifiedActivityLaunch.kt b/app/src/main/app/shell/UnifiedActivityLaunch.kt new file mode 100644 index 000000000..eb211455f --- /dev/null +++ b/app/src/main/app/shell/UnifiedActivityLaunch.kt @@ -0,0 +1,1036 @@ +package com.winlator.cmod.app.shell + +import android.app.Activity +import android.app.PendingIntent +import android.content.Intent +import android.content.res.Configuration +import android.hardware.input.InputManager +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.drawable.BitmapDrawable +import android.net.Uri +import android.os.Bundle +import android.provider.DocumentsContract +import android.util.Log +import androidx.activity.SystemBarStyle +import androidx.activity.compose.BackHandler +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AppCompatActivity +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.EnterTransition +import androidx.compose.animation.ExitTransition +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.FastOutSlowInEasing +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.spring +import androidx.compose.animation.core.tween +import androidx.compose.animation.expandVertically +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.shrinkVertically +import androidx.compose.animation.slideInHorizontally +import androidx.compose.animation.slideOutHorizontally +import androidx.compose.animation.togetherWith +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background +import androidx.compose.foundation.basicMarquee +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.focusable +import androidx.compose.foundation.gestures.detectHorizontalDragGestures +import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsPressedAsState +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items +import androidx.compose.foundation.lazy.grid.itemsIndexed +import androidx.compose.foundation.lazy.grid.rememberLazyGridState +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.outlined.ArrowBack +import androidx.compose.material.icons.automirrored.outlined.ExitToApp +import androidx.compose.material.icons.automirrored.outlined.OpenInNew +import androidx.compose.material.icons.outlined.* +import androidx.compose.material3.* +import androidx.compose.material3.pulltorefresh.PullToRefreshBox +import androidx.compose.runtime.* +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.snapshotFlow +import androidx.compose.runtime.snapshots.SnapshotStateMap +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.zIndex +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusProperties +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.focus.focusTarget +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.geometry.CornerRadius +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.TileMode +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.input.pointer.PointerEventPass +import androidx.compose.ui.input.pointer.PointerEventType +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties +import androidx.lifecycle.lifecycleScope +import androidx.navigation.NavHostController +import androidx.navigation.NavType +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import androidx.navigation.navArgument +import coil.compose.AsyncImage +import coil.imageLoader +import coil.request.ImageRequest +import com.winlator.cmod.BuildConfig +import com.winlator.cmod.R +import com.winlator.cmod.app.PluviaApp +import com.winlator.cmod.app.db.PluviaDatabase +import com.winlator.cmod.app.service.DownloadService +import com.winlator.cmod.app.service.download.DownloadCoordinator +import com.winlator.cmod.app.update.UpdateChecker +import com.winlator.cmod.feature.settings.InputControlsFragment +import com.winlator.cmod.feature.settings.SettingsFocusZone +import com.winlator.cmod.feature.settings.SettingsHost +import com.winlator.cmod.feature.settings.SettingsNavBridge +import com.winlator.cmod.feature.settings.SettingsNavItem +import com.winlator.cmod.feature.setup.SetupWizardActivity +import com.winlator.cmod.feature.shortcuts.LibraryShortcutUtils +import com.winlator.cmod.feature.shortcuts.LibraryShortcutArtwork +import com.winlator.cmod.feature.shortcuts.ShortcutBroadcastReceiver +import com.winlator.cmod.feature.shortcuts.ShortcutSettingsComposeDialog +import com.winlator.cmod.feature.shortcuts.ShortcutsFragment +import com.winlator.cmod.feature.stores.common.StoreArtworkCache +import com.winlator.cmod.feature.stores.epic.data.EpicCredentials +import com.winlator.cmod.feature.stores.epic.data.EpicGame +import com.winlator.cmod.feature.stores.epic.data.EpicGameToken +import com.winlator.cmod.feature.stores.epic.service.EpicAuthManager +import com.winlator.cmod.feature.stores.epic.service.EpicCloudSavesManager +import com.winlator.cmod.feature.stores.epic.service.EpicConstants +import com.winlator.cmod.feature.stores.epic.service.EpicDownloadManager +import com.winlator.cmod.feature.stores.epic.service.EpicGameLauncher +import com.winlator.cmod.feature.stores.epic.service.EpicManager +import com.winlator.cmod.feature.stores.epic.service.EpicService +import com.winlator.cmod.feature.stores.epic.service.EpicUpdateInfo +import com.winlator.cmod.feature.stores.epic.ui.auth.EpicOAuthActivity +import com.winlator.cmod.feature.stores.gog.data.GOGDlcInfo +import com.winlator.cmod.feature.stores.gog.data.GOGGame +import com.winlator.cmod.feature.stores.gog.data.LibraryItem +import com.winlator.cmod.feature.stores.gog.service.GOGAuthManager +import com.winlator.cmod.feature.stores.gog.service.GOGConstants +import com.winlator.cmod.feature.stores.gog.service.GOGManifestSizes +import com.winlator.cmod.feature.stores.gog.service.GOGService +import com.winlator.cmod.feature.stores.gog.service.GOGUpdateInfo +import com.winlator.cmod.feature.stores.gog.ui.auth.GOGOAuthActivity +import com.winlator.cmod.feature.stores.steam.SteamLoginActivity +import com.winlator.cmod.feature.stores.steam.data.DepotInfo +import com.winlator.cmod.feature.stores.steam.data.DownloadInfo +import com.winlator.cmod.feature.stores.steam.data.SteamApp +import com.winlator.cmod.feature.stores.steam.enums.DownloadPhase +import com.winlator.cmod.feature.stores.steam.events.AndroidEvent +import com.winlator.cmod.feature.stores.steam.events.EventDispatcher +import com.winlator.cmod.feature.stores.steam.service.SteamService +import com.winlator.cmod.feature.stores.steam.utils.PrefManager +import com.winlator.cmod.feature.stores.steam.utils.getAvatarURL +import com.winlator.cmod.feature.sync.CloudSyncHelper +import com.winlator.cmod.feature.sync.google.CloudSyncManager +import com.winlator.cmod.feature.sync.google.GameSaveBackupManager +import com.winlator.cmod.feature.sync.ui.CloudSavesContent +import com.winlator.cmod.runtime.container.ContainerManager +import com.winlator.cmod.runtime.container.Shortcut +import com.winlator.cmod.runtime.display.XServerDisplayActivity +import com.winlator.cmod.runtime.display.environment.ImageFs +import com.winlator.cmod.runtime.input.ControllerHelper +import com.winlator.cmod.runtime.wine.PeIconExtractor +import com.winlator.cmod.shared.android.ActivityResultHost +import com.winlator.cmod.shared.android.AppTerminationHelper +import com.winlator.cmod.shared.android.DirectoryPickerDialog +import com.winlator.cmod.shared.android.FixedFontScaleAppCompatActivity +import com.winlator.cmod.shared.android.RefreshRateUtils +import com.winlator.cmod.shared.io.StorageUtils +import com.winlator.cmod.shared.io.FileUtils +import com.winlator.cmod.shared.ui.CarouselView +import com.winlator.cmod.shared.ui.dialog.PopupDialog +import com.winlator.cmod.shared.ui.dialog.PopupTextAction +import androidx.compose.foundation.focusGroup +import com.winlator.cmod.shared.ui.focus.controllerFocusGlow +import com.winlator.cmod.shared.ui.focus.controllerMenuInput +import com.winlator.cmod.shared.ui.focus.controllerTextFieldEscape +import com.winlator.cmod.shared.ui.nav.DialogPaneNav +import com.winlator.cmod.shared.ui.nav.LocalPaneNav +import com.winlator.cmod.shared.ui.nav.PANE_DIR_ACTIVATE +import com.winlator.cmod.shared.ui.nav.PANE_DIR_DOWN +import com.winlator.cmod.shared.ui.nav.PANE_DIR_LEFT +import com.winlator.cmod.shared.ui.nav.PANE_DIR_RIGHT +import com.winlator.cmod.shared.ui.nav.PANE_DIR_SECONDARY +import com.winlator.cmod.shared.ui.nav.PANE_DIR_UP +import com.winlator.cmod.shared.ui.nav.PaneNavRegistry +import com.winlator.cmod.shared.ui.nav.paneNavItem +import com.winlator.cmod.shared.ui.FourByTwoGridView +import com.winlator.cmod.shared.ui.JoystickGridScroll +import com.winlator.cmod.shared.ui.JoystickListScroll +import com.winlator.cmod.shared.ui.ListView +import com.winlator.cmod.shared.ui.widget.chasingBorder +import com.winlator.cmod.shared.theme.WinNativeTheme +import dagger.hilt.android.AndroidEntryPoint +import dagger.Lazy +import com.winlator.cmod.feature.stores.steam.enums.EPersonaState +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import javax.inject.Inject +import kotlin.math.abs +import kotlin.math.roundToInt + +// Game launching (Steam/Epic/GOG/custom) + wine command builders, split out of UnifiedActivity.kt (behavior-identical). + +// Game launch with drive-aware mapping +internal fun UnifiedActivity.launchSteamGame( + context: android.content.Context, + containerManager: ContainerManager, + app: SteamApp, + joinConnect: String? = null, +) { + lifecycleScope.launch(Dispatchers.IO) { + val gameInstallPath = SteamService.getAppDirPath(app.id) + val gameDir = java.io.File(gameInstallPath) + if (!gameDir.exists()) { + withContext(Dispatchers.Main) { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + "Game not installed: ${app.name}", + android.widget.Toast.LENGTH_SHORT, + ) + } + return@launch + } + + val shortcut = + containerManager.loadShortcuts().find { + it.getExtra("game_source") == "STEAM" && it.getExtra("app_id") == app.id.toString() + } + val detectedLaunchExecutable = SteamService.getInstalledExe(app.id) + + if (shortcut != null) { + if (!SetupWizardActivity.isContainerUsable(context, shortcut.container)) { + withContext(Dispatchers.Main) { + SetupWizardActivity.promptToInstallWineOrCreateContainer( + context, + shortcut.container.wineVersion, + ) + } + return@launch + } + normalizeContainerDrives(shortcut.container) + shortcut.putExtra("game_source", "STEAM") + shortcut.putExtra("game_install_path", gameInstallPath) + val existingLaunchExecutable = shortcut.getExtra("launch_exe_path") + if (existingLaunchExecutable.isNullOrBlank() && detectedLaunchExecutable.isNotBlank()) { + shortcut.putExtra("launch_exe_path", detectedLaunchExecutable) + } + val loaderExec = "wine \"C:\\\\Program Files (x86)\\\\Steam\\\\steamclient_loader_x64.exe\"" + val lines = + com.winlator.cmod.shared.io.FileUtils + .readLines(shortcut.file) + val rewritten = StringBuilder() + var execUpdated = false + for (line in lines) { + if (line.startsWith("Exec=")) { + rewritten.append("Exec=").append(loaderExec).append("\n") + execUpdated = true + } else { + rewritten.append(line).append("\n") + } + } + if (!execUpdated) { + rewritten.append("Exec=").append(loaderExec).append("\n") + } + com.winlator.cmod.shared.io.FileUtils + .writeString(shortcut.file, rewritten.toString()) + shortcut.saveData() + val intent = Intent(context, XServerDisplayActivity::class.java) + intent.putExtra("container_id", shortcut.container.id) + intent.putExtra("shortcut_path", shortcut.file.path) + intent.putExtra("shortcut_name", shortcut.name) + if (!joinConnect.isNullOrBlank()) intent.putExtra("steam_join_connect", joinConnect) + withContext(Dispatchers.Main) { + launchGame(context, intent) + } + } else { + val container = SetupWizardActivity.getPreferredGameContainer(context, containerManager) + + if (container == null) { + withContext(Dispatchers.Main) { + SetupWizardActivity.promptToInstallWineOrCreateContainer(context) + } + return@launch + } + + normalizeContainerDrives(container) + + val execPath = "wine \"C:\\\\Program Files (x86)\\\\Steam\\\\steamclient_loader_x64.exe\"" + + // Generate a shortcut dynamically + val desktopDir = container.getDesktopDir() + if (!desktopDir.exists()) desktopDir.mkdirs() + val shortcutFile = java.io.File(desktopDir, "${app.name.replace("/", "_")}.desktop") + val content = java.lang.StringBuilder() + content.append("[Desktop Entry]\n") + content.append("Type=Application\n") + content.append("Name=${app.name}\n") + content.append("Exec=$execPath\n") + content.append("Icon=steam_icon_${app.id}\n") + content.append("\n[Extra Data]\n") + content.append("game_source=STEAM\n") + content.append("app_id=${app.id}\n") + content.append("container_id=${container.id}\n") + content.append("game_install_path=${gameInstallPath}\n") + content.append("launch_exe_path=${detectedLaunchExecutable}\n") + content.append("use_container_defaults=1\n") + + com.winlator.cmod.shared.io.FileUtils + .writeString(shortcutFile, content.toString()) + + container.saveData() + + val intent = Intent(context, XServerDisplayActivity::class.java) + intent.putExtra("container_id", container.id) + intent.putExtra("shortcut_path", shortcutFile.path) + intent.putExtra("shortcut_name", app.name) + if (!joinConnect.isNullOrBlank()) intent.putExtra("steam_join_connect", joinConnect) + withContext(Dispatchers.Main) { + launchGame(context, intent) + } + } + } +} + +internal fun UnifiedActivity.launchEpicGame( + context: android.content.Context, + containerManager: ContainerManager, + app: EpicGame, +) { + lifecycleScope.launch(Dispatchers.IO) { + val gameInstallPath = app.installPath.takeIf { it.isNotEmpty() } ?: EpicConstants.getGameInstallPath(context, app.appName) + val gameDir = java.io.File(gameInstallPath) + if (!gameDir.exists()) { + withContext(Dispatchers.Main) { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + "Game not installed: ${app.title}", + android.widget.Toast.LENGTH_SHORT, + ) + } + return@launch + } + + // Try to find an existing shortcut first (preserves per-game settings) + val existingShortcut = + containerManager.loadShortcuts().find { + it.getExtra("game_source") == "EPIC" && it.getExtra("app_id") == app.id.toString() + } + + if (existingShortcut != null) { + val launchContainer = + resolveShortcutLaunchContainer(containerManager, existingShortcut) + ?: existingShortcut.container + if (!SetupWizardActivity.isContainerUsable(context, launchContainer)) { + withContext(Dispatchers.Main) { + SetupWizardActivity.promptToInstallWineOrCreateContainer( + context, + launchContainer.wineVersion, + ) + } + return@launch + } + // Existing shortcut found: preserve per-game settings and update the mapped install path + val shortcut = existingShortcut + val epicDisplayName = + app.title.takeIf { it.isNotBlank() } + ?: shortcut.name.takeIf { it.isNotBlank() } + ?: app.appName + // Ensure game_install_path is always up-to-date + shortcut.putExtra("game_install_path", gameInstallPath) + shortcut.putExtra("container_id", launchContainer.id.toString()) + repairShortcutDisplayNameIfNeeded(shortcut, epicDisplayName, app.appName, app.id.toString()) + normalizeContainerDrives(launchContainer) + + // Repair broken Exec line if the executable is missing or still points at a legacy placeholder mapping. + val currentPath = shortcut.path + if (currentPath == null || currentPath == "D:\\" || currentPath == "D:\\\\" || + currentPath == "A:\\" || currentPath == "A:\\\\" || + currentPath.startsWith("A:\\") + ) { + val newExecCmd = + buildStoreWineExecCommandForSelectedExe( + launchContainer, + "EPIC", + gameInstallPath, + shortcut.getExtra("launch_exe_path"), + ) ?: run { + val exePath = EpicService.getInstalledExe(app.id) + if (exePath.isNotEmpty()) { + shortcut.putExtra("launch_exe_path", exePath) + buildStoreWineExecCommand( + launchContainer, + "EPIC", + gameInstallPath, + java.io.File(gameInstallPath, exePath.replace("\\", "/")), + ) + } else { + val exeFile = findGameExe(gameDir) + if (exeFile != null) { + shortcut.putExtra("launch_exe_path", exeFile.absolutePath) + buildStoreWineExecCommand(launchContainer, "EPIC", gameInstallPath, exeFile) + } else { + null + } + } + } + if (newExecCmd != null) { + // Rewrite the Exec line in the .desktop file while preserving all other content + val lines = + com.winlator.cmod.shared.io.FileUtils + .readLines(shortcut.file) + val sb = StringBuilder() + for (line in lines) { + if (line.startsWith("Exec=")) { + sb.append("Exec=$newExecCmd\n") + } else { + sb.append(line).append("\n") + } + } + com.winlator.cmod.shared.io.FileUtils + .writeString(shortcut.file, sb.toString()) + } + } + + shortcut.saveData() + val launchShortcutFile = ensureShortcutFileInContainer(shortcut, launchContainer) + + // Provision the EOS overlay into this container. Best-effort — failures are + // non-fatal (games without the EOS SDK ignore it; games with the SDK still run + // without the in-game HUD). Tokens must be staged inside the prefix because + // the dosdevices map doesn't expose the app cache dir on any drive letter. + runCatching { + EpicService.installOverlay(context, launchContainer) + }.onFailure { + Log.w("EPIC", "EOS overlay install failed for ${app.appName}; launching anyway", it) + } + + val launchArgsResult = + EpicGameLauncher.buildLaunchParameters( + context = context, + game = app, + container = launchContainer, + ) + launchArgsResult.exceptionOrNull()?.let { err -> + // The launch can still proceed (offline-tolerant titles, single-player non-DRM + // games), so we don't abort — but surface the failure prominently so users + // know why a DRM/online title may bounce to its login screen. + Log.e("EPIC", "Failed to build Epic launch parameters for ${app.appName}: ${err.message}", err) + withContext(Dispatchers.Main) { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + "Could not refresh Epic launch token: ${err.message ?: "unknown error"}", + android.widget.Toast.LENGTH_LONG, + ) + } + } + val args = launchArgsResult.getOrNull()?.joinToString(" ") ?: "" + + val intent = Intent(context, XServerDisplayActivity::class.java) + intent.putExtra("container_id", launchContainer.id) + intent.putExtra("shortcut_path", launchShortcutFile.path) + intent.putExtra("shortcut_name", epicDisplayName) + intent.putExtra("extra_exec_args", args) // Pass fresh tokens + withContext(Dispatchers.Main) { + launchGame(context, intent) + } + } else { + // No existing shortcut — create a new one + val exePath = EpicService.getInstalledExe(app.id) + val container = SetupWizardActivity.getPreferredGameContainer(context, containerManager) + + if (container == null) { + withContext(Dispatchers.Main) { + SetupWizardActivity.promptToInstallWineOrCreateContainer(context) + } + return@launch + } + + normalizeContainerDrives(container) + val execCmd = + if (exePath.isNotEmpty()) { + buildStoreWineExecCommand( + container, + "EPIC", + gameInstallPath, + java.io.File(gameInstallPath, exePath.replace("\\", "/")), + ) + } else { + val exeFile = findGameExe(gameDir) + if (exeFile != null) { + buildStoreWineExecCommand(container, "EPIC", gameInstallPath, exeFile) + } else { + "wine \"explorer.exe\"" + } + } + + val desktopDir = container.getDesktopDir() + if (!desktopDir.exists()) desktopDir.mkdirs() + val shortcutFile = java.io.File(desktopDir, "${app.appName}.desktop") + val content = java.lang.StringBuilder() + content.append("[Desktop Entry]\n") + content.append("Type=Application\n") + content.append("Name=${app.title}\n") + content.append("Exec=$execCmd\n") + content.append("Icon=epic_icon_${app.id}\n") + content.append("\n[Extra Data]\n") + content.append("game_source=EPIC\n") + content.append("app_id=${app.id}\n") + if (app.catalogId.isNotEmpty()) { + // Persist catalog_id so EpicGameFixHelper / GameFixes can dispatch the + // per-catalog registry/env/folder fixes without a DB round-trip on launch. + content.append("catalog_id=${app.catalogId}\n") + } + content.append("container_id=${container.id}\n") + content.append("game_install_path=${gameInstallPath}\n") + if (exePath.isNotEmpty()) { + content.append("launch_exe_path=${exePath}\n") + } + content.append("use_container_defaults=1\n") + + com.winlator.cmod.shared.io.FileUtils + .writeString(shortcutFile, content.toString()) + + container.saveData() + + // Best-effort EOS overlay provisioning — see existing-shortcut branch above. + runCatching { + EpicService.installOverlay(context, container) + }.onFailure { + Log.w("EPIC", "EOS overlay install failed for ${app.appName}; launching anyway", it) + } + + val launchArgsResult = + EpicGameLauncher.buildLaunchParameters( + context = context, + game = app, + container = container, + ) + launchArgsResult.exceptionOrNull()?.let { err -> + Log.e("EPIC", "Failed to build Epic launch parameters for ${app.appName}: ${err.message}", err) + withContext(Dispatchers.Main) { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + "Could not refresh Epic launch token: ${err.message ?: "unknown error"}", + android.widget.Toast.LENGTH_LONG, + ) + } + } + val args = launchArgsResult.getOrNull()?.joinToString(" ") ?: "" + + val intent = Intent(context, XServerDisplayActivity::class.java) + intent.putExtra("container_id", container.id) + intent.putExtra("shortcut_path", shortcutFile.path) + intent.putExtra("shortcut_name", app.title) + intent.putExtra("extra_exec_args", args) // Pass fresh tokens + withContext(Dispatchers.Main) { + launchGame(context, intent) + } + } + } +} + +internal fun UnifiedActivity.launchGogGame( + context: android.content.Context, + containerManager: ContainerManager, + app: GOGGame, +) { + lifecycleScope.launch(Dispatchers.IO) { + val gameInstallPath = app.installPath.takeIf { it.isNotEmpty() } ?: GOGConstants.getGameInstallPath(app.title) + val gameDir = java.io.File(gameInstallPath) + if (!gameDir.exists()) { + withContext(Dispatchers.Main) { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + "Game not installed: ${app.title}", + android.widget.Toast.LENGTH_SHORT, + ) + } + return@launch + } + + val existingShortcut = + containerManager.loadShortcuts().find { + it.getExtra("game_source") == "GOG" && it.getExtra("gog_id") == app.id + } + + val gogAppId = "GOG_${app.id}" + GOGService.syncCloudSaves(context, gogAppId) + + if (existingShortcut != null) { + val shortcut = existingShortcut + if (!SetupWizardActivity.isContainerUsable(context, shortcut.container)) { + withContext(Dispatchers.Main) { + SetupWizardActivity.promptToInstallWineOrCreateContainer( + context, + shortcut.container.wineVersion, + ) + } + return@launch + } + shortcut.putExtra("game_install_path", gameInstallPath) + normalizeContainerDrives(shortcut.container) + + // Repair broken Exec line if the executable is missing or still points at a legacy placeholder mapping. + val currentPath = shortcut.path + if (currentPath == null || currentPath == "D:\\" || currentPath == "D:\\\\" || + currentPath == "A:\\" || currentPath == "A:\\\\" || + currentPath.startsWith("A:\\") + ) { + val newExecCmd = + buildStoreWineExecCommandForSelectedExe( + shortcut.container, + "GOG", + gameInstallPath, + shortcut.getExtra("launch_exe_path"), + ) ?: run { + val libraryItem = + LibraryItem("GOG_${app.id}", app.title, com.winlator.cmod.feature.stores.steam.enums.GameSource.GOG) + val exePath = GOGService.getInstalledExe(libraryItem) + if (exePath.isNotEmpty()) { + shortcut.putExtra("launch_exe_path", exePath) + buildStoreWineExecCommand( + shortcut.container, + "GOG", + gameInstallPath, + java.io.File(gameInstallPath, exePath.replace("\\", "/")), + ) + } else { + val exeFile = findGameExe(gameDir) + if (exeFile != null) { + shortcut.putExtra("launch_exe_path", exeFile.absolutePath) + buildStoreWineExecCommand(shortcut.container, "GOG", gameInstallPath, exeFile) + } else { + null + } + } + } + if (newExecCmd != null) { + val lines = + com.winlator.cmod.shared.io.FileUtils + .readLines(shortcut.file) + val sb = StringBuilder() + for (line in lines) { + if (line.startsWith("Exec=")) { + sb.append("Exec=$newExecCmd\n") + } else { + sb.append(line).append("\n") + } + } + com.winlator.cmod.shared.io.FileUtils + .writeString(shortcut.file, sb.toString()) + } + } + + shortcut.saveData() + + val intent = Intent(context, XServerDisplayActivity::class.java) + intent.putExtra("container_id", shortcut.container.id) + intent.putExtra("shortcut_path", shortcut.file.path) + intent.putExtra("shortcut_name", shortcut.name) + withContext(Dispatchers.Main) { + launchGame(context, intent) + } + return@launch + } + + val libraryItem = LibraryItem("GOG_${app.id}", app.title, com.winlator.cmod.feature.stores.steam.enums.GameSource.GOG) + val exePath = GOGService.getInstalledExe(libraryItem) + + val container = SetupWizardActivity.getPreferredGameContainer(context, containerManager) + + if (container == null) { + withContext(Dispatchers.Main) { + SetupWizardActivity.promptToInstallWineOrCreateContainer(context) + } + return@launch + } + + normalizeContainerDrives(container) + val execCmd = + if (exePath.isNotEmpty()) { + buildStoreWineExecCommand( + container, + "GOG", + gameInstallPath, + java.io.File(gameInstallPath, exePath.replace("\\", "/")), + ) + } else { + val exeFile = findGameExe(gameDir) + if (exeFile != null) { + buildStoreWineExecCommand(container, "GOG", gameInstallPath, exeFile) + } else { + "wine \"explorer.exe\"" + } + } + + val desktopDir = container.getDesktopDir() + if (!desktopDir.exists()) desktopDir.mkdirs() + val shortcutFile = java.io.File(desktopDir, "${app.title.replace("/", "_")}.desktop") + val content = java.lang.StringBuilder() + content.append("[Desktop Entry]\n") + content.append("Type=Application\n") + content.append("Name=${app.title}\n") + content.append("Exec=$execCmd\n") + content.append("Icon=gog_icon_${app.id}\n") + content.append("\n[Extra Data]\n") + content.append("game_source=GOG\n") + content.append("gog_id=${app.id}\n") + content.append("app_id=${gogPseudoId(app.id)}\n") + content.append("container_id=${container.id}\n") + content.append("game_install_path=${gameInstallPath}\n") + if (exePath.isNotEmpty()) { + content.append("launch_exe_path=${exePath}\n") + } + content.append("use_container_defaults=1\n") + + com.winlator.cmod.shared.io.FileUtils + .writeString(shortcutFile, content.toString()) + container.saveData() + + val intent = Intent(context, XServerDisplayActivity::class.java) + intent.putExtra("container_id", container.id) + intent.putExtra("shortcut_path", shortcutFile.path) + intent.putExtra("shortcut_name", app.title) + withContext(Dispatchers.Main) { + launchGame(context, intent) + } + } +} + +internal fun UnifiedActivity.normalizeContainerDrives(container: com.winlator.cmod.runtime.container.Container) { + container.drives = + com.winlator.cmod.runtime.wine.WineUtils.normalizePersistentDrives( + this, + container.drives ?: com.winlator.cmod.runtime.container.Container.DEFAULT_DRIVES, + false, + ) +} + +internal fun UnifiedActivity.resolveShortcutLaunchContainer( + containerManager: ContainerManager, + shortcut: Shortcut, +): com.winlator.cmod.runtime.container.Container? { + val overrideContainerId = shortcut.getExtra("container_id").toIntOrNull()?.takeIf { it > 0 } + return overrideContainerId + ?.let { containerManager.getContainerById(it) } + ?: shortcut.container +} + +internal fun UnifiedActivity.ensureShortcutFileInContainer( + shortcut: Shortcut, + targetContainer: com.winlator.cmod.runtime.container.Container, +): java.io.File { + val targetDesktopDir = targetContainer.getDesktopDir() + val alreadyInTarget = + runCatching { + shortcut.file.parentFile?.canonicalFile == targetDesktopDir.canonicalFile + }.getOrDefault(false) + + if (alreadyInTarget) return shortcut.file + + if (!targetDesktopDir.exists()) targetDesktopDir.mkdirs() + shortcut.putExtra("container_id", targetContainer.id.toString()) + shortcut.saveData() + + val targetFile = java.io.File(targetDesktopDir, shortcut.file.name) + runCatching { + com.winlator.cmod.shared.io.FileUtils.copy(shortcut.file, targetFile) + val lnkFileName = shortcut.file.name.substringBeforeLast(".desktop") + ".lnk" + val oldLnkFile = java.io.File(shortcut.file.parentFile, lnkFileName) + if (oldLnkFile.exists()) { + com.winlator.cmod.shared.io.FileUtils.copy(oldLnkFile, java.io.File(targetDesktopDir, lnkFileName)) + oldLnkFile.delete() + } + shortcut.file.delete() + }.onFailure { + Log.w("EPIC", "Failed to move Epic shortcut ${shortcut.file.name} to container ${targetContainer.id}; launching original file", it) + return shortcut.file + } + + return targetFile +} + +internal fun UnifiedActivity.buildStoreWineExecCommand( + container: com.winlator.cmod.runtime.container.Container?, + source: String, + gameInstallPath: String, + exeFile: java.io.File, +): String { + val windowsPath = + container?.let { + com.winlator.cmod.runtime.wine.WineUtils.getDriveCGameWindowsPath( + it, + source, + gameInstallPath, + exeFile.absolutePath, + ) + } ?: run { + val relativePath = + try { + exeFile.relativeTo(java.io.File(gameInstallPath)).path.replace("/", "\\") + } catch (_: Exception) { + exeFile.name + } + val linkName = + com.winlator.cmod.runtime.wine.WineUtils.getDriveCGameLinkName(gameInstallPath) + "C:\\WinNative\\Games\\$source\\$linkName\\$relativePath" + } + return "wine \"$windowsPath\"" +} + +internal fun UnifiedActivity.buildStoreWineExecCommandForSelectedExe( + container: com.winlator.cmod.runtime.container.Container?, + source: String, + gameInstallPath: String, + selectedExePath: String?, +): String? { + if (selectedExePath.isNullOrBlank()) return null + + val selectedExe = java.io.File(selectedExePath) + if (!selectedExe.isFile) return null + + val normalizedBaseDir = + java.io + .File(gameInstallPath) + .absolutePath + .removeSuffix("/") + val normalizedExePath = selectedExe.absolutePath + return if (normalizedExePath == normalizedBaseDir || normalizedExePath.startsWith("$normalizedBaseDir/")) { + buildStoreWineExecCommand(container, source, gameInstallPath, selectedExe) + } else { + val hostPath = normalizedExePath.replace("/", "\\\\").let { if (it.startsWith("\\")) it else "\\$it" } + "wine \"Z:${hostPath}\"" + } +} + +// Launch custom game by shortcut name +internal fun UnifiedActivity.launchCustomGame( + context: android.content.Context, + containerManager: ContainerManager, + gameName: String, +) { + lifecycleScope.launch(Dispatchers.IO) { + val allShortcuts = containerManager.loadShortcuts() + + // Try matching by app_id (for non-official Steam/Epic), custom_name, or filename + var shortcut = + allShortcuts.find { it.getExtra("app_id") == gameName } + ?: allShortcuts.find { it.getExtra("custom_name") == gameName } + ?: allShortcuts.find { it.name == gameName } + ?: allShortcuts.find { it.name == gameName.replace("/", "_").replace("\\", "_") } + + // If still not found, try matching by looking at the safe filename directly + if (shortcut == null) { + val safeName = gameName.replace("/", "_").replace("\\", "_") + for (container in containerManager.containers) { + val desktopFile = java.io.File(container.getDesktopDir(), "$safeName.desktop") + if (desktopFile.exists()) { + shortcut = + com.winlator.cmod.runtime.container + .Shortcut(container, desktopFile) + break + } + } + } + + if (shortcut == null) { + withContext(Dispatchers.Main) { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + "Custom game shortcut not found: $gameName", + android.widget.Toast.LENGTH_SHORT, + ) + } + return@launch + } + + if (com.winlator.cmod.feature.retro.RetroShortcuts.isRetroShortcut(shortcut)) { + // Asks RetroShortcuts which launcher this shortcut needs rather + // than reasoning about it here. Listing the embedded paths in two + // places is what sent 3D-enabled games to the libretro core: this + // caller knew about PS2 and Dolphin but not about the 3D engine. + if (com.winlator.cmod.feature.retro.RetroShortcuts.usesEmbeddedLauncher(context, shortcut)) { + withContext(Dispatchers.Main) { + com.winlator.cmod.feature.retro.RetroShortcuts.launch(context, shortcut) + } + return@launch + } + val retroIntent = com.winlator.cmod.feature.retro.RetroShortcuts.launchIntent(context, shortcut) + withContext(Dispatchers.Main) { launchGame(context, retroIntent) } + return@launch + } + + // Backfill custom_name if missing (legacy shortcuts) + if (shortcut.getExtra("custom_name").isEmpty()) { + shortcut.putExtra("custom_name", gameName) + shortcut.saveData() + } + + // Refresh storage-root mappings; custom game paths launch through the drive_c game symlink. + val gameFolder = shortcut.getExtra("custom_game_folder", "") + if (gameFolder.isNotEmpty()) { + normalizeContainerDrives(shortcut.container) + shortcut.container.saveData() + } + val intent = Intent(context, XServerDisplayActivity::class.java) + intent.putExtra("container_id", shortcut.container.id) + intent.putExtra("shortcut_path", shortcut.file.path) + intent.putExtra("shortcut_name", gameName) + withContext(Dispatchers.Main) { + launchGame(context, intent) + } + } +} + +internal fun UnifiedActivity.launchGame( + context: android.content.Context, + intent: Intent, +) { + DownloadService.clearCompletedDownloads() + context.startActivity(intent) + // Suppress the default activity transition so the preloader stays seamless + if (context is android.app.Activity) { + com.winlator.cmod.shared.android.AppUtils + .applyOpenActivityTransition(context, 0, 0) + } +} + +internal fun UnifiedActivity.findGameExe(dir: java.io.File): java.io.File? { + // BFS: check each directory level fully before going deeper + val exclusions = + listOf( + "unins", + "redist", + "setup", + "dotnet", + "vcredist", + "dxsetup", + "helper", + "crash", + "ue4prereq", + "dxwebsetup", + "launcher", + ) + + var currentDirs = listOf(dir) + var depth = 0 + var fallbackExe: java.io.File? = null + + while (currentDirs.isNotEmpty() && depth <= 4) { + val nextDirs = mutableListOf() + val candidates = mutableListOf() + + for (d in currentDirs) { + val children = d.listFiles() ?: continue + for (f in children) { + if (f.isDirectory) { + nextDirs.add(f) + } else if (f.extension.equals("exe", ignoreCase = true)) { + val name = f.name.lowercase() + if (exclusions.none { name.contains(it) }) { + candidates.add(f) + } + } + } + } + + // Prefer 64-bit executable candidates at the current depth + val exe64 = + candidates.find { + it.name.lowercase().contains("64") || + it.parentFile + ?.name + ?.lowercase() + ?.contains("64") == true + } + if (exe64 != null) return exe64 + + // Collect the first valid candidate as a fallback + if (fallbackExe == null && candidates.isNotEmpty()) { + fallbackExe = candidates.first() + } + + currentDirs = nextDirs + depth++ + } + return fallbackExe +} diff --git a/app/src/main/app/shell/UnifiedActivityShortcuts.kt b/app/src/main/app/shell/UnifiedActivityShortcuts.kt new file mode 100644 index 000000000..26efbd45e --- /dev/null +++ b/app/src/main/app/shell/UnifiedActivityShortcuts.kt @@ -0,0 +1,642 @@ +package com.winlator.cmod.app.shell +import com.winlator.cmod.app.shell.UnifiedActivity.ArtworkCacheId + +import android.app.Activity +import android.app.PendingIntent +import android.content.Intent +import android.content.res.Configuration +import android.hardware.input.InputManager +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.drawable.BitmapDrawable +import android.net.Uri +import android.os.Bundle +import android.provider.DocumentsContract +import android.util.Log +import androidx.activity.SystemBarStyle +import androidx.activity.compose.BackHandler +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AppCompatActivity +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.EnterTransition +import androidx.compose.animation.ExitTransition +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.FastOutSlowInEasing +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.spring +import androidx.compose.animation.core.tween +import androidx.compose.animation.expandVertically +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.shrinkVertically +import androidx.compose.animation.slideInHorizontally +import androidx.compose.animation.slideOutHorizontally +import androidx.compose.animation.togetherWith +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background +import androidx.compose.foundation.basicMarquee +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.focusable +import androidx.compose.foundation.gestures.detectHorizontalDragGestures +import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsPressedAsState +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items +import androidx.compose.foundation.lazy.grid.itemsIndexed +import androidx.compose.foundation.lazy.grid.rememberLazyGridState +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.outlined.ArrowBack +import androidx.compose.material.icons.automirrored.outlined.ExitToApp +import androidx.compose.material.icons.automirrored.outlined.OpenInNew +import androidx.compose.material.icons.outlined.* +import androidx.compose.material3.* +import androidx.compose.material3.pulltorefresh.PullToRefreshBox +import androidx.compose.runtime.* +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.snapshotFlow +import androidx.compose.runtime.snapshots.SnapshotStateMap +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.zIndex +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusProperties +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.focus.focusTarget +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.geometry.CornerRadius +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.TileMode +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.input.pointer.PointerEventPass +import androidx.compose.ui.input.pointer.PointerEventType +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties +import androidx.lifecycle.lifecycleScope +import androidx.navigation.NavHostController +import androidx.navigation.NavType +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import androidx.navigation.navArgument +import coil.compose.AsyncImage +import coil.imageLoader +import coil.request.ImageRequest +import com.winlator.cmod.BuildConfig +import com.winlator.cmod.R +import com.winlator.cmod.app.PluviaApp +import com.winlator.cmod.app.db.PluviaDatabase +import com.winlator.cmod.app.service.DownloadService +import com.winlator.cmod.app.service.download.DownloadCoordinator +import com.winlator.cmod.app.update.UpdateChecker +import com.winlator.cmod.feature.settings.InputControlsFragment +import com.winlator.cmod.feature.settings.SettingsFocusZone +import com.winlator.cmod.feature.settings.SettingsHost +import com.winlator.cmod.feature.settings.SettingsNavBridge +import com.winlator.cmod.feature.settings.SettingsNavItem +import com.winlator.cmod.feature.setup.SetupWizardActivity +import com.winlator.cmod.feature.shortcuts.LibraryShortcutUtils +import com.winlator.cmod.feature.shortcuts.LibraryShortcutArtwork +import com.winlator.cmod.feature.shortcuts.ShortcutBroadcastReceiver +import com.winlator.cmod.feature.shortcuts.ShortcutSettingsComposeDialog +import com.winlator.cmod.feature.shortcuts.ShortcutsFragment +import com.winlator.cmod.feature.stores.common.StoreArtworkCache +import com.winlator.cmod.feature.stores.epic.data.EpicCredentials +import com.winlator.cmod.feature.stores.epic.data.EpicGame +import com.winlator.cmod.feature.stores.epic.data.EpicGameToken +import com.winlator.cmod.feature.stores.epic.service.EpicAuthManager +import com.winlator.cmod.feature.stores.epic.service.EpicCloudSavesManager +import com.winlator.cmod.feature.stores.epic.service.EpicConstants +import com.winlator.cmod.feature.stores.epic.service.EpicDownloadManager +import com.winlator.cmod.feature.stores.epic.service.EpicGameLauncher +import com.winlator.cmod.feature.stores.epic.service.EpicManager +import com.winlator.cmod.feature.stores.epic.service.EpicService +import com.winlator.cmod.feature.stores.epic.service.EpicUpdateInfo +import com.winlator.cmod.feature.stores.epic.ui.auth.EpicOAuthActivity +import com.winlator.cmod.feature.stores.gog.data.GOGDlcInfo +import com.winlator.cmod.feature.stores.gog.data.GOGGame +import com.winlator.cmod.feature.stores.gog.data.LibraryItem +import com.winlator.cmod.feature.stores.gog.service.GOGAuthManager +import com.winlator.cmod.feature.stores.gog.service.GOGConstants +import com.winlator.cmod.feature.stores.gog.service.GOGManifestSizes +import com.winlator.cmod.feature.stores.gog.service.GOGService +import com.winlator.cmod.feature.stores.gog.service.GOGUpdateInfo +import com.winlator.cmod.feature.stores.gog.ui.auth.GOGOAuthActivity +import com.winlator.cmod.feature.stores.steam.SteamLoginActivity +import com.winlator.cmod.feature.stores.steam.data.DepotInfo +import com.winlator.cmod.feature.stores.steam.data.DownloadInfo +import com.winlator.cmod.feature.stores.steam.data.SteamApp +import com.winlator.cmod.feature.stores.steam.enums.DownloadPhase +import com.winlator.cmod.feature.stores.steam.events.AndroidEvent +import com.winlator.cmod.feature.stores.steam.events.EventDispatcher +import com.winlator.cmod.feature.stores.steam.service.SteamService +import com.winlator.cmod.feature.stores.steam.utils.PrefManager +import com.winlator.cmod.feature.stores.steam.utils.getAvatarURL +import com.winlator.cmod.feature.sync.CloudSyncHelper +import com.winlator.cmod.feature.sync.google.CloudSyncManager +import com.winlator.cmod.feature.sync.google.GameSaveBackupManager +import com.winlator.cmod.feature.sync.ui.CloudSavesContent +import com.winlator.cmod.runtime.container.ContainerManager +import com.winlator.cmod.runtime.container.Shortcut +import com.winlator.cmod.runtime.display.XServerDisplayActivity +import com.winlator.cmod.runtime.display.environment.ImageFs +import com.winlator.cmod.runtime.input.ControllerHelper +import com.winlator.cmod.runtime.wine.PeIconExtractor +import com.winlator.cmod.shared.android.ActivityResultHost +import com.winlator.cmod.shared.android.AppTerminationHelper +import com.winlator.cmod.shared.android.DirectoryPickerDialog +import com.winlator.cmod.shared.android.FixedFontScaleAppCompatActivity +import com.winlator.cmod.shared.android.RefreshRateUtils +import com.winlator.cmod.shared.io.StorageUtils +import com.winlator.cmod.shared.io.FileUtils +import com.winlator.cmod.shared.ui.CarouselView +import com.winlator.cmod.shared.ui.dialog.PopupDialog +import com.winlator.cmod.shared.ui.dialog.PopupTextAction +import androidx.compose.foundation.focusGroup +import com.winlator.cmod.shared.ui.focus.controllerFocusGlow +import com.winlator.cmod.shared.ui.focus.controllerMenuInput +import com.winlator.cmod.shared.ui.focus.controllerTextFieldEscape +import com.winlator.cmod.shared.ui.nav.DialogPaneNav +import com.winlator.cmod.shared.ui.nav.LocalPaneNav +import com.winlator.cmod.shared.ui.nav.PANE_DIR_ACTIVATE +import com.winlator.cmod.shared.ui.nav.PANE_DIR_DOWN +import com.winlator.cmod.shared.ui.nav.PANE_DIR_LEFT +import com.winlator.cmod.shared.ui.nav.PANE_DIR_RIGHT +import com.winlator.cmod.shared.ui.nav.PANE_DIR_SECONDARY +import com.winlator.cmod.shared.ui.nav.PANE_DIR_UP +import com.winlator.cmod.shared.ui.nav.PaneNavRegistry +import com.winlator.cmod.shared.ui.nav.paneNavItem +import com.winlator.cmod.shared.ui.FourByTwoGridView +import com.winlator.cmod.shared.ui.JoystickGridScroll +import com.winlator.cmod.shared.ui.JoystickListScroll +import com.winlator.cmod.shared.ui.ListView +import com.winlator.cmod.shared.ui.widget.chasingBorder +import com.winlator.cmod.shared.theme.WinNativeTheme +import dagger.hilt.android.AndroidEntryPoint +import dagger.Lazy +import com.winlator.cmod.feature.stores.steam.enums.EPersonaState +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import javax.inject.Inject +import kotlin.math.abs +import kotlin.math.roundToInt + +// Shortcut lookup + artwork resolution + home-screen pinning, split out of UnifiedActivity.kt (behavior-identical). + +internal fun UnifiedActivity.findLibraryShortcutForGame( + containerManager: ContainerManager, + app: SteamApp, + isCustom: Boolean, + isEpic: Boolean, + epicId: Int, +): Shortcut? = findShortcutForGame(containerManager.loadShortcuts(), app, isCustom, isEpic, epicId) + + +internal fun UnifiedActivity.findShortcutForGame( + shortcuts: List, + app: SteamApp, + isCustom: Boolean, + isEpic: Boolean, + epicId: Int, +): Shortcut? = + when { + isEpic -> { + shortcuts.find { + it.getExtra("game_source") == "EPIC" && it.getExtra("app_id") == epicId.toString() + } + } + + else -> { + shortcuts.find { + it.getExtra("app_id") == app.id.toString() || it.getExtra("custom_name") == app.name || it.name == app.name + } + } + } + + +internal fun UnifiedActivity.findLibraryArtworkShortcut( + shortcuts: List, + app: SteamApp, + gogGame: GOGGame?, + epicGame: EpicGame?, +): Shortcut? = + when { + gogGame != null -> { + shortcuts.find { + it.getExtra("game_source") == "GOG" && it.getExtra("gog_id") == gogGame.id + } + } + + epicGame != null -> { + shortcuts.find { + it.getExtra("game_source") == "EPIC" && it.getExtra("app_id") == epicGame.id.toString() + } + } + + else -> { + findShortcutForGame( + shortcuts = shortcuts, + app = app, + isCustom = app.id < 0, + isEpic = app.id >= 2000000000, + epicId = if (app.id >= 2000000000) app.id - 2000000000 else 0, + ) + } + } + + +internal fun UnifiedActivity.artworkCacheId( + app: SteamApp, + gogGame: GOGGame?, + epicGame: EpicGame?, +): ArtworkCacheId? = + when { + gogGame != null -> ArtworkCacheId("gog", gogGame.id) + epicGame != null -> ArtworkCacheId("epic", epicGame.id.toString()) + app.id >= 0 -> ArtworkCacheId("steam", app.id.toString()) + else -> null + } + + +internal fun UnifiedActivity.customArtworkOverrideSlots( + app: SteamApp, + gogGame: GOGGame?, + epicGame: EpicGame?, + hasDefaultCustomArt: Boolean, + hasIconCustomArt: Boolean, + hasHeroCustomArt: Boolean, +): Set { + val overridesPrimary = hasDefaultCustomArt || hasIconCustomArt + if (!overridesPrimary && !hasHeroCustomArt) return emptySet() + + return when { + gogGame != null -> { + buildSet { + if (overridesPrimary) { + add("cover") + add("icon") + } + if (hasHeroCustomArt) add("hero") + } + } + + epicGame != null -> { + buildSet { + if (overridesPrimary) { + add("cover") + add("square") + add("logo") + } + if (hasHeroCustomArt) add("hero") + } + } + + app.id >= 0 -> { + buildSet { + if (overridesPrimary) { + add("capsule") + add("library_capsule") + add("small_capsule") + } + if (hasHeroCustomArt) add("hero") + } + } + + else -> emptySet() + } +} + +internal fun UnifiedActivity.isShortcutCloudSyncEnabled(shortcut: Shortcut?): Boolean = + shortcut == null || shortcut.getExtra("cloud_sync_disabled", "0") != "1" + + +internal fun UnifiedActivity.setShortcutCloudSyncEnabled( + shortcut: Shortcut?, + enabled: Boolean, +) { + if (shortcut == null) return + shortcut.putExtra("cloud_sync_disabled", if (enabled) null else "1") + if (enabled) { + shortcut.putExtra("cloud_force_download", null) + } + shortcut.saveData() +} + +internal fun UnifiedActivity.isShortcutOfflineMode(shortcut: Shortcut?): Boolean = + shortcut != null && shortcut.getExtra("offline_mode", "0") == "1" + + +internal fun UnifiedActivity.setShortcutOfflineMode( + shortcut: Shortcut?, + enabled: Boolean, +) { + if (shortcut == null) return + shortcut.putExtra("offline_mode", if (enabled) "1" else null) + shortcut.saveData() +} + +internal fun UnifiedActivity.repairShortcutDisplayNameIfNeeded( + shortcut: Shortcut, + displayName: String, + vararg technicalNames: String, +) { + if (displayName.isBlank() || !shortcut.file.isFile) return + + runCatching { + val technicalNameSet = (technicalNames.toList() + shortcut.file.nameWithoutExtension) + .filter { it.isNotBlank() } + .toSet() + val lines = com.winlator.cmod.shared.io.FileUtils.readLines(shortcut.file) + val sb = StringBuilder() + var changed = false + var sawName = false + + for (line in lines) { + if (line.startsWith("Name=")) { + sawName = true + val currentName = line.removePrefix("Name=").trim() + if (currentName.isBlank() || currentName in technicalNameSet) { + sb.append("Name=").append(displayName).append('\n') + changed = true + } else { + sb.append(line).append('\n') + } + } else { + sb.append(line).append('\n') + } + } + + if (!sawName) { + val desktopHeader = "[Desktop Entry]\n" + val insertIndex = + if (sb.startsWith(desktopHeader)) { + desktopHeader.length + } else { + 0 + } + sb.insert(insertIndex, "Name=$displayName\n") + changed = true + } + + if (changed) { + com.winlator.cmod.shared.io.FileUtils.writeString(shortcut.file, sb.toString()) + } + }.onFailure { + Log.w("SHORTCUTS", "Failed to repair shortcut display name for ${shortcut.file.name}", it) + } +} + +internal fun UnifiedActivity.resolveLibraryShortcutArtworkModel( + context: android.content.Context, + app: SteamApp, + isCustom: Boolean, + isEpic: Boolean, + epicArtworkUrl: String?, +): Any? = + when { + isCustom -> { + val safeName = app.name.replace("/", "_").replace("\\", "_") + val iconFile = java.io.File(context.filesDir, "custom_icons/$safeName.png") + if (iconFile.exists()) iconFile else null + } + + isEpic -> { + epicArtworkUrl?.takeIf { it.isNotBlank() } + } + + else -> { + app.getCapsuleUrl() + } + } + + +internal suspend fun UnifiedActivity.loadArtworkBitmap( + context: android.content.Context, + artworkModel: Any?, +): Bitmap? { + if (artworkModel == null) return null + return try { + val request = + ImageRequest + .Builder(context) + .data(artworkModel) + .allowHardware(false) + .size(192, 192) + .build() + val result = context.imageLoader.execute(request) + val drawable = result.drawable ?: return null + if (drawable is BitmapDrawable) { + drawable.bitmap + } else { + val width = if (drawable.intrinsicWidth > 0) drawable.intrinsicWidth else 192 + val height = if (drawable.intrinsicHeight > 0) drawable.intrinsicHeight else 192 + val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) + val canvas = Canvas(bitmap) + drawable.setBounds(0, 0, width, height) + drawable.draw(canvas) + bitmap + } + } catch (_: Exception) { + null + } +} + +internal suspend fun UnifiedActivity.requestPinnedHomeShortcut( + context: android.content.Context, + shortcut: Shortcut, + artworkModel: Any? = null, +): Boolean { + if (shortcut.getExtra("uuid").isEmpty()) { + shortcut.genUUID() + } + val shortcutId = shortcut.getExtra("uuid") + if (shortcutId.isEmpty()) return false + val canonicalShortcutPath = shortcut.file.absolutePath + val shortcutPathHash = canonicalShortcutPath.hashCode() + val containerIdForLaunch = shortcut.getExtra("container_id").toIntOrNull() ?: shortcut.container.id + val pinShortcutId = "shortcut_${shortcut.container.id}_${shortcutId}_${shortcutPathHash.toUInt().toString(16)}" + + val shortcutManager = context.getSystemService(android.content.pm.ShortcutManager::class.java) ?: return false + if (!shortcutManager.isRequestPinShortcutSupported) return false + + val launchIntent = + Intent(context, XServerDisplayActivity::class.java).apply { + val launchData = + Uri + .Builder() + .scheme("winnative") + .authority(BuildConfig.APPLICATION_ID) + .appendPath("shortcut") + .appendQueryParameter("uuid", shortcutId) + .appendQueryParameter("container", containerIdForLaunch.toString()) + .appendQueryParameter("hash", shortcutPathHash.toString()) + .build() + action = Intent.ACTION_VIEW + data = launchData + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP) + putExtra("container_id", containerIdForLaunch) + putExtra("shortcut_path", canonicalShortcutPath) + putExtra("shortcut_name", shortcut.name) + putExtra("shortcut_uuid", shortcutId) + putExtra("shortcut_path_hash", shortcutPathHash) + putExtra(XServerDisplayActivity.EXTRA_LAUNCHED_FROM_PINNED_SHORTCUT, true) + } + + val customIconPath = + shortcut + .getExtra("customLibraryIconPath") + .ifBlank { shortcut.getExtra("customCoverArtPath") } + val customArtworkModel = + customIconPath + .takeIf { it.isNotBlank() } + ?.let { java.io.File(it) } + ?.takeIf { it.exists() } + + val artworkBitmap = loadArtworkBitmap(context, customArtworkModel) ?: loadArtworkBitmap(context, artworkModel) + val shortcutIcon = + artworkBitmap?.let { + android.graphics.drawable.Icon + .createWithBitmap(it) + } + ?: shortcut.icon?.let { + android.graphics.drawable.Icon + .createWithBitmap(it) + } + ?: android.graphics.drawable.Icon + .createWithResource(context, R.drawable.icon_shortcut) + + val pinShortcutInfo = + android.content.pm.ShortcutInfo + .Builder(context, pinShortcutId) + .setShortLabel(shortcut.name) + .setLongLabel(shortcut.name) + .setIcon(shortcutIcon) + .setIntent(launchIntent) + .build() + + val callbackIntent = + Intent(context, ShortcutBroadcastReceiver::class.java).apply { + action = ShortcutBroadcastReceiver.ACTION_PIN_SHORTCUT_RESULT + putExtra("shortcut_path", canonicalShortcutPath) + putExtra("shortcut_name", shortcut.name) + } + val callbackFlags = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + val callback = + PendingIntent.getBroadcast( + context, + pinShortcutId.hashCode(), + callbackIntent, + callbackFlags, + ) + + val result = + ShortcutsFragment.pinOrUpdateShortcut( + shortcutManager, + pinShortcutInfo, + ShortcutsFragment.buildPinnedShortcutIds(containerIdForLaunch, shortcutId, canonicalShortcutPath), + callback.intentSender, + ) + if (result == ShortcutsFragment.PinShortcutResult.REUSED_EXISTING) { + val toastIcon = artworkBitmap ?: shortcut.icon + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + R.string.shortcuts_list_readded_existing, + toastIcon, + ) + } + return result != ShortcutsFragment.PinShortcutResult.FAILED +} + +internal suspend fun UnifiedActivity.addLibraryShortcutToHomeScreen( + context: android.content.Context, + app: SteamApp, + isCustom: Boolean, + isEpic: Boolean, + epicId: Int, + epicArtworkUrl: String? = null, +): Boolean { + val containerManager = ContainerManager(context) + val shortcut = findLibraryShortcutForGame(containerManager, app, isCustom, isEpic, epicId) ?: return false + val artworkModel = resolveLibraryShortcutArtworkModel(context, app, isCustom, isEpic, epicArtworkUrl) + return requestPinnedHomeShortcut(context, shortcut, artworkModel) +} + +internal suspend fun UnifiedActivity.addGogShortcutToHomeScreen( + context: android.content.Context, + app: GOGGame, + artworkUrl: String?, +): Boolean { + val shortcut = + ContainerManager(context).loadShortcuts().find { + it.getExtra("game_source") == "GOG" && it.getExtra("gog_id") == app.id + } ?: return false + val artworkModel = artworkUrl?.takeIf { it.isNotBlank() } + return requestPinnedHomeShortcut(context, shortcut, artworkModel) +} diff --git a/app/src/main/app/shell/UnifiedActivityStartup.kt b/app/src/main/app/shell/UnifiedActivityStartup.kt new file mode 100644 index 000000000..887ef156d --- /dev/null +++ b/app/src/main/app/shell/UnifiedActivityStartup.kt @@ -0,0 +1,572 @@ +package com.winlator.cmod.app.shell +import com.winlator.cmod.app.shell.UnifiedActivity.PendingNavigation +import com.winlator.cmod.app.shell.UnifiedActivity.TabDef + +import android.app.Activity +import android.app.PendingIntent +import android.content.Intent +import android.content.res.Configuration +import android.hardware.input.InputManager +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.drawable.BitmapDrawable +import android.net.Uri +import android.os.Bundle +import android.provider.DocumentsContract +import android.util.Log +import androidx.activity.SystemBarStyle +import androidx.activity.compose.BackHandler +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AppCompatActivity +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.EnterTransition +import androidx.compose.animation.ExitTransition +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.FastOutSlowInEasing +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.spring +import androidx.compose.animation.core.tween +import androidx.compose.animation.expandVertically +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.shrinkVertically +import androidx.compose.animation.slideInHorizontally +import androidx.compose.animation.slideOutHorizontally +import androidx.compose.animation.togetherWith +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background +import androidx.compose.foundation.basicMarquee +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.focusable +import androidx.compose.foundation.gestures.detectHorizontalDragGestures +import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsPressedAsState +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items +import androidx.compose.foundation.lazy.grid.itemsIndexed +import androidx.compose.foundation.lazy.grid.rememberLazyGridState +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.outlined.ArrowBack +import androidx.compose.material.icons.automirrored.outlined.ExitToApp +import androidx.compose.material.icons.automirrored.outlined.OpenInNew +import androidx.compose.material.icons.outlined.* +import androidx.compose.material3.* +import androidx.compose.material3.pulltorefresh.PullToRefreshBox +import androidx.compose.runtime.* +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.snapshotFlow +import androidx.compose.runtime.snapshots.SnapshotStateMap +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.zIndex +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusProperties +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.focus.focusTarget +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.geometry.CornerRadius +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.TileMode +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.input.pointer.PointerEventPass +import androidx.compose.ui.input.pointer.PointerEventType +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties +import androidx.lifecycle.lifecycleScope +import androidx.navigation.NavHostController +import androidx.navigation.NavType +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import androidx.navigation.navArgument +import coil.compose.AsyncImage +import coil.imageLoader +import coil.request.ImageRequest +import com.winlator.cmod.BuildConfig +import com.winlator.cmod.R +import com.winlator.cmod.app.PluviaApp +import com.winlator.cmod.app.db.PluviaDatabase +import com.winlator.cmod.app.service.DownloadService +import com.winlator.cmod.app.service.download.DownloadCoordinator +import com.winlator.cmod.app.update.UpdateChecker +import com.winlator.cmod.feature.settings.InputControlsFragment +import com.winlator.cmod.feature.settings.SettingsFocusZone +import com.winlator.cmod.feature.settings.SettingsHost +import com.winlator.cmod.feature.settings.SettingsNavBridge +import com.winlator.cmod.feature.settings.SettingsNavItem +import com.winlator.cmod.feature.setup.SetupWizardActivity +import com.winlator.cmod.feature.shortcuts.LibraryShortcutUtils +import com.winlator.cmod.feature.shortcuts.LibraryShortcutArtwork +import com.winlator.cmod.feature.shortcuts.ShortcutBroadcastReceiver +import com.winlator.cmod.feature.shortcuts.ShortcutSettingsComposeDialog +import com.winlator.cmod.feature.shortcuts.ShortcutsFragment +import com.winlator.cmod.feature.stores.common.StoreArtworkCache +import com.winlator.cmod.feature.stores.epic.data.EpicCredentials +import com.winlator.cmod.feature.stores.epic.data.EpicGame +import com.winlator.cmod.feature.stores.epic.data.EpicGameToken +import com.winlator.cmod.feature.stores.epic.service.EpicAuthManager +import com.winlator.cmod.feature.stores.epic.service.EpicCloudSavesManager +import com.winlator.cmod.feature.stores.epic.service.EpicConstants +import com.winlator.cmod.feature.stores.epic.service.EpicDownloadManager +import com.winlator.cmod.feature.stores.epic.service.EpicGameLauncher +import com.winlator.cmod.feature.stores.epic.service.EpicManager +import com.winlator.cmod.feature.stores.epic.service.EpicService +import com.winlator.cmod.feature.stores.epic.service.EpicUpdateInfo +import com.winlator.cmod.feature.stores.epic.ui.auth.EpicOAuthActivity +import com.winlator.cmod.feature.stores.gog.data.GOGDlcInfo +import com.winlator.cmod.feature.stores.gog.data.GOGGame +import com.winlator.cmod.feature.stores.gog.data.LibraryItem +import com.winlator.cmod.feature.stores.gog.service.GOGAuthManager +import com.winlator.cmod.feature.stores.gog.service.GOGConstants +import com.winlator.cmod.feature.stores.gog.service.GOGManifestSizes +import com.winlator.cmod.feature.stores.gog.service.GOGService +import com.winlator.cmod.feature.stores.gog.service.GOGUpdateInfo +import com.winlator.cmod.feature.stores.gog.ui.auth.GOGOAuthActivity +import com.winlator.cmod.feature.stores.steam.SteamLoginActivity +import com.winlator.cmod.feature.stores.steam.data.DepotInfo +import com.winlator.cmod.feature.stores.steam.data.DownloadInfo +import com.winlator.cmod.feature.stores.steam.data.SteamApp +import com.winlator.cmod.feature.stores.steam.enums.DownloadPhase +import com.winlator.cmod.feature.stores.steam.events.AndroidEvent +import com.winlator.cmod.feature.stores.steam.events.EventDispatcher +import com.winlator.cmod.feature.stores.steam.service.SteamService +import com.winlator.cmod.feature.stores.steam.utils.PrefManager +import com.winlator.cmod.feature.stores.steam.utils.getAvatarURL +import com.winlator.cmod.feature.sync.CloudSyncHelper +import com.winlator.cmod.feature.sync.google.CloudSyncManager +import com.winlator.cmod.feature.sync.google.GameSaveBackupManager +import com.winlator.cmod.feature.sync.ui.CloudSavesContent +import com.winlator.cmod.runtime.container.ContainerManager +import com.winlator.cmod.runtime.container.Shortcut +import com.winlator.cmod.runtime.display.XServerDisplayActivity +import com.winlator.cmod.runtime.display.environment.ImageFs +import com.winlator.cmod.runtime.input.ControllerHelper +import com.winlator.cmod.runtime.wine.PeIconExtractor +import com.winlator.cmod.shared.android.ActivityResultHost +import com.winlator.cmod.shared.android.AppTerminationHelper +import com.winlator.cmod.shared.android.DirectoryPickerDialog +import com.winlator.cmod.shared.android.FixedFontScaleAppCompatActivity +import com.winlator.cmod.shared.android.RefreshRateUtils +import com.winlator.cmod.shared.io.StorageUtils +import com.winlator.cmod.shared.io.FileUtils +import com.winlator.cmod.shared.ui.CarouselView +import com.winlator.cmod.shared.ui.dialog.PopupDialog +import com.winlator.cmod.shared.ui.dialog.PopupTextAction +import androidx.compose.foundation.focusGroup +import com.winlator.cmod.shared.ui.focus.controllerFocusGlow +import com.winlator.cmod.shared.ui.focus.controllerMenuInput +import com.winlator.cmod.shared.ui.focus.controllerTextFieldEscape +import com.winlator.cmod.shared.ui.nav.DialogPaneNav +import com.winlator.cmod.shared.ui.nav.LocalPaneNav +import com.winlator.cmod.shared.ui.nav.PANE_DIR_ACTIVATE +import com.winlator.cmod.shared.ui.nav.PANE_DIR_DOWN +import com.winlator.cmod.shared.ui.nav.PANE_DIR_LEFT +import com.winlator.cmod.shared.ui.nav.PANE_DIR_RIGHT +import com.winlator.cmod.shared.ui.nav.PANE_DIR_SECONDARY +import com.winlator.cmod.shared.ui.nav.PANE_DIR_UP +import com.winlator.cmod.shared.ui.nav.PaneNavRegistry +import com.winlator.cmod.shared.ui.nav.paneNavItem +import com.winlator.cmod.shared.ui.FourByTwoGridView +import com.winlator.cmod.shared.ui.JoystickGridScroll +import com.winlator.cmod.shared.ui.JoystickListScroll +import com.winlator.cmod.shared.ui.ListView +import com.winlator.cmod.shared.ui.widget.chasingBorder +import com.winlator.cmod.shared.theme.WinNativeTheme +import dagger.hilt.android.AndroidEntryPoint +import dagger.Lazy +import com.winlator.cmod.feature.stores.steam.enums.EPersonaState +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import javax.inject.Inject +import kotlin.math.abs +import kotlin.math.roundToInt + +// Settings-intent routing + desktop launch + startup bootstrap + tab building, split out of UnifiedActivity.kt (behavior-identical). + +internal fun UnifiedActivity.navigateToSettings( + item: SettingsNavItem = SettingsNavItem.CONTAINERS, + profileId: Int = 0, + editContainerId: Int = 0, + returnToGameOnBack: Boolean = false, +) { + // In-activity settings navigation does not trigger Activity resume. + reapplyPreferredRefreshRate() + val route = buildSettingsRoute(item, profileId, editContainerId, returnToGameOnBack) + val nav = rootNavController + if (nav == null) { + pendingNavigation = PendingNavigation(item, profileId, editContainerId, returnToGameOnBack) + return + } + isPoppingSettings = false + nav.navigate(route) { + launchSingleTop = true + } +} + +internal fun UnifiedActivity.buildSettingsRoute( + item: SettingsNavItem = SettingsNavItem.CONTAINERS, + profileId: Int = 0, + editContainerId: Int = 0, + returnToGameOnBack: Boolean = false, +): String = + "settings?item=${item.name}&profileId=$profileId&editContainerId=$editContainerId&returnToGameOnBack=$returnToGameOnBack" + + +internal fun UnifiedActivity.extractSettingsNavigation(intent: Intent?): PendingNavigation? { + if (intent == null) return null + + val editContainerId = intent.getIntExtra("edit_container_id", 0) + if (editContainerId > 0) { + return PendingNavigation(SettingsNavItem.CONTAINERS, 0, editContainerId) + } + + if (intent.getBooleanExtra("edit_input_controls", false)) { + val profileId = intent.getIntExtra("selected_profile_id", 0) + val returnToGameOnBack = intent.getBooleanExtra("return_to_game_on_back", false) + return PendingNavigation(SettingsNavItem.INPUT_CONTROLS, profileId, 0, returnToGameOnBack) + } + + val selectedMenuItemId = intent.getIntExtra("selected_menu_item_id", 0) + if (selectedMenuItemId > 0) { + val target = SettingsNavItem.fromMenuId(selectedMenuItemId) ?: SettingsNavItem.CONTAINERS + return PendingNavigation(target, 0, 0) + } + + return null +} + +internal fun UnifiedActivity.consumeSettingsIntent(intent: Intent?) { + intent ?: return + intent.removeExtra("edit_container_id") + intent.removeExtra("edit_input_controls") + intent.removeExtra("selected_profile_id") + intent.removeExtra("selected_menu_item_id") + intent.removeExtra("return_to_game_on_back") +} + +internal fun UnifiedActivity.handleSettingsIntent(intent: Intent?) { + val request = extractSettingsNavigation(intent) ?: return + consumeSettingsIntent(intent) + navigateToSettings(request.item, request.profileId, request.editContainerId, request.returnToGameOnBack) +} + +internal fun UnifiedActivity.maybeForwardFrontendLaunch(): Boolean { + val source = intent ?: return false + val path = resolveIncomingDesktopPath(source) ?: return false + startActivity( + Intent(this, XServerDisplayActivity::class.java).apply { + action = Intent.ACTION_VIEW + putExtra("shortcut_path", path) + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP) + }, + ) + finish() + return true +} + +internal fun UnifiedActivity.resolveIncomingDesktopPath(source: Intent): String? { + materializeDesktop(source.data)?.let { return it } + source.clipData?.let { clip -> + for (i in 0 until clip.itemCount) { + materializeDesktop(clip.getItemAt(i).uri)?.let { return it } + materializeDesktop(clip.getItemAt(i).text?.toString())?.let { return it } + } + } + val extras = source.extras ?: return null + for (key in extras.keySet()) { + materializeDesktop(extras.get(key))?.let { return it } + } + return null +} + +internal fun UnifiedActivity.materializeDesktop(value: Any?): String? = + when (value) { + is android.net.Uri -> materializeDesktopUri(value) + is String -> + if (value.startsWith("content://") || value.startsWith("file://")) { + materializeDesktopUri(android.net.Uri.parse(value)) + } else { + java.io.File(value).takeIf { it.isFile && looksLikeDesktopFile(it) }?.absolutePath + } + else -> null + } + + +internal fun UnifiedActivity.materializeDesktopUri(uri: android.net.Uri): String? { + when (uri.scheme?.lowercase()) { + "file" -> { + val file = uri.path?.let { java.io.File(it) } + if (file != null && file.isFile && looksLikeDesktopFile(file)) return file.absolutePath + } + "content" -> { + val resolved = com.winlator.cmod.shared.io.FileUtils.getFilePathFromUri(this, uri) + if (!resolved.isNullOrEmpty()) { + val file = java.io.File(resolved) + if (file.isFile && looksLikeDesktopFile(file)) return file.absolutePath + } + return copyUriToCacheDesktop(uri) + } + } + return null +} + +internal fun UnifiedActivity.copyUriToCacheDesktop(uri: android.net.Uri): String? = + runCatching { + val out = java.io.File(cacheDir, "frontend_launch.desktop") + val copied = + contentResolver.openInputStream(uri)?.use { input -> + out.outputStream().use { output -> input.copyTo(output) } + true + } ?: false + if (copied && out.isFile && looksLikeDesktopFile(out)) out.absolutePath else null + }.getOrNull() + + +internal fun UnifiedActivity.looksLikeDesktopFile(file: java.io.File): Boolean { + if (!file.isFile || file.length() > 1_000_000L) return false + return runCatching { + val text = file.readText() + text.contains("[Desktop Entry]") || text.contains("container_id") + }.getOrDefault(false) +} + +internal fun UnifiedActivity.bootstrapStartupState() { + startupBootstrapReady = false + startupLibraryLayoutMode = null + startupStoreVisible = null + startupContentFilters = null + + lifecycleScope.launch(Dispatchers.IO) { + val appContext = applicationContext + val resolvedLayoutMode = + runCatching { + PrefManager.init(appContext) + LibraryLayoutMode.valueOf(PrefManager.libraryLayoutMode) + }.getOrElse { error -> + Log.w("UnifiedActivity", "Failed to resolve initial library layout", error) + LibraryLayoutMode.GRID_4 + } + + val resolvedStoreVisible = + runCatching { + val saved = PrefManager.libraryStoreVisible.split(",").toSet() + mapOf("steam" to ("steam" in saved), "epic" to ("epic" in saved), "gog" to ("gog" in saved)) + }.getOrElse { mapOf("steam" to true, "epic" to true, "gog" to true) } + + val resolvedContentFilters = + runCatching { + val saved = PrefManager.libraryContentFilters.split(",").toSet() + mapOf( + "games" to ("games" in saved), + "dlc" to ("dlc" in saved), + "applications" to ("applications" in saved), + "tools" to ("tools" in saved), + ) + }.getOrElse { mapOf("games" to true, "dlc" to false, "applications" to false, "tools" to false) } + + runCatching { dbProvider.get() } + .onFailure { Log.w("UnifiedActivity", "Database warmup failed", it) } + runCatching { EpicAuthManager.updateLoginStatus(appContext) } + .onFailure { Log.w("UnifiedActivity", "Epic auth warmup failed", it) } + runCatching { GOGAuthManager.updateLoginStatus(appContext) } + .onFailure { Log.w("UnifiedActivity", "GOG auth warmup failed", it) } + runCatching { SteamService.initLoginStatus(appContext) } + .onFailure { Log.w("UnifiedActivity", "Steam auth warmup failed", it) } + + withContext(Dispatchers.Main.immediate) { + startupLibraryLayoutMode = resolvedLayoutMode + currentLibraryLayoutMode = resolvedLayoutMode + startupStoreVisible = resolvedStoreVisible + startupContentFilters = resolvedContentFilters + startupBootstrapReady = true + } + } +} + +/** When the "Sign in to Google on launch" toggle is on, attempt a silent Play Games sign-in once per launch. */ +internal fun UnifiedActivity.maybeAutoSignInGoogleOnLaunch() { + if (!com.winlator.cmod.feature.sync.google.CloudSyncManager.isAutoSignInOnLaunchEnabled(this)) return + runCatching { + com.winlator.cmod.feature.sync.google.PlayGamesBootstrap.ensureInitialized(this) + com.google.android.gms.games.PlayGames + .getGamesSignInClient(this) + .signIn() + .addOnCompleteListener { task -> + val authed = task.isSuccessful && task.result?.isAuthenticated == true + if (authed) { + com.winlator.cmod.feature.sync.google.GameSaveBackupManager + .setDriveConnected(applicationContext, true) + retryPendingRetroCloudBackup() + } + } + }.onFailure { + timber.log.Timber.tag("UnifiedActivity").w(it, "Auto Google sign-in on launch failed") + } +} + +internal fun UnifiedActivity.scheduleDeferredStoreBootstrap() { + window.decorView.post { + if (isFinishing || isDestroyed) return@post + lifecycleScope.launch(Dispatchers.IO) { + if (EpicService.hasStoredCredentials(this@scheduleDeferredStoreBootstrap)) { + EpicService.start(this@scheduleDeferredStoreBootstrap) + // Keep token validation off the first-frame path. + EpicAuthManager.getStoredCredentials(this@scheduleDeferredStoreBootstrap) + com.winlator.cmod.feature.stores.epic.service.EpicTokenRefreshWorker + .schedule(this@scheduleDeferredStoreBootstrap) + } + + if (SteamService.hasStoredCredentials(this@scheduleDeferredStoreBootstrap)) { + SteamService.start(this@scheduleDeferredStoreBootstrap) + } + + if (GOGAuthManager.isLoggedIn(this@scheduleDeferredStoreBootstrap)) { + GOGService.start(this@scheduleDeferredStoreBootstrap) + } + + SteamService.maybeRepairInstalledMetadataOnStartup(this@scheduleDeferredStoreBootstrap) + } + } +} + +internal fun UnifiedActivity.buildTabs(storeVisible: Map): List { + val base = + mutableListOf( + TabDef(getString(R.string.common_ui_library), "library"), + TabDef(getString(R.string.common_ui_downloads), "downloads"), + ) + if (storeVisible["steam"] != false) base.add(TabDef("Steam", "steam")) + if (storeVisible["epic"] != false) base.add(TabDef("Epic", "epic")) + if (storeVisible["gog"] != false) base.add(TabDef("GOG", "gog")) + return base +} + +@Composable +internal fun UnifiedActivity.rememberSteamInstallStateMap(apps: List): Map { + var installStateMap by remember { mutableStateOf>(emptyMap()) } + + LaunchedEffect(apps) { + installStateMap = + withContext(Dispatchers.IO) { + apps.associate { it.id to SteamService.isAppInstalled(it.id) } + } + } + + return installStateMap +} + +@Composable +internal fun UnifiedActivity.rememberInstallPathStateMap(entries: List>): Map + where K : Any { + var installStateMap by remember { mutableStateOf>(emptyMap()) } + + LaunchedEffect(entries) { + installStateMap = + withContext(Dispatchers.IO) { + entries.associate { (key, path) -> + key to (path?.isNotBlank() == true && java.io.File(path).exists()) + } + } + } + + return installStateMap +} + +@Composable +internal fun UnifiedActivity.rememberEpicInstallStateMap( + context: android.content.Context, + apps: List, +): Map { + var installStateMap by remember { mutableStateOf>(emptyMap()) } + + LaunchedEffect(apps) { + installStateMap = + withContext(Dispatchers.IO) { + apps.associate { it.id to EpicService.isGameInstalled(context, it.id) } + } + } + + return installStateMap +} + +@Composable +internal fun UnifiedActivity.rememberGogInstallStateMap(apps: List): Map { + var installStateMap by remember { mutableStateOf>(emptyMap()) } + + LaunchedEffect(apps) { + installStateMap = + withContext(Dispatchers.IO) { + apps.associate { it.id to GOGService.isGameInstalled(it.id) } + } + } + + return installStateMap +} diff --git a/app/src/main/app/shell/UnifiedActivityStores.kt b/app/src/main/app/shell/UnifiedActivityStores.kt new file mode 100644 index 000000000..f466d40da --- /dev/null +++ b/app/src/main/app/shell/UnifiedActivityStores.kt @@ -0,0 +1,2225 @@ +package com.winlator.cmod.app.shell + +import android.app.Activity +import android.app.PendingIntent +import android.content.Intent +import android.content.res.Configuration +import android.hardware.input.InputManager +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.drawable.BitmapDrawable +import android.net.Uri +import android.os.Bundle +import android.provider.DocumentsContract +import android.util.Log +import androidx.activity.SystemBarStyle +import androidx.activity.compose.BackHandler +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AppCompatActivity +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.EnterTransition +import androidx.compose.animation.ExitTransition +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.FastOutSlowInEasing +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.spring +import androidx.compose.animation.core.tween +import androidx.compose.animation.expandVertically +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.shrinkVertically +import androidx.compose.animation.slideInHorizontally +import androidx.compose.animation.slideOutHorizontally +import androidx.compose.animation.togetherWith +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background +import androidx.compose.foundation.basicMarquee +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.focusable +import androidx.compose.foundation.gestures.detectHorizontalDragGestures +import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsPressedAsState +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items +import androidx.compose.foundation.lazy.grid.itemsIndexed +import androidx.compose.foundation.lazy.grid.rememberLazyGridState +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.outlined.ArrowBack +import androidx.compose.material.icons.automirrored.outlined.ExitToApp +import androidx.compose.material.icons.automirrored.outlined.OpenInNew +import androidx.compose.material.icons.outlined.* +import androidx.compose.material3.* +import androidx.compose.material3.pulltorefresh.PullToRefreshBox +import androidx.compose.runtime.* +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.snapshotFlow +import androidx.compose.runtime.snapshots.SnapshotStateMap +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.zIndex +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusProperties +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.focus.focusTarget +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.geometry.CornerRadius +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.TileMode +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.input.pointer.PointerEventPass +import androidx.compose.ui.input.pointer.PointerEventType +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties +import androidx.lifecycle.lifecycleScope +import androidx.navigation.NavHostController +import androidx.navigation.NavType +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import androidx.navigation.navArgument +import coil.compose.AsyncImage +import coil.imageLoader +import coil.request.ImageRequest +import com.winlator.cmod.BuildConfig +import com.winlator.cmod.R +import com.winlator.cmod.app.PluviaApp +import com.winlator.cmod.app.db.PluviaDatabase +import com.winlator.cmod.app.service.DownloadService +import com.winlator.cmod.app.service.download.DownloadCoordinator +import com.winlator.cmod.app.update.UpdateChecker +import com.winlator.cmod.feature.settings.InputControlsFragment +import com.winlator.cmod.feature.settings.SettingsFocusZone +import com.winlator.cmod.feature.settings.SettingsHost +import com.winlator.cmod.feature.settings.SettingsNavBridge +import com.winlator.cmod.feature.settings.SettingsNavItem +import com.winlator.cmod.feature.setup.SetupWizardActivity +import com.winlator.cmod.feature.shortcuts.LibraryShortcutUtils +import com.winlator.cmod.feature.shortcuts.LibraryShortcutArtwork +import com.winlator.cmod.feature.shortcuts.ShortcutBroadcastReceiver +import com.winlator.cmod.feature.shortcuts.ShortcutSettingsComposeDialog +import com.winlator.cmod.feature.shortcuts.ShortcutsFragment +import com.winlator.cmod.feature.stores.common.StoreArtworkCache +import com.winlator.cmod.feature.stores.epic.data.EpicCredentials +import com.winlator.cmod.feature.stores.epic.data.EpicGame +import com.winlator.cmod.feature.stores.epic.data.EpicGameToken +import com.winlator.cmod.feature.stores.epic.service.EpicAuthManager +import com.winlator.cmod.feature.stores.epic.service.EpicCloudSavesManager +import com.winlator.cmod.feature.stores.epic.service.EpicConstants +import com.winlator.cmod.feature.stores.epic.service.EpicDownloadManager +import com.winlator.cmod.feature.stores.epic.service.EpicGameLauncher +import com.winlator.cmod.feature.stores.epic.service.EpicManager +import com.winlator.cmod.feature.stores.epic.service.EpicService +import com.winlator.cmod.feature.stores.epic.service.EpicUpdateInfo +import com.winlator.cmod.feature.stores.epic.ui.auth.EpicOAuthActivity +import com.winlator.cmod.feature.stores.gog.data.GOGDlcInfo +import com.winlator.cmod.feature.stores.gog.data.GOGGame +import com.winlator.cmod.feature.stores.gog.data.LibraryItem +import com.winlator.cmod.feature.stores.gog.service.GOGAuthManager +import com.winlator.cmod.feature.stores.gog.service.GOGConstants +import com.winlator.cmod.feature.stores.gog.service.GOGManifestSizes +import com.winlator.cmod.feature.stores.gog.service.GOGService +import com.winlator.cmod.feature.stores.gog.service.GOGUpdateInfo +import com.winlator.cmod.feature.stores.gog.ui.auth.GOGOAuthActivity +import com.winlator.cmod.feature.stores.steam.SteamLoginActivity +import com.winlator.cmod.feature.stores.steam.data.DepotInfo +import com.winlator.cmod.feature.stores.steam.data.DownloadInfo +import com.winlator.cmod.feature.stores.steam.data.SteamApp +import com.winlator.cmod.feature.stores.steam.enums.DownloadPhase +import com.winlator.cmod.feature.stores.steam.events.AndroidEvent +import com.winlator.cmod.feature.stores.steam.events.EventDispatcher +import com.winlator.cmod.feature.stores.steam.service.SteamService +import com.winlator.cmod.feature.stores.steam.utils.PrefManager +import com.winlator.cmod.feature.stores.steam.utils.getAvatarURL +import com.winlator.cmod.feature.sync.CloudSyncHelper +import com.winlator.cmod.feature.sync.google.CloudSyncManager +import com.winlator.cmod.feature.sync.google.GameSaveBackupManager +import com.winlator.cmod.feature.sync.ui.CloudSavesContent +import com.winlator.cmod.runtime.container.ContainerManager +import com.winlator.cmod.runtime.container.Shortcut +import com.winlator.cmod.runtime.display.XServerDisplayActivity +import com.winlator.cmod.runtime.display.environment.ImageFs +import com.winlator.cmod.runtime.input.ControllerHelper +import com.winlator.cmod.runtime.wine.PeIconExtractor +import com.winlator.cmod.shared.android.ActivityResultHost +import com.winlator.cmod.shared.android.AppTerminationHelper +import com.winlator.cmod.shared.android.DirectoryPickerDialog +import com.winlator.cmod.shared.android.FixedFontScaleAppCompatActivity +import com.winlator.cmod.shared.android.RefreshRateUtils +import com.winlator.cmod.shared.io.StorageUtils +import com.winlator.cmod.shared.io.FileUtils +import com.winlator.cmod.shared.ui.CarouselView +import com.winlator.cmod.shared.ui.dialog.PopupDialog +import com.winlator.cmod.shared.ui.dialog.PopupTextAction +import androidx.compose.foundation.focusGroup +import com.winlator.cmod.shared.ui.focus.controllerFocusGlow +import com.winlator.cmod.shared.ui.focus.controllerMenuInput +import com.winlator.cmod.shared.ui.focus.controllerTextFieldEscape +import com.winlator.cmod.shared.ui.nav.DialogPaneNav +import com.winlator.cmod.shared.ui.nav.LocalPaneNav +import com.winlator.cmod.shared.ui.nav.PANE_DIR_ACTIVATE +import com.winlator.cmod.shared.ui.nav.PANE_DIR_DOWN +import com.winlator.cmod.shared.ui.nav.PANE_DIR_LEFT +import com.winlator.cmod.shared.ui.nav.PANE_DIR_RIGHT +import com.winlator.cmod.shared.ui.nav.PANE_DIR_SECONDARY +import com.winlator.cmod.shared.ui.nav.PANE_DIR_UP +import com.winlator.cmod.shared.ui.nav.PaneNavRegistry +import com.winlator.cmod.shared.ui.nav.paneNavItem +import com.winlator.cmod.shared.ui.FourByTwoGridView +import com.winlator.cmod.shared.ui.JoystickGridScroll +import com.winlator.cmod.shared.ui.JoystickListScroll +import com.winlator.cmod.shared.ui.ListView +import com.winlator.cmod.shared.ui.widget.chasingBorder +import com.winlator.cmod.shared.theme.WinNativeTheme +import dagger.hilt.android.AndroidEntryPoint +import dagger.Lazy +import com.winlator.cmod.feature.stores.steam.enums.EPersonaState +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import javax.inject.Inject +import kotlin.math.abs +import kotlin.math.roundToInt + +// Epic/GOG/Steam store tabs, capsules and manager dialogs, split out of UnifiedActivity.kt (behavior-identical). + +@Composable +internal fun UnifiedActivity.CompactActionButton( + icon: ImageVector, + label: String, + tint: Color = TextPrimary, + bgColor: Color = SurfaceDark, + modifier: Modifier = Modifier, + height: Dp = 36.dp, + fontSize: TextUnit = 13.sp, + onClick: () -> Unit, +) { + val interactionSource = remember { MutableInteractionSource() } + val isPressed by interactionSource.collectIsPressedAsState() + val scale by animateFloatAsState( + targetValue = if (isPressed) 0.93f else 1f, + animationSpec = spring(dampingRatio = 0.6f, stiffness = 800f), + label = "btnScale", + ) + val glowAlpha by animateFloatAsState( + targetValue = if (isPressed) 0.18f else 0f, + animationSpec = tween(durationMillis = 120), + label = "btnGlow", + ) + Surface( + modifier = + modifier + .fillMaxWidth() + .height(height) + .graphicsLayer { + scaleX = scale + scaleY = scale + }.clip(RoundedCornerShape(10.dp)) + .clickable( + interactionSource = interactionSource, + indication = null, + onClick = onClick, + ), + color = bgColor, + shape = RoundedCornerShape(10.dp), + border = BorderStroke(1.dp, tint.copy(alpha = glowAlpha)), + ) { + Row( + modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + ) { + Icon(icon, contentDescription = null, modifier = Modifier.size(16.dp), tint = tint) + Spacer(Modifier.width(6.dp)) + Text( + label, + color = tint, + fontSize = fontSize, + fontWeight = FontWeight.SemiBold, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + } +} + +// Single game capsule for carousel / grid / list +@Composable +@OptIn(ExperimentalFoundationApi::class) +internal fun UnifiedActivity.GameCapsule( + app: SteamApp, + gogGame: GOGGame? = null, + epicGame: EpicGame? = null, + iconRefreshKey: Int = 0, + artworkCacheRefreshKey: Int = 0, + isFocusedOverride: Boolean = false, + isControllerActive: Boolean = false, + customArtworkPath: String? = null, + customIconPath: String? = null, + customListPath: String? = null, + customCarouselPath: String? = null, + customHeroPath: String? = null, + onClick: (() -> Unit)? = null, + onLongClick: (() -> Unit)? = null, + useLibraryCapsule: Boolean = false, + listMode: Boolean = false, + modifier: Modifier = Modifier, +) { + val context = LocalContext.current + val isCustom = app.id < 0 + val isEpic = app.id >= 2000000000 + val defaultClick: () -> Unit = { + val containerManager = + com.winlator.cmod.runtime.container + .ContainerManager(context) + if (isCustom) { + launchCustomGame(context, containerManager, app.name) + } else if (gogGame != null) { + launchGogGame(context, containerManager, gogGame) + } else if (isEpic) { + epicGame?.let { launchEpicGame(context, containerManager, it) } + } else { + launchSteamGame(context, containerManager, app) + } + } + // Each view has its own shape, so prefer the slot scraped for it. + val artworkToBeUsed = + when { + listMode -> customListPath ?: customArtworkPath + useLibraryCapsule -> customCarouselPath ?: customArtworkPath + else -> customArtworkPath + } + val clickInteraction = remember { MutableInteractionSource() } + val isPressed by clickInteraction.collectIsPressedAsState() + val isFocused = isControllerActive && isFocusedOverride + val glowAlpha by animateFloatAsState( + targetValue = if (isPressed) 0.7f else 0f, + animationSpec = if (isPressed) tween(100) else tween(400), + label = "capsuleGlow", + ) + val clickModifier = + Modifier + .then( + if (glowAlpha > 0f) { + Modifier.drawWithContent { + drawContent() + drawRoundRect( + color = AccentGlow, + alpha = glowAlpha * 0.25f, + cornerRadius = CornerRadius(12.dp.toPx()), + ) + } + } else { + Modifier + }, + ).combinedClickable( + interactionSource = clickInteraction, + indication = null, + onClick = onClick ?: defaultClick, + onLongClick = onLongClick, + ) + + @Composable + fun ArtContent(artModifier: Modifier) { + val customArtworkFile = + artworkToBeUsed + ?.let { java.io.File(it) } + + if (customArtworkFile != null) { + val customArtworkCacheKey = + "library_custom_icon:${customArtworkFile.absolutePath}:${customArtworkFile.lastModified()}" + AsyncImage( + model = + ImageRequest + .Builder(context) + .data(customArtworkFile) + .memoryCacheKey(customArtworkCacheKey) + .diskCacheKey(customArtworkCacheKey) + .crossfade(300) + .build(), + contentDescription = app.name, + modifier = artModifier, + contentScale = ContentScale.Crop, + ) + } else if (isCustom) { + val iconFile = customIconPath?.let { path -> java.io.File(path) } + if (iconFile != null) { + AsyncImage( + model = + ImageRequest + .Builder(context) + .data(iconFile) + .crossfade(300) + .build(), + contentDescription = app.name, + modifier = artModifier, + contentScale = ContentScale.Crop, + ) + } else { + Box( + modifier = artModifier.background(SurfaceDark), + contentAlignment = Alignment.Center, + ) { + Icon( + Icons.Outlined.SportsEsports, + contentDescription = app.name, + tint = Accent.copy(alpha = 0.6f), + modifier = Modifier.size(48.dp), + ) + } + } + } else { + val imageModel = + remember(app.id, gogGame, epicGame, useLibraryCapsule, listMode, artworkCacheRefreshKey) { + StoreArtworkCache.imageModel( + context, + StoreArtworkCache.primaryRef(app, gogGame, epicGame, useLibraryCapsule, listMode), + ) + } + AsyncImage( + model = + ImageRequest + .Builder(context) + .data(imageModel) + .crossfade(300) + .build(), + contentDescription = app.name, + modifier = artModifier, + contentScale = ContentScale.Crop, + ) + } + } + + if (listMode) { + // Horizontal row card with hero background + val heroRef = if (!isCustom && gogGame == null && !isEpic) StoreArtworkCache.heroRef(app, null, null) else null + val heroModel = + remember(app.id, heroRef, artworkCacheRefreshKey) { + StoreArtworkCache.imageModel(context, heroRef) + } + + Box( + modifier = + modifier + .fillMaxWidth() + .clip(RoundedCornerShape(14.dp)) + .then( + if (isControllerActive && !isFocused) { + Modifier.border(1.dp, CardBorder, RoundedCornerShape(14.dp)) + } else { + Modifier + }, + ).chasingBorder( + isFocused = isFocused, + paused = chasingBordersPaused.value || !libraryTabActive.value, + cornerRadius = 14.dp, + ).background(CardDark, RoundedCornerShape(14.dp)) + .focusable() + .then(clickModifier), + ) { + // Hero background layer (falls back to CardDark if image fails) + if (heroRef != null) { + AsyncImage( + model = + ImageRequest + .Builder(context) + .data(heroModel) + .crossfade(300) + .build(), + contentDescription = null, + modifier = + Modifier + .matchParentSize() + .graphicsLayer { alpha = 0.25f }, + contentScale = ContentScale.Crop, + ) + } else { + customHeroPath?.let { + val heroFile = java.io.File(customHeroPath) + if (heroFile.isFile) { + AsyncImage( + model = + ImageRequest + .Builder(context) + .data(heroFile) + .crossfade(300) + .build(), + contentDescription = null, + modifier = + Modifier + .matchParentSize() + .graphicsLayer { alpha = 0.25f }, + contentScale = ContentScale.Crop, + ) + } + } + } + + // Foreground content + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = + Modifier + .fillMaxWidth() + .padding(horizontal = 14.dp, vertical = 11.dp), + horizontalArrangement = Arrangement.Center, + ) { + Box( + modifier = + Modifier + .height(52.dp) + .aspectRatio(462f / 174f) + .clip(RoundedCornerShape(8.dp)), + ) { + ArtContent(Modifier.fillMaxSize()) + libraryBadgeLabel(app.id, isCustom)?.let { badge -> + RetroConsoleRibbon(badge, Modifier.align(Alignment.CenterStart)) + } + } + + Spacer(Modifier.width(14.dp)) + + Text( + text = app.name, + modifier = + Modifier + .weight(1f) + .then(if (isFocused) Modifier.basicMarquee(iterations = Int.MAX_VALUE) else Modifier), + color = TextPrimary, + fontSize = 15.sp, + fontWeight = FontWeight.SemiBold, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + ) + } + } + } else { + // Vertical card: art on top, title below + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = + modifier + .fillMaxWidth() + .then( + if (isFocused) { + Modifier + } else { + Modifier.border(1.dp, CardDark, RoundedCornerShape(12.dp)) + }, + ).chasingBorder( + isFocused = isFocused, + paused = chasingBordersPaused.value || !libraryTabActive.value, + cornerRadius = 12.dp, + ).background(CardDark, RoundedCornerShape(12.dp)) + .focusable() + .then(clickModifier), + ) { + Box( + modifier = + Modifier + .fillMaxWidth() + .weight(1f) + .clip(RoundedCornerShape(topStart = 12.dp, topEnd = 12.dp)), + ) { + ArtContent(Modifier.fillMaxSize()) + libraryBadgeLabel(app.id, isCustom)?.let { badge -> + RetroConsoleRibbon(badge, Modifier.align(Alignment.CenterStart)) + } + } + + Text( + text = app.name, + modifier = + Modifier + .fillMaxWidth() + .padding(horizontal = 4.dp, vertical = 4.dp) + .then(if (isFocused) Modifier.basicMarquee(iterations = Int.MAX_VALUE) else Modifier), + style = MaterialTheme.typography.bodySmall, + color = TextPrimary, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + textAlign = TextAlign.Center, + ) + } + } +} + +// Epic Store Tab +@Composable +internal fun UnifiedActivity.EpicStoreTab( + isLoggedIn: Boolean, + epicApps: List, + searchQuery: String = "", + layoutMode: LibraryLayoutMode = LibraryLayoutMode.GRID_4, + onLoginClick: () -> Unit, +) { + val context = LocalContext.current + + if (!isLoggedIn) { + LoginRequiredScreen("Epic Games", onLoginClick) + return + } + + val selectedAppId = remember { mutableStateOf(null) } + val gridState = rememberLazyGridState() + val activity = LocalContext.current as? UnifiedActivity + + // Ensure library updates from cloud + LaunchedEffect(Unit) { + if (epicApps.isEmpty()) { + EpicService.triggerLibrarySync(context) + } + } + + val displayedApps = + remember(epicApps, searchQuery) { + if (searchQuery.isBlank()) { + epicApps + } else { + epicApps.filter { it.title.contains(searchQuery, ignoreCase = true) } + } + } + val installStateById = rememberEpicInstallStateMap(context, displayedApps) + + // Sync store focus infrastructure + LaunchedEffect(displayedApps.size) { + activity?.storeItemCount = displayedApps.size + val lastIndex = (displayedApps.size - 1).coerceAtLeast(0) + if (activity != null && displayedApps.isNotEmpty() && activity.storeFocusIndex.value > lastIndex) { + activity.storeFocusIndex.value = lastIndex + } + } + DisposableEffect(displayedApps) { + val clickCallback: (Int) -> Unit = { idx -> + displayedApps.getOrNull(idx)?.let { selectedAppId.value = it.id } + } + activity?.storeItemClickCallback = clickCallback + activity?.storeGridState = gridState + onDispose { + if (activity?.storeItemClickCallback === clickCallback) { + activity?.storeItemClickCallback = null + activity?.storeGridState = null + } + } + } + + if (layoutMode == LibraryLayoutMode.LIST) { + val listViewState = rememberLazyListState() + JoystickListScroll(listViewState, activity?.rightStickScrollState) + ListView( + items = displayedApps, + modifier = Modifier.tabScreenPadding(), + listState = listViewState, + contentPadding = TabListContentPadding, + keyOf = { it.id }, + ) { app, _, _ -> + EpicStoreCapsule( + app, + isInstalled = installStateById[app.id] == true, + listMode = true, + isControllerActive = ControllerHelper.isControllerConnected(), + ) { + selectedAppId.value = + app.id + } + } + } else { + val focusIndex by (activity?.storeFocusIndex ?: kotlinx.coroutines.flow.MutableStateFlow(0)).collectAsState() + val focusRequesters = + remember(displayedApps.size) { + List(displayedApps.size) { FocusRequester() } + } + LaunchedEffect(focusIndex, focusRequesters.size) { + if (searchQuery.isEmpty() && focusRequesters.isNotEmpty() && focusIndex in focusRequesters.indices) { + gridState.animateScrollToItem(focusIndex) + try { + focusRequesters[focusIndex].requestFocus() + } catch (_: Exception) { + } + } + } + JoystickGridScroll(gridState, activity?.rightStickScrollState) + FourByTwoGridView( + items = displayedApps, + modifier = Modifier.tabScreenPadding(top = TabGridTopPadding), + gridState = gridState, + keyOf = { it.id }, + ) { app, index, rowHeight -> + Box( + Modifier.height(rowHeight).then( + if (index in focusRequesters.indices) { + Modifier.focusRequester(focusRequesters[index]) + } else { + Modifier + }, + ), + ) { + EpicStoreCapsule( + app, + isInstalled = installStateById[app.id] == true, + isFocusedOverride = index == focusIndex, + isControllerActive = ControllerHelper.isControllerConnected(), + ) { + selectedAppId.value = + app.id + } + } + } + } + + val selectedApp = epicApps.find { it.id == selectedAppId.value } + if (selectedApp != null) { + EpicGameManagerDialog( + app = selectedApp, + onDismissRequest = { selectedAppId.value = null }, + ) + } +} + +@Composable +internal fun UnifiedActivity.StoreInstalledBadge( + modifier: Modifier = Modifier, + compact: Boolean = false, + attachedCorner: Boolean = false, +) { + val shape = + if (attachedCorner) { + RoundedCornerShape(topStart = 8.dp) + } else { + RoundedCornerShape(4.dp) + } + Box( + modifier = + modifier + .background(StatusOnline, shape) + .border(1.dp, Color.White.copy(alpha = 0.34f), shape) + .padding( + start = if (compact) 6.dp else 9.dp, + end = if (compact) 6.dp else 9.dp, + top = if (compact) 2.dp else 4.dp, + bottom = if (compact) 1.dp else 2.dp, + ), + ) { + Text( + stringResource(R.string.library_games_installed_badge), + color = Color(0xFF06140A), + fontSize = if (compact) 9.sp else 11.sp, + fontWeight = FontWeight.Black, + letterSpacing = 0.6.sp, + maxLines = 1, + ) + } +} + +@Composable +internal fun UnifiedActivity.EpicStoreCapsule( + app: com.winlator.cmod.feature.stores.epic.data.EpicGame, + isInstalled: Boolean, + listMode: Boolean = false, + isFocusedOverride: Boolean = false, + isControllerActive: Boolean = false, + onClick: () -> Unit, +) { + val context = LocalContext.current + var isFocused by remember { mutableStateOf(false) } + val clickInteraction = remember { MutableInteractionSource() } + val isPressed by clickInteraction.collectIsPressedAsState() + val glowAlpha by animateFloatAsState( + targetValue = if (isPressed) 0.7f else 0f, + animationSpec = if (isPressed) tween(100) else tween(400), + label = "epicCapsuleGlow", + ) + val effectiveFocus = isControllerActive && (isFocusedOverride || isFocused) + val imageUrl = app.primaryImageUrl ?: app.iconUrl + + val borderColor = if (isControllerActive) CardBorder else Color.Transparent + + if (listMode) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = + Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(14.dp)) + .border(1.dp, borderColor, RoundedCornerShape(14.dp)) + .chasingBorder(isFocused = effectiveFocus, paused = chasingBordersPaused.value, cornerRadius = 14.dp) + .background(CardDark, RoundedCornerShape(14.dp)) + .onFocusChanged { isFocused = it.isFocused } + .focusable() + .then( + if (glowAlpha > 0f) { + Modifier.drawWithContent { + drawContent() + drawRoundRect(color = AccentGlow, alpha = glowAlpha * 0.25f, cornerRadius = CornerRadius(14.dp.toPx())) + } + } else { + Modifier + }, + ).clickable(interactionSource = clickInteraction, indication = null, onClick = onClick) + .padding(horizontal = 14.dp, vertical = 11.dp), + horizontalArrangement = Arrangement.Center, + ) { + Box( + Modifier + .height(52.dp) + .aspectRatio(462f / 174f) + .clip(RoundedCornerShape(8.dp)), + ) { + AsyncImage( + model = + ImageRequest + .Builder(context) + .data(imageUrl) + .crossfade(300) + .build(), + contentDescription = app.title, + modifier = Modifier.fillMaxSize(), + contentScale = ContentScale.Crop, + ) + if (isInstalled) { + StoreInstalledBadge( + modifier = Modifier.align(Alignment.BottomEnd).padding(4.dp), + compact = true, + ) + } + } + Spacer(Modifier.width(14.dp)) + Text( + app.title, + modifier = + Modifier + .weight(1f) + .then(if (effectiveFocus) Modifier.basicMarquee(iterations = Int.MAX_VALUE) else Modifier), + color = TextPrimary, + fontSize = 15.sp, + fontWeight = FontWeight.SemiBold, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + ) + } + } else { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = + Modifier + .fillMaxSize() + .border(1.dp, borderColor, RoundedCornerShape(16.dp)) + .chasingBorder(isFocused = effectiveFocus, paused = chasingBordersPaused.value, cornerRadius = 16.dp) + .background(CardDark, RoundedCornerShape(16.dp)) + .onFocusChanged { isFocused = it.isFocused } + .focusable() + .then( + if (glowAlpha > 0f) { + Modifier.drawWithContent { + drawContent() + drawRoundRect(color = AccentGlow, alpha = glowAlpha * 0.25f, cornerRadius = CornerRadius(16.dp.toPx())) + } + } else { + Modifier + }, + ).clickable(interactionSource = clickInteraction, indication = null, onClick = onClick), + ) { + Box( + Modifier + .fillMaxWidth() + .weight(1f) + .clip(RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp)), + ) { + AsyncImage( + model = + ImageRequest + .Builder(context) + .data(imageUrl) + .crossfade(300) + .build(), + contentDescription = app.title, + modifier = Modifier.fillMaxSize(), + contentScale = ContentScale.Crop, + ) + + if (isInstalled) { + StoreInstalledBadge( + modifier = Modifier.align(Alignment.BottomEnd), + attachedCorner = true, + ) + } + } + + Text( + app.title, + modifier = + Modifier + .padding(horizontal = 4.dp, vertical = 4.dp) + .fillMaxWidth() + .then(if (effectiveFocus) Modifier.basicMarquee(iterations = Int.MAX_VALUE) else Modifier), + style = MaterialTheme.typography.bodySmall, + color = TextPrimary, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + textAlign = TextAlign.Center, + ) + } + } +} + +@Composable +internal fun UnifiedActivity.EpicGameManagerDialog( + app: EpicGame, + onDismissRequest: () -> Unit, +) { + val context = LocalContext.current + val installed = EpicService.isGameInstalled(context, app.id) + val scope = rememberCoroutineScope() + + var isLoading by remember { mutableStateOf(!installed) } + var manifestSizes by remember { mutableStateOf(null) } + var dlcApps by remember { mutableStateOf>(emptyList()) } + val selectedDlcIds = remember { mutableStateListOf() } + var customPath by remember { mutableStateOf(null) } + var showCustomPathWarning by remember { mutableStateOf(false) } + var isCheckingForUpdate by remember(app.id) { mutableStateOf(false) } + var updateInfo by remember(app.id) { mutableStateOf(null) } + var updateStatusText by remember(app.id) { mutableStateOf(null) } + val epicDownloadRecords by com.winlator.cmod.app.service.download.DownloadCoordinator.records.collectAsState( + initial = com.winlator.cmod.app.service.download.DownloadCoordinator.snapshotRecords(), + ) + val hasBlockingEpicDownload = + epicDownloadRecords.any { + it.store == com.winlator.cmod.app.db.download.DownloadRecord.STORE_EPIC && + it.storeGameId == app.id.toString() && + it.status in setOf( + com.winlator.cmod.app.db.download.DownloadRecord.STATUS_QUEUED, + com.winlator.cmod.app.db.download.DownloadRecord.STATUS_DOWNLOADING, + com.winlator.cmod.app.db.download.DownloadRecord.STATUS_PAUSED, + com.winlator.cmod.app.db.download.DownloadRecord.STATUS_FAILED, + ) + } + val updateActionEnabled = !hasBlockingEpicDownload + val activeEpicDownloadText = stringResource(R.string.store_game_download_already_active) + val noUpdateAvailableText = stringResource(R.string.store_game_no_update_available) + val updateAvailableText = stringResource(R.string.store_game_update_available) + val updateFailedText = stringResource(R.string.store_game_update_check_failed) + + if (showCustomPathWarning) { + CustomPathWarningDialog( + onDismiss = { showCustomPathWarning = false }, + onProceed = { + showCustomPathWarning = false + DirectoryPickerDialog.show( + activity = this@EpicGameManagerDialog, + initialPath = customPath ?: EpicConstants.getGameInstallPath(context, app.appName), + title = getString(R.string.settings_content_install_directory), + extraRoots = driveRoots(includeInternal = true), + ) { path -> customPath = path } + }, + ) + } + + LaunchedEffect(app.id, installed) { + if (!installed) { + val (sizes, sizedDlcs) = + withContext(Dispatchers.IO) { + val baseSizes = EpicService.fetchManifestSizes(context, app.id) + val dlcs = EpicService.getDLCForGameSuspend(app.id) + val dlcsWithSizes = + dlcs + .map { dlc -> + async { + if (dlc.downloadSize > 0L || dlc.installSize > 0L) { + dlc + } else { + val dlcSizes = EpicService.fetchManifestSizes(context, dlc.id) + dlc.copy( + downloadSize = dlcSizes.downloadSize, + installSize = dlcSizes.installSize, + ) + } + } + }.awaitAll() + baseSizes to dlcsWithSizes + } + manifestSizes = sizes + dlcApps = sizedDlcs + isLoading = false + } else { + dlcApps = + withContext(Dispatchers.IO) { + EpicService + .getDLCForGameSuspend(app.id) + .map { dlc -> + async { + if (dlc.downloadSize > 0L || dlc.installSize > 0L) { + dlc + } else { + val dlcSizes = EpicService.fetchManifestSizes(context, dlc.id) + dlc.copy( + downloadSize = dlcSizes.downloadSize, + installSize = dlcSizes.installSize, + ) + } + } + }.awaitAll() + } + } + } + + val baseDownloadSize = manifestSizes?.downloadSize ?: 0L + val baseInstallSize = manifestSizes?.installSize ?: 0L + val selectedDlcDownloadBytes = + dlcApps.filter { it.id in selectedDlcIds }.sumOf { it.downloadSize.coerceAtLeast(0L) } + val selectedDlcInstallBytes = + dlcApps.filter { it.id in selectedDlcIds }.sumOf { it.installSize.coerceAtLeast(0L) } + val totalDownloadSize = baseDownloadSize + selectedDlcDownloadBytes + val totalInstallSize = baseInstallSize + selectedDlcInstallBytes + val defaultPathSet = + if (PrefManager.useSingleDownloadFolder) { + PrefManager.defaultDownloadFolder.isNotEmpty() + } else { + PrefManager.epicDownloadFolder + .isNotEmpty() + } + val effectivePath = customPath ?: EpicConstants.getGameInstallPath(context, app.appName) + val availableBytes = + try { + StorageUtils.getAvailableSpace(effectivePath) + } catch (e: Exception) { + 0L + } + // Installed game: base content is on disk, so only gate on the newly-selected DLC bytes. + val requiredBytes = if (installed) selectedDlcInstallBytes else totalInstallSize + val isInstallEnabled = requiredBytes == 0L || availableBytes >= requiredBytes + val installActionEnabled = isInstallEnabled && !hasBlockingEpicDownload + val installPathDisplay = if (installed) app.installPath else (customPath ?: EpicConstants.defaultEpicGamesPath(context)) + + val dlcItems = + remember(dlcApps) { + dlcApps.map { dlc -> + val size = + dlc.downloadSize.takeIf { it > 0L } + ?: dlc.installSize + StoreDlcItem(id = dlc.id, name = dlc.title, downloadSize = size, isInstalled = dlc.isInstalled) + } + } + val customPathLabel = + when { + customPath != null -> stringResource(R.string.common_ui_custom) + defaultPathSet -> stringResource(R.string.common_ui_already_set) + else -> stringResource(R.string.common_ui_custom) + } + + Dialog( + onDismissRequest = onDismissRequest, + properties = + DialogProperties( + usePlatformDefaultWidth = false, + decorFitsSystemWindows = false, + ), + ) { + Surface( + modifier = Modifier.fillMaxSize(), + shape = RectangleShape, + color = Color.Black, + ) { + StoreGameDetailScreen( + title = app.title, + subtitle = + listOfNotNull( + app.developer.takeIf { it.isNotBlank() }, + app.publisher.takeIf { + it.isNotBlank() && !it.equals(app.developer, ignoreCase = true) + }, + ).joinToString(" • "), + sourceLabel = "Epic Games", + heroImageUrl = StoreArtworkCache.imageModel(context, StoreArtworkCache.epicHeroRef(app)), + isLoading = isLoading, + isInstalled = installed, + installPathDisplay = installPathDisplay, + downloadSize = totalDownloadSize, + installSize = totalInstallSize, + availableBytes = availableBytes, + isInstallEnabled = isInstallEnabled, + isDownloadActionEnabled = installActionEnabled, + customPathLabel = customPathLabel, + showCustomPath = true, + showCloudSync = false, + showUninstall = false, + showUpdateCheck = installed, + isCheckingForUpdate = isCheckingForUpdate, + isUpdateAvailable = updateInfo?.hasUpdate == true, + updateDownloadSize = updateInfo?.downloadSize ?: 0L, + updateStatusText = updateStatusText, + isUpdateActionEnabled = updateActionEnabled, + showVerifyFiles = installed, + areSteamActionsEnabled = !hasBlockingEpicDownload, + dlcs = dlcItems, + selectedDlcIds = selectedDlcIds.toSet(), + onBack = onDismissRequest, + onInstall = { + if (hasBlockingEpicDownload) { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + activeEpicDownloadText, + android.widget.Toast.LENGTH_SHORT, + ) + return@StoreGameDetailScreen + } + val installPath = + if (customPath != null) { + val sanitizedTitle = app.title.replace(Regex("[^a-zA-Z0-9 \\-_]"), "").trim() + java.io.File(customPath!!, sanitizedTitle).absolutePath + } else { + EpicConstants.getGameInstallPath(context, app.title) + } + context.runIfOnlineOrToast { + EpicService.downloadGame(context, app.id, selectedDlcIds.toList(), installPath, "en-US") + onDismissRequest() + } + }, + onCloudSync = { + scope.launch(Dispatchers.IO) { + EpicCloudSavesManager.syncCloudSaves(context, app.id, "auto") + } + onDismissRequest() + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + context.getString(R.string.google_cloud_sync_started), + android.widget.Toast.LENGTH_SHORT, + ) + }, + onVerifyFiles = { + if (hasBlockingEpicDownload) { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + activeEpicDownloadText, + android.widget.Toast.LENGTH_SHORT, + ) + return@StoreGameDetailScreen + } + context.runIfOnlineOrToast { + scope.launch { + val started = + withContext(Dispatchers.IO) { + EpicService.verifyGameFiles(context, app.id) + } + if (started != null) { + showTaskProgressPopup( + started, + app.title, + getString(R.string.store_game_verify_complete), + getString(R.string.store_game_verify_failed_notice), + completeAsToast = true, + ) + } else { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + activeEpicDownloadText, + android.widget.Toast.LENGTH_SHORT, + ) + } + } + } + }, + onCheckForUpdate = { + if (hasBlockingEpicDownload) { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + activeEpicDownloadText, + android.widget.Toast.LENGTH_SHORT, + ) + return@StoreGameDetailScreen + } + context.runIfOnlineOrToast { + scope.launch { + isCheckingForUpdate = true + updateStatusText = null + val latest = + withContext(Dispatchers.IO) { + EpicService.checkForGameUpdate(context, app.id) + } + updateInfo = latest + updateStatusText = + when { + latest.hasUpdate -> updateAvailableText + latest.message != null -> updateFailedText + else -> null + } + isCheckingForUpdate = false + if (!latest.hasUpdate && latest.message == null) { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + noUpdateAvailableText, + android.widget.Toast.LENGTH_SHORT, + ) + } + } + } + }, + onDownloadUpdate = { + if (!updateActionEnabled || updateInfo?.hasUpdate != true) return@StoreGameDetailScreen + context.runIfOnlineOrToast { + scope.launch { + val latest = + withContext(Dispatchers.IO) { + EpicService.checkForGameUpdate(context, app.id) + } + updateInfo = latest + updateStatusText = + when { + latest.hasUpdate -> updateAvailableText + latest.message != null -> updateFailedText + else -> null + } + if (!latest.hasUpdate) { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + noUpdateAvailableText, + android.widget.Toast.LENGTH_SHORT, + ) + return@launch + } + val started = + withContext(Dispatchers.IO) { + EpicService.updateGameFiles(context, app.id) + } + if (started != null) { + onDismissRequest() + } else { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + activeEpicDownloadText, + android.widget.Toast.LENGTH_SHORT, + ) + } + } + } + }, + onUninstall = { + scope.launch(Dispatchers.IO) { + val result = EpicService.deleteGame(context, app.id) + withContext(Dispatchers.Main) { + if (!result.isSuccess) { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + getString( + R.string.library_games_failed_to_uninstall_reason, + result.exceptionOrNull()?.message + ?: getString(R.string.common_ui_unknown_error), + ), + android.widget.Toast.LENGTH_LONG, + ) + } + onDismissRequest() + } + } + }, + onCustomPath = { + if (customPath == null && defaultPathSet) { + showCustomPathWarning = true + } else { + DirectoryPickerDialog.show( + activity = this@EpicGameManagerDialog, + initialPath = customPath ?: EpicConstants.getGameInstallPath(context, app.appName), + title = getString(R.string.settings_content_install_directory), + extraRoots = driveRoots(includeInternal = true), + ) { path -> customPath = path } + } + }, + onToggleDlc = { id -> + if (selectedDlcIds.contains(id)) { + selectedDlcIds.remove(id) + } else { + selectedDlcIds.add(id) + } + }, + onToggleSelectAllDlcs = { + val all = dlcItems.isNotEmpty() && dlcItems.all { it.id in selectedDlcIds } + if (all) { + selectedDlcIds.clear() + } else { + dlcItems.forEach { if (it.id !in selectedDlcIds) selectedDlcIds.add(it.id) } + } + }, + ) + } + } +} + +@Composable +internal fun UnifiedActivity.GOGStoreTab( + isLoggedIn: Boolean, + gogApps: List, + searchQuery: String = "", + layoutMode: LibraryLayoutMode = LibraryLayoutMode.GRID_4, + onLoginClick: () -> Unit, +) { + if (!isLoggedIn) { + LoginRequiredScreen("GOG", onLoginClick) + return + } + + val selectedGameId = remember { mutableStateOf(null) } + val gridState = rememberLazyGridState() + val activity = LocalContext.current as? UnifiedActivity + + val displayedApps = + remember(gogApps, searchQuery) { + if (searchQuery.isBlank()) { + gogApps + } else { + gogApps.filter { it.title.contains(searchQuery, ignoreCase = true) } + } + } + val installStateById = rememberGogInstallStateMap(displayedApps) + + // Sync store focus infrastructure + LaunchedEffect(displayedApps.size) { + activity?.storeItemCount = displayedApps.size + val lastIndex = (displayedApps.size - 1).coerceAtLeast(0) + if (activity != null && displayedApps.isNotEmpty() && activity.storeFocusIndex.value > lastIndex) { + activity.storeFocusIndex.value = lastIndex + } + } + DisposableEffect(displayedApps) { + val clickCallback: (Int) -> Unit = { idx -> + displayedApps.getOrNull(idx)?.let { selectedGameId.value = it.id } + } + activity?.storeItemClickCallback = clickCallback + activity?.storeGridState = gridState + onDispose { + if (activity?.storeItemClickCallback === clickCallback) { + activity?.storeItemClickCallback = null + activity?.storeGridState = null + } + } + } + + val isControllerActive = ControllerHelper.isControllerConnected() + val gogBorderColor = if (isControllerActive) CardBorder else Color.Transparent + + if (layoutMode == LibraryLayoutMode.LIST) { + val listViewState = rememberLazyListState() + ListView( + items = displayedApps, + modifier = Modifier.tabScreenPadding(), + listState = listViewState, + contentPadding = TabListContentPadding, + keyOf = { it.id }, + ) { app, _, _ -> + val isInstalled = installStateById[app.id] == true + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = + Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(14.dp)) + .border(1.dp, gogBorderColor, RoundedCornerShape(14.dp)) + .background(CardDark, RoundedCornerShape(14.dp)) + .clickable { selectedGameId.value = app.id } + .padding(horizontal = 14.dp, vertical = 11.dp), + horizontalArrangement = Arrangement.Center, + ) { + Box( + Modifier + .height(52.dp) + .aspectRatio(462f / 174f) + .clip(RoundedCornerShape(8.dp)), + ) { + AsyncImage( + model = + ImageRequest + .Builder(LocalContext.current) + .data(app.imageUrl.ifEmpty { app.iconUrl }) + .crossfade(300) + .build(), + contentDescription = null, + modifier = Modifier.fillMaxSize(), + contentScale = ContentScale.Crop, + ) + if (isInstalled) { + StoreInstalledBadge( + modifier = Modifier.align(Alignment.BottomEnd).padding(4.dp), + compact = true, + ) + } + } + Spacer(Modifier.width(14.dp)) + Text( + text = app.title, + modifier = Modifier.weight(1f), + color = TextPrimary, + fontSize = 15.sp, + fontWeight = FontWeight.SemiBold, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + ) + } + } + } else { + val focusIndex by (activity?.storeFocusIndex ?: kotlinx.coroutines.flow.MutableStateFlow(0)).collectAsState() + val focusRequesters = + remember(displayedApps.size) { + List(displayedApps.size) { FocusRequester() } + } + LaunchedEffect(focusIndex, focusRequesters.size) { + if (searchQuery.isEmpty() && focusRequesters.isNotEmpty() && focusIndex in focusRequesters.indices) { + gridState.animateScrollToItem(focusIndex) + try { + focusRequesters[focusIndex].requestFocus() + } catch (_: Exception) { + } + } + } + FourByTwoGridView( + items = displayedApps, + modifier = Modifier.tabScreenPadding(top = TabGridTopPadding), + gridState = gridState, + keyOf = { it.id }, + ) { app, index, rowHeight -> + val isInstalled = installStateById[app.id] == true + val isItemFocused = isControllerActive && index == focusIndex + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = + Modifier + .fillMaxWidth() + .height(rowHeight) + .then( + if (index in focusRequesters.indices) { + Modifier.focusRequester(focusRequesters[index]) + } else { + Modifier + }, + ).border(1.dp, gogBorderColor, RoundedCornerShape(16.dp)) + .chasingBorder(isFocused = isItemFocused, paused = chasingBordersPaused.value, cornerRadius = 16.dp) + .background(CardDark, RoundedCornerShape(16.dp)) + .clickable { selectedGameId.value = app.id }, + ) { + Box( + Modifier + .fillMaxWidth() + .weight(1f) + .clip(RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp)), + ) { + AsyncImage( + model = + ImageRequest + .Builder(LocalContext.current) + .data(app.imageUrl.ifEmpty { app.iconUrl }) + .crossfade(300) + .build(), + contentDescription = null, + modifier = Modifier.fillMaxSize(), + contentScale = ContentScale.Crop, + ) + if (isInstalled) { + StoreInstalledBadge( + modifier = Modifier.align(Alignment.BottomEnd), + attachedCorner = true, + ) + } + } + + Text( + text = app.title, + modifier = Modifier.fillMaxWidth().padding(horizontal = 4.dp, vertical = 4.dp), + style = MaterialTheme.typography.bodySmall, + color = TextPrimary, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + textAlign = TextAlign.Center, + ) + } + } + } + + selectedGameId.value?.let { gameId -> + val app = gogApps.firstOrNull { it.id == gameId } + if (app != null) { + GOGGameManagerDialog(app = app) { selectedGameId.value = null } + } + } +} + +@Composable +internal fun UnifiedActivity.GOGGameManagerDialog( + app: GOGGame, + onDismissRequest: () -> Unit, +) { + val context = LocalContext.current + val installed = GOGService.isGameInstalled(app.id) + val scope = rememberCoroutineScope() + var isLoading by remember(app.id) { mutableStateOf(true) } + var selectedManifestSizes by remember(app.id) { mutableStateOf(GOGManifestSizes()) } + var dlcSizes by remember(app.id) { mutableStateOf>(emptyMap()) } + var customPath by remember { mutableStateOf(null) } + var showCustomPathWarning by remember { mutableStateOf(false) } + var dlcApps by remember(app.id) { mutableStateOf>(emptyList()) } + val selectedDlcIds = remember(app.id) { mutableStateListOf() } + var isCheckingForGogUpdate by remember(app.id) { mutableStateOf(false) } + var gogUpdateInfo by remember(app.id) { mutableStateOf(null) } + var gogUpdateStatusText by remember(app.id) { mutableStateOf(null) } + val gogDownloadRecords by com.winlator.cmod.app.service.download.DownloadCoordinator.records.collectAsState( + initial = com.winlator.cmod.app.service.download.DownloadCoordinator.snapshotRecords(), + ) + val hasBlockingGogDownload = + gogDownloadRecords.any { + it.store == com.winlator.cmod.app.db.download.DownloadRecord.STORE_GOG && + it.storeGameId == app.id && + it.status in setOf( + com.winlator.cmod.app.db.download.DownloadRecord.STATUS_QUEUED, + com.winlator.cmod.app.db.download.DownloadRecord.STATUS_DOWNLOADING, + com.winlator.cmod.app.db.download.DownloadRecord.STATUS_PAUSED, + com.winlator.cmod.app.db.download.DownloadRecord.STATUS_FAILED, + ) + } + val gogUpdateActionEnabled = !hasBlockingGogDownload + val activeGogDownloadText = stringResource(R.string.store_game_download_already_active) + val gogNoUpdateAvailableText = stringResource(R.string.store_game_no_update_available) + val gogUpdateAvailableText = stringResource(R.string.store_game_update_available) + val gogUpdateFailedText = stringResource(R.string.store_game_update_check_failed) + + if (showCustomPathWarning) { + CustomPathWarningDialog( + onDismiss = { showCustomPathWarning = false }, + onProceed = { + showCustomPathWarning = false + DirectoryPickerDialog.show( + activity = this@GOGGameManagerDialog, + initialPath = customPath ?: GOGConstants.defaultGOGGamesPath, + title = getString(R.string.settings_content_install_directory), + extraRoots = driveRoots(includeInternal = true), + ) { path -> customPath = path } + }, + ) + } + + data class GogInstallLoadData( + val dlcs: List, + val dlcSizes: Map, + val baseManifestSizes: GOGManifestSizes, + ) + + LaunchedEffect(app.id, PrefManager.containerLanguage) { + isLoading = true + val loadData = + withContext(Dispatchers.IO) { + val dlcs = GOGService.getDLCForGameSuspend(app.id, PrefManager.containerLanguage) + val perDlcSizes = + dlcs.mapNotNull { dlc -> + val id = dlc.id.toIntOrNull() ?: return@mapNotNull null + id to + GOGManifestSizes( + installSize = dlc.installSize, + downloadSize = dlc.downloadSize, + ) + }.toMap() + GogInstallLoadData( + dlcs = dlcs, + dlcSizes = perDlcSizes, + baseManifestSizes = + GOGService.getInstallableSelectedManifestSizes( + app.id, + PrefManager.containerLanguage, + ), + ) + } + dlcApps = loadData.dlcs + dlcSizes = loadData.dlcSizes + selectedManifestSizes = loadData.baseManifestSizes + selectedDlcIds.clear() + loadData.dlcs + .filterNot { it.isInstalled } + .mapNotNull { it.id.toIntOrNull() } + .forEach { selectedDlcIds.add(it) } + isLoading = false + } + + LaunchedEffect(app.id, PrefManager.containerLanguage, selectedDlcIds.toList()) { + selectedManifestSizes = + withContext(Dispatchers.IO) { + GOGService.getInstallableSelectedManifestSizes( + app.id, + PrefManager.containerLanguage, + selectedDlcIds.toList(), + ) + } + } + + val defaultPathSet = + if (PrefManager.useSingleDownloadFolder) { + PrefManager.defaultDownloadFolder.isNotEmpty() + } else { + PrefManager.gogDownloadFolder + .isNotEmpty() + } + val installRootPath = customPath ?: GOGConstants.defaultGOGGamesPath + val installPathDisplay = + if (installed) { + app.installPath + } else if (customPath != null) { + java.io.File(customPath!!, GOGConstants.getSanitizedGameFolderName(app.title)).absolutePath + } else { + GOGConstants.getGameInstallPath(app.title) + } + val dlcItems = + remember(dlcApps, dlcSizes) { + dlcApps.mapNotNull { dlc -> + val id = dlc.id.toIntOrNull() ?: return@mapNotNull null + val manifestSize = dlcSizes[id] + val size = + manifestSize + ?.downloadSize + ?.takeIf { it > 0L } + ?: manifestSize?.installSize?.takeIf { it > 0L } + ?: dlc.downloadSize.takeIf { it > 0L } + ?: dlc.installSize + StoreDlcItem(id = id, name = dlc.title, downloadSize = size, isInstalled = dlc.isInstalled) + } + } + val selectedDlcDownloadSize = + remember(dlcItems, selectedDlcIds.toList()) { + dlcItems + .filter { !it.isInstalled && it.id in selectedDlcIds } + .sumOf { it.downloadSize.coerceAtLeast(0L) } + } + val selectedDlcInstallSize = + remember(dlcSizes, dlcItems, selectedDlcIds.toList()) { + dlcItems + .filter { !it.isInstalled && it.id in selectedDlcIds } + .sumOf { dlcSizes[it.id]?.installSize?.takeIf { size -> size > 0L } ?: it.downloadSize.coerceAtLeast(0L) } + } + val totalDownloadSize = + if (installed) { + selectedDlcDownloadSize + } else { + selectedManifestSizes.downloadSize.takeIf { it > 0L } + ?: app.downloadSize + selectedDlcDownloadSize + } + val totalInstallSize = + if (installed) { + selectedDlcInstallSize + } else { + selectedManifestSizes.installSize.takeIf { it > 0L } + ?: app.installSize.takeIf { it > 0L } + ?: totalDownloadSize + } + val requiredBytes = + if (installed) { + selectedDlcInstallSize.takeIf { it > 0L } ?: selectedDlcDownloadSize + } else { + totalInstallSize.takeIf { it > 0L } ?: totalDownloadSize + } + val availableBytes = + try { + StorageUtils.getAvailableSpace(installRootPath) + } catch (_: Exception) { + 0L + } + val isInstallEnabled = requiredBytes == 0L || availableBytes >= requiredBytes + val installActionEnabled = isInstallEnabled && !hasBlockingGogDownload + val customPathLabel = + when { + customPath != null -> stringResource(R.string.common_ui_custom) + defaultPathSet -> stringResource(R.string.common_ui_already_set) + else -> stringResource(R.string.common_ui_custom) + } + + Dialog( + onDismissRequest = onDismissRequest, + properties = + DialogProperties( + usePlatformDefaultWidth = false, + decorFitsSystemWindows = false, + ), + ) { + Surface( + modifier = Modifier.fillMaxSize(), + shape = RectangleShape, + color = Color.Black, + ) { + StoreGameDetailScreen( + title = app.title, + subtitle = + listOfNotNull( + app.developer.takeIf { it.isNotBlank() }, + app.publisher.takeIf { + it.isNotBlank() && !it.equals(app.developer, ignoreCase = true) + }, + ).joinToString(" • "), + sourceLabel = "GOG", + heroImageUrl = StoreArtworkCache.imageModel(context, StoreArtworkCache.gogHeroRef(app)), + isLoading = isLoading, + isInstalled = installed, + installPathDisplay = installPathDisplay, + downloadSize = totalDownloadSize, + installSize = totalInstallSize, + availableBytes = availableBytes, + isInstallEnabled = isInstallEnabled, + isDownloadActionEnabled = installActionEnabled, + customPathLabel = customPathLabel, + showCustomPath = true, + showCloudSync = false, + showUninstall = false, + showUpdateCheck = installed, + isCheckingForUpdate = isCheckingForGogUpdate, + isUpdateAvailable = gogUpdateInfo?.hasUpdate == true, + updateDownloadSize = gogUpdateInfo?.downloadSize ?: 0L, + updateStatusText = gogUpdateStatusText, + isUpdateActionEnabled = gogUpdateActionEnabled, + showVerifyFiles = installed, + areSteamActionsEnabled = !hasBlockingGogDownload, + dlcs = dlcItems, + selectedDlcIds = selectedDlcIds.toSet(), + isDlcSelectionEnabled = installActionEnabled, + onBack = onDismissRequest, + onInstall = { + if (hasBlockingGogDownload) { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + getString(R.string.store_game_download_already_active), + android.widget.Toast.LENGTH_SHORT, + ) + return@StoreGameDetailScreen + } + context.runIfOnlineOrToast { + GOGService.downloadGame( + context, + app.id, + installPathDisplay, + PrefManager.containerLanguage, + selectedDlcIds.toList(), + ) + onDismissRequest() + } + }, + onCloudSync = { + scope.launch(Dispatchers.IO) { + GOGService.syncCloudSaves(context, "GOG_${app.id}", "auto") + } + onDismissRequest() + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + context.getString(R.string.google_cloud_sync_started), + android.widget.Toast.LENGTH_SHORT, + ) + }, + onVerifyFiles = { + if (hasBlockingGogDownload) { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + activeGogDownloadText, + android.widget.Toast.LENGTH_SHORT, + ) + return@StoreGameDetailScreen + } + context.runIfOnlineOrToast { + scope.launch { + val started = + withContext(Dispatchers.IO) { + GOGService.verifyGameFiles(context, app.id) + } + if (started != null) { + showTaskProgressPopup( + started, + app.title, + getString(R.string.store_game_verify_complete), + getString(R.string.store_game_verify_failed_notice), + completeAsToast = true, + ) + } else { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + activeGogDownloadText, + android.widget.Toast.LENGTH_SHORT, + ) + } + } + } + }, + onCheckForUpdate = { + if (hasBlockingGogDownload) { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + activeGogDownloadText, + android.widget.Toast.LENGTH_SHORT, + ) + return@StoreGameDetailScreen + } + context.runIfOnlineOrToast { + scope.launch { + isCheckingForGogUpdate = true + gogUpdateStatusText = null + val latest = + withContext(Dispatchers.IO) { + GOGService.checkForGameUpdate(context, app.id) + } + gogUpdateInfo = latest + gogUpdateStatusText = + when { + latest.hasUpdate -> gogUpdateAvailableText + latest.message != null -> gogUpdateFailedText + else -> null + } + isCheckingForGogUpdate = false + if (!latest.hasUpdate && latest.message == null) { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + gogNoUpdateAvailableText, + android.widget.Toast.LENGTH_SHORT, + ) + } + } + } + }, + onDownloadUpdate = { + if (!gogUpdateActionEnabled || gogUpdateInfo?.hasUpdate != true) return@StoreGameDetailScreen + context.runIfOnlineOrToast { + scope.launch { + val started = + withContext(Dispatchers.IO) { + GOGService.updateGameFiles(context, app.id) + } + if (started != null) { + onDismissRequest() + } else { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + activeGogDownloadText, + android.widget.Toast.LENGTH_SHORT, + ) + } + } + } + }, + onUninstall = { + scope.launch(Dispatchers.IO) { + val result = + GOGService.deleteGame( + context, + LibraryItem( + "GOG_${app.id}", + app.title, + com.winlator.cmod.feature.stores.steam.enums.GameSource.GOG, + ), + ) + withContext(Dispatchers.Main) { + if (!result.isSuccess) { + com.winlator.cmod.shared.ui.toast.WinToast.show( + context, + getString( + R.string.library_games_failed_to_uninstall_reason, + result.exceptionOrNull()?.message + ?: getString(R.string.common_ui_unknown_error), + ), + android.widget.Toast.LENGTH_LONG, + ) + } + onDismissRequest() + } + } + }, + onCustomPath = { + if (customPath == null && defaultPathSet) { + showCustomPathWarning = true + } else { + DirectoryPickerDialog.show( + activity = this@GOGGameManagerDialog, + initialPath = customPath ?: GOGConstants.defaultGOGGamesPath, + title = getString(R.string.settings_content_install_directory), + extraRoots = driveRoots(includeInternal = true), + ) { path -> customPath = path } + } + }, + onToggleDlc = { id -> + if (dlcItems.any { it.id == id && it.isInstalled }) { + return@StoreGameDetailScreen + } + if (selectedDlcIds.contains(id)) { + selectedDlcIds.remove(id) + } else { + selectedDlcIds.add(id) + } + }, + onToggleSelectAllDlcs = { + val selectableDlcItems = dlcItems.filterNot { it.isInstalled } + val all = selectableDlcItems.isNotEmpty() && selectableDlcItems.all { it.id in selectedDlcIds } + if (all) { + selectedDlcIds.removeAll(selectableDlcItems.map { it.id }.toSet()) + } else { + selectableDlcItems.forEach { if (it.id !in selectedDlcIds) selectedDlcIds.add(it.id) } + } + }, + ) + } + } +} + +// Steam Store Tab +@Composable +internal fun UnifiedActivity.SteamStoreTab( + isLoggedIn: Boolean, + steamApps: List, + searchQuery: String = "", + layoutMode: LibraryLayoutMode = LibraryLayoutMode.GRID_4, +) { + if (!isLoggedIn && !SteamService.hasStoredCredentials(this)) { + LoginRequiredScreen("Steam") { + startActivity(Intent(this@SteamStoreTab, SteamLoginActivity::class.java)) + } + return + } + + var selectedAppForDialog by remember { mutableStateOf(null) } + val gridState = rememberLazyGridState() + val activity = LocalContext.current as? UnifiedActivity + + val displayedApps = + remember(steamApps, searchQuery) { + if (searchQuery.isBlank()) { + steamApps + } else { + steamApps.filter { it.name.contains(searchQuery, ignoreCase = true) } + } + } + val installStateById = rememberSteamInstallStateMap(displayedApps) + + // Sync store focus infrastructure + LaunchedEffect(displayedApps.size) { + activity?.storeItemCount = displayedApps.size + val lastIndex = (displayedApps.size - 1).coerceAtLeast(0) + if (activity != null && displayedApps.isNotEmpty() && activity.storeFocusIndex.value > lastIndex) { + activity.storeFocusIndex.value = lastIndex + } + } + // Register A-button click callback and grid state for visible-area snapping + DisposableEffect(displayedApps) { + val clickCallback: (Int) -> Unit = { idx -> + displayedApps.getOrNull(idx)?.let { selectedAppForDialog = it } + } + activity?.storeItemClickCallback = clickCallback + activity?.storeGridState = gridState + onDispose { + if (activity?.storeItemClickCallback === clickCallback) { + activity?.storeItemClickCallback = null + activity?.storeGridState = null + } + } + } + + if (layoutMode == LibraryLayoutMode.LIST) { + val listViewState = rememberLazyListState() + JoystickListScroll(listViewState, activity?.rightStickScrollState, minSpeed = 2.5f, maxSpeed = 16f, quadratic = true) + ListView( + items = displayedApps, + modifier = Modifier.tabScreenPadding(), + listState = listViewState, + contentPadding = TabListContentPadding, + keyOf = { it.id }, + ) { app, _, _ -> + SteamStoreCapsule( + app, + isInstalled = installStateById[app.id] == true, + listMode = true, + isControllerActive = ControllerHelper.isControllerConnected(), + onClick = { + selectedAppForDialog = + app + }, + ) + } + } else { + val focusIndex by (activity?.storeFocusIndex ?: kotlinx.coroutines.flow.MutableStateFlow(0)).collectAsState() + val focusRequesters = + remember(displayedApps.size) { + List(displayedApps.size) { FocusRequester() } + } + LaunchedEffect(focusIndex, focusRequesters.size) { + if (searchQuery.isEmpty() && focusRequesters.isNotEmpty() && focusIndex in focusRequesters.indices) { + gridState.animateScrollToItem(focusIndex) + try { + focusRequesters[focusIndex].requestFocus() + } catch (_: Exception) { + } + } + } + // Right joystick: 2x faster at full push with quadratic speed curve + JoystickGridScroll(gridState, activity?.rightStickScrollState, minSpeed = 2.5f, maxSpeed = 16f, quadratic = true) + // Left joystick: 75% slower scrolling (vertical only, for browsing store) + JoystickGridScroll(gridState, activity?.leftStickScrollState, deadZone = 0.15f, minSpeed = 0.3125f, maxSpeed = 2f) + FourByTwoGridView( + items = displayedApps, + modifier = Modifier.tabScreenPadding(top = TabGridTopPadding), + gridState = gridState, + keyOf = { it.id }, + ) { app, index, rowHeight -> + Box( + Modifier.height(rowHeight).then( + if (index in focusRequesters.indices) { + Modifier.focusRequester(focusRequesters[index]) + } else { + Modifier + }, + ), + ) { + SteamStoreCapsule( + app, + isInstalled = installStateById[app.id] == true, + isFocusedOverride = index == focusIndex, + isControllerActive = + ControllerHelper + .isControllerConnected(), + onClick = { + selectedAppForDialog = + app + }, + ) + } + } + } + + if (selectedAppForDialog != null) { + GameManagerDialog( + app = selectedAppForDialog!!, + onDismissRequest = { selectedAppForDialog = null }, + ) + } +} + +@Composable +internal fun UnifiedActivity.SteamStoreCapsule( + app: SteamApp, + isInstalled: Boolean, + listMode: Boolean = false, + isFocusedOverride: Boolean = false, + isControllerActive: Boolean = false, + onClick: () -> Unit, +) { + val context = LocalContext.current + var isFocused by remember { mutableStateOf(false) } + val clickInteraction = remember { MutableInteractionSource() } + val isPressed by clickInteraction.collectIsPressedAsState() + val glowAlpha by animateFloatAsState( + targetValue = if (isPressed) 0.7f else 0f, + animationSpec = if (isPressed) tween(100) else tween(400), + label = "steamCapsuleGlow", + ) + val effectiveFocus = isControllerActive && (isFocusedOverride || isFocused) + val borderColor = if (isControllerActive) CardBorder else Color.Transparent + + if (listMode) { + Box( + modifier = + Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(14.dp)) + .border(1.dp, borderColor, RoundedCornerShape(14.dp)) + .chasingBorder(isFocused = effectiveFocus, paused = chasingBordersPaused.value, cornerRadius = 14.dp) + .background(CardDark, RoundedCornerShape(14.dp)) + .onFocusChanged { isFocused = it.isFocused } + .focusable() + .then( + if (glowAlpha > 0f) { + Modifier.drawWithContent { + drawContent() + drawRoundRect(color = AccentGlow, alpha = glowAlpha * 0.25f, cornerRadius = CornerRadius(14.dp.toPx())) + } + } else { + Modifier + }, + ).clickable(interactionSource = clickInteraction, indication = null, onClick = onClick), + ) { + // Hero background + AsyncImage( + model = + ImageRequest + .Builder(context) + .data(app.getHeroUrl()) + .crossfade(300) + .build(), + contentDescription = null, + modifier = + Modifier + .matchParentSize() + .graphicsLayer { alpha = 0.25f }, + contentScale = ContentScale.Crop, + ) + + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = + Modifier + .fillMaxWidth() + .padding(horizontal = 14.dp, vertical = 11.dp), + horizontalArrangement = Arrangement.Center, + ) { + Box( + Modifier + .height(52.dp) + .aspectRatio(462f / 174f) + .clip(RoundedCornerShape(8.dp)), + ) { + AsyncImage( + model = + ImageRequest + .Builder(context) + .data(app.getSmallCapsuleUrl()) + .crossfade(300) + .build(), + contentDescription = null, + modifier = Modifier.fillMaxSize(), + contentScale = ContentScale.Crop, + ) + if (isInstalled) { + StoreInstalledBadge( + modifier = Modifier.align(Alignment.BottomEnd).padding(4.dp), + compact = true, + ) + } + } + Spacer(Modifier.width(14.dp)) + Text( + text = app.name, + modifier = + Modifier + .weight(1f) + .then(if (effectiveFocus) Modifier.basicMarquee(iterations = Int.MAX_VALUE) else Modifier), + color = TextPrimary, + fontSize = 15.sp, + fontWeight = FontWeight.SemiBold, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + ) + } + } + } else { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = + Modifier + .fillMaxSize() + .border(1.dp, borderColor, RoundedCornerShape(16.dp)) + .chasingBorder(isFocused = effectiveFocus, paused = chasingBordersPaused.value, cornerRadius = 16.dp) + .background(CardDark, RoundedCornerShape(16.dp)) + .onFocusChanged { isFocused = it.isFocused } + .focusable() + .then( + if (glowAlpha > 0f) { + Modifier.drawWithContent { + drawContent() + drawRoundRect(color = AccentGlow, alpha = glowAlpha * 0.25f, cornerRadius = CornerRadius(16.dp.toPx())) + } + } else { + Modifier + }, + ).clickable(interactionSource = clickInteraction, indication = null, onClick = onClick), + ) { + Box( + Modifier + .fillMaxWidth() + .weight(1f) + .clip(RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp)), + ) { + val imageUrl = app.getCapsuleUrl() + + AsyncImage( + model = + ImageRequest + .Builder(context) + .data(imageUrl) + .crossfade(300) + .build(), + contentDescription = null, + modifier = Modifier.fillMaxSize(), + contentScale = ContentScale.Crop, + ) + + if (isInstalled) { + StoreInstalledBadge( + modifier = Modifier.align(Alignment.BottomEnd), + attachedCorner = true, + ) + } + } + + Text( + text = app.name, + modifier = + Modifier + .fillMaxWidth() + .padding(horizontal = 4.dp, vertical = 4.dp) + .then(if (effectiveFocus) Modifier.basicMarquee(iterations = Int.MAX_VALUE) else Modifier), + style = MaterialTheme.typography.bodySmall, + color = TextPrimary, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + textAlign = TextAlign.Center, + ) + } + } +} diff --git a/app/src/main/app/shell/UnifiedActivityUpdateChecks.kt b/app/src/main/app/shell/UnifiedActivityUpdateChecks.kt new file mode 100644 index 000000000..e40519c0d --- /dev/null +++ b/app/src/main/app/shell/UnifiedActivityUpdateChecks.kt @@ -0,0 +1,431 @@ +package com.winlator.cmod.app.shell + +import android.app.Activity +import android.app.PendingIntent +import android.content.Intent +import android.content.res.Configuration +import android.hardware.input.InputManager +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.drawable.BitmapDrawable +import android.net.Uri +import android.os.Bundle +import android.provider.DocumentsContract +import android.util.Log +import androidx.activity.SystemBarStyle +import androidx.activity.compose.BackHandler +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AppCompatActivity +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.EnterTransition +import androidx.compose.animation.ExitTransition +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.FastOutSlowInEasing +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.spring +import androidx.compose.animation.core.tween +import androidx.compose.animation.expandVertically +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.shrinkVertically +import androidx.compose.animation.slideInHorizontally +import androidx.compose.animation.slideOutHorizontally +import androidx.compose.animation.togetherWith +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background +import androidx.compose.foundation.basicMarquee +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.focusable +import androidx.compose.foundation.gestures.detectHorizontalDragGestures +import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsPressedAsState +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items +import androidx.compose.foundation.lazy.grid.itemsIndexed +import androidx.compose.foundation.lazy.grid.rememberLazyGridState +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.outlined.ArrowBack +import androidx.compose.material.icons.automirrored.outlined.ExitToApp +import androidx.compose.material.icons.automirrored.outlined.OpenInNew +import androidx.compose.material.icons.outlined.* +import androidx.compose.material3.* +import androidx.compose.material3.pulltorefresh.PullToRefreshBox +import androidx.compose.runtime.* +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.snapshotFlow +import androidx.compose.runtime.snapshots.SnapshotStateMap +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.zIndex +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusProperties +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.focus.focusTarget +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.geometry.CornerRadius +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.TileMode +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.input.pointer.PointerEventPass +import androidx.compose.ui.input.pointer.PointerEventType +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties +import androidx.lifecycle.lifecycleScope +import androidx.navigation.NavHostController +import androidx.navigation.NavType +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import androidx.navigation.navArgument +import coil.compose.AsyncImage +import coil.imageLoader +import coil.request.ImageRequest +import com.winlator.cmod.BuildConfig +import com.winlator.cmod.R +import com.winlator.cmod.app.PluviaApp +import com.winlator.cmod.app.db.PluviaDatabase +import com.winlator.cmod.app.service.DownloadService +import com.winlator.cmod.app.service.download.DownloadCoordinator +import com.winlator.cmod.app.update.UpdateChecker +import com.winlator.cmod.feature.settings.InputControlsFragment +import com.winlator.cmod.feature.settings.SettingsFocusZone +import com.winlator.cmod.feature.settings.SettingsHost +import com.winlator.cmod.feature.settings.SettingsNavBridge +import com.winlator.cmod.feature.settings.SettingsNavItem +import com.winlator.cmod.feature.setup.SetupWizardActivity +import com.winlator.cmod.feature.shortcuts.LibraryShortcutUtils +import com.winlator.cmod.feature.shortcuts.LibraryShortcutArtwork +import com.winlator.cmod.feature.shortcuts.ShortcutBroadcastReceiver +import com.winlator.cmod.feature.shortcuts.ShortcutSettingsComposeDialog +import com.winlator.cmod.feature.shortcuts.ShortcutsFragment +import com.winlator.cmod.feature.stores.common.StoreArtworkCache +import com.winlator.cmod.feature.stores.epic.data.EpicCredentials +import com.winlator.cmod.feature.stores.epic.data.EpicGame +import com.winlator.cmod.feature.stores.epic.data.EpicGameToken +import com.winlator.cmod.feature.stores.epic.service.EpicAuthManager +import com.winlator.cmod.feature.stores.epic.service.EpicCloudSavesManager +import com.winlator.cmod.feature.stores.epic.service.EpicConstants +import com.winlator.cmod.feature.stores.epic.service.EpicDownloadManager +import com.winlator.cmod.feature.stores.epic.service.EpicGameLauncher +import com.winlator.cmod.feature.stores.epic.service.EpicManager +import com.winlator.cmod.feature.stores.epic.service.EpicService +import com.winlator.cmod.feature.stores.epic.service.EpicUpdateInfo +import com.winlator.cmod.feature.stores.epic.ui.auth.EpicOAuthActivity +import com.winlator.cmod.feature.stores.gog.data.GOGDlcInfo +import com.winlator.cmod.feature.stores.gog.data.GOGGame +import com.winlator.cmod.feature.stores.gog.data.LibraryItem +import com.winlator.cmod.feature.stores.gog.service.GOGAuthManager +import com.winlator.cmod.feature.stores.gog.service.GOGConstants +import com.winlator.cmod.feature.stores.gog.service.GOGManifestSizes +import com.winlator.cmod.feature.stores.gog.service.GOGService +import com.winlator.cmod.feature.stores.gog.service.GOGUpdateInfo +import com.winlator.cmod.feature.stores.gog.ui.auth.GOGOAuthActivity +import com.winlator.cmod.feature.stores.steam.SteamLoginActivity +import com.winlator.cmod.feature.stores.steam.data.DepotInfo +import com.winlator.cmod.feature.stores.steam.data.DownloadInfo +import com.winlator.cmod.feature.stores.steam.data.SteamApp +import com.winlator.cmod.feature.stores.steam.enums.DownloadPhase +import com.winlator.cmod.feature.stores.steam.events.AndroidEvent +import com.winlator.cmod.feature.stores.steam.events.EventDispatcher +import com.winlator.cmod.feature.stores.steam.service.SteamService +import com.winlator.cmod.feature.stores.steam.utils.PrefManager +import com.winlator.cmod.feature.stores.steam.utils.getAvatarURL +import com.winlator.cmod.feature.sync.CloudSyncHelper +import com.winlator.cmod.feature.sync.google.CloudSyncManager +import com.winlator.cmod.feature.sync.google.GameSaveBackupManager +import com.winlator.cmod.feature.sync.ui.CloudSavesContent +import com.winlator.cmod.runtime.container.ContainerManager +import com.winlator.cmod.runtime.container.Shortcut +import com.winlator.cmod.runtime.display.XServerDisplayActivity +import com.winlator.cmod.runtime.display.environment.ImageFs +import com.winlator.cmod.runtime.input.ControllerHelper +import com.winlator.cmod.runtime.wine.PeIconExtractor +import com.winlator.cmod.shared.android.ActivityResultHost +import com.winlator.cmod.shared.android.AppTerminationHelper +import com.winlator.cmod.shared.android.DirectoryPickerDialog +import com.winlator.cmod.shared.android.FixedFontScaleAppCompatActivity +import com.winlator.cmod.shared.android.RefreshRateUtils +import com.winlator.cmod.shared.io.StorageUtils +import com.winlator.cmod.shared.io.FileUtils +import com.winlator.cmod.shared.ui.CarouselView +import com.winlator.cmod.shared.ui.dialog.PopupDialog +import com.winlator.cmod.shared.ui.dialog.PopupTextAction +import androidx.compose.foundation.focusGroup +import com.winlator.cmod.shared.ui.focus.controllerFocusGlow +import com.winlator.cmod.shared.ui.focus.controllerMenuInput +import com.winlator.cmod.shared.ui.focus.controllerTextFieldEscape +import com.winlator.cmod.shared.ui.nav.DialogPaneNav +import com.winlator.cmod.shared.ui.nav.LocalPaneNav +import com.winlator.cmod.shared.ui.nav.PANE_DIR_ACTIVATE +import com.winlator.cmod.shared.ui.nav.PANE_DIR_DOWN +import com.winlator.cmod.shared.ui.nav.PANE_DIR_LEFT +import com.winlator.cmod.shared.ui.nav.PANE_DIR_RIGHT +import com.winlator.cmod.shared.ui.nav.PANE_DIR_SECONDARY +import com.winlator.cmod.shared.ui.nav.PANE_DIR_UP +import com.winlator.cmod.shared.ui.nav.PaneNavRegistry +import com.winlator.cmod.shared.ui.nav.paneNavItem +import com.winlator.cmod.shared.ui.FourByTwoGridView +import com.winlator.cmod.shared.ui.JoystickGridScroll +import com.winlator.cmod.shared.ui.JoystickListScroll +import com.winlator.cmod.shared.ui.ListView +import com.winlator.cmod.shared.ui.widget.chasingBorder +import com.winlator.cmod.shared.theme.WinNativeTheme +import dagger.hilt.android.AndroidEntryPoint +import dagger.Lazy +import com.winlator.cmod.feature.stores.steam.enums.EPersonaState +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import javax.inject.Inject +import kotlin.math.abs +import kotlin.math.roundToInt + +// Task-progress popup + store update-check helpers, split out of UnifiedActivity.kt (behavior-identical). + +internal fun UnifiedActivity.showTaskProgressPopup( + info: DownloadInfo, + gameName: String, + completeMsg: String, + failedMsg: String, + completeAsToast: Boolean = false, +) { + taskCheckingShown = false + taskProgressInfo = info + taskProgressGameName = gameName + taskProgressCompleteMsg = completeMsg + taskProgressFailedMsg = failedMsg + taskProgressCompleteAsToast = completeAsToast + taskProgressShown = true + taskDoneMessage = null +} + +// Runs a Steam update check behind the checking pop-up. +internal fun UnifiedActivity.startUpdateCheck(appId: Int, gameName: String) { + if (updateCheckInProgress) return + if (!com.winlator.cmod.app.service.NetworkMonitor.hasInternet.value) { + com.winlator.cmod.shared.ui.toast.WinToast.show( + this, + getString(R.string.downloads_no_internet), + android.widget.Toast.LENGTH_SHORT, + ) + return + } + updateCheckInProgress = true + taskCheckingGameName = gameName + taskCheckingShown = true + taskDoneMessage = null + lifecycleScope.launch { + val result = + runCatching { + withContext(Dispatchers.IO) { SteamService.checkForAppUpdate(appId) } + }.getOrNull() + try { + when { + result == null || result.message != null -> { + taskCheckingShown = false + taskDoneFailed = true + taskDoneMessage = getString(R.string.store_game_update_check_failed_notice) + } + result.hasUpdate -> { + val started = + withContext(Dispatchers.IO) { + SteamService.downloadAppForUpdate(appId, result.depotIds) + } + if (started != null) { + showTaskProgressPopup( + started, + gameName, + getString(R.string.store_game_update_complete), + getString(R.string.store_game_update_failed_notice), + ) + } else { + // A download is already running — downloadApp showed its toast. + taskCheckingShown = false + } + } + else -> { + taskCheckingShown = false + taskDoneFailed = false + taskDoneMessage = getString(R.string.store_game_no_updates_notice) + } + } + } finally { + updateCheckInProgress = false + } + } +} + +internal fun UnifiedActivity.startGogUpdateCheck(gameId: String, gameName: String) { + if (updateCheckInProgress) return + if (!com.winlator.cmod.app.service.NetworkMonitor.hasInternet.value) { + com.winlator.cmod.shared.ui.toast.WinToast.show( + this, + getString(R.string.downloads_no_internet), + android.widget.Toast.LENGTH_SHORT, + ) + return + } + updateCheckInProgress = true + taskCheckingGameName = gameName + taskCheckingShown = true + taskDoneMessage = null + lifecycleScope.launch { + val result = + runCatching { + withContext(Dispatchers.IO) { GOGService.checkForGameUpdate(this@startGogUpdateCheck, gameId) } + }.getOrNull() + try { + when { + result == null || result.message != null -> { + taskCheckingShown = false + taskDoneFailed = true + taskDoneMessage = getString(R.string.store_game_update_check_failed_notice) + } + result.hasUpdate -> { + val started = + withContext(Dispatchers.IO) { + GOGService.updateGameFiles(this@startGogUpdateCheck, gameId) + } + if (started != null) { + showTaskProgressPopup( + started, + gameName, + getString(R.string.store_game_update_complete), + getString(R.string.store_game_update_failed_notice), + ) + } else { + taskCheckingShown = false + } + } + else -> { + taskCheckingShown = false + taskDoneFailed = false + taskDoneMessage = getString(R.string.store_game_no_updates_notice) + } + } + } finally { + updateCheckInProgress = false + } + } +} + +internal fun UnifiedActivity.startEpicUpdateCheck(appId: Int, gameName: String) { + if (updateCheckInProgress) return + if (!com.winlator.cmod.app.service.NetworkMonitor.hasInternet.value) { + com.winlator.cmod.shared.ui.toast.WinToast.show( + this, + getString(R.string.downloads_no_internet), + android.widget.Toast.LENGTH_SHORT, + ) + return + } + updateCheckInProgress = true + taskCheckingGameName = gameName + taskCheckingShown = true + taskDoneMessage = null + lifecycleScope.launch { + val result = + runCatching { + withContext(Dispatchers.IO) { EpicService.checkForGameUpdate(this@startEpicUpdateCheck, appId) } + }.getOrNull() + try { + when { + result == null || result.message != null -> { + taskCheckingShown = false + taskDoneFailed = true + taskDoneMessage = getString(R.string.store_game_update_check_failed_notice) + } + result.hasUpdate -> { + val started = + withContext(Dispatchers.IO) { + EpicService.updateGameFiles(this@startEpicUpdateCheck, appId) + } + if (started != null) { + showTaskProgressPopup( + started, + gameName, + getString(R.string.store_game_update_complete), + getString(R.string.store_game_update_failed_notice), + ) + } else { + taskCheckingShown = false + } + } + else -> { + taskCheckingShown = false + taskDoneFailed = false + taskDoneMessage = getString(R.string.store_game_no_updates_notice) + } + } + } finally { + updateCheckInProgress = false + } + } +} diff --git a/app/src/main/app/shell/WorkshopScreen.kt b/app/src/main/app/shell/WorkshopScreen.kt new file mode 100644 index 000000000..ea52ccc5a --- /dev/null +++ b/app/src/main/app/shell/WorkshopScreen.kt @@ -0,0 +1,646 @@ +package com.winlator.cmod.app.shell + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.navigationBars +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Close +import androidx.compose.material.icons.outlined.Construction +import androidx.compose.material.icons.outlined.Delete +import androidx.compose.material.icons.outlined.Download +import androidx.compose.material.icons.outlined.Inventory2 +import androidx.compose.material.icons.outlined.Refresh +import androidx.compose.material.icons.outlined.Search +import androidx.compose.material.icons.outlined.SearchOff +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import coil.compose.AsyncImage +import coil.request.CachePolicy +import coil.request.ImageRequest +import com.winlator.cmod.R +import com.winlator.cmod.shared.io.StorageUtils +import com.winlator.cmod.shared.ui.nav.DialogPaneNav +import com.winlator.cmod.shared.ui.nav.LocalPaneNav +import com.winlator.cmod.shared.ui.nav.PaneNavRegistry +import com.winlator.cmod.shared.ui.nav.paneNavItem +import org.json.JSONArray + +/** A single Steam Workshop / UGC item surfaced in the Workshop browser. */ +internal data class StoreWorkshopItem( + val publishedFileId: Long, + val title: String, + val author: String, + val previewImageUrl: String?, + val fileSizeBytes: Long, + val manifestId: Long = 0L, + val timeUpdated: Long = 0L, + val isInstalled: Boolean = false, +) + +/** Loading lifecycle for the Workshop browser. */ +internal enum class WorkshopLoadState { LOADING, READY, ERROR } + +// Palette — mirrors the per-game Settings dialog so the window feels native. +private val WsBg = Color(0xFF12121B) +private val WsBorder = Color(0xFF2A2A3A) +private val WsInputBg = Color(0xFF171722) +private val WsAccent = Color(0xFF1A9FFF) +private val WsAccentGlow = Color(0xFF58A6FF) +private val WsTextPrimary = Color(0xFFF0F4FF) +private val WsTextSecondary = Color(0xFF93A6BC) +private val WsTextDim = Color(0xFF6E7681) +private val WsInstalledTitle = Color(0xFFB7F8CE) +private val WsDanger = Color(0xFFFF6B6B) +private val WsScrim = Color(0xFF000000) + +/** + * The Steam Workshop browser — a Settings-shaped modal window with a search + * field in the header and a scrollable list of subscribed Workshop items the + * user can install into the game's `steam_settings/mods` directory. + * + * Stateless: all data and callbacks are hoisted to the [WorkshopDialog] wrapper. + */ +@Composable +internal fun StoreWorkshopScreen( + gameTitle: String, + loadState: WorkshopLoadState, + errorMessage: String?, + items: List, + query: String, + busyIds: Set, + onQueryChange: (String) -> Unit, + onInstall: (Long) -> Unit, + onUninstall: (Long) -> Unit, + onRetry: () -> Unit, + onClose: () -> Unit, +) { + val registry = remember { PaneNavRegistry() } + CompositionLocalProvider(LocalPaneNav provides registry) { + DialogPaneNav(registry, onDismiss = onClose) + BoxWithConstraints( + modifier = + Modifier + .fillMaxSize() + // Dim the game-detail screen behind so the modal reads as foreground. + .background(WsScrim.copy(alpha = 0.6f)) + .windowInsetsPadding(WindowInsets.navigationBars), + contentAlignment = Alignment.Center, + ) { + val dialogWidth = (maxWidth - 32.dp).coerceAtMost(560.dp) + val dialogHeight = (maxHeight - 48.dp).coerceIn(360.dp, 640.dp) + Surface( + modifier = + Modifier + .widthIn(min = 320.dp, max = dialogWidth) + .fillMaxWidth() + .height(dialogHeight), + shape = RoundedCornerShape(14.dp), + color = WsBg, + border = BorderStroke(1.dp, WsBorder), + tonalElevation = 8.dp, + ) { + Column(Modifier.fillMaxSize()) { + WorkshopHeader( + gameTitle = gameTitle, + itemCount = if (loadState == WorkshopLoadState.READY) items.size else null, + onClose = onClose, + ) + HorizontalDivider(color = WsBorder, thickness = 0.5.dp) + WorkshopSearchBar( + query = query, + enabled = loadState == WorkshopLoadState.READY, + onQueryChange = onQueryChange, + ) + HorizontalDivider(color = WsBorder, thickness = 0.5.dp) + Box(Modifier.fillMaxWidth().weight(1f)) { + when (loadState) { + WorkshopLoadState.LOADING -> + WorkshopStatus( + icon = null, + title = stringResource(R.string.workshop_loading_items), + subtitle = stringResource(R.string.workshop_loading_subtitle), + ) + WorkshopLoadState.ERROR -> + WorkshopStatus( + icon = Icons.Outlined.Refresh, + title = stringResource(R.string.workshop_error_title), + subtitle = errorMessage ?: stringResource(R.string.workshop_error_subtitle), + actionLabel = stringResource(R.string.session_drawer_retry), + onAction = onRetry, + ) + WorkshopLoadState.READY -> + if (items.isEmpty()) { + WorkshopStatus( + icon = if (query.isBlank()) Icons.Outlined.Inventory2 else Icons.Outlined.SearchOff, + title = + if (query.isBlank()) { + stringResource(R.string.workshop_empty_title) + } else { + stringResource(R.string.workshop_search_empty_title, query) + }, + subtitle = + if (query.isBlank()) { + stringResource(R.string.workshop_empty_subtitle) + } else { + stringResource(R.string.workshop_search_empty_subtitle) + }, + ) + } else { + WorkshopList( + items = items, + busyIds = busyIds, + onInstall = onInstall, + onUninstall = onUninstall, + ) + } + } + } + } + } + } + } +} + +@Composable +private fun WorkshopHeader( + gameTitle: String, + itemCount: Int?, + onClose: () -> Unit, +) { + Row( + modifier = Modifier.fillMaxWidth().padding(start = 16.dp, end = 8.dp, top = 10.dp, bottom = 10.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(10.dp), + ) { + Box( + Modifier + .size(34.dp) + .clip(RoundedCornerShape(9.dp)) + .background(WsAccent.copy(alpha = 0.16f)), + contentAlignment = Alignment.Center, + ) { + Icon( + Icons.Outlined.Construction, + contentDescription = null, + tint = WsAccentGlow, + modifier = Modifier.size(19.dp), + ) + } + Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(1.dp)) { + Text( + stringResource(R.string.workshop_title), + color = WsTextSecondary, + fontSize = 9.sp, + fontWeight = FontWeight.Bold, + letterSpacing = 0.9.sp, + ) + Text( + gameTitle, + style = MaterialTheme.typography.titleSmall, + color = WsTextPrimary, + fontWeight = FontWeight.Bold, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + if (itemCount != null) { + Surface( + modifier = + Modifier.semantics { + contentDescription = "$itemCount Workshop items" + }, + color = WsAccent.copy(alpha = 0.14f), + shape = RoundedCornerShape(7.dp), + ) { + Text( + itemCount.toString(), + color = WsAccentGlow, + fontSize = 11.sp, + fontWeight = FontWeight.Bold, + modifier = Modifier.padding(horizontal = 9.dp, vertical = 3.dp), + ) + } + } + IconButton(onClick = onClose, modifier = Modifier.size(36.dp).paneNavItem(onActivate = onClose)) { + Icon( + Icons.Outlined.Close, + contentDescription = stringResource(R.string.common_ui_close), + tint = WsTextSecondary, + modifier = Modifier.size(20.dp), + ) + } + } +} + +@Composable +private fun WorkshopSearchBar( + query: String, + enabled: Boolean, + onQueryChange: (String) -> Unit, +) { + val keyboard = LocalSoftwareKeyboardController.current + val fieldFocus = remember { FocusRequester() } + Row( + modifier = + Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp, vertical = 8.dp) + .paneNavItem(cornerRadius = 9.dp, onActivate = { runCatching { fieldFocus.requestFocus() } }) + .clip(RoundedCornerShape(9.dp)) + .background(WsInputBg) + .border(1.dp, WsBorder, RoundedCornerShape(9.dp)) + .padding(horizontal = 11.dp, vertical = 9.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(9.dp), + ) { + Icon( + Icons.Outlined.Search, + contentDescription = null, + tint = if (enabled) WsAccent else WsTextDim, + modifier = Modifier.size(18.dp), + ) + Box(Modifier.weight(1f), contentAlignment = Alignment.CenterStart) { + if (query.isEmpty()) { + Text( + stringResource(R.string.workshop_search_items), + color = WsTextDim, + fontSize = 13.sp, + ) + } + BasicTextField( + value = query, + onValueChange = onQueryChange, + enabled = enabled, + singleLine = true, + textStyle = TextStyle(color = WsTextPrimary, fontSize = 13.sp), + cursorBrush = Brush.verticalGradient(listOf(WsAccent, WsAccentGlow)), + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search), + keyboardActions = KeyboardActions(onSearch = { keyboard?.hide() }), + modifier = Modifier.fillMaxWidth().focusRequester(fieldFocus), + ) + } + if (query.isNotEmpty()) { + Box( + modifier = + Modifier + .size(34.dp) + .clip(CircleShape) + .clickable { onQueryChange("") }, + contentAlignment = Alignment.Center, + ) { + Icon( + Icons.Outlined.Close, + contentDescription = stringResource(R.string.workshop_clear_search), + tint = WsTextSecondary, + modifier = Modifier.size(18.dp), + ) + } + } + } +} + +@Composable +private fun WorkshopList( + items: List, + busyIds: Set, + onInstall: (Long) -> Unit, + onUninstall: (Long) -> Unit, +) { + Column( + modifier = + Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()) + .padding(vertical = 4.dp), + ) { + items.forEach { item -> + WorkshopItemRow( + item = item, + busy = item.publishedFileId in busyIds, + onInstall = { onInstall(item.publishedFileId) }, + onUninstall = { onUninstall(item.publishedFileId) }, + ) + HorizontalDivider( + color = Color.White.copy(alpha = 0.06f), + thickness = 1.dp, + modifier = Modifier.padding(horizontal = 14.dp), + ) + } + } +} + +@Composable +private fun WorkshopItemRow( + item: StoreWorkshopItem, + busy: Boolean, + onInstall: () -> Unit, + onUninstall: () -> Unit, +) { + val context = LocalContext.current + Row( + modifier = Modifier.fillMaxWidth().padding(horizontal = 12.dp, vertical = 9.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(11.dp), + ) { + Box( + Modifier + .size(width = 66.dp, height = 42.dp) + .clip(RoundedCornerShape(7.dp)) + .background(WsInputBg) + .border(1.dp, WsBorder, RoundedCornerShape(7.dp)), + contentAlignment = Alignment.Center, + ) { + if (!item.previewImageUrl.isNullOrBlank()) { + val request = + ImageRequest + .Builder(context) + .data(item.previewImageUrl) + .crossfade(120) + .memoryCachePolicy(CachePolicy.ENABLED) + .diskCachePolicy(CachePolicy.ENABLED) + .build() + AsyncImage( + model = request, + contentDescription = null, + modifier = Modifier.fillMaxSize(), + contentScale = ContentScale.Crop, + ) + } else { + Icon( + Icons.Outlined.Construction, + contentDescription = null, + tint = WsTextDim, + modifier = Modifier.size(20.dp), + ) + } + } + Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(2.dp)) { + Text( + item.title, + color = if (item.isInstalled) WsInstalledTitle else WsTextPrimary, + fontSize = 13.sp, + fontWeight = FontWeight.SemiBold, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + ) + val itemFallback = stringResource(R.string.workshop_item_fallback) + Text( + buildString { + if (item.author.isNotBlank()) append(item.author) + if (item.fileSizeBytes > 0L) { + if (isNotEmpty()) append(" · ") + append(StorageUtils.formatBinarySize(item.fileSizeBytes)) + } + if (isEmpty()) append(itemFallback) + }, + color = WsTextSecondary, + fontSize = 11.sp, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + WorkshopRowAction( + isInstalled = item.isInstalled, + busy = busy, + onInstall = onInstall, + onUninstall = onUninstall, + ) + } +} + +@Composable +private fun WorkshopRowAction( + isInstalled: Boolean, + busy: Boolean, + onInstall: () -> Unit, + onUninstall: () -> Unit, +) { + when { + busy -> + CircularProgressIndicator( + modifier = Modifier.size(20.dp), + color = WsAccentGlow, + strokeWidth = 2.dp, + ) + isInstalled -> + WorkshopActionPill( + icon = Icons.Outlined.Delete, + label = stringResource(R.string.common_ui_uninstall), + tint = WsDanger, + onClick = onUninstall, + ) + else -> + WorkshopActionPill( + icon = Icons.Outlined.Download, + label = stringResource(R.string.common_ui_install), + tint = WsAccentGlow, + onClick = onInstall, + ) + } +} + +/** Compact pill action used for a Workshop row's Install / Uninstall button. */ +@Composable +private fun WorkshopActionPill( + icon: ImageVector, + label: String, + tint: Color, + onClick: () -> Unit, +) { + // Outer Box keeps the touch target >= 44dp tall while the pill stays compact. + Box( + modifier = + Modifier + .heightIn(min = 44.dp) + .paneNavItem(cornerRadius = 8.dp, onActivate = onClick, tapToSelect = true) + .clip(RoundedCornerShape(8.dp)) + .clickable(onClick = onClick), + contentAlignment = Alignment.Center, + ) { + Surface( + color = tint.copy(alpha = 0.16f), + shape = RoundedCornerShape(8.dp), + border = BorderStroke(1.dp, tint.copy(alpha = 0.4f)), + ) { + Row( + modifier = Modifier.padding(horizontal = 11.dp, vertical = 7.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(5.dp), + ) { + Icon( + icon, + contentDescription = null, + tint = tint, + modifier = Modifier.size(15.dp), + ) + Text( + label, + color = tint, + fontSize = 12.sp, + fontWeight = FontWeight.Bold, + ) + } + } + } +} + +/** + * Parse the JSON array returned by `WnSteamSession.getSubscribedWorkshopItems` + * (objects keyed publishedFileId / title / previewUrl / fileSizeBytes / ) into + * the browser's [StoreWorkshopItem] model. [installedIds] marks which items + * already have content staged on disk. Returns an empty list on malformed JSON. + */ +internal fun parseWorkshopItemsJson( + json: String, + installedIds: Set = emptySet(), +): List { + val arr = + try { + JSONArray(json.trim()) + } catch (e: Exception) { + return emptyList() + } + val out = ArrayList(arr.length()) + for (i in 0 until arr.length()) { + val o = arr.optJSONObject(i) ?: continue + val id = o.optLong("publishedFileId", 0L) + if (id == 0L) continue + out.add( + StoreWorkshopItem( + publishedFileId = id, + title = o.optString("title").ifBlank { id.toString() }, + author = "", + previewImageUrl = o.optString("previewUrl").takeIf { it.isNotBlank() }, + fileSizeBytes = o.optLong("fileSizeBytes", 0L), + manifestId = o.optLong("hcontentFile", 0L), + timeUpdated = o.optLong("timeUpdated", 0L), + isInstalled = id in installedIds, + ), + ) + } + return out +} + +@Composable +private fun WorkshopStatus( + icon: ImageVector?, + title: String, + subtitle: String, + actionLabel: String? = null, + onAction: (() -> Unit)? = null, +) { + Column( + modifier = Modifier.fillMaxSize().padding(32.dp), + verticalArrangement = Arrangement.spacedBy(10.dp, Alignment.CenterVertically), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + if (icon != null) { + Icon( + icon, + contentDescription = null, + tint = WsTextDim, + modifier = Modifier.size(44.dp), + ) + } else { + CircularProgressIndicator( + modifier = Modifier.size(34.dp), + color = WsAccent, + strokeWidth = 3.dp, + ) + } + Text( + title, + color = WsTextPrimary, + fontSize = 14.sp, + fontWeight = FontWeight.Bold, + textAlign = androidx.compose.ui.text.style.TextAlign.Center, + ) + Text( + subtitle, + color = WsTextSecondary, + fontSize = 12.sp, + textAlign = androidx.compose.ui.text.style.TextAlign.Center, + ) + if (actionLabel != null && onAction != null) { + Spacer(Modifier.height(2.dp)) + Surface( + modifier = Modifier.paneNavItem(onActivate = onAction, tapToSelect = true).clip(RoundedCornerShape(8.dp)).clickable(onClick = onAction), + color = WsAccent.copy(alpha = 0.16f), + shape = RoundedCornerShape(8.dp), + border = BorderStroke(1.dp, WsAccentGlow.copy(alpha = 0.4f)), + ) { + Row( + modifier = Modifier.padding(horizontal = 16.dp, vertical = 9.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(6.dp), + ) { + Icon( + Icons.Outlined.Refresh, + contentDescription = null, + tint = WsAccentGlow, + modifier = Modifier.size(16.dp), + ) + Text( + actionLabel, + color = WsAccentGlow, + fontSize = 13.sp, + fontWeight = FontWeight.Bold, + ) + } + } + } + } +} diff --git a/app/src/main/app/update/UpdateChecker.kt b/app/src/main/app/update/UpdateChecker.kt index f2246a77b..f5423af44 100644 --- a/app/src/main/app/update/UpdateChecker.kt +++ b/app/src/main/app/update/UpdateChecker.kt @@ -39,21 +39,17 @@ object UpdateChecker { private const val PREF_INSTALL_TIMESTAMP = "app_install_timestamp" private const val PREF_LAST_UPDATE_CHECK = "last_update_check_time" - private const val CHECK_INTERVAL_MS = 60 * 60 * 1000L // 1 hour - private const val MANUAL_CHECK_COOLDOWN_MS = 30 * 1000L // 30 seconds - private const val POST_GAME_CHECK_DELAY_MS = 10 * 1000L // 10 seconds + private const val CHECK_INTERVAL_MS = 60 * 60 * 1000L + private const val MANUAL_CHECK_COOLDOWN_MS = 30 * 1000L + private const val POST_GAME_CHECK_DELAY_MS = 10 * 1000L - /** Tracks the last manual check time for 30s cooldown. */ private val lastManualCheckTime = AtomicLong(0L) - /** Prevents overlapping background checks. */ private val isChecking = AtomicBoolean(false) - /** Background handler for periodic checks. */ private var backgroundHandler: Handler? = null private var backgroundRunnable: Runnable? = null - /** Post-game exit handler. */ private var postGameHandler: Handler? = null private var postGameRunnable: Runnable? = null @@ -67,20 +63,12 @@ object UpdateChecker { private val mainHandler = Handler(Looper.getMainLooper()) - // ── Public API ──────────────────────────────────────────────────── - - /** - * Returns true if the user has the "Check for Updates" toggle enabled. - */ fun isEnabled(context: Context): Boolean { val prefs = PreferenceManager.getDefaultSharedPreferences(context) return prefs.getBoolean(PREF_CHECK_FOR_UPDATES, false) } - /** - * Records the app install/update timestamp from PackageManager. - * Should be called once during Application.onCreate(). - */ + // Record the app install/update timestamp from PackageManager. fun refreshInstallTimestamp(context: Context) { val prefs = PreferenceManager.getDefaultSharedPreferences(context) try { @@ -94,19 +82,13 @@ object UpdateChecker { } } - /** - * Returns true if enough time has passed since the last check. - */ fun isDueForCheck(context: Context): Boolean { val prefs = PreferenceManager.getDefaultSharedPreferences(context) val lastCheck = prefs.getLong(PREF_LAST_UPDATE_CHECK, 0L) return System.currentTimeMillis() - lastCheck >= CHECK_INTERVAL_MS } - /** - * Starts the hourly background loop when auto-update is enabled. - * Call from the main Activity's onResume/onCreate. - */ + // Start the hourly background loop when auto-update is enabled. fun startBackgroundLoop(context: Context) { stopBackgroundLoop() if (!isEnabled(context)) return @@ -123,24 +105,16 @@ object UpdateChecker { } } } - // First tick after 5 seconds (give the app time to finish initialising) backgroundHandler?.postDelayed(backgroundRunnable!!, 5_000L) } - /** - * Stops the hourly background loop. Call from onDestroy or when the - * toggle is turned off. - */ fun stopBackgroundLoop() { backgroundRunnable?.let { backgroundHandler?.removeCallbacks(it) } backgroundHandler = null backgroundRunnable = null } - /** - * Perform an automatic update check. Skipped if not due or already running. - * @param force If true, bypasses the interval timer (first app open). - */ + // Perform an automatic update check. fun checkForUpdate( context: Context, force: Boolean = false, @@ -151,10 +125,7 @@ object UpdateChecker { launchCheck(context) } - /** - * Manual "Check" button — respects a 30-second cooldown. - * @return `true` if the check was started, `false` if still in cooldown. - */ + // Manual check; returns false while in cooldown. fun checkForUpdateManual(context: Context): Boolean { val now = System.currentTimeMillis() val last = lastManualCheckTime.get() @@ -164,21 +135,13 @@ object UpdateChecker { return true } - /** - * Returns the remaining cooldown in seconds for the manual check button. - * Returns 0 if the button is ready. - */ fun manualCheckCooldownSeconds(): Int { val elapsed = System.currentTimeMillis() - lastManualCheckTime.get() val remaining = MANUAL_CHECK_COOLDOWN_MS - elapsed return if (remaining > 0) ((remaining + 999) / 1000).toInt() else 0 } - /** - * Schedule a deferred update check after a game exits. - * If another game is launched before the delay, cancel the pending check - * via [cancelPostGameCheck]. - */ + // Schedule a deferred update check after a game exits. fun schedulePostGameCheck(context: Context) { cancelPostGameCheck() if (!isEnabled(context)) return @@ -194,18 +157,12 @@ object UpdateChecker { postGameHandler?.postDelayed(postGameRunnable!!, POST_GAME_CHECK_DELAY_MS) } - /** - * Cancel the pending post-game check (e.g. user launched another game). - */ fun cancelPostGameCheck() { postGameRunnable?.let { postGameHandler?.removeCallbacks(it) } postGameHandler = null postGameRunnable = null } - /** - * Resets the last-check timer so the next periodic tick runs immediately. - */ fun resetCheckTimer(context: Context) { PreferenceManager .getDefaultSharedPreferences(context) @@ -214,8 +171,6 @@ object UpdateChecker { .apply() } - // ── Internal ────────────────────────────────────────────────────── - private fun isAutoCheckAllowed(): Boolean { val activity = PluviaApp.currentForegroundActivity ?: return false return activity !is XServerDisplayActivity @@ -233,7 +188,6 @@ object UpdateChecker { showUpdateDialog(context, result) } } - // Record that we checked PreferenceManager .getDefaultSharedPreferences(context) .edit() @@ -255,15 +209,8 @@ object UpdateChecker { val releaseNotes: String?, ) - /** - * Fetches the Downloads page HTML, parses the "Last Updated:" line, - * and compares it against the app's install timestamp. - * - * This is the fastest approach — a single lightweight GET of the HTML - * page rather than HEAD requests through the download.php redirector. - */ + // Fetch the downloads page and compare its "Last Updated" date. private fun fetchUpdateInfo(context: Context): UpdateInfo? { - // 1. Fetch the HTML page val pageRequest = Request .Builder() @@ -280,7 +227,6 @@ object UpdateChecker { pageResponse.body?.string() ?: return null } - // 2. Parse "Last Updated: March 29, 2026, 5:46 am EDT" val pattern = Pattern.compile( """Last\s+Updated:\s*(.+?)(?:\r?\n|<)""", @@ -299,24 +245,22 @@ object UpdateChecker { return null } - // 3. Compare with install timestamp val prefs = PreferenceManager.getDefaultSharedPreferences(context) val installTimestamp = prefs.getLong(PREF_INSTALL_TIMESTAMP, System.currentTimeMillis()) if (serverDate.time <= installTimestamp) { - return null // No update available + return null } - // 4. Build download URL based on package name val apkType = when (context.packageName) { "com.ludashi.benchmark" -> "ludashi" "com.tencent.ig" -> null + "com.antutu.ABenchMark" -> null else -> "standard" } ?: return null val downloadUrl = "${DOWNLOADS_PAGE_URL}download.php?type=$apkType" - // 5. Fetch optional release notes val releaseNotes = fetchReleaseNotes() val dateFormat = SimpleDateFormat("MMM dd, yyyy 'at' hh:mm a", Locale.US) @@ -331,9 +275,6 @@ object UpdateChecker { ) } - /** - * Parses date strings like "March 29, 2026, 5:46 am EDT". - */ private fun parseLastUpdatedDate(dateStr: String): Date? { val formats = arrayOf( @@ -394,7 +335,6 @@ object UpdateChecker { setPadding(padding, padding, padding, padding) } - // Released date val releasedLabel = TextView(context).apply { text = "Released: ${info.serverModifiedFormatted}" @@ -403,7 +343,6 @@ object UpdateChecker { } container.addView(releasedLabel) - // Release notes if (!info.releaseNotes.isNullOrBlank()) { val divider = android.view.View(context).apply { diff --git a/app/src/main/assets/ddrawrapper/dd7to9.tzst b/app/src/main/assets/ddrawrapper/dd7to9.tzst index bc10eef27..e5dd2f764 100644 Binary files a/app/src/main/assets/ddrawrapper/dd7to9.tzst and b/app/src/main/assets/ddrawrapper/dd7to9.tzst differ diff --git a/app/src/main/assets/ddrawrapper/ddraw-11.8.tzst b/app/src/main/assets/ddrawrapper/ddraw-11.8.tzst new file mode 100644 index 000000000..a74776acf Binary files /dev/null and b/app/src/main/assets/ddrawrapper/ddraw-11.8.tzst differ diff --git a/app/src/main/assets/ddrawrapper/ddraw-4.21.tzst b/app/src/main/assets/ddrawrapper/ddraw-4.21.tzst new file mode 100644 index 000000000..dc8b5fa57 Binary files /dev/null and b/app/src/main/assets/ddrawrapper/ddraw-4.21.tzst differ diff --git a/app/src/main/assets/dnas/dnas_bypass.json b/app/src/main/assets/dnas/dnas_bypass.json new file mode 100644 index 000000000..fb7610484 --- /dev/null +++ b/app/src/main/assets/dnas/dnas_bypass.json @@ -0,0 +1,514 @@ +{ + "SCES-51706": { + "title": "Amplitude", + "cheats": [ + { + "name": "DNAS Patch", + "codes": [ + "D04E7BFA 00000001", + "2025BE18 14400013" + ], + "auto": true + } + ] + }, + "SLUS-21165": { + "title": "Arc the Lad: End of Darkness", + "cheats": [ + { + "name": "DNAS Bypass by Harry62", + "codes": [ + "2011AF84 00000000" + ], + "auto": true + } + ] + }, + "SLES-53729": { + "title": "Battlefield 2: Modern Combat", + "cheats": [ + { + "name": "DNAS Bypass Code", + "codes": [ + "2021A268 00000000" + ], + "crc": "185D22A9", + "auto": true + }, + { + "name": "DNAS Bypass Code", + "codes": [ + "2021A078 00000000" + ], + "crc": "89CDE501", + "auto": true + } + ] + }, + "SLES-53730": { + "title": "Battlefield 2: Modern Combat", + "cheats": [ + { + "name": "DNAS Bypass Code", + "codes": [ + "2021A268 00000000" + ], + "auto": true + } + ] + }, + "SLPM-66206": { + "title": "Battlefield 2: Modern Combat", + "cheats": [ + { + "name": "DNAS Bypass Code", + "codes": [ + "2021A078 00000000" + ], + "auto": true + } + ] + }, + "SLUS-21026": { + "title": "Battlefield 2: Modern Combat", + "cheats": [ + { + "name": "DNAS Bypass", + "codes": [ + "2021A240 00000000" + ], + "auto": true + } + ] + }, + "SLES-52782": { + "title": "Call of Duty: Finest Hour", + "cheats": [ + { + "name": "DNAS Bypass Code", + "codes": [ + "D02942F0 0000F0B6", + "202942F0 00000000" + ], + "auto": true + } + ] + }, + "SCES-50781": { + "title": "Destruction Derby Arenas", + "cheats": [ + { + "name": "[MODE 3] DNAS Patch", + "codes": [ + "D1D8139C 24020006", + "01D81390 00000000", + "D1D8139C 24020006", + "01D8139C 00000005" + ], + "auto": true + } + ] + }, + "SCES-53033": { + "title": "Formula One 05", + "cheats": [ + { + "name": "DNAS bypass", + "codes": [ + "D039FBA4 24020006", + "0039FB98 00000000", + "D039FBA4 24020006", + "0039FBA4 00000005" + ], + "auto": true + } + ] + }, + "SLES-53667": { + "title": "Gauntlet: Seven Sorrows", + "cheats": [ + { + "name": "DNAS Bypass", + "codes": [ + "203842E0 03E00008", + "203842E4 00000000" + ], + "auto": true + } + ] + }, + "PAPX-90523": { + "title": "Gran Turismo 4 Online (Beta)", + "cheats": [ + { + "name": "DNAS Bypass Code", + "codes": [ + "D188D734 24020006", + "0188D728 00000000", + "D188D734 24020006", + "0188D734 00000005" + ], + "auto": true + } + ] + }, + "SCES-51977": { + "title": "Hardware: Online Arena", + "cheats": [ + { + "name": "DNAS Bypass", + "codes": [ + "D1D424A8 00000007", + "01D424A8 00000005", + "D1D424A8 00000006", + "01D424A8 00000005" + ], + "auto": true + } + ] + }, + "SLES-53585": { + "title": "Marvel Nemesis: Rise of the Imperfects", + "cheats": [ + { + "name": "DNAS Bypass", + "codes": [ + "2020249C 00000000" + ], + "auto": true + } + ] + }, + "SLUS-21281": { + "title": "Marvel Nemesis: Rise of the Imperfects", + "cheats": [ + { + "name": "DNAS Bypass", + "codes": [ + "2020249C 00000000", + "20509B9C 00000000" + ], + "auto": true + } + ] + }, + "SCES-51677": { + "title": "MyStreet", + "cheats": [ + { + "name": "DNAS Patch", + "codes": [ + "D19A40B4 24020006", + "019A40A8 00000000", + "D19A40B4 24020006", + "019A40B4 00000005" + ], + "auto": true + } + ] + }, + "SCES-52456": { + "title": "Ratchet & Clank 3", + "cheats": [ + { + "name": "DNAS Patch", + "codes": [ + "D04D55E4 24020006", + "004D55D8 00000000", + "D04D55E4 24020006", + "004D55E4 00000005" + ], + "auto": true + }, + { + "name": "DNAS Patch", + "codes": [ + "D178CAF4 24020006", + "0178CAE8 00000000", + "D178CAF4 24020006", + "0178CAF4 00000005" + ], + "auto": false + } + ] + }, + "SCUS-97474": { + "title": "SOCOM 3: U.S. Navy SEALs", + "cheats": [ + { + "name": "DNAS Bypass (Base version of the game)", + "codes": [ + "202859A0 03E00008", + "202859A4 00000000" + ], + "auto": true + }, + { + "name": "DNAS Bypass (Patch 2.3)", + "codes": [ + "2028AE10 03E00008", + "2028AE14 00000000" + ], + "auto": false + } + ] + }, + "SCUS-97489": { + "title": "SOCOM 3: U.S. Navy SEALs", + "cheats": [ + { + "name": "DNAS Bypass", + "codes": [ + "202461E0 03E00008", + "202461E4 00000000" + ], + "auto": true + } + ] + }, + "SCUS-97545": { + "title": "SOCOM: U.S. Navy SEALs Combined Assault", + "cheats": [ + { + "name": "DNAS Bypass (Works for both 1.0 and 1.4 version of the game)", + "codes": [ + "2029A2DC 24020001", + "2029BD90 03E00008", + "2029BD94 00000000" + ], + "auto": true + } + ] + }, + "SCES-52306": { + "title": "SOCOM II: U.S. Navy SEALs", + "cheats": [ + { + "name": "DNAS Bypass", + "codes": [ + "202D61B0 03E00008", + "202D61B4 00000000" + ], + "auto": true + } + ] + }, + "SCKA-20020": { + "title": "SOCOM II: U.S. Navy SEALs", + "cheats": [ + { + "name": "DNAS Bypass", + "codes": [ + "202D7818 24020001" + ], + "auto": true + } + ] + }, + "SCPS-15065": { + "title": "SOCOM II: U.S. Navy SEALs", + "cheats": [ + { + "name": "DNAS Bypass", + "codes": [ + "202DD648 24020001" + ], + "auto": true + } + ] + }, + "SCUS-97275": { + "title": "SOCOM II: U.S. Navy SEALs", + "cheats": [ + { + "name": "DNAS Bypass r0001", + "codes": [ + "202CC670 03E00008", + "202CC674 00000000" + ], + "auto": true + }, + { + "name": "DNAS Bypass r0004", + "codes": [ + "203953C0 03E00008", + "203953C4 00000000" + ], + "auto": true + } + ] + }, + "SCUS-97366": { + "title": "SOCOM II: U.S. Navy SEALs", + "cheats": [ + { + "name": "DNAS Bypass(Harry62)", + "codes": [ + "203953C0 03E00008", + "203953C4 00000000" + ], + "auto": true + } + ] + }, + "TCES-51904": { + "title": "SOCOM II: U.S. Navy SEALs", + "cheats": [ + { + "name": "DNAS Bypass", + "codes": [ + "2040F1C0 03E00008", + "2040F1C4 00000000" + ], + "auto": true + } + ] + }, + "SCKA-24008": { + "title": "SOCOM: U.S. Navy SEALs", + "cheats": [ + { + "name": "DNAS Bypass", + "codes": [ + "20379C48 00000000" + ], + "auto": true + } + ] + }, + "SCKA-90010": { + "title": "SOCOM: U.S. Navy SEALs", + "cheats": [ + { + "name": "DNAS Bypass", + "codes": [ + "20379548 00000000" + ], + "auto": true + } + ] + }, + "SCPS-15044": { + "title": "SOCOM: U.S. Navy SEALs", + "cheats": [ + { + "name": "DNAS Bypass", + "codes": [ + "203814C8 00000000" + ], + "auto": true + } + ] + }, + "SCES-52033": { + "title": "Syphon Filter: The Omega Strain", + "cheats": [ + { + "name": "DNAS Bypass", + "codes": [ + "20382390 03E00008", + "20382394 00000000" + ], + "auto": true + } + ] + }, + "SCKA-20032": { + "title": "Syphon Filter: The Omega Strain", + "cheats": [ + { + "name": "DNAS Bypass", + "codes": [ + "20384D30 03E00008", + "20384D34 00000000" + ], + "auto": true + } + ] + }, + "SCUS-97264": { + "title": "Syphon Filter: The Omega Strain", + "cheats": [ + { + "name": "DNAS Bypass", + "codes": [ + "2044B7A0 03E00008", + "2044B7A4 00000000" + ], + "auto": true + }, + { + "name": "DNAS Bypass", + "codes": [ + "20343940 03E00008", + "20343944 00000000" + ], + "auto": false + } + ] + }, + "SCUS-97397": { + "title": "Syphon Filter: The Omega Strain", + "cheats": [ + { + "name": "DNAS Bypass", + "codes": [ + "203563C0 03E00008", + "203563C4 00000000" + ], + "auto": true + } + ] + }, + "TCES-52033": { + "title": "Syphon Filter: The Omega Strain", + "cheats": [ + { + "name": "DNAS Bypass", + "codes": [ + "2037B960 03E00008", + "2037B964 00000000" + ], + "auto": true + } + ] + }, + "SCES-52389": { + "title": "WRC 4", + "cheats": [ + { + "name": "DNAS Bypass", + "codes": [ + "D05B4C88 00000000", + "205B4C88 00000001" + ], + "crc": "CDE7C999", + "auto": true + }, + { + "name": "DNAS Bypass", + "codes": [ + "D05B4E88 00000000", + "205B4E88 00000001" + ], + "crc": "AFD06CBA", + "auto": true + } + ] + }, + "TCES-53247": { + "title": "WRC: Rally Evolved", + "cheats": [ + { + "name": "DNAS Patch", + "codes": [ + "D19606AC 24020006", + "019606A0 00000000", + "D19606AC 24020006", + "019606AC 00000005" + ], + "auto": true + } + ] + } +} \ No newline at end of file diff --git a/app/src/main/assets/experimental-drm.tzst b/app/src/main/assets/experimental-drm.tzst index 5bfb87168..b1f2f3b00 100644 Binary files a/app/src/main/assets/experimental-drm.tzst and b/app/src/main/assets/experimental-drm.tzst differ diff --git a/app/src/main/assets/extras.tzst b/app/src/main/assets/extras.tzst index 097329359..4e5f4ad42 100644 Binary files a/app/src/main/assets/extras.tzst and b/app/src/main/assets/extras.tzst differ diff --git a/app/src/main/assets/ffmpeg8.tzst b/app/src/main/assets/ffmpeg8.tzst new file mode 100644 index 000000000..06f4f9dec Binary files /dev/null and b/app/src/main/assets/ffmpeg8.tzst differ diff --git a/app/src/main/assets/gestures/profiles/gesture-1.gcp b/app/src/main/assets/gestures/profiles/gesture-1.gcp new file mode 100644 index 000000000..b6dd81bc3 --- /dev/null +++ b/app/src/main/assets/gestures/profiles/gesture-1.gcp @@ -0,0 +1 @@ +{"id":1,"name":"Default","config":{}} \ No newline at end of file diff --git a/app/src/main/assets/graphics_driver/extra_libs.tzst b/app/src/main/assets/graphics_driver/extra_libs.tzst index 3f43e1914..35e365a8a 100644 Binary files a/app/src/main/assets/graphics_driver/extra_libs.tzst and b/app/src/main/assets/graphics_driver/extra_libs.tzst differ diff --git a/app/src/main/assets/graphics_driver/wrapper-gamenative.tzst b/app/src/main/assets/graphics_driver/wrapper-gamenative.tzst new file mode 100644 index 000000000..1604fef9d Binary files /dev/null and b/app/src/main/assets/graphics_driver/wrapper-gamenative.tzst differ diff --git a/app/src/main/assets/graphics_driver/wrapper-leegao.tzst b/app/src/main/assets/graphics_driver/wrapper-leegao.tzst new file mode 100644 index 000000000..5147bea47 Binary files /dev/null and b/app/src/main/assets/graphics_driver/wrapper-leegao.tzst differ diff --git a/app/src/main/assets/graphics_driver/wrapper.tzst b/app/src/main/assets/graphics_driver/wrapper.tzst index 6a5bd8254..528bc9b1f 100644 Binary files a/app/src/main/assets/graphics_driver/wrapper.tzst and b/app/src/main/assets/graphics_driver/wrapper.tzst differ diff --git a/app/src/main/assets/imagefs.txz b/app/src/main/assets/imagefs.txz deleted file mode 100644 index df8c59a3e..000000000 --- a/app/src/main/assets/imagefs.txz +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f96d362b7e148e86ab0d2c290978bf39b38e5c7ffc8ae4adf1d2a65c62bbb780 -size 183231056 diff --git a/app/src/main/assets/imagefs.tzst b/app/src/main/assets/imagefs.tzst new file mode 100644 index 000000000..34ce6700f --- /dev/null +++ b/app/src/main/assets/imagefs.tzst @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0902e324b60a5c234aa29fcf457f6475a38ef8f61ac2be2118daaef4f236499a +size 199788876 diff --git a/app/src/main/assets/inputcontrols/profiles/controls-4.icp b/app/src/main/assets/inputcontrols/profiles/controls-4.icp deleted file mode 100644 index 1aa0a0fca..000000000 --- a/app/src/main/assets/inputcontrols/profiles/controls-4.icp +++ /dev/null @@ -1 +0,0 @@ -{"id":4,"name":"Playstation Controller","cursorSpeed":1,"elements":[{"type":"BUTTON","shape":"ROUND_RECT","customColor":-12435134,"bindings":["GAMEPAD_BUTTON_L2","NONE","NONE","NONE"],"scale":1.25,"x":0.12871287763118744,"y":0.10775047540664673,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"ROUND_RECT","customColor":-12435134,"bindings":["GAMEPAD_BUTTON_R2","NONE","NONE","NONE"],"scale":1.25,"x":0.8704261779785156,"y":0.10775047540664673,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"ROUND_RECT","customColor":-8355712,"bindings":["GAMEPAD_BUTTON_L1","NONE","NONE","NONE"],"scale":1.1,"x":0.12871287763118744,"y":0.239130437374115,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"ROUND_RECT","customColor":-8355712,"bindings":["GAMEPAD_BUTTON_R1","NONE","NONE","NONE"],"scale":1.1,"x":0.8704261779785156,"y":0.239130437374115,"toggleSwitch":false,"text":"","iconId":0},{"type":"D_PAD","shape":"CIRCLE","customColor":-13948632,"bindings":["GAMEPAD_DPAD_UP","GAMEPAD_DPAD_RIGHT","GAMEPAD_DPAD_DOWN","GAMEPAD_DPAD_LEFT"],"scale":0.92,"x":0.2970297038555145,"y":0.7816635370254517,"toggleSwitch":false,"text":"","iconId":0},{"type":"STICK","shape":"CIRCLE","customColor":-1,"bindings":["GAMEPAD_LEFT_THUMB_UP","GAMEPAD_LEFT_THUMB_RIGHT","GAMEPAD_LEFT_THUMB_DOWN","GAMEPAD_LEFT_THUMB_LEFT"],"scale":1.08,"x":0.12742143869400024,"y":0.6153119206428528,"toggleSwitch":false,"text":"","iconId":0},{"type":"STICK","shape":"CIRCLE","customColor":-1,"bindings":["GAMEPAD_RIGHT_THUMB_UP","GAMEPAD_RIGHT_THUMB_RIGHT","GAMEPAD_RIGHT_THUMB_DOWN","GAMEPAD_RIGHT_THUMB_LEFT"],"scale":1.08,"x":0.8721480965614319,"y":0.6153119206428528,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","customColor":-65281,"bindings":["GAMEPAD_BUTTON_X","NONE","NONE","NONE"],"scale":1,"x":0.6534653306007385,"y":0.6739130616188049,"toggleSwitch":false,"text":"□","iconId":0},{"type":"BUTTON","shape":"CIRCLE","customColor":-16711936,"bindings":["GAMEPAD_BUTTON_Y","NONE","NONE","NONE"],"scale":1,"x":0.7029703259468079,"y":0.5652173757553101,"toggleSwitch":false,"text":"△","iconId":0},{"type":"BUTTON","shape":"CIRCLE","customColor":-16776961,"bindings":["GAMEPAD_BUTTON_A","NONE","NONE","NONE"],"scale":1,"x":0.7029703259468079,"y":0.7816635370254517,"toggleSwitch":false,"text":"×","iconId":0},{"type":"BUTTON","shape":"CIRCLE","customColor":-65536,"bindings":["GAMEPAD_BUTTON_B","NONE","NONE","NONE"],"scale":1,"x":0.7524752616882324,"y":0.6739130616188049,"toggleSwitch":false,"text":"o","iconId":0},{"type":"BUTTON","shape":"ROUND_RECT","customColor":-1,"bindings":["GAMEPAD_BUTTON_SELECT","NONE","NONE","NONE"],"scale":0.9,"x":0.44468361139297485,"y":0.95652174949646,"toggleSwitch":false,"text":"","iconId":16},{"type":"BUTTON","shape":"ROUND_RECT","customColor":-1,"bindings":["GAMEPAD_BUTTON_START","NONE","NONE","NONE"],"scale":0.9,"x":0.5445544719696045,"y":0.95652174949646,"toggleSwitch":false,"text":"","iconId":15},{"type":"BUTTON","shape":"CIRCLE","customColor":-8355712,"bindings":["GAMEPAD_BUTTON_L3","NONE","NONE","NONE"],"scale":0.78,"x":0.16745588183403015,"y":0.8468809127807617,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","customColor":-8355712,"bindings":["GAMEPAD_BUTTON_R3","NONE","NONE","NONE"],"scale":0.78,"x":0.7920792102813721,"y":0.8468809127807617,"toggleSwitch":false,"text":"","iconId":0}]} \ No newline at end of file diff --git a/app/src/main/assets/inputcontrols/profiles/controls-5.icp b/app/src/main/assets/inputcontrols/profiles/controls-5.icp deleted file mode 100644 index 9c3a3b095..000000000 --- a/app/src/main/assets/inputcontrols/profiles/controls-5.icp +++ /dev/null @@ -1 +0,0 @@ -{"id":5,"name":"Xbox Controller","cursorSpeed":1,"elements":[{"type":"BUTTON","shape":"ROUND_RECT","customColor":-12435134,"bindings":["GAMEPAD_BUTTON_L2","NONE","NONE","NONE"],"scale":1.25,"x":0.12871287763118744,"y":0.10869564861059189,"toggleSwitch":false,"text":"LT","iconId":0},{"type":"BUTTON","shape":"ROUND_RECT","customColor":-12435134,"bindings":["GAMEPAD_BUTTON_R2","NONE","NONE","NONE"],"scale":1.25,"x":0.8811880946159363,"y":0.10869564861059189,"toggleSwitch":false,"text":"RT","iconId":0},{"type":"BUTTON","shape":"ROUND_RECT","customColor":-8355712,"bindings":["GAMEPAD_BUTTON_L1","NONE","NONE","NONE"],"scale":1.05,"x":0.12871287763118744,"y":0.239130437374115,"toggleSwitch":false,"text":"LB","iconId":0},{"type":"BUTTON","shape":"ROUND_RECT","customColor":-8355712,"bindings":["GAMEPAD_BUTTON_R1","NONE","NONE","NONE"],"scale":1.15,"x":0.8811880946159363,"y":0.239130437374115,"toggleSwitch":false,"text":"RB","iconId":0},{"type":"D_PAD","shape":"CIRCLE","customColor":-13948632,"bindings":["GAMEPAD_DPAD_UP","GAMEPAD_DPAD_RIGHT","GAMEPAD_DPAD_DOWN","GAMEPAD_DPAD_LEFT"],"scale":0.92,"x":0.3164012134075165,"y":0.7816635370254517,"toggleSwitch":false,"text":"","iconId":0},{"type":"STICK","shape":"CIRCLE","customColor":-1,"bindings":["GAMEPAD_LEFT_THUMB_UP","GAMEPAD_LEFT_THUMB_RIGHT","GAMEPAD_LEFT_THUMB_DOWN","GAMEPAD_LEFT_THUMB_LEFT"],"scale":1.08,"x":0.12742143869400024,"y":0.6153119206428528,"toggleSwitch":false,"text":"","iconId":0},{"type":"STICK","shape":"CIRCLE","customColor":-1,"bindings":["GAMEPAD_RIGHT_THUMB_UP","GAMEPAD_RIGHT_THUMB_RIGHT","GAMEPAD_RIGHT_THUMB_DOWN","GAMEPAD_RIGHT_THUMB_LEFT"],"scale":1.08,"x":0.8721480965614319,"y":0.6153119206428528,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","customColor":-16776961,"bindings":["GAMEPAD_BUTTON_X","NONE","NONE","NONE"],"scale":1,"x":0.6530348658561707,"y":0.6739130616188049,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","customColor":-256,"bindings":["GAMEPAD_BUTTON_Y","NONE","NONE","NONE"],"scale":1,"x":0.7029703259468079,"y":0.5642722249031067,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","customColor":-16711936,"bindings":["GAMEPAD_BUTTON_A","NONE","NONE","NONE"],"scale":1,"x":0.7029703259468079,"y":0.7816635370254517,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","customColor":-65536,"bindings":["GAMEPAD_BUTTON_B","NONE","NONE","NONE"],"scale":1,"x":0.7524752616882324,"y":0.6739130616188049,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"ROUND_RECT","customColor":-1,"bindings":["GAMEPAD_BUTTON_SELECT","NONE","NONE","NONE"],"scale":0.9,"x":0.4451140761375427,"y":0.95652174949646,"toggleSwitch":false,"text":"","iconId":16},{"type":"BUTTON","shape":"ROUND_RECT","customColor":-1,"bindings":["GAMEPAD_BUTTON_START","NONE","NONE","NONE"],"scale":0.9,"x":0.5445544719696045,"y":0.95652174949646,"toggleSwitch":false,"text":"","iconId":15},{"type":"BUTTON","shape":"CIRCLE","customColor":-8355712,"bindings":["GAMEPAD_BUTTON_L3","NONE","NONE","NONE"],"scale":0.78,"x":0.16788634657859802,"y":0.8468809127807617,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","customColor":-8355712,"bindings":["GAMEPAD_BUTTON_R3","NONE","NONE","NONE"],"scale":0.78,"x":0.8015497326850891,"y":0.8468809127807617,"toggleSwitch":false,"text":"","iconId":0}]} \ No newline at end of file diff --git a/app/src/main/assets/inputcontrols/profiles/controls-6.icp b/app/src/main/assets/inputcontrols/profiles/controls-6.icp index 1da124f3f..ccadad6e1 100644 --- a/app/src/main/assets/inputcontrols/profiles/controls-6.icp +++ b/app/src/main/assets/inputcontrols/profiles/controls-6.icp @@ -1 +1 @@ -{"id":6,"name":"FPS","cursorSpeed":1.0000001,"elements":[{"type":"D_PAD","shape":"CIRCLE","bindings":["KEY_W","KEY_D","KEY_S","KEY_A"],"scale":1,"x":0.10000000149011612,"y":0.7333333492279053,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","bindings":["MOUSE_LEFT_BUTTON","NONE","NONE","NONE"],"scale":1,"x":0.8195833563804626,"y":0.7333333492279053,"toggleSwitch":false,"text":"","iconId":0,"mouseMoveMode":true},{"type":"BUTTON","shape":"CIRCLE","bindings":["KEY_R","NONE","NONE","NONE"],"scale":1,"x":0.8795833587646484,"y":0.6000000238418579,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","bindings":["KEY_SPACE","NONE","NONE","NONE"],"scale":1,"x":0.8795833587646484,"y":0.8666666746139526,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","bindings":["KEY_E","NONE","NONE","NONE"],"scale":1,"x":0.9395833611488342,"y":0.7333333492279053,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","bindings":["MOUSE_RIGHT_BUTTON","NONE","NONE","NONE"],"scale":1,"x":0.1899999976158142,"y":0.5333333611488342,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"ROUND_RECT","bindings":["KEY_ENTER","NONE","NONE","NONE"],"scale":0.85,"x":0.5400000214576721,"y":0.9111111164093018,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"ROUND_RECT","bindings":["KEY_ESC","NONE","NONE","NONE"],"scale":0.85,"x":0.46000000834465027,"y":0.9111111164093018,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","bindings":["KEY_SHIFT_L","NONE","NONE","NONE"],"scale":0.85,"x":0.75,"y":0.8888888955116272,"toggleSwitch":true,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","bindings":["KEY_CTRL_L","NONE","NONE","NONE"],"scale":0.85,"x":0.25,"y":0.8888888955116272,"toggleSwitch":true,"text":"","iconId":0},{"type":"RADIAL_MENU","shape":"CIRCLE","bindings":["KEY_F","KEY_G","KEY_C","KEY_T","KEY_V"],"scale":1,"x":0.9100000262260437,"y":0.3333333432674408,"toggleSwitch":false,"text":"","iconId":0},{"type":"RANGE_BUTTON","shape":"CIRCLE","bindings":["NONE","NONE","NONE","NONE"],"scale":1,"x":0.10958333313465118,"y":0.08888889104127884,"toggleSwitch":false,"text":"","iconId":0,"range":"FROM_0_TO_9"},{"type":"RANGE_BUTTON","shape":"CIRCLE","bindings":["NONE","NONE","NONE","NONE"],"scale":1,"x":0.8895833492279053,"y":0.08888889104127884,"toggleSwitch":false,"text":"","iconId":0,"range":"FROM_F1_TO_F12"},{"type":"RADIAL_MENU","shape":"CIRCLE","bindings":["KEY_I","KEY_Z","KEY_X","KEY_M","KEY_Q"],"scale":1,"x":0.09000000357627869,"y":0.3333333432674408,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","bindings":["KEY_TAB","NONE","NONE","NONE"],"scale":0.85,"x":0.3199999928474426,"y":0.8888888955116272,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","bindings":["KEY_ALT_L","NONE","NONE","NONE"],"scale":0.85,"x":0.6800000071525574,"y":0.8888888955116272,"toggleSwitch":true,"text":"","iconId":0}]} \ No newline at end of file +{"id":6,"name":"FPS","cursorSpeed":1.0000001,"elements":[{"type":"BUTTON","shape":"CIRCLE","customColor":-1,"bindings":["MOUSE_LEFT_BUTTON","NONE","NONE","NONE"],"scale":1,"x":0.8191993236541748,"y":0.7325142025947571,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","customColor":-1,"bindings":["KEY_R","NONE","NONE","NONE"],"scale":1,"x":0.8794662356376648,"y":0.5992438793182373,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","customColor":-1,"bindings":["KEY_SPACE","NONE","NONE","NONE"],"scale":1,"x":0.8794662356376648,"y":0.8657845258712769,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","customColor":-1,"bindings":["KEY_E","NONE","NONE","NONE"],"scale":1,"x":0.9388721585273743,"y":0.7325142025947571,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","customColor":-1,"bindings":["MOUSE_RIGHT_BUTTON","NONE","NONE","NONE"],"scale":1,"x":0.23762376606464386,"y":0.5207939743995667,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"ROUND_RECT","customColor":-1,"bindings":["KEY_ENTER","NONE","NONE","NONE"],"scale":0.85,"x":0.5393887162208557,"y":0.9092627763748169,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"ROUND_RECT","customColor":-1,"bindings":["KEY_ESC","NONE","NONE","NONE"],"scale":0.85,"x":0.45975032448768616,"y":0.9092627763748169,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","customColor":-1,"bindings":["KEY_SHIFT_L","NONE","NONE","NONE"],"scale":0.85,"x":0.7494618892669678,"y":0.8875236511230469,"toggleSwitch":true,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","customColor":-1,"bindings":["KEY_CTRL_L","NONE","NONE","NONE"],"scale":0.85,"x":0.24924665689468384,"y":0.8875236511230469,"toggleSwitch":true,"text":"","iconId":0},{"type":"RADIAL_MENU","shape":"CIRCLE","customColor":-1,"bindings":["KEY_F","KEY_G","KEY_C","KEY_T","KEY_V"],"scale":1,"x":0.9095996618270874,"y":0.3317580223083496,"toggleSwitch":false,"text":"","iconId":0},{"type":"RANGE_BUTTON","shape":"CIRCLE","customColor":-1,"bindings":["NONE","NONE","NONE","NONE"],"scale":1,"x":0.10891088843345642,"y":0.08790170401334763,"toggleSwitch":false,"text":"","iconId":0,"range":"FROM_0_TO_9"},{"type":"RANGE_BUTTON","shape":"CIRCLE","customColor":-1,"bindings":["NONE","NONE","NONE","NONE"],"scale":1,"x":0.8893672227859497,"y":0.08790170401334763,"toggleSwitch":false,"text":"","iconId":0,"range":"FROM_F1_TO_F12"},{"type":"RADIAL_MENU","shape":"CIRCLE","customColor":-1,"bindings":["KEY_I","KEY_Z","KEY_X","KEY_M","KEY_Q"],"scale":1,"x":0.08953938633203506,"y":0.3317580223083496,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","customColor":-1,"bindings":["KEY_TAB","NONE","NONE","NONE"],"scale":0.85,"x":0.31941455602645874,"y":0.8875236511230469,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","customColor":-1,"bindings":["KEY_ALT_L","NONE","NONE","NONE"],"scale":0.85,"x":0.6797245144844055,"y":0.8875236511230469,"toggleSwitch":true,"text":"","iconId":0},{"type":"STICK","shape":"CIRCLE","customColor":-1,"bindings":["KEY_W","KEY_D","KEY_S","KEY_A"],"scale":1,"x":0.14851485192775726,"y":0.695652186870575,"toggleSwitch":false,"text":"","iconId":0},{"type":"D_PAD","shape":"CIRCLE","customColor":-1,"bindings":["KEY_UP","KEY_RIGHT","KEY_DOWN","KEY_LEFT"],"scale":0.6,"x":0.2871287167072296,"y":0.15217390656471252,"toggleSwitch":false,"text":"","iconId":0}]} diff --git a/app/src/main/assets/inputcontrols/profiles/controls-7.icp b/app/src/main/assets/inputcontrols/profiles/controls-7.icp new file mode 100644 index 000000000..f7e7c1251 --- /dev/null +++ b/app/src/main/assets/inputcontrols/profiles/controls-7.icp @@ -0,0 +1 @@ +{"id":7,"name":"GameHub","cursorSpeed":1,"elements":[{"type":"BUTTON","shape":"ROUND_RECT","bindings":["GAMEPAD_BUTTON_L2","NONE","NONE","NONE"],"scale":1.3,"x":0.087,"y":0.115,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"ROUND_RECT","bindings":["GAMEPAD_BUTTON_R2","NONE","NONE","NONE"],"scale":1.3,"x":0.913,"y":0.115,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"ROUND_RECT","bindings":["GAMEPAD_BUTTON_L1","NONE","NONE","NONE"],"scale":1.3,"x":0.087,"y":0.28,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"ROUND_RECT","bindings":["GAMEPAD_BUTTON_R1","NONE","NONE","NONE"],"scale":1.3,"x":0.913,"y":0.28,"toggleSwitch":false,"text":"","iconId":0},{"type":"D_PAD","shape":"CIRCLE","bindings":["GAMEPAD_DPAD_UP","GAMEPAD_DPAD_RIGHT","GAMEPAD_DPAD_DOWN","GAMEPAD_DPAD_LEFT"],"scale":1.07,"x":0.335,"y":0.7,"toggleSwitch":false,"text":"","iconId":0},{"type":"STICK","shape":"CIRCLE","bindings":["GAMEPAD_LEFT_THUMB_UP","GAMEPAD_LEFT_THUMB_RIGHT","GAMEPAD_LEFT_THUMB_DOWN","GAMEPAD_LEFT_THUMB_LEFT"],"scale":1.17,"x":0.16,"y":0.7,"toggleSwitch":false,"text":"","iconId":0},{"type":"STICK","shape":"CIRCLE","bindings":["GAMEPAD_RIGHT_THUMB_UP","GAMEPAD_RIGHT_THUMB_RIGHT","GAMEPAD_RIGHT_THUMB_DOWN","GAMEPAD_RIGHT_THUMB_LEFT"],"scale":1.17,"x":0.72,"y":0.7,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","bindings":["GAMEPAD_BUTTON_X","NONE","NONE","NONE"],"scale":1.0,"x":0.835,"y":0.72,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","bindings":["GAMEPAD_BUTTON_Y","NONE","NONE","NONE"],"scale":1.0,"x":0.89,"y":0.6,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","bindings":["GAMEPAD_BUTTON_A","NONE","NONE","NONE"],"scale":1.0,"x":0.89,"y":0.84,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","bindings":["GAMEPAD_BUTTON_B","NONE","NONE","NONE"],"scale":1.0,"x":0.945,"y":0.72,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"ROUND_RECT","bindings":["GAMEPAD_BUTTON_SELECT","NONE","NONE","NONE"],"scale":0.75,"x":0.46,"y":0.94,"toggleSwitch":false,"text":"","iconId":16},{"type":"BUTTON","shape":"ROUND_RECT","bindings":["GAMEPAD_BUTTON_START","NONE","NONE","NONE"],"scale":0.75,"x":0.54,"y":0.94,"toggleSwitch":false,"text":"","iconId":15},{"type":"BUTTON","shape":"CIRCLE","bindings":["GAMEPAD_BUTTON_L3","NONE","NONE","NONE"],"scale":0.7,"x":0.055,"y":0.42,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","bindings":["GAMEPAD_BUTTON_R3","NONE","NONE","NONE"],"scale":0.7,"x":0.945,"y":0.42,"toggleSwitch":false,"text":"","iconId":0}]} \ No newline at end of file diff --git a/app/src/main/assets/inputcontrols/profiles/controls-8.icp b/app/src/main/assets/inputcontrols/profiles/controls-8.icp new file mode 100644 index 000000000..5ff39be3d --- /dev/null +++ b/app/src/main/assets/inputcontrols/profiles/controls-8.icp @@ -0,0 +1 @@ +{"id":8,"name":"No R-Stick","cursorSpeed":1,"elements":[{"type":"BUTTON","shape":"ROUND_RECT","customColor":-1,"bindings":["GAMEPAD_BUTTON_L2","NONE","NONE","NONE"],"scale":1.25,"x":0.12745098769664764,"y":0.10638298094272614,"toggleSwitch":false,"text":"LT","iconId":0},{"type":"BUTTON","shape":"ROUND_RECT","customColor":-1,"bindings":["GAMEPAD_BUTTON_R2","NONE","NONE","NONE"],"scale":1.25,"x":0.8823529481887817,"y":0.10638298094272614,"toggleSwitch":false,"text":"RT","iconId":0},{"type":"BUTTON","shape":"ROUND_RECT","customColor":-1,"bindings":["GAMEPAD_BUTTON_L1","NONE","NONE","NONE"],"scale":1.05,"x":0.12745098769664764,"y":0.23404255509376526,"toggleSwitch":false,"text":"LB","iconId":0},{"type":"BUTTON","shape":"ROUND_RECT","customColor":-1,"bindings":["GAMEPAD_BUTTON_R1","NONE","NONE","NONE"],"scale":1.15,"x":0.8823529481887817,"y":0.23404255509376526,"toggleSwitch":false,"text":"RB","iconId":0},{"type":"D_PAD","shape":"CIRCLE","customColor":-1,"bindings":["GAMEPAD_DPAD_UP","GAMEPAD_DPAD_RIGHT","GAMEPAD_DPAD_DOWN","GAMEPAD_DPAD_LEFT"],"scale":0.92,"x":0.3235294222831726,"y":0.7872340679168701,"toggleSwitch":false,"text":"","iconId":0},{"type":"STICK","shape":"CIRCLE","customColor":-1,"bindings":["GAMEPAD_LEFT_THUMB_UP","GAMEPAD_LEFT_THUMB_RIGHT","GAMEPAD_LEFT_THUMB_DOWN","GAMEPAD_LEFT_THUMB_LEFT"],"scale":1.08,"x":0.10856935381889343,"y":0.6296296119689941,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","customColor":-1,"bindings":["GAMEPAD_BUTTON_X","NONE","NONE","NONE"],"scale":1,"x":0.7549019455909729,"y":0.7659574747085571,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","customColor":-1,"bindings":["GAMEPAD_BUTTON_Y","NONE","NONE","NONE"],"scale":1,"x":0.813725471496582,"y":0.6382978558540344,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","customColor":-1,"bindings":["GAMEPAD_BUTTON_A","NONE","NONE","NONE"],"scale":1,"x":0.813725471496582,"y":0.8936170339584351,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","customColor":-1,"bindings":["GAMEPAD_BUTTON_B","NONE","NONE","NONE"],"scale":1,"x":0.8725489974021912,"y":0.7659574747085571,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"ROUND_RECT","customColor":-1,"bindings":["GAMEPAD_BUTTON_SELECT","NONE","NONE","NONE"],"scale":0.9,"x":0.4444444477558136,"y":0.9550827145576477,"toggleSwitch":false,"text":"","iconId":16},{"type":"BUTTON","shape":"ROUND_RECT","customColor":-1,"bindings":["GAMEPAD_BUTTON_START","NONE","NONE","NONE"],"scale":0.9,"x":0.5439360737800598,"y":0.9550827145576477,"toggleSwitch":false,"text":"","iconId":15},{"type":"BUTTON","shape":"CIRCLE","customColor":-1,"bindings":["GAMEPAD_BUTTON_L3","NONE","NONE","NONE"],"scale":0.78,"x":0.19607843458652496,"y":0.8510638475418091,"toggleSwitch":false,"text":"","iconId":0},{"type":"BUTTON","shape":"CIRCLE","customColor":-1,"bindings":["GAMEPAD_BUTTON_R3","NONE","NONE","NONE"],"scale":0.78,"x":0.9215686321258545,"y":0.531126856803894,"toggleSwitch":false,"text":"","iconId":0}]} \ No newline at end of file diff --git a/app/src/main/assets/metadata/box64_env_vars.json b/app/src/main/assets/metadata/box64_env_vars.json index 396966352..5c2b47826 100644 --- a/app/src/main/assets/metadata/box64_env_vars.json +++ b/app/src/main/assets/metadata/box64_env_vars.json @@ -18,6 +18,7 @@ {"name" : "BOX64_DYNAREC_PAUSE", "values" : ["0", "1", "2", "3"], "toggleSwitch" : false, "defaultValue" : "0"}, {"name" : "BOX64_DYNAREC_NOARCH", "values" : ["0", "1", "2"], "defaultValue" : "0"}, {"name" : "BOX64_DYNAREC_VOLATILE_METADATA", "values" : ["0", "1"], "toggleSwitch" : true, "defaultValue" : "1"}, + {"name" : "BOX64_DYNACACHE", "values" : ["0", "1", "2"], "defaultValue" : "0"}, {"name" : "BOX64_AVX", "values" : ["0", "1", "2"], "defaultValue" : "0"}, {"name" : "BOX64_AES", "values" : ["0", "1"], "toggleSwitch" : true, "defaultValue" : "1"}, {"name" : "BOX64_PCLMULQDQ", "values" : ["0", "1"], "toggleSwitch" : true, "defaultValue" : "1"}, diff --git a/app/src/main/assets/metadata/debug_channels.json b/app/src/main/assets/metadata/debug_channels.json index 91f46fec5..7227e3579 100644 --- a/app/src/main/assets/metadata/debug_channels.json +++ b/app/src/main/assets/metadata/debug_channels.json @@ -1 +1,521 @@ -["acledit","aclui","actctx","activeds","actxprxy","adpcm","adsldp","advapi","advpack","alsa","amsi","animate","appbar","apphelp","appwizcpl","appx","asmshader","aspi","atl","atlthunk","atmlib","atom","authz","avicap","avifile","avrt","bcrypt","bidi","bitblt","bitmap","bluetooth","bluetoothapis","browseui","button","bytecodewriter","cabinet","capi","capture","cards","cdosys","class","clipboard","clipping","clusapi","combase","combo","comboex","comm","commctrl","commdlg","compstui","comsvcs","concrt","connect","console","context","coreaudio","cred","credentials","credui","crypt","cryptasn","cryptdlg","cryptext","cryptnet","crypto","cryptui","ctapi32","cursor","d2d","d3d","d3d10","d3d10core","d3d11","d3d12","d3d8","d3d9","d3d_decl","d3d_shader","d3dcompiler","d3drm","d3dx","d3dxof","d3dxof_parsing","data","datetime","davclnt","dbgeng","dbghelp","dbghelp_coff","dbghelp_dwarf","dbghelp_macho","dbghelp_msc","dbghelp_stabs","dc","dciman","dcomp","ddeml","ddraw","ddrawex","debug_buffer","debugstr","devenum","dhcpcsvc","dhtmled","dialog","diasymreader","difxapi","dinput","display","dll","dmband","dmcompos","dmfile","dmime","dmloader","dmobj","dmscript","dmstyle","dmsynth","dmusic","dmusic32","dnsapi","dosmem","dpa","dplay","dpnet","dpnhpast","dpnhupnp","dpvoice","dragdrop","driver","dsa","dsdmo","dsound","dsound3d","dsquery","dssenh","dsuiext","dswave","dwmapi","dwrite","dx8vb","dxcore","dxdiag","dxgi","dxtrans","dxva2","edit","enhmetafile","enumeration","environ","err","event","eventlog","evr","exception","exec","explorerframe","faultrep","file","fixme","fixup","fltlib","fltmgr","font","fontcache","fontsub","fusion","fwpuclnt","g711","gamebar","gameux","gamingtcui","gdi","gdiplus","geolocator","gl_compat","global","globalmem","glu","graphics","gsm","handle","header","heap","hid","hlink","hnetcfg","hook","hostname","hotkey","htmlhelp","http","hvsi","iccvid","icm","icon","ieframe","image","imagehlp","imagelist","imm","inetcomm","inetcpl","inetmib1","infosoft","inkobj","input","inseng","int","int21","int31","ipaddress","iphlpapi","ir50_32","itss","joycpl","jscript","jsproxy","kerberos","kernelbase","keyboard","ksecdd","lanman","listbox","listview","loaddll","loadperf","local","locale","localspl","localui","macdrv","manipulation","mapi","mci","mciavi","mcicda","mcimidi","mciqtz","mciwave","mdi","media","mediacontrol","menu","menubuilder","message","metafile","mfplat","mgmtapi","midi","mlang","mmaux","mmc","mmdevapi","mmio","mmsys","mmtime","model","module","monthcal","mountmgr","mp3dmod","mpeg3","mpr","mprapi","msacm","msado15","msasn1","msauddecmft","mscms","msctf","msctfmonitor","msdasql","msdmo","msdrm","msftedit","msg","mshtml","msi","msidb","msident","msimg32","msimtf","msisip","msisys","msmpeg2vdec","msnet","msopc","mspatcha","msrle32","msscript","mssign","mstask","msttsengine","msvcirt","msvcm","msvcp","msvcrt","msvidc32","msvideo","mswsock","msxml","nativefont","ncrypt","nddeapi","ndis","netapi32","netbios","netcfgx","netio","netprofm","ninput","nls","nonclient","nsi","nstc","ntdll","ntdsapi","ntlm","ntoskrnl","ntprint","objsel","odbc","ole","oleacc","oledb","oledlg","olemalloc","olepicture","opencl","opengl","oss","packager","pager","palette","path","pdh","perception","pidgen","pidl","plugplay","powermgnt","powrprof","print","printui","prntvpt","process","profile","progress","propsheet","propsys","psdrv","pstores","pulse","qmgr","quartz","query","qwave","ras","rasdlg","rawinput","rebar","recyclebin","reg","region","relay","resource","richedit","richedit_lists","rpc","rstrtmgr","rtutils","sapi","schannel","schedsvc","scrobj","scroll","scrrun","scsiport","secur32","security","seh","selector","sensapi","service","setupapi","sfc","shcore","shdocvw","shell","shlctrl","slc","smbios","snmpapi","snoop","sound","speech","spoolss","sspicli","static","statusbar","sti","storage","stress","string","sxs","sync","syslevel","syslink","system","systray","t2embed","tab","tape","tapi","task","taskdialog","taskschd","tbs","tdh","tdi","text","theme_scroll","thread","threadpool","thunk","toolbar","toolhelp","tooltips","trackbar","traffic","treeview","twain","twinapi","ui","uianimation","uiautomation","uiribbon","unloaddll","unwind","updown","updspapi","url","urlmon","usb","usbd","user","uxtheme","variant","vbscript","vcomp","vcruntime","vdmdbg","ver","virtdisk","volume","vulkan","vxd","warn","wavemap","waylanddrv","wbemdisp","wbemprox","webservices","wer","wevtapi","wgl","wia","wimgapi","win","winebrowser","wincodecs","winemapi","wineusb","wing","winhttp","wininet","winmm","winprint","winscard","winsock","winspool","winsta","winstation","winstring","wintab","wintab32","wintrust","wintypes","winusb","wlanapi","wldap32","wldp","wmadec","wmi","wmiutils","wmp","wmvcore","wnet","wofutil","wow","wpc","wpcap","wsdapi","wshom","wsnmp32","wtsapi","wuapi","x11drv","xaudio2","xdnd","xim","xinput","xmllite","xolehlp","xrandr","xrender","xvidmode","dmo"] +[ + "acledit", + "aclui", + "actctx", + "activeds", + "actxprxy", + "adpcm", + "adsldp", + "advapi", + "advpack", + "alsa", + "amsi", + "animate", + "appbar", + "apphelp", + "appwizcpl", + "appx", + "asmshader", + "aspi", + "atl", + "atlthunk", + "atmlib", + "atom", + "authz", + "avicap", + "avifile", + "avrt", + "bcrypt", + "bidi", + "bitblt", + "bitmap", + "bluetooth", + "bluetoothapis", + "browseui", + "button", + "bytecodewriter", + "cabinet", + "capi", + "capture", + "cards", + "cdosys", + "class", + "clipboard", + "clipping", + "clusapi", + "combase", + "combo", + "comboex", + "comm", + "commctrl", + "commdlg", + "compstui", + "comsvcs", + "concrt", + "connect", + "console", + "context", + "coreaudio", + "cred", + "credentials", + "credui", + "crypt", + "cryptasn", + "cryptdlg", + "cryptext", + "cryptnet", + "crypto", + "cryptui", + "ctapi32", + "cursor", + "d2d", + "d3d", + "d3d10", + "d3d10core", + "d3d11", + "d3d12", + "d3d8", + "d3d9", + "d3d_decl", + "d3d_shader", + "d3dcompiler", + "d3drm", + "d3dx", + "d3dxof", + "d3dxof_parsing", + "data", + "datetime", + "davclnt", + "dbgeng", + "dbghelp", + "dbghelp_coff", + "dbghelp_dwarf", + "dbghelp_macho", + "dbghelp_msc", + "dbghelp_stabs", + "dc", + "dciman", + "dcomp", + "ddeml", + "ddraw", + "ddrawex", + "debug_buffer", + "debugstr", + "devenum", + "dhcpcsvc", + "dhtmled", + "dialog", + "diasymreader", + "difxapi", + "dinput", + "display", + "dll", + "dmband", + "dmcompos", + "dmfile", + "dmime", + "dmloader", + "dmobj", + "dmscript", + "dmstyle", + "dmsynth", + "dmusic", + "dmusic32", + "dnsapi", + "dosmem", + "dpa", + "dplay", + "dpnet", + "dpnhpast", + "dpnhupnp", + "dpvoice", + "dragdrop", + "driver", + "dsa", + "dsdmo", + "dsound", + "dsound3d", + "dsquery", + "dssenh", + "dsuiext", + "dswave", + "dwmapi", + "dwrite", + "dx8vb", + "dxcore", + "dxdiag", + "dxgi", + "dxtrans", + "dxva2", + "edit", + "enhmetafile", + "enumeration", + "environ", + "event", + "eventlog", + "evr", + "exception", + "exec", + "explorerframe", + "faultrep", + "file", + "fixup", + "fltlib", + "fltmgr", + "font", + "fontcache", + "fontsub", + "fusion", + "fwpuclnt", + "g711", + "gamebar", + "gameux", + "gamingtcui", + "gdi", + "gdiplus", + "geolocator", + "gl_compat", + "global", + "globalmem", + "glu", + "graphics", + "gsm", + "handle", + "header", + "heap", + "hid", + "hlink", + "hnetcfg", + "hook", + "hostname", + "hotkey", + "htmlhelp", + "http", + "hvsi", + "iccvid", + "icm", + "icon", + "ieframe", + "image", + "imagehlp", + "imagelist", + "imm", + "inetcomm", + "inetcpl", + "inetmib1", + "infosoft", + "inkobj", + "input", + "inseng", + "int", + "int21", + "int31", + "ipaddress", + "iphlpapi", + "ir50_32", + "itss", + "joycpl", + "jscript", + "jsproxy", + "kerberos", + "kernelbase", + "keyboard", + "ksecdd", + "lanman", + "listbox", + "listview", + "loaddll", + "loadperf", + "local", + "locale", + "localspl", + "localui", + "macdrv", + "manipulation", + "mapi", + "mci", + "mciavi", + "mcicda", + "mcimidi", + "mciqtz", + "mciwave", + "mdi", + "media", + "mediacontrol", + "menu", + "menubuilder", + "message", + "metafile", + "mfplat", + "mgmtapi", + "midi", + "mlang", + "mmaux", + "mmc", + "mmdevapi", + "mmio", + "mmsys", + "mmtime", + "model", + "module", + "monthcal", + "mountmgr", + "mp3dmod", + "mpeg3", + "mpr", + "mprapi", + "msacm", + "msado15", + "msasn1", + "msauddecmft", + "mscms", + "msctf", + "msctfmonitor", + "msdasql", + "msdmo", + "msdrm", + "msftedit", + "msg", + "mshtml", + "msi", + "msidb", + "msident", + "msimg32", + "msimtf", + "msisip", + "msisys", + "msmpeg2vdec", + "msnet", + "msopc", + "mspatcha", + "msrle32", + "msscript", + "mssign", + "mstask", + "msttsengine", + "msvcirt", + "msvcm", + "msvcp", + "msvcrt", + "msvidc32", + "msvideo", + "mswsock", + "msxml", + "nativefont", + "ncrypt", + "nddeapi", + "ndis", + "netapi32", + "netbios", + "netcfgx", + "netio", + "netprofm", + "ninput", + "nls", + "nonclient", + "nsi", + "nstc", + "ntdll", + "ntdsapi", + "ntlm", + "ntoskrnl", + "ntprint", + "objsel", + "odbc", + "ole", + "oleacc", + "oledb", + "oledlg", + "olemalloc", + "olepicture", + "opencl", + "opengl", + "oss", + "packager", + "pager", + "palette", + "path", + "pdh", + "perception", + "pidgen", + "pidl", + "plugplay", + "powermgnt", + "powrprof", + "print", + "printui", + "prntvpt", + "process", + "profile", + "progress", + "propsheet", + "propsys", + "psdrv", + "pstores", + "pulse", + "qmgr", + "quartz", + "query", + "qwave", + "ras", + "rasdlg", + "rawinput", + "rebar", + "recyclebin", + "reg", + "region", + "relay", + "resource", + "richedit", + "richedit_lists", + "rpc", + "rstrtmgr", + "rtutils", + "sapi", + "schannel", + "schedsvc", + "scrobj", + "scroll", + "scrrun", + "scsiport", + "secur32", + "security", + "seh", + "selector", + "sensapi", + "service", + "setupapi", + "sfc", + "shcore", + "shdocvw", + "shell", + "shlctrl", + "slc", + "smbios", + "snmpapi", + "snoop", + "sound", + "speech", + "spoolss", + "sspicli", + "static", + "statusbar", + "sti", + "storage", + "stress", + "string", + "sxs", + "sync", + "syslevel", + "syslink", + "system", + "systray", + "t2embed", + "tab", + "tape", + "tapi", + "task", + "taskdialog", + "taskschd", + "tbs", + "tdh", + "tdi", + "text", + "theme_scroll", + "thread", + "threadpool", + "thunk", + "toolbar", + "toolhelp", + "tooltips", + "trackbar", + "traffic", + "treeview", + "twain", + "twinapi", + "ui", + "uianimation", + "uiautomation", + "uiribbon", + "unloaddll", + "unwind", + "updown", + "updspapi", + "url", + "urlmon", + "usb", + "usbd", + "user", + "uxtheme", + "variant", + "vbscript", + "vcomp", + "vcruntime", + "vdmdbg", + "ver", + "virtdisk", + "virtual", + "volume", + "vulkan", + "vxd", + "wavemap", + "waylanddrv", + "wbemdisp", + "wbemprox", + "webservices", + "wer", + "wevtapi", + "wgl", + "wia", + "wimgapi", + "win", + "winebrowser", + "wincodecs", + "winemapi", + "wineusb", + "wing", + "winhttp", + "wininet", + "winmm", + "winprint", + "winscard", + "winsock", + "winspool", + "winsta", + "winstation", + "winstring", + "wintab", + "wintab32", + "wintrust", + "wintypes", + "winusb", + "wlanapi", + "wldap32", + "wldp", + "wmadec", + "wmi", + "wmiutils", + "wmp", + "wmvcore", + "wnet", + "wofutil", + "wow", + "wpc", + "wpcap", + "wsdapi", + "wshom", + "wsnmp32", + "wtsapi", + "wuapi", + "x11drv", + "xaudio2", + "xdnd", + "xim", + "xinput", + "xmllite", + "xolehlp", + "xrandr", + "xrender", + "xvidmode", + "dmo" +] \ No newline at end of file diff --git a/app/src/main/assets/metadata/gpu_cards.json b/app/src/main/assets/metadata/gpu_cards.json index ad7a56f02..a1ebafc3e 100644 --- a/app/src/main/assets/metadata/gpu_cards.json +++ b/app/src/main/assets/metadata/gpu_cards.json @@ -1 +1,1447 @@ -[{"name":"NVIDIA RIVA 128","deviceID":24,"vendorID":4318},{"name":"NVIDIA RIVA TNT","deviceID":32,"vendorID":4318},{"name":"NVIDIA RIVA TNT2\/TNT2 Pro","deviceID":40,"vendorID":4318},{"name":"NVIDIA GeForce 256","deviceID":256,"vendorID":4318},{"name":"NVIDIA GeForce2 GTS\/GeForce2 Pro","deviceID":336,"vendorID":4318},{"name":"NVIDIA GeForce2 MX\/MX 400","deviceID":272,"vendorID":4318},{"name":"NVIDIA GeForce3","deviceID":512,"vendorID":4318},{"name":"NVIDIA GeForce4 MX 460","deviceID":368,"vendorID":4318},{"name":"NVIDIA GeForce4 Ti 4200","deviceID":595,"vendorID":4318},{"name":"NVIDIA GeForce FX 5200","deviceID":800,"vendorID":4318},{"name":"NVIDIA GeForce FX 5600","deviceID":786,"vendorID":4318},{"name":"NVIDIA GeForce FX 5800","deviceID":770,"vendorID":4318},{"name":"NVIDIA GeForce 6200","deviceID":335,"vendorID":4318},{"name":"NVIDIA GeForce 6600 GT","deviceID":320,"vendorID":4318},{"name":"NVIDIA GeForce 6800","deviceID":65,"vendorID":4318},{"name":"NVIDIA GeForce Go 7300","deviceID":2026952880896,"vendorID":4318},{"name":"NVIDIA GeForce Go 7400","deviceID":472,"vendorID":4318},{"name":"NVIDIA GeForce 7600 GT","deviceID":913,"vendorID":4318},{"name":"NVIDIA GeForce 7800 GT","deviceID":146,"vendorID":4318},{"name":"NVIDIA GeForce 8200","deviceID":9113598691403,"vendorID":4318},{"name":"NVIDIA GeForce 8300 GS","deviceID":1059,"vendorID":4318},{"name":"NVIDIA GeForce 8400 GS","deviceID":1028,"vendorID":4318},{"name":"NVIDIA GeForce 8500 GT","deviceID":1057,"vendorID":4318},{"name":"NVIDIA GeForce 8600 GT","deviceID":1026,"vendorID":4318},{"name":"NVIDIA GeForce 8600M GT","deviceID":1031,"vendorID":4318},{"name":"NVIDIA GeForce 8800 GTS","deviceID":403,"vendorID":4318},{"name":"NVIDIA GeForce 8800 GTX","deviceID":401,"vendorID":4318},{"name":"NVIDIA GeForce 9200","deviceID":2157,"vendorID":4318},{"name":"NVIDIA GeForce 9300","deviceID":2156,"vendorID":4318},{"name":"NVIDIA GeForce 9400M","deviceID":2147,"vendorID":4318},{"name":"NVIDIA GeForce 9400 GT","deviceID":1068,"vendorID":4318},{"name":"NVIDIA GeForce 9500 GT","deviceID":1600,"vendorID":4318},{"name":"NVIDIA GeForce 9600 GT","deviceID":1570,"vendorID":4318},{"name":"NVIDIA GeForce 9700M GT","deviceID":1610,"vendorID":4318},{"name":"NVIDIA GeForce 9800 GT","deviceID":1556,"vendorID":4318},{"name":"NVIDIA GeForce 210","deviceID":2595,"vendorID":4318},{"name":"NVIDIA GeForce GT 220","deviceID":2592,"vendorID":4318},{"name":"NVIDIA GeForce GT 240","deviceID":3235,"vendorID":4318},{"name":"NVIDIA GeForce GTS 250","deviceID":1557,"vendorID":4318},{"name":"NVIDIA GeForce GTX 260","deviceID":1506,"vendorID":4318},{"name":"NVIDIA GeForce GTX 275","deviceID":1510,"vendorID":4318},{"name":"NVIDIA GeForce GTX 280","deviceID":1505,"vendorID":4318},{"name":"NVIDIA GeForce 315M","deviceID":2682,"vendorID":4318},{"name":"NVIDIA GeForce 320M","deviceID":2211,"vendorID":4318},{"name":"NVIDIA GeForce GT 320M","deviceID":2605,"vendorID":4318},{"name":"NVIDIA GeForce GT 325M","deviceID":2613,"vendorID":4318},{"name":"NVIDIA GeForce GT 330","deviceID":3232,"vendorID":4318},{"name":"NVIDIA GeForce GTS 350M","deviceID":3248,"vendorID":4318},{"name":"NVIDIA GeForce 410M","deviceID":4181,"vendorID":4318},{"name":"NVIDIA GeForce GT 420","deviceID":3554,"vendorID":4318},{"name":"NVIDIA GeForce GT 425M","deviceID":3568,"vendorID":4318},{"name":"NVIDIA GeForce GT 430","deviceID":3553,"vendorID":4318},{"name":"NVIDIA GeForce GT 440","deviceID":3552,"vendorID":4318},{"name":"NVIDIA GeForce GTS 450","deviceID":3524,"vendorID":4318},{"name":"NVIDIA GeForce GTX 460","deviceID":3618,"vendorID":4318},{"name":"NVIDIA GeForce GTX 460M","deviceID":3537,"vendorID":4318},{"name":"NVIDIA GeForce GTX 465","deviceID":1732,"vendorID":4318},{"name":"NVIDIA GeForce GTX 470","deviceID":1741,"vendorID":4318},{"name":"NVIDIA GeForce GTX 480","deviceID":1728,"vendorID":4318},{"name":"NVIDIA GeForce GT 520","deviceID":4160,"vendorID":4318},{"name":"NVIDIA GeForce GT 525M","deviceID":3564,"vendorID":4318},{"name":"NVIDIA GeForce GT 540M","deviceID":3572,"vendorID":4318},{"name":"NVIDIA GeForce GTX 550 Ti","deviceID":4676,"vendorID":4318},{"name":"NVIDIA GeForce GT 555M","deviceID":1208,"vendorID":4318},{"name":"NVIDIA GeForce GTX 560 Ti","deviceID":4608,"vendorID":4318},{"name":"NVIDIA GeForce GTX 560M","deviceID":4689,"vendorID":4318},{"name":"NVIDIA GeForce GTX 560","deviceID":4609,"vendorID":4318},{"name":"NVIDIA GeForce GTX 570","deviceID":4225,"vendorID":4318},{"name":"NVIDIA GeForce GTX 580","deviceID":4224,"vendorID":4318},{"name":"NVIDIA GeForce GT 610","deviceID":4170,"vendorID":4318},{"name":"NVIDIA GeForce GT 630","deviceID":3840,"vendorID":4318},{"name":"NVIDIA GeForce GT 630M","deviceID":3561,"vendorID":4318},{"name":"NVIDIA GeForce GT 640","deviceID":4033,"vendorID":4318},{"name":"NVIDIA GeForce GT 640M","deviceID":4050,"vendorID":4318},{"name":"NVIDIA GeForce GT 650M","deviceID":4049,"vendorID":4318},{"name":"NVIDIA GeForce GTX 650","deviceID":4038,"vendorID":4318},{"name":"NVIDIA GeForce GTX 650 Ti","deviceID":4550,"vendorID":4318},{"name":"NVIDIA GeForce GTX 660","deviceID":4544,"vendorID":4318},{"name":"NVIDIA GeForce GTX 660M","deviceID":4052,"vendorID":4318},{"name":"NVIDIA GeForce GTX 660 Ti","deviceID":4483,"vendorID":4318},{"name":"NVIDIA GeForce GTX 670","deviceID":4489,"vendorID":4318},{"name":"NVIDIA GeForce GTX 670MX","deviceID":4513,"vendorID":4318},{"name":"NVIDIA GeForce GTX 675MX","deviceID":4519,"vendorID":4318},{"name":"NVIDIA GeForce GTX 675MX","deviceID":4514,"vendorID":4318},{"name":"NVIDIA GeForce GTX 680","deviceID":4480,"vendorID":4318},{"name":"NVIDIA GeForce GTX 690","deviceID":4488,"vendorID":4318},{"name":"NVIDIA GeForce GT 720","deviceID":4747,"vendorID":4318},{"name":"NVIDIA GeForce GT 730","deviceID":4743,"vendorID":4318},{"name":"NVIDIA GeForce GT 730M","deviceID":4065,"vendorID":4318},{"name":"NVIDIA GeForce GT 740M","deviceID":4754,"vendorID":4318},{"name":"NVIDIA GeForce GT 750M","deviceID":4073,"vendorID":4318},{"name":"NVIDIA GeForce GT 755M","deviceID":4045,"vendorID":4318},{"name":"NVIDIA GeForce GTX 750","deviceID":4993,"vendorID":4318},{"name":"NVIDIA GeForce GTX 750 Ti","deviceID":4992,"vendorID":4318},{"name":"NVIDIA GeForce GTX 760","deviceID":4487,"vendorID":4318},{"name":"NVIDIA GeForce GTX 760 Ti","deviceID":4499,"vendorID":4318},{"name":"NVIDIA GeForce GTX 765M","deviceID":4578,"vendorID":4318},{"name":"NVIDIA GeForce GTX 770M","deviceID":4576,"vendorID":4318},{"name":"NVIDIA GeForce GTX 770","deviceID":4484,"vendorID":4318},{"name":"NVIDIA GeForce GTX 775M","deviceID":4509,"vendorID":4318},{"name":"NVIDIA GeForce GTX 780","deviceID":4100,"vendorID":4318},{"name":"NVIDIA GeForce GTX 780M","deviceID":4510,"vendorID":4318},{"name":"NVIDIA GeForce GTX 780 Ti","deviceID":4106,"vendorID":4318},{"name":"NVIDIA GeForce GTX TITAN","deviceID":4101,"vendorID":4318},{"name":"NVIDIA GeForce GTX TITAN Black","deviceID":4108,"vendorID":4318},{"name":"NVIDIA GeForce GTX TITAN X","deviceID":6082,"vendorID":4318},{"name":"NVIDIA GeForce GTX TITAN Z","deviceID":4097,"vendorID":4318},{"name":"NVIDIA GeForce 820M","deviceID":4077,"vendorID":4318},{"name":"NVIDIA GeForce 830M","deviceID":4928,"vendorID":4318},{"name":"NVIDIA GeForce 840M","deviceID":4929,"vendorID":4318},{"name":"NVIDIA GeForce 845M","deviceID":4932,"vendorID":4318},{"name":"NVIDIA GeForce GTX 850M","deviceID":5009,"vendorID":4318},{"name":"NVIDIA GeForce GTX 860M","deviceID":21521759211930,"vendorID":4318},{"name":"NVIDIA GeForce GTX 870M","deviceID":4505,"vendorID":4318},{"name":"NVIDIA GeForce GTX 880M","deviceID":4504,"vendorID":4318},{"name":"NVIDIA GeForce 940M","deviceID":4935,"vendorID":4318},{"name":"NVIDIA GeForce GTX 950","deviceID":5122,"vendorID":4318},{"name":"NVIDIA GeForce GTX 950M","deviceID":5018,"vendorID":4318},{"name":"NVIDIA GeForce GTX 960","deviceID":5121,"vendorID":4318},{"name":"NVIDIA GeForce GTX 960M","deviceID":5019,"vendorID":4318},{"name":"NVIDIA GeForce GTX 970","deviceID":5058,"vendorID":4318},{"name":"NVIDIA GeForce GTX 970M","deviceID":5080,"vendorID":4318},{"name":"NVIDIA GeForce GTX 980","deviceID":5056,"vendorID":4318},{"name":"NVIDIA GeForce GTX 980 Ti","deviceID":6088,"vendorID":4318},{"name":"NVIDIA GeForce GT 1030","deviceID":7425,"vendorID":4318},{"name":"NVIDIA GeForce GTX 1050","deviceID":7297,"vendorID":4318},{"name":"NVIDIA GeForce GTX 1050 Ti","deviceID":7298,"vendorID":4318},{"name":"NVIDIA GeForce GTX 1060 3GB","deviceID":7170,"vendorID":4318},{"name":"NVIDIA GeForce GTX 1060","deviceID":7171,"vendorID":4318},{"name":"NVIDIA GeForce GTX 1060M","deviceID":7200,"vendorID":4318},{"name":"NVIDIA GeForce GTX 1070","deviceID":7041,"vendorID":4318},{"name":"NVIDIA GeForce GTX 1080","deviceID":7040,"vendorID":4318},{"name":"NVIDIA GeForce GTX 1080M","deviceID":7136,"vendorID":4318},{"name":"NVIDIA GeForce GTX 1080 Ti","deviceID":6918,"vendorID":4318},{"name":"NVIDIA TITAN X (Pascal)","deviceID":6912,"vendorID":4318},{"name":"NVIDIA TITAN V","deviceID":7553,"vendorID":4318},{"name":"NVIDIA GeForce GTX 1650 SUPER","deviceID":8583,"vendorID":4318},{"name":"NVIDIA GeForce GTX 1660 SUPER","deviceID":8644,"vendorID":4318},{"name":"NVIDIA GeForce GTX 1660 Ti","deviceID":8578,"vendorID":4318},{"name":"NVIDIA GeForce RTX 2060","deviceID":7944,"vendorID":4318},{"name":"NVIDIA GeForce RTX 2070","deviceID":7943,"vendorID":4318},{"name":"NVIDIA GeForce RTX 2080","deviceID":7815,"vendorID":4318},{"name":"NVIDIA GeForce RTX 2080 Ti","deviceID":7687,"vendorID":4318},{"name":"NVIDIA GeForce RTX 3070","deviceID":9373,"vendorID":4318},{"name":"NVIDIA Tesla T4","deviceID":7864,"vendorID":4318},{"name":"NVIDIA Ampere A10","deviceID":8758,"vendorID":4318},{"name":"ATI Rage Fury","deviceID":21062,"vendorID":4098},{"name":"ATI RADEON 7200 SERIES","deviceID":20804,"vendorID":4098},{"name":"ATI RADEON 8500 SERIES","deviceID":20812,"vendorID":4098},{"name":"ATI Radeon 9500","deviceID":16708,"vendorID":4098},{"name":"ATI RADEON XPRESS 200M Series","deviceID":22869,"vendorID":4098},{"name":"ATI Radeon X700 SE","deviceID":24140,"vendorID":4098},{"name":"ATI Radeon X1600 Series","deviceID":29122,"vendorID":4098},{"name":"ATI Mobility Radeon HD 2350","deviceID":38087,"vendorID":4098},{"name":"ATI Mobility Radeon HD 2600","deviceID":38273,"vendorID":4098},{"name":"ATI Radeon HD 2900 XT","deviceID":37888,"vendorID":4098},{"name":"ATI Radeon HD 3200 Graphics","deviceID":38432,"vendorID":4098},{"name":"ATI Radeon HD 3850 AGP","deviceID":38165,"vendorID":4098},{"name":"ATI Mobility Radeon HD 4200","deviceID":38674,"vendorID":4098},{"name":"ATI Radeon HD 4350","deviceID":38223,"vendorID":4098},{"name":"ATI Radeon HD 4600 Series","deviceID":38037,"vendorID":4098},{"name":"ATI Radeon HD 4700 Series","deviceID":37966,"vendorID":4098},{"name":"ATI Radeon HD 4800 Series","deviceID":37964,"vendorID":4098},{"name":"ATI Radeon HD 5400 Series","deviceID":26873,"vendorID":4098},{"name":"ATI Radeon HD 5600 Series","deviceID":26840,"vendorID":4098},{"name":"ATI Radeon HD 5700 Series","deviceID":26814,"vendorID":4098},{"name":"ATI Radeon HD 5800 Series","deviceID":26776,"vendorID":4098},{"name":"ATI Radeon HD 5900 Series","deviceID":26780,"vendorID":4098},{"name":"AMD Radeon HD 6300 series Graphics","deviceID":38915,"vendorID":4098},{"name":"AMD Radeon HD 6400 Series","deviceID":26480,"vendorID":4098},{"name":"AMD Radeon HD 6410D","deviceID":38468,"vendorID":4098},{"name":"AMD Radeon HD 6480G","deviceID":38472,"vendorID":4098},{"name":"AMD Radeon HD 6490M","deviceID":26464,"vendorID":4098},{"name":"AMD Radeon HD 6550D","deviceID":38464,"vendorID":4098},{"name":"AMD Radeon HD 6600 Series","deviceID":26456,"vendorID":4098},{"name":"AMD Radeon HD 6600M Series","deviceID":26433,"vendorID":4098},{"name":"AMD Radeon HD 6700 Series","deviceID":26810,"vendorID":4098},{"name":"AMD Radeon HD 6800 Series","deviceID":26425,"vendorID":4098},{"name":"AMD Radeon HD 6900 Series","deviceID":26393,"vendorID":4098},{"name":"AMD Radeon HD 7660D","deviceID":39169,"vendorID":4098},{"name":"AMD Radeon HD 7700 Series","deviceID":26685,"vendorID":4098},{"name":"AMD Radeon HD 7800 Series","deviceID":26649,"vendorID":4098},{"name":"AMD Radeon HD 7870 Series","deviceID":26648,"vendorID":4098},{"name":"AMD Radeon HD 7900 Series","deviceID":26522,"vendorID":4098},{"name":"AMD Radeon HD 8600M Series","deviceID":26208,"vendorID":4098},{"name":"AMD Radeon HD 8670","deviceID":26128,"vendorID":4098},{"name":"AMD Radeon HD 8770","deviceID":26204,"vendorID":4098},{"name":"AMD Radeon HD 8400 \/ R3 Series","deviceID":38960,"vendorID":4098},{"name":"AMD Radeon(TM) R7 Graphics","deviceID":4879,"vendorID":4098},{"name":"AMD Radeon R9 285","deviceID":26937,"vendorID":4098},{"name":"AMD Radeon R9 290","deviceID":26545,"vendorID":4098},{"name":"AMD Radeon R9 290X","deviceID":26544,"vendorID":4098},{"name":"AMD Radeon (TM) R9 Fury Series","deviceID":29440,"vendorID":4098},{"name":"AMD Radeon R9 M370X","deviceID":26657,"vendorID":4098},{"name":"AMD Radeon R9 M380","deviceID":26183,"vendorID":4098},{"name":"AMD Radeon R9 M395X","deviceID":26912,"vendorID":4098},{"name":"Radeon(TM) RX 460 Graphics","deviceID":26607,"vendorID":4098},{"name":"Radeon (TM) RX 480 Graphics","deviceID":26591,"vendorID":4098},{"name":"Radeon RX Vega","deviceID":26751,"vendorID":4098},{"name":"Radeon Pro Vega 20","deviceID":27055,"vendorID":4098},{"name":"AMD Radeon(TM) Vega 10 Mobile Graphics","deviceID":5597,"vendorID":4098},{"name":"Radeon RX Vega 20","deviceID":26287,"vendorID":4098},{"name":"Radeon RX 5700 \/ 5700 XT","deviceID":29471,"vendorID":4098},{"name":"Radeon RX 5500M","deviceID":29504,"vendorID":4098},{"name":"Radeon RX 6800\/6800 XT \/ 6900 XT","deviceID":29631,"vendorID":4098},{"name":"Radeon Pro V620","deviceID":29601,"vendorID":4098},{"name":"Radeon Pro V620 VF","deviceID":29614,"vendorID":4098},{"name":"AMD VANGOGH","deviceID":5695,"vendorID":4098},{"name":"AMD Radeon(TM) Graphics","deviceID":5710,"vendorID":4098},{"name":"Intel(R) 82830M Graphics Controller","deviceID":13687,"vendorID":32902},{"name":"Intel(R) 82852\/82855 GM\/GME Graphics Controller","deviceID":13698,"vendorID":32902},{"name":"Intel(R) 845G","deviceID":9570,"vendorID":32902},{"name":"Intel(R) 82865G Graphics Controller","deviceID":9586,"vendorID":32902},{"name":"Intel(R) 82915G\/GV\/910GL Express Chipset Family","deviceID":9602,"vendorID":32902},{"name":"Intel(R) E7221G","deviceID":9610,"vendorID":32902},{"name":"Mobile Intel(R) 915GM\/GMS","deviceID":9618,"vendorID":32902},{"name":"Intel(R) 945G","deviceID":10098,"vendorID":32902},{"name":"Mobile Intel(R) 945GM Express Chipset Family","deviceID":10146,"vendorID":32902},{"name":"Intel(R) 945GME","deviceID":10158,"vendorID":32902},{"name":"Intel(R) Q35","deviceID":10674,"vendorID":32902},{"name":"Intel(R) G33","deviceID":10690,"vendorID":32902},{"name":"Intel(R) Q33","deviceID":10706,"vendorID":32902},{"name":"Intel(R) IGD","deviceID":40961,"vendorID":32902},{"name":"Intel(R) IGD","deviceID":40977,"vendorID":32902},{"name":"Intel(R) 965Q","deviceID":10642,"vendorID":32902},{"name":"Intel(R) 965G","deviceID":10626,"vendorID":32902},{"name":"Intel(R) 946GZ","deviceID":10610,"vendorID":32902},{"name":"Mobile Intel(R) 965 Express Chipset Family","deviceID":10754,"vendorID":32902},{"name":"Intel(R) 965GME","deviceID":10770,"vendorID":32902},{"name":"Mobile Intel(R) GM45 Express Chipset Family","deviceID":10818,"vendorID":32902},{"name":"Intel(R) Integrated Graphics Device","deviceID":11778,"vendorID":32902},{"name":"Intel(R) G45\/G43","deviceID":11810,"vendorID":32902},{"name":"Intel(R) Q45\/Q43","deviceID":11794,"vendorID":32902},{"name":"Intel(R) G41","deviceID":11826,"vendorID":32902},{"name":"Intel(R) B43","deviceID":11922,"vendorID":32902},{"name":"Intel(R) HD Graphics","deviceID":66,"vendorID":32902},{"name":"Intel(R) HD Graphics","deviceID":70,"vendorID":32902},{"name":"Intel(R) HD Graphics 3000","deviceID":290,"vendorID":32902},{"name":"Intel(R) HD Graphics 3000","deviceID":294,"vendorID":32902},{"name":"Intel(R) HD Graphics Family","deviceID":266,"vendorID":32902},{"name":"Intel(R) HD Graphics 4000","deviceID":354,"vendorID":32902},{"name":"Intel(R) HD Graphics 4000","deviceID":358,"vendorID":32902},{"name":"Intel(R) HD Graphics Family","deviceID":346,"vendorID":32902},{"name":"Intel(R) HD Graphics 4600","deviceID":1042,"vendorID":32902},{"name":"Intel(R) HD Graphics 4600","deviceID":1046,"vendorID":32902},{"name":"Intel(R) HD Graphics 5000","deviceID":2598,"vendorID":32902},{"name":"Intel(R) HD Graphics 5000","deviceID":1058,"vendorID":32902},{"name":"Intel(R) Iris(TM) Graphics 5100","deviceID":2594,"vendorID":32902},{"name":"Intel(R) Iris(TM) Graphics 5100","deviceID":2602,"vendorID":32902},{"name":"Intel(R) Iris(TM) Graphics 5100","deviceID":2603,"vendorID":32902},{"name":"Intel(R) Iris(TM) Graphics 5100","deviceID":2606,"vendorID":32902},{"name":"Intel(R) Iris(TM) Pro Graphics 5200","deviceID":3362,"vendorID":32902},{"name":"Intel(R) Iris(TM) Pro Graphics 5200","deviceID":3366,"vendorID":32902},{"name":"Intel(R) Iris(TM) Pro Graphics 5200","deviceID":3370,"vendorID":32902},{"name":"Intel(R) Iris(TM) Pro Graphics 5200","deviceID":3371,"vendorID":32902},{"name":"Intel(R) Iris(TM) Pro Graphics 5200","deviceID":3374,"vendorID":32902},{"name":"Intel(R) Iris(TM) Pro Graphics 5200","deviceID":3106,"vendorID":32902},{"name":"Intel(R) HD Graphics 5300","deviceID":5662,"vendorID":32902},{"name":"Intel(R) HD Graphics 5500","deviceID":5654,"vendorID":32902},{"name":"Intel(R) HD Graphics 5600","deviceID":5650,"vendorID":32902},{"name":"Intel(R) HD Graphics 6000","deviceID":5670,"vendorID":32902},{"name":"Intel(R) Iris(TM) Graphics 6100","deviceID":5675,"vendorID":32902},{"name":"Intel(R) Iris(TM) Pro Graphics 6200","deviceID":5666,"vendorID":32902},{"name":"Intel(R) Iris(TM) Pro Graphics P6300","deviceID":5674,"vendorID":32902},{"name":"Intel(R) HD Graphics 510","deviceID":6402,"vendorID":32902},{"name":"Intel(R) HD Graphics 510","deviceID":6406,"vendorID":32902},{"name":"Intel(R) HD Graphics 510","deviceID":6411,"vendorID":32902},{"name":"Intel(R) HD Graphics 515","deviceID":6430,"vendorID":32902},{"name":"Intel(R) HD Graphics 520","deviceID":6422,"vendorID":32902},{"name":"Intel(R) HD Graphics 520","deviceID":6433,"vendorID":32902},{"name":"Intel(R) HD Graphics 530","deviceID":6418,"vendorID":32902},{"name":"Intel(R) HD Graphics 530","deviceID":6427,"vendorID":32902},{"name":"Intel(R) HD Graphics P530","deviceID":6429,"vendorID":32902},{"name":"Intel(R) Iris(TM) Graphics 540","deviceID":6438,"vendorID":32902},{"name":"Intel(R) Iris(TM) Graphics 550","deviceID":6439,"vendorID":32902},{"name":"Intel(R) Iris(TM) Graphics 555","deviceID":6443,"vendorID":32902},{"name":"Intel(R) Iris(TM) Graphics P555","deviceID":6445,"vendorID":32902},{"name":"Intel(R) Iris(TM) Pro Graphics 580","deviceID":6450,"vendorID":32902},{"name":"Intel(R) Iris(TM) Pro Graphics 580","deviceID":6459,"vendorID":32902},{"name":"Intel(R) Iris(TM) Pro Graphics P580","deviceID":6458,"vendorID":32902},{"name":"Intel(R) Iris(TM) Pro Graphics P580","deviceID":6461,"vendorID":32902},{"name":"Intel(R) UHD Graphics 617","deviceID":34752,"vendorID":32902},{"name":"Intel(R) UHD Graphics 620","deviceID":16032,"vendorID":32902},{"name":"Intel(R) HD Graphics 615","deviceID":22814,"vendorID":32902},{"name":"Intel(R) HD Graphics 620","deviceID":22806,"vendorID":32902},{"name":"Intel(R) HD Graphics 630","deviceID":22802,"vendorID":32902},{"name":"Intel(R) HD Graphics 630","deviceID":22811,"vendorID":32902},{"name":"Intel(R) UHD Graphics 630","deviceID":16027,"vendorID":32902},{"name":"Intel(R) UHD Graphics 630","deviceID":16017,"vendorID":32902}] \ No newline at end of file +[ + { + "name": "NVIDIA RIVA 128", + "deviceID": 24, + "vendorID": 4318 + }, + { + "name": "NVIDIA RIVA TNT", + "deviceID": 32, + "vendorID": 4318 + }, + { + "name": "NVIDIA RIVA TNT2/TNT2 Pro", + "deviceID": 40, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce 256", + "deviceID": 256, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce2 GTS/GeForce2 Pro", + "deviceID": 336, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce2 MX/MX 400", + "deviceID": 272, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce3", + "deviceID": 512, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce4 MX 460", + "deviceID": 368, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce4 Ti 4200", + "deviceID": 595, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce FX 5200", + "deviceID": 800, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce FX 5600", + "deviceID": 786, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce FX 5800", + "deviceID": 770, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce 6200", + "deviceID": 335, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce 6600 GT", + "deviceID": 320, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce 6800", + "deviceID": 65, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce Go 7300", + "deviceID": 2026952880896, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce Go 7400", + "deviceID": 472, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce 7600 GT", + "deviceID": 913, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce 7800 GT", + "deviceID": 146, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce 8200", + "deviceID": 9113598691403, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce 8300 GS", + "deviceID": 1059, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce 8400 GS", + "deviceID": 1028, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce 8500 GT", + "deviceID": 1057, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce 8600 GT", + "deviceID": 1026, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce 8600M GT", + "deviceID": 1031, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce 8800 GTS", + "deviceID": 403, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce 8800 GTX", + "deviceID": 401, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce 9200", + "deviceID": 2157, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce 9300", + "deviceID": 2156, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce 9400M", + "deviceID": 2147, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce 9400 GT", + "deviceID": 1068, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce 9500 GT", + "deviceID": 1600, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce 9600 GT", + "deviceID": 1570, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce 9700M GT", + "deviceID": 1610, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce 9800 GT", + "deviceID": 1556, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce 210", + "deviceID": 2595, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GT 220", + "deviceID": 2592, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GT 240", + "deviceID": 3235, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTS 250", + "deviceID": 1557, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 260", + "deviceID": 1506, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 275", + "deviceID": 1510, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 280", + "deviceID": 1505, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce 315M", + "deviceID": 2682, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce 320M", + "deviceID": 2211, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GT 320M", + "deviceID": 2605, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GT 325M", + "deviceID": 2613, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GT 330", + "deviceID": 3232, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTS 350M", + "deviceID": 3248, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce 410M", + "deviceID": 4181, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GT 420", + "deviceID": 3554, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GT 425M", + "deviceID": 3568, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GT 430", + "deviceID": 3553, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GT 440", + "deviceID": 3552, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTS 450", + "deviceID": 3524, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 460", + "deviceID": 3618, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 460M", + "deviceID": 3537, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 465", + "deviceID": 1732, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 470", + "deviceID": 1741, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 480", + "deviceID": 1728, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GT 520", + "deviceID": 4160, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GT 525M", + "deviceID": 3564, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GT 540M", + "deviceID": 3572, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 550 Ti", + "deviceID": 4676, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GT 555M", + "deviceID": 1208, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 560 Ti", + "deviceID": 4608, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 560M", + "deviceID": 4689, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 560", + "deviceID": 4609, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 570", + "deviceID": 4225, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 580", + "deviceID": 4224, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GT 610", + "deviceID": 4170, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GT 630", + "deviceID": 3840, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GT 630M", + "deviceID": 3561, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GT 640", + "deviceID": 4033, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GT 640M", + "deviceID": 4050, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GT 650M", + "deviceID": 4049, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 650", + "deviceID": 4038, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 650 Ti", + "deviceID": 4550, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 660", + "deviceID": 4544, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 660M", + "deviceID": 4052, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 660 Ti", + "deviceID": 4483, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 670", + "deviceID": 4489, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 670MX", + "deviceID": 4513, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 675MX", + "deviceID": 4519, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 675MX", + "deviceID": 4514, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 680", + "deviceID": 4480, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 690", + "deviceID": 4488, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GT 720", + "deviceID": 4747, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GT 730", + "deviceID": 4743, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GT 730M", + "deviceID": 4065, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GT 740M", + "deviceID": 4754, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GT 750M", + "deviceID": 4073, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GT 755M", + "deviceID": 4045, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 750", + "deviceID": 4993, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 750 Ti", + "deviceID": 4992, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 760", + "deviceID": 4487, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 760 Ti", + "deviceID": 4499, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 765M", + "deviceID": 4578, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 770M", + "deviceID": 4576, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 770", + "deviceID": 4484, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 775M", + "deviceID": 4509, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 780", + "deviceID": 4100, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 780M", + "deviceID": 4510, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 780 Ti", + "deviceID": 4106, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX TITAN", + "deviceID": 4101, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX TITAN Black", + "deviceID": 4108, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX TITAN X", + "deviceID": 6082, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX TITAN Z", + "deviceID": 4097, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce 820M", + "deviceID": 4077, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce 830M", + "deviceID": 4928, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce 840M", + "deviceID": 4929, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce 845M", + "deviceID": 4932, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 850M", + "deviceID": 5009, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 860M", + "deviceID": 21521759211930, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 870M", + "deviceID": 4505, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 880M", + "deviceID": 4504, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce 940M", + "deviceID": 4935, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 950", + "deviceID": 5122, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 950M", + "deviceID": 5018, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 960", + "deviceID": 5121, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 960M", + "deviceID": 5019, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 970", + "deviceID": 5058, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 970M", + "deviceID": 5080, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 980", + "deviceID": 5056, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 980 Ti", + "deviceID": 6088, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GT 1030", + "deviceID": 7425, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 1050", + "deviceID": 7297, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 1050 Ti", + "deviceID": 7298, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 1060 3GB", + "deviceID": 7170, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 1060", + "deviceID": 7171, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 1060M", + "deviceID": 7200, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 1070", + "deviceID": 7041, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 1080", + "deviceID": 7040, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 1080M", + "deviceID": 7136, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 1080 Ti", + "deviceID": 6918, + "vendorID": 4318 + }, + { + "name": "NVIDIA TITAN X (Pascal)", + "deviceID": 6912, + "vendorID": 4318 + }, + { + "name": "NVIDIA TITAN V", + "deviceID": 7553, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 1650 SUPER", + "deviceID": 8583, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 1660 SUPER", + "deviceID": 8644, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce GTX 1660 Ti", + "deviceID": 8578, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce RTX 2060", + "deviceID": 7944, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce RTX 2070", + "deviceID": 7943, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce RTX 2080", + "deviceID": 7815, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce RTX 2080 Ti", + "deviceID": 7687, + "vendorID": 4318 + }, + { + "name": "NVIDIA GeForce RTX 3070", + "deviceID": 9373, + "vendorID": 4318 + }, + { + "name": "NVIDIA Tesla T4", + "deviceID": 7864, + "vendorID": 4318 + }, + { + "name": "NVIDIA Ampere A10", + "deviceID": 8758, + "vendorID": 4318 + }, + { + "name": "ATI Rage Fury", + "deviceID": 21062, + "vendorID": 4098 + }, + { + "name": "ATI RADEON 7200 SERIES", + "deviceID": 20804, + "vendorID": 4098 + }, + { + "name": "ATI RADEON 8500 SERIES", + "deviceID": 20812, + "vendorID": 4098 + }, + { + "name": "ATI Radeon 9500", + "deviceID": 16708, + "vendorID": 4098 + }, + { + "name": "ATI RADEON XPRESS 200M Series", + "deviceID": 22869, + "vendorID": 4098 + }, + { + "name": "ATI Radeon X700 SE", + "deviceID": 24140, + "vendorID": 4098 + }, + { + "name": "ATI Radeon X1600 Series", + "deviceID": 29122, + "vendorID": 4098 + }, + { + "name": "ATI Mobility Radeon HD 2350", + "deviceID": 38087, + "vendorID": 4098 + }, + { + "name": "ATI Mobility Radeon HD 2600", + "deviceID": 38273, + "vendorID": 4098 + }, + { + "name": "ATI Radeon HD 2900 XT", + "deviceID": 37888, + "vendorID": 4098 + }, + { + "name": "ATI Radeon HD 3200 Graphics", + "deviceID": 38432, + "vendorID": 4098 + }, + { + "name": "ATI Radeon HD 3850 AGP", + "deviceID": 38165, + "vendorID": 4098 + }, + { + "name": "ATI Mobility Radeon HD 4200", + "deviceID": 38674, + "vendorID": 4098 + }, + { + "name": "ATI Radeon HD 4350", + "deviceID": 38223, + "vendorID": 4098 + }, + { + "name": "ATI Radeon HD 4600 Series", + "deviceID": 38037, + "vendorID": 4098 + }, + { + "name": "ATI Radeon HD 4700 Series", + "deviceID": 37966, + "vendorID": 4098 + }, + { + "name": "ATI Radeon HD 4800 Series", + "deviceID": 37964, + "vendorID": 4098 + }, + { + "name": "ATI Radeon HD 5400 Series", + "deviceID": 26873, + "vendorID": 4098 + }, + { + "name": "ATI Radeon HD 5600 Series", + "deviceID": 26840, + "vendorID": 4098 + }, + { + "name": "ATI Radeon HD 5700 Series", + "deviceID": 26814, + "vendorID": 4098 + }, + { + "name": "ATI Radeon HD 5800 Series", + "deviceID": 26776, + "vendorID": 4098 + }, + { + "name": "ATI Radeon HD 5900 Series", + "deviceID": 26780, + "vendorID": 4098 + }, + { + "name": "AMD Radeon HD 6300 series Graphics", + "deviceID": 38915, + "vendorID": 4098 + }, + { + "name": "AMD Radeon HD 6400 Series", + "deviceID": 26480, + "vendorID": 4098 + }, + { + "name": "AMD Radeon HD 6410D", + "deviceID": 38468, + "vendorID": 4098 + }, + { + "name": "AMD Radeon HD 6480G", + "deviceID": 38472, + "vendorID": 4098 + }, + { + "name": "AMD Radeon HD 6490M", + "deviceID": 26464, + "vendorID": 4098 + }, + { + "name": "AMD Radeon HD 6550D", + "deviceID": 38464, + "vendorID": 4098 + }, + { + "name": "AMD Radeon HD 6600 Series", + "deviceID": 26456, + "vendorID": 4098 + }, + { + "name": "AMD Radeon HD 6600M Series", + "deviceID": 26433, + "vendorID": 4098 + }, + { + "name": "AMD Radeon HD 6700 Series", + "deviceID": 26810, + "vendorID": 4098 + }, + { + "name": "AMD Radeon HD 6800 Series", + "deviceID": 26425, + "vendorID": 4098 + }, + { + "name": "AMD Radeon HD 6900 Series", + "deviceID": 26393, + "vendorID": 4098 + }, + { + "name": "AMD Radeon HD 7660D", + "deviceID": 39169, + "vendorID": 4098 + }, + { + "name": "AMD Radeon HD 7700 Series", + "deviceID": 26685, + "vendorID": 4098 + }, + { + "name": "AMD Radeon HD 7800 Series", + "deviceID": 26649, + "vendorID": 4098 + }, + { + "name": "AMD Radeon HD 7870 Series", + "deviceID": 26648, + "vendorID": 4098 + }, + { + "name": "AMD Radeon HD 7900 Series", + "deviceID": 26522, + "vendorID": 4098 + }, + { + "name": "AMD Radeon HD 8600M Series", + "deviceID": 26208, + "vendorID": 4098 + }, + { + "name": "AMD Radeon HD 8670", + "deviceID": 26128, + "vendorID": 4098 + }, + { + "name": "AMD Radeon HD 8770", + "deviceID": 26204, + "vendorID": 4098 + }, + { + "name": "AMD Radeon HD 8400 / R3 Series", + "deviceID": 38960, + "vendorID": 4098 + }, + { + "name": "AMD Radeon(TM) R7 Graphics", + "deviceID": 4879, + "vendorID": 4098 + }, + { + "name": "AMD Radeon R9 285", + "deviceID": 26937, + "vendorID": 4098 + }, + { + "name": "AMD Radeon R9 290", + "deviceID": 26545, + "vendorID": 4098 + }, + { + "name": "AMD Radeon R9 290X", + "deviceID": 26544, + "vendorID": 4098 + }, + { + "name": "AMD Radeon (TM) R9 Fury Series", + "deviceID": 29440, + "vendorID": 4098 + }, + { + "name": "AMD Radeon R9 M370X", + "deviceID": 26657, + "vendorID": 4098 + }, + { + "name": "AMD Radeon R9 M380", + "deviceID": 26183, + "vendorID": 4098 + }, + { + "name": "AMD Radeon R9 M395X", + "deviceID": 26912, + "vendorID": 4098 + }, + { + "name": "Radeon(TM) RX 460 Graphics", + "deviceID": 26607, + "vendorID": 4098 + }, + { + "name": "Radeon (TM) RX 480 Graphics", + "deviceID": 26591, + "vendorID": 4098 + }, + { + "name": "Radeon RX Vega", + "deviceID": 26751, + "vendorID": 4098 + }, + { + "name": "Radeon Pro Vega 20", + "deviceID": 27055, + "vendorID": 4098 + }, + { + "name": "AMD Radeon(TM) Vega 10 Mobile Graphics", + "deviceID": 5597, + "vendorID": 4098 + }, + { + "name": "Radeon RX Vega 20", + "deviceID": 26287, + "vendorID": 4098 + }, + { + "name": "Radeon RX 5700 / 5700 XT", + "deviceID": 29471, + "vendorID": 4098 + }, + { + "name": "Radeon RX 5500M", + "deviceID": 29504, + "vendorID": 4098 + }, + { + "name": "Radeon RX 6800/6800 XT / 6900 XT", + "deviceID": 29631, + "vendorID": 4098 + }, + { + "name": "Radeon Pro V620", + "deviceID": 29601, + "vendorID": 4098 + }, + { + "name": "Radeon Pro V620 VF", + "deviceID": 29614, + "vendorID": 4098 + }, + { + "name": "AMD VANGOGH", + "deviceID": 5695, + "vendorID": 4098 + }, + { + "name": "AMD Radeon(TM) Graphics", + "deviceID": 5710, + "vendorID": 4098 + }, + { + "name": "Intel(R) 82830M Graphics Controller", + "deviceID": 13687, + "vendorID": 32902 + }, + { + "name": "Intel(R) 82852/82855 GM/GME Graphics Controller", + "deviceID": 13698, + "vendorID": 32902 + }, + { + "name": "Intel(R) 845G", + "deviceID": 9570, + "vendorID": 32902 + }, + { + "name": "Intel(R) 82865G Graphics Controller", + "deviceID": 9586, + "vendorID": 32902 + }, + { + "name": "Intel(R) 82915G/GV/910GL Express Chipset Family", + "deviceID": 9602, + "vendorID": 32902 + }, + { + "name": "Intel(R) E7221G", + "deviceID": 9610, + "vendorID": 32902 + }, + { + "name": "Mobile Intel(R) 915GM/GMS", + "deviceID": 9618, + "vendorID": 32902 + }, + { + "name": "Intel(R) 945G", + "deviceID": 10098, + "vendorID": 32902 + }, + { + "name": "Mobile Intel(R) 945GM Express Chipset Family", + "deviceID": 10146, + "vendorID": 32902 + }, + { + "name": "Intel(R) 945GME", + "deviceID": 10158, + "vendorID": 32902 + }, + { + "name": "Intel(R) Q35", + "deviceID": 10674, + "vendorID": 32902 + }, + { + "name": "Intel(R) G33", + "deviceID": 10690, + "vendorID": 32902 + }, + { + "name": "Intel(R) Q33", + "deviceID": 10706, + "vendorID": 32902 + }, + { + "name": "Intel(R) IGD", + "deviceID": 40961, + "vendorID": 32902 + }, + { + "name": "Intel(R) IGD", + "deviceID": 40977, + "vendorID": 32902 + }, + { + "name": "Intel(R) 965Q", + "deviceID": 10642, + "vendorID": 32902 + }, + { + "name": "Intel(R) 965G", + "deviceID": 10626, + "vendorID": 32902 + }, + { + "name": "Intel(R) 946GZ", + "deviceID": 10610, + "vendorID": 32902 + }, + { + "name": "Mobile Intel(R) 965 Express Chipset Family", + "deviceID": 10754, + "vendorID": 32902 + }, + { + "name": "Intel(R) 965GME", + "deviceID": 10770, + "vendorID": 32902 + }, + { + "name": "Mobile Intel(R) GM45 Express Chipset Family", + "deviceID": 10818, + "vendorID": 32902 + }, + { + "name": "Intel(R) Integrated Graphics Device", + "deviceID": 11778, + "vendorID": 32902 + }, + { + "name": "Intel(R) G45/G43", + "deviceID": 11810, + "vendorID": 32902 + }, + { + "name": "Intel(R) Q45/Q43", + "deviceID": 11794, + "vendorID": 32902 + }, + { + "name": "Intel(R) G41", + "deviceID": 11826, + "vendorID": 32902 + }, + { + "name": "Intel(R) B43", + "deviceID": 11922, + "vendorID": 32902 + }, + { + "name": "Intel(R) HD Graphics", + "deviceID": 66, + "vendorID": 32902 + }, + { + "name": "Intel(R) HD Graphics", + "deviceID": 70, + "vendorID": 32902 + }, + { + "name": "Intel(R) HD Graphics 3000", + "deviceID": 290, + "vendorID": 32902 + }, + { + "name": "Intel(R) HD Graphics 3000", + "deviceID": 294, + "vendorID": 32902 + }, + { + "name": "Intel(R) HD Graphics Family", + "deviceID": 266, + "vendorID": 32902 + }, + { + "name": "Intel(R) HD Graphics 4000", + "deviceID": 354, + "vendorID": 32902 + }, + { + "name": "Intel(R) HD Graphics 4000", + "deviceID": 358, + "vendorID": 32902 + }, + { + "name": "Intel(R) HD Graphics Family", + "deviceID": 346, + "vendorID": 32902 + }, + { + "name": "Intel(R) HD Graphics 4600", + "deviceID": 1042, + "vendorID": 32902 + }, + { + "name": "Intel(R) HD Graphics 4600", + "deviceID": 1046, + "vendorID": 32902 + }, + { + "name": "Intel(R) HD Graphics 5000", + "deviceID": 2598, + "vendorID": 32902 + }, + { + "name": "Intel(R) HD Graphics 5000", + "deviceID": 1058, + "vendorID": 32902 + }, + { + "name": "Intel(R) Iris(TM) Graphics 5100", + "deviceID": 2594, + "vendorID": 32902 + }, + { + "name": "Intel(R) Iris(TM) Graphics 5100", + "deviceID": 2602, + "vendorID": 32902 + }, + { + "name": "Intel(R) Iris(TM) Graphics 5100", + "deviceID": 2603, + "vendorID": 32902 + }, + { + "name": "Intel(R) Iris(TM) Graphics 5100", + "deviceID": 2606, + "vendorID": 32902 + }, + { + "name": "Intel(R) Iris(TM) Pro Graphics 5200", + "deviceID": 3362, + "vendorID": 32902 + }, + { + "name": "Intel(R) Iris(TM) Pro Graphics 5200", + "deviceID": 3366, + "vendorID": 32902 + }, + { + "name": "Intel(R) Iris(TM) Pro Graphics 5200", + "deviceID": 3370, + "vendorID": 32902 + }, + { + "name": "Intel(R) Iris(TM) Pro Graphics 5200", + "deviceID": 3371, + "vendorID": 32902 + }, + { + "name": "Intel(R) Iris(TM) Pro Graphics 5200", + "deviceID": 3374, + "vendorID": 32902 + }, + { + "name": "Intel(R) Iris(TM) Pro Graphics 5200", + "deviceID": 3106, + "vendorID": 32902 + }, + { + "name": "Intel(R) HD Graphics 5300", + "deviceID": 5662, + "vendorID": 32902 + }, + { + "name": "Intel(R) HD Graphics 5500", + "deviceID": 5654, + "vendorID": 32902 + }, + { + "name": "Intel(R) HD Graphics 5600", + "deviceID": 5650, + "vendorID": 32902 + }, + { + "name": "Intel(R) HD Graphics 6000", + "deviceID": 5670, + "vendorID": 32902 + }, + { + "name": "Intel(R) Iris(TM) Graphics 6100", + "deviceID": 5675, + "vendorID": 32902 + }, + { + "name": "Intel(R) Iris(TM) Pro Graphics 6200", + "deviceID": 5666, + "vendorID": 32902 + }, + { + "name": "Intel(R) Iris(TM) Pro Graphics P6300", + "deviceID": 5674, + "vendorID": 32902 + }, + { + "name": "Intel(R) HD Graphics 510", + "deviceID": 6402, + "vendorID": 32902 + }, + { + "name": "Intel(R) HD Graphics 510", + "deviceID": 6406, + "vendorID": 32902 + }, + { + "name": "Intel(R) HD Graphics 510", + "deviceID": 6411, + "vendorID": 32902 + }, + { + "name": "Intel(R) HD Graphics 515", + "deviceID": 6430, + "vendorID": 32902 + }, + { + "name": "Intel(R) HD Graphics 520", + "deviceID": 6422, + "vendorID": 32902 + }, + { + "name": "Intel(R) HD Graphics 520", + "deviceID": 6433, + "vendorID": 32902 + }, + { + "name": "Intel(R) HD Graphics 530", + "deviceID": 6418, + "vendorID": 32902 + }, + { + "name": "Intel(R) HD Graphics 530", + "deviceID": 6427, + "vendorID": 32902 + }, + { + "name": "Intel(R) HD Graphics P530", + "deviceID": 6429, + "vendorID": 32902 + }, + { + "name": "Intel(R) Iris(TM) Graphics 540", + "deviceID": 6438, + "vendorID": 32902 + }, + { + "name": "Intel(R) Iris(TM) Graphics 550", + "deviceID": 6439, + "vendorID": 32902 + }, + { + "name": "Intel(R) Iris(TM) Graphics 555", + "deviceID": 6443, + "vendorID": 32902 + }, + { + "name": "Intel(R) Iris(TM) Graphics P555", + "deviceID": 6445, + "vendorID": 32902 + }, + { + "name": "Intel(R) Iris(TM) Pro Graphics 580", + "deviceID": 6450, + "vendorID": 32902 + }, + { + "name": "Intel(R) Iris(TM) Pro Graphics 580", + "deviceID": 6459, + "vendorID": 32902 + }, + { + "name": "Intel(R) Iris(TM) Pro Graphics P580", + "deviceID": 6458, + "vendorID": 32902 + }, + { + "name": "Intel(R) Iris(TM) Pro Graphics P580", + "deviceID": 6461, + "vendorID": 32902 + }, + { + "name": "Intel(R) UHD Graphics 617", + "deviceID": 34752, + "vendorID": 32902 + }, + { + "name": "Intel(R) UHD Graphics 620", + "deviceID": 16032, + "vendorID": 32902 + }, + { + "name": "Intel(R) HD Graphics 615", + "deviceID": 22814, + "vendorID": 32902 + }, + { + "name": "Intel(R) HD Graphics 620", + "deviceID": 22806, + "vendorID": 32902 + }, + { + "name": "Intel(R) HD Graphics 630", + "deviceID": 22802, + "vendorID": 32902 + }, + { + "name": "Intel(R) HD Graphics 630", + "deviceID": 22811, + "vendorID": 32902 + }, + { + "name": "Intel(R) UHD Graphics 630", + "deviceID": 16027, + "vendorID": 32902 + }, + { + "name": "Intel(R) UHD Graphics 630", + "deviceID": 16017, + "vendorID": 32902 + } +] diff --git a/app/src/main/assets/metadata/startmenu.json b/app/src/main/assets/metadata/startmenu.json index 8b0c988a5..5d670ce76 100644 --- a/app/src/main/assets/metadata/startmenu.json +++ b/app/src/main/assets/metadata/startmenu.json @@ -12,6 +12,26 @@ { "name" : "Wordpad", "path" : "C:/windows/system32/write.exe" + }, + { + "name" : "Graphics Test (32-bit)", + "path" : "C:/ProgramData/Microsoft/Windows/Graphics-Test-32bit.exe" + }, + { + "name" : "Graphics Test (64-bit)", + "path" : "C:/ProgramData/Microsoft/Windows/Graphics-Test-64bit.exe" + }, + { + "name" : "Input Test (32-bit)", + "path" : "C:/ProgramData/Microsoft/Windows/InputControl32.exe" + }, + { + "name" : "Input Test (64-bit)", + "path" : "C:/ProgramData/Microsoft/Windows/InputControl64.exe" + }, + { + "name" : "Ajay Prefix Pro v1.8 Downloader", + "path" : "Z:/home/.Ajay_Prefix/Ajay_Prefix_Pro_v1.8_Downloader.exe" } ] }, @@ -54,4 +74,4 @@ } ] } -] + ] diff --git a/app/src/main/assets/pulseaudio-bin/libltdl.so b/app/src/main/assets/pulseaudio-bin/libltdl.so deleted file mode 100644 index 48b910a2a..000000000 Binary files a/app/src/main/assets/pulseaudio-bin/libltdl.so and /dev/null differ diff --git a/app/src/main/assets/pulseaudio-bin/libpulse.so b/app/src/main/assets/pulseaudio-bin/libpulse.so deleted file mode 100644 index bc8b723e0..000000000 Binary files a/app/src/main/assets/pulseaudio-bin/libpulse.so and /dev/null differ diff --git a/app/src/main/assets/pulseaudio-bin/libpulseaudio.so b/app/src/main/assets/pulseaudio-bin/libpulseaudio.so deleted file mode 100644 index 59a044255..000000000 Binary files a/app/src/main/assets/pulseaudio-bin/libpulseaudio.so and /dev/null differ diff --git a/app/src/main/assets/pulseaudio-bin/libpulsecommon-13.0.so b/app/src/main/assets/pulseaudio-bin/libpulsecommon-13.0.so deleted file mode 100644 index ce2e949f5..000000000 Binary files a/app/src/main/assets/pulseaudio-bin/libpulsecommon-13.0.so and /dev/null differ diff --git a/app/src/main/assets/pulseaudio-bin/libpulsecore-13.0.so b/app/src/main/assets/pulseaudio-bin/libpulsecore-13.0.so deleted file mode 100644 index 031ff579f..000000000 Binary files a/app/src/main/assets/pulseaudio-bin/libpulsecore-13.0.so and /dev/null differ diff --git a/app/src/main/assets/pulseaudio-bin/libsndfile.so b/app/src/main/assets/pulseaudio-bin/libsndfile.so deleted file mode 100644 index dcf49b7fa..000000000 Binary files a/app/src/main/assets/pulseaudio-bin/libsndfile.so and /dev/null differ diff --git a/app/src/main/assets/pulseaudio.tzst b/app/src/main/assets/pulseaudio.tzst index 076a47a3e..7cdea4405 100644 Binary files a/app/src/main/assets/pulseaudio.tzst and b/app/src/main/assets/pulseaudio.tzst differ diff --git a/app/src/main/assets/retro/GLideN64.custom.ini b/app/src/main/assets/retro/GLideN64.custom.ini new file mode 100644 index 000000000..842d2d54b --- /dev/null +++ b/app/src/main/assets/retro/GLideN64.custom.ini @@ -0,0 +1,360 @@ +; Custom game settings +[General] +version=13 + +[TWINE] +Good_Name=007 - The World Is Not Enough (E)(U) +frameBufferEmulation\N64DepthCompare=1 + +[40%20WINKS] +Good_Name=40 Winks (E) (M3) (Prototype) +graphics2D\enableNativeResTexrects=1 + +[BAKU-BOMBERMAN] +Good_Name=Baku Bomberman (J) [!] +generalEmulation\enableLegacyBlending=0 + +[BIOFREAKS] +Good_Name=Bio F.R.E.A.K.S. (E)(U) +frameBufferEmulation\copyToRDRAM=1 + +[BIOHAZARD%20II] +Good_Name=Biohazard 2 (J) +frameBufferEmulation\copyFromRDRAM=1 +frameBufferEmulation\copyToRDRAM=0 +frameBufferEmulation\copyDepthToRDRAM=0 + +[BOMBERMAN64E] +Good_Name=Bomberman 64 (E) [!] +generalEmulation\enableLegacyBlending=0 + +[BOMBERMAN64U] +Good_Name=Bomberman 64 (U) [!] +generalEmulation\enableLegacyBlending=0 + +[362D06B6] +Good_Name=Densha de Go! 64 (J) +generalEmulation\enableLegacyBlending=0 + +[68D128AE] +Good_Name=Densha de Go! 64 (J) (Localization Patch v1.01) +generalEmulation\enableLegacyBlending=0 + +[52150A67] +Good_Name=Bokujou Monogatari 2 (J) +frameBufferEmulation\N64DepthCompare=1 + +[67000C2B] +Good_Name=Eikou no Saint Andrews (J) +frameBufferEmulation\forceDepthBufferClear=1 + +[CAL%20SPEED] +Good_Name=California Speed (U) +frameBufferEmulation\bufferSwapMode=1 + +[CASTLEVANIA2] +Good_Name=Castlevania - Legacy Of Darkness (E)(U) +frameBufferEmulation\copyToRDRAM=1 + +[DMPJ] +Good_Name=Mario Artist Paint Studio (J) (64DD) +frameBufferEmulation\copyAuxToRDRAM=1 +frameBufferEmulation\copyFromRDRAM=1 +frameBufferEmulation\nativeResFactor=1 +generalEmulation\rdramImageDitheringMode=0 + +[DMTJ] +Good_Name=Mario Artist Talent Studio (J) (64DD) +frameBufferEmulation\copyAuxToRDRAM=1 + +[DINO%20PLANET] +Good_Name=Dinosaur Planet (Dec 2000 Beta) +frameBufferEmulation\copyToRDRAM=1 +frameBufferEmulation\copyAuxToRDRAM=1 + +[DONKEY%20KONG%2064] +Good_Name=Donkey Kong 64 (E)(J)(U) +frameBufferEmulation\copyDepthToRDRAM=0 +frameBufferEmulation\N64DepthCompare=0 + +[DR.MARIO%2064] +Good_Name=Dr. Mario 64 (U) +frameBufferEmulation\fbInfoDisabled=0 +frameBufferEmulation\copyFromRDRAM=1 +frameBufferEmulation\copyToRDRAM=0 + +[EXTREME_G] +Good_Name=Extreme-G (E) +frameBufferEmulation\N64DepthCompare=1 + +[EXTREME-G] +Good_Name=Extreme-G (J) +frameBufferEmulation\N64DepthCompare=1 + +[EXTREMEG] +Good_Name=Extreme-G (U) +frameBufferEmulation\N64DepthCompare=1 + +[EXTREME%20G%202] +Good_Name=Extreme-G XG2 (E) (U) +frameBufferEmulation\N64DepthCompare=1 + +[208E05CD] +Good_Name=Extreme-G XG2 (J) +frameBufferEmulation\N64DepthCompare=1 + +[F1%20POLE%20POSITION%2064] +Good_Name=F-1 Pole Position 64 (E)(U) +frameBufferEmulation\copyToRDRAM=1 + +[FLAPPYBIRD64] +Good_Name=FlappyBird64 +frameBufferEmulation\fbInfoDisabled=0 +frameBufferEmulation\copyFromRDRAM=1 +frameBufferEmulation\copyToRDRAM=0 + +[GOEMON2%20DERODERO] +Good_Name=Ganbare Goemon - Dero Dero Douchuu Obake Tenkomori (J) +graphics2D\enableNativeResTexrects=1 + +[GOEMONS%20GREAT%20ADV] +Good_Name=Goemons Great Adventure (U) +graphics2D\enableNativeResTexrects=1 + +[HARVESTMOON64] +Good_Name=Harvest Moon 64 (U) +frameBufferEmulation\N64DepthCompare=1 + +[5CFA0A2E] +Good_Name=Heiwa Pachinko World 64 (J) +frameBufferEmulation\copyToRDRAM=1 + +[HEXEN] +Good_Name=Hexen (E)(F)(G)(J)(U) +frameBufferEmulation\copyToRDRAM=1 + +[HUMAN%20GRAND%20PRIX] +Good_Name=Human Grand Prix - New Generation (J) +frameBufferEmulation\copyToRDRAM=1 + +[I%20S%20S%2064] +Good_Name=International Superstar Soccer 64 (E) (U) +frameBufferEmulation\N64DepthCompare=1 + +[JET%20FORCE%20GEMINI] +Good_Name=Jet Force Gemini (E)(U) +frameBufferEmulation\fbInfoDisabled=0 +frameBufferEmulation\copyAuxToRDRAM=1 +frameBufferEmulation\copyToRDRAM=0 + +[J%20F%20G%20DISPLAY] +Good_Name=Jet Force Gemini Kiosk Demo (U) +frameBufferEmulation\fbInfoDisabled=0 +frameBufferEmulation\copyAuxToRDRAM=1 +frameBufferEmulation\copyToRDRAM=0 + +[J%20WORLD%20SOCCER3] +Good_Name=Jikkyou World Soccer 3 (J) +frameBufferEmulation\N64DepthCompare=1 + +[301E07CC] +Good_Name=Mahjong Master (J) +frameBufferEmulation\N64DepthCompare=1 + +[DMGJ] +Good_Name=Mario Artist Polygon Studio (J) +frameBufferEmulation\copyAuxToRDRAM=1 + +[KEN%20GRIFFEY%20SLUGFEST] +Good_Name=Ken Griffey Jr.'s Slugfest +frameBufferEmulation\fbInfoDisabled=0 + +[KIRBY64] +Good_Name=Kirby 64 - The Crystal Shards (E)(J)(U) +graphics2D\enableNativeResTexrects=1 + +[MARIOGOLF64] +Good_Name=Mario Golf (E)(J)(U) +frameBufferEmulation\copyDepthToRDRAM=0 + +[MARIOKART64] +Good_Name=Mario Kart 64 (E)(J)(U) +graphics2D\enableNativeResTexrects=1 +graphics2D\enableTexCoordBounds=1 +frameBufferEmulation\copyToRDRAM=2 + +[MARIO%20STORY] +Good_Name=Mario Story (J) +frameBufferEmulation\copyToRDRAM=1 + +[MEGA%20MAN%2064] +Good_Name=Mega Man 64 (U) +graphics2D\correctTexrectCoords=2 + +[MEGAMAN%2064] +Good_Name=Mega Man 64 (Proto) +graphics2D\correctTexrectCoords=2 + +[MLB%20FEATURING%20K%20G%20JR] +Good_Name=Major League Baseball Featuring Ken Griffey Jr. +frameBufferEmulation\fbInfoDisabled=0 + +[MYSTICAL%20NINJA2%20SG] +Good_Name=Mystical Ninja 2 Starring Goemon (E) +graphics2D\enableNativeResTexrects=1 + +[NASCAR%202000] +Good_Name=NASCAR 2000 (U) +frameBufferEmulation\copyToRDRAM=1 + +[NASCAR%2099] +Good_Name=NASCAR 99 (U) +frameBufferEmulation\copyToRDRAM=1 + +[NUD-DMPJ-JPN_convert] +Good_Name=Mario Paint Studio (cart hack) +frameBufferEmulation\copyFromRDRAM=1 + +[NUD-DMTJ-JPN_convert] +Good_Name=Mario Artist Talent Studio (cart hack) +frameBufferEmulation\copyAuxToRDRAM=1 + +[OGREBATTLE64] +Good_Name=Ogre Battle 64 - Person of Lordly Caliber (U) +graphics2D\enableTexCoordBounds=1 + +[OLYMPIC%20HOCKEY] +Good_Name=Olympic Hockey Nagano '98 (E)(J)(U) +frameBufferEmulation\bufferSwapMode=1 + +[PAPER%20MARIO] +Good_Name=Paper Mario (E)(U) +frameBufferEmulation\copyToRDRAM=1 +graphics2D\enableTexCoordBounds=1 + +[PENNY%20RACERS] +Good_Name=Penny Racers (E)(U) +frameBufferEmulation\copyToRDRAM=0 + +[PERFECT%20STRIKER] +Good_Name=Jikkyou J.League Perfect Striker (J) +frameBufferEmulation\N64DepthCompare=1 + +[POKEMON%20SNAP] +Good_Name=Pokemon Snap (U) +generalEmulation\rdramImageDitheringMode=1 +frameBufferEmulation\copyAuxToRDRAM=1 +frameBufferEmulation\copyToRDRAM=1 +frameBufferEmulation\fbInfoDisabled=0 + +[POKEMON%20STADIUM] +Good_Name=Pokemon Stadium (U) +frameBufferEmulation\copyDepthToRDRAM=0 + +[POKEMON%20STADIUM%202] +Good_Name=Pokemon Stadium 2 (E)(F)(G)(I)(J)(S)(U) +frameBufferEmulation\copyToRDRAM=0 +frameBufferEmulation\copyDepthToRDRAM=0 + +[POKEMON%20STADIUM%20G%26S] +Good_Name=Pokemon Stadium Kin Gin (J) +frameBufferEmulation\copyToRDRAM=0 +frameBufferEmulation\copyDepthToRDRAM=0 + +[PUZZLE%20LEAGUE%20N64] +Good_Name=Pokemon Puzzle League (E)(F)(G)(U) +texture\enableHalosRemoval=1 + +[RAT%20ATTACK] +Good_Name=Rat Attack +frameBufferEmulation\fbInfoDisabled=0 + +[RESIDENT%20EVIL%20II] +Good_Name=Resident Evil 2 (E)(U) +frameBufferEmulation\copyFromRDRAM=1 +frameBufferEmulation\copyToRDRAM=0 +frameBufferEmulation\copyDepthToRDRAM=0 + +[ROCKMAN%20DASH] +Good_Name=Rockman Dash - Hagane no Boukenshin (J) +graphics2D\correctTexrectCoords=2 + +[RUSH%202] +Good_Name=Rush 2 - Extreme Racing USA (E)(U) +frameBufferEmulation\bufferSwapMode=1 +graphics2D\correctTexrectCoords=2 + +[SAN%20FRANCISCO%20RUSH] +Good_Name=San Francisco Rush Extreme Racing (U) +frameBufferEmulation\bufferSwapMode=1 +graphics2D\enableNativeResTexrects=2 + +[S.F.RUSH] +Good_Name=San Francisco Rush Extreme Racing (E) +frameBufferEmulation\bufferSwapMode=1 +graphics2D\enableNativeResTexrects=2 + +[S.F.%20RUSH] +Good_Name=San Francisco Rush Extreme Racing (U) +frameBufferEmulation\bufferSwapMode=1 +graphics2D\enableNativeResTexrects=2 + +[SHADOWMAN] +Good_Name=Shadow Man (B)(E)(F)(G)(U) +frameBufferEmulation\copyDepthToRDRAM=0 + +[SPACE%20INVADERS] +Good_Name=Space Invaders (U) +frameBufferEmulation\copyToRDRAM=0 + +[SILICON%20VALLEY] +Good_Name=Space Station Silicon Valley (U) +frameBufferEmulation\copyToRDRAM=1 + +[STAR%20TWINS] +Good_Name=Star Twins (J) +frameBufferEmulation\fbInfoDisabled=0 +frameBufferEmulation\copyAuxToRDRAM=1 +frameBufferEmulation\copyToRDRAM=0 + +[TEST] +Good_Name=Mario Artist Paint Studio (J) (1999-02-11 Prototype) (64DD) +frameBufferEmulation\copyAuxToRDRAM=1 +frameBufferEmulation\copyFromRDRAM=1 +generalEmulation\rdramImageDitheringMode=0 + +[TETRISPHERE] +Good_Name=Tetrisphere (E)(U) +graphics2D\correctTexrectCoords=2 + +[TIGGER%27S%20HONEY%20HUNT] +Good_Name=Tiggers Honey Hunt (E)(U) +frameBufferEmulation\N64DepthCompare=1 + +[TONIC%20TROUBLE] +Good_Name=Tonic Trouble (E)(U) +frameBufferEmulation\copyToRDRAM=1 + +[TG%20RALLY%202] +Good_Name=TG Rally 2 (E) +frameBufferEmulation\copyToRDRAM=1 + +[TOP%20GEAR%20RALLY%202] +Good_Name=Top Gear Rally 2 (E)(J)(U) +frameBufferEmulation\copyToRDRAM=1 + +[TUROK_DINOSAUR_HUNTE] +Good_Name=Turok - Dinosaur Hunter (E)(G)(U)(J) +frameBufferEmulation\copyDepthToRDRAM=1 + +[WAVE%20RACE%2064] +Good_Name=Wave Race 64 (E)(U)(J) +frameBufferEmulation\copyToRDRAM=1 + +[W.G.%203DHOCKEY] +Good_Name=Wayne Gretzky's 3D Hockey (E)(U) +frameBufferEmulation\bufferSwapMode=1 + +[WGHOCKEY] +Good_Name=Wayne Gretzky's 3D Hockey (J) +frameBufferEmulation\bufferSwapMode=1 diff --git a/app/src/main/assets/wincomponents/wincomponents.json b/app/src/main/assets/wincomponents/wincomponents.json index 4f75ed90b..0ac7ede0f 100644 --- a/app/src/main/assets/wincomponents/wincomponents.json +++ b/app/src/main/assets/wincomponents/wincomponents.json @@ -4,6 +4,7 @@ "directmusic" : ["dmband", "dmcompos", "dmime", "dmloader", "dmscript", "dmstyle", "dmsynth", "dmusic", "dmusic32", "dswave"], "directshow" : ["amstream", "qasf", "qcap", "qdvd", "qedit", "quartz"], "directplay" : ["dplaysvr.exe", "dplayx", "dpmodemx", "dpnet", "dpnhpast", "dpnhupnp", "dpnsvr.exe", "dpwsockx"], - "xaudio" : ["x3daudio1_0", "x3daudio1_1", "x3daudio1_2", "x3daudio1_3", "x3daudio1_4", "x3daudio1_5", "x3daudio1_6", "x3daudio1_7", "xactengine2_0", "xactengine2_1", "xactengine2_2", "xactengine2_3", "xactengine2_4", "xactengine2_5", "xactengine2_6", "xactengine2_7", "xactengine2_8", "xactengine2_9", "xactengine2_10", "xactengine3_0", "xactengine3_1", "xactengine3_2", "xactengine3_3", "xactengine3_4", "xactengine3_5", "xactengine3_6", "xactengine3_7", "xapofx1_0", "xapofx1_1", "xapofx1_2", "xapofx1_3", "xapofx1_4", "xapofx1_5", "xaudio2_0", "xaudio2_1", "xaudio2_2", "xaudio2_3", "xaudio2_4", "xaudio2_5", "xaudio2_6", "xaudio2_7"], + "xaudio" : ["x3daudio1_0", "x3daudio1_1", "x3daudio1_2", "x3daudio1_3", "x3daudio1_4", "x3daudio1_5", "x3daudio1_6", "x3daudio1_7", "xactengine2_0", "xactengine2_1", "xactengine2_2", "xactengine2_3", "xactengine2_4", "xactengine2_5", "xactengine2_6", "xactengine2_7", "xactengine2_8", "xactengine2_9", "xactengine2_10", "xactengine3_0", "xactengine3_1", "xactengine3_2", "xactengine3_3", "xactengine3_4", "xactengine3_5", "xactengine3_6", "xactengine3_7", "xapofx1_0", "xapofx1_1", "xapofx1_2", "xapofx1_3", "xapofx1_4", "xapofx1_5", "xaudio2_0", "xaudio2_1", "xaudio2_2", "xaudio2_3", "xaudio2_4", "xaudio2_5", "xaudio2_6", "xaudio2_7", "xaudio2_8", "xaudio2_9"], + "dinput8" : ["dinput8"], "vcrun2010" : ["msvcp100", "msvcr100", "vcomp100", "atl100"] } diff --git a/app/src/main/assets/wincomponents/xaudio.tzst b/app/src/main/assets/wincomponents/xaudio.tzst index 7da1a4d4e..59c8348c2 100644 Binary files a/app/src/main/assets/wincomponents/xaudio.tzst and b/app/src/main/assets/wincomponents/xaudio.tzst differ diff --git a/app/src/main/assets/winnative/Graphics-Test-32bit.exe b/app/src/main/assets/winnative/Graphics-Test-32bit.exe new file mode 100644 index 000000000..d708895b3 Binary files /dev/null and b/app/src/main/assets/winnative/Graphics-Test-32bit.exe differ diff --git a/app/src/main/assets/winnative/Graphics-Test-64bit.exe b/app/src/main/assets/winnative/Graphics-Test-64bit.exe new file mode 100644 index 000000000..e513db7f1 Binary files /dev/null and b/app/src/main/assets/winnative/Graphics-Test-64bit.exe differ diff --git a/app/src/main/assets/winnative/InputControl32.exe b/app/src/main/assets/winnative/InputControl32.exe new file mode 100644 index 000000000..25521c653 Binary files /dev/null and b/app/src/main/assets/winnative/InputControl32.exe differ diff --git a/app/src/main/assets/winnative/InputControl64.exe b/app/src/main/assets/winnative/InputControl64.exe new file mode 100644 index 000000000..eef4571fb Binary files /dev/null and b/app/src/main/assets/winnative/InputControl64.exe differ diff --git a/app/src/main/assets/winnative/refactorsize.exe b/app/src/main/assets/winnative/refactorsize.exe new file mode 100755 index 000000000..b61403caa Binary files /dev/null and b/app/src/main/assets/winnative/refactorsize.exe differ diff --git a/app/src/main/assets/wnsteam/bionic/service_current_versions.vdf b/app/src/main/assets/wnsteam/bionic/service_current_versions.vdf new file mode 100644 index 000000000..109067070 --- /dev/null +++ b/app/src/main/assets/wnsteam/bionic/service_current_versions.vdf @@ -0,0 +1,36 @@ +"SteamService" +{ + "version" "10520955" + "SteamService.dll" + { + "version" "10520955" + } + "SteamService.exe" + { + "version" "10520955" + } + "drivers.exe" + { + "version" "10001752" + } + "secure_desktop_capture.exe" + { + "version" "10520955" + } + "steamxboxutil.exe" + { + "version" "10255379" + } + "steamxboxutil64.exe" + { + "version" "10520955" + } +} +"kvsign2" +{ + "SteamService" "cc28bcee25917d6bc6f34460809b19f8f401a0266d3acd745dd37923d2979d44fe8f47303e2f256919ddc2c63d3f4e0e5681366c8ef308953e4b62d29f46b903" +} +"kvsignatures" +{ + "SteamService" "a0d76255f1bd483024cf068048dc59856f0a0ca3aa41f82091caa02ae6d71d33e0e1a8fa8f096f2a19394f9139e5c3bd85fdfd1fe0e9591390603db8c86d35818284e708a0bf415a319fa8b4bae46b27957cc7e7b2dbc13505d435bc8e2c6af98f1c63b085f77d142e39649f19d14dd2609788c301c6c270fafa0b0d2cdd6e94" +} diff --git a/app/src/main/assets/wnsteam/bionic/service_minimum_versions.vdf b/app/src/main/assets/wnsteam/bionic/service_minimum_versions.vdf new file mode 100644 index 000000000..8df37f03a --- /dev/null +++ b/app/src/main/assets/wnsteam/bionic/service_minimum_versions.vdf @@ -0,0 +1,28 @@ +"SteamService" +{ + "version" "6005235" + "drivers.exe" + { + "version" "6005235" + } + "secure_desktop_capture.exe" + { + "version" "5788668" + } + "SteamService.dll" + { + "version" "6002767" + } + "SteamService.exe" + { + "version" "6002767" + } +} +"kvsign2" +{ + "SteamService" "843dcc2751cf6678cba9cd575d76295275a4ba4dd78c6162d5c2570d59df496ef1c716c458ae8788acc2e18d56db6c61a5e32b01a73f45e584d399f1e7354b00" +} +"kvsignatures" +{ + "SteamService" "5c221556cd87c9dc8687ae253e48e5d98b7526596cf74be8e3b9513fe11d7d8278fdefd57a4f9be4c0530c64af92c41e7da09af2f1845c7ac2c84595125007fba34d66861d3ad725e1527ad8a21d8ffd31fc351b30d2cdabf44993d7d5b9619dabf43dfb81f61d206fe70345e6359f3b2fceed7ea17fc821feda624dc7bef83d" +} diff --git a/app/src/main/assets/wnsteam/bionic/steam.exe b/app/src/main/assets/wnsteam/bionic/steam.exe new file mode 100755 index 000000000..1b6d8a125 Binary files /dev/null and b/app/src/main/assets/wnsteam/bionic/steam.exe differ diff --git a/app/src/main/assets/wnsteam/bionic/steamservice.dll b/app/src/main/assets/wnsteam/bionic/steamservice.dll new file mode 100644 index 000000000..54d3bfc13 Binary files /dev/null and b/app/src/main/assets/wnsteam/bionic/steamservice.dll differ diff --git a/app/src/main/assets/wnsteam/bionic/steamservice.exe b/app/src/main/assets/wnsteam/bionic/steamservice.exe new file mode 100644 index 000000000..493dda4e6 Binary files /dev/null and b/app/src/main/assets/wnsteam/bionic/steamservice.exe differ diff --git a/app/src/main/assets/wnsteam/bionic/valve-steam-x86_64.tzst b/app/src/main/assets/wnsteam/bionic/valve-steam-x86_64.tzst new file mode 100644 index 000000000..2b8f10b71 Binary files /dev/null and b/app/src/main/assets/wnsteam/bionic/valve-steam-x86_64.tzst differ diff --git a/app/src/main/assets/wnsteam/bionic/wn-steam-helper.exe b/app/src/main/assets/wnsteam/bionic/wn-steam-helper.exe new file mode 100644 index 000000000..3104beb40 Binary files /dev/null and b/app/src/main/assets/wnsteam/bionic/wn-steam-helper.exe differ diff --git a/app/src/main/assets/wnsteam/lsteamclient-arm64ec.tzst b/app/src/main/assets/wnsteam/lsteamclient-arm64ec.tzst new file mode 100644 index 000000000..d7d32bad4 Binary files /dev/null and b/app/src/main/assets/wnsteam/lsteamclient-arm64ec.tzst differ diff --git a/app/src/main/assets/wnsteam/lsteamclient-x86_64.tzst b/app/src/main/assets/wnsteam/lsteamclient-x86_64.tzst new file mode 100644 index 000000000..e4f14ceaf Binary files /dev/null and b/app/src/main/assets/wnsteam/lsteamclient-x86_64.tzst differ diff --git a/app/src/main/assets/wnsteam/steam-androidarm64.tzst b/app/src/main/assets/wnsteam/steam-androidarm64.tzst new file mode 100644 index 000000000..8244f1d95 Binary files /dev/null and b/app/src/main/assets/wnsteam/steam-androidarm64.tzst differ diff --git a/app/src/main/assets/wnsteam/steampipe/original_steam_api64.dll b/app/src/main/assets/wnsteam/steampipe/original_steam_api64.dll new file mode 100644 index 000000000..299a81e9f Binary files /dev/null and b/app/src/main/assets/wnsteam/steampipe/original_steam_api64.dll differ diff --git a/app/src/main/assets/wnsteam/steampipe/steam_api.dll b/app/src/main/assets/wnsteam/steampipe/steam_api.dll new file mode 100644 index 000000000..20d12414c Binary files /dev/null and b/app/src/main/assets/wnsteam/steampipe/steam_api.dll differ diff --git a/app/src/main/assets/wnsteam/steampipe/steam_api64.dll b/app/src/main/assets/wnsteam/steampipe/steam_api64.dll new file mode 100644 index 000000000..6444cfe7d Binary files /dev/null and b/app/src/main/assets/wnsteam/steampipe/steam_api64.dll differ diff --git a/app/src/main/assets/xvfb-arm64/Xvfb b/app/src/main/assets/xvfb-arm64/Xvfb new file mode 100755 index 000000000..94ab78421 Binary files /dev/null and b/app/src/main/assets/xvfb-arm64/Xvfb differ diff --git a/app/src/main/assets/xvfb-arm64/dbus/dbus-daemon b/app/src/main/assets/xvfb-arm64/dbus/dbus-daemon new file mode 100755 index 000000000..5805a8176 Binary files /dev/null and b/app/src/main/assets/xvfb-arm64/dbus/dbus-daemon differ diff --git a/app/src/main/assets/xvfb-arm64/dbus/dbus-uuidgen b/app/src/main/assets/xvfb-arm64/dbus/dbus-uuidgen new file mode 100755 index 000000000..c4847f382 Binary files /dev/null and b/app/src/main/assets/xvfb-arm64/dbus/dbus-uuidgen differ diff --git a/app/src/main/assets/xvfb-arm64/dbus/session.conf b/app/src/main/assets/xvfb-arm64/dbus/session.conf new file mode 100644 index 000000000..0003b0550 --- /dev/null +++ b/app/src/main/assets/xvfb-arm64/dbus/session.conf @@ -0,0 +1,30 @@ + + + session + + unix:tmpdir=/tmp + EXTERNAL + + + + + + + + 1000000000 + 250000000 + 1000000000 + 250000000 + 1000000000 + 120000 + 240000 + 150000 + 100000 + 10000 + 100000 + 10000 + 50000 + 50000 + 50000 + diff --git a/app/src/main/assets/xvfb-arm64/libXfont2.so.2.0.0 b/app/src/main/assets/xvfb-arm64/libXfont2.so.2.0.0 new file mode 100644 index 000000000..536ed81c6 Binary files /dev/null and b/app/src/main/assets/xvfb-arm64/libXfont2.so.2.0.0 differ diff --git a/app/src/main/assets/xvfb-arm64/libapparmor.so.1.6.3 b/app/src/main/assets/xvfb-arm64/libapparmor.so.1.6.3 new file mode 100644 index 000000000..f04031587 Binary files /dev/null and b/app/src/main/assets/xvfb-arm64/libapparmor.so.1.6.3 differ diff --git a/app/src/main/assets/xvfb-arm64/libfontenc.so.1.0.0 b/app/src/main/assets/xvfb-arm64/libfontenc.so.1.0.0 new file mode 100644 index 000000000..7ac3bb3b0 Binary files /dev/null and b/app/src/main/assets/xvfb-arm64/libfontenc.so.1.0.0 differ diff --git a/app/src/main/assets/xvfb-arm64/libpixman-1.so.0.40.0 b/app/src/main/assets/xvfb-arm64/libpixman-1.so.0.40.0 new file mode 100644 index 000000000..c363c57e4 Binary files /dev/null and b/app/src/main/assets/xvfb-arm64/libpixman-1.so.0.40.0 differ diff --git a/app/src/main/assets/xvfb-arm64/libunwind.so.8.0.1 b/app/src/main/assets/xvfb-arm64/libunwind.so.8.0.1 new file mode 100644 index 000000000..165af1fe9 Binary files /dev/null and b/app/src/main/assets/xvfb-arm64/libunwind.so.8.0.1 differ diff --git a/app/src/main/assets/xvfb-arm64/libwinnative-setxid-noop.so b/app/src/main/assets/xvfb-arm64/libwinnative-setxid-noop.so new file mode 100755 index 000000000..fb2c13c4f Binary files /dev/null and b/app/src/main/assets/xvfb-arm64/libwinnative-setxid-noop.so differ diff --git a/app/src/main/assets/xvfb-arm64/libwinnative-steamwebhelper-preload.so b/app/src/main/assets/xvfb-arm64/libwinnative-steamwebhelper-preload.so new file mode 100755 index 000000000..c33499171 Binary files /dev/null and b/app/src/main/assets/xvfb-arm64/libwinnative-steamwebhelper-preload.so differ diff --git a/app/src/main/assets/xvfb-arm64/libxkbfile.so.1.0.2 b/app/src/main/assets/xvfb-arm64/libxkbfile.so.1.0.2 new file mode 100644 index 000000000..89c126aa6 Binary files /dev/null and b/app/src/main/assets/xvfb-arm64/libxkbfile.so.1.0.2 differ diff --git a/app/src/main/assets/xvfb-arm64/winnative-driverquery-noop b/app/src/main/assets/xvfb-arm64/winnative-driverquery-noop new file mode 100755 index 000000000..ea9d89839 Binary files /dev/null and b/app/src/main/assets/xvfb-arm64/winnative-driverquery-noop differ diff --git a/app/src/main/assets/xvfb-arm64/winnative-steamwebhelper-wrapper b/app/src/main/assets/xvfb-arm64/winnative-steamwebhelper-wrapper new file mode 100644 index 000000000..d239635f7 Binary files /dev/null and b/app/src/main/assets/xvfb-arm64/winnative-steamwebhelper-wrapper differ diff --git a/app/src/main/assets/xvfb-arm64/xkbcomp b/app/src/main/assets/xvfb-arm64/xkbcomp new file mode 100755 index 000000000..5049cf360 Binary files /dev/null and b/app/src/main/assets/xvfb-arm64/xkbcomp differ diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt index 9d5f75846..43c8e2abb 100644 --- a/app/src/main/cpp/CMakeLists.txt +++ b/app/src/main/cpp/CMakeLists.txt @@ -2,55 +2,177 @@ cmake_minimum_required(VERSION 3.22.1) project(Winlator) +include(FetchContent) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O2 -Wno-unused-function -Wimplicit-function-declaration") +# Zstandard is used by winlator/native_content_io.cpp. Keep this dependency in +# the parent build instead of relying on the Steam client subproject to create it. +FetchContent_Declare( + zstd + GIT_REPOSITORY https://github.com/facebook/zstd.git + GIT_TAG v1.5.6 + GIT_SHALLOW TRUE + SOURCE_SUBDIR build/cmake +) +set(ZSTD_BUILD_STATIC ON CACHE BOOL "" FORCE) +set(ZSTD_BUILD_SHARED OFF CACHE BOOL "" FORCE) +set(ZSTD_BUILD_PROGRAMS OFF CACHE BOOL "" FORCE) +set(ZSTD_BUILD_TESTS OFF CACHE BOOL "" FORCE) +set(ZSTD_LEGACY_SUPPORT OFF CACHE BOOL "" FORCE) +FetchContent_MakeAvailable(zstd) + +# liblzma (xz): fast XZ decoder for .txz/.xz layer extraction. +FetchContent_Declare( + xz + GIT_REPOSITORY https://github.com/tukaani-project/xz.git + GIT_TAG v5.4.6 + GIT_SHALLOW TRUE +) +FetchContent_GetProperties(xz) +if(NOT xz_POPULATED) + FetchContent_Populate(xz) + set(_winlator_saved_bsl "${BUILD_SHARED_LIBS}") + set(BUILD_SHARED_LIBS OFF) + add_subdirectory(${xz_SOURCE_DIR} ${xz_BINARY_DIR} EXCLUDE_FROM_ALL) + set(BUILD_SHARED_LIBS "${_winlator_saved_bsl}") +endif() + add_subdirectory(patchelf) add_subdirectory(adrenotools) +add_subdirectory(wn-steam-client) +add_subdirectory(wn-steam-bootstrap) +add_subdirectory(wn-libsteamclient) + +find_package(curl REQUIRED CONFIG) + +# ---------------------------------------------------------------------------- +# SPIR-V shader compilation +# Each .glsl is compiled by glslc (shipped with the NDK) into a .spv binary, +# then converted to a C uint32_t array via bin2c.cmake. Headers are emitted +# under ${CMAKE_CURRENT_BINARY_DIR}/shaders/*.spv.h and included from vk_renderer.c. +# ---------------------------------------------------------------------------- + +# Locate glslc shipped with the NDK. ANDROID_NDK is provided by the Android Gradle plugin. +if(NOT DEFINED ANDROID_NDK) + message(FATAL_ERROR "ANDROID_NDK not defined; this project must be built via the Android Gradle plugin") +endif() + +if(CMAKE_HOST_WIN32) + set(GLSLC_HOST_TAG "windows-x86_64") + set(GLSLC_EXE_NAME "glslc.exe") +elseif(CMAKE_HOST_APPLE) + set(GLSLC_HOST_TAG "darwin-x86_64") + set(GLSLC_EXE_NAME "glslc") +else() + set(GLSLC_HOST_TAG "linux-x86_64") + set(GLSLC_EXE_NAME "glslc") +endif() + +set(GLSLC "${ANDROID_NDK}/shader-tools/${GLSLC_HOST_TAG}/${GLSLC_EXE_NAME}") +if(NOT EXISTS "${GLSLC}") + message(FATAL_ERROR "glslc not found at ${GLSLC}; check NDK installation") +endif() + +set(SHADER_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/winlator/vk/shaders") +set(SHADER_OUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/shaders") +file(MAKE_DIRECTORY "${SHADER_OUT_DIR}") + +set(BIN2C_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/winlator/vk/bin2c.cmake") + +# (input filename without extension, var name, stage) +set(SHADER_LIST + "window:vert:window_vert" + "window:frag:window_frag" + "cursor:frag:cursor_frag" + "quad:vert:quad_vert" + "blit:frag:blit_frag" + "effect_crt:frag:effect_crt_frag" + "effect_vivid:frag:effect_vivid_frag" + "effect_hdr:frag:effect_hdr_frag" + "effect_natural:frag:effect_natural_frag" + "effect_toon:frag:effect_toon_frag" + "effect_ntsc:frag:effect_ntsc_frag" + "effect_ntsc2:frag:effect_ntsc2_frag" + "effect_coloradj:frag:effect_coloradj_frag" + "effect_colorgrade:frag:effect_colorgrade_frag" + "effect_sharpen:frag:effect_sharpen_frag" + "effect_scanlines:frag:effect_scanlines_frag" + "effect_colorblind:frag:effect_colorblind_frag" + "effect_pixelate:frag:effect_pixelate_frag" + "sgsr1:frag:sgsr1_frag" +) + +set(SHADER_HEADERS "") +foreach(entry ${SHADER_LIST}) + string(REPLACE ":" ";" parts ${entry}) + list(GET parts 0 base) + list(GET parts 1 stage) + list(GET parts 2 var) + set(input "${SHADER_SRC_DIR}/${base}.${stage}") + set(spv "${SHADER_OUT_DIR}/${var}.spv") + set(hdr "${SHADER_OUT_DIR}/${var}.spv.h") + + add_custom_command( + OUTPUT "${hdr}" + COMMAND "${GLSLC}" --target-env=vulkan1.1 -O "${input}" -o "${spv}" + COMMAND "${CMAKE_COMMAND}" + -DINPUT_FILE=${spv} + -DOUTPUT_FILE=${hdr} + -DVAR_NAME=${var} + -P "${BIN2C_SCRIPT}" + DEPENDS "${input}" "${BIN2C_SCRIPT}" + COMMENT "Compiling shader ${base}.${stage} -> ${var}.spv.h" + VERBATIM + ) + list(APPEND SHADER_HEADERS "${hdr}") +endforeach() + +add_custom_target(winlator_shaders DEPENDS ${SHADER_HEADERS}) + +# ---------------------------------------------------------------------------- +# Winlator native library (X-server, AHB, Vulkan compositor, helpers) +# ---------------------------------------------------------------------------- -# Add Winlator shared library add_library(winlator SHARED winlator/drawable.c + winlator/native_content_io.cpp winlator/gpu_image.c winlator/surface_compositor.c + winlator/ring_fence.c + winlator/sync_fence.c winlator/sysvshared_memory.c winlator/xconnector_epoll.c - winlator/alsa_client.c winlator/process_lifecycle.c winlator/vulkan.c - xz/native_xz_stream.c - xz/xz_embedded/xz_crc32.c - xz/xz_embedded/xz_crc64.c - xz/xz_embedded/xz_dec_bcj.c - xz/xz_embedded/xz_dec_lzma2.c - xz/xz_embedded/xz_dec_stream.c + winlator/vk/vk_dispatch.c + winlator/vk/vk_image.c + winlator/vk/vk_renderer.c ) +add_dependencies(winlator winlator_shaders) + target_compile_options(winlator PRIVATE -Wall -Wextra) target_compile_definitions(winlator PRIVATE - XZ_USE_CRC64 - XZ_DEC_CONCATENATED - XZ_DEC_X86 - XZ_DEC_ARM - XZ_DEC_ARMTHUMB - XZ_DEC_ARM64 - XZ_DEC_RISCV - XZ_DEC_POWERPC - XZ_DEC_IA64 - XZ_DEC_SPARC + VK_USE_PLATFORM_ANDROID_KHR ) target_include_directories(winlator PRIVATE - xz/xz_embedded + ${xz_SOURCE_DIR}/src/liblzma/api + winlator/vk + ${CMAKE_CURRENT_BINARY_DIR} ) +target_compile_features(winlator PRIVATE cxx_std_17) + target_link_libraries(winlator log android jnigraphics - aaudio - EGL - GLESv2 - GLESv3 + vulkan adrenotools + libzstd_static + liblzma + curl::curl ) # Fake evdev input shim used by the pb_controller_fix controller path. @@ -59,10 +181,3 @@ add_library(fakeinput SHARED ) target_compile_options(fakeinput PRIVATE -Wall -Wextra -fvisibility=hidden) target_link_libraries(fakeinput log dl) - -# Evshim - controller support shim (loaded via LD_PRELOAD, uses dlopen for SDL2) -add_library(evshim SHARED - winlator/evshim.c -) -target_compile_options(evshim PRIVATE -Wall -Wextra -fvisibility=hidden) -target_link_libraries(evshim log dl) diff --git a/app/src/main/cpp/steamwebhelper-preload/steamwebhelper_preload.c b/app/src/main/cpp/steamwebhelper-preload/steamwebhelper_preload.c new file mode 100644 index 000000000..11c1729f7 --- /dev/null +++ b/app/src/main/cpp/steamwebhelper-preload/steamwebhelper_preload.c @@ -0,0 +1,684 @@ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef SYS_landlock_create_ruleset +#define SYS_landlock_create_ruleset 444 +#endif + +#if defined(__aarch64__) +static long raw_syscall6(long number, long a1, long a2, long a3, long a4, + long a5, long a6) { + register long x0 __asm__("x0") = a1; + register long x1 __asm__("x1") = a2; + register long x2 __asm__("x2") = a3; + register long x3 __asm__("x3") = a4; + register long x4 __asm__("x4") = a5; + register long x5 __asm__("x5") = a6; + register long x8 __asm__("x8") = number; + __asm__ volatile("svc #0" + : "+r"(x0) + : "r"(x1), "r"(x2), "r"(x3), "r"(x4), "r"(x5), "r"(x8) + : "memory", "cc"); + return x0; +} + +static long syscall_result(long rc) { + if (rc < 0 && rc >= -4095) { + errno = (int)-rc; + return -1; + } + return rc; +} +#else +#error "steamwebhelper preload raw syscall shim is only implemented for aarch64" +#endif + +struct cached_link { + char path[PATH_MAX]; + char target[PATH_MAX]; +}; + +static struct cached_link g_links[64]; +static int g_link_count; +static char g_log_path[PATH_MAX]; + +static int is_steam_singleton_path(const char *path); +static void log_msg(const char *fmt, ...); + +static int sidecar_path(const char *path, char *out, size_t out_size) { + if (!is_steam_singleton_path(path)) { + return -1; + } + if (snprintf(out, out_size, "%s.winnative-readlink-target", path) >= (int)out_size) { + return -1; + } + return 0; +} + +static void write_sidecar_link(const char *path, const char *target) { + char sidecar[PATH_MAX]; + size_t len; + int fd; + + if (target == NULL || sidecar_path(path, sidecar, sizeof(sidecar)) < 0) { + return; + } + + fd = open(sidecar, O_WRONLY | O_CREAT | O_TRUNC, 0600); + if (fd < 0) { + return; + } + + len = strlen(target); + if (write(fd, target, len) != (ssize_t)len) { + log_msg("winnative steamwebhelper preload: sidecar write failed path=%s errno=%d", + sidecar, errno); + } + close(fd); +} + +static const char *read_sidecar_link(const char *path) { + static char target[PATH_MAX]; + char sidecar[PATH_MAX]; + ssize_t len; + int fd; + + if (sidecar_path(path, sidecar, sizeof(sidecar)) < 0) { + return NULL; + } + + fd = open(sidecar, O_RDONLY); + if (fd < 0) { + return NULL; + } + len = read(fd, target, sizeof(target) - 1); + close(fd); + if (len <= 0) { + return NULL; + } + target[len] = '\0'; + return target; +} + +static int sidecar_link_exists(const char *path, char *target, size_t target_size) { + char sidecar[PATH_MAX]; + ssize_t len; + int fd; + + if (target == NULL || target_size == 0 || + sidecar_path(path, sidecar, sizeof(sidecar)) < 0) { + return 0; + } + + fd = open(sidecar, O_RDONLY); + if (fd < 0) { + return 0; + } + len = read(fd, target, target_size - 1); + close(fd); + if (len <= 0) { + return 0; + } + target[len] = '\0'; + return 1; +} + +static void init_log_path(void) { + const char *override = getenv("WINNATIVE_STEAMWEBHELPER_LOG"); + const char *home = getenv("HOME"); + + if (g_log_path[0] != '\0') { + return; + } + if (override != NULL && override[0] != '\0') { + if (snprintf(g_log_path, sizeof(g_log_path), "%s", override) >= + (int)sizeof(g_log_path)) { + g_log_path[0] = '\0'; + } + return; + } + if (home != NULL && home[0] != '\0') { + if (snprintf(g_log_path, sizeof(g_log_path), + "%s/.steam/steam/logs/steamwebhelper.log", home) >= + (int)sizeof(g_log_path)) { + g_log_path[0] = '\0'; + } + return; + } + if (snprintf(g_log_path, sizeof(g_log_path), + "/tmp/winnative-steamwebhelper.log") >= (int)sizeof(g_log_path)) { + g_log_path[0] = '\0'; + } +} + +static void log_msg(const char *fmt, ...) { + init_log_path(); + if (g_log_path[0] == '\0') { + return; + } + + int fd = open(g_log_path, O_WRONLY | O_CREAT | O_APPEND, 0600); + if (fd < 0) { + return; + } + + char line[PATH_MAX * 2]; + va_list ap; + va_start(ap, fmt); + int len = vsnprintf(line, sizeof(line), fmt, ap); + va_end(ap); + if (len > 0) { + if (len > (int)sizeof(line) - 2) { + len = (int)sizeof(line) - 2; + } + line[len++] = '\n'; + ssize_t ignored = write(fd, line, (size_t)len); + (void)ignored; + } + close(fd); +} + +static int is_steam_singleton_path(const char *path) { + if (path == NULL) { + return 0; + } + return strstr(path, "/tmp/.com.valvesoftware.Steam.") != NULL || + strstr(path, "SingletonCookie") != NULL || + strstr(path, "SingletonSocket") != NULL || + strstr(path, "SingletonLock") != NULL; +} + +static void cache_link(const char *path, const char *target) { + if (!is_steam_singleton_path(path) || target == NULL) { + return; + } + write_sidecar_link(path, target); + + for (int i = 0; i < g_link_count; i++) { + if (strcmp(g_links[i].path, path) == 0) { + snprintf(g_links[i].target, sizeof(g_links[i].target), "%s", target); + return; + } + } + + if (g_link_count >= (int)(sizeof(g_links) / sizeof(g_links[0]))) { + memmove(&g_links[0], &g_links[1], sizeof(g_links[0]) * (g_link_count - 1)); + g_link_count--; + } + + snprintf(g_links[g_link_count].path, sizeof(g_links[g_link_count].path), "%s", path); + snprintf(g_links[g_link_count].target, sizeof(g_links[g_link_count].target), "%s", target); + g_link_count++; +} + +static const char *lookup_link(const char *path) { + if (!is_steam_singleton_path(path)) { + return NULL; + } + for (int i = g_link_count - 1; i >= 0; i--) { + if (strcmp(g_links[i].path, path) == 0) { + return g_links[i].target; + } + } + return read_sidecar_link(path); +} + +static ssize_t copy_target(const char *path, const char *target, char *buf, size_t bufsiz) { + size_t len = strlen(target); + size_t out = len; + if (out > bufsiz) { + out = bufsiz; + } + if (out > 0) { + memcpy(buf, target, out); + } + log_msg("winnative steamwebhelper preload: readlink shim path=%s target=%s", path, target); + return (ssize_t)out; +} + +static void fill_symlink_stat(struct stat *st, const char *target) { + memset(st, 0, sizeof(*st)); + st->st_mode = S_IFLNK | 0777; + st->st_nlink = 1; + st->st_uid = getuid(); + st->st_gid = getgid(); + st->st_size = (off_t)strlen(target); + st->st_blksize = 4096; +} + +static int fake_lstat_if_sidecar(const char *path, struct stat *st) { + char target[PATH_MAX]; + + if (st == NULL || !sidecar_link_exists(path, target, sizeof(target))) { + return -1; + } + fill_symlink_stat(st, target); + log_msg("winnative steamwebhelper preload: lstat shim path=%s target=%s", + path, target); + errno = 0; + return 0; +} + +int symlink(const char *target, const char *linkpath) { + int rc = (int)syscall_result(raw_syscall6(SYS_symlinkat, (long)target, AT_FDCWD, + (long)linkpath, 0, 0, 0)); + if (rc == 0) { + cache_link(linkpath, target); + } else if (errno == ENOSYS && is_steam_singleton_path(linkpath)) { + cache_link(linkpath, target); + log_msg("winnative steamwebhelper preload: symlink ENOSYS shim link=%s target=%s", + linkpath, target); + errno = 0; + return 0; + } + return rc; +} + +int symlinkat(const char *target, int newdirfd, const char *linkpath) { + int rc = (int)syscall_result(raw_syscall6(SYS_symlinkat, (long)target, newdirfd, + (long)linkpath, 0, 0, 0)); + if (rc == 0) { + cache_link(linkpath, target); + } else if (errno == ENOSYS && is_steam_singleton_path(linkpath)) { + cache_link(linkpath, target); + log_msg("winnative steamwebhelper preload: symlinkat ENOSYS shim link=%s target=%s", + linkpath, target); + errno = 0; + return 0; + } + return rc; +} + +ssize_t readlink(const char *path, char *buf, size_t bufsiz) { + ssize_t rc = (ssize_t)syscall_result(raw_syscall6(SYS_readlinkat, AT_FDCWD, + (long)path, (long)buf, + (long)bufsiz, 0, 0)); + if (rc >= 0 || errno != ENOSYS) { + return rc; + } + + const char *target = lookup_link(path); + if (target == NULL) { + return rc; + } + errno = 0; + return copy_target(path, target, buf, bufsiz); +} + +ssize_t readlinkat(int dirfd, const char *path, char *buf, size_t bufsiz) { + ssize_t rc = (ssize_t)syscall_result(raw_syscall6(SYS_readlinkat, dirfd, + (long)path, (long)buf, + (long)bufsiz, 0, 0)); + if (rc >= 0 || errno != ENOSYS) { + return rc; + } + + const char *target = lookup_link(path); + if (target == NULL) { + return rc; + } + errno = 0; + return copy_target(path, target, buf, bufsiz); +} + +ssize_t __readlink_chk(const char *path, char *buf, size_t bufsiz, + size_t bufsize) { + if (bufsiz > bufsize) { + errno = ERANGE; + return -1; + } + return readlink(path, buf, bufsiz); +} + +ssize_t __readlinkat_chk(int dirfd, const char *path, char *buf, size_t bufsiz, + size_t bufsize) { + if (bufsiz > bufsize) { + errno = ERANGE; + return -1; + } + return readlinkat(dirfd, path, buf, bufsiz); +} + +int lstat(const char *path, struct stat *st) { + int rc = (int)syscall_result(raw_syscall6(SYS_newfstatat, AT_FDCWD, + (long)path, (long)st, + AT_SYMLINK_NOFOLLOW, 0, 0)); + if (rc == 0 || errno != ENOENT) { + return rc; + } + return fake_lstat_if_sidecar(path, st); +} + +int __lxstat(int ver, const char *path, struct stat *st) { + (void)ver; + return lstat(path, st); +} + +int __lxstat64(int ver, const char *path, struct stat *st) { + (void)ver; + return lstat(path, st); +} + +int access(const char *path, int mode) { + int rc = (int)syscall_result(raw_syscall6(SYS_faccessat, AT_FDCWD, + (long)path, mode, 0, 0, 0)); + if (rc == 0 || errno != ENOENT) { + return rc; + } + if ((mode & X_OK) == 0 && sidecar_link_exists(path, (char[PATH_MAX]){0}, PATH_MAX)) { + log_msg("winnative steamwebhelper preload: access shim path=%s mode=%d", + path, mode); + errno = 0; + return 0; + } + return rc; +} + +int faccessat(int dirfd, const char *path, int mode, int flags) { + int rc = (int)syscall_result(raw_syscall6(SYS_faccessat, dirfd, (long)path, + mode, flags, 0, 0)); + if (rc == 0 || errno != ENOENT || dirfd != AT_FDCWD) { + return rc; + } + if ((mode & X_OK) == 0 && sidecar_link_exists(path, (char[PATH_MAX]){0}, PATH_MAX)) { + log_msg("winnative steamwebhelper preload: faccessat shim path=%s mode=%d", + path, mode); + errno = 0; + return 0; + } + return rc; +} + +int unlink(const char *path) { + char sidecar[PATH_MAX]; + int had_sidecar = 0; + int rc = (int)syscall_result(raw_syscall6(SYS_unlinkat, AT_FDCWD, + (long)path, 0, 0, 0, 0)); + int saved_errno = errno; + + if (sidecar_path(path, sidecar, sizeof(sidecar)) == 0) { + had_sidecar = (raw_syscall6(SYS_unlinkat, AT_FDCWD, (long)sidecar, + 0, 0, 0, 0) == 0); + } + if (rc == 0) { + return 0; + } + errno = saved_errno; + if (errno == ENOENT && had_sidecar) { + log_msg("winnative steamwebhelper preload: unlink sidecar-only path=%s", + path); + errno = 0; + return 0; + } + return rc; +} + +static size_t append_literal(char *buf, size_t pos, size_t size, const char *text) { + while (text != NULL && *text != '\0' && pos + 1 < size) { + buf[pos++] = *text++; + } + return pos; +} + +static size_t append_long(char *buf, size_t pos, size_t size, long value) { + char tmp[32]; + size_t count = 0; + unsigned long n; + + if (value < 0) { + if (pos + 1 < size) { + buf[pos++] = '-'; + } + n = (unsigned long)(-value); + } else { + n = (unsigned long)value; + } + do { + tmp[count++] = (char)('0' + (n % 10)); + n /= 10; + } while (n != 0 && count < sizeof(tmp)); + while (count > 0 && pos + 1 < size) { + buf[pos++] = tmp[--count]; + } + return pos; +} + +static size_t append_hex_ulong(char *buf, size_t pos, size_t size, + unsigned long value) { + static const char digits[] = "0123456789abcdef"; + int shift; + + pos = append_literal(buf, pos, size, "0x"); + for (shift = (int)(sizeof(value) * 8) - 4; shift > 0; shift -= 4) { + if (((value >> shift) & 0xf) != 0) { + break; + } + } + for (; shift >= 0 && pos + 1 < size; shift -= 4) { + buf[pos++] = digits[(value >> shift) & 0xf]; + } + return pos; +} + +static void raw_log_signal(const char *kind, int value) { + char line[256]; + size_t pos = 0; + int fd; + + if (g_log_path[0] == '\0') { + return; + } + pos = append_literal(line, pos, sizeof(line), + "winnative steamwebhelper preload: "); + pos = append_literal(line, pos, sizeof(line), kind); + pos = append_literal(line, pos, sizeof(line), " pid="); + pos = append_long(line, pos, sizeof(line), + raw_syscall6(SYS_getpid, 0, 0, 0, 0, 0, 0)); + pos = append_literal(line, pos, sizeof(line), " tid="); + pos = append_long(line, pos, sizeof(line), + raw_syscall6(SYS_gettid, 0, 0, 0, 0, 0, 0)); + pos = append_literal(line, pos, sizeof(line), " value="); + pos = append_long(line, pos, sizeof(line), value); + if (pos + 1 < sizeof(line)) { + line[pos++] = '\n'; + } + + fd = (int)raw_syscall6(SYS_openat, AT_FDCWD, (long)g_log_path, + O_WRONLY | O_CREAT | O_APPEND, 0600, 0, 0); + if (fd >= 0) { + raw_syscall6(SYS_write, fd, (long)line, (long)pos, 0, 0, 0); + raw_syscall6(SYS_close, fd, 0, 0, 0, 0, 0); + } +} + +static void raw_log_signal_context(const char *kind, int sig, void *context) { + char line[512]; + size_t pos = 0; + int fd; + unsigned long pc = 0; + unsigned long lr = 0; + unsigned long sp = 0; + +#if defined(__aarch64__) + if (context != NULL) { + ucontext_t *uc = (ucontext_t *)context; + pc = (unsigned long)uc->uc_mcontext.pc; + lr = (unsigned long)uc->uc_mcontext.regs[30]; + sp = (unsigned long)uc->uc_mcontext.sp; + } +#endif + + if (g_log_path[0] == '\0') { + return; + } + pos = append_literal(line, pos, sizeof(line), + "winnative steamwebhelper preload: "); + pos = append_literal(line, pos, sizeof(line), kind); + pos = append_literal(line, pos, sizeof(line), " pid="); + pos = append_long(line, pos, sizeof(line), + raw_syscall6(SYS_getpid, 0, 0, 0, 0, 0, 0)); + pos = append_literal(line, pos, sizeof(line), " tid="); + pos = append_long(line, pos, sizeof(line), + raw_syscall6(SYS_gettid, 0, 0, 0, 0, 0, 0)); + pos = append_literal(line, pos, sizeof(line), " signal="); + pos = append_long(line, pos, sizeof(line), sig); + pos = append_literal(line, pos, sizeof(line), " pc="); + pos = append_hex_ulong(line, pos, sizeof(line), pc); + pos = append_literal(line, pos, sizeof(line), " lr="); + pos = append_hex_ulong(line, pos, sizeof(line), lr); + pos = append_literal(line, pos, sizeof(line), " sp="); + pos = append_hex_ulong(line, pos, sizeof(line), sp); + if (pos + 1 < sizeof(line)) { + line[pos++] = '\n'; + } + + fd = (int)raw_syscall6(SYS_openat, AT_FDCWD, (long)g_log_path, + O_WRONLY | O_CREAT | O_APPEND, 0600, 0, 0); + if (fd >= 0) { + raw_syscall6(SYS_write, fd, (long)line, (long)pos, 0, 0, 0); + raw_syscall6(SYS_close, fd, 0, 0, 0, 0, 0); + } +} + +static int should_block_crashpad_ptrace(long request) { + const char *visible = getenv("WINNATIVE_STEAM_VISIBLE_UI"); + + if (visible == NULL || strcmp(visible, "1") != 0) { + return 0; + } + + return request == PTRACE_ATTACH || request == PTRACE_SEIZE; +} + +long ptrace(enum __ptrace_request request, ...) { + va_list ap; + long pid; + long addr; + long data; + + va_start(ap, request); + pid = va_arg(ap, long); + addr = va_arg(ap, long); + data = va_arg(ap, long); + va_end(ap); + + if (should_block_crashpad_ptrace((long)request)) { + log_msg("winnative steamwebhelper preload: ptrace attach shim request=%ld pid=%ld EPERM", + (long)request, pid); + errno = EPERM; + return -1; + } + + return syscall_result(raw_syscall6(SYS_ptrace, (long)request, pid, addr, data, 0, 0)); +} + +static void crash_signal_handler(int sig, siginfo_t *info, void *context) { + (void)info; + raw_log_signal_context("caught fatal signal", sig, context); + raw_syscall6(SYS_rt_sigaction, sig, 0, 0, 8, 0, 0); + raw_syscall6(SYS_tgkill, raw_syscall6(SYS_getpid, 0, 0, 0, 0, 0, 0), + raw_syscall6(SYS_gettid, 0, 0, 0, 0, 0, 0), sig, 0, 0, 0); + raw_syscall6(SYS_exit_group, 128 + sig, 0, 0, 0, 0, 0); +} + +__attribute__((constructor)) static void install_crash_logging(void) { + struct sigaction sa; + + init_log_path(); + memset(&sa, 0, sizeof(sa)); + sa.sa_sigaction = crash_signal_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_SIGINFO | SA_RESETHAND; + sigaction(SIGABRT, &sa, NULL); + sigaction(SIGSEGV, &sa, NULL); + sigaction(SIGBUS, &sa, NULL); + sigaction(SIGILL, &sa, NULL); + sigaction(SIGTRAP, &sa, NULL); + sigaction(SIGSYS, &sa, NULL); +} + +void abort(void) { + raw_log_signal("abort called", SIGABRT); + raw_syscall6(SYS_tgkill, raw_syscall6(SYS_getpid, 0, 0, 0, 0, 0, 0), + raw_syscall6(SYS_gettid, 0, 0, 0, 0, 0, 0), SIGABRT, 0, 0, 0); + raw_syscall6(SYS_exit_group, 134, 0, 0, 0, 0, 0); + __builtin_unreachable(); +} + +long syscall(long number, ...) { + va_list ap; + long a1; + long a2; + long a3; + long a4; + long a5; + long a6; + long rc; + + va_start(ap, number); + a1 = va_arg(ap, long); + a2 = va_arg(ap, long); + a3 = va_arg(ap, long); + a4 = va_arg(ap, long); + a5 = va_arg(ap, long); + a6 = va_arg(ap, long); + va_end(ap); + + if (number == SYS_landlock_create_ruleset) { + log_msg("winnative steamwebhelper preload: landlock_create_ruleset shim flags=%ld ENOSYS->EOPNOTSUPP", + a3); + errno = EOPNOTSUPP; + return -1; + } + + if (number == SYS_ptrace && should_block_crashpad_ptrace(a1)) { + log_msg("winnative steamwebhelper preload: syscall ptrace attach shim request=%ld pid=%ld EPERM", + a1, a2); + errno = EPERM; + return -1; + } + + rc = syscall_result(raw_syscall6(number, a1, a2, a3, a4, a5, a6)); + if (rc >= 0 || errno != ENOSYS) { + return rc; + } + + if (number == SYS_symlinkat && is_steam_singleton_path((const char *)a3)) { + cache_link((const char *)a3, (const char *)a1); + log_msg("winnative steamwebhelper preload: syscall symlinkat ENOSYS shim link=%s target=%s", + (const char *)a3, (const char *)a1); + errno = 0; + return 0; + } + + if (number == SYS_readlinkat) { + const char *target = lookup_link((const char *)a2); + if (target != NULL) { + errno = 0; + return copy_target((const char *)a2, target, (char *)a3, (size_t)a4); + } + } + + if (number == SYS_unlinkat && is_steam_singleton_path((const char *)a2)) { + char sidecar[PATH_MAX]; + if (sidecar_path((const char *)a2, sidecar, sizeof(sidecar)) == 0) { + raw_syscall6(SYS_unlinkat, a1, (long)sidecar, a3, 0, 0, 0); + } + } + + return rc; +} diff --git a/app/src/main/cpp/virglrenderer/CMakeLists.txt b/app/src/main/cpp/virglrenderer/CMakeLists.txt deleted file mode 100644 index 6c160759c..000000000 --- a/app/src/main/cpp/virglrenderer/CMakeLists.txt +++ /dev/null @@ -1,55 +0,0 @@ -cmake_minimum_required(VERSION 3.22.1) - -project(VirGLRenderer) - -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O2 -Wno-unused-function -Wimplicit-function-declaration") - -include_directories(src - src/gallium/include - src/gallium/auxiliary - src/gallium/auxiliary/util - server) - -add_library(virglrenderer SHARED - src/iov.c - src/vrend_blitter.c - src/vrend_decode.c - src/vrend_formats.c - src/vrend_object.c - src/vrend_renderer.c - src/vrend_shader.c - server/virgl_server.c - server/virgl_server_shm.c - server/virgl_server_renderer.c - src/gallium/auxiliary/util/u_format.c - src/gallium/auxiliary/util/u_format_table.c - src/gallium/auxiliary/util/u_texture.c - src/gallium/auxiliary/util/u_hash_table.c - src/gallium/auxiliary/util/u_debug.c - src/gallium/auxiliary/util/u_cpu_detect.c - src/gallium/auxiliary/util/u_bitmask.c - src/gallium/auxiliary/util/u_surface.c - src/gallium/auxiliary/util/u_math.c - src/gallium/auxiliary/util/u_debug_describe.c - src/gallium/auxiliary/cso_cache/cso_cache.c - src/gallium/auxiliary/cso_cache/cso_hash.c - src/gallium/auxiliary/tgsi/tgsi_dump.c - src/gallium/auxiliary/tgsi/tgsi_ureg.c - src/gallium/auxiliary/tgsi/tgsi_build.c - src/gallium/auxiliary/tgsi/tgsi_scan.c - src/gallium/auxiliary/tgsi/tgsi_info.c - src/gallium/auxiliary/tgsi/tgsi_parse.c - src/gallium/auxiliary/tgsi/tgsi_text.c - src/gallium/auxiliary/tgsi/tgsi_strings.c - src/gallium/auxiliary/tgsi/tgsi_sanity.c - src/gallium/auxiliary/tgsi/tgsi_iterate.c - src/gallium/auxiliary/tgsi/tgsi_util.c - src/gallium/auxiliary/tgsi/tgsi_transform.c - src/gallium/auxiliary/os/os_misc.c) - -target_link_libraries(virglrenderer - log - android - EGL - GLESv2 - GLESv3) \ No newline at end of file diff --git a/app/src/main/cpp/virglrenderer/server/virgl_server.c b/app/src/main/cpp/virglrenderer/server/virgl_server.c deleted file mode 100644 index 40526a4af..000000000 --- a/app/src/main/cpp/virglrenderer/server/virgl_server.c +++ /dev/null @@ -1,152 +0,0 @@ -/************************************************************************** - * - * Copyright (C) 2015 Red Hat Inc. - * - * 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. - * - **************************************************************************/ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "util/u_memory.h" -#include "virgl_server.h" -#include "virgl_server_protocol.h" - -struct jni_info jni_info; - -static struct virgl_client *virgl_server_handle_new_connection(int fd) { - struct virgl_client *client = calloc(1, sizeof(struct virgl_client)); - client->fd = fd; - client->initialized = false; - return client; -} - -static void virgl_server_kill_connection(struct virgl_client *client) { - (*jni_info.env) - ->CallVoidMethod(jni_info.env, jni_info.obj, jni_info.kill_connection, - client->fd); -} - -static void virgl_server_destroy_client(struct virgl_client **client) { - virgl_server_destroy_renderer(*client); - - free(*client); - *client = NULL; -} - -static void virgl_server_handle_request(struct virgl_client *client) { - int ret; - uint32_t header[2]; - - ret = virgl_block_read(client->fd, &header, sizeof(header)); - if (ret < 0 || (size_t)ret < sizeof(header)) { - virgl_server_kill_connection(client); - return; - } - - if (!client->initialized) { - if (header[1] != VCMD_CREATE_RENDERER) { - virgl_server_kill_connection(client); - return; - } - - ret = virgl_server_create_renderer(client, header[0]); - client->initialized = true; - } - - vrend_renderer_check_fences(client); - - switch (header[1]) { - case VCMD_GET_CAPS: - ret = virgl_server_send_caps(client, header[0]); - break; - case VCMD_RESOURCE_CREATE: - ret = virgl_server_resource_create(client, header[0]); - break; - case VCMD_RESOURCE_DESTROY: - ret = virgl_server_resource_destroy(client, header[0]); - break; - case VCMD_TRANSFER_GET: - ret = virgl_server_transfer_get(client, header[0]); - break; - case VCMD_TRANSFER_PUT: - ret = virgl_server_transfer_put(client, header[0]); - break; - case VCMD_SUBMIT_CMD: - ret = virgl_server_submit_cmd(client, header[0]); - break; - case VCMD_RESOURCE_BUSY_WAIT: - ret = virgl_server_resource_busy_wait(client, header[0]); - break; - case VCMD_FLUSH_FRONTBUFFER: - ret = virgl_server_flush_frontbuffer(client, header[0]); - break; - } - - if (ret < 0) - virgl_server_kill_connection(client); -} - -JNIEXPORT jlong JNICALL -Java_com_winlator_xenvironment_components_VirGLRendererComponent_handleNewConnection( - JNIEnv *env, jobject obj, jint fd) { - jni_info.env = env; - jni_info.obj = obj; - - jclass cls = (*env)->GetObjectClass(env, obj); - jni_info.kill_connection = - (*env)->GetMethodID(env, cls, "killConnection", "(I)V"); - jni_info.get_shared_egl_context = - (*env)->GetMethodID(env, cls, "getSharedEGLContext", "()J"); - jni_info.flush_frontbuffer = - (*env)->GetMethodID(env, cls, "flushFrontbuffer", "(II)V"); - - return (jlong)virgl_server_handle_new_connection(fd); -} - -JNIEXPORT void JNICALL -Java_com_winlator_xenvironment_components_VirGLRendererComponent_handleRequest( - JNIEnv *env, jobject obj, jlong clientPtr) { - jni_info.env = env; - jni_info.obj = obj; - virgl_server_handle_request((struct virgl_client *)clientPtr); -} - -JNIEXPORT void JNICALL -Java_com_winlator_xenvironment_components_VirGLRendererComponent_destroyClient( - JNIEnv *env, jobject obj, jlong clientPtr) { - struct virgl_client *client = (struct virgl_client *)clientPtr; - virgl_server_destroy_client(&client); -} - -JNIEXPORT void JNICALL -Java_com_winlator_xenvironment_components_VirGLRendererComponent_destroyRenderer( - JNIEnv *env, jobject obj, jlong clientPtr) { - virgl_server_destroy_renderer((struct virgl_client *)clientPtr); -} \ No newline at end of file diff --git a/app/src/main/cpp/virglrenderer/server/virgl_server.h b/app/src/main/cpp/virglrenderer/server/virgl_server.h deleted file mode 100644 index bba324287..000000000 --- a/app/src/main/cpp/virglrenderer/server/virgl_server.h +++ /dev/null @@ -1,92 +0,0 @@ -/************************************************************************** - * - * Copyright (C) 2015 Red Hat Inc. - * - * 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. - * - **************************************************************************/ - -#ifndef VIRGL_SERVER_H -#define VIRGL_SERVER_H - -#include -#include -#include -#include - -#include "vrend_renderer.h" - -#include -#include - -#define printf(...) \ - __android_log_print(ANDROID_LOG_DEBUG, "System.out", __VA_ARGS__); - -struct jni_info { - jobject obj; - JNIEnv *env; - jmethodID kill_connection; - jmethodID get_shared_egl_context; - jmethodID flush_frontbuffer; -}; - -struct virgl_server_renderer { - struct util_hash_table *iovec_hash; - GLuint framebuffer; - int handle; - int ctx_id; - int fence_id; - int last_fence_id; - - EGLDisplay egl_display; - EGLConfig egl_conf; - EGLContext egl_ctx; -}; - -struct virgl_client { - int fd; - struct virgl_server_renderer *renderer; - struct vrend_state *vrend_state; - struct util_hash_table *res_hash; - struct vrend_decode_ctx *dec_ctx[VREND_MAX_CTX]; - struct vrend_blitter_ctx *vrend_blit_ctx; - bool initialized; -}; - -extern struct jni_info jni_info; - -int virgl_server_create_renderer(struct virgl_client *client, uint32_t length); -int virgl_server_send_caps(struct virgl_client *client, uint32_t length); -int virgl_server_resource_create(struct virgl_client *client, uint32_t length); -int virgl_server_resource_destroy(struct virgl_client *client, uint32_t length); -int virgl_server_transfer_get(struct virgl_client *client, uint32_t length); -int virgl_server_transfer_put(struct virgl_client *client, uint32_t length); -int virgl_server_submit_cmd(struct virgl_client *client, uint32_t length); -int virgl_server_resource_busy_wait(struct virgl_client *client, - uint32_t length); -int virgl_server_flush_frontbuffer(struct virgl_client *client, - uint32_t length); - -int virgl_block_read(int fd, void *buf, int size); - -int virgl_server_renderer_create_fence(struct virgl_client *client); - -void virgl_server_destroy_renderer(struct virgl_client *client); - -#endif diff --git a/app/src/main/cpp/virglrenderer/server/virgl_server_protocol.h b/app/src/main/cpp/virglrenderer/server/virgl_server_protocol.h deleted file mode 100644 index a0c3f3a7d..000000000 --- a/app/src/main/cpp/virglrenderer/server/virgl_server_protocol.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2014, 2015 Red Hat. - * - * 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 - * on the rights to use, copy, modify, merge, publish, distribute, sub - * license, 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 (including the next - * paragraph) 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 NON-INFRINGEMENT. IN NO EVENT SHALL - * THE AUTHOR(S) AND/OR THEIR SUPPLIERS 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. - */ -#ifndef VIRGL_SERVER_PROTOCOL_H -#define VIRGL_SERVER_PROTOCOL_H - -#define VCMD_CREATE_RENDERER 1 -#define VCMD_GET_CAPS 2 -#define VCMD_RESOURCE_CREATE 3 -#define VCMD_RESOURCE_DESTROY 4 -#define VCMD_TRANSFER_GET 5 -#define VCMD_TRANSFER_PUT 6 -#define VCMD_SUBMIT_CMD 7 -#define VCMD_RESOURCE_BUSY_WAIT 8 -#define VCMD_FLUSH_FRONTBUFFER 9 - -#define VCMD_BUSY_WAIT_FLAG_WAIT 1 - -#endif diff --git a/app/src/main/cpp/virglrenderer/server/virgl_server_renderer.c b/app/src/main/cpp/virglrenderer/server/virgl_server_renderer.c deleted file mode 100644 index b07381cd9..000000000 --- a/app/src/main/cpp/virglrenderer/server/virgl_server_renderer.c +++ /dev/null @@ -1,594 +0,0 @@ -/************************************************************************** - * - * Copyright (C) 2015 Red Hat Inc. - * - * 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. - * - **************************************************************************/ - -#include -#include -#include -#include -#include -#include - -#include "virgl_hw.h" - -#include -#include -#include - -#include - -#include "virgl_server.h" -#include "virgl_server_protocol.h" -#include "virgl_server_shm.h" - -#include "util/u_debug.h" -#include "util/u_hash_table.h" -#include "util/u_math.h" -#include "util/u_memory.h" - -#include - -static void virgl_server_write_fence(struct virgl_client *client, - uint32_t fence_id) { - client->renderer->last_fence_id = fence_id; -} - -static virgl_gl_context -virgl_server_egl_create_context(struct virgl_client *client) { - struct virgl_server_renderer *renderer = client->renderer; - EGLContext egl_ctx; - EGLint ctx_att[] = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE}; - - egl_ctx = eglCreateContext(renderer->egl_display, renderer->egl_conf, - renderer->egl_ctx, ctx_att); - - return (virgl_gl_context)egl_ctx; -} - -static void virgl_server_egl_destroy_context(struct virgl_client *client, - virgl_gl_context ctx) { - eglDestroyContext(client->renderer->egl_display, (EGLContext)ctx); -} - -static int virgl_server_egl_make_current(struct virgl_client *client, - virgl_gl_context ctx) { - return eglMakeCurrent(client->renderer->egl_display, EGL_NO_SURFACE, - EGL_NO_SURFACE, (EGLContext)ctx); -} - -struct vrend_if_cbs virgl_server_cbs = { - .write_fence = virgl_server_write_fence, - .create_gl_context = virgl_server_egl_create_context, - .destroy_gl_context = virgl_server_egl_destroy_context, - .make_current = virgl_server_egl_make_current, -}; - -static bool virgl_server_egl_init(struct virgl_server_renderer *renderer) { - static EGLint conf_att[] = { - EGL_SURFACE_TYPE, - EGL_PBUFFER_BIT, - EGL_RENDERABLE_TYPE, - EGL_OPENGL_ES_BIT, - EGL_RED_SIZE, - 8, - EGL_GREEN_SIZE, - 8, - EGL_BLUE_SIZE, - 8, - EGL_ALPHA_SIZE, - 0, - EGL_NONE, - }; - static const EGLint ctx_att[] = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE}; - - EGLBoolean success; - EGLint major, minor, num_configs; - - renderer->egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY); - - if (!renderer->egl_display) - return false; - - success = eglInitialize(renderer->egl_display, &major, &minor); - if (!success) - return false; - - success = eglBindAPI(EGL_OPENGL_ES_API); - if (!success) - return false; - - success = eglChooseConfig(renderer->egl_display, conf_att, - &renderer->egl_conf, 1, &num_configs); - - if (!success || num_configs != 1) - return false; - - jlong shared_egl_ctx_ptr = - (*jni_info.env) - ->CallLongMethod(jni_info.env, jni_info.obj, - jni_info.get_shared_egl_context); - EGLContext shared_egl_ctx = (EGLContext)shared_egl_ctx_ptr; - - renderer->egl_ctx = eglCreateContext( - renderer->egl_display, renderer->egl_conf, - shared_egl_ctx ? shared_egl_ctx : EGL_NO_CONTEXT, ctx_att); - - eglMakeCurrent(renderer->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, - renderer->egl_ctx); - if (!renderer->egl_ctx) - return false; - - return true; -} - -static unsigned hash_func(void *key) { - intptr_t ip = pointer_to_intptr(key); - return (unsigned)(ip & 0xffffffff); -} - -static int compare_iovecs(void *key1, void *key2) { - if (key1 < key2) { - return -1; - } else if (key1 > key2) { - return 1; - } else { - return 0; - } -} - -static void free_iovec(void *value) { - struct iovec *iovec = value; - if (iovec->iov_base) - munmap(iovec->iov_base, iovec->iov_len); - free(iovec); -} - -static int virgl_block_write(int fd, void *buf, int size) { - char *ptr = buf; - int left; - int ret; - left = size; - - do { - ret = write(fd, ptr, left); - if (ret < 0) - return -errno; - - left -= ret; - ptr += ret; - } while (left); - - return size; -} - -int virgl_block_read(int fd, void *buf, int size) { - char *ptr = buf; - int left; - int ret; - - left = size; - do { - ret = read(fd, ptr, left); - if (ret <= 0) - return ret == -1 ? -errno : 0; - - left -= ret; - ptr += ret; - } while (left); - - return size; -} - -static int virgl_server_send_fd(int sock_fd, int fd) { - struct iovec iovec; - char buf[CMSG_SPACE(sizeof(int))], c; - struct msghdr msgh = {0}; - memset(buf, 0, sizeof(buf)); - - iovec.iov_base = &c; - iovec.iov_len = sizeof(char); - - msgh.msg_name = NULL; - msgh.msg_namelen = 0; - msgh.msg_iov = &iovec; - msgh.msg_iovlen = 1; - msgh.msg_control = buf; - msgh.msg_controllen = sizeof(buf); - msgh.msg_flags = 0; - - struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msgh); - cmsg->cmsg_level = SOL_SOCKET; - cmsg->cmsg_type = SCM_RIGHTS; - cmsg->cmsg_len = CMSG_LEN(sizeof(int)); - - *((int *)CMSG_DATA(cmsg)) = fd; - - int size = sendmsg(sock_fd, &msgh, 0); - if (size < 0) - return -EINVAL; - - return 0; -} - -int virgl_server_create_renderer(struct virgl_client *client, uint32_t length) { - int ret; - - struct virgl_server_renderer *renderer = - calloc(1, sizeof(struct virgl_server_renderer)); - renderer->iovec_hash = - util_hash_table_create(hash_func, compare_iovecs, free_iovec); - renderer->ctx_id = 1; - - client->renderer = renderer; - - virgl_server_egl_init(renderer); - - ret = vrend_renderer_init(client, &virgl_server_cbs); - if (ret) - return -1; - - ret = vrend_renderer_context_create(client, renderer->ctx_id); - return ret; -} - -void virgl_server_destroy_renderer(struct virgl_client *client) { - if (!client->initialized) - return; - - if (client->renderer->framebuffer) - glDeleteFramebuffers(1, &client->renderer->framebuffer); - - vrend_renderer_context_destroy(client, client->renderer->ctx_id); - vrend_renderer_fini(client); - util_hash_table_destroy(client->renderer->iovec_hash); - client->renderer->iovec_hash = NULL; - - free(client->renderer); - client->renderer = NULL; - client->initialized = false; -} - -int virgl_server_send_caps(struct virgl_client *client, - UNUSED uint32_t length) { - uint32_t send_buf[2]; - void *caps_buf; - int ret; - uint32_t max_ver, max_size; - - vrend_renderer_get_cap_set(2, &max_ver, &max_size); - - if (max_size == 0) - return -1; - - caps_buf = malloc(max_size); - if (!caps_buf) - return -1; - - vrend_renderer_fill_caps(client, 2, 1, caps_buf); - - send_buf[0] = max_size + 1; - send_buf[1] = 2; - ret = virgl_block_write(client->fd, send_buf, 8); - if (ret < 0) - goto end; - - virgl_block_write(client->fd, caps_buf, max_size); - -end: - free(caps_buf); - return 0; -} - -int virgl_server_resource_create(struct virgl_client *client, - UNUSED uint32_t length) { - uint32_t recv_buf[11]; - struct vrend_renderer_resource_create_args args; - struct iovec *iovec; - int ret, fd; - - ret = virgl_block_read(client->fd, &recv_buf, sizeof(recv_buf)); - if (ret != sizeof(recv_buf)) - return -1; - - args.handle = recv_buf[0]; - args.target = recv_buf[1]; - args.format = recv_buf[2]; - args.bind = recv_buf[3]; - args.width = recv_buf[4]; - args.height = recv_buf[5]; - args.depth = recv_buf[6]; - args.array_size = recv_buf[7]; - args.last_level = recv_buf[8]; - args.nr_samples = recv_buf[9]; - args.flags = 0; - - if (util_hash_table_get(client->renderer->iovec_hash, - intptr_to_pointer(args.handle))) - return -EEXIST; - - ret = vrend_renderer_resource_create(client, &args, NULL, 0); - if (ret) - return ret; - - vrend_renderer_attach_res_ctx(client, client->renderer->ctx_id, args.handle); - - iovec = CALLOC_STRUCT(iovec); - if (!iovec) - return -ENOMEM; - - iovec->iov_len = recv_buf[10]; - - if (iovec->iov_len == 0) { - iovec->iov_base = NULL; - goto out; - } - - fd = virgl_server_new_shm(args.handle, iovec->iov_len); - if (fd < 0) { - FREE(iovec); - return fd; - } - - iovec->iov_base = - mmap(NULL, iovec->iov_len, PROT_WRITE | PROT_READ, MAP_SHARED, fd, 0); - - if (iovec->iov_base == MAP_FAILED) { - close(fd); - FREE(iovec); - return -ENOMEM; - } - - ret = virgl_server_send_fd(client->fd, fd); - if (ret < 0) { - close(fd); - munmap(iovec->iov_base, iovec->iov_len); - return ret; - } - - close(fd); - -out: - vrend_renderer_resource_attach_iov(client, args.handle, iovec, 1); - util_hash_table_set(client->renderer->iovec_hash, - intptr_to_pointer(args.handle), iovec); - return 0; -} - -int virgl_server_resource_destroy(struct virgl_client *client, - UNUSED uint32_t length) { - uint32_t recv_buf[1]; - int ret; - uint32_t handle; - - ret = virgl_block_read(client->fd, &recv_buf, sizeof(recv_buf)); - if (ret != sizeof(recv_buf)) - return -1; - - handle = recv_buf[0]; - vrend_renderer_attach_res_ctx(client, client->renderer->ctx_id, handle); - - vrend_renderer_resource_detach_iov(client, handle, NULL, NULL); - util_hash_table_remove(client->renderer->iovec_hash, - intptr_to_pointer(handle)); - - vrend_renderer_resource_unref(client, handle); - return 0; -} - -int virgl_server_transfer_get(struct virgl_client *client, - UNUSED uint32_t length) { - uint32_t recv_buf[10]; - int ret; - struct pipe_box box; - struct iovec *iovec; - struct vrend_transfer_info transfer_info; - - ret = virgl_block_read(client->fd, &recv_buf, sizeof(recv_buf)); - if (ret != sizeof(recv_buf)) - return ret; - - box.x = recv_buf[2]; - box.y = recv_buf[3]; - box.z = recv_buf[4]; - box.width = recv_buf[5]; - box.height = recv_buf[6]; - box.depth = recv_buf[7]; - - transfer_info.handle = recv_buf[0]; - transfer_info.ctx_id = client->renderer->ctx_id; - transfer_info.level = recv_buf[1]; - transfer_info.stride = 0; - transfer_info.layer_stride = 0; - transfer_info.box = &box; - transfer_info.offset = recv_buf[9]; - transfer_info.iovec = NULL; - transfer_info.iovec_cnt = 0; - transfer_info.context0 = true; - transfer_info.synchronized = false; - - iovec = util_hash_table_get(client->renderer->iovec_hash, - intptr_to_pointer(transfer_info.handle)); - if (!iovec) - return -ESRCH; - - if (transfer_info.offset >= iovec->iov_len) - return -EFAULT; - - ret = vrend_renderer_transfer_iov(client, &transfer_info, - VIRGL_TRANSFER_FROM_HOST); - - if (ret) - return ret; - - return 0; -} - -int virgl_server_transfer_put(struct virgl_client *client, - UNUSED uint32_t length) { - uint32_t recv_buf[10]; - int ret; - struct pipe_box box; - struct iovec *iovec; - struct vrend_transfer_info transfer_info; - - ret = virgl_block_read(client->fd, &recv_buf, sizeof(recv_buf)); - if (ret != sizeof(recv_buf)) - return ret; - - box.x = recv_buf[2]; - box.y = recv_buf[3]; - box.z = recv_buf[4]; - box.width = recv_buf[5]; - box.height = recv_buf[6]; - box.depth = recv_buf[7]; - - transfer_info.handle = recv_buf[0]; - transfer_info.ctx_id = client->renderer->ctx_id; - transfer_info.level = recv_buf[1]; - transfer_info.stride = 0; - transfer_info.layer_stride = 0; - transfer_info.box = &box; - transfer_info.offset = recv_buf[9]; - transfer_info.iovec = NULL; - transfer_info.iovec_cnt = 0; - transfer_info.context0 = true; - transfer_info.synchronized = false; - - iovec = util_hash_table_get(client->renderer->iovec_hash, - intptr_to_pointer(transfer_info.handle)); - if (!iovec) - return -ESRCH; - - vrend_renderer_transfer_iov(client, &transfer_info, VIRGL_TRANSFER_TO_HOST); - - if (ret) - return ret; - - return 0; -} - -int virgl_server_submit_cmd(struct virgl_client *client, uint32_t length) { - uint32_t *cbuf; - int cbuf_len, ret; - - cbuf_len = length * 4; - cbuf = malloc(cbuf_len); - if (!cbuf) - return -1; - - ret = virgl_block_read(client->fd, cbuf, cbuf_len); - if (ret != cbuf_len) { - free(cbuf); - return -1; - } - - vrend_decode_block(client, client->renderer->ctx_id, cbuf, length); - - free(cbuf); - virgl_server_renderer_create_fence(client); - return 0; -} - -int virgl_server_resource_busy_wait(struct virgl_client *client, - UNUSED uint32_t length) { - uint32_t recv_buf[2]; - uint32_t send_buf[3]; - int ret; - int flags; - bool busy = false; - - ret = virgl_block_read(client->fd, &recv_buf, sizeof(recv_buf)); - if (ret != sizeof(recv_buf)) - return -1; - - flags = recv_buf[1]; - - do { - busy = client->renderer->last_fence_id != client->renderer->fence_id; - if (!busy || !(flags & VCMD_BUSY_WAIT_FLAG_WAIT)) - break; - - vrend_renderer_check_fences(client); - } while (1); - - send_buf[0] = 1; - send_buf[1] = VCMD_RESOURCE_BUSY_WAIT; - send_buf[2] = busy ? 1 : 0; - - ret = virgl_block_write(client->fd, send_buf, sizeof(send_buf)); - if (ret < 0) - return ret; - - return 0; -} - -int virgl_server_flush_frontbuffer(struct virgl_client *client, - UNUSED uint32_t length) { - uint32_t recv_buf[2]; - uint32_t handle, drawable; - int ret; - - ret = virgl_block_read(client->fd, &recv_buf, sizeof(recv_buf)); - if (ret != sizeof(recv_buf)) - return -1; - - handle = recv_buf[0]; - drawable = recv_buf[1]; - - if (handle != client->renderer->handle) { - struct vrend_context *ctx = - vrend_lookup_renderer_ctx(client, client->renderer->ctx_id); - struct vrend_resource *res = vrend_renderer_ctx_res_lookup(ctx, handle); - - if (client->renderer->framebuffer) - glDeleteFramebuffers(1, &client->renderer->framebuffer); - - GLuint framebuffer; - glGenFramebuffers(1, &framebuffer); - glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); - - vrend_fb_bind_texture(res, 0, 0, 0); - - glBindFramebuffer(GL_FRAMEBUFFER, 0); - - client->renderer->framebuffer = framebuffer; - client->renderer->handle = handle; - } - - (*jni_info.env) - ->CallVoidMethod(jni_info.env, jni_info.obj, jni_info.flush_frontbuffer, - drawable, client->renderer->framebuffer); - return 0; -} - -int virgl_server_renderer_create_fence(struct virgl_client *client) { - vrend_renderer_create_fence(client, ++client->renderer->fence_id, 0); - return 0; -} - -JNIEXPORT jlong JNICALL -Java_com_winlator_xenvironment_components_VirGLRendererComponent_getCurrentEGLContextPtr( - JNIEnv *env, jobject obj) { - EGLContext egl_ctx = eglGetCurrentContext(); - return egl_ctx != EGL_NO_CONTEXT ? (jlong)egl_ctx : 0; -} \ No newline at end of file diff --git a/app/src/main/cpp/virglrenderer/server/virgl_server_shm.c b/app/src/main/cpp/virglrenderer/server/virgl_server_shm.c deleted file mode 100644 index 404ab522d..000000000 --- a/app/src/main/cpp/virglrenderer/server/virgl_server_shm.c +++ /dev/null @@ -1,61 +0,0 @@ -/************************************************************************** - * - * Copyright (C) 2018 Chromium. - * - * 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. - * - **************************************************************************/ - -#include "virgl_server_shm.h" - -#include -#include -#include -#include - -#include -#include - -static int memfd_create(const char *name, unsigned int flags) { -#ifdef __NR_memfd_create - return syscall(__NR_memfd_create, name, flags); -#else - return -1; -#endif -} - -int virgl_server_new_shm(uint32_t handle, size_t size) { - int fd, ret; - int length = snprintf(NULL, 0, "virgl-res-%u", handle); - char *str = malloc(length + 1); - snprintf(str, length + 1, "virgl-res-%u", handle); - - fd = memfd_create(str, MFD_ALLOW_SEALING); - free(str); - if (fd < 0) - return -errno; - - ret = ftruncate(fd, size); - if (ret < 0) { - close(fd); - return -errno; - } - - return fd; -} diff --git a/app/src/main/cpp/virglrenderer/server/virgl_server_shm.h b/app/src/main/cpp/virglrenderer/server/virgl_server_shm.h deleted file mode 100644 index 6929e8829..000000000 --- a/app/src/main/cpp/virglrenderer/server/virgl_server_shm.h +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef VIRGL_SERVER_SHM_H -#define VIRGL_SERVER_SHM_H - -#ifndef F_LINUX_SPECIFIC_BASE -#define F_LINUX_SPECIFIC_BASE 1024 -#endif - -#ifndef F_ADD_SEALS -#define F_ADD_SEALS (F_LINUX_SPECIFIC_BASE + 9) -#define F_GET_SEALS (F_LINUX_SPECIFIC_BASE + 10) - -#define F_SEAL_SEAL 0x0001 /* prevent further seals from being set */ -#define F_SEAL_SHRINK 0x0002 /* prevent file from shrinking */ -#define F_SEAL_GROW 0x0004 /* prevent file from growing */ -#define F_SEAL_WRITE 0x0008 /* prevent writes */ -#endif - -#ifndef MFD_CLOEXEC -#define MFD_CLOEXEC 0x0001U -#endif - -#ifndef MFD_ALLOW_SEALING -#define MFD_ALLOW_SEALING 0x0002U -#endif - -#ifndef MFD_HUGETLB -#define MFD_HUGETLB 0x0004U -#endif - -#ifndef MFD_HUGE_SHIFT -#define MFD_HUGE_SHIFT 26 -#endif - -#include -#include - -int virgl_server_new_shm(uint32_t handle, size_t size); - -#endif diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/cso_cache/cso_cache.c b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/cso_cache/cso_cache.c deleted file mode 100644 index 404ac3adc..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/cso_cache/cso_cache.c +++ /dev/null @@ -1,287 +0,0 @@ -/************************************************************************** - * - * Copyright 2007 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -/* Authors: Zack Rusin - */ - -#include "util/u_debug.h" - -#include "util/u_memory.h" - -#include "cso_cache.h" -#include "cso_hash.h" - -struct cso_cache { - struct cso_hash *hashes[CSO_CACHE_MAX]; - int max_size; - - cso_sanitize_callback sanitize_cb; - void *sanitize_data; -}; - -#if 1 -static unsigned hash_key(const void *key, unsigned key_size) { - unsigned *ikey = (unsigned *)key; - unsigned hash = 0, i; - - assert(key_size % 4 == 0); - - /* I'm sure this can be improved on: - */ - for (i = 0; i < key_size / 4; i++) - hash ^= ikey[i]; - - return hash; -} -#else -static unsigned hash_key(const unsigned char *p, int n) { - unsigned h = 0; - unsigned g; - - while (n--) { - h = (h << 4) + *p++; - if ((g = (h & 0xf0000000)) != 0) - h ^= g >> 23; - h &= ~g; - } - return h; -} -#endif - -unsigned cso_construct_key(void *item, int item_size) { - return hash_key((item), item_size); -} - -static inline struct cso_hash *_cso_hash_for_type(struct cso_cache *sc, - enum cso_cache_type type) { - struct cso_hash *hash; - hash = sc->hashes[type]; - return hash; -} - -static void delete_blend_state(void *state, UNUSED void *data) { - struct cso_blend *cso = (struct cso_blend *)state; - if (cso->delete_state) - cso->delete_state(cso->context, cso->data); - FREE(state); -} - -static void delete_depth_stencil_state(void *state, UNUSED void *data) { - struct cso_depth_stencil_alpha *cso = (struct cso_depth_stencil_alpha *)state; - if (cso->delete_state) - cso->delete_state(cso->context, cso->data); - FREE(state); -} - -static void delete_sampler_state(void *state, UNUSED void *data) { - struct cso_sampler *cso = (struct cso_sampler *)state; - if (cso->delete_state) - cso->delete_state(cso->context, cso->data); - FREE(state); -} - -static void delete_rasterizer_state(void *state, UNUSED void *data) { - struct cso_rasterizer *cso = (struct cso_rasterizer *)state; - if (cso->delete_state) - cso->delete_state(cso->context, cso->data); - FREE(state); -} - -static void delete_velements(void *state, UNUSED void *data) { - struct cso_velements *cso = (struct cso_velements *)state; - if (cso->delete_state) - cso->delete_state(cso->context, cso->data); - FREE(state); -} - -static inline void delete_cso(void *state, enum cso_cache_type type) { - switch (type) { - case CSO_BLEND: - delete_blend_state(state, 0); - break; - case CSO_SAMPLER: - delete_sampler_state(state, 0); - break; - case CSO_DEPTH_STENCIL_ALPHA: - delete_depth_stencil_state(state, 0); - break; - case CSO_RASTERIZER: - delete_rasterizer_state(state, 0); - break; - case CSO_VELEMENTS: - delete_velements(state, 0); - break; - default: - assert(0); - FREE(state); - } -} - -static inline void sanitize_hash(struct cso_cache *sc, struct cso_hash *hash, - enum cso_cache_type type, int max_size) { - if (sc->sanitize_cb) - sc->sanitize_cb(hash, type, max_size, sc->sanitize_data); -} - -static inline void sanitize_cb(struct cso_hash *hash, enum cso_cache_type type, - int max_size, UNUSED void *user_data) { - /* if we're approach the maximum size, remove fourth of the entries - * otherwise every subsequent call will go through the same */ - int hash_size = cso_hash_size(hash); - int max_entries = (max_size > hash_size) ? max_size : hash_size; - int to_remove = (max_size < max_entries) * max_entries / 4; - if (hash_size > max_size) - to_remove += hash_size - max_size; - while (to_remove) { - /*remove elements until we're good */ - /*fixme: currently we pick the nodes to remove at random*/ - struct cso_hash_iter iter = cso_hash_first_node(hash); - void *cso = cso_hash_take(hash, cso_hash_iter_key(iter)); - delete_cso(cso, type); - --to_remove; - } -} - -struct cso_hash_iter cso_insert_state(struct cso_cache *sc, unsigned hash_key, - enum cso_cache_type type, void *state) { - struct cso_hash *hash = _cso_hash_for_type(sc, type); - sanitize_hash(sc, hash, type, sc->max_size); - - return cso_hash_insert(hash, hash_key, state); -} - -struct cso_hash_iter cso_find_state(struct cso_cache *sc, unsigned hash_key, - enum cso_cache_type type) { - struct cso_hash *hash = _cso_hash_for_type(sc, type); - - return cso_hash_find(hash, hash_key); -} - -void *cso_hash_find_data_from_template(struct cso_hash *hash, unsigned hash_key, - void *templ, int size) { - struct cso_hash_iter iter = cso_hash_find(hash, hash_key); - while (!cso_hash_iter_is_null(iter)) { - void *iter_data = cso_hash_iter_data(iter); - if (!memcmp(iter_data, templ, size)) { - /* We found a match - */ - return iter_data; - } - iter = cso_hash_iter_next(iter); - } - return NULL; -} - -struct cso_hash_iter cso_find_state_template(struct cso_cache *sc, - unsigned hash_key, - enum cso_cache_type type, - void *templ, unsigned size) { - struct cso_hash_iter iter = cso_find_state(sc, hash_key, type); - while (!cso_hash_iter_is_null(iter)) { - void *iter_data = cso_hash_iter_data(iter); - if (!memcmp(iter_data, templ, size)) - return iter; - iter = cso_hash_iter_next(iter); - } - return iter; -} - -void *cso_take_state(struct cso_cache *sc, unsigned hash_key, - enum cso_cache_type type) { - struct cso_hash *hash = _cso_hash_for_type(sc, type); - return cso_hash_take(hash, hash_key); -} - -struct cso_cache *cso_cache_create(void) { - struct cso_cache *sc = MALLOC_STRUCT(cso_cache); - int i; - if (sc == NULL) - return NULL; - - sc->max_size = 4096; - for (i = 0; i < CSO_CACHE_MAX; i++) - sc->hashes[i] = cso_hash_create(); - - sc->sanitize_cb = sanitize_cb; - sc->sanitize_data = 0; - - return sc; -} - -void cso_for_each_state(struct cso_cache *sc, enum cso_cache_type type, - cso_state_callback func, void *user_data) { - struct cso_hash *hash = _cso_hash_for_type(sc, type); - struct cso_hash_iter iter; - - iter = cso_hash_first_node(hash); - while (!cso_hash_iter_is_null(iter)) { - void *state = cso_hash_iter_data(iter); - iter = cso_hash_iter_next(iter); - if (state) { - func(state, user_data); - } - } -} - -void cso_cache_delete(struct cso_cache *sc) { - int i; - assert(sc); - - if (!sc) - return; - - /* delete driver data */ - cso_for_each_state(sc, CSO_BLEND, delete_blend_state, 0); - cso_for_each_state(sc, CSO_DEPTH_STENCIL_ALPHA, delete_depth_stencil_state, - 0); - cso_for_each_state(sc, CSO_RASTERIZER, delete_rasterizer_state, 0); - cso_for_each_state(sc, CSO_SAMPLER, delete_sampler_state, 0); - cso_for_each_state(sc, CSO_VELEMENTS, delete_velements, 0); - - for (i = 0; i < CSO_CACHE_MAX; i++) - cso_hash_delete(sc->hashes[i]); - - FREE(sc); -} - -void cso_set_maximum_cache_size(struct cso_cache *sc, int number) { - int i; - - sc->max_size = number; - - for (i = 0; i < CSO_CACHE_MAX; i++) - sanitize_hash(sc, sc->hashes[i], i, sc->max_size); -} - -int cso_maximum_cache_size(const struct cso_cache *sc) { return sc->max_size; } - -void cso_cache_set_sanitize_callback(struct cso_cache *sc, - cso_sanitize_callback cb, - void *user_data) { - sc->sanitize_cb = cb; - sc->sanitize_data = user_data; -} diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/cso_cache/cso_cache.h b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/cso_cache/cso_cache.h deleted file mode 100644 index 515e2334e..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/cso_cache/cso_cache.h +++ /dev/null @@ -1,172 +0,0 @@ -/************************************************************************** - * - * Copyright 2007-2008 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -/** - * @file - * Constant State Object (CSO) cache. - * - * The basic idea is that the states are created via the - * create_state/bind_state/delete_state semantics. The driver is expected to - * perform as much of the Gallium state translation to whatever its internal - * representation is during the create call. Gallium then has a caching - * mechanism where it stores the created states. When the pipeline needs an - * actual state change, a bind call is issued. In the bind call the driver - * gets its already translated representation. - * - * Those semantics mean that the driver doesn't do the repeated translations - * of states on every frame, but only once, when a new state is actually - * created. - * - * Even on hardware that doesn't do any kind of state cache, it makes the - * driver look a lot neater, plus it avoids all the redundant state - * translations on every frame. - * - * Currently our constant state objects are: - * - alpha test - * - blend - * - depth stencil - * - fragment shader - * - rasterizer (old setup) - * - sampler - * - vertex shader - * - vertex elements - * - * Things that are not constant state objects include: - * - blend_color - * - clip_state - * - clear_color_state - * - constant_buffer - * - feedback_state - * - framebuffer_state - * - polygon_stipple - * - scissor_state - * - texture_state - * - viewport_state - * - * @author Zack Rusin - */ - -#ifndef CSO_CACHE_H -#define CSO_CACHE_H - -#include "pipe/p_context.h" -#include "pipe/p_state.h" - -/* cso_hash.h is necessary for cso_hash_iter, as MSVC requires structures - * returned by value to be fully defined */ -#include "cso_hash.h" - -#ifdef __cplusplus -extern "C" { -#endif - -enum cso_cache_type { - CSO_RASTERIZER, - CSO_BLEND, - CSO_DEPTH_STENCIL_ALPHA, - CSO_SAMPLER, - CSO_VELEMENTS, - CSO_CACHE_MAX, -}; - -typedef void (*cso_state_callback)(void *ctx, void *obj); - -typedef void (*cso_sanitize_callback)(struct cso_hash *hash, - enum cso_cache_type type, int max_size, - void *user_data); - -struct cso_cache; - -struct cso_blend { - struct pipe_blend_state state; - void *data; - cso_state_callback delete_state; - struct pipe_context *context; -}; - -struct cso_depth_stencil_alpha { - struct pipe_depth_stencil_alpha_state state; - void *data; - cso_state_callback delete_state; - struct pipe_context *context; -}; - -struct cso_rasterizer { - struct pipe_rasterizer_state state; - void *data; - cso_state_callback delete_state; - struct pipe_context *context; -}; - -struct cso_sampler { - struct pipe_sampler_state state; - void *data; - cso_state_callback delete_state; - struct pipe_context *context; -}; - -struct cso_velems_state { - unsigned count; - struct pipe_vertex_element velems[PIPE_MAX_ATTRIBS]; -}; - -struct cso_velements { - struct cso_velems_state state; - void *data; - cso_state_callback delete_state; - struct pipe_context *context; -}; - -unsigned cso_construct_key(void *item, int item_size); - -struct cso_cache *cso_cache_create(void); -void cso_cache_delete(struct cso_cache *sc); - -void cso_cache_set_sanitize_callback(struct cso_cache *sc, - cso_sanitize_callback cb, void *user_data); - -struct cso_hash_iter cso_insert_state(struct cso_cache *sc, unsigned hash_key, - enum cso_cache_type type, void *state); -struct cso_hash_iter cso_find_state(struct cso_cache *sc, unsigned hash_key, - enum cso_cache_type type); -struct cso_hash_iter cso_find_state_template(struct cso_cache *sc, - unsigned hash_key, - enum cso_cache_type type, - void *templ, unsigned size); -void cso_for_each_state(struct cso_cache *sc, enum cso_cache_type type, - cso_state_callback func, void *user_data); -void *cso_take_state(struct cso_cache *sc, unsigned hash_key, - enum cso_cache_type type); - -void cso_set_maximum_cache_size(struct cso_cache *sc, int number); -int cso_maximum_cache_size(const struct cso_cache *sc); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/cso_cache/cso_hash.c b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/cso_cache/cso_hash.c deleted file mode 100644 index c792c81b0..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/cso_cache/cso_hash.c +++ /dev/null @@ -1,410 +0,0 @@ -/************************************************************************** - * - * Copyright 2007 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -/* - * Authors: - * Zack Rusin - */ - -#include "util/u_debug.h" -#include "util/u_memory.h" - -#include "cso_hash.h" - -#define MAX(a, b) ((a > b) ? (a) : (b)) - -static const int MinNumBits = 4; - -static const unsigned char prime_deltas[] = { - 0, 0, 1, 3, 1, 5, 3, 3, 1, 9, 7, 5, 3, 9, 25, 3, - 1, 21, 3, 21, 7, 15, 9, 5, 3, 29, 15, 0, 0, 0, 0, 0}; - -static int primeForNumBits(int numBits) { - return (1 << numBits) + prime_deltas[numBits]; -} - -/* - Returns the smallest integer n such that - primeForNumBits(n) >= hint. -*/ -static int countBits(int hint) { - int numBits = 0; - int bits = hint; - - while (bits > 1) { - bits >>= 1; - numBits++; - } - - if (numBits >= (int)sizeof(prime_deltas)) { - numBits = sizeof(prime_deltas) - 1; - } else if (primeForNumBits(numBits) < hint) { - ++numBits; - } - return numBits; -} - -struct cso_node { - struct cso_node *next; - unsigned key; - void *value; -}; - -struct cso_hash_data { - struct cso_node *fakeNext; - struct cso_node **buckets; - int size; - int nodeSize; - short userNumBits; - short numBits; - int numBuckets; -}; - -struct cso_hash { - union { - struct cso_hash_data *d; - struct cso_node *e; - } data; -}; - -static void *cso_data_allocate_node(struct cso_hash_data *hash) { - return MALLOC(hash->nodeSize); -} - -static void cso_free_node(struct cso_node *node) { FREE(node); } - -static struct cso_node *cso_hash_create_node(struct cso_hash *hash, - unsigned akey, void *avalue, - struct cso_node **anextNode) { - struct cso_node *node = cso_data_allocate_node(hash->data.d); - - if (!node) - return NULL; - - node->key = akey; - node->value = avalue; - - node->next = (struct cso_node *)(*anextNode); - *anextNode = node; - ++hash->data.d->size; - return node; -} - -static void cso_data_rehash(struct cso_hash_data *hash, int hint) { - if (hint < 0) { - hint = countBits(-hint); - if (hint < MinNumBits) - hint = MinNumBits; - hash->userNumBits = (short)hint; - while (primeForNumBits(hint) < (hash->size >> 1)) - ++hint; - } else if (hint < MinNumBits) { - hint = MinNumBits; - } - - if (hash->numBits != hint) { - struct cso_node *e = (struct cso_node *)(hash); - struct cso_node **oldBuckets = hash->buckets; - int oldNumBuckets = hash->numBuckets; - int i = 0; - - hash->numBits = (short)hint; - hash->numBuckets = primeForNumBits(hint); - hash->buckets = MALLOC(sizeof(struct cso_node *) * hash->numBuckets); - for (i = 0; i < hash->numBuckets; ++i) - hash->buckets[i] = e; - - for (i = 0; i < oldNumBuckets; ++i) { - struct cso_node *firstNode = oldBuckets[i]; - while (firstNode != e) { - unsigned h = firstNode->key; - struct cso_node *lastNode = firstNode; - struct cso_node *afterLastNode; - struct cso_node **beforeFirstNode; - - while (lastNode->next != e && lastNode->next->key == h) - lastNode = lastNode->next; - - afterLastNode = lastNode->next; - beforeFirstNode = &hash->buckets[h % hash->numBuckets]; - while (*beforeFirstNode != e) - beforeFirstNode = &(*beforeFirstNode)->next; - lastNode->next = *beforeFirstNode; - *beforeFirstNode = firstNode; - firstNode = afterLastNode; - } - } - FREE(oldBuckets); - } -} - -static void cso_data_might_grow(struct cso_hash_data *hash) { - if (hash->size >= hash->numBuckets) - cso_data_rehash(hash, hash->numBits + 1); -} - -static void cso_data_has_shrunk(struct cso_hash_data *hash) { - if (hash->size <= (hash->numBuckets >> 3) && - hash->numBits > hash->userNumBits) { - int max = MAX(hash->numBits - 2, hash->userNumBits); - cso_data_rehash(hash, max); - } -} - -static struct cso_node *cso_data_first_node(struct cso_hash_data *hash) { - struct cso_node *e = (struct cso_node *)(hash); - struct cso_node **bucket = hash->buckets; - int n = hash->numBuckets; - while (n--) { - if (*bucket != e) - return *bucket; - ++bucket; - } - return e; -} - -static struct cso_node **cso_hash_find_node(struct cso_hash *hash, - unsigned akey) { - struct cso_node **node; - - if (hash->data.d->numBuckets) { - node = - (struct cso_node **)(&hash->data.d - ->buckets[akey % hash->data.d->numBuckets]); - assert(*node == hash->data.e || (*node)->next); - while (*node != hash->data.e && (*node)->key != akey) - node = &(*node)->next; - } else { - node = - (struct cso_node **)((const struct cso_node *const *)(&hash->data.e)); - } - return node; -} - -struct cso_hash_iter cso_hash_insert(struct cso_hash *hash, unsigned key, - void *data) { - cso_data_might_grow(hash->data.d); - - { - struct cso_node **nextNode = cso_hash_find_node(hash, key); - struct cso_node *node = cso_hash_create_node(hash, key, data, nextNode); - if (!node) { - struct cso_hash_iter null_iter = {hash, 0}; - return null_iter; - } - - { - struct cso_hash_iter iter = {hash, node}; - return iter; - } - } -} - -struct cso_hash *cso_hash_create(void) { - struct cso_hash *hash = MALLOC_STRUCT(cso_hash); - if (!hash) - return NULL; - - hash->data.d = MALLOC_STRUCT(cso_hash_data); - if (!hash->data.d) { - FREE(hash); - return NULL; - } - - hash->data.d->fakeNext = 0; - hash->data.d->buckets = 0; - hash->data.d->size = 0; - hash->data.d->nodeSize = sizeof(struct cso_node); - hash->data.d->userNumBits = (short)MinNumBits; - hash->data.d->numBits = 0; - hash->data.d->numBuckets = 0; - - return hash; -} - -void cso_hash_delete(struct cso_hash *hash) { - struct cso_node *e_for_x = (struct cso_node *)(hash->data.d); - struct cso_node **bucket = (struct cso_node **)(hash->data.d->buckets); - int n = hash->data.d->numBuckets; - while (n--) { - struct cso_node *cur = *bucket++; - while (cur != e_for_x) { - struct cso_node *next = cur->next; - cso_free_node(cur); - cur = next; - } - } - FREE(hash->data.d->buckets); - FREE(hash->data.d); - FREE(hash); -} - -struct cso_hash_iter cso_hash_find(struct cso_hash *hash, unsigned key) { - struct cso_node **nextNode = cso_hash_find_node(hash, key); - struct cso_hash_iter iter = {hash, *nextNode}; - return iter; -} - -unsigned cso_hash_iter_key(struct cso_hash_iter iter) { - if (!iter.node || iter.hash->data.e == iter.node) - return 0; - return iter.node->key; -} - -void *cso_hash_iter_data(struct cso_hash_iter iter) { - if (!iter.node || iter.hash->data.e == iter.node) - return 0; - return iter.node->value; -} - -static struct cso_node *cso_hash_data_next(struct cso_node *node) { - union { - struct cso_node *next; - struct cso_node *e; - struct cso_hash_data *d; - } a; - int start; - struct cso_node **bucket; - int n; - - a.next = node->next; - if (!a.next) { - debug_printf("iterating beyond the last element\n"); - return 0; - } - if (a.next->next) - return a.next; - - start = (node->key % a.d->numBuckets) + 1; - bucket = a.d->buckets + start; - n = a.d->numBuckets - start; - while (n--) { - if (*bucket != a.e) - return *bucket; - ++bucket; - } - return a.e; -} - -static struct cso_node *cso_hash_data_prev(struct cso_node *node) { - union { - struct cso_node *e; - struct cso_hash_data *d; - } a; - int start; - struct cso_node *sentinel; - struct cso_node **bucket; - - a.e = node; - while (a.e->next) - a.e = a.e->next; - - if (node == a.e) - start = a.d->numBuckets - 1; - else - start = node->key % a.d->numBuckets; - - sentinel = node; - bucket = a.d->buckets + start; - while (start >= 0) { - if (*bucket != sentinel) { - struct cso_node *prev = *bucket; - while (prev->next != sentinel) - prev = prev->next; - return prev; - } - - sentinel = a.e; - --bucket; - --start; - } - debug_printf("iterating backward beyond first element\n"); - return a.e; -} - -struct cso_hash_iter cso_hash_iter_next(struct cso_hash_iter iter) { - struct cso_hash_iter next = {iter.hash, cso_hash_data_next(iter.node)}; - return next; -} - -int cso_hash_iter_is_null(struct cso_hash_iter iter) { - if (!iter.node || iter.node == iter.hash->data.e) - return 1; - return 0; -} - -void *cso_hash_take(struct cso_hash *hash, unsigned akey) { - struct cso_node **node = cso_hash_find_node(hash, akey); - if (*node != hash->data.e) { - void *t = (*node)->value; - struct cso_node *next = (*node)->next; - cso_free_node(*node); - *node = next; - --hash->data.d->size; - cso_data_has_shrunk(hash->data.d); - return t; - } - return 0; -} - -struct cso_hash_iter cso_hash_iter_prev(struct cso_hash_iter iter) { - struct cso_hash_iter prev = {iter.hash, cso_hash_data_prev(iter.node)}; - return prev; -} - -struct cso_hash_iter cso_hash_first_node(struct cso_hash *hash) { - struct cso_hash_iter iter = {hash, cso_data_first_node(hash->data.d)}; - return iter; -} - -int cso_hash_size(struct cso_hash *hash) { return hash->data.d->size; } - -struct cso_hash_iter cso_hash_erase(struct cso_hash *hash, - struct cso_hash_iter iter) { - struct cso_hash_iter ret = iter; - struct cso_node *node = iter.node; - struct cso_node **node_ptr; - - if (node == hash->data.e) - return iter; - - ret = cso_hash_iter_next(ret); - node_ptr = - (struct cso_node * - *)(&hash->data.d->buckets[node->key % hash->data.d->numBuckets]); - while (*node_ptr != node) - node_ptr = &(*node_ptr)->next; - *node_ptr = node->next; - cso_free_node(node); - --hash->data.d->size; - return ret; -} - -boolean cso_hash_contains(struct cso_hash *hash, unsigned key) { - struct cso_node **node = cso_hash_find_node(hash, key); - return (*node != hash->data.e); -} diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/cso_cache/cso_hash.h b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/cso_cache/cso_hash.h deleted file mode 100644 index 35324875c..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/cso_cache/cso_hash.h +++ /dev/null @@ -1,117 +0,0 @@ -/************************************************************************** - * - * Copyright 2007 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -/** - * @file - * Hash table implementation. - * - * This file provides a hash implementation that is capable of dealing - * with collisions. It stores colliding entries in linked list. All - * functions operating on the hash return an iterator. The iterator - * itself points to the collision list. If there wasn't any collision - * the list will have just one entry, otherwise client code should - * iterate over the entries to find the exact entry among ones that - * had the same key (e.g. memcmp could be used on the data to check - * that) - * - * @author Zack Rusin - */ - -#ifndef CSO_HASH_H -#define CSO_HASH_H - -#include "pipe/p_compiler.h" - -#ifdef __cplusplus -extern "C" { -#endif - -struct cso_hash; -struct cso_node; - -struct cso_hash_iter { - struct cso_hash *hash; - struct cso_node *node; -}; - -struct cso_hash *cso_hash_create(void); -void cso_hash_delete(struct cso_hash *hash); - -int cso_hash_size(struct cso_hash *hash); - -/** - * Adds a data with the given key to the hash. If entry with the given - * key is already in the hash, this current entry is instered before it - * in the collision list. - * Function returns iterator pointing to the inserted item in the hash. - */ -struct cso_hash_iter cso_hash_insert(struct cso_hash *hash, unsigned key, - void *data); -/** - * Removes the item pointed to by the current iterator from the hash. - * Note that the data itself is not erased and if it was a malloc'ed pointer - * it will have to be freed after calling this function by the callee. - * Function returns iterator pointing to the item after the removed one in - * the hash. - */ -struct cso_hash_iter cso_hash_erase(struct cso_hash *hash, - struct cso_hash_iter iter); - -void *cso_hash_take(struct cso_hash *hash, unsigned key); - -struct cso_hash_iter cso_hash_first_node(struct cso_hash *hash); - -/** - * Return an iterator pointing to the first entry in the collision list. - */ -struct cso_hash_iter cso_hash_find(struct cso_hash *hash, unsigned key); - -/** - * Returns true if a value with the given key exists in the hash - */ -boolean cso_hash_contains(struct cso_hash *hash, unsigned key); - -int cso_hash_iter_is_null(struct cso_hash_iter iter); -unsigned cso_hash_iter_key(struct cso_hash_iter iter); -void *cso_hash_iter_data(struct cso_hash_iter iter); - -struct cso_hash_iter cso_hash_iter_next(struct cso_hash_iter iter); -struct cso_hash_iter cso_hash_iter_prev(struct cso_hash_iter iter); - -/** - * Convenience routine to iterate over the collision list while doing a memory - * comparison to see which entry in the list is a direct copy of our template - * and returns that entry. - */ -void *cso_hash_find_data_from_template(struct cso_hash *hash, unsigned hash_key, - void *templ, int size); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/os/os_memory.h b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/os/os_memory.h deleted file mode 100644 index b846b9721..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/os/os_memory.h +++ /dev/null @@ -1,71 +0,0 @@ -/************************************************************************** - * - * Copyright 2010 Vmware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -/* - * OS memory management abstractions - */ - -#ifndef _OS_MEMORY_H_ -#define _OS_MEMORY_H_ - -#include "pipe/p_compiler.h" -#include "pipe/p_config.h" - -#if defined(PIPE_SUBSYSTEM_EMBEDDED) - -#ifdef __cplusplus -extern "C" { -#endif - -void *os_malloc(size_t size); - -void *os_calloc(size_t count, size_t size); - -void os_free(void *ptr); - -void *os_realloc(void *ptr, size_t old_size, size_t new_size); - -void *os_malloc_aligned(size_t size, size_t alignment); - -void os_free_aligned(void *ptr); - -#ifdef __cplusplus -} -#endif - -#elif defined(PIPE_OS_WINDOWS) && defined(DEBUG) && \ - !defined(DEBUG_MEMORY_IMPLEMENTATION) - -#include "os_memory_debug.h" - -#else - -#include "os_memory_stdc.h" - -#endif - -#endif /* _OS_MEMORY_H_ */ diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/os/os_memory_aligned.h b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/os/os_memory_aligned.h deleted file mode 100644 index 6ae610301..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/os/os_memory_aligned.h +++ /dev/null @@ -1,64 +0,0 @@ -/************************************************************************** - * - * Copyright 2008-2010 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -/* - * Memory alignment wrappers. - */ - -#ifndef _OS_MEMORY_H_ -#error "Must not be included directly. Include os_memory.h instead" -#endif - -#include "pipe/p_compiler.h" - -/** - * Return memory on given byte alignment - */ -static inline void *os_malloc_aligned(size_t size, size_t alignment) { - char *ptr, *buf; - - ptr = (char *)os_malloc(size + alignment + sizeof(void *)); - if (!ptr) - return NULL; - - buf = (char *)(((uintptr_t)ptr + sizeof(void *) + alignment - 1) & - ~((uintptr_t)(alignment - 1))); - *(char **)(buf - sizeof(void *)) = ptr; - - return buf; -} - -/** - * Free memory returned by align_malloc(). - */ -static inline void os_free_aligned(void *ptr) { - if (ptr) { - void **cubbyHole = (void **)((char *)ptr - sizeof(void *)); - void *realAddr = *cubbyHole; - os_free(realAddr); - } -} diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/os/os_memory_debug.h b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/os/os_memory_debug.h deleted file mode 100644 index b4b00d11d..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/os/os_memory_debug.h +++ /dev/null @@ -1,76 +0,0 @@ -/************************************************************************** - * - * Copyright 2008-2010 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -/* - * Debugging wrappers for OS memory management abstractions. - */ - -#ifndef _OS_MEMORY_H_ -#error "Must not be included directly. Include os_memory.h instead" -#endif - -#include "pipe/p_compiler.h" - -#ifdef __cplusplus -extern "C" { -#endif - -void *debug_malloc(const char *file, unsigned line, const char *function, - size_t size); - -void *debug_calloc(const char *file, unsigned line, const char *function, - size_t count, size_t size); - -void debug_free(const char *file, unsigned line, const char *function, - void *ptr); - -void *debug_realloc(const char *file, unsigned line, const char *function, - void *old_ptr, size_t old_size, size_t new_size); - -void debug_memory_tag(void *ptr, unsigned tag); - -void debug_memory_check_block(void *ptr); - -void debug_memory_check(void); - -#ifdef __cplusplus -} -#endif - -#ifndef DEBUG_MEMORY_IMPLEMENTATION - -#define os_malloc(_size) debug_malloc(__FILE__, __LINE__, __FUNCTION__, _size) -#define os_calloc(_count, _size) \ - debug_calloc(__FILE__, __LINE__, __FUNCTION__, _count, _size) -#define os_free(_ptr) debug_free(__FILE__, __LINE__, __FUNCTION__, _ptr) -#define os_realloc(_ptr, _old_size, _new_size) \ - debug_realloc(__FILE__, __LINE__, __FUNCTION__, _ptr, _old_size, _new_size) - -/* TODO: wrap os_malloc_aligned() and os_free_aligned() too */ -#include "os_memory_aligned.h" - -#endif /* !DEBUG_MEMORY_IMPLEMENTATION */ diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/os/os_memory_stdc.h b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/os/os_memory_stdc.h deleted file mode 100644 index 6a24f791a..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/os/os_memory_stdc.h +++ /dev/null @@ -1,70 +0,0 @@ -/************************************************************************** - * - * Copyright 2008-2010 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -/* - * OS memory management abstractions for the standard C library. - */ - -#ifndef _OS_MEMORY_H_ -#error "Must not be included directly. Include os_memory.h instead" -#endif - -#include - -#include "pipe/p_compiler.h" - -#define os_malloc(_size) malloc(_size) -#define os_calloc(_count, _size) calloc(_count, _size) -#define os_free(_ptr) free(_ptr) - -#define os_realloc(_old_ptr, _old_size, _new_size) \ - realloc(_old_ptr, _new_size + 0 * (_old_size)) - -#if defined(HAVE_POSIX_MEMALIGN) - -static inline void *os_malloc_aligned(size_t size, size_t alignment) { - void *ptr; - alignment = (alignment + sizeof(void *) - 1) & ~(sizeof(void *) - 1); - if (posix_memalign(&ptr, alignment, size) != 0) - return NULL; - return ptr; -} - -#define os_free_aligned(_ptr) free(_ptr) - -#elif defined(PIPE_OS_WINDOWS) - -#include - -#define os_malloc_aligned(_size, _align) _aligned_malloc(_size, _align) -#define os_free_aligned(_ptr) _aligned_free(_ptr) - -#else - -#include "os_memory_aligned.h" - -#endif diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/os/os_misc.c b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/os/os_misc.c deleted file mode 100644 index 42f6887bd..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/os/os_misc.c +++ /dev/null @@ -1,79 +0,0 @@ -/************************************************************************** - * - * Copyright 2008-2010 Vmware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -#include "os_misc.h" - -#include - -#if defined(PIPE_SUBSYSTEM_WINDOWS_USER) - -#ifndef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers -#endif -#include -#include - -#else - -#include -#include - -#endif - -void os_log_message(const char *message) { - /* If the GALLIUM_LOG_FILE environment variable is set to a valid filename, - * write all messages to that file. - */ - static FILE *fout = NULL; - - if (!fout) { - /* one-time init */ - const char *filename = os_get_option("GALLIUM_LOG_FILE"); - if (filename) - fout = fopen(filename, "w"); - if (!fout) - fout = stderr; - } - -#if defined(PIPE_SUBSYSTEM_WINDOWS_USER) - OutputDebugStringA(message); - if (GetConsoleWindow() && !IsDebuggerPresent()) { - fflush(stdout); - fputs(message, fout); - fflush(fout); - } else if (fout != stderr) { - fputs(message, fout); - fflush(fout); - } -#else /* !PIPE_SUBSYSTEM_WINDOWS */ - fflush(stdout); - fputs(message, fout); - fflush(fout); -#endif -} - -const char *os_get_option(const char *name) { return getenv(name); } diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/os/os_misc.h b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/os/os_misc.h deleted file mode 100644 index 86787c4e2..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/os/os_misc.h +++ /dev/null @@ -1,95 +0,0 @@ -/************************************************************************** - * - * Copyright 2010 Vmware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -/* - * Miscellaneous OS services. - */ - -#ifndef _OS_MISC_H_ -#define _OS_MISC_H_ - -#include "pipe/p_compiler.h" - -#if defined(PIPE_OS_UNIX) -#include /* for kill() */ -#include /* getpid() */ -#endif - -#ifdef __GNUC__ -#define UNUSED __attribute__((unused)) -#ifdef NDEBUG -#define MAYBE_UNUSED __attribute__((unused)) -#else -#define MAYBE_UNUSED -#endif -#else -#define UNUSED -#define MAYBE_UNUSED -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -/* - * Trap into the debugger. - */ -#if (defined(PIPE_ARCH_X86) || defined(PIPE_ARCH_X86_64)) && \ - defined(PIPE_CC_GCC) -#define os_break() __asm("int3") -#elif defined(PIPE_CC_MSVC) -#define os_break() __debugbreak() -#elif defined(PIPE_OS_UNIX) -#define os_break() kill(getpid(), SIGTRAP) -#else -#define os_break() abort() -#endif - -/* - * Abort the program. - */ -#if defined(DEBUG) -#define os_abort() os_break() -#else -#define os_abort() abort() -#endif - -/* - * Output a message. Message should preferably end in a newline. - */ -void os_log_message(const char *message); - -/* - * Get an option. Should return NULL if specified option is not set. - */ -const char *os_get_option(const char *name); - -#ifdef __cplusplus -} -#endif - -#endif /* _OS_MISC_H_ */ diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/os/os_mman.h b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/os/os_mman.h deleted file mode 100644 index eee7202e6..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/os/os_mman.h +++ /dev/null @@ -1,85 +0,0 @@ -/************************************************************************** - * - * Copyright 2011 LunarG, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -/** - * @file - * OS independent memory mapping (with large file support). - * - * @author Chia-I Wu - */ - -#ifndef _OS_MMAN_H_ -#define _OS_MMAN_H_ - -#include "pipe/p_compiler.h" -#include "pipe/p_config.h" - -#if defined(PIPE_OS_UNIX) -#ifndef _FILE_OFFSET_BITS -#error _FILE_OFFSET_BITS must be defined to 64 -#endif -#include -#else -#error Unsupported OS -#endif - -#if defined(PIPE_OS_ANDROID) -#include /* for EINVAL */ -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -#if defined(PIPE_OS_ANDROID) - -extern void *__mmap2(void *, size_t, int, int, int, size_t); - -static inline void *os_mmap(void *addr, size_t length, int prot, int flags, - int fd, loff_t offset) { - /* offset must be aligned to 4096 (not necessarily the page size) */ - if (unlikely(offset & 4095)) { - errno = EINVAL; - return MAP_FAILED; - } - - return __mmap2(addr, length, prot, flags, fd, (size_t)(offset >> 12)); -} - -#else -/* assume large file support exists */ -#define os_mmap(addr, length, prot, flags, fd, offset) \ - mmap(addr, length, prot, flags, fd, offset) -#endif - -#define os_munmap(addr, length) munmap(addr, length) - -#ifdef __cplusplus -} -#endif - -#endif /* _OS_MMAN_H_ */ diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/os/os_thread.h b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/os/os_thread.h deleted file mode 100644 index 3f4f80668..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/os/os_thread.h +++ /dev/null @@ -1,245 +0,0 @@ -/************************************************************************** - * - * Copyright 1999-2006 Brian Paul - * Copyright 2008 VMware, Inc. - * All Rights Reserved. - * - * 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. - * - **************************************************************************/ - -/** - * @file - * - * Thread, mutex, condition variable, barrier, semaphore and - * thread-specific data functions. - */ - -#ifndef OS_THREAD_H_ -#define OS_THREAD_H_ - -#include "pipe/p_compiler.h" -#include "util/u_debug.h" /* for assert */ - -#include "c11/threads.h" -#include - -/* pipe_thread - */ -typedef thrd_t pipe_thread; - -#define PIPE_THREAD_ROUTINE(name, param) int name(void *param) - -static inline pipe_thread pipe_thread_create(PIPE_THREAD_ROUTINE((*routine), ), - void *param) { - pipe_thread thread; - sigset_t saved_set, new_set; - int ret; - - sigfillset(&new_set); - pthread_sigmask(SIG_SETMASK, &new_set, &saved_set); - ret = thrd_create(&thread, routine, param); - pthread_sigmask(SIG_SETMASK, &saved_set, NULL); - - if (ret) - return 0; - - return thread; -} - -static inline int pipe_thread_wait(pipe_thread thread) { - return thrd_join(thread, NULL); -} - -static inline int pipe_thread_destroy(pipe_thread thread) { - return thrd_detach(thread); -} - -/* pipe_mutex - */ -typedef mtx_t pipe_mutex; - -#define pipe_static_mutex(mutex) static pipe_mutex mutex = _MTX_INITIALIZER_NP - -#define pipe_mutex_init(mutex) (void)mtx_init(&(mutex), mtx_plain) - -#define pipe_mutex_destroy(mutex) mtx_destroy(&(mutex)) - -#define pipe_mutex_lock(mutex) (void)mtx_lock(&(mutex)) - -#define pipe_mutex_unlock(mutex) (void)mtx_unlock(&(mutex)) - -/* pipe_condvar - */ -typedef cnd_t pipe_condvar; - -#define pipe_condvar_init(cond) cnd_init(&(cond)) - -#define pipe_condvar_destroy(cond) cnd_destroy(&(cond)) - -#define pipe_condvar_wait(cond, mutex) cnd_wait(&(cond), &(mutex)) - -#define pipe_condvar_signal(cond) cnd_signal(&(cond)) - -#define pipe_condvar_broadcast(cond) cnd_broadcast(&(cond)) - -/* - * pipe_barrier - */ - -#if (defined(PIPE_OS_LINUX) || defined(PIPE_OS_BSD) || \ - defined(PIPE_OS_SOLARIS) || defined(PIPE_OS_HURD)) && \ - !defined(PIPE_OS_ANDROID) - -typedef pthread_barrier_t pipe_barrier; - -static inline void pipe_barrier_init(pipe_barrier *barrier, unsigned count) { - pthread_barrier_init(barrier, NULL, count); -} - -static inline void pipe_barrier_destroy(pipe_barrier *barrier) { - pthread_barrier_destroy(barrier); -} - -static inline void pipe_barrier_wait(pipe_barrier *barrier) { - pthread_barrier_wait(barrier); -} - -#else /* If the OS doesn't have its own, implement barriers using a mutex and \ - a condvar */ - -typedef struct { - unsigned count; - unsigned waiters; - uint64_t sequence; - pipe_mutex mutex; - pipe_condvar condvar; -} pipe_barrier; - -static inline void pipe_barrier_init(pipe_barrier *barrier, unsigned count) { - barrier->count = count; - barrier->waiters = 0; - barrier->sequence = 0; - pipe_mutex_init(barrier->mutex); - pipe_condvar_init(barrier->condvar); -} - -static inline void pipe_barrier_destroy(pipe_barrier *barrier) { - assert(barrier->waiters == 0); - pipe_mutex_destroy(barrier->mutex); - pipe_condvar_destroy(barrier->condvar); -} - -static inline void pipe_barrier_wait(pipe_barrier *barrier) { - pipe_mutex_lock(barrier->mutex); - - assert(barrier->waiters < barrier->count); - barrier->waiters++; - - if (barrier->waiters < barrier->count) { - uint64_t sequence = barrier->sequence; - - do { - pipe_condvar_wait(barrier->condvar, barrier->mutex); - } while (sequence == barrier->sequence); - } else { - barrier->waiters = 0; - barrier->sequence++; - pipe_condvar_broadcast(barrier->condvar); - } - - pipe_mutex_unlock(barrier->mutex); -} - -#endif - -/* - * Semaphores - */ - -typedef struct { - pipe_mutex mutex; - pipe_condvar cond; - int counter; -} pipe_semaphore; - -static inline void pipe_semaphore_init(pipe_semaphore *sema, int init_val) { - pipe_mutex_init(sema->mutex); - pipe_condvar_init(sema->cond); - sema->counter = init_val; -} - -static inline void pipe_semaphore_destroy(pipe_semaphore *sema) { - pipe_mutex_destroy(sema->mutex); - pipe_condvar_destroy(sema->cond); -} - -/** Signal/increment semaphore counter */ -static inline void pipe_semaphore_signal(pipe_semaphore *sema) { - pipe_mutex_lock(sema->mutex); - sema->counter++; - pipe_condvar_signal(sema->cond); - pipe_mutex_unlock(sema->mutex); -} - -/** Wait for semaphore counter to be greater than zero */ -static inline void pipe_semaphore_wait(pipe_semaphore *sema) { - pipe_mutex_lock(sema->mutex); - while (sema->counter <= 0) { - pipe_condvar_wait(sema->cond, sema->mutex); - } - sema->counter--; - pipe_mutex_unlock(sema->mutex); -} - -/* - * Thread-specific data. - */ - -typedef struct { - tss_t key; - int initMagic; -} pipe_tsd; - -#define PIPE_TSD_INIT_MAGIC 0xff8adc98 - -static inline void pipe_tsd_init(pipe_tsd *tsd) { - if (tss_create(&tsd->key, NULL /*free*/) != 0) { - exit(-1); - } - tsd->initMagic = PIPE_TSD_INIT_MAGIC; -} - -static inline void *pipe_tsd_get(pipe_tsd *tsd) { - if (tsd->initMagic != (int)PIPE_TSD_INIT_MAGIC) { - pipe_tsd_init(tsd); - } - return tss_get(tsd->key); -} - -static inline void pipe_tsd_set(pipe_tsd *tsd, void *value) { - if (tsd->initMagic != (int)PIPE_TSD_INIT_MAGIC) { - pipe_tsd_init(tsd); - } - if (tss_set(tsd->key, value) != 0) { - exit(-1); - } -} - -#endif /* OS_THREAD_H_ */ diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_build.c b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_build.c deleted file mode 100644 index 8bca2f763..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_build.c +++ /dev/null @@ -1,1138 +0,0 @@ -/************************************************************************** - * - * Copyright 2007 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -#include "tgsi_build.h" -#include "pipe/p_format.h" -#include "pipe/p_shader_tokens.h" -#include "tgsi_parse.h" -#include "util/u_debug.h" - -/* - * header - */ - -struct tgsi_header tgsi_build_header(void) { - struct tgsi_header header; - - header.HeaderSize = 1; - header.BodySize = 0; - - return header; -} - -static void header_headersize_grow(struct tgsi_header *header) { - assert(header->HeaderSize < 0xFF); - assert(header->BodySize == 0); - - header->HeaderSize++; -} - -static void header_bodysize_grow(struct tgsi_header *header) { - assert(header->BodySize < 0xFFFFFF); - - header->BodySize++; -} - -struct tgsi_processor tgsi_build_processor(unsigned type, - struct tgsi_header *header) { - struct tgsi_processor processor; - - processor.Processor = type; - processor.Padding = 0; - - header_headersize_grow(header); - - return processor; -} - -/* - * declaration - */ - -static void declaration_grow(struct tgsi_declaration *declaration, - struct tgsi_header *header) { - assert(declaration->NrTokens < 0xFF); - - declaration->NrTokens++; - - header_bodysize_grow(header); -} - -static struct tgsi_declaration tgsi_default_declaration(void) { - struct tgsi_declaration declaration; - - declaration.Type = TGSI_TOKEN_TYPE_DECLARATION; - declaration.NrTokens = 1; - declaration.File = TGSI_FILE_NULL; - declaration.UsageMask = TGSI_WRITEMASK_XYZW; - declaration.Interpolate = 0; - declaration.Dimension = 0; - declaration.Semantic = 0; - declaration.Invariant = 0; - declaration.Local = 0; - declaration.Array = 0; - declaration.Atomic = 0; - declaration.MemType = TGSI_MEMORY_TYPE_GLOBAL; - declaration.Padding = 0; - - return declaration; -} - -static struct tgsi_declaration -tgsi_build_declaration(unsigned file, unsigned usage_mask, unsigned interpolate, - unsigned dimension, unsigned semantic, - unsigned invariant, unsigned local, unsigned array, - unsigned atomic, unsigned memtype, - struct tgsi_header *header) { - struct tgsi_declaration declaration; - - assert(file < TGSI_FILE_COUNT); - assert(interpolate < TGSI_INTERPOLATE_COUNT); - - declaration = tgsi_default_declaration(); - declaration.File = file; - declaration.UsageMask = usage_mask; - declaration.Interpolate = interpolate; - declaration.Dimension = dimension; - declaration.Semantic = semantic; - declaration.Invariant = invariant; - declaration.Local = local; - declaration.Array = array; - declaration.Atomic = atomic; - declaration.MemType = memtype; - header_bodysize_grow(header); - - return declaration; -} - -static struct tgsi_declaration_range tgsi_default_declaration_range(void) { - struct tgsi_declaration_range dr; - - dr.First = 0; - dr.Last = 0; - - return dr; -} - -static struct tgsi_declaration_range -tgsi_build_declaration_range(unsigned first, unsigned last, - struct tgsi_declaration *declaration, - struct tgsi_header *header) { - struct tgsi_declaration_range declaration_range; - - assert(last >= first); - assert(last <= 0xFFFF); - - declaration_range.First = first; - declaration_range.Last = last; - - declaration_grow(declaration, header); - - return declaration_range; -} - -static struct tgsi_declaration_dimension -tgsi_build_declaration_dimension(unsigned index_2d, - struct tgsi_declaration *declaration, - struct tgsi_header *header) { - struct tgsi_declaration_dimension dd; - - assert(index_2d <= 0xFFFF); - - dd.Index2D = index_2d; - dd.Padding = 0; - - declaration_grow(declaration, header); - - return dd; -} - -static struct tgsi_declaration_interp tgsi_default_declaration_interp(void) { - struct tgsi_declaration_interp di; - - di.Interpolate = TGSI_INTERPOLATE_CONSTANT; - di.Location = TGSI_INTERPOLATE_LOC_CENTER; - di.CylindricalWrap = 0; - di.Padding = 0; - - return di; -} - -static struct tgsi_declaration_interp tgsi_build_declaration_interp( - unsigned interpolate, unsigned interpolate_location, - unsigned cylindrical_wrap, struct tgsi_declaration *declaration, - struct tgsi_header *header) { - struct tgsi_declaration_interp di; - - di.Interpolate = interpolate; - di.Location = interpolate_location; - di.CylindricalWrap = cylindrical_wrap; - di.Padding = 0; - - declaration_grow(declaration, header); - - return di; -} - -static struct tgsi_declaration_semantic -tgsi_default_declaration_semantic(void) { - struct tgsi_declaration_semantic ds; - - ds.Name = TGSI_SEMANTIC_POSITION; - ds.Index = 0; - ds.StreamX = 0; - ds.StreamY = 0; - ds.StreamZ = 0; - ds.StreamW = 0; - - return ds; -} - -static struct tgsi_declaration_semantic tgsi_build_declaration_semantic( - unsigned semantic_name, unsigned semantic_index, unsigned streamx, - unsigned streamy, unsigned streamz, unsigned streamw, - struct tgsi_declaration *declaration, struct tgsi_header *header) { - struct tgsi_declaration_semantic ds; - - assert(semantic_name <= TGSI_SEMANTIC_COUNT); - assert(semantic_index <= 0xFFFF); - - ds.Name = semantic_name; - ds.Index = semantic_index; - ds.StreamX = streamx; - ds.StreamY = streamy; - ds.StreamZ = streamz; - ds.StreamW = streamw; - - declaration_grow(declaration, header); - - return ds; -} - -static struct tgsi_declaration_image tgsi_default_declaration_image(void) { - struct tgsi_declaration_image di; - - di.Resource = TGSI_TEXTURE_BUFFER; - di.Raw = 0; - di.Writable = 0; - di.Format = 0; - di.Padding = 0; - - return di; -} - -static struct tgsi_declaration_image tgsi_build_declaration_image( - unsigned texture, unsigned format, unsigned raw, unsigned writable, - struct tgsi_declaration *declaration, struct tgsi_header *header) { - struct tgsi_declaration_image di; - - di = tgsi_default_declaration_image(); - di.Resource = texture; - di.Format = format; - di.Raw = raw; - di.Writable = writable; - - declaration_grow(declaration, header); - - return di; -} - -static struct tgsi_declaration_sampler_view -tgsi_default_declaration_sampler_view(void) { - struct tgsi_declaration_sampler_view dsv; - - dsv.Resource = TGSI_TEXTURE_BUFFER; - dsv.ReturnTypeX = TGSI_RETURN_TYPE_UNORM; - dsv.ReturnTypeY = TGSI_RETURN_TYPE_UNORM; - dsv.ReturnTypeZ = TGSI_RETURN_TYPE_UNORM; - dsv.ReturnTypeW = TGSI_RETURN_TYPE_UNORM; - - return dsv; -} - -static struct tgsi_declaration_sampler_view tgsi_build_declaration_sampler_view( - unsigned texture, unsigned return_type_x, unsigned return_type_y, - unsigned return_type_z, unsigned return_type_w, - struct tgsi_declaration *declaration, struct tgsi_header *header) { - struct tgsi_declaration_sampler_view dsv; - - dsv = tgsi_default_declaration_sampler_view(); - dsv.Resource = texture; - dsv.ReturnTypeX = return_type_x; - dsv.ReturnTypeY = return_type_y; - dsv.ReturnTypeZ = return_type_z; - dsv.ReturnTypeW = return_type_w; - - declaration_grow(declaration, header); - - return dsv; -} - -static struct tgsi_declaration_array tgsi_default_declaration_array(void) { - struct tgsi_declaration_array a; - - a.ArrayID = 0; - a.Padding = 0; - - return a; -} - -static struct tgsi_declaration_array -tgsi_build_declaration_array(unsigned arrayid, - struct tgsi_declaration *declaration, - struct tgsi_header *header) { - struct tgsi_declaration_array da; - - da = tgsi_default_declaration_array(); - da.ArrayID = arrayid; - - declaration_grow(declaration, header); - - return da; -} - -struct tgsi_full_declaration tgsi_default_full_declaration(void) { - struct tgsi_full_declaration full_declaration; - - full_declaration.Declaration = tgsi_default_declaration(); - full_declaration.Range = tgsi_default_declaration_range(); - full_declaration.Semantic = tgsi_default_declaration_semantic(); - full_declaration.Interp = tgsi_default_declaration_interp(); - full_declaration.Image = tgsi_default_declaration_image(); - full_declaration.SamplerView = tgsi_default_declaration_sampler_view(); - full_declaration.Array = tgsi_default_declaration_array(); - - return full_declaration; -} - -unsigned -tgsi_build_full_declaration(const struct tgsi_full_declaration *full_decl, - struct tgsi_token *tokens, - struct tgsi_header *header, unsigned maxsize) { - unsigned size = 0; - struct tgsi_declaration *declaration; - struct tgsi_declaration_range *dr; - - if (maxsize <= size) - return 0; - declaration = (struct tgsi_declaration *)&tokens[size]; - size++; - - *declaration = tgsi_build_declaration( - full_decl->Declaration.File, full_decl->Declaration.UsageMask, - full_decl->Declaration.Interpolate, full_decl->Declaration.Dimension, - full_decl->Declaration.Semantic, full_decl->Declaration.Invariant, - full_decl->Declaration.Local, full_decl->Declaration.Array, - full_decl->Declaration.Atomic, full_decl->Declaration.MemType, header); - - if (maxsize <= size) - return 0; - dr = (struct tgsi_declaration_range *)&tokens[size]; - size++; - - *dr = tgsi_build_declaration_range( - full_decl->Range.First, full_decl->Range.Last, declaration, header); - - if (full_decl->Declaration.Dimension) { - struct tgsi_declaration_dimension *dd; - - if (maxsize <= size) { - return 0; - } - dd = (struct tgsi_declaration_dimension *)&tokens[size]; - size++; - - *dd = tgsi_build_declaration_dimension(full_decl->Dim.Index2D, declaration, - header); - } - - if (full_decl->Declaration.Interpolate) { - struct tgsi_declaration_interp *di; - - if (maxsize <= size) { - return 0; - } - di = (struct tgsi_declaration_interp *)&tokens[size]; - size++; - - *di = tgsi_build_declaration_interp( - full_decl->Interp.Interpolate, full_decl->Interp.Location, - full_decl->Interp.CylindricalWrap, declaration, header); - } - - if (full_decl->Declaration.Semantic) { - struct tgsi_declaration_semantic *ds; - - if (maxsize <= size) - return 0; - ds = (struct tgsi_declaration_semantic *)&tokens[size]; - size++; - - *ds = tgsi_build_declaration_semantic( - full_decl->Semantic.Name, full_decl->Semantic.Index, - full_decl->Semantic.StreamX, full_decl->Semantic.StreamY, - full_decl->Semantic.StreamZ, full_decl->Semantic.StreamW, declaration, - header); - } - - if (full_decl->Declaration.File == TGSI_FILE_IMAGE) { - struct tgsi_declaration_image *di; - - if (maxsize <= size) { - return 0; - } - di = (struct tgsi_declaration_image *)&tokens[size]; - size++; - - *di = tgsi_build_declaration_image( - full_decl->Image.Resource, full_decl->Image.Format, - full_decl->Image.Raw, full_decl->Image.Writable, declaration, header); - } - - if (full_decl->Declaration.File == TGSI_FILE_SAMPLER_VIEW) { - struct tgsi_declaration_sampler_view *dsv; - - if (maxsize <= size) { - return 0; - } - dsv = (struct tgsi_declaration_sampler_view *)&tokens[size]; - size++; - - *dsv = tgsi_build_declaration_sampler_view( - full_decl->SamplerView.Resource, full_decl->SamplerView.ReturnTypeX, - full_decl->SamplerView.ReturnTypeY, full_decl->SamplerView.ReturnTypeZ, - full_decl->SamplerView.ReturnTypeW, declaration, header); - } - - if (full_decl->Declaration.Array) { - struct tgsi_declaration_array *da; - - if (maxsize <= size) { - return 0; - } - da = (struct tgsi_declaration_array *)&tokens[size]; - size++; - *da = tgsi_build_declaration_array(full_decl->Array.ArrayID, declaration, - header); - } - return size; -} - -/* - * immediate - */ - -static struct tgsi_immediate tgsi_default_immediate(void) { - struct tgsi_immediate immediate; - - immediate.Type = TGSI_TOKEN_TYPE_IMMEDIATE; - immediate.NrTokens = 1; - immediate.DataType = TGSI_IMM_FLOAT32; - immediate.Padding = 0; - - return immediate; -} - -static struct tgsi_immediate tgsi_build_immediate(struct tgsi_header *header, - unsigned type) { - struct tgsi_immediate immediate; - - immediate = tgsi_default_immediate(); - immediate.DataType = type; - - header_bodysize_grow(header); - - return immediate; -} - -struct tgsi_full_immediate tgsi_default_full_immediate(void) { - struct tgsi_full_immediate fullimm; - - fullimm.Immediate = tgsi_default_immediate(); - fullimm.u[0].Float = 0.0f; - fullimm.u[1].Float = 0.0f; - fullimm.u[2].Float = 0.0f; - fullimm.u[3].Float = 0.0f; - - return fullimm; -} - -static void immediate_grow(struct tgsi_immediate *immediate, - struct tgsi_header *header) { - assert(immediate->NrTokens < 0xFF); - - immediate->NrTokens++; - - header_bodysize_grow(header); -} - -unsigned tgsi_build_full_immediate(const struct tgsi_full_immediate *full_imm, - struct tgsi_token *tokens, - struct tgsi_header *header, - unsigned maxsize) { - unsigned size = 0; - int i; - struct tgsi_immediate *immediate; - - if (maxsize <= size) - return 0; - immediate = (struct tgsi_immediate *)&tokens[size]; - size++; - - *immediate = tgsi_build_immediate(header, full_imm->Immediate.DataType); - - assert(full_imm->Immediate.NrTokens <= 4 + 1); - - for (i = 0; i < full_imm->Immediate.NrTokens - 1; i++) { - union tgsi_immediate_data *data; - - if (maxsize <= size) - return 0; - - data = (union tgsi_immediate_data *)&tokens[size]; - *data = full_imm->u[i]; - - immediate_grow(immediate, header); - size++; - } - - return size; -} - -/* - * instruction - */ - -struct tgsi_instruction tgsi_default_instruction(void) { - struct tgsi_instruction instruction; - - instruction.Type = TGSI_TOKEN_TYPE_INSTRUCTION; - instruction.NrTokens = 0; - instruction.Opcode = TGSI_OPCODE_MOV; - instruction.Saturate = 0; - instruction.NumDstRegs = 1; - instruction.NumSrcRegs = 1; - instruction.Label = 0; - instruction.Texture = 0; - instruction.Memory = 0; - instruction.Precise = 0; - - return instruction; -} - -static struct tgsi_instruction -tgsi_build_instruction(unsigned opcode, unsigned saturate, unsigned precise, - unsigned num_dst_regs, unsigned num_src_regs, - struct tgsi_header *header) { - struct tgsi_instruction instruction; - - assert(opcode <= TGSI_OPCODE_LAST); - assert(saturate <= 1); - assert(num_dst_regs <= 3); - assert(num_src_regs <= 15); - - instruction = tgsi_default_instruction(); - instruction.Opcode = opcode; - instruction.Saturate = saturate; - instruction.Precise = precise; - instruction.NumDstRegs = num_dst_regs; - instruction.NumSrcRegs = num_src_regs; - - header_bodysize_grow(header); - - return instruction; -} - -static void instruction_grow(struct tgsi_instruction *instruction, - struct tgsi_header *header) { - assert(instruction->NrTokens < 0xFF); - - instruction->NrTokens++; - - header_bodysize_grow(header); -} - -static struct tgsi_instruction_label tgsi_default_instruction_label(void) { - struct tgsi_instruction_label instruction_label; - - instruction_label.Label = 0; - instruction_label.Padding = 0; - - return instruction_label; -} - -static struct tgsi_instruction_label -tgsi_build_instruction_label(unsigned label, - struct tgsi_instruction *instruction, - struct tgsi_header *header) { - struct tgsi_instruction_label instruction_label; - - instruction_label.Label = label; - instruction_label.Padding = 0; - instruction->Label = 1; - - instruction_grow(instruction, header); - - return instruction_label; -} - -static struct tgsi_instruction_texture tgsi_default_instruction_texture(void) { - struct tgsi_instruction_texture instruction_texture; - - instruction_texture.Texture = TGSI_TEXTURE_UNKNOWN; - instruction_texture.NumOffsets = 0; - instruction_texture.Padding = 0; - - return instruction_texture; -} - -static struct tgsi_instruction_texture -tgsi_build_instruction_texture(unsigned texture, unsigned num_offsets, - struct tgsi_instruction *instruction, - struct tgsi_header *header) { - struct tgsi_instruction_texture instruction_texture; - - instruction_texture.Texture = texture; - instruction_texture.NumOffsets = num_offsets; - instruction_texture.Padding = 0; - instruction->Texture = 1; - - instruction_grow(instruction, header); - - return instruction_texture; -} - -static struct tgsi_instruction_memory tgsi_default_instruction_memory(void) { - struct tgsi_instruction_memory instruction_memory; - - instruction_memory.Qualifier = 0; - instruction_memory.Texture = 0; - instruction_memory.Format = 0; - instruction_memory.Padding = 0; - - return instruction_memory; -} - -static struct tgsi_instruction_memory tgsi_build_instruction_memory( - unsigned qualifier, unsigned texture, unsigned format, - struct tgsi_instruction *instruction, struct tgsi_header *header) { - struct tgsi_instruction_memory instruction_memory; - - instruction_memory.Qualifier = qualifier; - instruction_memory.Texture = texture; - instruction_memory.Format = format; - instruction_memory.Padding = 0; - instruction->Memory = 1; - - instruction_grow(instruction, header); - - return instruction_memory; -} - -static struct tgsi_texture_offset tgsi_default_texture_offset(void) { - struct tgsi_texture_offset texture_offset; - - texture_offset.Index = 0; - texture_offset.File = 0; - texture_offset.SwizzleX = 0; - texture_offset.SwizzleY = 0; - texture_offset.SwizzleZ = 0; - texture_offset.Padding = 0; - - return texture_offset; -} - -static struct tgsi_texture_offset -tgsi_build_texture_offset(int index, int file, int swizzle_x, int swizzle_y, - int swizzle_z, struct tgsi_instruction *instruction, - struct tgsi_header *header) { - struct tgsi_texture_offset texture_offset; - - texture_offset.Index = index; - texture_offset.File = file; - texture_offset.SwizzleX = swizzle_x; - texture_offset.SwizzleY = swizzle_y; - texture_offset.SwizzleZ = swizzle_z; - texture_offset.Padding = 0; - - instruction_grow(instruction, header); - - return texture_offset; -} - -static struct tgsi_src_register tgsi_default_src_register(void) { - struct tgsi_src_register src_register; - - src_register.File = TGSI_FILE_NULL; - src_register.SwizzleX = TGSI_SWIZZLE_X; - src_register.SwizzleY = TGSI_SWIZZLE_Y; - src_register.SwizzleZ = TGSI_SWIZZLE_Z; - src_register.SwizzleW = TGSI_SWIZZLE_W; - src_register.Negate = 0; - src_register.Absolute = 0; - src_register.Indirect = 0; - src_register.Dimension = 0; - src_register.Index = 0; - - return src_register; -} - -static struct tgsi_src_register tgsi_build_src_register( - unsigned file, unsigned swizzle_x, unsigned swizzle_y, unsigned swizzle_z, - unsigned swizzle_w, unsigned negate, unsigned absolute, unsigned indirect, - unsigned dimension, int index, struct tgsi_instruction *instruction, - struct tgsi_header *header) { - struct tgsi_src_register src_register; - - assert(file < TGSI_FILE_COUNT); - assert(swizzle_x <= TGSI_SWIZZLE_W); - assert(swizzle_y <= TGSI_SWIZZLE_W); - assert(swizzle_z <= TGSI_SWIZZLE_W); - assert(swizzle_w <= TGSI_SWIZZLE_W); - assert(negate <= 1); - assert(index >= -0x8000 && index <= 0x7FFF); - - src_register.File = file; - src_register.SwizzleX = swizzle_x; - src_register.SwizzleY = swizzle_y; - src_register.SwizzleZ = swizzle_z; - src_register.SwizzleW = swizzle_w; - src_register.Negate = negate; - src_register.Absolute = absolute; - src_register.Indirect = indirect; - src_register.Dimension = dimension; - src_register.Index = index; - - instruction_grow(instruction, header); - - return src_register; -} - -static struct tgsi_ind_register tgsi_default_ind_register(void) { - struct tgsi_ind_register ind_register; - - ind_register.File = TGSI_FILE_NULL; - ind_register.Index = 0; - ind_register.Swizzle = TGSI_SWIZZLE_X; - ind_register.ArrayID = 0; - - return ind_register; -} - -static struct tgsi_ind_register -tgsi_build_ind_register(unsigned file, unsigned swizzle, int index, - unsigned arrayid, struct tgsi_instruction *instruction, - struct tgsi_header *header) { - struct tgsi_ind_register ind_register; - - assert(file < TGSI_FILE_COUNT); - assert(swizzle <= TGSI_SWIZZLE_W); - assert(index >= -0x8000 && index <= 0x7FFF); - - ind_register.File = file; - ind_register.Swizzle = swizzle; - ind_register.Index = index; - ind_register.ArrayID = arrayid; - - instruction_grow(instruction, header); - - return ind_register; -} - -static struct tgsi_dimension tgsi_default_dimension(void) { - struct tgsi_dimension dimension; - - dimension.Indirect = 0; - dimension.Dimension = 0; - dimension.Padding = 0; - dimension.Index = 0; - - return dimension; -} - -static struct tgsi_full_src_register tgsi_default_full_src_register(void) { - struct tgsi_full_src_register full_src_register; - - full_src_register.Register = tgsi_default_src_register(); - full_src_register.Indirect = tgsi_default_ind_register(); - full_src_register.Dimension = tgsi_default_dimension(); - full_src_register.DimIndirect = tgsi_default_ind_register(); - - return full_src_register; -} - -static struct tgsi_dimension -tgsi_build_dimension(unsigned indirect, unsigned index, - struct tgsi_instruction *instruction, - struct tgsi_header *header) { - struct tgsi_dimension dimension; - - dimension.Indirect = indirect; - dimension.Dimension = 0; - dimension.Padding = 0; - dimension.Index = index; - - instruction_grow(instruction, header); - - return dimension; -} - -static struct tgsi_dst_register tgsi_default_dst_register(void) { - struct tgsi_dst_register dst_register; - - dst_register.File = TGSI_FILE_NULL; - dst_register.WriteMask = TGSI_WRITEMASK_XYZW; - dst_register.Indirect = 0; - dst_register.Dimension = 0; - dst_register.Index = 0; - dst_register.Padding = 0; - - return dst_register; -} - -static struct tgsi_dst_register -tgsi_build_dst_register(unsigned file, unsigned mask, unsigned indirect, - unsigned dimension, int index, - struct tgsi_instruction *instruction, - struct tgsi_header *header) { - struct tgsi_dst_register dst_register; - - assert(file < TGSI_FILE_COUNT); - assert(mask <= TGSI_WRITEMASK_XYZW); - assert(index >= -32768 && index <= 32767); - - dst_register.File = file; - dst_register.WriteMask = mask; - dst_register.Indirect = indirect; - dst_register.Dimension = dimension; - dst_register.Index = index; - dst_register.Padding = 0; - - instruction_grow(instruction, header); - - return dst_register; -} - -static struct tgsi_full_dst_register tgsi_default_full_dst_register(void) { - struct tgsi_full_dst_register full_dst_register; - - full_dst_register.Register = tgsi_default_dst_register(); - full_dst_register.Indirect = tgsi_default_ind_register(); - full_dst_register.Dimension = tgsi_default_dimension(); - full_dst_register.DimIndirect = tgsi_default_ind_register(); - - return full_dst_register; -} - -struct tgsi_full_instruction tgsi_default_full_instruction(void) { - struct tgsi_full_instruction full_instruction; - unsigned i; - - full_instruction.Instruction = tgsi_default_instruction(); - full_instruction.Label = tgsi_default_instruction_label(); - full_instruction.Texture = tgsi_default_instruction_texture(); - full_instruction.Memory = tgsi_default_instruction_memory(); - for (i = 0; i < TGSI_FULL_MAX_TEX_OFFSETS; i++) { - full_instruction.TexOffsets[i] = tgsi_default_texture_offset(); - } - for (i = 0; i < TGSI_FULL_MAX_DST_REGISTERS; i++) { - full_instruction.Dst[i] = tgsi_default_full_dst_register(); - } - for (i = 0; i < TGSI_FULL_MAX_SRC_REGISTERS; i++) { - full_instruction.Src[i] = tgsi_default_full_src_register(); - } - - return full_instruction; -} - -unsigned -tgsi_build_full_instruction(const struct tgsi_full_instruction *full_inst, - struct tgsi_token *tokens, - struct tgsi_header *header, unsigned maxsize) { - unsigned size = 0; - unsigned i; - struct tgsi_instruction *instruction; - - if (maxsize <= size) - return 0; - instruction = (struct tgsi_instruction *)&tokens[size]; - size++; - - *instruction = tgsi_build_instruction( - full_inst->Instruction.Opcode, full_inst->Instruction.Saturate, - full_inst->Instruction.Precise, full_inst->Instruction.NumDstRegs, - full_inst->Instruction.NumSrcRegs, header); - - if (full_inst->Instruction.Label) { - struct tgsi_instruction_label *instruction_label; - - if (maxsize <= size) - return 0; - instruction_label = (struct tgsi_instruction_label *)&tokens[size]; - size++; - - *instruction_label = tgsi_build_instruction_label(full_inst->Label.Label, - instruction, header); - } - - if (full_inst->Instruction.Texture) { - struct tgsi_instruction_texture *instruction_texture; - - if (maxsize <= size) - return 0; - instruction_texture = (struct tgsi_instruction_texture *)&tokens[size]; - size++; - - *instruction_texture = tgsi_build_instruction_texture( - full_inst->Texture.Texture, full_inst->Texture.NumOffsets, instruction, - header); - - for (i = 0; i < full_inst->Texture.NumOffsets; i++) { - struct tgsi_texture_offset *texture_offset; - - if (maxsize <= size) - return 0; - texture_offset = (struct tgsi_texture_offset *)&tokens[size]; - size++; - *texture_offset = tgsi_build_texture_offset( - full_inst->TexOffsets[i].Index, full_inst->TexOffsets[i].File, - full_inst->TexOffsets[i].SwizzleX, full_inst->TexOffsets[i].SwizzleY, - full_inst->TexOffsets[i].SwizzleZ, instruction, header); - } - } - - if (full_inst->Instruction.Memory) { - struct tgsi_instruction_memory *instruction_memory; - - if (maxsize <= size) - return 0; - instruction_memory = (struct tgsi_instruction_memory *)&tokens[size]; - size++; - - *instruction_memory = tgsi_build_instruction_memory( - full_inst->Memory.Qualifier, full_inst->Memory.Texture, - full_inst->Memory.Format, instruction, header); - } - - for (i = 0; i < full_inst->Instruction.NumDstRegs; i++) { - const struct tgsi_full_dst_register *reg = &full_inst->Dst[i]; - struct tgsi_dst_register *dst_register; - - if (maxsize <= size) - return 0; - dst_register = (struct tgsi_dst_register *)&tokens[size]; - size++; - - *dst_register = tgsi_build_dst_register( - reg->Register.File, reg->Register.WriteMask, reg->Register.Indirect, - reg->Register.Dimension, reg->Register.Index, instruction, header); - - if (reg->Register.Indirect) { - struct tgsi_ind_register *ind; - - if (maxsize <= size) - return 0; - ind = (struct tgsi_ind_register *)&tokens[size]; - size++; - - *ind = tgsi_build_ind_register(reg->Indirect.File, reg->Indirect.Swizzle, - reg->Indirect.Index, reg->Indirect.ArrayID, - instruction, header); - } - - if (reg->Register.Dimension) { - struct tgsi_dimension *dim; - - assert(!reg->Dimension.Dimension); - - if (maxsize <= size) - return 0; - dim = (struct tgsi_dimension *)&tokens[size]; - size++; - - *dim = tgsi_build_dimension(reg->Dimension.Indirect, reg->Dimension.Index, - instruction, header); - - if (reg->Dimension.Indirect) { - struct tgsi_ind_register *ind; - - if (maxsize <= size) - return 0; - ind = (struct tgsi_ind_register *)&tokens[size]; - size++; - - *ind = tgsi_build_ind_register( - reg->DimIndirect.File, reg->DimIndirect.Swizzle, - reg->DimIndirect.Index, reg->DimIndirect.ArrayID, instruction, - header); - } - } - } - - for (i = 0; i < full_inst->Instruction.NumSrcRegs; i++) { - const struct tgsi_full_src_register *reg = &full_inst->Src[i]; - struct tgsi_src_register *src_register; - - if (maxsize <= size) - return 0; - src_register = (struct tgsi_src_register *)&tokens[size]; - size++; - - *src_register = tgsi_build_src_register( - reg->Register.File, reg->Register.SwizzleX, reg->Register.SwizzleY, - reg->Register.SwizzleZ, reg->Register.SwizzleW, reg->Register.Negate, - reg->Register.Absolute, reg->Register.Indirect, reg->Register.Dimension, - reg->Register.Index, instruction, header); - - if (reg->Register.Indirect) { - struct tgsi_ind_register *ind; - - if (maxsize <= size) - return 0; - ind = (struct tgsi_ind_register *)&tokens[size]; - size++; - - *ind = tgsi_build_ind_register(reg->Indirect.File, reg->Indirect.Swizzle, - reg->Indirect.Index, reg->Indirect.ArrayID, - instruction, header); - } - - if (reg->Register.Dimension) { - struct tgsi_dimension *dim; - - assert(!reg->Dimension.Dimension); - - if (maxsize <= size) - return 0; - dim = (struct tgsi_dimension *)&tokens[size]; - size++; - - *dim = tgsi_build_dimension(reg->Dimension.Indirect, reg->Dimension.Index, - instruction, header); - - if (reg->Dimension.Indirect) { - struct tgsi_ind_register *ind; - - if (maxsize <= size) - return 0; - ind = (struct tgsi_ind_register *)&tokens[size]; - size++; - - *ind = tgsi_build_ind_register( - reg->DimIndirect.File, reg->DimIndirect.Swizzle, - reg->DimIndirect.Index, reg->DimIndirect.ArrayID, instruction, - header); - } - } - } - - return size; -} - -static struct tgsi_property tgsi_default_property(void) { - struct tgsi_property property; - - property.Type = TGSI_TOKEN_TYPE_PROPERTY; - property.NrTokens = 1; - property.PropertyName = TGSI_PROPERTY_GS_INPUT_PRIM; - property.Padding = 0; - - return property; -} - -static struct tgsi_property tgsi_build_property(unsigned property_name, - struct tgsi_header *header) { - struct tgsi_property property; - - property = tgsi_default_property(); - property.PropertyName = property_name; - - header_bodysize_grow(header); - - return property; -} - -struct tgsi_full_property tgsi_default_full_property(void) { - struct tgsi_full_property full_property; - - full_property.Property = tgsi_default_property(); - memset(full_property.u, 0, sizeof(struct tgsi_property_data) * 8); - - return full_property; -} - -static void property_grow(struct tgsi_property *property, - struct tgsi_header *header) { - assert(property->NrTokens < 0xFF); - - property->NrTokens++; - - header_bodysize_grow(header); -} - -static struct tgsi_property_data -tgsi_build_property_data(unsigned value, struct tgsi_property *property, - struct tgsi_header *header) { - struct tgsi_property_data property_data; - - property_data.Data = value; - - property_grow(property, header); - - return property_data; -} - -unsigned tgsi_build_full_property(const struct tgsi_full_property *full_prop, - struct tgsi_token *tokens, - struct tgsi_header *header, - unsigned maxsize) { - unsigned size = 0; - int i; - struct tgsi_property *property; - - if (maxsize <= size) - return 0; - property = (struct tgsi_property *)&tokens[size]; - size++; - - *property = tgsi_build_property(full_prop->Property.PropertyName, header); - - assert(full_prop->Property.NrTokens <= 8 + 1); - - for (i = 0; i < full_prop->Property.NrTokens - 1; i++) { - struct tgsi_property_data *data; - - if (maxsize <= size) - return 0; - data = (struct tgsi_property_data *)&tokens[size]; - size++; - - *data = tgsi_build_property_data(full_prop->u[i].Data, property, header); - } - - return size; -} diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_build.h b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_build.h deleted file mode 100644 index d5da64915..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_build.h +++ /dev/null @@ -1,97 +0,0 @@ -/************************************************************************** - * - * Copyright 2007 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -#ifndef TGSI_BUILD_H -#define TGSI_BUILD_H - -struct tgsi_token; - -#if defined __cplusplus -extern "C" { -#endif - -/* - * header - */ - -struct tgsi_header tgsi_build_header(void); - -struct tgsi_processor tgsi_build_processor(unsigned processor, - struct tgsi_header *header); - -/* - * declaration - */ - -struct tgsi_full_declaration tgsi_default_full_declaration(void); - -unsigned -tgsi_build_full_declaration(const struct tgsi_full_declaration *full_decl, - struct tgsi_token *tokens, - struct tgsi_header *header, unsigned maxsize); - -/* - * immediate - */ - -struct tgsi_full_immediate tgsi_default_full_immediate(void); - -unsigned tgsi_build_full_immediate(const struct tgsi_full_immediate *full_imm, - struct tgsi_token *tokens, - struct tgsi_header *header, - unsigned maxsize); - -/* - * properties - */ - -struct tgsi_full_property tgsi_default_full_property(void); - -unsigned tgsi_build_full_property(const struct tgsi_full_property *full_prop, - struct tgsi_token *tokens, - struct tgsi_header *header, unsigned maxsize); - -/* - * instruction - */ - -struct tgsi_instruction tgsi_default_instruction(void); - -struct tgsi_full_instruction tgsi_default_full_instruction(void); - -unsigned -tgsi_build_full_instruction(const struct tgsi_full_instruction *full_inst, - struct tgsi_token *tokens, - struct tgsi_header *header, unsigned maxsize); - -struct tgsi_instruction_predicate tgsi_default_instruction_predicate(void); - -#if defined __cplusplus -} -#endif - -#endif /* TGSI_BUILD_H */ diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_dump.c b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_dump.c deleted file mode 100644 index e62fc64af..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_dump.c +++ /dev/null @@ -1,744 +0,0 @@ -/************************************************************************** - * - * Copyright 2007-2008 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -#include "tgsi_dump.h" -#include "tgsi_info.h" -#include "tgsi_iterate.h" -#include "tgsi_strings.h" -#include "util/u_debug.h" -#include "util/u_math.h" -#include "util/u_memory.h" -#include "util/u_string.h" - -/** Number of spaces to indent for IF/LOOP/etc */ -static const int indent_spaces = 3; - -struct dump_ctx { - struct tgsi_iterate_context iter; - - boolean dump_float_as_hex; - - uint instno; - uint immno; - int indent; - - uint indentation; - FILE *file; - - void (*dump_printf)(struct dump_ctx *ctx, const char *format, ...); -}; - -static void dump_ctx_printf(struct dump_ctx *ctx, const char *format, ...) { - va_list ap; - (void)ctx; - va_start(ap, format); - if (ctx->file) - vfprintf(ctx->file, format, ap); - else - _debug_vprintf(format, ap); - va_end(ap); -} - -static void dump_enum(struct dump_ctx *ctx, uint e, const char **enums, - uint enum_count) { - if (e >= enum_count) - ctx->dump_printf(ctx, "%u", e); - else - ctx->dump_printf(ctx, "%s", enums[e]); -} - -#define EOL() ctx->dump_printf(ctx, "\n") -#define TXT(S) ctx->dump_printf(ctx, "%s", S) -#define CHR(C) ctx->dump_printf(ctx, "%c", C) -#define UIX(I) ctx->dump_printf(ctx, "0x%x", I) -#define UID(I) ctx->dump_printf(ctx, "%u", I) -#define INSTID(I) ctx->dump_printf(ctx, "% 3u", I) -#define SID(I) ctx->dump_printf(ctx, "%d", I) -#define FLT(F) ctx->dump_printf(ctx, "%10.4f", F) -#define DBL(D) ctx->dump_printf(ctx, "%10.8f", D) -#define HFLT(F) ctx->dump_printf(ctx, "0x%08x", fui((F))) -#define ENM(E, ENUMS) dump_enum(ctx, E, ENUMS, sizeof(ENUMS) / sizeof(*ENUMS)) - -const char *tgsi_swizzle_names[4] = {"x", "y", "z", "w"}; - -static void _dump_register_src(struct dump_ctx *ctx, - const struct tgsi_full_src_register *src) { - TXT(tgsi_file_name(src->Register.File)); - if (src->Register.Dimension) { - if (src->Dimension.Indirect) { - CHR('['); - TXT(tgsi_file_name(src->DimIndirect.File)); - CHR('['); - SID(src->DimIndirect.Index); - TXT("]."); - ENM(src->DimIndirect.Swizzle, tgsi_swizzle_names); - if (src->Dimension.Index != 0) { - if (src->Dimension.Index > 0) - CHR('+'); - SID(src->Dimension.Index); - } - CHR(']'); - if (src->DimIndirect.ArrayID) { - CHR('('); - SID(src->DimIndirect.ArrayID); - CHR(')'); - } - } else { - CHR('['); - SID(src->Dimension.Index); - CHR(']'); - } - } - if (src->Register.Indirect) { - CHR('['); - TXT(tgsi_file_name(src->Indirect.File)); - CHR('['); - SID(src->Indirect.Index); - TXT("]."); - ENM(src->Indirect.Swizzle, tgsi_swizzle_names); - if (src->Register.Index != 0) { - if (src->Register.Index > 0) - CHR('+'); - SID(src->Register.Index); - } - CHR(']'); - if (src->Indirect.ArrayID) { - CHR('('); - SID(src->Indirect.ArrayID); - CHR(')'); - } - } else { - CHR('['); - SID(src->Register.Index); - CHR(']'); - } -} - -static void _dump_register_dst(struct dump_ctx *ctx, - const struct tgsi_full_dst_register *dst) { - TXT(tgsi_file_name(dst->Register.File)); - if (dst->Register.Dimension) { - if (dst->Dimension.Indirect) { - CHR('['); - TXT(tgsi_file_name(dst->DimIndirect.File)); - CHR('['); - SID(dst->DimIndirect.Index); - TXT("]."); - ENM(dst->DimIndirect.Swizzle, tgsi_swizzle_names); - if (dst->Dimension.Index != 0) { - if (dst->Dimension.Index > 0) - CHR('+'); - SID(dst->Dimension.Index); - } - CHR(']'); - if (dst->DimIndirect.ArrayID) { - CHR('('); - SID(dst->DimIndirect.ArrayID); - CHR(')'); - } - } else { - CHR('['); - SID(dst->Dimension.Index); - CHR(']'); - } - } - if (dst->Register.Indirect) { - CHR('['); - TXT(tgsi_file_name(dst->Indirect.File)); - CHR('['); - SID(dst->Indirect.Index); - TXT("]."); - ENM(dst->Indirect.Swizzle, tgsi_swizzle_names); - if (dst->Register.Index != 0) { - if (dst->Register.Index > 0) - CHR('+'); - SID(dst->Register.Index); - } - CHR(']'); - if (dst->Indirect.ArrayID) { - CHR('('); - SID(dst->Indirect.ArrayID); - CHR(')'); - } - } else { - CHR('['); - SID(dst->Register.Index); - CHR(']'); - } -} -static void _dump_writemask(struct dump_ctx *ctx, uint writemask) { - if (writemask != TGSI_WRITEMASK_XYZW) { - CHR('.'); - if (writemask & TGSI_WRITEMASK_X) - CHR('x'); - if (writemask & TGSI_WRITEMASK_Y) - CHR('y'); - if (writemask & TGSI_WRITEMASK_Z) - CHR('z'); - if (writemask & TGSI_WRITEMASK_W) - CHR('w'); - } -} - -static void dump_imm_data(struct tgsi_iterate_context *iter, - union tgsi_immediate_data *data, unsigned num_tokens, - unsigned data_type) { - struct dump_ctx *ctx = (struct dump_ctx *)iter; - unsigned i; - - TXT(" {"); - - assert(num_tokens <= 4); - for (i = 0; i < num_tokens; i++) { - switch (data_type) { - case TGSI_IMM_FLOAT64: { - union di d; - d.ui = data[i].Uint | (uint64_t)data[i + 1].Uint << 32; - DBL(d.d); - i++; - break; - } - case TGSI_IMM_FLOAT32: - if (ctx->dump_float_as_hex) - HFLT(data[i].Float); - else - FLT(data[i].Float); - break; - case TGSI_IMM_UINT32: - UID(data[i].Uint); - break; - case TGSI_IMM_INT32: - SID(data[i].Int); - break; - default: - assert(0); - } - - if (i < num_tokens - 1) - TXT(", "); - } - TXT("}"); -} - -static boolean iter_declaration(struct tgsi_iterate_context *iter, - struct tgsi_full_declaration *decl) { - struct dump_ctx *ctx = (struct dump_ctx *)iter; - boolean patch = decl->Semantic.Name == TGSI_SEMANTIC_PATCH || - decl->Semantic.Name == TGSI_SEMANTIC_TESSINNER || - decl->Semantic.Name == TGSI_SEMANTIC_TESSOUTER || - decl->Semantic.Name == TGSI_SEMANTIC_PRIMID; - - TXT("DCL "); - - TXT(tgsi_file_name(decl->Declaration.File)); - - /* all geometry shader inputs and non-patch tessellation shader inputs are - * two dimensional - */ - if (decl->Declaration.File == TGSI_FILE_INPUT && - (iter->processor.Processor == TGSI_PROCESSOR_GEOMETRY || - (!patch && (iter->processor.Processor == TGSI_PROCESSOR_TESS_CTRL || - iter->processor.Processor == TGSI_PROCESSOR_TESS_EVAL)))) { - TXT("[]"); - } - - /* all non-patch tess ctrl shader outputs are two dimensional */ - if (decl->Declaration.File == TGSI_FILE_OUTPUT && !patch && - iter->processor.Processor == TGSI_PROCESSOR_TESS_CTRL) { - TXT("[]"); - } - - if (decl->Declaration.Dimension) { - CHR('['); - SID(decl->Dim.Index2D); - CHR(']'); - } - - CHR('['); - SID(decl->Range.First); - if (decl->Range.First != decl->Range.Last) { - TXT(".."); - SID(decl->Range.Last); - } - CHR(']'); - - _dump_writemask(ctx, decl->Declaration.UsageMask); - - if (decl->Declaration.Array) { - TXT(", ARRAY("); - SID(decl->Array.ArrayID); - CHR(')'); - } - - if (decl->Declaration.Local) - TXT(", LOCAL"); - - if (decl->Declaration.Semantic) { - TXT(", "); - ENM(decl->Semantic.Name, tgsi_semantic_names); - if (decl->Semantic.Index != 0 || - decl->Semantic.Name == TGSI_SEMANTIC_TEXCOORD || - decl->Semantic.Name == TGSI_SEMANTIC_GENERIC) { - CHR('['); - UID(decl->Semantic.Index); - CHR(']'); - } - } - - if (decl->Declaration.File == TGSI_FILE_IMAGE) { - TXT(", "); - ENM(decl->Image.Resource, tgsi_texture_names); - TXT(", "); - TXT(util_format_name(decl->Image.Format)); - if (decl->Image.Writable) - TXT(", WR"); - if (decl->Image.Raw) - TXT(", RAW"); - } - - if (decl->Declaration.File == TGSI_FILE_BUFFER) { - if (decl->Declaration.Atomic) - TXT(", ATOMIC"); - } - - if (decl->Declaration.File == TGSI_FILE_MEMORY) { - switch (decl->Declaration.MemType) { - /* Note: ,GLOBAL is optional / the default */ - case TGSI_MEMORY_TYPE_GLOBAL: - TXT(", GLOBAL"); - break; - case TGSI_MEMORY_TYPE_SHARED: - TXT(", SHARED"); - break; - case TGSI_MEMORY_TYPE_PRIVATE: - TXT(", PRIVATE"); - break; - case TGSI_MEMORY_TYPE_INPUT: - TXT(", INPUT"); - break; - } - } - - if (decl->Declaration.File == TGSI_FILE_SAMPLER_VIEW) { - TXT(", "); - ENM(decl->SamplerView.Resource, tgsi_texture_names); - TXT(", "); - if ((decl->SamplerView.ReturnTypeX == decl->SamplerView.ReturnTypeY) && - (decl->SamplerView.ReturnTypeX == decl->SamplerView.ReturnTypeZ) && - (decl->SamplerView.ReturnTypeX == decl->SamplerView.ReturnTypeW)) { - ENM(decl->SamplerView.ReturnTypeX, tgsi_return_type_names); - } else { - ENM(decl->SamplerView.ReturnTypeX, tgsi_return_type_names); - TXT(", "); - ENM(decl->SamplerView.ReturnTypeY, tgsi_return_type_names); - TXT(", "); - ENM(decl->SamplerView.ReturnTypeZ, tgsi_return_type_names); - TXT(", "); - ENM(decl->SamplerView.ReturnTypeW, tgsi_return_type_names); - } - } - - if (decl->Declaration.Interpolate) { - if (iter->processor.Processor == TGSI_PROCESSOR_FRAGMENT && - decl->Declaration.File == TGSI_FILE_INPUT) { - TXT(", "); - ENM(decl->Interp.Interpolate, tgsi_interpolate_names); - } - - if (decl->Interp.Location != TGSI_INTERPOLATE_LOC_CENTER) { - TXT(", "); - ENM(decl->Interp.Location, tgsi_interpolate_locations); - } - - if (decl->Interp.CylindricalWrap) { - TXT(", CYLWRAP_"); - if (decl->Interp.CylindricalWrap & TGSI_CYLINDRICAL_WRAP_X) { - CHR('X'); - } - if (decl->Interp.CylindricalWrap & TGSI_CYLINDRICAL_WRAP_Y) { - CHR('Y'); - } - if (decl->Interp.CylindricalWrap & TGSI_CYLINDRICAL_WRAP_Z) { - CHR('Z'); - } - if (decl->Interp.CylindricalWrap & TGSI_CYLINDRICAL_WRAP_W) { - CHR('W'); - } - } - } - - if (decl->Declaration.Invariant) { - TXT(", INVARIANT"); - } - - EOL(); - - return TRUE; -} - -void tgsi_dump_declaration(const struct tgsi_full_declaration *decl) { - struct dump_ctx ctx; - - ctx.dump_printf = dump_ctx_printf; - - iter_declaration(&ctx.iter, (struct tgsi_full_declaration *)decl); -} - -static boolean iter_property(struct tgsi_iterate_context *iter, - struct tgsi_full_property *prop) { - int i; - struct dump_ctx *ctx = (struct dump_ctx *)iter; - - TXT("PROPERTY "); - ENM(prop->Property.PropertyName, tgsi_property_names); - - if (prop->Property.NrTokens > 1) - TXT(" "); - - for (i = 0; i < prop->Property.NrTokens - 1; ++i) { - switch (prop->Property.PropertyName) { - case TGSI_PROPERTY_GS_INPUT_PRIM: - case TGSI_PROPERTY_GS_OUTPUT_PRIM: - ENM(prop->u[i].Data, tgsi_primitive_names); - break; - case TGSI_PROPERTY_FS_COORD_ORIGIN: - ENM(prop->u[i].Data, tgsi_fs_coord_origin_names); - break; - case TGSI_PROPERTY_FS_COORD_PIXEL_CENTER: - ENM(prop->u[i].Data, tgsi_fs_coord_pixel_center_names); - break; - default: - SID(prop->u[i].Data); - break; - } - if (i < prop->Property.NrTokens - 2) - TXT(", "); - } - EOL(); - - return TRUE; -} - -void tgsi_dump_property(const struct tgsi_full_property *prop) { - struct dump_ctx ctx; - - ctx.dump_printf = dump_ctx_printf; - - iter_property(&ctx.iter, (struct tgsi_full_property *)prop); -} - -static boolean iter_immediate(struct tgsi_iterate_context *iter, - struct tgsi_full_immediate *imm) { - struct dump_ctx *ctx = (struct dump_ctx *)iter; - - TXT("IMM["); - SID(ctx->immno++); - TXT("] "); - ENM(imm->Immediate.DataType, tgsi_immediate_type_names); - - dump_imm_data(iter, imm->u, imm->Immediate.NrTokens - 1, - imm->Immediate.DataType); - - EOL(); - - return TRUE; -} - -void tgsi_dump_immediate(const struct tgsi_full_immediate *imm) { - struct dump_ctx ctx; - - ctx.dump_printf = dump_ctx_printf; - - iter_immediate(&ctx.iter, (struct tgsi_full_immediate *)imm); -} - -static boolean iter_instruction(struct tgsi_iterate_context *iter, - struct tgsi_full_instruction *inst) { - struct dump_ctx *ctx = (struct dump_ctx *)iter; - uint instno = ctx->instno++; - const struct tgsi_opcode_info *info = - tgsi_get_opcode_info(inst->Instruction.Opcode); - uint i; - boolean first_reg = TRUE; - - INSTID(instno); - TXT(": "); - - ctx->indent -= info->pre_dedent; - for (i = 0; (int)i < ctx->indent; ++i) - TXT(" "); - ctx->indent += info->post_indent; - - TXT(info->mnemonic); - - if (inst->Instruction.Saturate) { - TXT("_SAT"); - } - - for (i = 0; i < inst->Instruction.NumDstRegs; i++) { - const struct tgsi_full_dst_register *dst = &inst->Dst[i]; - - if (!first_reg) - CHR(','); - CHR(' '); - - _dump_register_dst(ctx, dst); - _dump_writemask(ctx, dst->Register.WriteMask); - - first_reg = FALSE; - } - - for (i = 0; i < inst->Instruction.NumSrcRegs; i++) { - const struct tgsi_full_src_register *src = &inst->Src[i]; - - if (!first_reg) - CHR(','); - CHR(' '); - - if (src->Register.Negate) - CHR('-'); - if (src->Register.Absolute) - CHR('|'); - - _dump_register_src(ctx, src); - - if (src->Register.SwizzleX != TGSI_SWIZZLE_X || - src->Register.SwizzleY != TGSI_SWIZZLE_Y || - src->Register.SwizzleZ != TGSI_SWIZZLE_Z || - src->Register.SwizzleW != TGSI_SWIZZLE_W) { - CHR('.'); - ENM(src->Register.SwizzleX, tgsi_swizzle_names); - ENM(src->Register.SwizzleY, tgsi_swizzle_names); - ENM(src->Register.SwizzleZ, tgsi_swizzle_names); - ENM(src->Register.SwizzleW, tgsi_swizzle_names); - } - - if (src->Register.Absolute) - CHR('|'); - - first_reg = FALSE; - } - - if (inst->Instruction.Texture) { - if (!(inst->Instruction.Opcode >= TGSI_OPCODE_SAMPLE && - inst->Instruction.Opcode <= TGSI_OPCODE_GATHER4)) { - TXT(", "); - ENM(inst->Texture.Texture, tgsi_texture_names); - } - for (i = 0; i < inst->Texture.NumOffsets; i++) { - TXT(", "); - TXT(tgsi_file_name(inst->TexOffsets[i].File)); - CHR('['); - SID(inst->TexOffsets[i].Index); - CHR(']'); - CHR('.'); - ENM(inst->TexOffsets[i].SwizzleX, tgsi_swizzle_names); - ENM(inst->TexOffsets[i].SwizzleY, tgsi_swizzle_names); - ENM(inst->TexOffsets[i].SwizzleZ, tgsi_swizzle_names); - } - } - - if (inst->Instruction.Memory) { - uint32_t qualifier = inst->Memory.Qualifier; - while (qualifier) { - int bit = ffs(qualifier) - 1; - qualifier &= ~(1U << bit); - TXT(", "); - ENM(bit, tgsi_memory_names); - } - if (inst->Memory.Texture) { - TXT(", "); - ENM(inst->Memory.Texture, tgsi_texture_names); - } - if (inst->Memory.Format) { - TXT(", "); - TXT(util_format_name(inst->Memory.Format)); - } - } - - if (inst->Instruction.Label) { - switch (inst->Instruction.Opcode) { - case TGSI_OPCODE_IF: - case TGSI_OPCODE_UIF: - case TGSI_OPCODE_ELSE: - case TGSI_OPCODE_BGNLOOP: - case TGSI_OPCODE_ENDLOOP: - case TGSI_OPCODE_CAL: - case TGSI_OPCODE_BGNSUB: - TXT(" :"); - UID(inst->Label.Label); - break; - } - } - - /* update indentation */ - if (inst->Instruction.Opcode == TGSI_OPCODE_IF || - inst->Instruction.Opcode == TGSI_OPCODE_UIF || - inst->Instruction.Opcode == TGSI_OPCODE_ELSE || - inst->Instruction.Opcode == TGSI_OPCODE_BGNLOOP) { - ctx->indentation += indent_spaces; - } - - EOL(); - - return TRUE; -} - -void tgsi_dump_instruction(const struct tgsi_full_instruction *inst, - uint instno) { - struct dump_ctx ctx; - - ctx.instno = instno; - ctx.immno = instno; - ctx.indent = 0; - ctx.dump_printf = dump_ctx_printf; - ctx.indentation = 0; - ctx.file = NULL; - - iter_instruction(&ctx.iter, (struct tgsi_full_instruction *)inst); -} - -static boolean prolog(struct tgsi_iterate_context *iter) { - struct dump_ctx *ctx = (struct dump_ctx *)iter; - ENM(iter->processor.Processor, tgsi_processor_type_names); - EOL(); - return TRUE; -} - -void tgsi_dump_to_file(const struct tgsi_token *tokens, uint flags, - FILE *file) { - struct dump_ctx ctx; - - ctx.iter.prolog = prolog; - ctx.iter.iterate_instruction = iter_instruction; - ctx.iter.iterate_declaration = iter_declaration; - ctx.iter.iterate_immediate = iter_immediate; - ctx.iter.iterate_property = iter_property; - ctx.iter.epilog = NULL; - - ctx.instno = 0; - ctx.immno = 0; - ctx.indent = 0; - ctx.dump_printf = dump_ctx_printf; - ctx.indentation = 0; - ctx.file = file; - - if (flags & TGSI_DUMP_FLOAT_AS_HEX) - ctx.dump_float_as_hex = TRUE; - else - ctx.dump_float_as_hex = FALSE; - - tgsi_iterate_shader(tokens, &ctx.iter); -} - -void tgsi_dump(const struct tgsi_token *tokens, uint flags) { - tgsi_dump_to_file(tokens, flags, NULL); -} - -struct str_dump_ctx { - struct dump_ctx base; - char *str; - char *ptr; - int left; - bool nospace; -}; - -static void str_dump_ctx_printf(struct dump_ctx *ctx, const char *format, ...) { - struct str_dump_ctx *sctx = (struct str_dump_ctx *)ctx; - - if (sctx->left > 1) { - int written; - va_list ap; - va_start(ap, format); - written = util_vsnprintf(sctx->ptr, sctx->left, format, ap); - va_end(ap); - - /* Some complicated logic needed to handle the return value of - * vsnprintf: - */ - if (written > 0) { - written = MIN2(sctx->left, written); - sctx->ptr += written; - sctx->left -= written; - } - } else - sctx->nospace = true; -} - -bool tgsi_dump_str(const struct tgsi_token *tokens, uint flags, char *str, - size_t size) { - struct str_dump_ctx ctx; - - ctx.base.iter.prolog = prolog; - ctx.base.iter.iterate_instruction = iter_instruction; - ctx.base.iter.iterate_declaration = iter_declaration; - ctx.base.iter.iterate_immediate = iter_immediate; - ctx.base.iter.iterate_property = iter_property; - ctx.base.iter.epilog = NULL; - - ctx.base.instno = 0; - ctx.base.immno = 0; - ctx.base.indent = 0; - ctx.base.dump_printf = &str_dump_ctx_printf; - ctx.base.indentation = 0; - ctx.base.file = NULL; - - ctx.str = str; - ctx.str[0] = 0; - ctx.ptr = str; - ctx.left = (int)size; - ctx.nospace = false; - - if (flags & TGSI_DUMP_FLOAT_AS_HEX) - ctx.base.dump_float_as_hex = TRUE; - else - ctx.base.dump_float_as_hex = FALSE; - - tgsi_iterate_shader(tokens, &ctx.base.iter); - - return !ctx.nospace; -} - -void tgsi_dump_instruction_str(const struct tgsi_full_instruction *inst, - uint instno, char *str, size_t size) { - struct str_dump_ctx ctx; - - ctx.base.instno = instno; - ctx.base.immno = instno; - ctx.base.indent = 0; - ctx.base.dump_printf = &str_dump_ctx_printf; - ctx.base.indentation = 0; - ctx.base.file = NULL; - - ctx.str = str; - ctx.str[0] = 0; - ctx.ptr = str; - ctx.left = (int)size; - ctx.nospace = false; - - iter_instruction(&ctx.base.iter, (struct tgsi_full_instruction *)inst); -} diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_dump.h b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_dump.h deleted file mode 100644 index 9e8d57b0c..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_dump.h +++ /dev/null @@ -1,71 +0,0 @@ -/************************************************************************** - * - * Copyright 2007-2008 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -#ifndef TGSI_DUMP_H -#define TGSI_DUMP_H - -#include "pipe/p_compiler.h" -#include "pipe/p_defines.h" -#include "pipe/p_shader_tokens.h" - -#include - -#if defined __cplusplus -extern "C" { -#endif - -#define TGSI_DUMP_FLOAT_AS_HEX (1 << 0) - -bool tgsi_dump_str(const struct tgsi_token *tokens, uint flags, char *str, - size_t size); - -void tgsi_dump_to_file(const struct tgsi_token *tokens, uint flags, FILE *file); - -void tgsi_dump(const struct tgsi_token *tokens, uint flags); - -struct tgsi_full_immediate; -struct tgsi_full_instruction; -struct tgsi_full_declaration; -struct tgsi_full_property; - -void tgsi_dump_immediate(const struct tgsi_full_immediate *imm); - -void tgsi_dump_instruction_str(const struct tgsi_full_instruction *inst, - uint instno, char *str, size_t size); - -void tgsi_dump_instruction(const struct tgsi_full_instruction *inst, - uint instno); - -void tgsi_dump_declaration(const struct tgsi_full_declaration *decl); - -void tgsi_dump_property(const struct tgsi_full_property *prop); - -#if defined __cplusplus -} -#endif - -#endif /* TGSI_DUMP_H */ diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_info.c b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_info.c deleted file mode 100644 index 700ed27aa..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_info.c +++ /dev/null @@ -1,538 +0,0 @@ -/************************************************************************** - * - * Copyright 2008 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -#include "tgsi_info.h" -#include "util/u_debug.h" -#include "util/u_memory.h" - -#define NONE TGSI_OUTPUT_NONE -#define COMP TGSI_OUTPUT_COMPONENTWISE -#define REPL TGSI_OUTPUT_REPLICATE -#define CHAN TGSI_OUTPUT_CHAN_DEPENDENT -#define OTHR TGSI_OUTPUT_OTHER - -static const struct tgsi_opcode_info opcode_info[TGSI_OPCODE_LAST] = { - {1, 1, 0, 0, 0, 0, COMP, "ARL", TGSI_OPCODE_ARL}, - {1, 1, 0, 0, 0, 0, COMP, "MOV", TGSI_OPCODE_MOV}, - {1, 1, 0, 0, 0, 0, CHAN, "LIT", TGSI_OPCODE_LIT}, - {1, 1, 0, 0, 0, 0, REPL, "RCP", TGSI_OPCODE_RCP}, - {1, 1, 0, 0, 0, 0, REPL, "RSQ", TGSI_OPCODE_RSQ}, - {1, 1, 0, 0, 0, 0, CHAN, "EXP", TGSI_OPCODE_EXP}, - {1, 1, 0, 0, 0, 0, CHAN, "LOG", TGSI_OPCODE_LOG}, - {1, 2, 0, 0, 0, 0, COMP, "MUL", TGSI_OPCODE_MUL}, - {1, 2, 0, 0, 0, 0, COMP, "ADD", TGSI_OPCODE_ADD}, - {1, 2, 0, 0, 0, 0, REPL, "DP3", TGSI_OPCODE_DP3}, - {1, 2, 0, 0, 0, 0, REPL, "DP4", TGSI_OPCODE_DP4}, - {1, 2, 0, 0, 0, 0, CHAN, "DST", TGSI_OPCODE_DST}, - {1, 2, 0, 0, 0, 0, COMP, "MIN", TGSI_OPCODE_MIN}, - {1, 2, 0, 0, 0, 0, COMP, "MAX", TGSI_OPCODE_MAX}, - {1, 2, 0, 0, 0, 0, COMP, "SLT", TGSI_OPCODE_SLT}, - {1, 2, 0, 0, 0, 0, COMP, "SGE", TGSI_OPCODE_SGE}, - {1, 3, 0, 0, 0, 0, COMP, "MAD", TGSI_OPCODE_MAD}, - {1, 2, 0, 0, 0, 0, COMP, "SUB", TGSI_OPCODE_SUB}, - {1, 3, 0, 0, 0, 0, COMP, "LRP", TGSI_OPCODE_LRP}, - {1, 3, 0, 0, 0, 0, COMP, "FMA", TGSI_OPCODE_FMA}, - {1, 1, 0, 0, 0, 0, REPL, "SQRT", TGSI_OPCODE_SQRT}, - {1, 3, 0, 0, 0, 0, REPL, "", 21}, /* removed */ - {0, 0, 0, 0, 0, 0, NONE, "", 22}, /* removed */ - {0, 0, 0, 0, 0, 0, NONE, "", 23}, /* removed */ - {1, 1, 0, 0, 0, 0, COMP, "FRC", TGSI_OPCODE_FRC}, - {1, 3, 0, 0, 0, 0, COMP, "", 25}, /* removed */ - {1, 1, 0, 0, 0, 0, COMP, "FLR", TGSI_OPCODE_FLR}, - {1, 1, 0, 0, 0, 0, COMP, "ROUND", TGSI_OPCODE_ROUND}, - {1, 1, 0, 0, 0, 0, REPL, "EX2", TGSI_OPCODE_EX2}, - {1, 1, 0, 0, 0, 0, REPL, "LG2", TGSI_OPCODE_LG2}, - {1, 2, 0, 0, 0, 0, REPL, "POW", TGSI_OPCODE_POW}, - {1, 2, 0, 0, 0, 0, COMP, "XPD", TGSI_OPCODE_XPD}, - {0, 0, 0, 0, 0, 0, NONE, "", 32}, /* removed */ - {1, 1, 0, 0, 0, 0, COMP, "ABS", TGSI_OPCODE_ABS}, - {0, 0, 0, 0, 0, 0, NONE, "", 34}, /* removed */ - {1, 2, 0, 0, 0, 0, REPL, "DPH", TGSI_OPCODE_DPH}, - {1, 1, 0, 0, 0, 0, REPL, "COS", TGSI_OPCODE_COS}, - {1, 1, 0, 0, 0, 0, COMP, "DDX", TGSI_OPCODE_DDX}, - {1, 1, 0, 0, 0, 0, COMP, "DDY", TGSI_OPCODE_DDY}, - {0, 0, 0, 0, 0, 0, NONE, "KILL", TGSI_OPCODE_KILL}, - {1, 1, 0, 0, 0, 0, COMP, "PK2H", TGSI_OPCODE_PK2H}, - {1, 1, 0, 0, 0, 0, COMP, "PK2US", TGSI_OPCODE_PK2US}, - {1, 1, 0, 0, 0, 0, COMP, "PK4B", TGSI_OPCODE_PK4B}, - {1, 1, 0, 0, 0, 0, COMP, "PK4UB", TGSI_OPCODE_PK4UB}, - {0, 1, 0, 0, 0, 1, NONE, "", 44}, /* removed */ - {1, 2, 0, 0, 0, 0, COMP, "SEQ", TGSI_OPCODE_SEQ}, - {0, 1, 0, 0, 0, 1, NONE, "", 46}, /* removed */ - {1, 2, 0, 0, 0, 0, COMP, "SGT", TGSI_OPCODE_SGT}, - {1, 1, 0, 0, 0, 0, REPL, "SIN", TGSI_OPCODE_SIN}, - {1, 2, 0, 0, 0, 0, COMP, "SLE", TGSI_OPCODE_SLE}, - {1, 2, 0, 0, 0, 0, COMP, "SNE", TGSI_OPCODE_SNE}, - {0, 1, 0, 0, 0, 1, NONE, "", 51}, /* removed */ - {1, 2, 1, 0, 0, 0, OTHR, "TEX", TGSI_OPCODE_TEX}, - {1, 4, 1, 0, 0, 0, OTHR, "TXD", TGSI_OPCODE_TXD}, - {1, 2, 1, 0, 0, 0, OTHR, "TXP", TGSI_OPCODE_TXP}, - {1, 1, 0, 0, 0, 0, COMP, "UP2H", TGSI_OPCODE_UP2H}, - {1, 1, 0, 0, 0, 0, COMP, "UP2US", TGSI_OPCODE_UP2US}, - {1, 1, 0, 0, 0, 0, COMP, "UP4B", TGSI_OPCODE_UP4B}, - {1, 1, 0, 0, 0, 0, COMP, "UP4UB", TGSI_OPCODE_UP4UB}, - {0, 1, 0, 0, 0, 1, NONE, "", 59}, /* removed */ - {0, 1, 0, 0, 0, 1, NONE, "", 60}, /* removed */ - {1, 1, 0, 0, 0, 0, COMP, "ARR", TGSI_OPCODE_ARR}, - {0, 1, 0, 0, 0, 1, NONE, "", 62}, /* removed */ - {0, 0, 0, 1, 0, 0, NONE, "CAL", TGSI_OPCODE_CAL}, - {0, 0, 0, 0, 0, 0, NONE, "RET", TGSI_OPCODE_RET}, - {1, 1, 0, 0, 0, 0, COMP, "SSG", TGSI_OPCODE_SSG}, - {1, 3, 0, 0, 0, 0, COMP, "CMP", TGSI_OPCODE_CMP}, - {1, 1, 0, 0, 0, 0, CHAN, "SCS", TGSI_OPCODE_SCS}, - {1, 2, 1, 0, 0, 0, OTHR, "TXB", TGSI_OPCODE_TXB}, - {1, 1, 0, 0, 0, 0, OTHR, "FBFETCH", TGSI_OPCODE_FBFETCH}, - {1, 2, 0, 0, 0, 0, COMP, "DIV", TGSI_OPCODE_DIV}, - {1, 2, 0, 0, 0, 0, REPL, "DP2", TGSI_OPCODE_DP2}, - {1, 2, 1, 0, 0, 0, OTHR, "TXL", TGSI_OPCODE_TXL}, - {0, 0, 0, 0, 0, 0, NONE, "BRK", TGSI_OPCODE_BRK}, - {0, 1, 0, 1, 0, 1, NONE, "IF", TGSI_OPCODE_IF}, - {0, 1, 0, 1, 0, 1, NONE, "UIF", TGSI_OPCODE_UIF}, - {0, 1, 0, 0, 0, 1, NONE, "", 76}, /* removed */ - {0, 0, 0, 1, 1, 1, NONE, "ELSE", TGSI_OPCODE_ELSE}, - {0, 0, 0, 0, 1, 0, NONE, "ENDIF", TGSI_OPCODE_ENDIF}, - {1, 1, 0, 0, 0, 0, COMP, "DDX_FINE", TGSI_OPCODE_DDX_FINE}, - {1, 1, 0, 0, 0, 0, COMP, "DDY_FINE", TGSI_OPCODE_DDY_FINE}, - {0, 0, 0, 0, 0, 0, NONE, "", 81}, /* removed */ - {0, 0, 0, 0, 0, 0, NONE, "", 82}, /* removed */ - {1, 1, 0, 0, 0, 0, COMP, "CEIL", TGSI_OPCODE_CEIL}, - {1, 1, 0, 0, 0, 0, COMP, "I2F", TGSI_OPCODE_I2F}, - {1, 1, 0, 0, 0, 0, COMP, "NOT", TGSI_OPCODE_NOT}, - {1, 1, 0, 0, 0, 0, COMP, "TRUNC", TGSI_OPCODE_TRUNC}, - {1, 2, 0, 0, 0, 0, COMP, "SHL", TGSI_OPCODE_SHL}, - {0, 0, 0, 0, 0, 0, NONE, "", 88}, /* removed */ - {1, 2, 0, 0, 0, 0, COMP, "AND", TGSI_OPCODE_AND}, - {1, 2, 0, 0, 0, 0, COMP, "OR", TGSI_OPCODE_OR}, - {1, 2, 0, 0, 0, 0, COMP, "MOD", TGSI_OPCODE_MOD}, - {1, 2, 0, 0, 0, 0, COMP, "XOR", TGSI_OPCODE_XOR}, - {0, 0, 0, 0, 0, 0, COMP, "", 93}, /* removed */ - {1, 2, 1, 0, 0, 0, OTHR, "TXF", TGSI_OPCODE_TXF}, - {1, 2, 1, 0, 0, 0, OTHR, "TXQ", TGSI_OPCODE_TXQ}, - {0, 0, 0, 0, 0, 0, NONE, "CONT", TGSI_OPCODE_CONT}, - {0, 1, 0, 0, 0, 0, NONE, "EMIT", TGSI_OPCODE_EMIT}, - {0, 1, 0, 0, 0, 0, NONE, "ENDPRIM", TGSI_OPCODE_ENDPRIM}, - {0, 0, 0, 1, 0, 1, NONE, "BGNLOOP", TGSI_OPCODE_BGNLOOP}, - {0, 0, 0, 0, 0, 1, NONE, "BGNSUB", TGSI_OPCODE_BGNSUB}, - {0, 0, 0, 1, 1, 0, NONE, "ENDLOOP", TGSI_OPCODE_ENDLOOP}, - {0, 0, 0, 0, 1, 0, NONE, "ENDSUB", TGSI_OPCODE_ENDSUB}, - {0, 0, 0, 0, 0, 0, OTHR, "", 103}, /* removed */ - {1, 1, 1, 0, 0, 0, OTHR, "TXQS", TGSI_OPCODE_TXQS}, - {1, 1, 0, 0, 0, 0, OTHR, "RESQ", TGSI_OPCODE_RESQ}, - {0, 0, 0, 0, 0, 0, NONE, "", 106}, /* removed */ - {0, 0, 0, 0, 0, 0, NONE, "NOP", TGSI_OPCODE_NOP}, - {1, 2, 0, 0, 0, 0, COMP, "FSEQ", TGSI_OPCODE_FSEQ}, - {1, 2, 0, 0, 0, 0, COMP, "FSGE", TGSI_OPCODE_FSGE}, - {1, 2, 0, 0, 0, 0, COMP, "FSLT", TGSI_OPCODE_FSLT}, - {1, 2, 0, 0, 0, 0, COMP, "FSNE", TGSI_OPCODE_FSNE}, - {0, 1, 0, 0, 0, 0, OTHR, "MEMBAR", TGSI_OPCODE_MEMBAR}, - {0, 1, 0, 0, 0, 0, NONE, "", 113}, /* removed */ - {0, 1, 0, 0, 0, 0, NONE, "", 114}, /* removed */ - {0, 1, 0, 0, 0, 0, NONE, "", 115}, /* removed */ - {0, 1, 0, 0, 0, 0, NONE, "KILL_IF", TGSI_OPCODE_KILL_IF}, - {0, 0, 0, 0, 0, 0, NONE, "END", TGSI_OPCODE_END}, - {1, 3, 0, 0, 0, 0, COMP, "DFMA", TGSI_OPCODE_DFMA}, - {1, 1, 0, 0, 0, 0, COMP, "F2I", TGSI_OPCODE_F2I}, - {1, 2, 0, 0, 0, 0, COMP, "IDIV", TGSI_OPCODE_IDIV}, - {1, 2, 0, 0, 0, 0, COMP, "IMAX", TGSI_OPCODE_IMAX}, - {1, 2, 0, 0, 0, 0, COMP, "IMIN", TGSI_OPCODE_IMIN}, - {1, 1, 0, 0, 0, 0, COMP, "INEG", TGSI_OPCODE_INEG}, - {1, 2, 0, 0, 0, 0, COMP, "ISGE", TGSI_OPCODE_ISGE}, - {1, 2, 0, 0, 0, 0, COMP, "ISHR", TGSI_OPCODE_ISHR}, - {1, 2, 0, 0, 0, 0, COMP, "ISLT", TGSI_OPCODE_ISLT}, - {1, 1, 0, 0, 0, 0, COMP, "F2U", TGSI_OPCODE_F2U}, - {1, 1, 0, 0, 0, 0, COMP, "U2F", TGSI_OPCODE_U2F}, - {1, 2, 0, 0, 0, 0, COMP, "UADD", TGSI_OPCODE_UADD}, - {1, 2, 0, 0, 0, 0, COMP, "UDIV", TGSI_OPCODE_UDIV}, - {1, 3, 0, 0, 0, 0, COMP, "UMAD", TGSI_OPCODE_UMAD}, - {1, 2, 0, 0, 0, 0, COMP, "UMAX", TGSI_OPCODE_UMAX}, - {1, 2, 0, 0, 0, 0, COMP, "UMIN", TGSI_OPCODE_UMIN}, - {1, 2, 0, 0, 0, 0, COMP, "UMOD", TGSI_OPCODE_UMOD}, - {1, 2, 0, 0, 0, 0, COMP, "UMUL", TGSI_OPCODE_UMUL}, - {1, 2, 0, 0, 0, 0, COMP, "USEQ", TGSI_OPCODE_USEQ}, - {1, 2, 0, 0, 0, 0, COMP, "USGE", TGSI_OPCODE_USGE}, - {1, 2, 0, 0, 0, 0, COMP, "USHR", TGSI_OPCODE_USHR}, - {1, 2, 0, 0, 0, 0, COMP, "USLT", TGSI_OPCODE_USLT}, - {1, 2, 0, 0, 0, 0, COMP, "USNE", TGSI_OPCODE_USNE}, - {0, 1, 0, 0, 0, 0, NONE, "SWITCH", TGSI_OPCODE_SWITCH}, - {0, 1, 0, 0, 0, 0, NONE, "CASE", TGSI_OPCODE_CASE}, - {0, 0, 0, 0, 0, 0, NONE, "DEFAULT", TGSI_OPCODE_DEFAULT}, - {0, 0, 0, 0, 0, 0, NONE, "ENDSWITCH", TGSI_OPCODE_ENDSWITCH}, - - {1, 3, 0, 0, 0, 0, OTHR, "SAMPLE", TGSI_OPCODE_SAMPLE}, - {1, 2, 0, 0, 0, 0, OTHR, "SAMPLE_I", TGSI_OPCODE_SAMPLE_I}, - {1, 3, 0, 0, 0, 0, OTHR, "SAMPLE_I_MS", TGSI_OPCODE_SAMPLE_I_MS}, - {1, 4, 0, 0, 0, 0, OTHR, "SAMPLE_B", TGSI_OPCODE_SAMPLE_B}, - {1, 4, 0, 0, 0, 0, OTHR, "SAMPLE_C", TGSI_OPCODE_SAMPLE_C}, - {1, 4, 0, 0, 0, 0, OTHR, "SAMPLE_C_LZ", TGSI_OPCODE_SAMPLE_C_LZ}, - {1, 5, 0, 0, 0, 0, OTHR, "SAMPLE_D", TGSI_OPCODE_SAMPLE_D}, - {1, 4, 0, 0, 0, 0, OTHR, "SAMPLE_L", TGSI_OPCODE_SAMPLE_L}, - {1, 3, 0, 0, 0, 0, OTHR, "GATHER4", TGSI_OPCODE_GATHER4}, - {1, 2, 0, 0, 0, 0, OTHR, "SVIEWINFO", TGSI_OPCODE_SVIEWINFO}, - {1, 2, 0, 0, 0, 0, OTHR, "SAMPLE_POS", TGSI_OPCODE_SAMPLE_POS}, - {1, 2, 0, 0, 0, 0, OTHR, "SAMPLE_INFO", TGSI_OPCODE_SAMPLE_INFO}, - {1, 1, 0, 0, 0, 0, COMP, "UARL", TGSI_OPCODE_UARL}, - {1, 3, 0, 0, 0, 0, COMP, "UCMP", TGSI_OPCODE_UCMP}, - {1, 1, 0, 0, 0, 0, COMP, "IABS", TGSI_OPCODE_IABS}, - {1, 1, 0, 0, 0, 0, COMP, "ISSG", TGSI_OPCODE_ISSG}, - {1, 2, 0, 0, 0, 0, OTHR, "LOAD", TGSI_OPCODE_LOAD}, - {1, 2, 0, 0, 0, 0, OTHR, "STORE", TGSI_OPCODE_STORE}, - {1, 0, 0, 0, 0, 0, OTHR, "", 163}, - {1, 0, 0, 0, 0, 0, OTHR, "", 164}, - {1, 0, 0, 0, 0, 0, OTHR, "", 165}, - {0, 0, 0, 0, 0, 0, OTHR, "BARRIER", TGSI_OPCODE_BARRIER}, - - {1, 3, 0, 0, 0, 0, OTHR, "ATOMUADD", TGSI_OPCODE_ATOMUADD}, - {1, 3, 0, 0, 0, 0, OTHR, "ATOMXCHG", TGSI_OPCODE_ATOMXCHG}, - {1, 4, 0, 0, 0, 0, OTHR, "ATOMCAS", TGSI_OPCODE_ATOMCAS}, - {1, 3, 0, 0, 0, 0, OTHR, "ATOMAND", TGSI_OPCODE_ATOMAND}, - {1, 3, 0, 0, 0, 0, OTHR, "ATOMOR", TGSI_OPCODE_ATOMOR}, - {1, 3, 0, 0, 0, 0, OTHR, "ATOMXOR", TGSI_OPCODE_ATOMXOR}, - {1, 3, 0, 0, 0, 0, OTHR, "ATOMUMIN", TGSI_OPCODE_ATOMUMIN}, - {1, 3, 0, 0, 0, 0, OTHR, "ATOMUMAX", TGSI_OPCODE_ATOMUMAX}, - {1, 3, 0, 0, 0, 0, OTHR, "ATOMIMIN", TGSI_OPCODE_ATOMIMIN}, - {1, 3, 0, 0, 0, 0, OTHR, "ATOMIMAX", TGSI_OPCODE_ATOMIMAX}, - {1, 3, 1, 0, 0, 0, OTHR, "TEX2", TGSI_OPCODE_TEX2}, - {1, 3, 1, 0, 0, 0, OTHR, "TXB2", TGSI_OPCODE_TXB2}, - {1, 3, 1, 0, 0, 0, OTHR, "TXL2", TGSI_OPCODE_TXL2}, - {1, 2, 0, 0, 0, 0, COMP, "IMUL_HI", TGSI_OPCODE_IMUL_HI}, - {1, 2, 0, 0, 0, 0, COMP, "UMUL_HI", TGSI_OPCODE_UMUL_HI}, - {1, 3, 1, 0, 0, 0, OTHR, "TG4", TGSI_OPCODE_TG4}, - {1, 2, 1, 0, 0, 0, OTHR, "LODQ", TGSI_OPCODE_LODQ}, - {1, 3, 0, 0, 0, 0, COMP, "IBFE", TGSI_OPCODE_IBFE}, - {1, 3, 0, 0, 0, 0, COMP, "UBFE", TGSI_OPCODE_UBFE}, - {1, 4, 0, 0, 0, 0, COMP, "BFI", TGSI_OPCODE_BFI}, - {1, 1, 0, 0, 0, 0, COMP, "BREV", TGSI_OPCODE_BREV}, - {1, 1, 0, 0, 0, 0, COMP, "POPC", TGSI_OPCODE_POPC}, - {1, 1, 0, 0, 0, 0, COMP, "LSB", TGSI_OPCODE_LSB}, - {1, 1, 0, 0, 0, 0, COMP, "IMSB", TGSI_OPCODE_IMSB}, - {1, 1, 0, 0, 0, 0, COMP, "UMSB", TGSI_OPCODE_UMSB}, - {1, 1, 0, 0, 0, 0, OTHR, "INTERP_CENTROID", TGSI_OPCODE_INTERP_CENTROID}, - {1, 2, 0, 0, 0, 0, OTHR, "INTERP_SAMPLE", TGSI_OPCODE_INTERP_SAMPLE}, - {1, 2, 0, 0, 0, 0, OTHR, "INTERP_OFFSET", TGSI_OPCODE_INTERP_OFFSET}, - {1, 1, 0, 0, 0, 0, COMP, "F2D", TGSI_OPCODE_F2D}, - {1, 1, 0, 0, 0, 0, COMP, "D2F", TGSI_OPCODE_D2F}, - {1, 1, 0, 0, 0, 0, COMP, "DABS", TGSI_OPCODE_DABS}, - {1, 1, 0, 0, 0, 0, COMP, "DNEG", TGSI_OPCODE_DNEG}, - {1, 2, 0, 0, 0, 0, COMP, "DADD", TGSI_OPCODE_DADD}, - {1, 2, 0, 0, 0, 0, COMP, "DMUL", TGSI_OPCODE_DMUL}, - {1, 2, 0, 0, 0, 0, COMP, "DMAX", TGSI_OPCODE_DMAX}, - {1, 2, 0, 0, 0, 0, COMP, "DMIN", TGSI_OPCODE_DMIN}, - {1, 2, 0, 0, 0, 0, COMP, "DSLT", TGSI_OPCODE_DSLT}, - {1, 2, 0, 0, 0, 0, COMP, "DSGE", TGSI_OPCODE_DSGE}, - {1, 2, 0, 0, 0, 0, COMP, "DSEQ", TGSI_OPCODE_DSEQ}, - {1, 2, 0, 0, 0, 0, COMP, "DSNE", TGSI_OPCODE_DSNE}, - {1, 1, 0, 0, 0, 0, COMP, "DRCP", TGSI_OPCODE_DRCP}, - {1, 1, 0, 0, 0, 0, COMP, "DSQRT", TGSI_OPCODE_DSQRT}, - {1, 3, 0, 0, 0, 0, COMP, "DMAD", TGSI_OPCODE_DMAD}, - {1, 1, 0, 0, 0, 0, COMP, "DFRAC", TGSI_OPCODE_DFRAC}, - {1, 2, 0, 0, 0, 0, COMP, "DLDEXP", TGSI_OPCODE_DLDEXP}, - {2, 1, 0, 0, 0, 0, COMP, "DFRACEXP", TGSI_OPCODE_DFRACEXP}, - {1, 1, 0, 0, 0, 0, COMP, "D2I", TGSI_OPCODE_D2I}, - {1, 1, 0, 0, 0, 0, COMP, "I2D", TGSI_OPCODE_I2D}, - {1, 1, 0, 0, 0, 0, COMP, "D2U", TGSI_OPCODE_D2U}, - {1, 1, 0, 0, 0, 0, COMP, "U2D", TGSI_OPCODE_U2D}, - {1, 1, 0, 0, 0, 0, COMP, "DRSQ", TGSI_OPCODE_DRSQ}, - {1, 1, 0, 0, 0, 0, COMP, "DTRUNC", TGSI_OPCODE_DTRUNC}, - {1, 1, 0, 0, 0, 0, COMP, "DCEIL", TGSI_OPCODE_DCEIL}, - {1, 1, 0, 0, 0, 0, COMP, "DFLR", TGSI_OPCODE_DFLR}, - {1, 1, 0, 0, 0, 0, COMP, "DROUND", TGSI_OPCODE_DROUND}, - {1, 1, 0, 0, 0, 0, COMP, "DSSG", TGSI_OPCODE_DSSG}, - {1, 2, 0, 0, 0, 0, COMP, "DDIV", TGSI_OPCODE_DDIV}, - {1, 0, 0, 0, 0, 0, OTHR, "CLOCK", TGSI_OPCODE_CLOCK}, - - {1, 1, 0, 0, 0, 0, COMP, "I64ABS", TGSI_OPCODE_I64ABS}, - {1, 1, 0, 0, 0, 0, COMP, "I64NEG", TGSI_OPCODE_I64NEG}, - {1, 1, 0, 0, 0, 0, COMP, "I64SSG", TGSI_OPCODE_I64SSG}, - {1, 2, 0, 0, 0, 0, COMP, "I64SLT", TGSI_OPCODE_I64SLT}, - {1, 2, 0, 0, 0, 0, COMP, "I64SGE", TGSI_OPCODE_I64SGE}, - {1, 2, 0, 0, 0, 0, COMP, "I64MIN", TGSI_OPCODE_I64MIN}, - {1, 2, 0, 0, 0, 0, COMP, "I64MAX", TGSI_OPCODE_I64MAX}, - {1, 2, 0, 0, 0, 0, COMP, "I64SHR", TGSI_OPCODE_I64SHR}, - {1, 2, 0, 0, 0, 0, COMP, "I64DIV", TGSI_OPCODE_I64DIV}, - {1, 2, 0, 0, 0, 0, COMP, "I64MOD", TGSI_OPCODE_I64MOD}, - {1, 1, 0, 0, 0, 0, COMP, "F2I64", TGSI_OPCODE_F2I64}, - {1, 1, 0, 0, 0, 0, COMP, "U2I64", TGSI_OPCODE_U2I64}, - {1, 1, 0, 0, 0, 0, COMP, "I2I64", TGSI_OPCODE_I2I64}, - {1, 1, 0, 0, 0, 0, COMP, "D2I64", TGSI_OPCODE_D2I64}, - {1, 1, 0, 0, 0, 0, COMP, "I642F", TGSI_OPCODE_I642F}, - {1, 1, 0, 0, 0, 0, COMP, "I642D", TGSI_OPCODE_I642D}, - - {1, 2, 0, 0, 0, 0, COMP, "U64ADD", TGSI_OPCODE_U64ADD}, - {1, 2, 0, 0, 0, 0, COMP, "U64MUL", TGSI_OPCODE_U64MUL}, - {1, 2, 0, 0, 0, 0, COMP, "U64SEQ", TGSI_OPCODE_U64SEQ}, - {1, 2, 0, 0, 0, 0, COMP, "U64SNE", TGSI_OPCODE_U64SNE}, - {1, 2, 0, 0, 0, 0, COMP, "U64SLT", TGSI_OPCODE_U64SLT}, - {1, 2, 0, 0, 0, 0, COMP, "U64SGE", TGSI_OPCODE_U64SGE}, - {1, 2, 0, 0, 0, 0, COMP, "U64MIN", TGSI_OPCODE_U64MIN}, - {1, 2, 0, 0, 0, 0, COMP, "U64MAX", TGSI_OPCODE_U64MAX}, - {1, 2, 0, 0, 0, 0, COMP, "U64SHL", TGSI_OPCODE_U64SHL}, - {1, 2, 0, 0, 0, 0, COMP, "U64SHR", TGSI_OPCODE_U64SHR}, - {1, 2, 0, 0, 0, 0, COMP, "U64DIV", TGSI_OPCODE_U64DIV}, - {1, 2, 0, 0, 0, 0, COMP, "U64MOD", TGSI_OPCODE_U64MOD}, - {1, 1, 0, 0, 0, 0, COMP, "F2U64", TGSI_OPCODE_F2U64}, - {1, 1, 0, 0, 0, 0, COMP, "D2U64", TGSI_OPCODE_D2U64}, - {1, 1, 0, 0, 0, 0, COMP, "U642F", TGSI_OPCODE_U642F}, - {1, 1, 0, 0, 0, 0, COMP, "U642D", TGSI_OPCODE_U642D}}; - -const struct tgsi_opcode_info *tgsi_get_opcode_info(uint opcode) { - static boolean firsttime = 1; - - if (firsttime) { - unsigned i; - firsttime = 0; - for (i = 0; i < Elements(opcode_info); i++) - assert(opcode_info[i].opcode == i); - } - - if (opcode < TGSI_OPCODE_LAST) - return &opcode_info[opcode]; - - assert(0); - return NULL; -} - -const char *tgsi_get_opcode_name(uint opcode) { - const struct tgsi_opcode_info *info = tgsi_get_opcode_info(opcode); - return info->mnemonic; -} - -const char *tgsi_get_processor_name(uint processor) { - switch (processor) { - case TGSI_PROCESSOR_VERTEX: - return "vertex shader"; - case TGSI_PROCESSOR_FRAGMENT: - return "fragment shader"; - case TGSI_PROCESSOR_GEOMETRY: - return "geometry shader"; - case TGSI_PROCESSOR_TESS_CTRL: - return "tessellation control shader"; - case TGSI_PROCESSOR_TESS_EVAL: - return "tessellation evaluation shader"; - default: - return "unknown shader type!"; - } -} - -/** - * Infer the type (of the dst) of the opcode. - * - * MOV and UCMP is special so return VOID - */ -static inline enum tgsi_opcode_type tgsi_opcode_infer_type(uint opcode) { - switch (opcode) { - case TGSI_OPCODE_MOV: - case TGSI_OPCODE_UCMP: - return TGSI_TYPE_UNTYPED; - case TGSI_OPCODE_NOT: - case TGSI_OPCODE_SHL: - case TGSI_OPCODE_AND: - case TGSI_OPCODE_OR: - case TGSI_OPCODE_XOR: - case TGSI_OPCODE_TXQ: - case TGSI_OPCODE_TXQS: - case TGSI_OPCODE_F2U: - case TGSI_OPCODE_UDIV: - case TGSI_OPCODE_UMAD: - case TGSI_OPCODE_UMAX: - case TGSI_OPCODE_UMIN: - case TGSI_OPCODE_UMOD: - case TGSI_OPCODE_UMUL: - case TGSI_OPCODE_USEQ: - case TGSI_OPCODE_USGE: - case TGSI_OPCODE_USHR: - case TGSI_OPCODE_USLT: - case TGSI_OPCODE_USNE: - case TGSI_OPCODE_SVIEWINFO: - case TGSI_OPCODE_UMUL_HI: - case TGSI_OPCODE_UBFE: - case TGSI_OPCODE_BFI: - case TGSI_OPCODE_BREV: - case TGSI_OPCODE_D2U: - case TGSI_OPCODE_CLOCK: - return TGSI_TYPE_UNSIGNED; - case TGSI_OPCODE_ARL: - case TGSI_OPCODE_ARR: - case TGSI_OPCODE_MOD: - case TGSI_OPCODE_F2I: - case TGSI_OPCODE_FSEQ: - case TGSI_OPCODE_FSGE: - case TGSI_OPCODE_FSLT: - case TGSI_OPCODE_FSNE: - case TGSI_OPCODE_IDIV: - case TGSI_OPCODE_IMAX: - case TGSI_OPCODE_IMIN: - case TGSI_OPCODE_INEG: - case TGSI_OPCODE_ISGE: - case TGSI_OPCODE_ISHR: - case TGSI_OPCODE_ISLT: - case TGSI_OPCODE_UADD: - case TGSI_OPCODE_UARL: - case TGSI_OPCODE_IABS: - case TGSI_OPCODE_ISSG: - case TGSI_OPCODE_IMUL_HI: - case TGSI_OPCODE_IBFE: - case TGSI_OPCODE_IMSB: - case TGSI_OPCODE_DSEQ: - case TGSI_OPCODE_DSGE: - case TGSI_OPCODE_DSLT: - case TGSI_OPCODE_DSNE: - case TGSI_OPCODE_D2I: - case TGSI_OPCODE_LSB: - case TGSI_OPCODE_POPC: - case TGSI_OPCODE_UMSB: - case TGSI_OPCODE_U64SEQ: - case TGSI_OPCODE_U64SNE: - case TGSI_OPCODE_U64SLT: - case TGSI_OPCODE_U64SGE: - case TGSI_OPCODE_I64SLT: - case TGSI_OPCODE_I64SGE: - return TGSI_TYPE_SIGNED; - case TGSI_OPCODE_DADD: - case TGSI_OPCODE_DABS: - case TGSI_OPCODE_DFMA: - case TGSI_OPCODE_DNEG: - case TGSI_OPCODE_DMUL: - case TGSI_OPCODE_DMAX: - case TGSI_OPCODE_DMIN: - case TGSI_OPCODE_DRCP: - case TGSI_OPCODE_DSQRT: - case TGSI_OPCODE_DMAD: - case TGSI_OPCODE_DLDEXP: - case TGSI_OPCODE_DFRACEXP: - case TGSI_OPCODE_DFRAC: - case TGSI_OPCODE_DRSQ: - case TGSI_OPCODE_DTRUNC: - case TGSI_OPCODE_DCEIL: - case TGSI_OPCODE_DFLR: - case TGSI_OPCODE_DROUND: - case TGSI_OPCODE_DSSG: - case TGSI_OPCODE_DDIV: - case TGSI_OPCODE_F2D: - case TGSI_OPCODE_I2D: - case TGSI_OPCODE_U2D: - case TGSI_OPCODE_U642D: - case TGSI_OPCODE_I642D: - return TGSI_TYPE_DOUBLE; - case TGSI_OPCODE_U64MAX: - case TGSI_OPCODE_U64MIN: - case TGSI_OPCODE_U64ADD: - case TGSI_OPCODE_U64MUL: - case TGSI_OPCODE_U64DIV: - case TGSI_OPCODE_U64MOD: - case TGSI_OPCODE_U64SHL: - case TGSI_OPCODE_U64SHR: - case TGSI_OPCODE_F2U64: - case TGSI_OPCODE_D2U64: - return TGSI_TYPE_UNSIGNED64; - case TGSI_OPCODE_I64MAX: - case TGSI_OPCODE_I64MIN: - case TGSI_OPCODE_I64ABS: - case TGSI_OPCODE_I64SSG: - case TGSI_OPCODE_I64NEG: - case TGSI_OPCODE_I64SHR: - case TGSI_OPCODE_I64DIV: - case TGSI_OPCODE_I64MOD: - case TGSI_OPCODE_F2I64: - case TGSI_OPCODE_U2I64: - case TGSI_OPCODE_I2I64: - case TGSI_OPCODE_D2I64: - return TGSI_TYPE_SIGNED64; - default: - return TGSI_TYPE_FLOAT; - } -} - -/* - * infer the source type of a TGSI opcode. - */ -enum tgsi_opcode_type tgsi_opcode_infer_src_type(uint opcode) { - switch (opcode) { - case TGSI_OPCODE_UIF: - case TGSI_OPCODE_TXF: - case TGSI_OPCODE_U2F: - case TGSI_OPCODE_U2D: - case TGSI_OPCODE_UADD: - case TGSI_OPCODE_SWITCH: - case TGSI_OPCODE_CASE: - case TGSI_OPCODE_SAMPLE_I: - case TGSI_OPCODE_SAMPLE_I_MS: - case TGSI_OPCODE_UMUL_HI: - case TGSI_OPCODE_UMSB: - case TGSI_OPCODE_U2I64: - case TGSI_OPCODE_MEMBAR: - return TGSI_TYPE_UNSIGNED; - case TGSI_OPCODE_IMUL_HI: - case TGSI_OPCODE_I2F: - case TGSI_OPCODE_I2D: - case TGSI_OPCODE_I2I64: - return TGSI_TYPE_SIGNED; - case TGSI_OPCODE_ARL: - case TGSI_OPCODE_ARR: - case TGSI_OPCODE_F2D: - case TGSI_OPCODE_F2I: - case TGSI_OPCODE_F2U: - case TGSI_OPCODE_FSEQ: - case TGSI_OPCODE_FSGE: - case TGSI_OPCODE_FSLT: - case TGSI_OPCODE_FSNE: - case TGSI_OPCODE_UCMP: - case TGSI_OPCODE_F2U64: - case TGSI_OPCODE_F2I64: - return TGSI_TYPE_FLOAT; - case TGSI_OPCODE_D2F: - case TGSI_OPCODE_D2U: - case TGSI_OPCODE_D2I: - case TGSI_OPCODE_DSEQ: - case TGSI_OPCODE_DSGE: - case TGSI_OPCODE_DSLT: - case TGSI_OPCODE_DSNE: - case TGSI_OPCODE_D2U64: - case TGSI_OPCODE_D2I64: - return TGSI_TYPE_DOUBLE; - case TGSI_OPCODE_U64SEQ: - case TGSI_OPCODE_U64SNE: - case TGSI_OPCODE_U64SLT: - case TGSI_OPCODE_U64SGE: - case TGSI_OPCODE_U642F: - case TGSI_OPCODE_U642D: - return TGSI_TYPE_UNSIGNED64; - case TGSI_OPCODE_I64SLT: - case TGSI_OPCODE_I64SGE: - case TGSI_OPCODE_I642F: - case TGSI_OPCODE_I642D: - return TGSI_TYPE_SIGNED64; - default: - return tgsi_opcode_infer_type(opcode); - } -} - -/* - * infer the destination type of a TGSI opcode. - */ -enum tgsi_opcode_type tgsi_opcode_infer_dst_type(uint opcode) { - return tgsi_opcode_infer_type(opcode); -} diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_info.h b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_info.h deleted file mode 100644 index 0a141100c..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_info.h +++ /dev/null @@ -1,109 +0,0 @@ -/************************************************************************** - * - * Copyright 2008 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -#ifndef TGSI_INFO_H -#define TGSI_INFO_H - -#include "pipe/p_compiler.h" -#include "pipe/p_shader_tokens.h" -#include "util/u_format.h" - -#if defined __cplusplus -extern "C" { -#endif - -/* This enum describes how an opcode calculates its result. */ -enum tgsi_output_mode { - /** The opcode produces no result. */ - TGSI_OUTPUT_NONE = 0, - - /** When this opcode writes to a channel of the destination register, - * it takes as arguments values from the same channel of the source - * register(s). - * - * Example: TGSI_OPCODE_ADD - */ - TGSI_OUTPUT_COMPONENTWISE = 1, - - /** This opcode writes the same value to all enabled channels of the - * destination register. - * - * Example: TGSI_OPCODE_RSQ - */ - TGSI_OUTPUT_REPLICATE = 2, - - /** The operation performed by this opcode is dependent on which channel - * of the destination register is being written. - * - * Example: TGSI_OPCODE_LOG - */ - TGSI_OUTPUT_CHAN_DEPENDENT = 3, - - /** - * Example: TGSI_OPCODE_TEX - */ - TGSI_OUTPUT_OTHER = 4 -}; - -struct tgsi_opcode_info { - unsigned num_dst : 3; - unsigned num_src : 3; - unsigned is_tex : 1; - unsigned is_branch : 1; - int pre_dedent : 2; - int post_indent : 2; - enum tgsi_output_mode output_mode : 3; - const char *mnemonic; - uint opcode; -}; - -const struct tgsi_opcode_info *tgsi_get_opcode_info(uint opcode); - -const char *tgsi_get_opcode_name(uint opcode); - -const char *tgsi_get_processor_name(uint processor); - -enum tgsi_opcode_type { - TGSI_TYPE_UNTYPED, /* for MOV */ - TGSI_TYPE_VOID, - TGSI_TYPE_UNSIGNED, - TGSI_TYPE_SIGNED, - TGSI_TYPE_FLOAT, - TGSI_TYPE_DOUBLE, - TGSI_TYPE_UNSIGNED64, - TGSI_TYPE_SIGNED64 -}; - -enum tgsi_opcode_type tgsi_opcode_infer_src_type(uint opcode); - -enum tgsi_opcode_type tgsi_opcode_infer_dst_type(uint opcode); - -#if defined __cplusplus -} -#endif - -#endif /* TGSI_INFO_H */ diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_iterate.c b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_iterate.c deleted file mode 100644 index f1315ae95..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_iterate.c +++ /dev/null @@ -1,87 +0,0 @@ -/************************************************************************** - * - * Copyright 2008 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -#include "tgsi_iterate.h" -#include "util/u_debug.h" - -boolean tgsi_iterate_shader(const struct tgsi_token *tokens, - struct tgsi_iterate_context *ctx) { - struct tgsi_parse_context parse; - - if (tgsi_parse_init(&parse, tokens) != TGSI_PARSE_OK) - return FALSE; - - ctx->processor = parse.FullHeader.Processor; - - if (ctx->prolog) - if (!ctx->prolog(ctx)) - goto fail; - - while (!tgsi_parse_end_of_tokens(&parse)) { - tgsi_parse_token(&parse); - - switch (parse.FullToken.Token.Type) { - case TGSI_TOKEN_TYPE_INSTRUCTION: - if (ctx->iterate_instruction) - if (!ctx->iterate_instruction(ctx, &parse.FullToken.FullInstruction)) - goto fail; - break; - - case TGSI_TOKEN_TYPE_DECLARATION: - if (ctx->iterate_declaration) - if (!ctx->iterate_declaration(ctx, &parse.FullToken.FullDeclaration)) - goto fail; - break; - - case TGSI_TOKEN_TYPE_IMMEDIATE: - if (ctx->iterate_immediate) - if (!ctx->iterate_immediate(ctx, &parse.FullToken.FullImmediate)) - goto fail; - break; - - case TGSI_TOKEN_TYPE_PROPERTY: - if (ctx->iterate_property) - if (!ctx->iterate_property(ctx, &parse.FullToken.FullProperty)) - goto fail; - break; - - default: - assert(0); - } - } - - if (ctx->epilog) - if (!ctx->epilog(ctx)) - goto fail; - - tgsi_parse_free(&parse); - return TRUE; - -fail: - tgsi_parse_free(&parse); - return FALSE; -} diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_iterate.h b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_iterate.h deleted file mode 100644 index 8eacb5318..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_iterate.h +++ /dev/null @@ -1,65 +0,0 @@ -/************************************************************************** - * - * Copyright 2008 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -#ifndef TGSI_ITERATE_H -#define TGSI_ITERATE_H - -#include "pipe/p_shader_tokens.h" -#include "tgsi/tgsi_parse.h" - -#if defined __cplusplus -extern "C" { -#endif - -struct tgsi_iterate_context { - boolean (*prolog)(struct tgsi_iterate_context *ctx); - - boolean (*iterate_instruction)(struct tgsi_iterate_context *ctx, - struct tgsi_full_instruction *inst); - - boolean (*iterate_declaration)(struct tgsi_iterate_context *ctx, - struct tgsi_full_declaration *decl); - - boolean (*iterate_immediate)(struct tgsi_iterate_context *ctx, - struct tgsi_full_immediate *imm); - - boolean (*iterate_property)(struct tgsi_iterate_context *ctx, - struct tgsi_full_property *prop); - - boolean (*epilog)(struct tgsi_iterate_context *ctx); - - struct tgsi_processor processor; -}; - -boolean tgsi_iterate_shader(const struct tgsi_token *tokens, - struct tgsi_iterate_context *ctx); - -#if defined __cplusplus -} -#endif - -#endif /* TGSI_ITERATE_H */ diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_opcode_tmp.h b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_opcode_tmp.h deleted file mode 100644 index b46eb1302..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_opcode_tmp.h +++ /dev/null @@ -1,218 +0,0 @@ -/************************************************************************** - * - * Copyright 2008 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ -#ifndef OP12_TEX -#define OP12_TEX(a) OP12(a) -#endif - -#ifndef OP14_TEX -#define OP14_TEX(a) OP14(a) -#endif - -#ifndef OP12_SAMPLE -#define OP12_SAMPLE(a) OP12(a) -#endif - -#ifndef OP13_SAMPLE -#define OP13_SAMPLE(a) OP13(a) -#endif - -#ifndef OP14_SAMPLE -#define OP14_SAMPLE(a) OP14(a) -#endif - -#ifndef OP15_SAMPLE -#define OP15_SAMPLE(a) OP15(a) -#endif - -#ifndef OP00_LBL -#define OP00_LBL(a) OP00(a) -#endif - -#ifndef OP01_LBL -#define OP01_LBL(a) OP01(a) -#endif - -OP11(ARL) -OP11(MOV) -OP11(LIT) -OP11(RCP) -OP11(RSQ) -OP11(EXP) -OP11(LOG) -OP12(MUL) -OP12(ADD) -OP12(DP3) -OP12(DP4) -OP12(DST) -OP12(MIN) -OP12(MAX) -OP12(SLT) -OP12(SGE) -OP13(MAD) -OP12(SUB) -OP13(LRP) -OP11(SQRT) -OP11(FRC) -OP11(FLR) -OP11(ROUND) -OP11(EX2) -OP11(LG2) -OP12(POW) -OP12(XPD) -OP11(ABS) -OP12(DPH) -OP11(COS) -OP11(DDX) -OP11(DDY) -OP00(KILL) -OP11(PK2H) -OP11(PK2US) -OP11(PK4B) -OP11(PK4UB) -OP12(SEQ) -OP12(SGT) -OP11(SIN) -OP12(SLE) -OP12(SNE) -OP12_TEX(TEX) -OP14_TEX(TXD) -OP12_TEX(TXP) -OP11(UP2H) -OP11(UP2US) -OP11(UP4B) -OP11(UP4UB) -OP11(ARR) -OP00_LBL(CAL) -OP00(RET) -OP11(SSG) -OP13(CMP) -OP11(SCS) -OP12_TEX(TXB) -OP12(DIV) -OP12(DP2) -OP12_TEX(TXL) -OP00(BRK) -OP01_LBL(IF) -OP01_LBL(UIF) -OP00_LBL(ELSE) -OP00(ENDIF) -OP11(CEIL) -OP11(I2F) -OP11(NOT) -OP11(TRUNC) -OP12(SHL) -OP12(AND) -OP12(OR) -OP12(MOD) -OP12(XOR) -OP12_TEX(TXF) -OP12_TEX(TXQ) -OP00(CONT) -OP01(EMIT) -OP01(ENDPRIM) -OP00_LBL(BGNLOOP) -OP00(BGNSUB) -OP00_LBL(ENDLOOP) -OP00(ENDSUB) -OP00(NOP) -OP01(KILL_IF) -OP00(END) -OP11(F2I) -OP12(FSEQ) -OP12(FSGE) -OP12(FSLT) -OP12(FSNE) -OP12(IDIV) -OP12(IMAX) -OP12(IMIN) -OP11(INEG) -OP12(ISGE) -OP12(ISHR) -OP12(ISLT) -OP11(F2U) -OP11(U2F) -OP12(UADD) -OP12(UDIV) -OP13(UMAD) -OP12(UMAX) -OP12(UMIN) -OP12(UMOD) -OP12(UMUL) -OP12(USEQ) -OP12(USGE) -OP12(USHR) -OP12(USLT) -OP12(USNE) -OP01(SWITCH) -OP01(CASE) -OP00(DEFAULT) -OP00(ENDSWITCH) - -OP13_SAMPLE(SAMPLE) -OP12_SAMPLE(SAMPLE_I) -OP13_SAMPLE(SAMPLE_I_MS) -OP14_SAMPLE(SAMPLE_B) -OP14_SAMPLE(SAMPLE_C) -OP14_SAMPLE(SAMPLE_C_LZ) -OP15_SAMPLE(SAMPLE_D) -OP14_SAMPLE(SAMPLE_L) -OP13_SAMPLE(GATHER4) -OP12(SVIEWINFO) -OP13(SAMPLE_POS) -OP12(SAMPLE_INFO) -OP11(UARL) - -OP13(UCMP) - -OP12(IMUL_HI) -OP12(UMUL_HI) - -#undef OP00 -#undef OP01 -#undef OP10 -#undef OP11 -#undef OP12 -#undef OP13 - -#ifdef OP14 -#undef OP14 -#endif - -#ifdef OP15 -#undef OP15 -#endif - -#undef OP00_LBL -#undef OP01_LBL - -#undef OP12_TEX -#undef OP14_TEX - -#undef OP12_SAMPLE -#undef OP13_SAMPLE -#undef OP14_SAMPLE -#undef OP15_SAMPLE diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_parse.c b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_parse.c deleted file mode 100644 index 3ecd9d89f..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_parse.c +++ /dev/null @@ -1,274 +0,0 @@ -/************************************************************************** - * - * Copyright 2007 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -#include "tgsi_parse.h" -#include "pipe/p_shader_tokens.h" -#include "util/u_debug.h" -#include "util/u_memory.h" - -unsigned tgsi_parse_init(struct tgsi_parse_context *ctx, - const struct tgsi_token *tokens) { - ctx->FullHeader.Header = *(struct tgsi_header *)&tokens[0]; - if (ctx->FullHeader.Header.HeaderSize >= 2) { - ctx->FullHeader.Processor = *(struct tgsi_processor *)&tokens[1]; - } else { - return TGSI_PARSE_ERROR; - } - - ctx->Tokens = tokens; - ctx->Position = ctx->FullHeader.Header.HeaderSize; - - return TGSI_PARSE_OK; -} - -void tgsi_parse_free(UNUSED struct tgsi_parse_context *ctx) {} - -boolean tgsi_parse_end_of_tokens(struct tgsi_parse_context *ctx) { - /* All values involved are unsigned, but the sum will be promoted to - * a signed value (at least on 64 bit). To capture a possible overflow - * make it a signed comparison. - */ - return (int)ctx->Position >= - ctx->FullHeader.Header.HeaderSize + ctx->FullHeader.Header.BodySize; -} - -/** - * This function is used to avoid and work-around type punning/aliasing - * warnings. The warnings seem harmless on x86 but on PPC they cause - * real failures. - */ -static inline void copy_token(void *dst, const void *src) { - memcpy(dst, src, 4); -} - -/** - * Get next 4-byte token, return it at address specified by 'token' - */ -static void next_token(struct tgsi_parse_context *ctx, void *token) { - assert(!tgsi_parse_end_of_tokens(ctx)); - copy_token(token, &ctx->Tokens[ctx->Position]); - ctx->Position++; -} - -void tgsi_parse_token(struct tgsi_parse_context *ctx) { - struct tgsi_token token; - unsigned i; - - next_token(ctx, &token); - - switch (token.Type) { - case TGSI_TOKEN_TYPE_DECLARATION: { - struct tgsi_full_declaration *decl = &ctx->FullToken.FullDeclaration; - - memset(decl, 0, sizeof *decl); - copy_token(&decl->Declaration, &token); - - next_token(ctx, &decl->Range); - - if (decl->Declaration.Dimension) { - next_token(ctx, &decl->Dim); - } - - if (decl->Declaration.Interpolate) { - next_token(ctx, &decl->Interp); - } - - if (decl->Declaration.Semantic) { - next_token(ctx, &decl->Semantic); - } - - if (decl->Declaration.File == TGSI_FILE_IMAGE) { - next_token(ctx, &decl->Image); - } - - if (decl->Declaration.File == TGSI_FILE_SAMPLER_VIEW) { - next_token(ctx, &decl->SamplerView); - } - - if (decl->Declaration.Array) { - next_token(ctx, &decl->Array); - } - - break; - } - - case TGSI_TOKEN_TYPE_IMMEDIATE: { - struct tgsi_full_immediate *imm = &ctx->FullToken.FullImmediate; - uint imm_count; - - memset(imm, 0, sizeof *imm); - copy_token(&imm->Immediate, &token); - - imm_count = imm->Immediate.NrTokens - 1; - - switch (imm->Immediate.DataType) { - case TGSI_IMM_FLOAT32: - for (i = 0; i < imm_count; i++) { - next_token(ctx, &imm->u[i].Float); - } - break; - - case TGSI_IMM_UINT32: - case TGSI_IMM_FLOAT64: - for (i = 0; i < imm_count; i++) { - next_token(ctx, &imm->u[i].Uint); - } - break; - - case TGSI_IMM_INT32: - for (i = 0; i < imm_count; i++) { - next_token(ctx, &imm->u[i].Int); - } - break; - - default: - assert(0); - } - - break; - } - - case TGSI_TOKEN_TYPE_INSTRUCTION: { - struct tgsi_full_instruction *inst = &ctx->FullToken.FullInstruction; - - memset(inst, 0, sizeof *inst); - copy_token(&inst->Instruction, &token); - - if (inst->Instruction.Label) { - next_token(ctx, &inst->Label); - } - - if (inst->Instruction.Texture) { - next_token(ctx, &inst->Texture); - for (i = 0; i < inst->Texture.NumOffsets; i++) { - next_token(ctx, &inst->TexOffsets[i]); - } - } - - if (inst->Instruction.Memory) { - next_token(ctx, &inst->Memory); - } - - assert(inst->Instruction.NumDstRegs <= TGSI_FULL_MAX_DST_REGISTERS); - - for (i = 0; i < inst->Instruction.NumDstRegs; i++) { - - next_token(ctx, &inst->Dst[i].Register); - - if (inst->Dst[i].Register.Indirect) - next_token(ctx, &inst->Dst[i].Indirect); - - if (inst->Dst[i].Register.Dimension) { - next_token(ctx, &inst->Dst[i].Dimension); - - /* - * No support for multi-dimensional addressing. - */ - assert(!inst->Dst[i].Dimension.Dimension); - - if (inst->Dst[i].Dimension.Indirect) - next_token(ctx, &inst->Dst[i].DimIndirect); - } - } - - assert(inst->Instruction.NumSrcRegs <= TGSI_FULL_MAX_SRC_REGISTERS); - - for (i = 0; i < inst->Instruction.NumSrcRegs; i++) { - - next_token(ctx, &inst->Src[i].Register); - - if (inst->Src[i].Register.Indirect) - next_token(ctx, &inst->Src[i].Indirect); - - if (inst->Src[i].Register.Dimension) { - next_token(ctx, &inst->Src[i].Dimension); - - /* - * No support for multi-dimensional addressing. - */ - assert(!inst->Src[i].Dimension.Dimension); - - if (inst->Src[i].Dimension.Indirect) - next_token(ctx, &inst->Src[i].DimIndirect); - } - } - - break; - } - - case TGSI_TOKEN_TYPE_PROPERTY: { - struct tgsi_full_property *prop = &ctx->FullToken.FullProperty; - uint prop_count; - - memset(prop, 0, sizeof *prop); - copy_token(&prop->Property, &token); - - prop_count = prop->Property.NrTokens - 1; - for (i = 0; i < prop_count; i++) { - next_token(ctx, &prop->u[i]); - } - - break; - } - - default: - assert(0); - } -} - -/** - * Make a new copy of a token array. - */ -struct tgsi_token *tgsi_dup_tokens(const struct tgsi_token *tokens) { - unsigned n = tgsi_num_tokens(tokens); - unsigned bytes = n * sizeof(struct tgsi_token); - struct tgsi_token *new_tokens = (struct tgsi_token *)MALLOC(bytes); - if (new_tokens) - memcpy(new_tokens, tokens, bytes); - return new_tokens; -} - -/** - * Allocate memory for num_tokens tokens. - */ -struct tgsi_token *tgsi_alloc_tokens(unsigned num_tokens) { - unsigned bytes = num_tokens * sizeof(struct tgsi_token); - return (struct tgsi_token *)MALLOC(bytes); -} - -void tgsi_dump_tokens(const struct tgsi_token *tokens) { - const unsigned *dwords = (const unsigned *)tokens; - int nr = tgsi_num_tokens(tokens); - int i; - - assert(sizeof(*tokens) == sizeof(unsigned)); - - debug_printf("const unsigned tokens[%d] = {\n", nr); - for (i = 0; i < nr; i++) - debug_printf("0x%08x,\n", dwords[i]); - debug_printf("};\n"); -} diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_parse.h b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_parse.h deleted file mode 100644 index 8c86993ed..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_parse.h +++ /dev/null @@ -1,137 +0,0 @@ -/************************************************************************** - * - * Copyright 2007 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -#ifndef TGSI_PARSE_H -#define TGSI_PARSE_H - -#include "pipe/p_compiler.h" -#include "pipe/p_shader_tokens.h" - -#if defined __cplusplus -extern "C" { -#endif - -struct tgsi_full_header { - struct tgsi_header Header; - struct tgsi_processor Processor; -}; - -struct tgsi_full_dst_register { - struct tgsi_dst_register Register; - struct tgsi_ind_register Indirect; - struct tgsi_dimension Dimension; - struct tgsi_ind_register DimIndirect; -}; - -struct tgsi_full_src_register { - struct tgsi_src_register Register; - struct tgsi_ind_register Indirect; - struct tgsi_dimension Dimension; - struct tgsi_ind_register DimIndirect; -}; - -struct tgsi_full_declaration { - struct tgsi_declaration Declaration; - struct tgsi_declaration_range Range; - struct tgsi_declaration_dimension Dim; - struct tgsi_declaration_interp Interp; - struct tgsi_declaration_semantic Semantic; - struct tgsi_declaration_image Image; - struct tgsi_declaration_sampler_view SamplerView; - struct tgsi_declaration_array Array; -}; - -struct tgsi_full_immediate { - struct tgsi_immediate Immediate; - union tgsi_immediate_data u[4]; -}; - -struct tgsi_full_property { - struct tgsi_property Property; - struct tgsi_property_data u[8]; -}; - -#define TGSI_FULL_MAX_DST_REGISTERS 2 -#define TGSI_FULL_MAX_SRC_REGISTERS 5 /* SAMPLE_D has 5 */ -#define TGSI_FULL_MAX_TEX_OFFSETS 4 - -struct tgsi_full_instruction { - struct tgsi_instruction Instruction; - struct tgsi_instruction_label Label; - struct tgsi_instruction_texture Texture; - struct tgsi_instruction_memory Memory; - struct tgsi_full_dst_register Dst[TGSI_FULL_MAX_DST_REGISTERS]; - struct tgsi_full_src_register Src[TGSI_FULL_MAX_SRC_REGISTERS]; - struct tgsi_texture_offset TexOffsets[TGSI_FULL_MAX_TEX_OFFSETS]; -}; - -union tgsi_full_token { - struct tgsi_token Token; - struct tgsi_full_declaration FullDeclaration; - struct tgsi_full_immediate FullImmediate; - struct tgsi_full_instruction FullInstruction; - struct tgsi_full_property FullProperty; -}; - -struct tgsi_parse_context { - const struct tgsi_token *Tokens; - unsigned Position; - struct tgsi_full_header FullHeader; - union tgsi_full_token FullToken; -}; - -#define TGSI_PARSE_OK 0 -#define TGSI_PARSE_ERROR 1 - -unsigned tgsi_parse_init(struct tgsi_parse_context *ctx, - const struct tgsi_token *tokens); - -void tgsi_parse_free(struct tgsi_parse_context *ctx); - -boolean tgsi_parse_end_of_tokens(struct tgsi_parse_context *ctx); - -void tgsi_parse_token(struct tgsi_parse_context *ctx); - -static inline unsigned tgsi_num_tokens(const struct tgsi_token *tokens) { - struct tgsi_header header; - memcpy(&header, tokens, sizeof(header)); - return header.HeaderSize + header.BodySize; -} - -void tgsi_dump_tokens(const struct tgsi_token *tokens); - -struct tgsi_token *tgsi_dup_tokens(const struct tgsi_token *tokens); - -struct tgsi_token *tgsi_alloc_tokens(unsigned num_tokens); - -void tgsi_free_tokens(const struct tgsi_token *tokens); - -#if defined __cplusplus -} -#endif - -#endif /* TGSI_PARSE_H */ diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_sanity.c b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_sanity.c deleted file mode 100644 index bc1ead938..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_sanity.c +++ /dev/null @@ -1,465 +0,0 @@ -/************************************************************************** - * - * Copyright 2008 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -#include "tgsi_sanity.h" -#include "cso_cache/cso_hash.h" -#include "tgsi_info.h" -#include "tgsi_iterate.h" -#include "util/u_debug.h" -#include "util/u_memory.h" -#include "util/u_prim.h" - -DEBUG_GET_ONCE_BOOL_OPTION(print_sanity, "TGSI_PRINT_SANITY", FALSE) - -typedef struct { - uint file : 28; - /* max 2 dimensions */ - uint dimensions : 4; - uint indices[2]; -} scan_register; - -struct sanity_check_ctx { - struct tgsi_iterate_context iter; - struct cso_hash *regs_decl; - struct cso_hash *regs_used; - struct cso_hash *regs_ind_used; - - uint num_imms; - uint num_instructions; - uint index_of_END; - - uint errors; - uint warnings; - uint implied_array_size; - - boolean print; -}; - -static inline unsigned scan_register_key(const scan_register *reg) { - unsigned key = reg->file; - key |= (reg->indices[0] << 4); - key |= (reg->indices[1] << 18); - - return key; -} - -static void fill_scan_register1d(scan_register *reg, uint file, uint index) { - reg->file = file; - reg->dimensions = 1; - reg->indices[0] = index; - reg->indices[1] = 0; -} - -static void fill_scan_register2d(scan_register *reg, uint file, uint index1, - uint index2) { - reg->file = file; - reg->dimensions = 2; - reg->indices[0] = index1; - reg->indices[1] = index2; -} - -static void scan_register_dst(scan_register *reg, - struct tgsi_full_dst_register *dst) { - if (dst->Register.Dimension) { - /*FIXME: right now we don't support indirect - * multidimensional addressing */ - fill_scan_register2d(reg, dst->Register.File, dst->Register.Index, - dst->Dimension.Index); - } else { - fill_scan_register1d(reg, dst->Register.File, dst->Register.Index); - } -} - -static void scan_register_src(scan_register *reg, - struct tgsi_full_src_register *src) { - if (src->Register.Dimension) { - /*FIXME: right now we don't support indirect - * multidimensional addressing */ - fill_scan_register2d(reg, src->Register.File, src->Register.Index, - src->Dimension.Index); - } else { - fill_scan_register1d(reg, src->Register.File, src->Register.Index); - } -} - -static scan_register * -create_scan_register_src(struct tgsi_full_src_register *src) { - scan_register *reg = MALLOC(sizeof(scan_register)); - scan_register_src(reg, src); - - return reg; -} - -static scan_register * -create_scan_register_dst(struct tgsi_full_dst_register *dst) { - scan_register *reg = MALLOC(sizeof(scan_register)); - scan_register_dst(reg, dst); - - return reg; -} - -static void report_error(struct sanity_check_ctx *ctx, const char *format, - ...) { - va_list args; - - if (!ctx->print) - return; - - debug_printf("Error : "); - va_start(args, format); - _debug_vprintf(format, args); - va_end(args); - debug_printf("\n"); - ctx->errors++; -} - -static void report_warning(struct sanity_check_ctx *ctx, const char *format, - ...) { - va_list args; - - if (!ctx->print) - return; - - debug_printf("Warning: "); - va_start(args, format); - _debug_vprintf(format, args); - va_end(args); - debug_printf("\n"); - ctx->warnings++; -} - -static boolean check_file_name(struct sanity_check_ctx *ctx, uint file) { - if (file <= TGSI_FILE_NULL || file >= TGSI_FILE_COUNT) { - report_error(ctx, "(%u): Invalid register file name", file); - return FALSE; - } - return TRUE; -} - -static boolean is_register_declared(struct sanity_check_ctx *ctx, - const scan_register *reg) { - void *data = - cso_hash_find_data_from_template(ctx->regs_decl, scan_register_key(reg), - (void *)reg, sizeof(scan_register)); - return data ? TRUE : FALSE; -} - -static boolean is_any_register_declared(struct sanity_check_ctx *ctx, - uint file) { - struct cso_hash_iter iter = cso_hash_first_node(ctx->regs_decl); - - while (!cso_hash_iter_is_null(iter)) { - scan_register *reg = (scan_register *)cso_hash_iter_data(iter); - if (reg->file == file) - return TRUE; - iter = cso_hash_iter_next(iter); - } - - return FALSE; -} - -static boolean is_register_used(struct sanity_check_ctx *ctx, - scan_register *reg) { - void *data = cso_hash_find_data_from_template( - ctx->regs_used, scan_register_key(reg), reg, sizeof(scan_register)); - return data ? TRUE : FALSE; -} - -static boolean is_ind_register_used(struct sanity_check_ctx *ctx, - scan_register *reg) { - return cso_hash_contains(ctx->regs_ind_used, reg->file); -} - -static const char *file_names[TGSI_FILE_COUNT] = { - "NULL", "CONST", "IN", "OUT", "TEMP", "SAMP", - "ADDR", "IMM", "PRED", "SV", "RES"}; - -static boolean check_register_usage(struct sanity_check_ctx *ctx, - scan_register *reg, const char *name, - boolean indirect_access) { - if (!check_file_name(ctx, reg->file)) { - FREE(reg); - return FALSE; - } - - if (indirect_access) { - /* Note that 'index' is an offset relative to the value of the - * address register. No range checking done here.*/ - reg->indices[0] = 0; - reg->indices[1] = 0; - if (!is_any_register_declared(ctx, reg->file)) - report_error(ctx, "%s: Undeclared %s register", file_names[reg->file], - name); - if (!is_ind_register_used(ctx, reg)) - cso_hash_insert(ctx->regs_ind_used, reg->file, reg); - else - FREE(reg); - } else { - if (!is_register_declared(ctx, reg)) { - if (reg->dimensions == 2) { - report_error(ctx, "%s[%d][%d]: Undeclared %s register", - file_names[reg->file], reg->indices[0], reg->indices[1], - name); - } else { - report_error(ctx, "%s[%d]: Undeclared %s register", - file_names[reg->file], reg->indices[0], name); - } - } - if (!is_register_used(ctx, reg)) - cso_hash_insert(ctx->regs_used, scan_register_key(reg), reg); - else - FREE(reg); - } - return TRUE; -} - -static boolean iter_instruction(struct tgsi_iterate_context *iter, - struct tgsi_full_instruction *inst) { - struct sanity_check_ctx *ctx = (struct sanity_check_ctx *)iter; - const struct tgsi_opcode_info *info; - uint i; - - if (inst->Instruction.Opcode == TGSI_OPCODE_END) { - if (ctx->index_of_END != ~0u) { - report_error(ctx, "Too many END instructions"); - } - ctx->index_of_END = ctx->num_instructions; - } - - info = tgsi_get_opcode_info(inst->Instruction.Opcode); - if (info == NULL) { - report_error(ctx, "(%u): Invalid instruction opcode", - inst->Instruction.Opcode); - return TRUE; - } - - if (info->num_dst != inst->Instruction.NumDstRegs) { - report_error(ctx, - "%s: Invalid number of destination operands, should be %u", - info->mnemonic, info->num_dst); - } - if (info->num_src != inst->Instruction.NumSrcRegs) { - report_error(ctx, "%s: Invalid number of source operands, should be %u", - info->mnemonic, info->num_src); - } - - /* Check destination and source registers' validity. - * Mark the registers as used. - */ - for (i = 0; i < inst->Instruction.NumDstRegs; i++) { - scan_register *reg = create_scan_register_dst(&inst->Dst[i]); - check_register_usage(ctx, reg, "destination", FALSE); - if (!inst->Dst[i].Register.WriteMask) { - report_error(ctx, "Destination register has empty writemask"); - } - } - for (i = 0; i < inst->Instruction.NumSrcRegs; i++) { - scan_register *reg = create_scan_register_src(&inst->Src[i]); - check_register_usage(ctx, reg, "source", - (boolean)inst->Src[i].Register.Indirect); - if (inst->Src[i].Register.Indirect) { - scan_register *ind_reg = MALLOC(sizeof(scan_register)); - - fill_scan_register1d(ind_reg, inst->Src[i].Indirect.File, - inst->Src[i].Indirect.Index); - check_register_usage(ctx, ind_reg, "indirect", FALSE); - } - } - - ctx->num_instructions++; - - return TRUE; -} - -static void check_and_declare(struct sanity_check_ctx *ctx, - scan_register *reg) { - if (is_register_declared(ctx, reg)) - report_error(ctx, "%s[%u]: The same register declared more than once", - file_names[reg->file], reg->indices[0]); - cso_hash_insert(ctx->regs_decl, scan_register_key(reg), reg); -} - -static boolean iter_declaration(struct tgsi_iterate_context *iter, - struct tgsi_full_declaration *decl) { - struct sanity_check_ctx *ctx = (struct sanity_check_ctx *)iter; - uint file; - uint i; - - /* No declarations allowed after the first instruction. - */ - if (ctx->num_instructions > 0) - report_error(ctx, "Instruction expected but declaration found"); - - /* Check registers' validity. - * Mark the registers as declared. - */ - file = decl->Declaration.File; - if (!check_file_name(ctx, file)) - return TRUE; - for (i = decl->Range.First; i <= decl->Range.Last; i++) { - /* declared TGSI_FILE_INPUT's for geometry processor - * have an implied second dimension */ - if (file == TGSI_FILE_INPUT && - ctx->iter.processor.Processor == TGSI_PROCESSOR_GEOMETRY) { - uint vert; - for (vert = 0; vert < ctx->implied_array_size; ++vert) { - scan_register *reg = MALLOC(sizeof(scan_register)); - fill_scan_register2d(reg, file, i, vert); - check_and_declare(ctx, reg); - } - } else { - scan_register *reg = MALLOC(sizeof(scan_register)); - if (decl->Declaration.Dimension) { - fill_scan_register2d(reg, file, i, decl->Dim.Index2D); - } else { - fill_scan_register1d(reg, file, i); - } - check_and_declare(ctx, reg); - } - } - - return TRUE; -} - -static boolean iter_immediate(struct tgsi_iterate_context *iter, - struct tgsi_full_immediate *imm) { - struct sanity_check_ctx *ctx = (struct sanity_check_ctx *)iter; - scan_register *reg; - - /* No immediates allowed after the first instruction. - */ - if (ctx->num_instructions > 0) - report_error(ctx, "Instruction expected but immediate found"); - - /* Mark the register as declared. - */ - reg = MALLOC(sizeof(scan_register)); - fill_scan_register1d(reg, TGSI_FILE_IMMEDIATE, ctx->num_imms); - cso_hash_insert(ctx->regs_decl, scan_register_key(reg), reg); - ctx->num_imms++; - - /* Check data type validity. - */ - if (imm->Immediate.DataType != TGSI_IMM_FLOAT32 && - imm->Immediate.DataType != TGSI_IMM_UINT32 && - imm->Immediate.DataType != TGSI_IMM_INT32 && - imm->Immediate.DataType != TGSI_IMM_FLOAT64) { - report_error(ctx, "(%u): Invalid immediate data type", - imm->Immediate.DataType); - return TRUE; - } - - return TRUE; -} - -static boolean iter_property(struct tgsi_iterate_context *iter, - struct tgsi_full_property *prop) { - struct sanity_check_ctx *ctx = (struct sanity_check_ctx *)iter; - - if (iter->processor.Processor == TGSI_PROCESSOR_GEOMETRY && - prop->Property.PropertyName == TGSI_PROPERTY_GS_INPUT_PRIM) { - ctx->implied_array_size = u_vertices_per_prim(prop->u[0].Data); - } - return TRUE; -} - -static boolean epilog(struct tgsi_iterate_context *iter) { - struct sanity_check_ctx *ctx = (struct sanity_check_ctx *)iter; - - /* There must be an END instruction somewhere. - */ - if (ctx->index_of_END == ~0u) { - report_error(ctx, "Missing END instruction"); - } - - /* Check if all declared registers were used. - */ - { - struct cso_hash_iter iter = cso_hash_first_node(ctx->regs_decl); - - while (!cso_hash_iter_is_null(iter)) { - scan_register *reg = (scan_register *)cso_hash_iter_data(iter); - if (!is_register_used(ctx, reg) && !is_ind_register_used(ctx, reg)) { - report_warning(ctx, "%s[%u]: Register never used", - file_names[reg->file], reg->indices[0]); - } - iter = cso_hash_iter_next(iter); - } - } - - /* Print totals, if any. - */ - if (ctx->errors || ctx->warnings) - debug_printf("%u errors, %u warnings\n", ctx->errors, ctx->warnings); - - return TRUE; -} - -static void regs_hash_destroy(struct cso_hash *hash) { - struct cso_hash_iter iter = cso_hash_first_node(hash); - while (!cso_hash_iter_is_null(iter)) { - scan_register *reg = (scan_register *)cso_hash_iter_data(iter); - iter = cso_hash_erase(hash, iter); - assert(reg->file < TGSI_FILE_COUNT); - FREE(reg); - } - cso_hash_delete(hash); -} - -boolean tgsi_sanity_check(const struct tgsi_token *tokens) { - struct sanity_check_ctx ctx; - boolean retval; - - ctx.iter.prolog = NULL; - ctx.iter.iterate_instruction = iter_instruction; - ctx.iter.iterate_declaration = iter_declaration; - ctx.iter.iterate_immediate = iter_immediate; - ctx.iter.iterate_property = iter_property; - ctx.iter.epilog = epilog; - - ctx.regs_decl = cso_hash_create(); - ctx.regs_used = cso_hash_create(); - ctx.regs_ind_used = cso_hash_create(); - - ctx.num_imms = 0; - ctx.num_instructions = 0; - ctx.index_of_END = ~0; - - ctx.errors = 0; - ctx.warnings = 0; - ctx.implied_array_size = 0; - ctx.print = debug_get_option_print_sanity(); - - retval = tgsi_iterate_shader(tokens, &ctx.iter); - regs_hash_destroy(ctx.regs_decl); - regs_hash_destroy(ctx.regs_used); - regs_hash_destroy(ctx.regs_ind_used); - if (retval == FALSE) - return FALSE; - - return ctx.errors == 0; -} diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_sanity.h b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_sanity.h deleted file mode 100644 index 325c7d0b1..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_sanity.h +++ /dev/null @@ -1,51 +0,0 @@ -/************************************************************************** - * - * Copyright 2008 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -#ifndef TGSI_SANITY_H -#define TGSI_SANITY_H - -#if defined __cplusplus -extern "C" { -#endif - -#include "pipe/p_compiler.h" - -struct tgsi_token; - -/* Check the given token stream for errors and common mistakes. - * Diagnostic messages are printed out to the debug output, and is - * controlled by the debug option TGSI_PRINT_SANITY (default false). - * Returns TRUE if there are no errors, even though there could be some - * warnings. - */ -boolean tgsi_sanity_check(const struct tgsi_token *tokens); - -#if defined __cplusplus -} -#endif - -#endif /* TGSI_SANITY_H */ diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_scan.c b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_scan.c deleted file mode 100644 index c012de90c..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_scan.c +++ /dev/null @@ -1,507 +0,0 @@ -/************************************************************************** - * - * Copyright 2008 VMware, Inc. - * All Rights Reserved. - * Copyright 2008 VMware, Inc. All rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -/** - * TGSI program scan utility. - * Used to determine which registers and instructions are used by a shader. - * - * Authors: Brian Paul - */ - -#include "tgsi/tgsi_scan.h" -#include "tgsi/tgsi_parse.h" -#include "tgsi/tgsi_util.h" -#include "util/u_debug.h" -#include "util/u_math.h" -#include "util/u_memory.h" -#include "util/u_prim.h" - -/** - * Scan the given TGSI shader to collect information such as number of - * registers used, special instructions used, etc. - * \return info the result of the scan - */ -void tgsi_scan_shader(const struct tgsi_token *tokens, - struct tgsi_shader_info *info) { - uint procType, i; - struct tgsi_parse_context parse; - unsigned current_depth = 0; - - memset(info, 0, sizeof(*info)); - for (i = 0; i < TGSI_FILE_COUNT; i++) - info->file_max[i] = -1; - for (i = 0; i < Elements(info->const_file_max); i++) - info->const_file_max[i] = -1; - info->properties[TGSI_PROPERTY_GS_INVOCATIONS] = 1; - - /** - ** Setup to begin parsing input shader - **/ - if (tgsi_parse_init(&parse, tokens) != TGSI_PARSE_OK) { - debug_printf("tgsi_parse_init() failed in tgsi_scan_shader()!\n"); - return; - } - procType = parse.FullHeader.Processor.Processor; - assert(procType == TGSI_PROCESSOR_FRAGMENT || - procType == TGSI_PROCESSOR_VERTEX || - procType == TGSI_PROCESSOR_GEOMETRY || - procType == TGSI_PROCESSOR_TESS_CTRL || - procType == TGSI_PROCESSOR_TESS_EVAL || - procType == TGSI_PROCESSOR_COMPUTE); - info->processor = procType; - - /** - ** Loop over incoming program tokens/instructions - */ - while (!tgsi_parse_end_of_tokens(&parse)) { - - info->num_tokens++; - - tgsi_parse_token(&parse); - - switch (parse.FullToken.Token.Type) { - case TGSI_TOKEN_TYPE_INSTRUCTION: { - const struct tgsi_full_instruction *fullinst = - &parse.FullToken.FullInstruction; - uint i; - - assert(fullinst->Instruction.Opcode < TGSI_OPCODE_LAST); - info->opcode_count[fullinst->Instruction.Opcode]++; - - switch (fullinst->Instruction.Opcode) { - case TGSI_OPCODE_IF: - case TGSI_OPCODE_UIF: - case TGSI_OPCODE_BGNLOOP: - current_depth++; - info->max_depth = MAX2(info->max_depth, current_depth); - break; - case TGSI_OPCODE_ENDIF: - case TGSI_OPCODE_ENDLOOP: - current_depth--; - break; - default: - break; - } - - if (fullinst->Instruction.Opcode == TGSI_OPCODE_INTERP_CENTROID || - fullinst->Instruction.Opcode == TGSI_OPCODE_INTERP_OFFSET || - fullinst->Instruction.Opcode == TGSI_OPCODE_INTERP_SAMPLE) { - const struct tgsi_full_src_register *src0 = &fullinst->Src[0]; - unsigned input; - - if (src0->Register.Indirect && src0->Indirect.ArrayID) - input = info->input_array_first[src0->Indirect.ArrayID]; - else - input = src0->Register.Index; - - /* For the INTERP opcodes, the interpolation is always - * PERSPECTIVE unless LINEAR is specified. - */ - switch (info->input_interpolate[input]) { - case TGSI_INTERPOLATE_COLOR: - case TGSI_INTERPOLATE_CONSTANT: - case TGSI_INTERPOLATE_PERSPECTIVE: - switch (fullinst->Instruction.Opcode) { - case TGSI_OPCODE_INTERP_CENTROID: - info->uses_persp_opcode_interp_centroid = true; - break; - case TGSI_OPCODE_INTERP_OFFSET: - info->uses_persp_opcode_interp_offset = true; - break; - case TGSI_OPCODE_INTERP_SAMPLE: - info->uses_persp_opcode_interp_sample = true; - break; - } - break; - - case TGSI_INTERPOLATE_LINEAR: - switch (fullinst->Instruction.Opcode) { - case TGSI_OPCODE_INTERP_CENTROID: - info->uses_linear_opcode_interp_centroid = true; - break; - case TGSI_OPCODE_INTERP_OFFSET: - info->uses_linear_opcode_interp_offset = true; - break; - case TGSI_OPCODE_INTERP_SAMPLE: - info->uses_linear_opcode_interp_sample = true; - break; - } - break; - } - } - - if (fullinst->Instruction.Opcode >= TGSI_OPCODE_F2D && - fullinst->Instruction.Opcode <= TGSI_OPCODE_DSSG) - info->uses_doubles = true; - - for (i = 0; i < fullinst->Instruction.NumSrcRegs; i++) { - const struct tgsi_full_src_register *src = &fullinst->Src[i]; - int ind = src->Register.Index; - - /* Mark which inputs are effectively used */ - if (src->Register.File == TGSI_FILE_INPUT) { - unsigned usage_mask; - usage_mask = tgsi_util_get_inst_usage_mask(fullinst, i); - if (src->Register.Indirect) { - for (ind = 0; ind < info->num_inputs; ++ind) { - info->input_usage_mask[ind] |= usage_mask; - } - } else { - assert(ind >= 0); - assert(ind < PIPE_MAX_SHADER_INPUTS); - info->input_usage_mask[ind] |= usage_mask; - } - - if (procType == TGSI_PROCESSOR_FRAGMENT && info->reads_position && - src->Register.Index == 0 && - (src->Register.SwizzleX == TGSI_SWIZZLE_Z || - src->Register.SwizzleY == TGSI_SWIZZLE_Z || - src->Register.SwizzleZ == TGSI_SWIZZLE_Z || - src->Register.SwizzleW == TGSI_SWIZZLE_Z)) { - info->reads_z = TRUE; - } - } - - /* check for indirect register reads */ - if (src->Register.Indirect) { - info->indirect_files |= (1 << src->Register.File); - info->indirect_files_read |= (1 << src->Register.File); - } - - if (src->Register.Dimension && src->Dimension.Indirect) { - info->dimension_indirect_files |= (1 << src->Register.File); - } - /* MSAA samplers */ - if (src->Register.File == TGSI_FILE_SAMPLER) { - assert(fullinst->Instruction.Texture); - assert((unsigned)src->Register.Index < - Elements(info->is_msaa_sampler)); - - if (fullinst->Instruction.Texture && - (fullinst->Texture.Texture == TGSI_TEXTURE_2D_MSAA || - fullinst->Texture.Texture == TGSI_TEXTURE_2D_ARRAY_MSAA)) { - info->is_msaa_sampler[src->Register.Index] = TRUE; - } - } - } - - /* check for indirect register writes */ - for (i = 0; i < fullinst->Instruction.NumDstRegs; i++) { - const struct tgsi_full_dst_register *dst = &fullinst->Dst[i]; - if (dst->Register.Indirect) { - info->indirect_files |= (1 << dst->Register.File); - info->indirect_files_written |= (1 << dst->Register.File); - } - if (dst->Register.Dimension && dst->Dimension.Indirect) - info->dimension_indirect_files |= (1 << dst->Register.File); - } - - info->num_instructions++; - } break; - - case TGSI_TOKEN_TYPE_DECLARATION: { - const struct tgsi_full_declaration *fulldecl = - &parse.FullToken.FullDeclaration; - const uint file = fulldecl->Declaration.File; - uint reg; - - if (fulldecl->Declaration.Array) { - unsigned array_id = fulldecl->Array.ArrayID; - - switch (file) { - case TGSI_FILE_INPUT: - assert(array_id < ARRAY_SIZE(info->input_array_first)); - info->input_array_first[array_id] = fulldecl->Range.First; - info->input_array_last[array_id] = fulldecl->Range.Last; - break; - case TGSI_FILE_OUTPUT: - assert(array_id < ARRAY_SIZE(info->output_array_first)); - info->output_array_first[array_id] = fulldecl->Range.First; - info->output_array_last[array_id] = fulldecl->Range.Last; - break; - } - info->array_max[file] = MAX2(info->array_max[file], array_id); - } - - for (reg = fulldecl->Range.First; reg <= fulldecl->Range.Last; reg++) { - unsigned semName = fulldecl->Semantic.Name; - unsigned semIndex = - fulldecl->Semantic.Index + (reg - fulldecl->Range.First); - - /* only first 32 regs will appear in this bitfield */ - info->file_mask[file] |= (1 << reg); - info->file_count[file]++; - info->file_max[file] = MAX2(info->file_max[file], (int)reg); - - if (file == TGSI_FILE_CONSTANT) { - int buffer = 0; - - if (fulldecl->Declaration.Dimension) - buffer = fulldecl->Dim.Index2D; - - info->const_file_max[buffer] = - MAX2(info->const_file_max[buffer], (int)reg); - } else if (file == TGSI_FILE_INPUT) { - info->input_semantic_name[reg] = (ubyte)semName; - info->input_semantic_index[reg] = (ubyte)semIndex; - info->input_interpolate[reg] = (ubyte)fulldecl->Interp.Interpolate; - info->input_interpolate_loc[reg] = (ubyte)fulldecl->Interp.Location; - info->input_cylindrical_wrap[reg] = - (ubyte)fulldecl->Interp.CylindricalWrap; - info->num_inputs++; - - /* Only interpolated varyings. Don't include POSITION. - * Don't include integer varyings, because they are not - * interpolated. - */ - if (semName == TGSI_SEMANTIC_GENERIC || - semName == TGSI_SEMANTIC_TEXCOORD || - semName == TGSI_SEMANTIC_COLOR || - semName == TGSI_SEMANTIC_BCOLOR || semName == TGSI_SEMANTIC_FOG || - semName == TGSI_SEMANTIC_CLIPDIST || - semName == TGSI_SEMANTIC_CULLDIST) { - switch (fulldecl->Interp.Interpolate) { - case TGSI_INTERPOLATE_COLOR: - case TGSI_INTERPOLATE_PERSPECTIVE: - switch (fulldecl->Interp.Location) { - case TGSI_INTERPOLATE_LOC_CENTER: - info->uses_persp_center = true; - break; - case TGSI_INTERPOLATE_LOC_CENTROID: - info->uses_persp_centroid = true; - break; - case TGSI_INTERPOLATE_LOC_SAMPLE: - info->uses_persp_sample = true; - break; - } - break; - case TGSI_INTERPOLATE_LINEAR: - switch (fulldecl->Interp.Location) { - case TGSI_INTERPOLATE_LOC_CENTER: - info->uses_linear_center = true; - break; - case TGSI_INTERPOLATE_LOC_CENTROID: - info->uses_linear_centroid = true; - break; - case TGSI_INTERPOLATE_LOC_SAMPLE: - info->uses_linear_sample = true; - break; - } - break; - /* TGSI_INTERPOLATE_CONSTANT doesn't do any interpolation. */ - } - } - - if (semName == TGSI_SEMANTIC_PRIMID) - info->uses_primid = TRUE; - else if (procType == TGSI_PROCESSOR_FRAGMENT) { - if (semName == TGSI_SEMANTIC_POSITION) - info->reads_position = TRUE; - else if (semName == TGSI_SEMANTIC_FACE) - info->uses_frontface = TRUE; - } - } else if (file == TGSI_FILE_SYSTEM_VALUE) { - unsigned index = fulldecl->Range.First; - - info->system_value_semantic_name[index] = semName; - info->num_system_values = MAX2(info->num_system_values, index + 1); - - if (semName == TGSI_SEMANTIC_INSTANCEID) { - info->uses_instanceid = TRUE; - } else if (semName == TGSI_SEMANTIC_VERTEXID) { - info->uses_vertexid = TRUE; - } else if (semName == TGSI_SEMANTIC_VERTEXID_NOBASE) { - info->uses_vertexid_nobase = TRUE; - } else if (semName == TGSI_SEMANTIC_BASEVERTEX) { - info->uses_basevertex = TRUE; - } else if (semName == TGSI_SEMANTIC_PRIMID) { - info->uses_primid = TRUE; - } else if (semName == TGSI_SEMANTIC_INVOCATIONID) { - info->uses_invocationid = TRUE; - } - } else if (file == TGSI_FILE_OUTPUT) { - info->output_semantic_name[reg] = (ubyte)semName; - info->output_semantic_index[reg] = (ubyte)semIndex; - info->num_outputs++; - - if (semName == TGSI_SEMANTIC_COLOR) - info->colors_written |= 1 << semIndex; - - if (procType == TGSI_PROCESSOR_VERTEX || - procType == TGSI_PROCESSOR_GEOMETRY || - procType == TGSI_PROCESSOR_TESS_CTRL || - procType == TGSI_PROCESSOR_TESS_EVAL) { - if (semName == TGSI_SEMANTIC_VIEWPORT_INDEX) { - info->writes_viewport_index = TRUE; - } else if (semName == TGSI_SEMANTIC_LAYER) { - info->writes_layer = TRUE; - } else if (semName == TGSI_SEMANTIC_PSIZE) { - info->writes_psize = TRUE; - } else if (semName == TGSI_SEMANTIC_CLIPVERTEX) { - info->writes_clipvertex = TRUE; - } - } - - if (procType == TGSI_PROCESSOR_FRAGMENT) { - if (semName == TGSI_SEMANTIC_POSITION) { - info->writes_z = TRUE; - } else if (semName == TGSI_SEMANTIC_STENCIL) { - info->writes_stencil = TRUE; - } - } - - if (procType == TGSI_PROCESSOR_VERTEX) { - if (semName == TGSI_SEMANTIC_EDGEFLAG) { - info->writes_edgeflag = TRUE; - } - } - } else if (file == TGSI_FILE_SAMPLER) { - info->samplers_declared |= 1 << reg; - } - } - } break; - - case TGSI_TOKEN_TYPE_IMMEDIATE: { - uint reg = info->immediate_count++; - uint file = TGSI_FILE_IMMEDIATE; - - info->file_mask[file] |= (1 << reg); - info->file_count[file]++; - info->file_max[file] = MAX2(info->file_max[file], (int)reg); - } break; - - case TGSI_TOKEN_TYPE_PROPERTY: { - const struct tgsi_full_property *fullprop = &parse.FullToken.FullProperty; - unsigned name = fullprop->Property.PropertyName; - unsigned value = fullprop->u[0].Data; - - assert(name < Elements(info->properties)); - info->properties[name] = value; - - switch (name) { - case TGSI_PROPERTY_NUM_CLIPDIST_ENABLED: - info->num_written_clipdistance = value; - info->clipdist_writemask |= (1 << value) - 1; - break; - case TGSI_PROPERTY_NUM_CULLDIST_ENABLED: - info->num_written_culldistance = value; - info->culldist_writemask |= (1 << value) - 1; - break; - } - } break; - - default: - assert(0); - } - } - - info->uses_kill = (info->opcode_count[TGSI_OPCODE_KILL_IF] || - info->opcode_count[TGSI_OPCODE_KILL]); - - /* The dimensions of the IN decleration in geometry shader have - * to be deduced from the type of the input primitive. - */ - if (procType == TGSI_PROCESSOR_GEOMETRY) { - unsigned input_primitive = info->properties[TGSI_PROPERTY_GS_INPUT_PRIM]; - int num_verts = u_vertices_per_prim(input_primitive); - int j; - info->file_count[TGSI_FILE_INPUT] = num_verts; - info->file_max[TGSI_FILE_INPUT] = - MAX2(info->file_max[TGSI_FILE_INPUT], num_verts - 1); - for (j = 0; j < num_verts; ++j) { - info->file_mask[TGSI_FILE_INPUT] |= (1 << j); - } - } - - tgsi_parse_free(&parse); -} - -/** - * Check if the given shader is a "passthrough" shader consisting of only - * MOV instructions of the form: MOV OUT[n], IN[n] - * - */ -boolean tgsi_is_passthrough_shader(const struct tgsi_token *tokens) { - struct tgsi_parse_context parse; - - /** - ** Setup to begin parsing input shader - **/ - if (tgsi_parse_init(&parse, tokens) != TGSI_PARSE_OK) { - debug_printf("tgsi_parse_init() failed in tgsi_is_passthrough_shader()!\n"); - return FALSE; - } - - /** - ** Loop over incoming program tokens/instructions - */ - while (!tgsi_parse_end_of_tokens(&parse)) { - - tgsi_parse_token(&parse); - - switch (parse.FullToken.Token.Type) { - case TGSI_TOKEN_TYPE_INSTRUCTION: { - struct tgsi_full_instruction *fullinst = &parse.FullToken.FullInstruction; - const struct tgsi_full_src_register *src = &fullinst->Src[0]; - const struct tgsi_full_dst_register *dst = &fullinst->Dst[0]; - - /* Do a whole bunch of checks for a simple move */ - if (fullinst->Instruction.Opcode != TGSI_OPCODE_MOV || - (src->Register.File != TGSI_FILE_INPUT && - src->Register.File != TGSI_FILE_SYSTEM_VALUE) || - dst->Register.File != TGSI_FILE_OUTPUT || - src->Register.Index != dst->Register.Index || - - src->Register.Negate || src->Register.Absolute || - - src->Register.SwizzleX != TGSI_SWIZZLE_X || - src->Register.SwizzleY != TGSI_SWIZZLE_Y || - src->Register.SwizzleZ != TGSI_SWIZZLE_Z || - src->Register.SwizzleW != TGSI_SWIZZLE_W || - - dst->Register.WriteMask != TGSI_WRITEMASK_XYZW) { - tgsi_parse_free(&parse); - return FALSE; - } - } break; - - case TGSI_TOKEN_TYPE_DECLARATION: - /* fall-through */ - case TGSI_TOKEN_TYPE_IMMEDIATE: - /* fall-through */ - case TGSI_TOKEN_TYPE_PROPERTY: - /* fall-through */ - default:; /* no-op */ - } - } - - tgsi_parse_free(&parse); - - /* if we get here, it's a pass-through shader */ - return TRUE; -} diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_scan.h b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_scan.h deleted file mode 100644 index d8c98c57f..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_scan.h +++ /dev/null @@ -1,146 +0,0 @@ -/************************************************************************** - * - * Copyright 2008 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -#ifndef TGSI_SCAN_H -#define TGSI_SCAN_H - -#include "pipe/p_compiler.h" -#include "pipe/p_shader_tokens.h" -#include "pipe/p_state.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * Shader summary info - */ -struct tgsi_shader_info { - uint num_tokens; - - ubyte num_inputs; - ubyte num_outputs; - ubyte input_semantic_name[PIPE_MAX_SHADER_INPUTS]; /**< TGSI_SEMANTIC_x */ - ubyte input_semantic_index[PIPE_MAX_SHADER_INPUTS]; - ubyte input_interpolate[PIPE_MAX_SHADER_INPUTS]; - ubyte input_interpolate_loc[PIPE_MAX_SHADER_INPUTS]; - ubyte input_usage_mask[PIPE_MAX_SHADER_INPUTS]; - ubyte input_cylindrical_wrap[PIPE_MAX_SHADER_INPUTS]; - ubyte output_semantic_name[PIPE_MAX_SHADER_OUTPUTS]; /**< TGSI_SEMANTIC_x */ - ubyte output_semantic_index[PIPE_MAX_SHADER_OUTPUTS]; - - ubyte num_system_values; - ubyte system_value_semantic_name[PIPE_MAX_SHADER_INPUTS]; - - ubyte processor; - - uint file_mask[TGSI_FILE_COUNT]; /**< bitmask of declared registers */ - uint file_count[TGSI_FILE_COUNT]; /**< number of declared registers */ - int file_max[TGSI_FILE_COUNT]; /**< highest index of declared registers */ - int const_file_max[PIPE_MAX_CONSTANT_BUFFERS]; - unsigned samplers_declared; /**< bitmask of declared samplers */ - - ubyte input_array_first[PIPE_MAX_SHADER_INPUTS]; - ubyte input_array_last[PIPE_MAX_SHADER_INPUTS]; - ubyte output_array_first[PIPE_MAX_SHADER_OUTPUTS]; - ubyte output_array_last[PIPE_MAX_SHADER_OUTPUTS]; - unsigned - array_max[TGSI_FILE_COUNT]; /**< highest index array per register file */ - - uint immediate_count; /**< number of immediates declared */ - uint num_instructions; - - uint opcode_count[TGSI_OPCODE_LAST]; /**< opcode histogram */ - - ubyte colors_written; - boolean reads_position; /**< does fragment shader read position? */ - boolean reads_z; /**< does fragment shader read depth? */ - boolean writes_z; /**< does fragment shader write Z value? */ - boolean writes_stencil; /**< does fragment shader write stencil value? */ - boolean writes_edgeflag; /**< vertex shader outputs edgeflag */ - boolean uses_kill; /**< KILL or KILL_IF instruction used? */ - boolean uses_persp_center; - boolean uses_persp_centroid; - boolean uses_persp_sample; - boolean uses_linear_center; - boolean uses_linear_centroid; - boolean uses_linear_sample; - boolean uses_persp_opcode_interp_centroid; - boolean uses_persp_opcode_interp_offset; - boolean uses_persp_opcode_interp_sample; - boolean uses_linear_opcode_interp_centroid; - boolean uses_linear_opcode_interp_offset; - boolean uses_linear_opcode_interp_sample; - boolean uses_instanceid; - boolean uses_vertexid; - boolean uses_vertexid_nobase; - boolean uses_basevertex; - boolean uses_primid; - boolean uses_frontface; - boolean uses_invocationid; - boolean writes_psize; - boolean writes_clipvertex; - boolean writes_viewport_index; - boolean writes_layer; - boolean is_msaa_sampler[PIPE_MAX_SAMPLERS]; - boolean uses_doubles; /**< uses any of the double instructions */ - unsigned clipdist_writemask; - unsigned culldist_writemask; - unsigned num_written_culldistance; - unsigned num_written_clipdistance; - /** - * Bitmask indicating which register files are accessed with - * indirect addressing. The bits are (1 << TGSI_FILE_x), etc. - */ - unsigned indirect_files; - /** - * Bitmask indicating which register files are read / written with - * indirect addressing. The bits are (1 << TGSI_FILE_x). - */ - unsigned indirect_files_read; - unsigned indirect_files_written; - - unsigned dimension_indirect_files; - - unsigned properties[TGSI_PROPERTY_COUNT]; /* index with TGSI_PROPERTY_ */ - - /** - * Max nesting limit of loops/if's - */ - unsigned max_depth; -}; - -extern void tgsi_scan_shader(const struct tgsi_token *tokens, - struct tgsi_shader_info *info); - -extern boolean tgsi_is_passthrough_shader(const struct tgsi_token *tokens); - -#ifdef __cplusplus -} // extern "C" -#endif - -#endif /* TGSI_SCAN_H */ diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_strings.c b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_strings.c deleted file mode 100644 index 2aec87b49..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_strings.c +++ /dev/null @@ -1,167 +0,0 @@ -/************************************************************************** - * - * Copyright 2007-2008 VMware, Inc. - * Copyright 2012 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -#include "tgsi_strings.h" -#include "pipe/p_compiler.h" -#include "util/u_memory.h" - -const char *tgsi_processor_type_names[6] = {"FRAG", "VERT", "GEOM", - "TESS_CTRL", "TESS_EVAL", "COMP"}; - -static const char *tgsi_file_names[] = { - "NULL", "CONST", "IN", "OUT", "TEMP", "SAMP", "ADDR", "IMM", - "PRED", "SV", "IMAGE", "SVIEW", "BUFFER", "MEMORY", "HWATOMIC", -}; - -const char *tgsi_semantic_names[TGSI_SEMANTIC_COUNT] = { - "POSITION", "COLOR", "BCOLOR", "FOG", - "PSIZE", "GENERIC", "NORMAL", "FACE", - "EDGEFLAG", "PRIM_ID", "INSTANCEID", "VERTEXID", - "STENCIL", "CLIPDIST", "CLIPVERTEX", "GRID_SIZE", - "BLOCK_ID", "BLOCK_SIZE", "THREAD_ID", "TEXCOORD", - "PCOORD", "VIEWPORT_INDEX", "LAYER", "CULLDIST", - "SAMPLEID", "SAMPLEPOS", "SAMPLEMASK", "INVOCATIONID", - "VERTEXID_NOBASE", "BASEVERTEX", "PATCH", "TESSCOORD", - "TESSOUTER", "TESSINNER", "VERTICESIN", "HELPER_INVOCATION", -}; - -const char *tgsi_texture_names[TGSI_TEXTURE_COUNT] = { - "BUFFER", - "1D", - "2D", - "3D", - "CUBE", - "RECT", - "SHADOW1D", - "SHADOW2D", - "SHADOWRECT", - "1D_ARRAY", - "2D_ARRAY", - "SHADOW1D_ARRAY", - "SHADOW2D_ARRAY", - "SHADOWCUBE", - "2D_MSAA", - "2D_ARRAY_MSAA", - "CUBEARRAY", - "SHADOWCUBEARRAY", - "UNKNOWN", -}; - -const char *tgsi_property_names[TGSI_PROPERTY_COUNT] = { - "GS_INPUT_PRIMITIVE", - "GS_OUTPUT_PRIMITIVE", - "GS_MAX_OUTPUT_VERTICES", - "FS_COORD_ORIGIN", - "FS_COORD_PIXEL_CENTER", - "FS_COLOR0_WRITES_ALL_CBUFS", - "FS_DEPTH_LAYOUT", - "VS_PROHIBIT_UCPS", - "GS_INVOCATIONS", - "VS_WINDOW_SPACE_POSITION", - "TCS_VERTICES_OUT", - "TES_PRIM_MODE", - "TES_SPACING", - "TES_VERTEX_ORDER_CW", - "TES_POINT_MODE", - "NUM_CLIPDIST_ENABLED", - "NUM_CULLDIST_ENABLED", - "FS_EARLY_DEPTH_STENCIL", - "FS_POST_DEPTH_COVERAGE", - "NEXT_SHADER", - "CS_FIXED_BLOCK_WIDTH", - "CS_FIXED_BLOCK_HEIGHT", - "CS_FIXED_BLOCK_DEPTH", - "MUL_ZERO_WINS", -}; - -const char *tgsi_return_type_names[TGSI_RETURN_TYPE_COUNT] = { - "UNORM", "SNORM", "SINT", "UINT", "FLOAT"}; - -const char *tgsi_interpolate_names[TGSI_INTERPOLATE_COUNT] = { - "CONSTANT", "LINEAR", "PERSPECTIVE", "COLOR"}; - -const char *tgsi_interpolate_locations[TGSI_INTERPOLATE_LOC_COUNT] = { - "CENTER", - "CENTROID", - "SAMPLE", -}; - -const char *tgsi_invariant_name = "INVARIANT"; - -const char *tgsi_primitive_names[PIPE_PRIM_MAX] = { - "POINTS", - "LINES", - "LINE_LOOP", - "LINE_STRIP", - "TRIANGLES", - "TRIANGLE_STRIP", - "TRIANGLE_FAN", - "QUADS", - "QUAD_STRIP", - "POLYGON", - "LINES_ADJACENCY", - "LINE_STRIP_ADJACENCY", - "TRIANGLES_ADJACENCY", - "TRIANGLE_STRIP_ADJACENCY", - "PATCHES", -}; - -const char *tgsi_fs_coord_origin_names[2] = {"UPPER_LEFT", "LOWER_LEFT"}; - -const char *tgsi_fs_coord_pixel_center_names[2] = {"HALF_INTEGER", "INTEGER"}; - -const char *tgsi_immediate_type_names[4] = {"FLT32", "UINT32", "INT32", - "FLT64"}; - -const char *tgsi_memory_names[3] = { - "COHERENT", - "RESTRICT", - "VOLATILE", -}; - -static inline void tgsi_strings_check(void) { - STATIC_ASSERT(Elements(tgsi_semantic_names) == TGSI_SEMANTIC_COUNT); - STATIC_ASSERT(Elements(tgsi_texture_names) == TGSI_TEXTURE_COUNT); - STATIC_ASSERT(Elements(tgsi_property_names) == TGSI_PROPERTY_COUNT); - STATIC_ASSERT(Elements(tgsi_primitive_names) == PIPE_PRIM_MAX); - STATIC_ASSERT(Elements(tgsi_interpolate_names) == TGSI_INTERPOLATE_COUNT); - STATIC_ASSERT(Elements(tgsi_return_type_names) == TGSI_RETURN_TYPE_COUNT); - (void)tgsi_processor_type_names; - (void)tgsi_return_type_names; - (void)tgsi_immediate_type_names; - (void)tgsi_fs_coord_origin_names; - (void)tgsi_fs_coord_pixel_center_names; -} - -const char *tgsi_file_name(unsigned file) { - STATIC_ASSERT(Elements(tgsi_file_names) == TGSI_FILE_COUNT); - if (file < Elements(tgsi_file_names)) - return tgsi_file_names[file]; - else - return "invalid file"; -} diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_strings.h b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_strings.h deleted file mode 100644 index 5e1442884..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_strings.h +++ /dev/null @@ -1,71 +0,0 @@ -/************************************************************************** - * - * Copyright 2007-2008 VMware, Inc. - * Copyright 2012 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -#ifndef TGSI_STRINGS_H -#define TGSI_STRINGS_H - -#include "pipe/p_shader_tokens.h" -#include "pipe/p_state.h" - -#if defined __cplusplus -extern "C" { -#endif - -extern const char *tgsi_processor_type_names[6]; - -extern const char *tgsi_semantic_names[TGSI_SEMANTIC_COUNT]; - -extern const char *tgsi_texture_names[TGSI_TEXTURE_COUNT]; - -extern const char *tgsi_property_names[TGSI_PROPERTY_COUNT]; - -extern const char *tgsi_return_type_names[TGSI_RETURN_TYPE_COUNT]; - -extern const char *tgsi_interpolate_names[TGSI_INTERPOLATE_COUNT]; - -extern const char *tgsi_interpolate_locations[TGSI_INTERPOLATE_LOC_COUNT]; - -extern const char *tgsi_invariant_name; - -extern const char *tgsi_primitive_names[PIPE_PRIM_MAX]; - -extern const char *tgsi_fs_coord_origin_names[2]; - -extern const char *tgsi_fs_coord_pixel_center_names[2]; - -extern const char *tgsi_immediate_type_names[4]; - -extern const char *tgsi_memory_names[3]; - -const char *tgsi_file_name(unsigned file); - -#if defined __cplusplus -} -#endif - -#endif /* TGSI_STRINGS_H */ diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_text.c b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_text.c deleted file mode 100644 index 42fee715a..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_text.c +++ /dev/null @@ -1,1767 +0,0 @@ -/************************************************************************** - * - * Copyright 2008 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -#include "tgsi_text.h" -#include "pipe/p_defines.h" -#include "tgsi_build.h" -#include "tgsi_dump.h" -#include "tgsi_info.h" -#include "tgsi_parse.h" -#include "tgsi_sanity.h" -#include "tgsi_strings.h" -#include "tgsi_util.h" -#include "util/u_debug.h" -#include "util/u_inlines.h" -#include "util/u_memory.h" -#include "util/u_prim.h" - -static boolean is_alpha_underscore(const char *cur) { - return (*cur >= 'a' && *cur <= 'z') || (*cur >= 'A' && *cur <= 'Z') || - *cur == '_'; -} - -static boolean is_digit(const char *cur) { return *cur >= '0' && *cur <= '9'; } - -static boolean is_digit_alpha_underscore(const char *cur) { - return is_digit(cur) || is_alpha_underscore(cur); -} - -static char uprcase(char c) { - if (c >= 'a' && c <= 'z') - return c + 'A' - 'a'; - return c; -} - -/* - * Ignore case of str1 and assume str1 is already uppercase. - * Return TRUE iff str1 and str2 are equal. - */ -static int streq_nocase_uprcase(const char *str1, const char *str2) { - while (*str1 && *str2) { - if (*str1 != uprcase(*str2)) - return FALSE; - str1++; - str2++; - } - return *str1 == 0 && *str2 == 0; -} - -/* Return TRUE if both strings match. - * The second string is terminated by zero. - * The pointer to the first string is moved at end of the read word - * on success. - */ -static boolean str_match_no_case(const char **pcur, const char *str) { - const char *cur = *pcur; - - while (*str != '\0' && *str == uprcase(*cur)) { - str++; - cur++; - } - if (*str == '\0') { - *pcur = cur; - return TRUE; - } - return FALSE; -} - -/* Return TRUE if both strings match. - * The first string is be terminated by a non-digit non-letter non-underscore - * character, the second string is terminated by zero. - * The pointer to the first string is moved at end of the read word - * on success. - */ -static boolean str_match_nocase_whole(const char **pcur, const char *str) { - const char *cur = *pcur; - - if (str_match_no_case(&cur, str) && !is_digit_alpha_underscore(cur)) { - *pcur = cur; - return TRUE; - } - return FALSE; -} - -/* Return the array index that matches starting at *pcur, where the string at - * *pcur is terminated by a non-digit non-letter non-underscore. - * Returns -1 if no match is found. - * - * On success, the pointer to the first string is moved to the end of the read - * word. - */ -static int str_match_name_from_array(const char **pcur, - const char *const *array, - unsigned array_size) { - for (unsigned j = 0; j < array_size; ++j) { - if (str_match_nocase_whole(pcur, array[j])) - return j; - } - return -1; -} - -/* Return the format corresponding to the name at *pcur. - * Returns -1 if there is no format name. - * - * On success, the pointer to the string is moved to the end of the read format - * name. - */ -static int str_match_format(const char **pcur) { - for (unsigned i = 0; i < PIPE_FORMAT_COUNT; i++) { - const struct util_format_description *desc = util_format_description(i); - if (desc && str_match_nocase_whole(pcur, desc->name)) { - return i; - } - } - return -1; -} - -/* Eat until eol - */ -static void eat_until_eol(const char **pcur) { - while (**pcur != '\0' && **pcur != '\n') - (*pcur)++; -} - -/* Eat zero or more whitespaces. - */ -static void eat_opt_white(const char **pcur) { - while (**pcur == ' ' || **pcur == '\t' || **pcur == '\n') - (*pcur)++; -} - -/* Eat one or more whitespaces. - * Return TRUE if at least one whitespace eaten. - */ -static boolean eat_white(const char **pcur) { - const char *cur = *pcur; - - eat_opt_white(pcur); - return *pcur > cur; -} - -/* Parse unsigned integer. - * No checks for overflow. - */ -static boolean parse_uint(const char **pcur, uint *val) { - const char *cur = *pcur; - - if (is_digit(cur)) { - *val = *cur++ - '0'; - while (is_digit(cur)) - *val = *val * 10 + *cur++ - '0'; - *pcur = cur; - return TRUE; - } - return FALSE; -} - -static boolean parse_int(const char **pcur, int *val) { - const char *cur = *pcur; - int sign = (*cur == '-' ? -1 : 1); - - if (*cur == '+' || *cur == '-') - cur++; - - if (parse_uint(&cur, (uint *)val)) { - *val *= sign; - *pcur = cur; - return TRUE; - } - - return FALSE; -} - -static boolean parse_identifier(const char **pcur, char *ret, size_t len) { - const char *cur = *pcur; - size_t i = 0; - if (is_alpha_underscore(cur)) { - ret[i++] = *cur++; - while (is_alpha_underscore(cur) || is_digit(cur)) { - if (i == len - 1) - return FALSE; - ret[i++] = *cur++; - } - ret[i++] = '\0'; - *pcur = cur; - return TRUE; - } - return FALSE; -} - -/* Parse floating point. - */ -static boolean parse_float(const char **pcur, float *val) { - const char *cur = *pcur; - boolean integral_part = FALSE; - boolean fractional_part = FALSE; - - if (*cur == '0' && *(cur + 1) == 'x') { - union fi fi; - fi.ui = strtoul(cur, NULL, 16); - *val = fi.f; - cur += 10; - goto out; - } - - *val = (float)atof(cur); - if (*cur == '-' || *cur == '+') - cur++; - if (is_digit(cur)) { - cur++; - integral_part = TRUE; - while (is_digit(cur)) - cur++; - } - if (*cur == '.') { - cur++; - if (is_digit(cur)) { - cur++; - fractional_part = TRUE; - while (is_digit(cur)) - cur++; - } - } - if (!integral_part && !fractional_part) - return FALSE; - if (uprcase(*cur) == 'E') { - cur++; - if (*cur == '-' || *cur == '+') - cur++; - if (is_digit(cur)) { - cur++; - while (is_digit(cur)) - cur++; - } else - return FALSE; - } - -out: - *pcur = cur; - return TRUE; -} - -static boolean parse_double(const char **pcur, uint32_t *val0, uint32_t *val1) { - const char *cur = *pcur; - union { - double dval; - uint32_t uval[2]; - } v; - - if (*cur == '0' && *(cur + 1) == 'x') { - *val0 = strtoul(cur, NULL, 16); - cur += 11; - *val1 = strtoul(cur, NULL, 16); - cur += 11; - *pcur = cur; - return TRUE; - } - v.dval = strtod(cur, (char **)pcur); - if (*pcur == cur) - return FALSE; - - *val0 = v.uval[0]; - *val1 = v.uval[1]; - - return TRUE; -} - -struct translate_ctx { - const char *text; - const char *cur; - struct tgsi_token *tokens; - struct tgsi_token *tokens_cur; - struct tgsi_token *tokens_end; - struct tgsi_header *header; - unsigned processor : 4; - unsigned implied_array_size : 6; - unsigned num_immediates; -}; - -static void report_error(struct translate_ctx *ctx, const char *format, ...) { - va_list args; - int line = 1; - int column = 1; - const char *itr = ctx->text; - - debug_printf("\nTGSI asm error: "); - - va_start(args, format); - _debug_vprintf(format, args); - va_end(args); - - while (itr != ctx->cur) { - if (*itr == '\n') { - column = 1; - ++line; - } - ++column; - ++itr; - } - - debug_printf(" [%d : %d] \n", line, column); -} - -/* Parse shader header. - * Return TRUE for one of the following headers. - * FRAG - * GEOM - * VERT - */ -static boolean parse_header(struct translate_ctx *ctx) { - uint processor; - - if (str_match_nocase_whole(&ctx->cur, "FRAG")) - processor = TGSI_PROCESSOR_FRAGMENT; - else if (str_match_nocase_whole(&ctx->cur, "VERT")) - processor = TGSI_PROCESSOR_VERTEX; - else if (str_match_nocase_whole(&ctx->cur, "GEOM")) - processor = TGSI_PROCESSOR_GEOMETRY; - else if (str_match_nocase_whole(&ctx->cur, "TESS_CTRL")) - processor = TGSI_PROCESSOR_TESS_CTRL; - else if (str_match_nocase_whole(&ctx->cur, "TESS_EVAL")) - processor = TGSI_PROCESSOR_TESS_EVAL; - else if (str_match_nocase_whole(&ctx->cur, "COMP")) - processor = TGSI_PROCESSOR_COMPUTE; - else { - report_error(ctx, "Unknown header"); - return FALSE; - } - - if (ctx->tokens_cur >= ctx->tokens_end) - return FALSE; - ctx->header = (struct tgsi_header *)ctx->tokens_cur++; - *ctx->header = tgsi_build_header(); - - if (ctx->tokens_cur >= ctx->tokens_end) - return FALSE; - *(struct tgsi_processor *)ctx->tokens_cur++ = - tgsi_build_processor(processor, ctx->header); - ctx->processor = processor; - - return TRUE; -} - -static boolean parse_label(struct translate_ctx *ctx, uint *val) { - const char *cur = ctx->cur; - - if (parse_uint(&cur, val)) { - eat_opt_white(&cur); - if (*cur == ':') { - cur++; - ctx->cur = cur; - return TRUE; - } - } - return FALSE; -} - -static boolean parse_file(const char **pcur, uint *file) { - uint i; - - for (i = 0; i < TGSI_FILE_COUNT; i++) { - const char *cur = *pcur; - - if (str_match_nocase_whole(&cur, tgsi_file_name(i))) { - *pcur = cur; - *file = i; - return TRUE; - } - } - return FALSE; -} - -static boolean parse_opt_writemask(struct translate_ctx *ctx, uint *writemask) { - const char *cur; - - cur = ctx->cur; - eat_opt_white(&cur); - if (*cur == '.') { - cur++; - *writemask = TGSI_WRITEMASK_NONE; - eat_opt_white(&cur); - if (uprcase(*cur) == 'X') { - cur++; - *writemask |= TGSI_WRITEMASK_X; - } - if (uprcase(*cur) == 'Y') { - cur++; - *writemask |= TGSI_WRITEMASK_Y; - } - if (uprcase(*cur) == 'Z') { - cur++; - *writemask |= TGSI_WRITEMASK_Z; - } - if (uprcase(*cur) == 'W') { - cur++; - *writemask |= TGSI_WRITEMASK_W; - } - - if (*writemask == TGSI_WRITEMASK_NONE) { - report_error(ctx, "Writemask expected"); - return FALSE; - } - - ctx->cur = cur; - } else { - *writemask = TGSI_WRITEMASK_XYZW; - } - return TRUE; -} - -/* ::= `[' - */ -static boolean parse_register_file_bracket(struct translate_ctx *ctx, - uint *file) { - if (!parse_file(&ctx->cur, file)) { - report_error(ctx, "Unknown register file"); - return FALSE; - } - eat_opt_white(&ctx->cur); - if (*ctx->cur != '[') { - report_error(ctx, "Expected `['"); - return FALSE; - } - ctx->cur++; - return TRUE; -} - -/* ::= - */ -static boolean parse_register_file_bracket_index(struct translate_ctx *ctx, - uint *file, int *index) { - uint uindex; - - if (!parse_register_file_bracket(ctx, file)) - return FALSE; - eat_opt_white(&ctx->cur); - if (!parse_uint(&ctx->cur, &uindex)) { - report_error(ctx, "Expected literal unsigned integer"); - return FALSE; - } - *index = (int)uindex; - return TRUE; -} - -/* Parse simple 1d register operand. - * ::= `]' - */ -static boolean parse_register_1d(struct translate_ctx *ctx, uint *file, - int *index) { - if (!parse_register_file_bracket_index(ctx, file, index)) - return FALSE; - eat_opt_white(&ctx->cur); - if (*ctx->cur != ']') { - report_error(ctx, "Expected `]'"); - return FALSE; - } - ctx->cur++; - return TRUE; -} - -struct parsed_bracket { - int index; - - uint ind_file; - int ind_index; - uint ind_comp; - uint ind_array; -}; - -static boolean parse_register_bracket(struct translate_ctx *ctx, - struct parsed_bracket *brackets) { - const char *cur; - uint uindex; - - memset(brackets, 0, sizeof(struct parsed_bracket)); - - eat_opt_white(&ctx->cur); - - cur = ctx->cur; - if (parse_file(&cur, &brackets->ind_file)) { - if (!parse_register_1d(ctx, &brackets->ind_file, &brackets->ind_index)) - return FALSE; - eat_opt_white(&ctx->cur); - - if (*ctx->cur == '.') { - ctx->cur++; - eat_opt_white(&ctx->cur); - - switch (uprcase(*ctx->cur)) { - case 'X': - brackets->ind_comp = TGSI_SWIZZLE_X; - break; - case 'Y': - brackets->ind_comp = TGSI_SWIZZLE_Y; - break; - case 'Z': - brackets->ind_comp = TGSI_SWIZZLE_Z; - break; - case 'W': - brackets->ind_comp = TGSI_SWIZZLE_W; - break; - default: - report_error(ctx, "Expected indirect register swizzle component `x', " - "`y', `z' or `w'"); - return FALSE; - } - ctx->cur++; - eat_opt_white(&ctx->cur); - } - - if (*ctx->cur == '+' || *ctx->cur == '-') - parse_int(&ctx->cur, &brackets->index); - else - brackets->index = 0; - } else { - if (!parse_uint(&ctx->cur, &uindex)) { - report_error(ctx, "Expected literal unsigned integer"); - return FALSE; - } - brackets->index = (int)uindex; - brackets->ind_file = TGSI_FILE_NULL; - brackets->ind_index = 0; - } - eat_opt_white(&ctx->cur); - if (*ctx->cur != ']') { - report_error(ctx, "Expected `]'"); - return FALSE; - } - ctx->cur++; - if (*ctx->cur == '(') { - ctx->cur++; - eat_opt_white(&ctx->cur); - if (!parse_uint(&ctx->cur, &brackets->ind_array)) { - report_error(ctx, "Expected literal unsigned integer"); - return FALSE; - } - eat_opt_white(&ctx->cur); - if (*ctx->cur != ')') { - report_error(ctx, "Expected `)'"); - return FALSE; - } - ctx->cur++; - } - return TRUE; -} - -static boolean parse_opt_register_src_bracket(struct translate_ctx *ctx, - struct parsed_bracket *brackets, - int *parsed_brackets) { - const char *cur = ctx->cur; - - *parsed_brackets = 0; - - eat_opt_white(&cur); - if (cur[0] == '[') { - ++cur; - ctx->cur = cur; - - if (!parse_register_bracket(ctx, brackets)) - return FALSE; - - *parsed_brackets = 1; - } - - return TRUE; -} - -/* Parse source register operand. - * ::= `]' | - * [`.' (`x' | `y' - * | `z' | `w')] `]' | [`.' (`x' | `y' | - * `z' | `w')] `+' `]' | [`.' (`x' - * | `y' | `z' | `w')] `-' `]' - */ -static boolean parse_register_src(struct translate_ctx *ctx, uint *file, - struct parsed_bracket *brackets) { - brackets->ind_comp = TGSI_SWIZZLE_X; - if (!parse_register_file_bracket(ctx, file)) - return FALSE; - if (!parse_register_bracket(ctx, brackets)) - return FALSE; - - return TRUE; -} - -struct parsed_dcl_bracket { - uint first; - uint last; -}; - -static boolean parse_register_dcl_bracket(struct translate_ctx *ctx, - struct parsed_dcl_bracket *bracket) { - uint uindex; - memset(bracket, 0, sizeof(struct parsed_dcl_bracket)); - - eat_opt_white(&ctx->cur); - - if (!parse_uint(&ctx->cur, &uindex)) { - /* it can be an empty bracket [] which means its range - * is from 0 to some implied size */ - if (ctx->cur[0] == ']' && ctx->implied_array_size != 0) { - bracket->first = 0; - bracket->last = ctx->implied_array_size - 1; - goto cleanup; - } - report_error(ctx, "Expected literal unsigned integer"); - return FALSE; - } - bracket->first = uindex; - - eat_opt_white(&ctx->cur); - - if (ctx->cur[0] == '.' && ctx->cur[1] == '.') { - uint uindex; - - ctx->cur += 2; - eat_opt_white(&ctx->cur); - if (!parse_uint(&ctx->cur, &uindex)) { - report_error(ctx, "Expected literal integer"); - return FALSE; - } - bracket->last = (int)uindex; - eat_opt_white(&ctx->cur); - } else { - bracket->last = bracket->first; - } - -cleanup: - if (*ctx->cur != ']') { - report_error(ctx, "Expected `]' or `..'"); - return FALSE; - } - ctx->cur++; - return TRUE; -} - -/* Parse register declaration. - * ::= `]' | - * `..' `]' - */ -static boolean parse_register_dcl(struct translate_ctx *ctx, uint *file, - struct parsed_dcl_bracket *brackets, - int *num_brackets) { - const char *cur; - - *num_brackets = 0; - - if (!parse_register_file_bracket(ctx, file)) - return FALSE; - if (!parse_register_dcl_bracket(ctx, &brackets[0])) - return FALSE; - - *num_brackets = 1; - - cur = ctx->cur; - eat_opt_white(&cur); - - if (cur[0] == '[') { - bool is_in = *file == TGSI_FILE_INPUT; - bool is_out = *file == TGSI_FILE_OUTPUT; - - ++cur; - ctx->cur = cur; - if (!parse_register_dcl_bracket(ctx, &brackets[1])) - return FALSE; - /* for geometry shader we don't really care about - * the first brackets it's always the size of the - * input primitive. so we want to declare just - * the index relevant to the semantics which is in - * the second bracket */ - - /* tessellation has similar constraints to geometry shader */ - if ((ctx->processor == TGSI_PROCESSOR_GEOMETRY && is_in) || - (ctx->processor == TGSI_PROCESSOR_TESS_EVAL && is_in) || - (ctx->processor == TGSI_PROCESSOR_TESS_CTRL && (is_in || is_out))) { - brackets[0] = brackets[1]; - *num_brackets = 1; - } else { - *num_brackets = 2; - } - } - - return TRUE; -} - -/* Parse destination register operand.*/ -static boolean parse_register_dst(struct translate_ctx *ctx, uint *file, - struct parsed_bracket *brackets) { - brackets->ind_comp = TGSI_SWIZZLE_X; - if (!parse_register_file_bracket(ctx, file)) - return FALSE; - if (!parse_register_bracket(ctx, brackets)) - return FALSE; - - return TRUE; -} - -static boolean parse_dst_operand(struct translate_ctx *ctx, - struct tgsi_full_dst_register *dst) { - uint file; - uint writemask; - const char *cur; - struct parsed_bracket bracket[2]; - int parsed_opt_brackets; - - if (!parse_register_dst(ctx, &file, &bracket[0])) - return FALSE; - if (!parse_opt_register_src_bracket(ctx, &bracket[1], &parsed_opt_brackets)) - return FALSE; - - cur = ctx->cur; - eat_opt_white(&cur); - - if (!parse_opt_writemask(ctx, &writemask)) - return FALSE; - - dst->Register.File = file; - if (parsed_opt_brackets) { - dst->Register.Dimension = 1; - dst->Dimension.Indirect = 0; - dst->Dimension.Dimension = 0; - dst->Dimension.Index = bracket[0].index; - - if (bracket[0].ind_file != TGSI_FILE_NULL) { - dst->Dimension.Indirect = 1; - dst->DimIndirect.File = bracket[0].ind_file; - dst->DimIndirect.Index = bracket[0].ind_index; - dst->DimIndirect.Swizzle = bracket[0].ind_comp; - dst->DimIndirect.ArrayID = bracket[0].ind_array; - } - bracket[0] = bracket[1]; - } - dst->Register.Index = bracket[0].index; - dst->Register.WriteMask = writemask; - if (bracket[0].ind_file != TGSI_FILE_NULL) { - dst->Register.Indirect = 1; - dst->Indirect.File = bracket[0].ind_file; - dst->Indirect.Index = bracket[0].ind_index; - dst->Indirect.Swizzle = bracket[0].ind_comp; - dst->Indirect.ArrayID = bracket[0].ind_array; - } - return TRUE; -} - -static boolean parse_optional_swizzle(struct translate_ctx *ctx, uint *swizzle, - boolean *parsed_swizzle, int components) { - const char *cur = ctx->cur; - - *parsed_swizzle = FALSE; - - eat_opt_white(&cur); - if (*cur == '.') { - int i; - - cur++; - eat_opt_white(&cur); - for (i = 0; i < components; i++) { - if (uprcase(*cur) == 'X') - swizzle[i] = TGSI_SWIZZLE_X; - else if (uprcase(*cur) == 'Y') - swizzle[i] = TGSI_SWIZZLE_Y; - else if (uprcase(*cur) == 'Z') - swizzle[i] = TGSI_SWIZZLE_Z; - else if (uprcase(*cur) == 'W') - swizzle[i] = TGSI_SWIZZLE_W; - else { - report_error( - ctx, "Expected register swizzle component `x', `y', `z' or `w'"); - return FALSE; - } - cur++; - } - *parsed_swizzle = TRUE; - ctx->cur = cur; - } - return TRUE; -} - -static boolean parse_src_operand(struct translate_ctx *ctx, - struct tgsi_full_src_register *src) { - uint file; - uint swizzle[4]; - boolean parsed_swizzle; - struct parsed_bracket bracket[2]; - int parsed_opt_brackets; - - if (*ctx->cur == '-') { - ctx->cur++; - eat_opt_white(&ctx->cur); - src->Register.Negate = 1; - } - - if (*ctx->cur == '|') { - ctx->cur++; - eat_opt_white(&ctx->cur); - src->Register.Absolute = 1; - } - - if (!parse_register_src(ctx, &file, &bracket[0])) - return FALSE; - if (!parse_opt_register_src_bracket(ctx, &bracket[1], &parsed_opt_brackets)) - return FALSE; - - src->Register.File = file; - if (parsed_opt_brackets) { - src->Register.Dimension = 1; - src->Dimension.Indirect = 0; - src->Dimension.Dimension = 0; - src->Dimension.Index = bracket[0].index; - if (bracket[0].ind_file != TGSI_FILE_NULL) { - src->Dimension.Indirect = 1; - src->DimIndirect.File = bracket[0].ind_file; - src->DimIndirect.Index = bracket[0].ind_index; - src->DimIndirect.Swizzle = bracket[0].ind_comp; - src->DimIndirect.ArrayID = bracket[0].ind_array; - } - bracket[0] = bracket[1]; - } - src->Register.Index = bracket[0].index; - if (bracket[0].ind_file != TGSI_FILE_NULL) { - src->Register.Indirect = 1; - src->Indirect.File = bracket[0].ind_file; - src->Indirect.Index = bracket[0].ind_index; - src->Indirect.Swizzle = bracket[0].ind_comp; - src->Indirect.ArrayID = bracket[0].ind_array; - } - - /* Parse optional swizzle. - */ - if (parse_optional_swizzle(ctx, swizzle, &parsed_swizzle, 4)) { - if (parsed_swizzle) { - src->Register.SwizzleX = swizzle[0]; - src->Register.SwizzleY = swizzle[1]; - src->Register.SwizzleZ = swizzle[2]; - src->Register.SwizzleW = swizzle[3]; - } - } - - if (src->Register.Absolute) { - eat_opt_white(&ctx->cur); - if (*ctx->cur != '|') { - report_error(ctx, "Expected `|'"); - return FALSE; - } - ctx->cur++; - } - - return TRUE; -} - -static boolean parse_texoffset_operand(struct translate_ctx *ctx, - struct tgsi_texture_offset *src) { - uint file; - uint swizzle[3]; - boolean parsed_swizzle; - struct parsed_bracket bracket; - - if (!parse_register_src(ctx, &file, &bracket)) - return FALSE; - - src->File = file; - src->Index = bracket.index; - - /* Parse optional swizzle. - */ - if (parse_optional_swizzle(ctx, swizzle, &parsed_swizzle, 3)) { - if (parsed_swizzle) { - src->SwizzleX = swizzle[0]; - src->SwizzleY = swizzle[1]; - src->SwizzleZ = swizzle[2]; - } - } - - return TRUE; -} - -static boolean match_inst(const char **pcur, unsigned *saturate, - unsigned *precise, - const struct tgsi_opcode_info *info) { - const char *cur = *pcur; - - /* simple case: the whole string matches the instruction name */ - if (str_match_nocase_whole(&cur, info->mnemonic)) { - *pcur = cur; - *saturate = 0; - *precise = 0; - return TRUE; - } - - if (str_match_no_case(&cur, info->mnemonic)) { - /* the instruction has a suffix, figure it out */ - if (str_match_no_case(&cur, "_SAT")) { - *pcur = cur; - *saturate = 1; - } - - if (str_match_no_case(&cur, "_PRECISE")) { - *pcur = cur; - *precise = 1; - } - - if (!is_digit_alpha_underscore(cur)) - return TRUE; - } - - return FALSE; -} - -static boolean parse_instruction(struct translate_ctx *ctx, boolean has_label) { - int i; - uint saturate = 0; - uint precise = 0; - const struct tgsi_opcode_info *info; - struct tgsi_full_instruction inst; - const char *cur; - uint advance; - - inst = tgsi_default_full_instruction(); - - /* Parse instruction name. - */ - eat_opt_white(&ctx->cur); - for (i = 0; i < TGSI_OPCODE_LAST; i++) { - cur = ctx->cur; - - info = tgsi_get_opcode_info(i); - if (match_inst(&cur, &saturate, &precise, info)) { - if (info->num_dst + info->num_src + info->is_tex == 0) { - ctx->cur = cur; - break; - } else if (*cur == '\0' || eat_white(&cur)) { - ctx->cur = cur; - break; - } - } - } - if (i == TGSI_OPCODE_LAST) { - if (has_label) - report_error(ctx, "Unknown opcode"); - else - report_error(ctx, "Expected `DCL', `IMM' or a label"); - return FALSE; - } - - inst.Instruction.Opcode = i; - inst.Instruction.Saturate = saturate; - inst.Instruction.Precise = precise; - inst.Instruction.NumDstRegs = info->num_dst; - inst.Instruction.NumSrcRegs = info->num_src; - - if (i >= TGSI_OPCODE_SAMPLE && i <= TGSI_OPCODE_GATHER4) { - /* - * These are not considered tex opcodes here (no additional - * target argument) however we're required to set the Texture - * bit so we can set the number of tex offsets. - */ - inst.Instruction.Texture = 1; - inst.Texture.Texture = TGSI_TEXTURE_UNKNOWN; - } - - if ((i >= TGSI_OPCODE_LOAD && i <= TGSI_OPCODE_ATOMIMAX) || - i == TGSI_OPCODE_RESQ) { - inst.Instruction.Memory = 1; - inst.Memory.Qualifier = 0; - } - - /* Parse instruction operands. - */ - for (i = 0; i < info->num_dst + info->num_src + info->is_tex; i++) { - if (i > 0) { - eat_opt_white(&ctx->cur); - if (*ctx->cur != ',') { - report_error(ctx, "Expected `,'"); - return FALSE; - } - ctx->cur++; - eat_opt_white(&ctx->cur); - } - - if (i < info->num_dst) { - if (!parse_dst_operand(ctx, &inst.Dst[i])) - return FALSE; - } else if (i < info->num_dst + info->num_src) { - if (!parse_src_operand(ctx, &inst.Src[i - info->num_dst])) - return FALSE; - } else { - uint j; - - for (j = 0; j < TGSI_TEXTURE_COUNT; j++) { - if (str_match_nocase_whole(&ctx->cur, tgsi_texture_names[j])) { - inst.Instruction.Texture = 1; - inst.Texture.Texture = j; - break; - } - } - if (j == TGSI_TEXTURE_COUNT) { - report_error(ctx, "Expected texture target"); - return FALSE; - } - } - } - - cur = ctx->cur; - eat_opt_white(&cur); - for (i = 0; - inst.Instruction.Texture && *cur == ',' && i < TGSI_FULL_MAX_TEX_OFFSETS; - i++) { - cur++; - eat_opt_white(&cur); - ctx->cur = cur; - if (!parse_texoffset_operand(ctx, &inst.TexOffsets[i])) - return FALSE; - cur = ctx->cur; - eat_opt_white(&cur); - } - inst.Texture.NumOffsets = i; - - cur = ctx->cur; - eat_opt_white(&cur); - - for (; inst.Instruction.Memory && *cur == ','; - ctx->cur = cur, eat_opt_white(&cur)) { - int j; - - cur++; - eat_opt_white(&cur); - - j = str_match_name_from_array(&cur, tgsi_memory_names, - ARRAY_SIZE(tgsi_memory_names)); - if (j >= 0) { - inst.Memory.Qualifier |= 1U << j; - continue; - } - - j = str_match_name_from_array(&cur, tgsi_texture_names, - ARRAY_SIZE(tgsi_texture_names)); - if (j >= 0) { - inst.Memory.Texture = j; - continue; - } - - j = str_match_format(&cur); - if (j >= 0) { - inst.Memory.Format = j; - continue; - } - - ctx->cur = cur; - report_error(ctx, "Expected memory qualifier, texture target, or format\n"); - return FALSE; - } - - cur = ctx->cur; - eat_opt_white(&cur); - if (info->is_branch && *cur == ':') { - uint target; - - cur++; - eat_opt_white(&cur); - if (!parse_uint(&cur, &target)) { - report_error(ctx, "Expected a label"); - return FALSE; - } - inst.Instruction.Label = 1; - inst.Label.Label = target; - ctx->cur = cur; - } - - advance = - tgsi_build_full_instruction(&inst, ctx->tokens_cur, ctx->header, - (uint)(ctx->tokens_end - ctx->tokens_cur)); - if (advance == 0) - return FALSE; - ctx->tokens_cur += advance; - - return TRUE; -} - -/* parses a 4-touple of the form {x, y, z, w} - * where x, y, z, w are numbers */ -static boolean parse_immediate_data(struct translate_ctx *ctx, unsigned type, - union tgsi_immediate_data *values) { - unsigned i; - int ret; - - eat_opt_white(&ctx->cur); - if (*ctx->cur != '{') { - report_error(ctx, "Expected `{'"); - return FALSE; - } - ctx->cur++; - for (i = 0; i < 4; i++) { - eat_opt_white(&ctx->cur); - if (i > 0) { - if (*ctx->cur != ',') { - report_error(ctx, "Expected `,'"); - return FALSE; - } - ctx->cur++; - eat_opt_white(&ctx->cur); - } - - switch (type) { - case TGSI_IMM_FLOAT64: - ret = parse_double(&ctx->cur, &values[i].Uint, &values[i + 1].Uint); - i++; - break; - case TGSI_IMM_FLOAT32: - ret = parse_float(&ctx->cur, &values[i].Float); - break; - case TGSI_IMM_UINT32: - ret = parse_uint(&ctx->cur, &values[i].Uint); - break; - case TGSI_IMM_INT32: - ret = parse_int(&ctx->cur, &values[i].Int); - break; - default: - assert(0); - ret = FALSE; - break; - } - - if (!ret) { - report_error(ctx, "Expected immediate constant"); - return FALSE; - } - } - eat_opt_white(&ctx->cur); - if (*ctx->cur != '}') { - report_error(ctx, "Expected `}'"); - return FALSE; - } - ctx->cur++; - - return TRUE; -} - -static boolean parse_declaration(struct translate_ctx *ctx) { - struct tgsi_full_declaration decl; - uint file; - struct parsed_dcl_bracket brackets[2]; - int num_brackets; - uint writemask; - const char *cur, *cur2; - uint advance; - boolean is_vs_input; - - if (!eat_white(&ctx->cur)) { - report_error(ctx, "Syntax error"); - return FALSE; - } - if (!parse_register_dcl(ctx, &file, brackets, &num_brackets)) - return FALSE; - if (!parse_opt_writemask(ctx, &writemask)) - return FALSE; - - decl = tgsi_default_full_declaration(); - decl.Declaration.File = file; - decl.Declaration.UsageMask = writemask; - - if (num_brackets == 1) { - decl.Range.First = brackets[0].first; - decl.Range.Last = brackets[0].last; - } else { - decl.Range.First = brackets[1].first; - decl.Range.Last = brackets[1].last; - - decl.Declaration.Dimension = 1; - decl.Dim.Index2D = brackets[0].first; - } - - is_vs_input = - (file == TGSI_FILE_INPUT && ctx->processor == TGSI_PROCESSOR_VERTEX); - - cur = ctx->cur; - eat_opt_white(&cur); - if (*cur == ',') { - cur2 = cur; - cur2++; - eat_opt_white(&cur2); - if (str_match_nocase_whole(&cur2, "ARRAY")) { - int arrayid; - if (*cur2 != '(') { - report_error(ctx, "Expected `('"); - return FALSE; - } - cur2++; - eat_opt_white(&cur2); - if (!parse_int(&cur2, &arrayid)) { - report_error(ctx, "Expected `,'"); - return FALSE; - } - eat_opt_white(&cur2); - if (*cur2 != ')') { - report_error(ctx, "Expected `)'"); - return FALSE; - } - cur2++; - decl.Declaration.Array = 1; - decl.Array.ArrayID = arrayid; - ctx->cur = cur = cur2; - } - } - - if (*cur == ',' && !is_vs_input) { - uint i, j; - - cur++; - eat_opt_white(&cur); - if (file == TGSI_FILE_IMAGE) { - for (i = 0; i < TGSI_TEXTURE_COUNT; i++) { - if (str_match_nocase_whole(&cur, tgsi_texture_names[i])) { - decl.Image.Resource = i; - break; - } - } - if (i == TGSI_TEXTURE_COUNT) { - report_error(ctx, "Expected texture target"); - return FALSE; - } - - cur2 = cur; - eat_opt_white(&cur2); - while (*cur2 == ',') { - cur2++; - eat_opt_white(&cur2); - if (str_match_nocase_whole(&cur2, "RAW")) { - decl.Image.Raw = 1; - - } else if (str_match_nocase_whole(&cur2, "WR")) { - decl.Image.Writable = 1; - - } else { - int format = str_match_format(&cur2); - if (format < 0) - break; - - decl.Image.Format = format; - } - cur = cur2; - eat_opt_white(&cur2); - } - - ctx->cur = cur; - - } else if (file == TGSI_FILE_SAMPLER_VIEW) { - for (i = 0; i < TGSI_TEXTURE_COUNT; i++) { - if (str_match_nocase_whole(&cur, tgsi_texture_names[i])) { - decl.SamplerView.Resource = i; - break; - } - } - if (i == TGSI_TEXTURE_COUNT) { - report_error(ctx, "Expected texture target"); - return FALSE; - } - eat_opt_white(&cur); - if (*cur != ',') { - report_error(ctx, "Expected `,'"); - return FALSE; - } - ++cur; - eat_opt_white(&cur); - for (j = 0; j < 4; ++j) { - for (i = 0; i < TGSI_RETURN_TYPE_COUNT; ++i) { - if (str_match_nocase_whole(&cur, tgsi_return_type_names[i])) { - switch (j) { - case 0: - decl.SamplerView.ReturnTypeX = i; - break; - case 1: - decl.SamplerView.ReturnTypeY = i; - break; - case 2: - decl.SamplerView.ReturnTypeZ = i; - break; - case 3: - decl.SamplerView.ReturnTypeW = i; - break; - default: - assert(0); - } - break; - } - } - if (i == TGSI_RETURN_TYPE_COUNT) { - if (j == 0 || j > 2) { - report_error(ctx, "Expected type name"); - return FALSE; - } - break; - } else { - cur2 = cur; - eat_opt_white(&cur2); - if (*cur2 == ',') { - cur2++; - eat_opt_white(&cur2); - cur = cur2; - continue; - } else - break; - } - } - if (j < 4) { - decl.SamplerView.ReturnTypeY = decl.SamplerView.ReturnTypeZ = - decl.SamplerView.ReturnTypeW = decl.SamplerView.ReturnTypeX; - } - ctx->cur = cur; - } else if (file == TGSI_FILE_BUFFER) { - if (str_match_nocase_whole(&cur, "ATOMIC")) { - decl.Declaration.Atomic = 1; - ctx->cur = cur; - } - } else if (file == TGSI_FILE_MEMORY) { - if (str_match_nocase_whole(&cur, "GLOBAL")) { - /* Note this is a no-op global is the default */ - decl.Declaration.MemType = TGSI_MEMORY_TYPE_GLOBAL; - ctx->cur = cur; - } else if (str_match_nocase_whole(&cur, "SHARED")) { - decl.Declaration.MemType = TGSI_MEMORY_TYPE_SHARED; - ctx->cur = cur; - } else if (str_match_nocase_whole(&cur, "PRIVATE")) { - decl.Declaration.MemType = TGSI_MEMORY_TYPE_PRIVATE; - ctx->cur = cur; - } else if (str_match_nocase_whole(&cur, "INPUT")) { - decl.Declaration.MemType = TGSI_MEMORY_TYPE_INPUT; - ctx->cur = cur; - } - } else { - if (str_match_nocase_whole(&cur, "LOCAL")) { - decl.Declaration.Local = 1; - ctx->cur = cur; - } - - cur = ctx->cur; - eat_opt_white(&cur); - if (*cur == ',') { - cur++; - eat_opt_white(&cur); - - for (i = 0; i < TGSI_SEMANTIC_COUNT; i++) { - if (str_match_nocase_whole(&cur, tgsi_semantic_names[i])) { - uint index; - - cur2 = cur; - eat_opt_white(&cur2); - if (*cur2 == '[') { - cur2++; - eat_opt_white(&cur2); - if (!parse_uint(&cur2, &index)) { - report_error(ctx, "Expected literal integer"); - return FALSE; - } - eat_opt_white(&cur2); - if (*cur2 != ']') { - report_error(ctx, "Expected `]'"); - return FALSE; - } - cur2++; - - decl.Semantic.Index = index; - - cur = cur2; - } - - decl.Declaration.Semantic = 1; - decl.Semantic.Name = i; - - ctx->cur = cur; - break; - } - } - } - } - } - - cur = ctx->cur; - eat_opt_white(&cur); - if (*cur == ',' && file == TGSI_FILE_OUTPUT && - ctx->processor == PIPE_SHADER_GEOMETRY) { - cur++; - eat_opt_white(&cur); - if (str_match_nocase_whole(&cur, "STREAM")) { - uint stream[4]; - - eat_opt_white(&cur); - if (*cur != '(') { - report_error(ctx, "Expected '('"); - return FALSE; - } - cur++; - - for (int i = 0; i < 4; ++i) { - eat_opt_white(&cur); - if (!parse_uint(&cur, &stream[i])) { - report_error(ctx, "Expected literal integer"); - return FALSE; - } - - eat_opt_white(&cur); - if (i < 3) { - if (*cur != ',') { - report_error(ctx, "Expected ','"); - return FALSE; - } - cur++; - } - } - - if (*cur != ')') { - report_error(ctx, "Expected ')'"); - return FALSE; - } - cur++; - - decl.Semantic.StreamX = stream[0]; - decl.Semantic.StreamY = stream[1]; - decl.Semantic.StreamZ = stream[2]; - decl.Semantic.StreamW = stream[3]; - - ctx->cur = cur; - } - } - - cur = ctx->cur; - eat_opt_white(&cur); - if (*cur == ',' && !is_vs_input) { - uint i; - - cur++; - eat_opt_white(&cur); - for (i = 0; i < TGSI_INTERPOLATE_COUNT; i++) { - if (str_match_nocase_whole(&cur, tgsi_interpolate_names[i])) { - decl.Declaration.Interpolate = 1; - decl.Interp.Interpolate = i; - - ctx->cur = cur; - break; - } - } - } - - cur = ctx->cur; - eat_opt_white(&cur); - if (*cur == ',' && !is_vs_input) { - uint i; - - cur++; - eat_opt_white(&cur); - for (i = 0; i < TGSI_INTERPOLATE_LOC_COUNT; i++) { - if (str_match_nocase_whole(&cur, tgsi_interpolate_locations[i])) { - decl.Interp.Location = i; - - ctx->cur = cur; - break; - } - } - } - - cur = ctx->cur; - eat_opt_white(&cur); - if (*cur == ',' && !is_vs_input) { - cur++; - eat_opt_white(&cur); - if (str_match_nocase_whole(&cur, tgsi_invariant_name)) { - decl.Declaration.Invariant = 1; - ctx->cur = cur; - } else { - report_error(ctx, - "Expected semantic, interpolate attribute, or invariant " - "\"%.10s...\" ", - cur); - return FALSE; - } - } - - advance = - tgsi_build_full_declaration(&decl, ctx->tokens_cur, ctx->header, - (uint)(ctx->tokens_end - ctx->tokens_cur)); - - if (advance == 0) - return FALSE; - ctx->tokens_cur += advance; - - return TRUE; -} - -static boolean parse_immediate(struct translate_ctx *ctx) { - struct tgsi_full_immediate imm; - uint advance; - uint type; - - if (*ctx->cur == '[') { - uint uindex; - - ++ctx->cur; - - eat_opt_white(&ctx->cur); - if (!parse_uint(&ctx->cur, &uindex)) { - report_error(ctx, "Expected literal unsigned integer"); - return FALSE; - } - - if (uindex != ctx->num_immediates) { - report_error(ctx, "Immediates must be sorted"); - return FALSE; - } - - eat_opt_white(&ctx->cur); - if (*ctx->cur != ']') { - report_error(ctx, "Expected `]'"); - return FALSE; - } - - ctx->cur++; - } - - if (!eat_white(&ctx->cur)) { - report_error(ctx, "Syntax error"); - return FALSE; - } - for (type = 0; type < Elements(tgsi_immediate_type_names); ++type) { - if (str_match_nocase_whole(&ctx->cur, tgsi_immediate_type_names[type])) - break; - } - if (type == Elements(tgsi_immediate_type_names)) { - report_error(ctx, "Expected immediate type"); - return FALSE; - } - - imm = tgsi_default_full_immediate(); - imm.Immediate.NrTokens += 4; - imm.Immediate.DataType = type; - parse_immediate_data(ctx, type, imm.u); - - advance = - tgsi_build_full_immediate(&imm, ctx->tokens_cur, ctx->header, - (uint)(ctx->tokens_end - ctx->tokens_cur)); - if (advance == 0) - return FALSE; - ctx->tokens_cur += advance; - - ctx->num_immediates++; - - return TRUE; -} - -static boolean parse_primitive(const char **pcur, uint *primitive) { - uint i; - - for (i = 0; i < PIPE_PRIM_MAX; i++) { - const char *cur = *pcur; - - if (str_match_nocase_whole(&cur, tgsi_primitive_names[i])) { - *primitive = i; - *pcur = cur; - return TRUE; - } - } - return FALSE; -} - -static boolean parse_fs_coord_origin(const char **pcur, uint *fs_coord_origin) { - uint i; - - for (i = 0; i < Elements(tgsi_fs_coord_origin_names); i++) { - const char *cur = *pcur; - - if (str_match_nocase_whole(&cur, tgsi_fs_coord_origin_names[i])) { - *fs_coord_origin = i; - *pcur = cur; - return TRUE; - } - } - return FALSE; -} - -static boolean parse_fs_coord_pixel_center(const char **pcur, - uint *fs_coord_pixel_center) { - uint i; - - for (i = 0; i < Elements(tgsi_fs_coord_pixel_center_names); i++) { - const char *cur = *pcur; - - if (str_match_nocase_whole(&cur, tgsi_fs_coord_pixel_center_names[i])) { - *fs_coord_pixel_center = i; - *pcur = cur; - return TRUE; - } - } - return FALSE; -} - -static boolean parse_property_next_shader(const char **pcur, - uint *next_shader) { - uint i; - - for (i = 0; i < ARRAY_SIZE(tgsi_processor_type_names); i++) { - const char *cur = *pcur; - - if (str_match_nocase_whole(&cur, tgsi_processor_type_names[i])) { - *next_shader = i; - *pcur = cur; - return TRUE; - } - } - return FALSE; -} - -static boolean parse_property(struct translate_ctx *ctx) { - struct tgsi_full_property prop; - uint property_name; - uint values[8]; - uint advance; - char id[64]; - - if (!eat_white(&ctx->cur)) { - report_error(ctx, "Syntax error"); - return FALSE; - } - if (!parse_identifier(&ctx->cur, id, sizeof(id))) { - report_error(ctx, "Syntax error"); - return FALSE; - } - for (property_name = 0; property_name < TGSI_PROPERTY_COUNT; - ++property_name) { - if (streq_nocase_uprcase(tgsi_property_names[property_name], id)) { - break; - } - } - if (property_name >= TGSI_PROPERTY_COUNT) { - eat_until_eol(&ctx->cur); - report_error(ctx, "\nError: Unknown property : '%s'\n", id); - return TRUE; - } - - eat_opt_white(&ctx->cur); - switch (property_name) { - case TGSI_PROPERTY_GS_INPUT_PRIM: - case TGSI_PROPERTY_GS_OUTPUT_PRIM: - if (!parse_primitive(&ctx->cur, &values[0])) { - report_error(ctx, "Unknown primitive name as property!"); - return FALSE; - } - if (property_name == TGSI_PROPERTY_GS_INPUT_PRIM && - ctx->processor == TGSI_PROCESSOR_GEOMETRY) { - ctx->implied_array_size = u_vertices_per_prim(values[0]); - } - break; - case TGSI_PROPERTY_FS_COORD_ORIGIN: - if (!parse_fs_coord_origin(&ctx->cur, &values[0])) { - report_error(ctx, "Unknown coord origin as property: must be UPPER_LEFT " - "or LOWER_LEFT!"); - return FALSE; - } - break; - case TGSI_PROPERTY_FS_COORD_PIXEL_CENTER: - if (!parse_fs_coord_pixel_center(&ctx->cur, &values[0])) { - report_error(ctx, "Unknown coord pixel center as property: must be " - "HALF_INTEGER or INTEGER!"); - return FALSE; - } - break; - case TGSI_PROPERTY_NEXT_SHADER: - if (!parse_property_next_shader(&ctx->cur, &values[0])) { - report_error(ctx, "Unknown next shader property value."); - return FALSE; - } - break; - case TGSI_PROPERTY_FS_COLOR0_WRITES_ALL_CBUFS: - default: - if (!parse_uint(&ctx->cur, &values[0])) { - report_error(ctx, "Expected unsigned integer as property!"); - return FALSE; - } - } - - prop = tgsi_default_full_property(); - prop.Property.PropertyName = property_name; - prop.Property.NrTokens += 1; - prop.u[0].Data = values[0]; - - advance = tgsi_build_full_property(&prop, ctx->tokens_cur, ctx->header, - (uint)(ctx->tokens_end - ctx->tokens_cur)); - if (advance == 0) - return FALSE; - ctx->tokens_cur += advance; - - return TRUE; -} - -static boolean translate(struct translate_ctx *ctx) { - eat_opt_white(&ctx->cur); - if (!parse_header(ctx)) - return FALSE; - - if (ctx->processor == TGSI_PROCESSOR_TESS_CTRL || - ctx->processor == TGSI_PROCESSOR_TESS_EVAL) - ctx->implied_array_size = 32; - - while (*ctx->cur != '\0') { - uint label_val = 0; - if (!eat_white(&ctx->cur)) { - report_error(ctx, "Syntax error"); - return FALSE; - } - - if (*ctx->cur == '\0') - break; - if (parse_label(ctx, &label_val)) { - if (!parse_instruction(ctx, TRUE)) - return FALSE; - } else if (str_match_nocase_whole(&ctx->cur, "DCL")) { - if (!parse_declaration(ctx)) - return FALSE; - } else if (str_match_nocase_whole(&ctx->cur, "IMM")) { - if (!parse_immediate(ctx)) - return FALSE; - } else if (str_match_nocase_whole(&ctx->cur, "PROPERTY")) { - if (!parse_property(ctx)) - return FALSE; - } else if (!parse_instruction(ctx, FALSE)) { - return FALSE; - } - } - - return TRUE; -} - -boolean tgsi_text_translate(const char *text, struct tgsi_token *tokens, - uint num_tokens) { - struct translate_ctx ctx = {0}; - - ctx.text = text; - ctx.cur = text; - ctx.tokens = tokens; - ctx.tokens_cur = tokens; - ctx.tokens_end = tokens + num_tokens; - - if (!translate(&ctx)) - return FALSE; - - return tgsi_sanity_check(tokens); -} diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_text.h b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_text.h deleted file mode 100644 index dadcd2613..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_text.h +++ /dev/null @@ -1,46 +0,0 @@ -/************************************************************************** - * - * Copyright 2008 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -#ifndef TGSI_TEXT_H -#define TGSI_TEXT_H - -#if defined __cplusplus -extern "C" { -#endif - -#include "pipe/p_compiler.h" - -struct tgsi_token; - -boolean tgsi_text_translate(const char *text, struct tgsi_token *tokens, - uint num_tokens); - -#if defined __cplusplus -} -#endif - -#endif /* TGSI_TEXT_H */ diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_transform.c b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_transform.c deleted file mode 100644 index 80d6d8179..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_transform.c +++ /dev/null @@ -1,202 +0,0 @@ -/************************************************************************** - * - * Copyright 2008 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -/** - * TGSI program transformation utility. - * - * Authors: Brian Paul - */ - -#include "util/u_debug.h" - -#include "tgsi_transform.h" - -static void emit_instruction(struct tgsi_transform_context *ctx, - const struct tgsi_full_instruction *inst) { - uint ti = ctx->ti; - - ti += tgsi_build_full_instruction(inst, ctx->tokens_out + ti, ctx->header, - ctx->max_tokens_out - ti); - ctx->ti = ti; -} - -static void emit_declaration(struct tgsi_transform_context *ctx, - const struct tgsi_full_declaration *decl) { - uint ti = ctx->ti; - - ti += tgsi_build_full_declaration(decl, ctx->tokens_out + ti, ctx->header, - ctx->max_tokens_out - ti); - ctx->ti = ti; -} - -static void emit_immediate(struct tgsi_transform_context *ctx, - const struct tgsi_full_immediate *imm) { - uint ti = ctx->ti; - - ti += tgsi_build_full_immediate(imm, ctx->tokens_out + ti, ctx->header, - ctx->max_tokens_out - ti); - ctx->ti = ti; -} - -static void emit_property(struct tgsi_transform_context *ctx, - const struct tgsi_full_property *prop) { - uint ti = ctx->ti; - - ti += tgsi_build_full_property(prop, ctx->tokens_out + ti, ctx->header, - ctx->max_tokens_out - ti); - ctx->ti = ti; -} - -/** - * Apply user-defined transformations to the input shader to produce - * the output shader. - * For example, a register search-and-replace operation could be applied - * by defining a transform_instruction() callback that examined and changed - * the instruction src/dest regs. - * - * \return number of tokens emitted - */ -int tgsi_transform_shader(const struct tgsi_token *tokens_in, - struct tgsi_token *tokens_out, uint max_tokens_out, - struct tgsi_transform_context *ctx) { - uint procType; - - /* input shader */ - struct tgsi_parse_context parse; - - /* output shader */ - struct tgsi_processor *processor; - - /** - ** callback context init - **/ - ctx->emit_instruction = emit_instruction; - ctx->emit_declaration = emit_declaration; - ctx->emit_immediate = emit_immediate; - ctx->emit_property = emit_property; - ctx->tokens_out = tokens_out; - ctx->max_tokens_out = max_tokens_out; - - /** - ** Setup to begin parsing input shader - **/ - if (tgsi_parse_init(&parse, tokens_in) != TGSI_PARSE_OK) { - debug_printf("tgsi_parse_init() failed in tgsi_transform_shader()!\n"); - return -1; - } - procType = parse.FullHeader.Processor.Processor; - assert(procType == TGSI_PROCESSOR_FRAGMENT || - procType == TGSI_PROCESSOR_VERTEX || - procType == TGSI_PROCESSOR_GEOMETRY); - - /** - ** Setup output shader - **/ - ctx->header = (struct tgsi_header *)tokens_out; - *ctx->header = tgsi_build_header(); - - processor = (struct tgsi_processor *)(tokens_out + 1); - *processor = tgsi_build_processor(procType, ctx->header); - - ctx->ti = 2; - - /** - ** Loop over incoming program tokens/instructions - */ - while (!tgsi_parse_end_of_tokens(&parse)) { - - tgsi_parse_token(&parse); - - switch (parse.FullToken.Token.Type) { - case TGSI_TOKEN_TYPE_INSTRUCTION: { - struct tgsi_full_instruction *fullinst = &parse.FullToken.FullInstruction; - - if (ctx->transform_instruction) - ctx->transform_instruction(ctx, fullinst); - else - ctx->emit_instruction(ctx, fullinst); - } break; - - case TGSI_TOKEN_TYPE_DECLARATION: { - struct tgsi_full_declaration *fulldecl = &parse.FullToken.FullDeclaration; - - if (ctx->transform_declaration) - ctx->transform_declaration(ctx, fulldecl); - else - ctx->emit_declaration(ctx, fulldecl); - } break; - - case TGSI_TOKEN_TYPE_IMMEDIATE: { - struct tgsi_full_immediate *fullimm = &parse.FullToken.FullImmediate; - - if (ctx->transform_immediate) - ctx->transform_immediate(ctx, fullimm); - else - ctx->emit_immediate(ctx, fullimm); - } break; - case TGSI_TOKEN_TYPE_PROPERTY: { - struct tgsi_full_property *fullprop = &parse.FullToken.FullProperty; - - if (ctx->transform_property) - ctx->transform_property(ctx, fullprop); - else - ctx->emit_property(ctx, fullprop); - } break; - - default: - assert(0); - } - } - - if (ctx->epilog) { - ctx->epilog(ctx); - } - - tgsi_parse_free(&parse); - - return ctx->ti; -} - -#include "tgsi_text.h" - -extern int tgsi_transform_foo(struct tgsi_token *tokens_out, - uint max_tokens_out); - -/* This function exists only so that tgsi_text_translate() doesn't get - * magic-ed out of the libtgsi.a archive by the build system. Don't - * remove unless you know this has been fixed - check on mingw/scons - * builds as well. - */ -int tgsi_transform_foo(struct tgsi_token *tokens_out, uint max_tokens_out) { - const char *text = "FRAG\n" - "DCL IN[0], COLOR, CONSTANT\n" - "DCL OUT[0], COLOR\n" - " 0: MOV OUT[0], IN[0]\n" - " 1: END"; - - return tgsi_text_translate(text, tokens_out, max_tokens_out); -} diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_transform.h b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_transform.h deleted file mode 100644 index 0d68c04bd..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_transform.h +++ /dev/null @@ -1,87 +0,0 @@ -/************************************************************************** - * - * Copyright 2008 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -#ifndef TGSI_TRANSFORM_H -#define TGSI_TRANSFORM_H - -#include "pipe/p_shader_tokens.h" -#include "tgsi/tgsi_build.h" -#include "tgsi/tgsi_parse.h" - -/** - * Subclass this to add caller-specific data - */ -struct tgsi_transform_context { - /**** PUBLIC ***/ - - /** - * User-defined callbacks invoked per instruction. - */ - void (*transform_instruction)(struct tgsi_transform_context *ctx, - struct tgsi_full_instruction *inst); - - void (*transform_declaration)(struct tgsi_transform_context *ctx, - struct tgsi_full_declaration *decl); - - void (*transform_immediate)(struct tgsi_transform_context *ctx, - struct tgsi_full_immediate *imm); - void (*transform_property)(struct tgsi_transform_context *ctx, - struct tgsi_full_property *prop); - - /** - * Called at end of input program to allow caller to append extra - * instructions. Return number of tokens emitted. - */ - void (*epilog)(struct tgsi_transform_context *ctx); - - /*** PRIVATE ***/ - - /** - * These are setup by tgsi_transform_shader() and cannot be overridden. - * Meant to be called from in the above user callback functions. - */ - void (*emit_instruction)(struct tgsi_transform_context *ctx, - const struct tgsi_full_instruction *inst); - void (*emit_declaration)(struct tgsi_transform_context *ctx, - const struct tgsi_full_declaration *decl); - void (*emit_immediate)(struct tgsi_transform_context *ctx, - const struct tgsi_full_immediate *imm); - void (*emit_property)(struct tgsi_transform_context *ctx, - const struct tgsi_full_property *prop); - - struct tgsi_header *header; - uint max_tokens_out; - struct tgsi_token *tokens_out; - uint ti; -}; - -extern int tgsi_transform_shader(const struct tgsi_token *tokens_in, - struct tgsi_token *tokens_out, - uint max_tokens_out, - struct tgsi_transform_context *ctx); - -#endif /* TGSI_TRANSFORM_H */ diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_ureg.c b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_ureg.c deleted file mode 100644 index 4451d659d..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_ureg.c +++ /dev/null @@ -1,1458 +0,0 @@ -/************************************************************************** - * - * Copyright 2009-2010 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE, INC AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -#include "tgsi/tgsi_ureg.h" -#include "pipe/p_context.h" -#include "pipe/p_state.h" -#include "tgsi/tgsi_build.h" -#include "tgsi/tgsi_dump.h" -#include "tgsi/tgsi_info.h" -#include "tgsi/tgsi_sanity.h" -#include "util/u_bitmask.h" -#include "util/u_debug.h" -#include "util/u_math.h" -#include "util/u_memory.h" - -union tgsi_any_token { - struct tgsi_header header; - struct tgsi_processor processor; - struct tgsi_token token; - struct tgsi_property prop; - struct tgsi_property_data prop_data; - struct tgsi_declaration decl; - struct tgsi_declaration_range decl_range; - struct tgsi_declaration_dimension decl_dim; - struct tgsi_declaration_interp decl_interp; - struct tgsi_declaration_semantic decl_semantic; - struct tgsi_declaration_sampler_view decl_sampler_view; - struct tgsi_declaration_array array; - struct tgsi_immediate imm; - union tgsi_immediate_data imm_data; - struct tgsi_instruction insn; - struct tgsi_instruction_label insn_label; - struct tgsi_instruction_texture insn_texture; - struct tgsi_texture_offset insn_texture_offset; - struct tgsi_src_register src; - struct tgsi_ind_register ind; - struct tgsi_dimension dim; - struct tgsi_dst_register dst; - unsigned value; -}; - -struct ureg_tokens { - union tgsi_any_token *tokens; - unsigned size; - unsigned order; - unsigned count; -}; - -#define UREG_MAX_INPUT PIPE_MAX_ATTRIBS -#define UREG_MAX_SYSTEM_VALUE PIPE_MAX_ATTRIBS -#define UREG_MAX_OUTPUT PIPE_MAX_SHADER_OUTPUTS -#define UREG_MAX_CONSTANT_RANGE 32 -#define UREG_MAX_HW_ATOMIC_RANGE 32 -#define UREG_MAX_IMMEDIATE 4096 -#define UREG_MAX_ADDR 3 -#define UREG_MAX_PRED 1 -#define UREG_MAX_ARRAY_TEMPS 256 - -struct const_decl { - struct { - unsigned first; - unsigned last; - } constant_range[UREG_MAX_CONSTANT_RANGE]; - unsigned nr_constant_ranges; -}; - -struct hw_atomic_decl { - struct { - unsigned first; - unsigned last; - unsigned array_id; - } hw_atomic_range[UREG_MAX_HW_ATOMIC_RANGE]; - unsigned nr_hw_atomic_ranges; -}; - -#define DOMAIN_DECL 0 -#define DOMAIN_INSN 1 - -struct ureg_program { - unsigned processor; - struct pipe_context *pipe; - - struct { - unsigned semantic_name; - unsigned semantic_index; - unsigned interp; - unsigned char cylindrical_wrap; - unsigned interp_location; - } fs_input[UREG_MAX_INPUT]; - unsigned nr_fs_inputs; - - unsigned vs_inputs[UREG_MAX_INPUT / 32]; - - struct { - unsigned index; - unsigned semantic_name; - unsigned semantic_index; - } gs_input[UREG_MAX_INPUT]; - unsigned nr_gs_inputs; - - struct { - unsigned index; - unsigned semantic_name; - unsigned semantic_index; - } system_value[UREG_MAX_SYSTEM_VALUE]; - unsigned nr_system_values; - - struct { - unsigned semantic_name; - unsigned semantic_index; - unsigned usage_mask; /* = TGSI_WRITEMASK_* */ - } output[UREG_MAX_OUTPUT]; - unsigned nr_outputs; - - struct { - union { - float f[4]; - unsigned u[4]; - int i[4]; - } value; - unsigned nr; - unsigned type; - } immediate[UREG_MAX_IMMEDIATE]; - unsigned nr_immediates; - - struct ureg_src sampler[PIPE_MAX_SAMPLERS]; - unsigned nr_samplers; - - struct { - unsigned index; - unsigned target; - unsigned return_type_x; - unsigned return_type_y; - unsigned return_type_z; - unsigned return_type_w; - } sampler_view[PIPE_MAX_SHADER_SAMPLER_VIEWS]; - unsigned nr_sampler_views; - - struct util_bitmask *free_temps; - struct util_bitmask *local_temps; - struct util_bitmask *decl_temps; - unsigned nr_temps; - - unsigned array_temps[UREG_MAX_ARRAY_TEMPS]; - unsigned nr_array_temps; - - struct const_decl const_decls; - struct const_decl const_decls2D[PIPE_MAX_CONSTANT_BUFFERS]; - - struct hw_atomic_decl hw_atomic_decls[PIPE_MAX_HW_ATOMIC_BUFFERS]; - - unsigned properties[TGSI_PROPERTY_COUNT]; - - unsigned nr_addrs; - unsigned nr_preds; - unsigned nr_instructions; - - struct ureg_tokens domain[2]; -}; - -static union tgsi_any_token error_tokens[32]; - -static void tokens_error(struct ureg_tokens *tokens) { - if (tokens->tokens && tokens->tokens != error_tokens) - FREE(tokens->tokens); - - tokens->tokens = error_tokens; - tokens->size = ARRAY_SIZE(error_tokens); - tokens->count = 0; -} - -static void tokens_expand(struct ureg_tokens *tokens, unsigned count) { - unsigned old_size = tokens->size * sizeof(unsigned); - - if (tokens->tokens == error_tokens) { - return; - } - - while (tokens->count + count > tokens->size) { - tokens->size = (1 << ++tokens->order); - } - - tokens->tokens = - REALLOC(tokens->tokens, old_size, tokens->size * sizeof(unsigned)); - if (tokens->tokens == NULL) { - tokens_error(tokens); - } -} - -static void set_bad(struct ureg_program *ureg) { - tokens_error(&ureg->domain[0]); -} - -static union tgsi_any_token *get_tokens(struct ureg_program *ureg, - unsigned domain, unsigned count) { - struct ureg_tokens *tokens = &ureg->domain[domain]; - union tgsi_any_token *result; - - if (tokens->count + count > tokens->size) - tokens_expand(tokens, count); - - result = &tokens->tokens[tokens->count]; - tokens->count += count; - return result; -} - -static union tgsi_any_token *retrieve_token(struct ureg_program *ureg, - unsigned domain, unsigned nr) { - if (ureg->domain[domain].tokens == error_tokens) - return &error_tokens[0]; - - return &ureg->domain[domain].tokens[nr]; -} - -static inline struct ureg_dst ureg_dst_register(unsigned file, unsigned index) { - struct ureg_dst dst; - - dst.File = file; - dst.WriteMask = TGSI_WRITEMASK_XYZW; - dst.Indirect = 0; - dst.IndirectFile = TGSI_FILE_NULL; - dst.IndirectIndex = 0; - dst.IndirectSwizzle = 0; - dst.Saturate = 0; - dst.PredNegate = 0; - dst.PredSwizzleX = TGSI_SWIZZLE_X; - dst.PredSwizzleY = TGSI_SWIZZLE_Y; - dst.PredSwizzleZ = TGSI_SWIZZLE_Z; - dst.PredSwizzleW = TGSI_SWIZZLE_W; - dst.Index = index; - dst.ArrayID = 0; - - return dst; -} - -void ureg_property(struct ureg_program *ureg, unsigned name, unsigned value) { - assert(name < ARRAY_SIZE(ureg->properties)); - ureg->properties[name] = value; -} - -struct ureg_src ureg_DECL_fs_input_cyl_centroid( - struct ureg_program *ureg, unsigned semantic_name, unsigned semantic_index, - unsigned interp_mode, unsigned cylindrical_wrap, unsigned interp_location) { - unsigned i; - - for (i = 0; i < ureg->nr_fs_inputs; i++) { - if (ureg->fs_input[i].semantic_name == semantic_name && - ureg->fs_input[i].semantic_index == semantic_index) { - goto out; - } - } - - if (ureg->nr_fs_inputs < UREG_MAX_INPUT) { - ureg->fs_input[i].semantic_name = semantic_name; - ureg->fs_input[i].semantic_index = semantic_index; - ureg->fs_input[i].interp = interp_mode; - ureg->fs_input[i].cylindrical_wrap = cylindrical_wrap; - ureg->fs_input[i].interp_location = interp_location; - ureg->nr_fs_inputs++; - } else { - set_bad(ureg); - } - -out: - return ureg_src_register(TGSI_FILE_INPUT, i); -} - -struct ureg_src ureg_DECL_vs_input(struct ureg_program *ureg, unsigned index) { - assert(ureg->processor == TGSI_PROCESSOR_VERTEX); - - ureg->vs_inputs[index / 32] |= 1 << (index % 32); - return ureg_src_register(TGSI_FILE_INPUT, index); -} - -struct ureg_src ureg_DECL_gs_input(struct ureg_program *ureg, unsigned index, - unsigned semantic_name, - unsigned semantic_index) { - if (ureg->nr_gs_inputs < UREG_MAX_INPUT) { - ureg->gs_input[ureg->nr_gs_inputs].index = index; - ureg->gs_input[ureg->nr_gs_inputs].semantic_name = semantic_name; - ureg->gs_input[ureg->nr_gs_inputs].semantic_index = semantic_index; - ureg->nr_gs_inputs++; - } else { - set_bad(ureg); - } - - /* XXX: Add suport for true 2D input registers. */ - return ureg_src_register(TGSI_FILE_INPUT, index); -} - -struct ureg_src ureg_DECL_system_value(struct ureg_program *ureg, - unsigned index, unsigned semantic_name, - unsigned semantic_index) { - if (ureg->nr_system_values < UREG_MAX_SYSTEM_VALUE) { - ureg->system_value[ureg->nr_system_values].index = index; - ureg->system_value[ureg->nr_system_values].semantic_name = semantic_name; - ureg->system_value[ureg->nr_system_values].semantic_index = semantic_index; - ureg->nr_system_values++; - } else { - set_bad(ureg); - } - - return ureg_src_register(TGSI_FILE_SYSTEM_VALUE, index); -} - -struct ureg_dst ureg_DECL_output_masked(struct ureg_program *ureg, - unsigned name, unsigned index, - unsigned usage_mask) { - unsigned i; - - assert(usage_mask != 0); - - for (i = 0; i < ureg->nr_outputs; i++) { - if (ureg->output[i].semantic_name == name && - ureg->output[i].semantic_index == index) { - ureg->output[i].usage_mask |= usage_mask; - goto out; - } - } - - if (ureg->nr_outputs < UREG_MAX_OUTPUT) { - ureg->output[i].semantic_name = name; - ureg->output[i].semantic_index = index; - ureg->output[i].usage_mask = usage_mask; - ureg->nr_outputs++; - } else { - set_bad(ureg); - } - -out: - return ureg_dst_register(TGSI_FILE_OUTPUT, i); -} - -struct ureg_dst ureg_DECL_output(struct ureg_program *ureg, unsigned name, - unsigned index) { - return ureg_DECL_output_masked(ureg, name, index, TGSI_WRITEMASK_XYZW); -} - -/* Returns a new constant register. Keep track of which have been - * referred to so that we can emit decls later. - * - * Constant operands declared with this function must be addressed - * with a two-dimensional index. - * - * There is nothing in this code to bind this constant to any tracked - * value or manage any constant_buffer contents -- that's the - * resposibility of the calling code. - */ -void ureg_DECL_constant2D(struct ureg_program *ureg, unsigned first, - unsigned last, unsigned index2D) { - struct const_decl *decl = &ureg->const_decls2D[index2D]; - - assert(index2D < PIPE_MAX_CONSTANT_BUFFERS); - - if (decl->nr_constant_ranges < UREG_MAX_CONSTANT_RANGE) { - uint i = decl->nr_constant_ranges++; - - decl->constant_range[i].first = first; - decl->constant_range[i].last = last; - } -} - -/* A one-dimensional, depricated version of ureg_DECL_constant2D(). - * - * Constant operands declared with this function must be addressed - * with a one-dimensional index. - */ -struct ureg_src ureg_DECL_constant(struct ureg_program *ureg, unsigned index) { - struct const_decl *decl = &ureg->const_decls; - unsigned minconst = index, maxconst = index; - unsigned i; - - /* Inside existing range? - */ - for (i = 0; i < decl->nr_constant_ranges; i++) { - if (decl->constant_range[i].first <= index && - decl->constant_range[i].last >= index) { - goto out; - } - } - - /* Extend existing range? - */ - for (i = 0; i < decl->nr_constant_ranges; i++) { - if (decl->constant_range[i].last == index - 1) { - decl->constant_range[i].last = index; - goto out; - } - - if (decl->constant_range[i].first == index + 1) { - decl->constant_range[i].first = index; - goto out; - } - - minconst = MIN2(minconst, decl->constant_range[i].first); - maxconst = MAX2(maxconst, decl->constant_range[i].last); - } - - /* Create new range? - */ - if (decl->nr_constant_ranges < UREG_MAX_CONSTANT_RANGE) { - i = decl->nr_constant_ranges++; - decl->constant_range[i].first = index; - decl->constant_range[i].last = index; - goto out; - } - - /* Collapse all ranges down to one: - */ - i = 0; - decl->constant_range[0].first = minconst; - decl->constant_range[0].last = maxconst; - decl->nr_constant_ranges = 1; - -out: - assert(i < decl->nr_constant_ranges); - assert(decl->constant_range[i].first <= index); - assert(decl->constant_range[i].last >= index); - return ureg_src_register(TGSI_FILE_CONSTANT, index); -} - -/* Returns a new hw atomic register. Keep track of which have been - * referred to so that we can emit decls later. - */ -void ureg_DECL_hw_atomic(struct ureg_program *ureg, unsigned first, - unsigned last, unsigned buffer_id, unsigned array_id) { - struct hw_atomic_decl *decl = &ureg->hw_atomic_decls[buffer_id]; - - if (decl->nr_hw_atomic_ranges < UREG_MAX_HW_ATOMIC_RANGE) { - uint i = decl->nr_hw_atomic_ranges++; - - decl->hw_atomic_range[i].first = first; - decl->hw_atomic_range[i].last = last; - decl->hw_atomic_range[i].array_id = array_id; - } else { - set_bad(ureg); - } -} - -static struct ureg_dst alloc_temporary(struct ureg_program *ureg, - boolean local) { - unsigned i; - - /* Look for a released temporary. - */ - for (i = util_bitmask_get_first_index(ureg->free_temps); - i != UTIL_BITMASK_INVALID_INDEX; - i = util_bitmask_get_next_index(ureg->free_temps, i + 1)) { - if (util_bitmask_get(ureg->local_temps, i) == local) - break; - } - - /* Or allocate a new one. - */ - if (i == UTIL_BITMASK_INVALID_INDEX) { - i = ureg->nr_temps++; - - if (local) - util_bitmask_set(ureg->local_temps, i); - - /* Start a new declaration when the local flag changes */ - if (!i || util_bitmask_get(ureg->local_temps, i - 1) != local) - util_bitmask_set(ureg->decl_temps, i); - } - - util_bitmask_clear(ureg->free_temps, i); - - return ureg_dst_register(TGSI_FILE_TEMPORARY, i); -} - -struct ureg_dst ureg_DECL_temporary(struct ureg_program *ureg) { - return alloc_temporary(ureg, FALSE); -} - -struct ureg_dst ureg_DECL_local_temporary(struct ureg_program *ureg) { - return alloc_temporary(ureg, TRUE); -} - -struct ureg_dst ureg_DECL_array_temporary(struct ureg_program *ureg, - unsigned size, boolean local) { - unsigned i = ureg->nr_temps; - struct ureg_dst dst = ureg_dst_register(TGSI_FILE_TEMPORARY, i); - - if (local) - util_bitmask_set(ureg->local_temps, i); - - /* Always start a new declaration at the start */ - util_bitmask_set(ureg->decl_temps, i); - - ureg->nr_temps += size; - - /* and also at the end of the array */ - util_bitmask_set(ureg->decl_temps, ureg->nr_temps); - - if (ureg->nr_array_temps < UREG_MAX_ARRAY_TEMPS) { - ureg->array_temps[ureg->nr_array_temps++] = i; - dst.ArrayID = ureg->nr_array_temps; - } - - return dst; -} - -void ureg_release_temporary(struct ureg_program *ureg, struct ureg_dst tmp) { - if (tmp.File == TGSI_FILE_TEMPORARY) - util_bitmask_set(ureg->free_temps, tmp.Index); -} - -/* Allocate a new address register. - */ -struct ureg_dst ureg_DECL_address(struct ureg_program *ureg) { - if (ureg->nr_addrs < UREG_MAX_ADDR) - return ureg_dst_register(TGSI_FILE_ADDRESS, ureg->nr_addrs++); - - assert(0); - return ureg_dst_register(TGSI_FILE_ADDRESS, 0); -} - -/* Allocate a new predicate register. - */ -struct ureg_dst ureg_DECL_predicate(struct ureg_program *ureg) { - if (ureg->nr_preds < UREG_MAX_PRED) { - return ureg_dst_register(TGSI_FILE_PREDICATE, ureg->nr_preds++); - } - - assert(0); - return ureg_dst_register(TGSI_FILE_PREDICATE, 0); -} - -/* Allocate a new sampler. - */ -struct ureg_src ureg_DECL_sampler(struct ureg_program *ureg, int nr) { - unsigned i; - - for (i = 0; i < ureg->nr_samplers; i++) - if (ureg->sampler[i].Index == nr) - return ureg->sampler[i]; - - if (i < PIPE_MAX_SAMPLERS) { - ureg->sampler[i] = ureg_src_register(TGSI_FILE_SAMPLER, nr); - ureg->nr_samplers++; - return ureg->sampler[i]; - } - - assert(0); - return ureg->sampler[0]; -} - -/* - * Allocate a new shader sampler view. - */ -struct ureg_src ureg_DECL_sampler_view(struct ureg_program *ureg, - unsigned index, unsigned target, - unsigned return_type_x, - unsigned return_type_y, - unsigned return_type_z, - unsigned return_type_w) { - struct ureg_src reg = ureg_src_register(TGSI_FILE_SAMPLER_VIEW, index); - uint i; - - for (i = 0; i < ureg->nr_sampler_views; i++) { - if (ureg->sampler_view[i].index == index) { - return reg; - } - } - - if (i < PIPE_MAX_SHADER_SAMPLER_VIEWS) { - ureg->sampler_view[i].index = index; - ureg->sampler_view[i].target = target; - ureg->sampler_view[i].return_type_x = return_type_x; - ureg->sampler_view[i].return_type_y = return_type_y; - ureg->sampler_view[i].return_type_z = return_type_z; - ureg->sampler_view[i].return_type_w = return_type_w; - ureg->nr_sampler_views++; - return reg; - } - - assert(0); - return reg; -} - -static int match_or_expand_immediate(const unsigned *v, unsigned nr, - unsigned *v2, unsigned *pnr2, - unsigned *swizzle) { - unsigned nr2 = *pnr2; - unsigned i, j; - - *swizzle = 0; - - for (i = 0; i < nr; i++) { - boolean found = FALSE; - - for (j = 0; j < nr2 && !found; j++) { - if (v[i] == v2[j]) { - *swizzle |= j << (i * 2); - found = TRUE; - } - } - - if (!found) { - if (nr2 >= 4) { - return FALSE; - } - - v2[nr2] = v[i]; - *swizzle |= nr2 << (i * 2); - nr2++; - } - } - - /* Actually expand immediate only when fully succeeded. - */ - *pnr2 = nr2; - return TRUE; -} - -static struct ureg_src decl_immediate(struct ureg_program *ureg, - const unsigned *v, unsigned nr, - unsigned type) { - unsigned i, j; - unsigned swizzle = 0; - - /* Could do a first pass where we examine all existing immediates - * without expanding. - */ - - for (i = 0; i < ureg->nr_immediates; i++) { - if (ureg->immediate[i].type != type) { - continue; - } - if (match_or_expand_immediate(v, nr, ureg->immediate[i].value.u, - &ureg->immediate[i].nr, &swizzle)) { - goto out; - } - } - - if (ureg->nr_immediates < UREG_MAX_IMMEDIATE) { - i = ureg->nr_immediates++; - ureg->immediate[i].type = type; - if (match_or_expand_immediate(v, nr, ureg->immediate[i].value.u, - &ureg->immediate[i].nr, &swizzle)) { - goto out; - } - } - - set_bad(ureg); - -out: - /* Make sure that all referenced elements are from this immediate. - * Has the effect of making size-one immediates into scalars. - */ - for (j = nr; j < 4; j++) { - swizzle |= (swizzle & 0x3) << (j * 2); - } - - return ureg_swizzle(ureg_src_register(TGSI_FILE_IMMEDIATE, i), - (swizzle >> 0) & 0x3, (swizzle >> 2) & 0x3, - (swizzle >> 4) & 0x3, (swizzle >> 6) & 0x3); -} - -struct ureg_src ureg_DECL_immediate(struct ureg_program *ureg, const float *v, - unsigned nr) { - union { - float f[4]; - unsigned u[4]; - } fu; - unsigned int i; - - for (i = 0; i < nr; i++) { - fu.f[i] = v[i]; - } - - return decl_immediate(ureg, fu.u, nr, TGSI_IMM_FLOAT32); -} - -struct ureg_src ureg_DECL_immediate_uint(struct ureg_program *ureg, - const unsigned *v, unsigned nr) { - return decl_immediate(ureg, v, nr, TGSI_IMM_UINT32); -} - -struct ureg_src ureg_DECL_immediate_block_uint(struct ureg_program *ureg, - const unsigned *v, unsigned nr) { - uint index; - uint i; - - if (ureg->nr_immediates + (nr + 3) / 4 > UREG_MAX_IMMEDIATE) { - set_bad(ureg); - return ureg_src_register(TGSI_FILE_IMMEDIATE, 0); - } - - index = ureg->nr_immediates; - ureg->nr_immediates += (nr + 3) / 4; - - for (i = index; i < ureg->nr_immediates; i++) { - ureg->immediate[i].type = TGSI_IMM_UINT32; - ureg->immediate[i].nr = nr > 4 ? 4 : nr; - memcpy(ureg->immediate[i].value.u, &v[(i - index) * 4], - ureg->immediate[i].nr * sizeof(uint)); - nr -= 4; - } - - return ureg_src_register(TGSI_FILE_IMMEDIATE, index); -} - -struct ureg_src ureg_DECL_immediate_int(struct ureg_program *ureg, const int *v, - unsigned nr) { - return decl_immediate(ureg, (const unsigned *)v, nr, TGSI_IMM_INT32); -} - -void ureg_emit_src(struct ureg_program *ureg, struct ureg_src src) { - unsigned size = 1 + (src.Indirect ? 1 : 0) + - (src.Dimension ? (src.DimIndirect ? 2 : 1) : 0); - - union tgsi_any_token *out = get_tokens(ureg, DOMAIN_INSN, size); - unsigned n = 0; - - assert(src.File != TGSI_FILE_NULL); - assert(src.File < TGSI_FILE_COUNT); - - out[n].value = 0; - out[n].src.File = src.File; - out[n].src.SwizzleX = src.SwizzleX; - out[n].src.SwizzleY = src.SwizzleY; - out[n].src.SwizzleZ = src.SwizzleZ; - out[n].src.SwizzleW = src.SwizzleW; - out[n].src.Index = src.Index; - out[n].src.Negate = src.Negate; - out[0].src.Absolute = src.Absolute; - n++; - - if (src.Indirect) { - out[0].src.Indirect = 1; - out[n].value = 0; - out[n].ind.File = src.IndirectFile; - out[n].ind.Swizzle = src.IndirectSwizzle; - out[n].ind.Index = src.IndirectIndex; - out[n].ind.ArrayID = src.ArrayID; - n++; - } - - if (src.Dimension) { - out[0].src.Dimension = 1; - out[n].dim.Dimension = 0; - out[n].dim.Padding = 0; - if (src.DimIndirect) { - out[n].dim.Indirect = 1; - out[n].dim.Index = src.DimensionIndex; - n++; - out[n].value = 0; - out[n].ind.File = src.DimIndFile; - out[n].ind.Swizzle = src.DimIndSwizzle; - out[n].ind.Index = src.DimIndIndex; - out[n].ind.ArrayID = src.ArrayID; - } else { - out[n].dim.Indirect = 0; - out[n].dim.Index = src.DimensionIndex; - } - n++; - } - - assert(n == size); -} - -void ureg_emit_dst(struct ureg_program *ureg, struct ureg_dst dst) { - unsigned size = (1 + (dst.Indirect ? 1 : 0)); - - union tgsi_any_token *out = get_tokens(ureg, DOMAIN_INSN, size); - unsigned n = 0; - - assert(dst.File != TGSI_FILE_NULL); - assert(dst.File != TGSI_FILE_CONSTANT); - assert(dst.File != TGSI_FILE_INPUT); - assert(dst.File != TGSI_FILE_SAMPLER); - assert(dst.File != TGSI_FILE_SAMPLER_VIEW); - assert(dst.File != TGSI_FILE_IMMEDIATE); - assert(dst.File < TGSI_FILE_COUNT); - - out[n].value = 0; - out[n].dst.File = dst.File; - out[n].dst.WriteMask = dst.WriteMask; - out[n].dst.Indirect = dst.Indirect; - out[n].dst.Index = dst.Index; - n++; - - if (dst.Indirect) { - out[n].value = 0; - out[n].ind.File = dst.IndirectFile; - out[n].ind.Swizzle = dst.IndirectSwizzle; - out[n].ind.Index = dst.IndirectIndex; - out[n].ind.ArrayID = dst.ArrayID; - n++; - } - - assert(n == size); -} - -static void validate(unsigned opcode, unsigned nr_dst, unsigned nr_src) { -#ifdef DEBUG - const struct tgsi_opcode_info *info = tgsi_get_opcode_info(opcode); - assert(info); - if (info) { - assert(nr_dst == info->num_dst); - assert(nr_src == info->num_src); - } -#else - (void)opcode; - (void)nr_dst; - (void)nr_src; -#endif -} - -struct ureg_emit_insn_result ureg_emit_insn(struct ureg_program *ureg, - unsigned opcode, boolean saturate, - unsigned precise, unsigned num_dst, - unsigned num_src) { - union tgsi_any_token *out; - uint count = 1; - struct ureg_emit_insn_result result; - - validate(opcode, num_dst, num_src); - - out = get_tokens(ureg, DOMAIN_INSN, count); - out[0].insn = tgsi_default_instruction(); - out[0].insn.Opcode = opcode; - out[0].insn.Saturate = saturate; - out[0].insn.Precise = precise; - out[0].insn.NumDstRegs = num_dst; - out[0].insn.NumSrcRegs = num_src; - - result.insn_token = ureg->domain[DOMAIN_INSN].count - count; - result.extended_token = result.insn_token; - - ureg->nr_instructions++; - - return result; -} - -void ureg_emit_label(struct ureg_program *ureg, unsigned extended_token, - unsigned *label_token) { - union tgsi_any_token *out, *insn; - - if (!label_token) - return; - - out = get_tokens(ureg, DOMAIN_INSN, 1); - out[0].value = 0; - - insn = retrieve_token(ureg, DOMAIN_INSN, extended_token); - insn->insn.Label = 1; - - *label_token = ureg->domain[DOMAIN_INSN].count - 1; -} - -/* Will return a number which can be used in a label to point to the - * next instruction to be emitted. - */ -unsigned ureg_get_instruction_number(struct ureg_program *ureg) { - return ureg->nr_instructions; -} - -/* Patch a given label (expressed as a token number) to point to a - * given instruction (expressed as an instruction number). - */ -void ureg_fixup_label(struct ureg_program *ureg, unsigned label_token, - unsigned instruction_number) { - union tgsi_any_token *out = retrieve_token(ureg, DOMAIN_INSN, label_token); - - out->insn_label.Label = instruction_number; -} - -void ureg_emit_texture(struct ureg_program *ureg, unsigned extended_token, - unsigned target, unsigned num_offsets) { - union tgsi_any_token *out, *insn; - - out = get_tokens(ureg, DOMAIN_INSN, 1); - insn = retrieve_token(ureg, DOMAIN_INSN, extended_token); - - insn->insn.Texture = 1; - - out[0].value = 0; - out[0].insn_texture.Texture = target; - out[0].insn_texture.NumOffsets = num_offsets; -} - -void ureg_emit_texture_offset(struct ureg_program *ureg, - const struct tgsi_texture_offset *offset) { - union tgsi_any_token *out; - - out = get_tokens(ureg, DOMAIN_INSN, 1); - - out[0].value = 0; - out[0].insn_texture_offset = *offset; -} - -void ureg_fixup_insn_size(struct ureg_program *ureg, unsigned insn) { - union tgsi_any_token *out = retrieve_token(ureg, DOMAIN_INSN, insn); - - assert(out->insn.Type == TGSI_TOKEN_TYPE_INSTRUCTION); - out->insn.NrTokens = ureg->domain[DOMAIN_INSN].count - insn - 1; -} - -void ureg_insn(struct ureg_program *ureg, unsigned opcode, - const struct ureg_dst *dst, unsigned nr_dst, - const struct ureg_src *src, unsigned nr_src, unsigned precise) { - struct ureg_emit_insn_result insn; - unsigned i; - boolean saturate; - - if (nr_dst && ureg_dst_is_empty(dst[0])) { - return; - } - - saturate = nr_dst ? dst[0].Saturate : FALSE; - - insn = ureg_emit_insn(ureg, opcode, saturate, precise, nr_dst, nr_src); - - for (i = 0; i < nr_dst; i++) - ureg_emit_dst(ureg, dst[i]); - - for (i = 0; i < nr_src; i++) - ureg_emit_src(ureg, src[i]); - - ureg_fixup_insn_size(ureg, insn.insn_token); -} - -void ureg_tex_insn(struct ureg_program *ureg, unsigned opcode, - const struct ureg_dst *dst, unsigned nr_dst, unsigned target, - const struct tgsi_texture_offset *texoffsets, - unsigned nr_offset, const struct ureg_src *src, - unsigned nr_src) { - struct ureg_emit_insn_result insn; - unsigned i; - boolean saturate; - - if (nr_dst && ureg_dst_is_empty(dst[0])) { - return; - } - - saturate = nr_dst ? dst[0].Saturate : FALSE; - - insn = ureg_emit_insn(ureg, opcode, saturate, 0, nr_dst, nr_src); - - ureg_emit_texture(ureg, insn.extended_token, target, nr_offset); - - for (i = 0; i < nr_offset; i++) - ureg_emit_texture_offset(ureg, &texoffsets[i]); - - for (i = 0; i < nr_dst; i++) - ureg_emit_dst(ureg, dst[i]); - - for (i = 0; i < nr_src; i++) - ureg_emit_src(ureg, src[i]); - - ureg_fixup_insn_size(ureg, insn.insn_token); -} - -void ureg_label_insn(struct ureg_program *ureg, unsigned opcode, - const struct ureg_src *src, unsigned nr_src, - unsigned *label_token) { - struct ureg_emit_insn_result insn; - unsigned i; - - insn = ureg_emit_insn(ureg, opcode, FALSE, 0, 0, nr_src); - - ureg_emit_label(ureg, insn.extended_token, label_token); - - for (i = 0; i < nr_src; i++) - ureg_emit_src(ureg, src[i]); - - ureg_fixup_insn_size(ureg, insn.insn_token); -} - -static void emit_decl_semantic(struct ureg_program *ureg, unsigned file, - unsigned index, unsigned semantic_name, - unsigned semantic_index, unsigned usage_mask) { - union tgsi_any_token *out = get_tokens(ureg, DOMAIN_DECL, 3); - - out[0].value = 0; - out[0].decl.Type = TGSI_TOKEN_TYPE_DECLARATION; - out[0].decl.NrTokens = 3; - out[0].decl.File = file; - out[0].decl.UsageMask = usage_mask; - out[0].decl.Semantic = 1; - - out[1].value = 0; - out[1].decl_range.First = index; - out[1].decl_range.Last = index; - - out[2].value = 0; - out[2].decl_semantic.Name = semantic_name; - out[2].decl_semantic.Index = semantic_index; -} - -static void emit_decl_fs(struct ureg_program *ureg, unsigned file, - unsigned index, unsigned semantic_name, - unsigned semantic_index, unsigned interpolate, - unsigned cylindrical_wrap, - unsigned interpolate_location) { - union tgsi_any_token *out = get_tokens(ureg, DOMAIN_DECL, 4); - - out[0].value = 0; - out[0].decl.Type = TGSI_TOKEN_TYPE_DECLARATION; - out[0].decl.NrTokens = 4; - out[0].decl.File = file; - out[0].decl.UsageMask = TGSI_WRITEMASK_XYZW; /* FIXME! */ - out[0].decl.Interpolate = 1; - out[0].decl.Semantic = 1; - - out[1].value = 0; - out[1].decl_range.First = index; - out[1].decl_range.Last = index; - - out[2].value = 0; - out[2].decl_interp.Interpolate = interpolate; - out[2].decl_interp.CylindricalWrap = cylindrical_wrap; - out[2].decl_interp.Location = interpolate_location; - - out[3].value = 0; - out[3].decl_semantic.Name = semantic_name; - out[3].decl_semantic.Index = semantic_index; -} - -static void emit_decl_temps(struct ureg_program *ureg, unsigned first, - unsigned last, boolean local, unsigned arrayid) { - union tgsi_any_token *out = get_tokens(ureg, DOMAIN_DECL, arrayid ? 3 : 2); - - out[0].value = 0; - out[0].decl.Type = TGSI_TOKEN_TYPE_DECLARATION; - out[0].decl.NrTokens = 2; - out[0].decl.File = TGSI_FILE_TEMPORARY; - out[0].decl.UsageMask = TGSI_WRITEMASK_XYZW; - out[0].decl.Local = local; - - out[1].value = 0; - out[1].decl_range.First = first; - out[1].decl_range.Last = last; - - if (arrayid) { - out[0].decl.Array = 1; - out[2].value = 0; - out[2].array.ArrayID = arrayid; - } -} - -static void emit_decl_range(struct ureg_program *ureg, unsigned file, - unsigned first, unsigned count) { - union tgsi_any_token *out = get_tokens(ureg, DOMAIN_DECL, 2); - - out[0].value = 0; - out[0].decl.Type = TGSI_TOKEN_TYPE_DECLARATION; - out[0].decl.NrTokens = 2; - out[0].decl.File = file; - out[0].decl.UsageMask = TGSI_WRITEMASK_XYZW; - out[0].decl.Semantic = 0; - - out[1].value = 0; - out[1].decl_range.First = first; - out[1].decl_range.Last = first + count - 1; -} - -static void emit_decl_range2D(struct ureg_program *ureg, unsigned file, - unsigned first, unsigned last, unsigned index2D) { - union tgsi_any_token *out = get_tokens(ureg, DOMAIN_DECL, 3); - - out[0].value = 0; - out[0].decl.Type = TGSI_TOKEN_TYPE_DECLARATION; - out[0].decl.NrTokens = 3; - out[0].decl.File = file; - out[0].decl.UsageMask = TGSI_WRITEMASK_XYZW; - out[0].decl.Dimension = 1; - - out[1].value = 0; - out[1].decl_range.First = first; - out[1].decl_range.Last = last; - - out[2].value = 0; - out[2].decl_dim.Index2D = index2D; -} - -static void emit_decl_sampler_view(struct ureg_program *ureg, unsigned index, - unsigned target, unsigned return_type_x, - unsigned return_type_y, - unsigned return_type_z, - unsigned return_type_w) { - union tgsi_any_token *out = get_tokens(ureg, DOMAIN_DECL, 3); - - out[0].value = 0; - out[0].decl.Type = TGSI_TOKEN_TYPE_DECLARATION; - out[0].decl.NrTokens = 3; - out[0].decl.File = TGSI_FILE_SAMPLER_VIEW; - out[0].decl.UsageMask = 0xf; - - out[1].value = 0; - out[1].decl_range.First = index; - out[1].decl_range.Last = index; - - out[2].value = 0; - out[2].decl_sampler_view.Resource = target; - out[2].decl_sampler_view.ReturnTypeX = return_type_x; - out[2].decl_sampler_view.ReturnTypeY = return_type_y; - out[2].decl_sampler_view.ReturnTypeZ = return_type_z; - out[2].decl_sampler_view.ReturnTypeW = return_type_w; -} - -static void emit_immediate(struct ureg_program *ureg, const unsigned *v, - unsigned type) { - union tgsi_any_token *out = get_tokens(ureg, DOMAIN_DECL, 5); - - out[0].value = 0; - out[0].imm.Type = TGSI_TOKEN_TYPE_IMMEDIATE; - out[0].imm.NrTokens = 5; - out[0].imm.DataType = type; - out[0].imm.Padding = 0; - - out[1].imm_data.Uint = v[0]; - out[2].imm_data.Uint = v[1]; - out[3].imm_data.Uint = v[2]; - out[4].imm_data.Uint = v[3]; -} - -static void emit_property(struct ureg_program *ureg, unsigned name, - unsigned data) { - union tgsi_any_token *out = get_tokens(ureg, DOMAIN_DECL, 2); - - out[0].value = 0; - out[0].prop.Type = TGSI_TOKEN_TYPE_PROPERTY; - out[0].prop.NrTokens = 2; - out[0].prop.PropertyName = name; - - out[1].prop_data.Data = data; -} - -static void emit_decl_atomic_2d(struct ureg_program *ureg, unsigned first, - unsigned last, unsigned index2D, - unsigned array_id) { - union tgsi_any_token *out = get_tokens(ureg, DOMAIN_DECL, array_id ? 4 : 3); - - out[0].value = 0; - out[0].decl.Type = TGSI_TOKEN_TYPE_DECLARATION; - out[0].decl.NrTokens = 3; - out[0].decl.File = TGSI_FILE_HW_ATOMIC; - out[0].decl.UsageMask = TGSI_WRITEMASK_XYZW; - out[0].decl.Dimension = 1; - out[0].decl.Array = array_id != 0; - - out[1].value = 0; - out[1].decl_range.First = first; - out[1].decl_range.Last = last; - - out[2].value = 0; - out[2].decl_dim.Index2D = index2D; - - if (array_id) { - out[3].value = 0; - out[3].array.ArrayID = array_id; - } -} - -static void emit_decls(struct ureg_program *ureg) { - unsigned i; - - for (i = 0; i < ARRAY_SIZE(ureg->properties); i++) - if (ureg->properties[i] != ~0u) - emit_property(ureg, i, ureg->properties[i]); - - if (ureg->processor == TGSI_PROCESSOR_VERTEX) { - for (i = 0; i < UREG_MAX_INPUT; i++) { - if (ureg->vs_inputs[i / 32] & (1 << (i % 32))) { - emit_decl_range(ureg, TGSI_FILE_INPUT, i, 1); - } - } - } else if (ureg->processor == TGSI_PROCESSOR_FRAGMENT) { - for (i = 0; i < ureg->nr_fs_inputs; i++) { - emit_decl_fs(ureg, TGSI_FILE_INPUT, i, ureg->fs_input[i].semantic_name, - ureg->fs_input[i].semantic_index, ureg->fs_input[i].interp, - ureg->fs_input[i].cylindrical_wrap, - ureg->fs_input[i].interp_location); - } - } else { - for (i = 0; i < ureg->nr_gs_inputs; i++) { - emit_decl_semantic(ureg, TGSI_FILE_INPUT, ureg->gs_input[i].index, - ureg->gs_input[i].semantic_name, - ureg->gs_input[i].semantic_index, TGSI_WRITEMASK_XYZW); - } - } - - for (i = 0; i < ureg->nr_system_values; i++) { - emit_decl_semantic( - ureg, TGSI_FILE_SYSTEM_VALUE, ureg->system_value[i].index, - ureg->system_value[i].semantic_name, - ureg->system_value[i].semantic_index, TGSI_WRITEMASK_XYZW); - } - - for (i = 0; i < ureg->nr_outputs; i++) { - emit_decl_semantic(ureg, TGSI_FILE_OUTPUT, i, ureg->output[i].semantic_name, - ureg->output[i].semantic_index, - ureg->output[i].usage_mask); - } - - for (i = 0; i < ureg->nr_samplers; i++) { - emit_decl_range(ureg, TGSI_FILE_SAMPLER, ureg->sampler[i].Index, 1); - } - - for (i = 0; i < ureg->nr_sampler_views; i++) { - emit_decl_sampler_view(ureg, ureg->sampler_view[i].index, - ureg->sampler_view[i].target, - ureg->sampler_view[i].return_type_x, - ureg->sampler_view[i].return_type_y, - ureg->sampler_view[i].return_type_z, - ureg->sampler_view[i].return_type_w); - } - - if (ureg->const_decls.nr_constant_ranges) { - for (i = 0; i < ureg->const_decls.nr_constant_ranges; i++) { - emit_decl_range(ureg, TGSI_FILE_CONSTANT, - ureg->const_decls.constant_range[i].first, - ureg->const_decls.constant_range[i].last - - ureg->const_decls.constant_range[i].first + 1); - } - } - - for (i = 0; i < PIPE_MAX_CONSTANT_BUFFERS; i++) { - struct const_decl *decl = &ureg->const_decls2D[i]; - - if (decl->nr_constant_ranges) { - uint j; - - for (j = 0; j < decl->nr_constant_ranges; j++) { - emit_decl_range2D(ureg, TGSI_FILE_CONSTANT, - decl->constant_range[j].first, - decl->constant_range[j].last, i); - } - } - } - - for (i = 0; i < PIPE_MAX_HW_ATOMIC_BUFFERS; i++) { - struct hw_atomic_decl *decl = &ureg->hw_atomic_decls[i]; - - if (decl->nr_hw_atomic_ranges) { - uint j; - - for (j = 0; j < decl->nr_hw_atomic_ranges; j++) { - emit_decl_atomic_2d(ureg, decl->hw_atomic_range[j].first, - decl->hw_atomic_range[j].last, i, - decl->hw_atomic_range[j].array_id); - } - } - } - - if (ureg->nr_temps) { - unsigned array = 0; - for (i = 0; i < ureg->nr_temps;) { - boolean local = util_bitmask_get(ureg->local_temps, i); - unsigned first = i; - i = util_bitmask_get_next_index(ureg->decl_temps, i + 1); - if (i == UTIL_BITMASK_INVALID_INDEX) - i = ureg->nr_temps; - - if (array < ureg->nr_array_temps && ureg->array_temps[array] == first) - emit_decl_temps(ureg, first, i - 1, local, ++array); - else - emit_decl_temps(ureg, first, i - 1, local, 0); - } - } - - if (ureg->nr_addrs) { - emit_decl_range(ureg, TGSI_FILE_ADDRESS, 0, ureg->nr_addrs); - } - - if (ureg->nr_preds) { - emit_decl_range(ureg, TGSI_FILE_PREDICATE, 0, ureg->nr_preds); - } - - for (i = 0; i < ureg->nr_immediates; i++) { - emit_immediate(ureg, ureg->immediate[i].value.u, ureg->immediate[i].type); - } -} - -/* Append the instruction tokens onto the declarations to build a - * contiguous stream suitable to send to the driver. - */ -static void copy_instructions(struct ureg_program *ureg) { - unsigned nr_tokens = ureg->domain[DOMAIN_INSN].count; - union tgsi_any_token *out = get_tokens(ureg, DOMAIN_DECL, nr_tokens); - - memcpy(out, ureg->domain[DOMAIN_INSN].tokens, nr_tokens * sizeof out[0]); -} - -static void fixup_header_size(struct ureg_program *ureg) { - union tgsi_any_token *out = retrieve_token(ureg, DOMAIN_DECL, 0); - - out->header.BodySize = ureg->domain[DOMAIN_DECL].count - 2; -} - -static void emit_header(struct ureg_program *ureg) { - union tgsi_any_token *out = get_tokens(ureg, DOMAIN_DECL, 2); - - out[0].header.HeaderSize = 2; - out[0].header.BodySize = 0; - - out[1].processor.Processor = ureg->processor; - out[1].processor.Padding = 0; -} - -const struct tgsi_token *ureg_finalize(struct ureg_program *ureg) { - const struct tgsi_token *tokens; - - emit_header(ureg); - emit_decls(ureg); - copy_instructions(ureg); - fixup_header_size(ureg); - - if (ureg->domain[0].tokens == error_tokens || - ureg->domain[1].tokens == error_tokens) { - debug_printf("%s: error in generated shader\n", __FUNCTION__); - assert(0); - return NULL; - } - - tokens = &ureg->domain[DOMAIN_DECL].tokens[0].token; - - if (0) { - debug_printf("%s: emitted shader %d tokens:\n", __FUNCTION__, - ureg->domain[DOMAIN_DECL].count); - tgsi_dump(tokens, 0); - } - -#if DEBUG - if (tokens && !tgsi_sanity_check(tokens)) { - debug_printf("tgsi_ureg.c, sanity check failed on generated tokens:\n"); - tgsi_dump(tokens, 0); - assert(0); - } -#endif - - return tokens; -} - -void *ureg_create_shader(struct ureg_program *ureg, struct pipe_context *pipe, - const struct pipe_stream_output_info *so) { - struct pipe_shader_state state; - - state.tokens = ureg_finalize(ureg); - if (!state.tokens) - return NULL; - - if (so) - state.stream_output = *so; - else - memset(&state.stream_output, 0, sizeof(state.stream_output)); - - if (ureg->processor == TGSI_PROCESSOR_VERTEX) - return pipe->create_vs_state(pipe, &state); - else - return pipe->create_fs_state(pipe, &state); -} - -const struct tgsi_token *ureg_get_tokens(struct ureg_program *ureg, - unsigned *nr_tokens) { - const struct tgsi_token *tokens; - - ureg_finalize(ureg); - - tokens = &ureg->domain[DOMAIN_DECL].tokens[0].token; - - if (nr_tokens) - *nr_tokens = ureg->domain[DOMAIN_DECL].size; - - ureg->domain[DOMAIN_DECL].tokens = 0; - ureg->domain[DOMAIN_DECL].size = 0; - ureg->domain[DOMAIN_DECL].order = 0; - ureg->domain[DOMAIN_DECL].count = 0; - - return tokens; -} - -void ureg_free_tokens(const struct tgsi_token *tokens) { - FREE((struct tgsi_token *)tokens); -} - -struct ureg_program *ureg_create(unsigned processor) { - unsigned i; - struct ureg_program *ureg = CALLOC_STRUCT(ureg_program); - if (ureg == NULL) - goto no_ureg; - - ureg->processor = processor; - - for (i = 0; i < ARRAY_SIZE(ureg->properties); i++) - ureg->properties[i] = ~0; - - ureg->free_temps = util_bitmask_create(); - if (ureg->free_temps == NULL) - goto no_free_temps; - - ureg->local_temps = util_bitmask_create(); - if (ureg->local_temps == NULL) - goto no_local_temps; - - ureg->decl_temps = util_bitmask_create(); - if (ureg->decl_temps == NULL) - goto no_decl_temps; - - return ureg; - -no_decl_temps: - util_bitmask_destroy(ureg->local_temps); -no_local_temps: - util_bitmask_destroy(ureg->free_temps); -no_free_temps: - FREE(ureg); -no_ureg: - return NULL; -} - -unsigned ureg_get_nr_outputs(const struct ureg_program *ureg) { - if (!ureg) - return 0; - return ureg->nr_outputs; -} - -void ureg_destroy(struct ureg_program *ureg) { - unsigned i; - - for (i = 0; i < ARRAY_SIZE(ureg->domain); i++) { - if (ureg->domain[i].tokens && ureg->domain[i].tokens != error_tokens) - FREE(ureg->domain[i].tokens); - } - - util_bitmask_destroy(ureg->free_temps); - util_bitmask_destroy(ureg->local_temps); - util_bitmask_destroy(ureg->decl_temps); - - FREE(ureg); -} diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_ureg.h b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_ureg.h deleted file mode 100644 index f6c287c0c..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_ureg.h +++ /dev/null @@ -1,888 +0,0 @@ -/************************************************************************** - * - * Copyright 2009 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE, INC AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -#ifndef TGSI_UREG_H -#define TGSI_UREG_H - -#include "pipe/p_compiler.h" -#include "pipe/p_shader_tokens.h" -#include "util/u_debug.h" - -#ifdef __cplusplus -extern "C" { -#endif - -struct ureg_program; -struct pipe_stream_output_info; - -/* Almost a tgsi_src_register, but we need to pull in the Absolute - * flag from the _ext token. Indirect flag always implies ADDR[0]. - */ -struct ureg_src { - unsigned File : 4; /* TGSI_FILE_ */ - unsigned SwizzleX : 2; /* TGSI_SWIZZLE_ */ - unsigned SwizzleY : 2; /* TGSI_SWIZZLE_ */ - unsigned SwizzleZ : 2; /* TGSI_SWIZZLE_ */ - unsigned SwizzleW : 2; /* TGSI_SWIZZLE_ */ - unsigned Indirect : 1; /* BOOL */ - unsigned DimIndirect : 1; /* BOOL */ - unsigned Dimension : 1; /* BOOL */ - unsigned Absolute : 1; /* BOOL */ - unsigned Negate : 1; /* BOOL */ - unsigned IndirectFile : 4; /* TGSI_FILE_ */ - unsigned IndirectSwizzle : 2; /* TGSI_SWIZZLE_ */ - unsigned DimIndFile : 4; /* TGSI_FILE_ */ - unsigned DimIndSwizzle : 2; /* TGSI_SWIZZLE_ */ - int Index : 16; /* SINT */ - int IndirectIndex : 16; /* SINT */ - int DimensionIndex : 16; /* SINT */ - int DimIndIndex : 16; /* SINT */ - unsigned ArrayID : 10; /* UINT */ -}; - -/* Very similar to a tgsi_dst_register, removing unsupported fields - * and adding a Saturate flag. It's easier to push saturate into the - * destination register than to try and create a _SAT variant of each - * instruction function. - */ -struct ureg_dst { - unsigned File : 4; /* TGSI_FILE_ */ - unsigned WriteMask : 4; /* TGSI_WRITEMASK_ */ - unsigned Indirect : 1; /* BOOL */ - unsigned Saturate : 1; /* BOOL */ - unsigned Predicate : 1; - unsigned PredNegate : 1; /* BOOL */ - unsigned PredSwizzleX : 2; /* TGSI_SWIZZLE_ */ - unsigned PredSwizzleY : 2; /* TGSI_SWIZZLE_ */ - unsigned PredSwizzleZ : 2; /* TGSI_SWIZZLE_ */ - unsigned PredSwizzleW : 2; /* TGSI_SWIZZLE_ */ - int Index : 16; /* SINT */ - int IndirectIndex : 16; /* SINT */ - unsigned IndirectFile : 4; /* TGSI_FILE_ */ - int IndirectSwizzle : 2; /* TGSI_SWIZZLE_ */ - unsigned ArrayID : 10; /* UINT */ -}; - -struct pipe_context; - -struct ureg_program *ureg_create(unsigned processor); - -const struct tgsi_token *ureg_finalize(struct ureg_program *); - -/* Create and return a shader: - */ -void *ureg_create_shader(struct ureg_program *, struct pipe_context *pipe, - const struct pipe_stream_output_info *so); - -/* Alternately, return the built token stream and hand ownership of - * that memory to the caller: - */ -const struct tgsi_token *ureg_get_tokens(struct ureg_program *ureg, - unsigned *nr_tokens); - -/* - * Returns the number of currently declared outputs. - */ -unsigned ureg_get_nr_outputs(const struct ureg_program *ureg); - -/* Free the tokens created by ureg_get_tokens() */ -void ureg_free_tokens(const struct tgsi_token *tokens); - -void ureg_destroy(struct ureg_program *); - -/*********************************************************************** - * Convenience routine: - */ -static inline void *ureg_create_shader_with_so_and_destroy( - struct ureg_program *p, struct pipe_context *pipe, - const struct pipe_stream_output_info *so) { - void *result = ureg_create_shader(p, pipe, so); - ureg_destroy(p); - return result; -} - -static inline void *ureg_create_shader_and_destroy(struct ureg_program *p, - struct pipe_context *pipe) { - return ureg_create_shader_with_so_and_destroy(p, pipe, NULL); -} - -/*********************************************************************** - * Build shader properties: - */ - -void ureg_property(struct ureg_program *ureg, unsigned name, unsigned value); - -/*********************************************************************** - * Build shader declarations: - */ - -struct ureg_src ureg_DECL_fs_input_cyl_centroid( - struct ureg_program *, unsigned semantic_name, unsigned semantic_index, - unsigned interp_mode, unsigned cylindrical_wrap, unsigned interp_location); - -static inline struct ureg_src -ureg_DECL_fs_input_cyl(struct ureg_program *ureg, unsigned semantic_name, - unsigned semantic_index, unsigned interp_mode, - unsigned cylindrical_wrap) { - return ureg_DECL_fs_input_cyl_centroid(ureg, semantic_name, semantic_index, - interp_mode, cylindrical_wrap, 0); -} - -static inline struct ureg_src ureg_DECL_fs_input(struct ureg_program *ureg, - unsigned semantic_name, - unsigned semantic_index, - unsigned interp_mode) { - return ureg_DECL_fs_input_cyl_centroid(ureg, semantic_name, semantic_index, - interp_mode, 0, 0); -} - -struct ureg_src ureg_DECL_vs_input(struct ureg_program *, unsigned index); - -struct ureg_src ureg_DECL_gs_input(struct ureg_program *, unsigned index, - unsigned semantic_name, - unsigned semantic_index); - -struct ureg_src ureg_DECL_system_value(struct ureg_program *, unsigned index, - unsigned semantic_name, - unsigned semantic_index); - -struct ureg_dst ureg_DECL_output_masked(struct ureg_program *, - unsigned semantic_name, - unsigned semantic_index, - unsigned usage_mask); - -struct ureg_dst ureg_DECL_output(struct ureg_program *, unsigned semantic_name, - unsigned semantic_index); - -struct ureg_src ureg_DECL_immediate(struct ureg_program *, const float *v, - unsigned nr); - -struct ureg_src ureg_DECL_immediate_uint(struct ureg_program *, - const unsigned *v, unsigned nr); - -struct ureg_src ureg_DECL_immediate_block_uint(struct ureg_program *, - const unsigned *v, unsigned nr); - -struct ureg_src ureg_DECL_immediate_int(struct ureg_program *, const int *v, - unsigned nr); - -void ureg_DECL_constant2D(struct ureg_program *ureg, unsigned first, - unsigned last, unsigned index2D); - -struct ureg_src ureg_DECL_constant(struct ureg_program *, unsigned index); - -void ureg_DECL_hw_atomic(struct ureg_program *ureg, unsigned first, - unsigned last, unsigned buffer_id, unsigned array_id); - -struct ureg_dst ureg_DECL_temporary(struct ureg_program *); - -/** - * Emit a temporary with the LOCAL declaration flag set. For use when - * the register value is not required to be preserved across - * subroutine boundaries. - */ -struct ureg_dst ureg_DECL_local_temporary(struct ureg_program *); - -/** - * Declare "size" continuous temporary registers. - */ -struct ureg_dst ureg_DECL_array_temporary(struct ureg_program *, unsigned size, - boolean local); - -void ureg_release_temporary(struct ureg_program *ureg, struct ureg_dst tmp); - -struct ureg_dst ureg_DECL_address(struct ureg_program *); - -struct ureg_dst ureg_DECL_predicate(struct ureg_program *); - -/* Supply an index to the sampler declaration as this is the hook to - * the external pipe_sampler state. Users of this function probably - * don't want just any sampler, but a specific one which they've set - * up state for in the context. - */ -struct ureg_src ureg_DECL_sampler(struct ureg_program *, int index); - -struct ureg_src ureg_DECL_sampler_view(struct ureg_program *, unsigned index, - unsigned target, unsigned return_type_x, - unsigned return_type_y, - unsigned return_type_z, - unsigned return_type_w); - -static inline struct ureg_src ureg_imm4f(struct ureg_program *ureg, float a, - float b, float c, float d) { - float v[4]; - v[0] = a; - v[1] = b; - v[2] = c; - v[3] = d; - return ureg_DECL_immediate(ureg, v, 4); -} - -static inline struct ureg_src ureg_imm3f(struct ureg_program *ureg, float a, - float b, float c) { - float v[3]; - v[0] = a; - v[1] = b; - v[2] = c; - return ureg_DECL_immediate(ureg, v, 3); -} - -static inline struct ureg_src ureg_imm2f(struct ureg_program *ureg, float a, - float b) { - float v[2]; - v[0] = a; - v[1] = b; - return ureg_DECL_immediate(ureg, v, 2); -} - -static inline struct ureg_src ureg_imm1f(struct ureg_program *ureg, float a) { - float v[1]; - v[0] = a; - return ureg_DECL_immediate(ureg, v, 1); -} - -static inline struct ureg_src ureg_imm4u(struct ureg_program *ureg, unsigned a, - unsigned b, unsigned c, unsigned d) { - unsigned v[4]; - v[0] = a; - v[1] = b; - v[2] = c; - v[3] = d; - return ureg_DECL_immediate_uint(ureg, v, 4); -} - -static inline struct ureg_src ureg_imm3u(struct ureg_program *ureg, unsigned a, - unsigned b, unsigned c) { - unsigned v[3]; - v[0] = a; - v[1] = b; - v[2] = c; - return ureg_DECL_immediate_uint(ureg, v, 3); -} - -static inline struct ureg_src ureg_imm2u(struct ureg_program *ureg, unsigned a, - unsigned b) { - unsigned v[2]; - v[0] = a; - v[1] = b; - return ureg_DECL_immediate_uint(ureg, v, 2); -} - -static inline struct ureg_src ureg_imm1u(struct ureg_program *ureg, - unsigned a) { - return ureg_DECL_immediate_uint(ureg, &a, 1); -} - -static inline struct ureg_src ureg_imm4i(struct ureg_program *ureg, int a, - int b, int c, int d) { - int v[4]; - v[0] = a; - v[1] = b; - v[2] = c; - v[3] = d; - return ureg_DECL_immediate_int(ureg, v, 4); -} - -static inline struct ureg_src ureg_imm3i(struct ureg_program *ureg, int a, - int b, int c) { - int v[3]; - v[0] = a; - v[1] = b; - v[2] = c; - return ureg_DECL_immediate_int(ureg, v, 3); -} - -static inline struct ureg_src ureg_imm2i(struct ureg_program *ureg, int a, - int b) { - int v[2]; - v[0] = a; - v[1] = b; - return ureg_DECL_immediate_int(ureg, v, 2); -} - -static inline struct ureg_src ureg_imm1i(struct ureg_program *ureg, int a) { - return ureg_DECL_immediate_int(ureg, &a, 1); -} - -/* Where the destination register has a valid file, but an empty - * writemask. - */ -static inline boolean ureg_dst_is_empty(struct ureg_dst dst) { - return dst.File != TGSI_FILE_NULL && dst.WriteMask == 0; -} - -/*********************************************************************** - * Functions for patching up labels - */ - -/* Will return a number which can be used in a label to point to the - * next instruction to be emitted. - */ -unsigned ureg_get_instruction_number(struct ureg_program *ureg); - -/* Patch a given label (expressed as a token number) to point to a - * given instruction (expressed as an instruction number). - * - * Labels are obtained from instruction emitters, eg ureg_CAL(). - * Instruction numbers are obtained from ureg_get_instruction_number(), - * above. - */ -void ureg_fixup_label(struct ureg_program *ureg, unsigned label_token, - unsigned instruction_number); - -/* Generic instruction emitter. Use if you need to pass the opcode as - * a parameter, rather than using the emit_OP() variants below. - */ -void ureg_insn(struct ureg_program *ureg, unsigned opcode, - const struct ureg_dst *dst, unsigned nr_dst, - const struct ureg_src *src, unsigned nr_src, unsigned precise); - -void ureg_tex_insn(struct ureg_program *ureg, unsigned opcode, - const struct ureg_dst *dst, unsigned nr_dst, unsigned target, - const struct tgsi_texture_offset *texoffsets, - unsigned nr_offset, const struct ureg_src *src, - unsigned nr_src); - -void ureg_label_insn(struct ureg_program *ureg, unsigned opcode, - const struct ureg_src *src, unsigned nr_src, - unsigned *label); - -/*********************************************************************** - * Internal instruction helpers, don't call these directly: - */ - -struct ureg_emit_insn_result { - unsigned insn_token; /*< Used to fixup insn size. */ - unsigned extended_token; /*< Used to set the Extended bit, usually the same as - insn_token. */ -}; - -struct ureg_emit_insn_result ureg_emit_insn(struct ureg_program *ureg, - unsigned opcode, boolean saturate, - unsigned precise, unsigned num_dst, - unsigned num_src); - -void ureg_emit_label(struct ureg_program *ureg, unsigned insn_token, - unsigned *label_token); - -void ureg_emit_texture(struct ureg_program *ureg, unsigned insn_token, - unsigned target, unsigned num_offsets); - -void ureg_emit_texture_offset(struct ureg_program *ureg, - const struct tgsi_texture_offset *offset); - -void ureg_emit_dst(struct ureg_program *ureg, struct ureg_dst dst); - -void ureg_emit_src(struct ureg_program *ureg, struct ureg_src src); - -void ureg_fixup_insn_size(struct ureg_program *ureg, unsigned insn); - -#define OP00(op) \ - static inline void ureg_##op(struct ureg_program *ureg) { \ - unsigned opcode = TGSI_OPCODE_##op; \ - struct ureg_emit_insn_result insn; \ - insn = ureg_emit_insn(ureg, opcode, FALSE, 0, 0, 0); \ - ureg_fixup_insn_size(ureg, insn.insn_token); \ - } - -#define OP01(op) \ - static inline void ureg_##op(struct ureg_program *ureg, \ - struct ureg_src src) { \ - unsigned opcode = TGSI_OPCODE_##op; \ - struct ureg_emit_insn_result insn; \ - insn = ureg_emit_insn(ureg, opcode, FALSE, 0, 0, 1); \ - ureg_emit_src(ureg, src); \ - ureg_fixup_insn_size(ureg, insn.insn_token); \ - } - -#define OP00_LBL(op) \ - static inline void ureg_##op(struct ureg_program *ureg, \ - unsigned *label_token) { \ - unsigned opcode = TGSI_OPCODE_##op; \ - struct ureg_emit_insn_result insn; \ - insn = ureg_emit_insn(ureg, opcode, FALSE, 0, 0, 0); \ - ureg_emit_label(ureg, insn.extended_token, label_token); \ - ureg_fixup_insn_size(ureg, insn.insn_token); \ - } - -#define OP01_LBL(op) \ - static inline void ureg_##op(struct ureg_program *ureg, struct ureg_src src, \ - unsigned *label_token) { \ - unsigned opcode = TGSI_OPCODE_##op; \ - struct ureg_emit_insn_result insn; \ - insn = ureg_emit_insn(ureg, opcode, FALSE, 0, 0, 1); \ - ureg_emit_label(ureg, insn.extended_token, label_token); \ - ureg_emit_src(ureg, src); \ - ureg_fixup_insn_size(ureg, insn.insn_token); \ - } - -#define OP10(op) \ - static inline void ureg_##op(struct ureg_program *ureg, \ - struct ureg_dst dst) { \ - unsigned opcode = TGSI_OPCODE_##op; \ - struct ureg_emit_insn_result insn; \ - if (ureg_dst_is_empty(dst)) \ - return; \ - insn = ureg_emit_insn(ureg, opcode, dst.Saturate, 0, 1, 0); \ - ureg_emit_dst(ureg, dst); \ - ureg_fixup_insn_size(ureg, insn.insn_token); \ - } - -#define OP11(op) \ - static inline void ureg_##op(struct ureg_program *ureg, struct ureg_dst dst, \ - struct ureg_src src) { \ - unsigned opcode = TGSI_OPCODE_##op; \ - struct ureg_emit_insn_result insn; \ - if (ureg_dst_is_empty(dst)) \ - return; \ - insn = ureg_emit_insn(ureg, opcode, dst.Saturate, 0, 1, 1); \ - ureg_emit_dst(ureg, dst); \ - ureg_emit_src(ureg, src); \ - ureg_fixup_insn_size(ureg, insn.insn_token); \ - } - -#define OP12(op) \ - static inline void ureg_##op(struct ureg_program *ureg, struct ureg_dst dst, \ - struct ureg_src src0, struct ureg_src src1) { \ - unsigned opcode = TGSI_OPCODE_##op; \ - struct ureg_emit_insn_result insn; \ - if (ureg_dst_is_empty(dst)) \ - return; \ - insn = ureg_emit_insn(ureg, opcode, dst.Saturate, 0, 1, 2); \ - ureg_emit_dst(ureg, dst); \ - ureg_emit_src(ureg, src0); \ - ureg_emit_src(ureg, src1); \ - ureg_fixup_insn_size(ureg, insn.insn_token); \ - } - -#define OP12_TEX(op) \ - static inline void ureg_##op(struct ureg_program *ureg, struct ureg_dst dst, \ - unsigned target, struct ureg_src src0, \ - struct ureg_src src1) { \ - unsigned opcode = TGSI_OPCODE_##op; \ - struct ureg_emit_insn_result insn; \ - if (ureg_dst_is_empty(dst)) \ - return; \ - insn = ureg_emit_insn(ureg, opcode, dst.Saturate, 0, 1, 2); \ - ureg_emit_texture(ureg, insn.extended_token, target, 0); \ - ureg_emit_dst(ureg, dst); \ - ureg_emit_src(ureg, src0); \ - ureg_emit_src(ureg, src1); \ - ureg_fixup_insn_size(ureg, insn.insn_token); \ - } - -#define OP12_SAMPLE(op) \ - static inline void ureg_##op(struct ureg_program *ureg, struct ureg_dst dst, \ - struct ureg_src src0, struct ureg_src src1) { \ - unsigned opcode = TGSI_OPCODE_##op; \ - unsigned target = TGSI_TEXTURE_UNKNOWN; \ - struct ureg_emit_insn_result insn; \ - if (ureg_dst_is_empty(dst)) \ - return; \ - insn = ureg_emit_insn(ureg, opcode, dst.Saturate, 0, 1, 2); \ - ureg_emit_texture(ureg, insn.extended_token, target, 0); \ - ureg_emit_dst(ureg, dst); \ - ureg_emit_src(ureg, src0); \ - ureg_emit_src(ureg, src1); \ - ureg_fixup_insn_size(ureg, insn.insn_token); \ - } - -#define OP13(op) \ - static inline void ureg_##op(struct ureg_program *ureg, struct ureg_dst dst, \ - struct ureg_src src0, struct ureg_src src1, \ - struct ureg_src src2) { \ - unsigned opcode = TGSI_OPCODE_##op; \ - struct ureg_emit_insn_result insn; \ - if (ureg_dst_is_empty(dst)) \ - return; \ - insn = ureg_emit_insn(ureg, opcode, dst.Saturate, 0, 1, 3); \ - ureg_emit_dst(ureg, dst); \ - ureg_emit_src(ureg, src0); \ - ureg_emit_src(ureg, src1); \ - ureg_emit_src(ureg, src2); \ - ureg_fixup_insn_size(ureg, insn.insn_token); \ - } - -#define OP13_SAMPLE(op) \ - static inline void ureg_##op(struct ureg_program *ureg, struct ureg_dst dst, \ - struct ureg_src src0, struct ureg_src src1, \ - struct ureg_src src2) { \ - unsigned opcode = TGSI_OPCODE_##op; \ - unsigned target = TGSI_TEXTURE_UNKNOWN; \ - struct ureg_emit_insn_result insn; \ - if (ureg_dst_is_empty(dst)) \ - return; \ - insn = ureg_emit_insn(ureg, opcode, dst.Saturate, 0, 1, 3); \ - ureg_emit_texture(ureg, insn.extended_token, target, 0); \ - ureg_emit_dst(ureg, dst); \ - ureg_emit_src(ureg, src0); \ - ureg_emit_src(ureg, src1); \ - ureg_emit_src(ureg, src2); \ - ureg_fixup_insn_size(ureg, insn.insn_token); \ - } - -#define OP14_TEX(op) \ - static inline void ureg_##op(struct ureg_program *ureg, struct ureg_dst dst, \ - unsigned target, struct ureg_src src0, \ - struct ureg_src src1, struct ureg_src src2, \ - struct ureg_src src3) { \ - unsigned opcode = TGSI_OPCODE_##op; \ - struct ureg_emit_insn_result insn; \ - if (ureg_dst_is_empty(dst)) \ - return; \ - insn = ureg_emit_insn(ureg, opcode, dst.Saturate, 0, 1, 4); \ - ureg_emit_texture(ureg, insn.extended_token, target, 0); \ - ureg_emit_dst(ureg, dst); \ - ureg_emit_src(ureg, src0); \ - ureg_emit_src(ureg, src1); \ - ureg_emit_src(ureg, src2); \ - ureg_emit_src(ureg, src3); \ - ureg_fixup_insn_size(ureg, insn.insn_token); \ - } - -#define OP14_SAMPLE(op) \ - static inline void ureg_##op(struct ureg_program *ureg, struct ureg_dst dst, \ - struct ureg_src src0, struct ureg_src src1, \ - struct ureg_src src2, struct ureg_src src3) { \ - unsigned opcode = TGSI_OPCODE_##op; \ - unsigned target = TGSI_TEXTURE_UNKNOWN; \ - struct ureg_emit_insn_result insn; \ - if (ureg_dst_is_empty(dst)) \ - return; \ - insn = ureg_emit_insn(ureg, opcode, dst.Saturate, 0, 1, 4); \ - ureg_emit_texture(ureg, insn.extended_token, target, 0); \ - ureg_emit_dst(ureg, dst); \ - ureg_emit_src(ureg, src0); \ - ureg_emit_src(ureg, src1); \ - ureg_emit_src(ureg, src2); \ - ureg_emit_src(ureg, src3); \ - ureg_fixup_insn_size(ureg, insn.insn_token); \ - } - -#define OP14(op) \ - static inline void ureg_##op(struct ureg_program *ureg, struct ureg_dst dst, \ - struct ureg_src src0, struct ureg_src src1, \ - struct ureg_src src2, struct ureg_src src3) { \ - unsigned opcode = TGSI_OPCODE_##op; \ - struct ureg_emit_insn_result insn; \ - if (ureg_dst_is_empty(dst)) \ - return; \ - insn = ureg_emit_insn(ureg, opcode, dst.Saturate, 0, 1, 4); \ - ureg_emit_dst(ureg, dst); \ - ureg_emit_src(ureg, src0); \ - ureg_emit_src(ureg, src1); \ - ureg_emit_src(ureg, src2); \ - ureg_emit_src(ureg, src3); \ - ureg_fixup_insn_size(ureg, insn.insn_token); \ - } - -#define OP15(op) \ - static inline void ureg_##op(struct ureg_program *ureg, struct ureg_dst dst, \ - struct ureg_src src0, struct ureg_src src1, \ - struct ureg_src src2, struct ureg_src src3, \ - struct ureg_src src4) { \ - unsigned opcode = TGSI_OPCODE_##op; \ - struct ureg_emit_insn_result insn; \ - if (ureg_dst_is_empty(dst)) \ - return; \ - insn = ureg_emit_insn(ureg, opcode, dst.Saturate, 0, 1, 5); \ - ureg_emit_dst(ureg, dst); \ - ureg_emit_src(ureg, src0); \ - ureg_emit_src(ureg, src1); \ - ureg_emit_src(ureg, src2); \ - ureg_emit_src(ureg, src3); \ - ureg_emit_src(ureg, src4); \ - ureg_fixup_insn_size(ureg, insn.insn_token); \ - } - -#define OP15_SAMPLE(op) \ - static inline void ureg_##op(struct ureg_program *ureg, struct ureg_dst dst, \ - struct ureg_src src0, struct ureg_src src1, \ - struct ureg_src src2, struct ureg_src src3, \ - struct ureg_src src4) { \ - unsigned opcode = TGSI_OPCODE_##op; \ - unsigned target = TGSI_TEXTURE_UNKNOWN; \ - struct ureg_emit_insn_result insn; \ - if (ureg_dst_is_empty(dst)) \ - return; \ - insn = ureg_emit_insn(ureg, opcode, dst.Saturate, 0, 1, 5); \ - ureg_emit_texture(ureg, insn.extended_token, target, 0); \ - ureg_emit_dst(ureg, dst); \ - ureg_emit_src(ureg, src0); \ - ureg_emit_src(ureg, src1); \ - ureg_emit_src(ureg, src2); \ - ureg_emit_src(ureg, src3); \ - ureg_emit_src(ureg, src4); \ - ureg_fixup_insn_size(ureg, insn.insn_token); \ - } - -/* Use a template include to generate a correctly-typed ureg_OP() - * function for each TGSI opcode: - */ -#include "tgsi_opcode_tmp.h" - -/*********************************************************************** - * Inline helpers for manipulating register structs: - */ -static inline struct ureg_src ureg_negate(struct ureg_src reg) { - assert(reg.File != TGSI_FILE_NULL); - reg.Negate ^= 1; - return reg; -} - -static inline struct ureg_src ureg_abs(struct ureg_src reg) { - assert(reg.File != TGSI_FILE_NULL); - reg.Absolute = 1; - reg.Negate = 0; - return reg; -} - -static inline struct ureg_src ureg_swizzle(struct ureg_src reg, int x, int y, - int z, int w) { - unsigned swz = ((reg.SwizzleX << 0) | (reg.SwizzleY << 2) | - (reg.SwizzleZ << 4) | (reg.SwizzleW << 6)); - - assert(reg.File != TGSI_FILE_NULL); - assert(x < 4); - assert(y < 4); - assert(z < 4); - assert(w < 4); - - reg.SwizzleX = (swz >> (x * 2)) & 0x3; - reg.SwizzleY = (swz >> (y * 2)) & 0x3; - reg.SwizzleZ = (swz >> (z * 2)) & 0x3; - reg.SwizzleW = (swz >> (w * 2)) & 0x3; - return reg; -} - -static inline struct ureg_src ureg_scalar(struct ureg_src reg, int x) { - return ureg_swizzle(reg, x, x, x, x); -} - -static inline struct ureg_dst ureg_writemask(struct ureg_dst reg, - unsigned writemask) { - assert(reg.File != TGSI_FILE_NULL); - reg.WriteMask &= writemask; - return reg; -} - -static inline struct ureg_dst ureg_saturate(struct ureg_dst reg) { - assert(reg.File != TGSI_FILE_NULL); - reg.Saturate = 1; - return reg; -} - -static inline struct ureg_dst -ureg_predicate(struct ureg_dst reg, boolean negate, unsigned swizzle_x, - unsigned swizzle_y, unsigned swizzle_z, unsigned swizzle_w) { - assert(reg.File != TGSI_FILE_NULL); - reg.Predicate = 1; - reg.PredNegate = negate; - reg.PredSwizzleX = swizzle_x; - reg.PredSwizzleY = swizzle_y; - reg.PredSwizzleZ = swizzle_z; - reg.PredSwizzleW = swizzle_w; - return reg; -} - -static inline struct ureg_dst ureg_dst_indirect(struct ureg_dst reg, - struct ureg_src addr) { - assert(reg.File != TGSI_FILE_NULL); - assert(addr.File == TGSI_FILE_ADDRESS || addr.File == TGSI_FILE_TEMPORARY); - reg.Indirect = 1; - reg.IndirectFile = addr.File; - reg.IndirectIndex = addr.Index; - reg.IndirectSwizzle = addr.SwizzleX; - return reg; -} - -static inline struct ureg_src ureg_src_indirect(struct ureg_src reg, - struct ureg_src addr) { - assert(reg.File != TGSI_FILE_NULL); - assert(addr.File == TGSI_FILE_ADDRESS || addr.File == TGSI_FILE_TEMPORARY); - reg.Indirect = 1; - reg.IndirectFile = addr.File; - reg.IndirectIndex = addr.Index; - reg.IndirectSwizzle = addr.SwizzleX; - return reg; -} - -static inline struct ureg_src ureg_src_dimension(struct ureg_src reg, - int index) { - assert(reg.File != TGSI_FILE_NULL); - reg.Dimension = 1; - reg.DimIndirect = 0; - reg.DimensionIndex = index; - return reg; -} - -static inline struct ureg_src ureg_src_dimension_indirect(struct ureg_src reg, - struct ureg_src addr, - int index) { - assert(reg.File != TGSI_FILE_NULL); - reg.Dimension = 1; - reg.DimIndirect = 1; - reg.DimensionIndex = index; - reg.DimIndFile = addr.File; - reg.DimIndIndex = addr.Index; - reg.DimIndSwizzle = addr.SwizzleX; - return reg; -} - -static inline struct ureg_dst ureg_dst_array_offset(struct ureg_dst reg, - int offset) { - assert(reg.File == TGSI_FILE_TEMPORARY); - reg.Index += offset; - return reg; -} - -static inline struct ureg_dst ureg_dst(struct ureg_src src) { - struct ureg_dst dst; - - assert(!src.Indirect || (src.IndirectFile == TGSI_FILE_ADDRESS || - src.IndirectFile == TGSI_FILE_TEMPORARY)); - - dst.File = src.File; - dst.WriteMask = TGSI_WRITEMASK_XYZW; - dst.IndirectFile = src.IndirectFile; - dst.Indirect = src.Indirect; - dst.IndirectIndex = src.IndirectIndex; - dst.IndirectSwizzle = src.IndirectSwizzle; - dst.Saturate = 0; - dst.Index = src.Index; - dst.ArrayID = src.ArrayID; - - return dst; -} - -static inline struct ureg_src ureg_src_register(unsigned file, unsigned index) { - struct ureg_src src; - - src.File = file; - src.SwizzleX = TGSI_SWIZZLE_X; - src.SwizzleY = TGSI_SWIZZLE_Y; - src.SwizzleZ = TGSI_SWIZZLE_Z; - src.SwizzleW = TGSI_SWIZZLE_W; - src.Indirect = 0; - src.IndirectFile = TGSI_FILE_NULL; - src.IndirectIndex = 0; - src.IndirectSwizzle = 0; - src.Absolute = 0; - src.Index = index; - src.Negate = 0; - src.Dimension = 0; - src.DimensionIndex = 0; - src.DimIndirect = 0; - src.DimIndFile = TGSI_FILE_NULL; - src.DimIndIndex = 0; - src.DimIndSwizzle = 0; - src.ArrayID = 0; - - return src; -} - -static inline struct ureg_src ureg_src(struct ureg_dst dst) { - struct ureg_src src; - - src.File = dst.File; - src.SwizzleX = TGSI_SWIZZLE_X; - src.SwizzleY = TGSI_SWIZZLE_Y; - src.SwizzleZ = TGSI_SWIZZLE_Z; - src.SwizzleW = TGSI_SWIZZLE_W; - src.Indirect = dst.Indirect; - src.IndirectFile = dst.IndirectFile; - src.IndirectIndex = dst.IndirectIndex; - src.IndirectSwizzle = dst.IndirectSwizzle; - src.Absolute = 0; - src.Index = dst.Index; - src.Negate = 0; - src.Dimension = 0; - src.DimensionIndex = 0; - src.DimIndirect = 0; - src.DimIndFile = TGSI_FILE_NULL; - src.DimIndIndex = 0; - src.DimIndSwizzle = 0; - src.ArrayID = dst.ArrayID; - - return src; -} - -static inline struct ureg_dst ureg_dst_undef(void) { - struct ureg_dst dst; - - dst.File = TGSI_FILE_NULL; - dst.WriteMask = 0; - dst.Indirect = 0; - dst.IndirectFile = TGSI_FILE_NULL; - dst.IndirectIndex = 0; - dst.IndirectSwizzle = 0; - dst.Saturate = 0; - dst.Index = 0; - dst.ArrayID = 0; - - return dst; -} - -static inline struct ureg_src ureg_src_undef(void) { - struct ureg_src src; - - src.File = TGSI_FILE_NULL; - src.SwizzleX = 0; - src.SwizzleY = 0; - src.SwizzleZ = 0; - src.SwizzleW = 0; - src.Indirect = 0; - src.IndirectFile = TGSI_FILE_NULL; - src.IndirectIndex = 0; - src.IndirectSwizzle = 0; - src.Absolute = 0; - src.Index = 0; - src.Negate = 0; - src.Dimension = 0; - src.DimensionIndex = 0; - src.DimIndirect = 0; - src.DimIndFile = TGSI_FILE_NULL; - src.DimIndIndex = 0; - src.DimIndSwizzle = 0; - src.ArrayID = 0; - - return src; -} - -static inline boolean ureg_src_is_undef(struct ureg_src src) { - return src.File == TGSI_FILE_NULL; -} - -static inline boolean ureg_dst_is_undef(struct ureg_dst dst) { - return dst.File == TGSI_FILE_NULL; -} - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_util.c b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_util.c deleted file mode 100644 index 5fa0afded..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_util.c +++ /dev/null @@ -1,425 +0,0 @@ -/************************************************************************** - * - * Copyright 2007 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -#include "tgsi_util.h" -#include "pipe/p_shader_tokens.h" -#include "tgsi_parse.h" -#include "util/u_debug.h" - -union pointer_hack { - void *pointer; - uint64_t uint64; -}; - -void *tgsi_align_128bit(void *unaligned) { - union pointer_hack ph; - - ph.uint64 = 0; - ph.pointer = unaligned; - ph.uint64 = (ph.uint64 + 15) & ~15; - return ph.pointer; -} - -unsigned tgsi_util_get_src_register_swizzle(const struct tgsi_src_register *reg, - unsigned component) { - switch (component) { - case 0: - return reg->SwizzleX; - case 1: - return reg->SwizzleY; - case 2: - return reg->SwizzleZ; - case 3: - return reg->SwizzleW; - default: - assert(0); - } - return 0; -} - -unsigned tgsi_util_get_full_src_register_swizzle( - const struct tgsi_full_src_register *reg, unsigned component) { - return tgsi_util_get_src_register_swizzle(®->Register, component); -} - -void tgsi_util_set_src_register_swizzle(struct tgsi_src_register *reg, - unsigned swizzle, unsigned component) { - switch (component) { - case 0: - reg->SwizzleX = swizzle; - break; - case 1: - reg->SwizzleY = swizzle; - break; - case 2: - reg->SwizzleZ = swizzle; - break; - case 3: - reg->SwizzleW = swizzle; - break; - default: - assert(0); - } -} - -unsigned tgsi_util_get_full_src_register_sign_mode( - const struct tgsi_full_src_register *reg, UNUSED unsigned component) { - unsigned sign_mode; - - if (reg->Register.Absolute) { - /* Consider only the post-abs negation. */ - - if (reg->Register.Negate) { - sign_mode = TGSI_UTIL_SIGN_SET; - } else { - sign_mode = TGSI_UTIL_SIGN_CLEAR; - } - } else { - if (reg->Register.Negate) { - sign_mode = TGSI_UTIL_SIGN_TOGGLE; - } else { - sign_mode = TGSI_UTIL_SIGN_KEEP; - } - } - - return sign_mode; -} - -void tgsi_util_set_full_src_register_sign_mode( - struct tgsi_full_src_register *reg, unsigned sign_mode) { - switch (sign_mode) { - case TGSI_UTIL_SIGN_CLEAR: - reg->Register.Negate = 0; - reg->Register.Absolute = 1; - break; - - case TGSI_UTIL_SIGN_SET: - reg->Register.Absolute = 1; - reg->Register.Negate = 1; - break; - - case TGSI_UTIL_SIGN_TOGGLE: - reg->Register.Negate = 1; - reg->Register.Absolute = 0; - break; - - case TGSI_UTIL_SIGN_KEEP: - reg->Register.Negate = 0; - reg->Register.Absolute = 0; - break; - - default: - assert(0); - } -} - -/** - * Determine which channels of the specificed src register are effectively - * used by this instruction. - */ -unsigned tgsi_util_get_inst_usage_mask(const struct tgsi_full_instruction *inst, - unsigned src_idx) { - const struct tgsi_full_src_register *src = &inst->Src[src_idx]; - unsigned write_mask = inst->Dst[0].Register.WriteMask; - unsigned read_mask; - unsigned usage_mask; - unsigned chan; - - switch (inst->Instruction.Opcode) { - case TGSI_OPCODE_MOV: - case TGSI_OPCODE_ARL: - case TGSI_OPCODE_ARR: - case TGSI_OPCODE_RCP: - case TGSI_OPCODE_MUL: - case TGSI_OPCODE_DIV: - case TGSI_OPCODE_ADD: - case TGSI_OPCODE_MIN: - case TGSI_OPCODE_MAX: - case TGSI_OPCODE_SLT: - case TGSI_OPCODE_SGE: - case TGSI_OPCODE_MAD: - case TGSI_OPCODE_SUB: - case TGSI_OPCODE_LRP: - case TGSI_OPCODE_FRC: - case TGSI_OPCODE_CEIL: - case TGSI_OPCODE_FLR: - case TGSI_OPCODE_ROUND: - case TGSI_OPCODE_POW: - case TGSI_OPCODE_ABS: - case TGSI_OPCODE_COS: - case TGSI_OPCODE_SIN: - case TGSI_OPCODE_DDX: - case TGSI_OPCODE_DDY: - case TGSI_OPCODE_SEQ: - case TGSI_OPCODE_SGT: - case TGSI_OPCODE_SLE: - case TGSI_OPCODE_SNE: - case TGSI_OPCODE_SSG: - case TGSI_OPCODE_CMP: - case TGSI_OPCODE_TRUNC: - case TGSI_OPCODE_NOT: - case TGSI_OPCODE_AND: - case TGSI_OPCODE_OR: - case TGSI_OPCODE_XOR: - case TGSI_OPCODE_FSEQ: - case TGSI_OPCODE_FSGE: - case TGSI_OPCODE_FSLT: - case TGSI_OPCODE_FSNE: - case TGSI_OPCODE_F2I: - case TGSI_OPCODE_IDIV: - case TGSI_OPCODE_IMAX: - case TGSI_OPCODE_IMIN: - case TGSI_OPCODE_INEG: - case TGSI_OPCODE_ISGE: - case TGSI_OPCODE_ISHR: - case TGSI_OPCODE_ISLT: - case TGSI_OPCODE_F2U: - case TGSI_OPCODE_U2F: - case TGSI_OPCODE_UADD: - case TGSI_OPCODE_UDIV: - case TGSI_OPCODE_UMAD: - case TGSI_OPCODE_UMAX: - case TGSI_OPCODE_UMIN: - case TGSI_OPCODE_UMOD: - case TGSI_OPCODE_UMUL: - case TGSI_OPCODE_USEQ: - case TGSI_OPCODE_USGE: - case TGSI_OPCODE_USHR: - case TGSI_OPCODE_USLT: - case TGSI_OPCODE_USNE: - case TGSI_OPCODE_IMUL_HI: - case TGSI_OPCODE_UMUL_HI: - case TGSI_OPCODE_DDX_FINE: - case TGSI_OPCODE_DDY_FINE: - /* Channel-wise operations */ - read_mask = write_mask; - break; - - case TGSI_OPCODE_EX2: - case TGSI_OPCODE_LG2: - read_mask = TGSI_WRITEMASK_X; - break; - - case TGSI_OPCODE_SCS: - read_mask = write_mask & TGSI_WRITEMASK_XY ? TGSI_WRITEMASK_X : 0; - break; - - case TGSI_OPCODE_EXP: - case TGSI_OPCODE_LOG: - read_mask = write_mask & TGSI_WRITEMASK_XYZ ? TGSI_WRITEMASK_X : 0; - break; - - case TGSI_OPCODE_DP2: - read_mask = TGSI_WRITEMASK_XY; - break; - - case TGSI_OPCODE_DP3: - read_mask = TGSI_WRITEMASK_XYZ; - break; - - case TGSI_OPCODE_DP4: - read_mask = TGSI_WRITEMASK_XYZW; - break; - - case TGSI_OPCODE_DPH: - read_mask = src_idx == 0 ? TGSI_WRITEMASK_XYZ : TGSI_WRITEMASK_XYZW; - break; - - case TGSI_OPCODE_TEX: - case TGSI_OPCODE_TXD: - case TGSI_OPCODE_TXB: - case TGSI_OPCODE_TXL: - case TGSI_OPCODE_TXP: - if (src_idx == 0) { - /* Note that the SHADOW variants use the Z component too */ - switch (inst->Texture.Texture) { - case TGSI_TEXTURE_1D: - read_mask = TGSI_WRITEMASK_X; - break; - case TGSI_TEXTURE_SHADOW1D: - read_mask = TGSI_WRITEMASK_XZ; - break; - case TGSI_TEXTURE_1D_ARRAY: - case TGSI_TEXTURE_2D: - case TGSI_TEXTURE_RECT: - read_mask = TGSI_WRITEMASK_XY; - break; - case TGSI_TEXTURE_SHADOW1D_ARRAY: - case TGSI_TEXTURE_SHADOW2D: - case TGSI_TEXTURE_SHADOWRECT: - case TGSI_TEXTURE_2D_ARRAY: - case TGSI_TEXTURE_3D: - case TGSI_TEXTURE_CUBE: - case TGSI_TEXTURE_2D_MSAA: - read_mask = TGSI_WRITEMASK_XYZ; - break; - case TGSI_TEXTURE_SHADOW2D_ARRAY: - case TGSI_TEXTURE_CUBE_ARRAY: - case TGSI_TEXTURE_SHADOWCUBE: - case TGSI_TEXTURE_2D_ARRAY_MSAA: - case TGSI_TEXTURE_SHADOWCUBE_ARRAY: - read_mask = TGSI_WRITEMASK_XYZW; - break; - default: - assert(0); - read_mask = 0; - } - - if (inst->Instruction.Opcode != TGSI_OPCODE_TEX) { - read_mask |= TGSI_WRITEMASK_W; - } - } else { - /* A safe approximation */ - read_mask = TGSI_WRITEMASK_XYZW; - } - break; - - default: - /* Assume all channels are read */ - read_mask = TGSI_WRITEMASK_XYZW; - break; - } - - usage_mask = 0; - for (chan = 0; chan < 4; ++chan) { - if (read_mask & (1 << chan)) { - usage_mask |= 1 << tgsi_util_get_full_src_register_swizzle(src, chan); - } - } - - return usage_mask; -} - -/** - * Convert a tgsi_ind_register into a tgsi_src_register - */ -struct tgsi_src_register -tgsi_util_get_src_from_ind(const struct tgsi_ind_register *reg) { - struct tgsi_src_register src = {0}; - - src.File = reg->File; - src.Index = reg->Index; - src.SwizzleX = reg->Swizzle; - src.SwizzleY = reg->Swizzle; - src.SwizzleZ = reg->Swizzle; - src.SwizzleW = reg->Swizzle; - - return src; -} - -/** - * Return the dimension of the texture coordinates (layer included for array - * textures), as well as the location of the shadow reference value or the - * sample index. - */ -int tgsi_util_get_texture_coord_dim(int tgsi_tex, int *shadow_or_sample) { - int dim; - - /* - * Depending on the texture target, (src0.xyzw, src1.x) is interpreted - * differently: - * - * (s, X, X, X, X), for BUFFER - * (s, X, X, X, X), for 1D - * (s, t, X, X, X), for 2D, RECT - * (s, t, r, X, X), for 3D, CUBE - * - * (s, layer, X, X, X), for 1D_ARRAY - * (s, t, layer, X, X), for 2D_ARRAY - * (s, t, r, layer, X), for CUBE_ARRAY - * - * (s, X, shadow, X, X), for SHADOW1D - * (s, t, shadow, X, X), for SHADOW2D, SHADOWRECT - * (s, t, r, shadow, X), for SHADOWCUBE - * - * (s, layer, shadow, X, X), for SHADOW1D_ARRAY - * (s, t, layer, shadow, X), for SHADOW2D_ARRAY - * (s, t, r, layer, shadow), for SHADOWCUBE_ARRAY - * - * (s, t, sample, X, X), for 2D_MSAA - * (s, t, layer, sample, X), for 2D_ARRAY_MSAA - */ - switch (tgsi_tex) { - case TGSI_TEXTURE_BUFFER: - case TGSI_TEXTURE_1D: - case TGSI_TEXTURE_SHADOW1D: - dim = 1; - break; - case TGSI_TEXTURE_2D: - case TGSI_TEXTURE_RECT: - case TGSI_TEXTURE_1D_ARRAY: - case TGSI_TEXTURE_SHADOW2D: - case TGSI_TEXTURE_SHADOWRECT: - case TGSI_TEXTURE_SHADOW1D_ARRAY: - case TGSI_TEXTURE_2D_MSAA: - dim = 2; - break; - case TGSI_TEXTURE_3D: - case TGSI_TEXTURE_CUBE: - case TGSI_TEXTURE_2D_ARRAY: - case TGSI_TEXTURE_SHADOWCUBE: - case TGSI_TEXTURE_SHADOW2D_ARRAY: - case TGSI_TEXTURE_2D_ARRAY_MSAA: - dim = 3; - break; - case TGSI_TEXTURE_CUBE_ARRAY: - case TGSI_TEXTURE_SHADOWCUBE_ARRAY: - dim = 4; - break; - default: - assert(!"unknown texture target"); - dim = 0; - break; - } - - if (shadow_or_sample) { - switch (tgsi_tex) { - case TGSI_TEXTURE_SHADOW1D: - /* there is a gap */ - *shadow_or_sample = 2; - break; - case TGSI_TEXTURE_SHADOW2D: - case TGSI_TEXTURE_SHADOWRECT: - case TGSI_TEXTURE_SHADOWCUBE: - case TGSI_TEXTURE_SHADOW1D_ARRAY: - case TGSI_TEXTURE_SHADOW2D_ARRAY: - case TGSI_TEXTURE_SHADOWCUBE_ARRAY: - *shadow_or_sample = dim; - break; - case TGSI_TEXTURE_2D_MSAA: - case TGSI_TEXTURE_2D_ARRAY_MSAA: - *shadow_or_sample = 3; - break; - default: - /* no shadow nor sample */ - *shadow_or_sample = -1; - break; - } - } - - return dim; -} diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_util.h b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_util.h deleted file mode 100644 index e8d41f27d..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/tgsi/tgsi_util.h +++ /dev/null @@ -1,73 +0,0 @@ -/************************************************************************** - * - * Copyright 2007 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -#ifndef TGSI_UTIL_H -#define TGSI_UTIL_H - -#if defined __cplusplus -extern "C" { -#endif - -struct tgsi_src_register; -struct tgsi_full_src_register; -struct tgsi_full_instruction; - -void *tgsi_align_128bit(void *unaligned); - -unsigned tgsi_util_get_src_register_swizzle(const struct tgsi_src_register *reg, - unsigned component); - -unsigned tgsi_util_get_full_src_register_swizzle( - const struct tgsi_full_src_register *reg, unsigned component); - -void tgsi_util_set_src_register_swizzle(struct tgsi_src_register *reg, - unsigned swizzle, unsigned component); - -#define TGSI_UTIL_SIGN_CLEAR 0 /* Force positive */ -#define TGSI_UTIL_SIGN_SET 1 /* Force negative */ -#define TGSI_UTIL_SIGN_TOGGLE 2 /* Negate */ -#define TGSI_UTIL_SIGN_KEEP 3 /* No change */ - -unsigned tgsi_util_get_full_src_register_sign_mode( - const struct tgsi_full_src_register *reg, unsigned component); - -void tgsi_util_set_full_src_register_sign_mode( - struct tgsi_full_src_register *reg, unsigned sign_mode); - -unsigned tgsi_util_get_inst_usage_mask(const struct tgsi_full_instruction *inst, - unsigned src_idx); - -struct tgsi_src_register -tgsi_util_get_src_from_ind(const struct tgsi_ind_register *reg); - -int tgsi_util_get_texture_coord_dim(int tgsi_tex, int *shadow_or_sample); - -#if defined __cplusplus -} -#endif - -#endif /* TGSI_UTIL_H */ diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/rgtc.c b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/rgtc.c deleted file mode 100644 index ec378dcf6..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/rgtc.c +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2011 Red Hat Inc. - * - * block compression parts are: - * Copyright (C) 2004 Roland Scheidegger All Rights Reserved. - * - * 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 (including the next - * paragraph) 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. - * - * Author: - * Dave Airlie - */ - -#include "macros.h" -#include - -#include "rgtc.h" - -#define RGTC_DEBUG 0 - -#define TAG(x) util_format_unsigned_##x - -#define TYPE unsigned char -#define T_MIN 0 -#define T_MAX 0xff - -#include "texcompress_rgtc_tmp.h" - -#undef TAG -#undef TYPE -#undef T_MIN -#undef T_MAX - -#define TAG(x) util_format_signed_##x -#define TYPE signed char -#define T_MIN (signed char)-128 -#define T_MAX (signed char)127 - -#include "texcompress_rgtc_tmp.h" - -#undef TAG -#undef TYPE -#undef T_MIN -#undef T_MAX diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/rgtc.h b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/rgtc.h deleted file mode 100644 index ac06ff241..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/rgtc.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright © 2014 Red Hat - * - * 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 (including the next - * paragraph) 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. - * - */ - -#ifndef _RGTC_H -#define _RGTC_H - -void util_format_unsigned_fetch_texel_rgtc(unsigned srcRowStride, - const unsigned char *pixdata, - unsigned i, unsigned j, - unsigned char *value, - unsigned comps); - -void util_format_signed_fetch_texel_rgtc(unsigned srcRowStride, - const signed char *pixdata, unsigned i, - unsigned j, signed char *value, - unsigned comps); - -void util_format_unsigned_encode_rgtc_ubyte(unsigned char *blkaddr, - unsigned char srccolors[4][4], - int numxpixels, int numypixels); - -void util_format_signed_encode_rgtc_ubyte(signed char *blkaddr, - signed char srccolors[4][4], - int numxpixels, int numypixels); -#endif /* _RGTC_H */ diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_atomic.h b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_atomic.h deleted file mode 100644 index b3c07efd2..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_atomic.h +++ /dev/null @@ -1,294 +0,0 @@ -/** - * Many similar implementations exist. See for example libwsbm - * or the linux kernel include/atomic.h - * - * No copyright claimed on this file. - * - */ - -#ifndef U_ATOMIC_H -#define U_ATOMIC_H - -#include "pipe/p_compiler.h" -#include "pipe/p_defines.h" - -/* Favor OS-provided implementations. - * - * Where no OS-provided implementation is available, fall back to - * locally coded assembly, compiler intrinsic or ultimately a - * mutex-based implementation. - */ -#if defined(PIPE_OS_SOLARIS) -#define PIPE_ATOMIC_OS_SOLARIS -#elif defined(PIPE_CC_MSVC) -#define PIPE_ATOMIC_MSVC_INTRINSIC -#elif (defined(PIPE_CC_MSVC) && defined(PIPE_ARCH_X86)) -#define PIPE_ATOMIC_ASM_MSVC_X86 -#elif (defined(PIPE_CC_GCC) && defined(PIPE_ARCH_X86)) -#define PIPE_ATOMIC_ASM_GCC_X86 -#elif (defined(PIPE_CC_GCC) && defined(PIPE_ARCH_X86_64)) -#define PIPE_ATOMIC_ASM_GCC_X86_64 -#elif defined(PIPE_CC_GCC) && (PIPE_CC_GCC_VERSION >= 401) -#define PIPE_ATOMIC_GCC_INTRINSIC -#else -#error "Unsupported platform" -#endif - -#if defined(PIPE_ATOMIC_ASM_GCC_X86_64) -#define PIPE_ATOMIC "GCC x86_64 assembly" - -#ifdef __cplusplus -extern "C" { -#endif - -#define p_atomic_set(_v, _i) (*(_v) = (_i)) -#define p_atomic_read(_v) (*(_v)) - -static inline boolean p_atomic_dec_zero(int32_t *v) { - unsigned char c; - - __asm__ __volatile__("lock; decl %0; sete %1" : "+m"(*v), "=qm"(c)::"memory"); - - return c != 0; -} - -static inline void p_atomic_inc(int32_t *v) { - __asm__ __volatile__("lock; incl %0" : "+m"(*v)); -} - -static inline void p_atomic_dec(int32_t *v) { - __asm__ __volatile__("lock; decl %0" : "+m"(*v)); -} - -static inline int32_t p_atomic_cmpxchg(int32_t *v, int32_t old, int32_t _new) { - return __sync_val_compare_and_swap(v, old, _new); -} - -#ifdef __cplusplus -} -#endif - -#endif /* PIPE_ATOMIC_ASM_GCC_X86_64 */ - -#if defined(PIPE_ATOMIC_ASM_GCC_X86) - -#define PIPE_ATOMIC "GCC x86 assembly" - -#ifdef __cplusplus -extern "C" { -#endif - -#define p_atomic_set(_v, _i) (*(_v) = (_i)) -#define p_atomic_read(_v) (*(_v)) - -static inline boolean p_atomic_dec_zero(int32_t *v) { - unsigned char c; - - __asm__ __volatile__("lock; decl %0; sete %1" : "+m"(*v), "=qm"(c)::"memory"); - - return c != 0; -} - -static inline void p_atomic_inc(int32_t *v) { - __asm__ __volatile__("lock; incl %0" : "+m"(*v)); -} - -static inline void p_atomic_dec(int32_t *v) { - __asm__ __volatile__("lock; decl %0" : "+m"(*v)); -} - -static inline int32_t p_atomic_cmpxchg(int32_t *v, int32_t old, int32_t _new) { - return __sync_val_compare_and_swap(v, old, _new); -} - -#ifdef __cplusplus -} -#endif - -#endif - -/* Implementation using GCC-provided synchronization intrinsics - */ -#if defined(PIPE_ATOMIC_GCC_INTRINSIC) - -#define PIPE_ATOMIC "GCC Sync Intrinsics" - -#ifdef __cplusplus -extern "C" { -#endif - -#define p_atomic_set(_v, _i) (*(_v) = (_i)) -#define p_atomic_read(_v) (*(_v)) - -static inline boolean p_atomic_dec_zero(int32_t *v) { - return (__sync_sub_and_fetch(v, 1) == 0); -} - -static inline void p_atomic_inc(int32_t *v) { - (void)__sync_add_and_fetch(v, 1); -} - -static inline void p_atomic_dec(int32_t *v) { - (void)__sync_sub_and_fetch(v, 1); -} - -static inline int32_t p_atomic_cmpxchg(int32_t *v, int32_t old, int32_t _new) { - return __sync_val_compare_and_swap(v, old, _new); -} - -#ifdef __cplusplus -} -#endif - -#endif - -/* Unlocked version for single threaded environments, such as some - * windows kernel modules. - */ -#if defined(PIPE_ATOMIC_OS_UNLOCKED) - -#define PIPE_ATOMIC "Unlocked" - -#define p_atomic_set(_v, _i) (*(_v) = (_i)) -#define p_atomic_read(_v) (*(_v)) -#define p_atomic_dec_zero(_v) ((boolean)--(*(_v))) -#define p_atomic_inc(_v) ((void)(*(_v))++) -#define p_atomic_dec(_v) ((void)(*(_v))--) -#define p_atomic_cmpxchg(_v, old, _new) (*(_v) == old ? *(_v) = (_new) : *(_v)) - -#endif - -/* Locally coded assembly for MSVC on x86: - */ -#if defined(PIPE_ATOMIC_ASM_MSVC_X86) - -#define PIPE_ATOMIC "MSVC x86 assembly" - -#ifdef __cplusplus -extern "C" { -#endif - -#define p_atomic_set(_v, _i) (*(_v) = (_i)) -#define p_atomic_read(_v) (*(_v)) - -static inline boolean p_atomic_dec_zero(int32_t *v) { - unsigned char c; - - __asm { - mov eax, [v] - lock dec dword ptr [eax] - sete byte ptr [c] - } - - return c != 0; -} - -static inline void p_atomic_inc(int32_t *v) { - __asm { - mov eax, [v] - lock inc dword ptr [eax] - } -} - -static inline void p_atomic_dec(int32_t *v) { - __asm { - mov eax, [v] - lock dec dword ptr [eax] - } -} - -static inline int32_t p_atomic_cmpxchg(int32_t *v, int32_t old, int32_t _new) { - int32_t orig; - - __asm { - mov ecx, [v] - mov eax, [old] - mov edx, [_new] - lock cmpxchg [ecx], edx - mov [orig], eax - } - - return orig; -} - -#ifdef __cplusplus -} -#endif - -#endif - -#if defined(PIPE_ATOMIC_MSVC_INTRINSIC) - -#define PIPE_ATOMIC "MSVC Intrinsics" - -#include - -#pragma intrinsic(_InterlockedIncrement) -#pragma intrinsic(_InterlockedDecrement) -#pragma intrinsic(_InterlockedCompareExchange) - -#ifdef __cplusplus -extern "C" { -#endif - -#define p_atomic_set(_v, _i) (*(_v) = (_i)) -#define p_atomic_read(_v) (*(_v)) - -static inline boolean p_atomic_dec_zero(int32_t *v) { - return _InterlockedDecrement((long *)v) == 0; -} - -static inline void p_atomic_inc(int32_t *v) { - _InterlockedIncrement((long *)v); -} - -static inline void p_atomic_dec(int32_t *v) { - _InterlockedDecrement((long *)v); -} - -static inline int32_t p_atomic_cmpxchg(int32_t *v, int32_t old, int32_t _new) { - return _InterlockedCompareExchange((long *)v, _new, old); -} - -#ifdef __cplusplus -} -#endif - -#endif - -#if defined(PIPE_ATOMIC_OS_SOLARIS) - -#define PIPE_ATOMIC "Solaris OS atomic functions" - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#define p_atomic_set(_v, _i) (*(_v) = (_i)) -#define p_atomic_read(_v) (*(_v)) - -static inline boolean p_atomic_dec_zero(int32_t *v) { - uint32_t n = atomic_dec_32_nv((uint32_t *)v); - - return n != 0; -} - -#define p_atomic_inc(_v) atomic_inc_32((uint32_t *)_v) -#define p_atomic_dec(_v) atomic_dec_32((uint32_t *)_v) - -#define p_atomic_cmpxchg(_v, _old, _new) \ - atomic_cas_32((uint32_t *)_v, (uint32_t)_old, (uint32_t)_new) - -#ifdef __cplusplus -} -#endif - -#endif - -#ifndef PIPE_ATOMIC -#error "No pipe_atomic implementation selected" -#endif - -#endif /* U_ATOMIC_H */ diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_bitmask.c b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_bitmask.c deleted file mode 100644 index d145b2ffa..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_bitmask.c +++ /dev/null @@ -1,286 +0,0 @@ -/************************************************************************** - * - * Copyright 2009 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -/** - * @file - * Generic bitmask implementation. - * - * @author Jose Fonseca - */ - -#include "pipe/p_compiler.h" -#include "util/u_debug.h" - -#include "util/u_bitmask.h" -#include "util/u_memory.h" - -typedef uint32_t util_bitmask_word; - -#define UTIL_BITMASK_INITIAL_WORDS 16 -#define UTIL_BITMASK_BITS_PER_BYTE 8 -#define UTIL_BITMASK_BITS_PER_WORD \ - (sizeof(util_bitmask_word) * UTIL_BITMASK_BITS_PER_BYTE) - -struct util_bitmask { - util_bitmask_word *words; - - /** Number of bits we can currently hold */ - unsigned size; - - /** Number of consecutive bits set at the start of the bitmask */ - unsigned filled; -}; - -struct util_bitmask *util_bitmask_create(void) { - struct util_bitmask *bm; - - bm = MALLOC_STRUCT(util_bitmask); - if (!bm) - return NULL; - - bm->words = (util_bitmask_word *)CALLOC(UTIL_BITMASK_INITIAL_WORDS, - sizeof(util_bitmask_word)); - if (!bm->words) { - FREE(bm); - return NULL; - } - - bm->size = UTIL_BITMASK_INITIAL_WORDS * UTIL_BITMASK_BITS_PER_WORD; - bm->filled = 0; - - return bm; -} - -/** - * Resize the bitmask if necessary - */ -static inline boolean util_bitmask_resize(struct util_bitmask *bm, - unsigned minimum_index) { - unsigned minimum_size = minimum_index + 1; - unsigned new_size; - util_bitmask_word *new_words; - - /* Check integer overflow */ - if (!minimum_size) - return FALSE; - - if (bm->size >= minimum_size) - return TRUE; - - assert(bm->size % UTIL_BITMASK_BITS_PER_WORD == 0); - new_size = bm->size; - while (new_size < minimum_size) { - new_size *= 2; - /* Check integer overflow */ - if (new_size < bm->size) - return FALSE; - } - assert(new_size); - assert(new_size % UTIL_BITMASK_BITS_PER_WORD == 0); - - new_words = (util_bitmask_word *)REALLOC( - (void *)bm->words, bm->size / UTIL_BITMASK_BITS_PER_BYTE, - new_size / UTIL_BITMASK_BITS_PER_BYTE); - if (!new_words) - return FALSE; - - memset(new_words + bm->size / UTIL_BITMASK_BITS_PER_WORD, 0, - (new_size - bm->size) / UTIL_BITMASK_BITS_PER_BYTE); - - bm->size = new_size; - bm->words = new_words; - - return TRUE; -} - -/** - * Lazily update the filled. - */ -static inline void util_bitmask_filled_set(struct util_bitmask *bm, - unsigned index) { - assert(bm->filled <= bm->size); - assert(index < bm->size); - - if (index == bm->filled) { - ++bm->filled; - assert(bm->filled <= bm->size); - } -} - -static inline void util_bitmask_filled_unset(struct util_bitmask *bm, - unsigned index) { - assert(bm->filled <= bm->size); - assert(index < bm->size); - - if (index < bm->filled) - bm->filled = index; -} - -unsigned util_bitmask_add(struct util_bitmask *bm) { - unsigned word; - unsigned bit; - util_bitmask_word mask; - - assert(bm); - - /* linear search for an empty index */ - word = bm->filled / UTIL_BITMASK_BITS_PER_WORD; - bit = bm->filled % UTIL_BITMASK_BITS_PER_WORD; - mask = 1 << bit; - while (word < bm->size / UTIL_BITMASK_BITS_PER_WORD) { - while (bit < UTIL_BITMASK_BITS_PER_WORD) { - if (!(bm->words[word] & mask)) - goto found; - ++bm->filled; - ++bit; - mask <<= 1; - } - ++word; - bit = 0; - mask = 1; - } -found: - - /* grow the bitmask if necessary */ - if (!util_bitmask_resize(bm, bm->filled)) - return UTIL_BITMASK_INVALID_INDEX; - - assert(!(bm->words[word] & mask)); - bm->words[word] |= mask; - - return bm->filled++; -} - -unsigned util_bitmask_set(struct util_bitmask *bm, unsigned index) { - unsigned word; - unsigned bit; - util_bitmask_word mask; - - assert(bm); - - /* grow the bitmask if necessary */ - if (!util_bitmask_resize(bm, index)) - return UTIL_BITMASK_INVALID_INDEX; - - word = index / UTIL_BITMASK_BITS_PER_WORD; - bit = index % UTIL_BITMASK_BITS_PER_WORD; - mask = 1 << bit; - - bm->words[word] |= mask; - - util_bitmask_filled_set(bm, index); - - return index; -} - -void util_bitmask_clear(struct util_bitmask *bm, unsigned index) { - unsigned word; - unsigned bit; - util_bitmask_word mask; - - assert(bm); - - if (index >= bm->size) - return; - - word = index / UTIL_BITMASK_BITS_PER_WORD; - bit = index % UTIL_BITMASK_BITS_PER_WORD; - mask = 1 << bit; - - bm->words[word] &= ~mask; - - util_bitmask_filled_unset(bm, index); -} - -boolean util_bitmask_get(struct util_bitmask *bm, unsigned index) { - unsigned word = index / UTIL_BITMASK_BITS_PER_WORD; - unsigned bit = index % UTIL_BITMASK_BITS_PER_WORD; - util_bitmask_word mask = 1 << bit; - - assert(bm); - - if (index < bm->filled) { - assert(bm->words[word] & mask); - return TRUE; - } - - if (index >= bm->size) - return FALSE; - - if (bm->words[word] & mask) { - util_bitmask_filled_set(bm, index); - return TRUE; - } else - return FALSE; -} - -unsigned util_bitmask_get_next_index(struct util_bitmask *bm, unsigned index) { - unsigned word = index / UTIL_BITMASK_BITS_PER_WORD; - unsigned bit = index % UTIL_BITMASK_BITS_PER_WORD; - util_bitmask_word mask = 1 << bit; - - if (index < bm->filled) { - assert(bm->words[word] & mask); - return index; - } - - if (index >= bm->size) { - return UTIL_BITMASK_INVALID_INDEX; - } - - /* Do a linear search */ - while (word < bm->size / UTIL_BITMASK_BITS_PER_WORD) { - while (bit < UTIL_BITMASK_BITS_PER_WORD) { - if (bm->words[word] & mask) { - if (index == bm->filled) { - ++bm->filled; - assert(bm->filled <= bm->size); - } - return index; - } - ++index; - ++bit; - mask <<= 1; - } - ++word; - bit = 0; - mask = 1; - } - - return UTIL_BITMASK_INVALID_INDEX; -} - -unsigned util_bitmask_get_first_index(struct util_bitmask *bm) { - return util_bitmask_get_next_index(bm, 0); -} - -void util_bitmask_destroy(struct util_bitmask *bm) { - assert(bm); - - FREE(bm->words); - FREE(bm); -} diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_bitmask.h b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_bitmask.h deleted file mode 100644 index 84f988c15..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_bitmask.h +++ /dev/null @@ -1,95 +0,0 @@ -/************************************************************************** - * - * Copyright 2009 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -/** - * @file - * Generic bitmask. - * - * @author Jose Fonseca - */ - -#ifndef U_HANDLE_BITMASK_H_ -#define U_HANDLE_BITMASK_H_ - -#include "pipe/p_compiler.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#define UTIL_BITMASK_INVALID_INDEX (~0U) - -/** - * Abstract data type to represent arbitrary set of bits. - */ -struct util_bitmask; - -struct util_bitmask *util_bitmask_create(void); - -/** - * Search a cleared bit and set it. - * - * It searches for the first cleared bit. - * - * Returns the bit index on success, or UTIL_BITMASK_INVALID_INDEX on out of - * memory growing the bitmask. - */ -unsigned util_bitmask_add(struct util_bitmask *bm); - -/** - * Set a bit. - * - * Returns the input index on success, or UTIL_BITMASK_INVALID_INDEX on out of - * memory growing the bitmask. - */ -unsigned util_bitmask_set(struct util_bitmask *bm, unsigned index); - -void util_bitmask_clear(struct util_bitmask *bm, unsigned index); - -boolean util_bitmask_get(struct util_bitmask *bm, unsigned index); - -void util_bitmask_destroy(struct util_bitmask *bm); - -/** - * Search for the first set bit. - * - * Returns UTIL_BITMASK_INVALID_INDEX if a set bit cannot be found. - */ -unsigned util_bitmask_get_first_index(struct util_bitmask *bm); - -/** - * Search for the first set bit, starting from the giving index. - * - * Returns UTIL_BITMASK_INVALID_INDEX if a set bit cannot be found. - */ -unsigned util_bitmask_get_next_index(struct util_bitmask *bm, unsigned index); - -#ifdef __cplusplus -} -#endif - -#endif /* U_HANDLE_BITMASK_H_ */ diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_box.h b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_box.h deleted file mode 100644 index f3970b4b2..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_box.h +++ /dev/null @@ -1,56 +0,0 @@ -#ifndef UTIL_BOX_INLINES_H -#define UTIL_BOX_INLINES_H - -#include "pipe/p_state.h" - -static inline void u_box_1d(unsigned x, unsigned w, struct pipe_box *box) { - box->x = x; - box->y = 0; - box->z = 0; - box->width = w; - box->height = 1; - box->depth = 1; -} - -static inline void u_box_2d(unsigned x, unsigned y, unsigned w, unsigned h, - struct pipe_box *box) { - box->x = x; - box->y = y; - box->z = 0; - box->width = w; - box->height = h; - box->depth = 1; -} - -static inline void u_box_origin_2d(unsigned w, unsigned h, - struct pipe_box *box) { - box->x = 0; - box->y = 0; - box->z = 0; - box->width = w; - box->height = h; - box->depth = 1; -} - -static inline void u_box_2d_zslice(unsigned x, unsigned y, unsigned z, - unsigned w, unsigned h, - struct pipe_box *box) { - box->x = x; - box->y = y; - box->z = z; - box->width = w; - box->height = h; - box->depth = 1; -} - -static inline void u_box_3d(unsigned x, unsigned y, unsigned z, unsigned w, - unsigned h, unsigned d, struct pipe_box *box) { - box->x = x; - box->y = y; - box->z = z; - box->width = w; - box->height = h; - box->depth = d; -} - -#endif diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_cpu_detect.c b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_cpu_detect.c deleted file mode 100644 index 848edea75..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_cpu_detect.c +++ /dev/null @@ -1,421 +0,0 @@ -/************************************************************************** - * - * Copyright 2008 Dennis Smit - * All Rights Reserved. - * - * 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 - * on the rights to use, copy, modify, merge, publish, distribute, sub - * license, 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 (including the next - * paragraph) 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 NON-INFRINGEMENT. IN NO EVENT SHALL - * AUTHORS, COPYRIGHT HOLDERS, AND/OR THEIR SUPPLIERS 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. - * - **************************************************************************/ - -/** - * @file - * CPU feature detection. - * - * @author Dennis Smit - * @author Based on the work of Eric Anholt - */ - -#include "pipe/p_config.h" - -#include "u_cpu_detect.h" -#include "u_debug.h" - -#if defined(PIPE_ARCH_PPC) -#if defined(PIPE_OS_APPLE) -#include -#else -#include -#include -#endif -#endif - -#if defined(PIPE_OS_NETBSD) || defined(PIPE_OS_OPENBSD) -#include -#include -#include -#endif - -#if defined(PIPE_OS_FREEBSD) -#include -#include -#endif - -#if defined(PIPE_OS_LINUX) -#include -#endif - -#ifdef PIPE_OS_UNIX -#include -#endif - -#if defined(PIPE_OS_WINDOWS) -#include -#if defined(PIPE_CC_MSVC) -#include -#endif -#endif - -#ifdef DEBUG -DEBUG_GET_ONCE_BOOL_OPTION(dump_cpu, "GALLIUM_DUMP_CPU", FALSE) -#endif - -struct util_cpu_caps util_cpu_caps; - -#if defined(PIPE_ARCH_X86) || defined(PIPE_ARCH_X86_64) -static int has_cpuid(void); -#endif - -#if defined(PIPE_ARCH_PPC) && !defined(PIPE_OS_APPLE) -static jmp_buf __lv_powerpc_jmpbuf; -static volatile sig_atomic_t __lv_powerpc_canjump = 0; - -static void sigill_handler(int sig) { - if (!__lv_powerpc_canjump) { - signal(sig, SIG_DFL); - raise(sig); - } - - __lv_powerpc_canjump = 0; - longjmp(__lv_powerpc_jmpbuf, 1); -} -#endif - -#if defined(PIPE_ARCH_PPC) -static void check_os_altivec_support(void) { -#if defined(PIPE_OS_APPLE) - int sels[2] = {CTL_HW, HW_VECTORUNIT}; - int has_vu = 0; - int len = sizeof(has_vu); - int err; - - err = sysctl(sels, 2, &has_vu, &len, NULL, 0); - - if (err == 0) { - if (has_vu != 0) { - util_cpu_caps.has_altivec = 1; - } - } -#else /* !PIPE_OS_APPLE */ - /* not on Apple/Darwin, do it the brute-force way */ - /* this is borrowed from the libmpeg2 library */ - signal(SIGILL, sigill_handler); - if (setjmp(__lv_powerpc_jmpbuf)) { - signal(SIGILL, SIG_DFL); - } else { - __lv_powerpc_canjump = 1; - - __asm __volatile("mtspr 256, %0\n\t" - "vand %%v0, %%v0, %%v0" - : - : "r"(-1)); - - signal(SIGILL, SIG_DFL); - util_cpu_caps.has_altivec = 1; - } -#endif /* !PIPE_OS_APPLE */ -} -#endif /* PIPE_ARCH_PPC */ - -#if defined(PIPE_ARCH_X86) || defined(PIPE_ARCH_X86_64) -static int has_cpuid(void) { -#if defined(PIPE_ARCH_X86) -#if defined(PIPE_OS_GCC) - int a, c; - - __asm __volatile("pushf\n" - "popl %0\n" - "movl %0, %1\n" - "xorl $0x200000, %0\n" - "push %0\n" - "popf\n" - "pushf\n" - "popl %0\n" - : "=a"(a), "=c"(c) - : - : "cc"); - - return a != c; -#else - /* FIXME */ - return 1; -#endif -#elif defined(PIPE_ARCH_X86_64) - return 1; -#else - return 0; -#endif -} - -/** - * @sa cpuid.h included in gcc-4.3 onwards. - * @sa http://msdn.microsoft.com/en-us/library/hskdteyh.aspx - */ -static inline void cpuid(uint32_t ax, uint32_t *p) { -#if (defined(PIPE_CC_GCC) || defined(PIPE_CC_SUNPRO)) && defined(PIPE_ARCH_X86) - __asm __volatile("xchgl %%ebx, %1\n\t" - "cpuid\n\t" - "xchgl %%ebx, %1" - : "=a"(p[0]), "=S"(p[1]), "=c"(p[2]), "=d"(p[3]) - : "0"(ax)); -#elif (defined(PIPE_CC_GCC) || defined(PIPE_CC_SUNPRO)) && \ - defined(PIPE_ARCH_X86_64) - __asm __volatile("cpuid\n\t" - : "=a"(p[0]), "=b"(p[1]), "=c"(p[2]), "=d"(p[3]) - : "0"(ax)); -#elif defined(PIPE_CC_MSVC) - __cpuid(p, ax); -#else - p[0] = 0; - p[1] = 0; - p[2] = 0; - p[3] = 0; -#endif -} - -/** - * @sa cpuid.h included in gcc-4.4 onwards. - * @sa http://msdn.microsoft.com/en-us/library/hskdteyh%28v=vs.90%29.aspx - */ -static inline void cpuid_count(uint32_t ax, uint32_t cx, uint32_t *p) { -#if (defined(PIPE_CC_GCC) || defined(PIPE_CC_SUNPRO)) && defined(PIPE_ARCH_X86) - __asm __volatile("xchgl %%ebx, %1\n\t" - "cpuid\n\t" - "xchgl %%ebx, %1" - : "=a"(p[0]), "=S"(p[1]), "=c"(p[2]), "=d"(p[3]) - : "0"(ax), "2"(cx)); -#elif (defined(PIPE_CC_GCC) || defined(PIPE_CC_SUNPRO)) && \ - defined(PIPE_ARCH_X86_64) - __asm __volatile("cpuid\n\t" - : "=a"(p[0]), "=b"(p[1]), "=c"(p[2]), "=d"(p[3]) - : "0"(ax), "2"(cx)); -#elif defined(PIPE_CC_MSVC) - __cpuidex(p, ax, cx); -#else - p[0] = 0; - p[1] = 0; - p[2] = 0; - p[3] = 0; -#endif -} - -static inline uint64_t xgetbv(void) { -#if defined(PIPE_CC_GCC) - uint32_t eax, edx; - - __asm __volatile( - ".byte 0x0f, 0x01, 0xd0" // xgetbv isn't supported on gcc < 4.4 - : "=a"(eax), "=d"(edx) - : "c"(0)); - - return ((uint64_t)edx << 32) | eax; -#elif defined(PIPE_CC_MSVC) && defined(_MSC_FULL_VER) && \ - defined(_XCR_XFEATURE_ENABLED_MASK) - return _xgetbv(_XCR_XFEATURE_ENABLED_MASK); -#else - return 0; -#endif -} - -#if defined(PIPE_ARCH_X86) -static inline boolean sse2_has_daz(void) { - struct { - uint32_t pad1[7]; - uint32_t mxcsr_mask; - uint32_t pad2[128 - 8]; - } PIPE_ALIGN_VAR(16) fxarea; - - fxarea.mxcsr_mask = 0; -#if (defined(PIPE_CC_GCC) || defined(PIPE_CC_SUNPRO)) - __asm __volatile("fxsave %0" : "+m"(fxarea)); -#elif (defined(PIPE_CC_MSVC) && _MSC_VER >= 1700) || defined(PIPE_CC_ICL) - /* 1700 = Visual Studio 2012 */ - _fxsave(&fxarea); -#else - fxarea.mxcsr_mask = 0; -#endif - return !!(fxarea.mxcsr_mask & (1 << 6)); -} -#endif - -#endif /* X86 or X86_64 */ - -void util_cpu_detect(void) { - static boolean util_cpu_detect_initialized = FALSE; - - if (util_cpu_detect_initialized) - return; - - memset(&util_cpu_caps, 0, sizeof util_cpu_caps); - - /* Count the number of CPUs in system */ -#if defined(PIPE_OS_WINDOWS) - { - SYSTEM_INFO system_info; - GetSystemInfo(&system_info); - util_cpu_caps.nr_cpus = system_info.dwNumberOfProcessors; - } -#elif defined(PIPE_OS_UNIX) && defined(_SC_NPROCESSORS_ONLN) - util_cpu_caps.nr_cpus = sysconf(_SC_NPROCESSORS_ONLN); - if (util_cpu_caps.nr_cpus == -1) - util_cpu_caps.nr_cpus = 1; -#elif defined(PIPE_OS_BSD) - { - int mib[2], ncpu; - int len; - - mib[0] = CTL_HW; - mib[1] = HW_NCPU; - - len = sizeof(ncpu); - sysctl(mib, 2, &ncpu, &len, NULL, 0); - util_cpu_caps.nr_cpus = ncpu; - } -#else - util_cpu_caps.nr_cpus = 1; -#endif - - /* Make the fallback cacheline size nonzero so that it can be - * safely passed to align(). - */ - util_cpu_caps.cacheline = sizeof(void *); - -#if defined(PIPE_ARCH_X86) || defined(PIPE_ARCH_X86_64) - if (has_cpuid()) { - uint32_t regs[4]; - uint32_t regs2[4]; - - util_cpu_caps.cacheline = 32; - - /* Get max cpuid level */ - cpuid(0x00000000, regs); - - if (regs[0] >= 0x00000001) { - unsigned int cacheline; - - cpuid(0x00000001, regs2); - - util_cpu_caps.x86_cpu_type = (regs2[0] >> 8) & 0xf; - if (util_cpu_caps.x86_cpu_type == 0xf) - util_cpu_caps.x86_cpu_type = - 8 + ((regs2[0] >> 20) & 255); /* use extended family (P4, IA64) */ - - /* general feature flags */ - util_cpu_caps.has_tsc = (regs2[3] >> 4) & 1; /* 0x0000010 */ - util_cpu_caps.has_mmx = (regs2[3] >> 23) & 1; /* 0x0800000 */ - util_cpu_caps.has_sse = (regs2[3] >> 25) & 1; /* 0x2000000 */ - util_cpu_caps.has_sse2 = (regs2[3] >> 26) & 1; /* 0x4000000 */ - util_cpu_caps.has_sse3 = (regs2[2] >> 0) & 1; /* 0x0000001 */ - util_cpu_caps.has_ssse3 = (regs2[2] >> 9) & 1; /* 0x0000020 */ - util_cpu_caps.has_sse4_1 = (regs2[2] >> 19) & 1; - util_cpu_caps.has_sse4_2 = (regs2[2] >> 20) & 1; - util_cpu_caps.has_popcnt = (regs2[2] >> 23) & 1; - util_cpu_caps.has_avx = ((regs2[2] >> 28) & 1) && // AVX - ((regs2[2] >> 27) & 1) && // OSXSAVE - ((xgetbv() & 6) == 6); // XMM & YMM - util_cpu_caps.has_f16c = (regs2[2] >> 29) & 1; - util_cpu_caps.has_mmx2 = - util_cpu_caps.has_sse; /* SSE cpus supports mmxext too */ -#if defined(PIPE_ARCH_X86_64) - util_cpu_caps.has_daz = 1; -#else - util_cpu_caps.has_daz = - util_cpu_caps.has_sse3 || (util_cpu_caps.has_sse2 && sse2_has_daz()); -#endif - - cacheline = ((regs2[1] >> 8) & 0xFF) * 8; - if (cacheline > 0) - util_cpu_caps.cacheline = cacheline; - } - if (util_cpu_caps.has_avx && regs[0] >= 0x00000007) { - uint32_t regs7[4]; - cpuid_count(0x00000007, 0x00000000, regs7); - util_cpu_caps.has_avx2 = (regs7[1] >> 5) & 1; - } - - if (regs[1] == 0x756e6547 && regs[2] == 0x6c65746e && - regs[3] == 0x49656e69) { - /* GenuineIntel */ - util_cpu_caps.has_intel = 1; - } - - cpuid(0x80000000, regs); - - if (regs[0] >= 0x80000001) { - - cpuid(0x80000001, regs2); - - util_cpu_caps.has_mmx |= (regs2[3] >> 23) & 1; - util_cpu_caps.has_mmx2 |= (regs2[3] >> 22) & 1; - util_cpu_caps.has_3dnow = (regs2[3] >> 31) & 1; - util_cpu_caps.has_3dnow_ext = (regs2[3] >> 30) & 1; - - util_cpu_caps.has_xop = util_cpu_caps.has_avx && ((regs2[2] >> 11) & 1); - } - - if (regs[0] >= 0x80000006) { - cpuid(0x80000006, regs2); - util_cpu_caps.cacheline = regs2[2] & 0xFF; - } - - if (!util_cpu_caps.has_sse) { - util_cpu_caps.has_sse2 = 0; - util_cpu_caps.has_sse3 = 0; - util_cpu_caps.has_ssse3 = 0; - util_cpu_caps.has_sse4_1 = 0; - } - } -#endif /* PIPE_ARCH_X86 || PIPE_ARCH_X86_64 */ - -#if defined(PIPE_ARCH_PPC) - check_os_altivec_support(); -#endif /* PIPE_ARCH_PPC */ - -#ifdef DEBUG - if (debug_get_option_dump_cpu()) { - debug_printf("util_cpu_caps.nr_cpus = %u\n", util_cpu_caps.nr_cpus); - - debug_printf("util_cpu_caps.x86_cpu_type = %u\n", - util_cpu_caps.x86_cpu_type); - debug_printf("util_cpu_caps.cacheline = %u\n", util_cpu_caps.cacheline); - - debug_printf("util_cpu_caps.has_tsc = %u\n", util_cpu_caps.has_tsc); - debug_printf("util_cpu_caps.has_mmx = %u\n", util_cpu_caps.has_mmx); - debug_printf("util_cpu_caps.has_mmx2 = %u\n", util_cpu_caps.has_mmx2); - debug_printf("util_cpu_caps.has_sse = %u\n", util_cpu_caps.has_sse); - debug_printf("util_cpu_caps.has_sse2 = %u\n", util_cpu_caps.has_sse2); - debug_printf("util_cpu_caps.has_sse3 = %u\n", util_cpu_caps.has_sse3); - debug_printf("util_cpu_caps.has_ssse3 = %u\n", util_cpu_caps.has_ssse3); - debug_printf("util_cpu_caps.has_sse4_1 = %u\n", util_cpu_caps.has_sse4_1); - debug_printf("util_cpu_caps.has_sse4_2 = %u\n", util_cpu_caps.has_sse4_2); - debug_printf("util_cpu_caps.has_avx = %u\n", util_cpu_caps.has_avx); - debug_printf("util_cpu_caps.has_avx2 = %u\n", util_cpu_caps.has_avx2); - debug_printf("util_cpu_caps.has_f16c = %u\n", util_cpu_caps.has_f16c); - debug_printf("util_cpu_caps.has_popcnt = %u\n", util_cpu_caps.has_popcnt); - debug_printf("util_cpu_caps.has_3dnow = %u\n", util_cpu_caps.has_3dnow); - debug_printf("util_cpu_caps.has_3dnow_ext = %u\n", - util_cpu_caps.has_3dnow_ext); - debug_printf("util_cpu_caps.has_xop = %u\n", util_cpu_caps.has_xop); - debug_printf("util_cpu_caps.has_altivec = %u\n", util_cpu_caps.has_altivec); - debug_printf("util_cpu_caps.has_daz = %u\n", util_cpu_caps.has_daz); - } -#endif - - util_cpu_detect_initialized = TRUE; -} diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_cpu_detect.h b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_cpu_detect.h deleted file mode 100644 index 2554c8ebd..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_cpu_detect.h +++ /dev/null @@ -1,81 +0,0 @@ -/************************************************************************** - * - * Copyright 2008 Dennis Smit - * All Rights Reserved. - * - * 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 - * on the rights to use, copy, modify, merge, publish, distribute, sub - * license, 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 (including the next - * paragraph) 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 NON-INFRINGEMENT. IN NO EVENT SHALL - * AUTHORS, COPYRIGHT HOLDERS, AND/OR THEIR SUPPLIERS 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. - * - ***************************************************************************/ - -/** - * @file - * CPU feature detection. - * - * @author Dennis Smit - * @author Based on the work of Eric Anholt - */ - -#ifndef _UTIL_CPU_DETECT_H -#define _UTIL_CPU_DETECT_H - -#include "pipe/p_compiler.h" -#include "pipe/p_config.h" - -#ifdef __cplusplus -extern "C" { -#endif - -struct util_cpu_caps { - int nr_cpus; - - /* Feature flags */ - int x86_cpu_type; - unsigned cacheline; - - unsigned has_intel : 1; - unsigned has_tsc : 1; - unsigned has_mmx : 1; - unsigned has_mmx2 : 1; - unsigned has_sse : 1; - unsigned has_sse2 : 1; - unsigned has_sse3 : 1; - unsigned has_ssse3 : 1; - unsigned has_sse4_1 : 1; - unsigned has_sse4_2 : 1; - unsigned has_popcnt : 1; - unsigned has_avx : 1; - unsigned has_avx2 : 1; - unsigned has_f16c : 1; - unsigned has_3dnow : 1; - unsigned has_3dnow_ext : 1; - unsigned has_xop : 1; - unsigned has_altivec : 1; - unsigned has_daz : 1; -}; - -extern struct util_cpu_caps util_cpu_caps; - -void util_cpu_detect(void); - -#ifdef __cplusplus -} -#endif - -#endif /* _UTIL_CPU_DETECT_H */ diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_debug.c b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_debug.c deleted file mode 100644 index 72eb7bc6b..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_debug.c +++ /dev/null @@ -1,454 +0,0 @@ -/************************************************************************** - * - * Copyright 2008 VMware, Inc. - * Copyright (c) 2008 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -#include "pipe/p_config.h" - -#include "pipe/p_compiler.h" -#include "pipe/p_format.h" -#include "pipe/p_state.h" -#include "util/u_debug.h" -#include "util/u_format.h" -#include "util/u_inlines.h" -#include "util/u_math.h" -#include "util/u_memory.h" -#include "util/u_prim.h" -#include "util/u_string.h" -#include "util/u_surface.h" - -#include /* isalnum */ -#include /* CHAR_BIT */ -#include - -#ifdef _WIN32 -#include -#include -#endif - -void _debug_vprintf(const char *format, va_list ap) { - static char buf[4096] = {'\0'}; -#if defined(PIPE_OS_WINDOWS) || defined(PIPE_SUBSYSTEM_EMBEDDED) - /* We buffer until we find a newline. */ - size_t len = strlen(buf); - int ret = util_vsnprintf(buf + len, sizeof(buf) - len, format, ap); - if (ret > (int)(sizeof(buf) - len - 1) || util_strchr(buf + len, '\n')) { - os_log_message(buf); - buf[0] = '\0'; - } -#else - util_vsnprintf(buf, sizeof(buf), format, ap); - os_log_message(buf); -#endif -} - -void debug_disable_error_message_boxes(void) { -#ifdef _WIN32 - /* When Windows' error message boxes are disabled for this process (as is - * typically the case when running tests in an automated fashion) we disable - * CRT message boxes too. - */ - UINT uMode = SetErrorMode(0); - SetErrorMode(uMode); - if (uMode & SEM_FAILCRITICALERRORS) { - /* Disable assertion failure message box. - * http://msdn.microsoft.com/en-us/library/sas1dkb2.aspx - */ - _set_error_mode(_OUT_TO_STDERR); -#ifdef _MSC_VER - /* Disable abort message box. - * http://msdn.microsoft.com/en-us/library/e631wekh.aspx - */ - _set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); -#endif - } -#endif /* _WIN32 */ -} - -#ifdef DEBUG -void debug_print_blob(const char *name, const void *blob, unsigned size) { - const unsigned *ublob = (const unsigned *)blob; - unsigned i; - - debug_printf("%s (%d dwords%s)\n", name, size / 4, - size % 4 ? "... plus a few bytes" : ""); - - for (i = 0; i < size / 4; i++) { - debug_printf("%d:\t%08x\n", i, ublob[i]); - } -} -#endif - -static boolean debug_get_option_should_print(void) { - static boolean first = TRUE; - static boolean value = FALSE; - - if (!first) - return value; - - /* Oh hey this will call into this function, - * but its cool since we set first to false - */ - first = FALSE; - value = debug_get_bool_option("GALLIUM_PRINT_OPTIONS", FALSE); - /* XXX should we print this option? Currently it wont */ - return value; -} - -const char *debug_get_option(const char *name, const char *dfault) { - const char *result; - - result = os_get_option(name); - if (!result) - result = dfault; - - if (debug_get_option_should_print()) - debug_printf("%s: %s = %s\n", __FUNCTION__, name, - result ? result : "(null)"); - - return result; -} - -boolean debug_get_bool_option(const char *name, boolean dfault) { - const char *str = os_get_option(name); - boolean result; - - if (str == NULL) - result = dfault; - else if (!util_strcmp(str, "n")) - result = FALSE; - else if (!util_strcmp(str, "no")) - result = FALSE; - else if (!util_strcmp(str, "0")) - result = FALSE; - else if (!util_strcmp(str, "f")) - result = FALSE; - else if (!util_strcmp(str, "F")) - result = FALSE; - else if (!util_strcmp(str, "false")) - result = FALSE; - else if (!util_strcmp(str, "FALSE")) - result = FALSE; - else - result = TRUE; - - if (debug_get_option_should_print()) - debug_printf("%s: %s = %s\n", __FUNCTION__, name, - result ? "TRUE" : "FALSE"); - - return result; -} - -long debug_get_num_option(const char *name, long dfault) { - long result; - const char *str; - - str = os_get_option(name); - if (!str) - result = dfault; - else { - long sign; - char c; - c = *str++; - if (c == '-') { - sign = -1; - c = *str++; - } else { - sign = 1; - } - result = 0; - while ('0' <= c && c <= '9') { - result = result * 10 + (c - '0'); - c = *str++; - } - result *= sign; - } - - if (debug_get_option_should_print()) - debug_printf("%s: %s = %li\n", __FUNCTION__, name, result); - - return result; -} - -static boolean str_has_option(const char *str, const char *name) { - /* Empty string. */ - if (!*str) { - return FALSE; - } - - /* OPTION=all */ - if (!util_strcmp(str, "all")) { - return TRUE; - } - - /* Find 'name' in 'str' surrounded by non-alphanumeric characters. */ - { - const char *start = str; - unsigned name_len = strlen(name); - - /* 'start' is the beginning of the currently-parsed word, - * we increment 'str' each iteration. - * if we find either the end of string or a non-alphanumeric character, - * we compare 'start' up to 'str-1' with 'name'. */ - - while (1) { - if (!*str || !(isalnum(*str) || *str == '_')) { - if (str - start == name_len && !memcmp(start, name, name_len)) { - return TRUE; - } - - if (!*str) { - return FALSE; - } - - start = str + 1; - } - - str++; - } - } - - return FALSE; -} - -unsigned long debug_get_flags_option(const char *name, - const struct debug_named_value *flags, - unsigned long dfault) { - unsigned long result; - const char *str; - const struct debug_named_value *orig = flags; - unsigned namealign = 0; - - str = os_get_option(name); - if (!str) - result = dfault; - else if (!util_strcmp(str, "help")) { - result = dfault; - _debug_printf("%s: help for %s:\n", __FUNCTION__, name); - for (; flags->name; ++flags) - namealign = MAX2(namealign, strlen(flags->name)); - for (flags = orig; flags->name; ++flags) - _debug_printf("| %*s [0x%0*lx]%s%s\n", namealign, flags->name, - (int)sizeof(unsigned long) * CHAR_BIT / 4, flags->value, - flags->desc ? " " : "", flags->desc ? flags->desc : ""); - } else { - result = 0; - while (flags->name) { - if (str_has_option(str, flags->name)) - result |= flags->value; - ++flags; - } - } - - if (debug_get_option_should_print()) { - if (str) { - debug_printf("%s: %s = 0x%lx (%s)\n", __FUNCTION__, name, result, str); - } else { - debug_printf("%s: %s = 0x%lx\n", __FUNCTION__, name, result); - } - } - - return result; -} - -void _debug_assert_fail(const char *expr, const char *file, unsigned line, - const char *function) { - _debug_printf("%s:%u:%s: Assertion `%s' failed.\n", file, line, function, - expr); - os_abort(); -} - -const char *debug_dump_enum(const struct debug_named_value *names, - unsigned long value) { - static char rest[64]; - - while (names->name) { - if (names->value == value) - return names->name; - ++names; - } - - util_snprintf(rest, sizeof(rest), "0x%08lx", value); - return rest; -} - -const char *debug_dump_enum_noprefix(const struct debug_named_value *names, - const char *prefix, unsigned long value) { - static char rest[64]; - - while (names->name) { - if (names->value == value) { - const char *name = names->name; - while (*name == *prefix) { - name++; - prefix++; - } - return name; - } - ++names; - } - - util_snprintf(rest, sizeof(rest), "0x%08lx", value); - return rest; -} - -const char *debug_dump_flags(const struct debug_named_value *names, - unsigned long value) { - static char output[4096]; - static char rest[256]; - int first = 1; - - output[0] = '\0'; - - while (names->name) { - if ((names->value & value) == names->value) { - if (!first) - util_strncat(output, "|", sizeof(output) - strlen(output) - 1); - else - first = 0; - util_strncat(output, names->name, sizeof(output) - strlen(output) - 1); - output[sizeof(output) - 1] = '\0'; - value &= ~names->value; - } - ++names; - } - - if (value) { - if (!first) - util_strncat(output, "|", sizeof(output) - strlen(output) - 1); - else - first = 0; - - util_snprintf(rest, sizeof(rest), "0x%08lx", value); - util_strncat(output, rest, sizeof(output) - strlen(output) - 1); - output[sizeof(output) - 1] = '\0'; - } - - if (first) - return "0"; - - return output; -} - -#ifdef DEBUG -void debug_print_format(const char *msg, unsigned fmt) { - debug_printf("%s: %s\n", msg, util_format_name(fmt)); -} -#endif - -static const struct debug_named_value pipe_prim_names[] = { -#ifdef DEBUG - DEBUG_NAMED_VALUE(PIPE_PRIM_POINTS), - DEBUG_NAMED_VALUE(PIPE_PRIM_LINES), - DEBUG_NAMED_VALUE(PIPE_PRIM_LINE_LOOP), - DEBUG_NAMED_VALUE(PIPE_PRIM_LINE_STRIP), - DEBUG_NAMED_VALUE(PIPE_PRIM_TRIANGLES), - DEBUG_NAMED_VALUE(PIPE_PRIM_TRIANGLE_STRIP), - DEBUG_NAMED_VALUE(PIPE_PRIM_TRIANGLE_FAN), - DEBUG_NAMED_VALUE(PIPE_PRIM_QUADS), - DEBUG_NAMED_VALUE(PIPE_PRIM_QUAD_STRIP), - DEBUG_NAMED_VALUE(PIPE_PRIM_POLYGON), - DEBUG_NAMED_VALUE(PIPE_PRIM_LINES_ADJACENCY), - DEBUG_NAMED_VALUE(PIPE_PRIM_LINE_STRIP_ADJACENCY), - DEBUG_NAMED_VALUE(PIPE_PRIM_TRIANGLES_ADJACENCY), - DEBUG_NAMED_VALUE(PIPE_PRIM_TRIANGLE_STRIP_ADJACENCY), -#endif - DEBUG_NAMED_VALUE_END}; - -const char *u_prim_name(unsigned prim) { - return debug_dump_enum(pipe_prim_names, prim); -} - -#ifdef DEBUG -int fl_indent = 0; -const char *fl_function[1024]; - -int debug_funclog_enter(const char *f, UNUSED const int line, - UNUSED const char *file) { - int i; - - for (i = 0; i < fl_indent; i++) - debug_printf(" "); - debug_printf("%s\n", f); - - assert(fl_indent < 1023); - fl_function[fl_indent++] = f; - - return 0; -} - -void debug_funclog_exit(const char *f, UNUSED const int line, - UNUSED const char *file) { - --fl_indent; - assert(fl_indent >= 0); - assert(fl_function[fl_indent] == f); -} - -void debug_funclog_enter_exit(const char *f, UNUSED const int line, - UNUSED const char *file) { - int i; - for (i = 0; i < fl_indent; i++) - debug_printf(" "); - debug_printf("%s\n", f); -} -#endif - -#ifdef DEBUG -/** - * Print PIPE_TRANSFER_x flags with a message. - */ -void debug_print_transfer_flags(const char *msg, unsigned usage) { -#define FLAG(x) {x, #x} - static const struct { - unsigned bit; - const char *name; - } flags[] = {FLAG(PIPE_TRANSFER_READ), - FLAG(PIPE_TRANSFER_WRITE), - FLAG(PIPE_TRANSFER_MAP_DIRECTLY), - FLAG(PIPE_TRANSFER_DISCARD_RANGE), - FLAG(PIPE_TRANSFER_DONTBLOCK), - FLAG(PIPE_TRANSFER_UNSYNCHRONIZED), - FLAG(PIPE_TRANSFER_FLUSH_EXPLICIT), - FLAG(PIPE_TRANSFER_DISCARD_WHOLE_RESOURCE)}; - unsigned i; - - debug_printf("%s ", msg); - - for (i = 0; i < ARRAY_SIZE(flags); i++) { - if (usage & flags[i].bit) { - debug_printf("%s", flags[i].name); - usage &= ~flags[i].bit; - if (usage) { - debug_printf(" | "); - } - } - } - - debug_printf("\n"); -#undef FLAG -} - -#endif diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_debug.h b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_debug.h deleted file mode 100644 index 520a8c028..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_debug.h +++ /dev/null @@ -1,411 +0,0 @@ -/************************************************************************** - * - * Copyright 2008 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -/** - * @file - * Cross-platform debugging helpers. - * - * For now it just has assert and printf replacements, but it might be extended - * with stack trace reports and more advanced logging in the near future. - * - * @author Jose Fonseca - */ - -#ifndef U_DEBUG_H_ -#define U_DEBUG_H_ - -#include "os/os_misc.h" - -#include "pipe/p_format.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#if defined(__GNUC__) -#define _util_printf_format(fmt, list) \ - __attribute__((format(printf, fmt, list))) -#else -#define _util_printf_format(fmt, list) -#endif - -void _debug_vprintf(const char *format, va_list ap); - -static inline void _debug_printf(const char *format, ...) { - va_list ap; - va_start(ap, format); - _debug_vprintf(format, ap); - va_end(ap); -} - -/** - * Print debug messages. - * - * The actual channel used to output debug message is platform specific. To - * avoid misformating or truncation, follow these rules of thumb: - * - output whole lines - * - avoid outputing large strings (512 bytes is the current maximum length - * that is guaranteed to be printed in all platforms) - */ -#if !defined(PIPE_OS_HAIKU) -static inline void debug_printf(const char *format, ...) - _util_printf_format(1, 2); - -static inline void debug_printf(const char *format, ...) { -#ifdef DEBUG - va_list ap; - va_start(ap, format); - _debug_vprintf(format, ap); - va_end(ap); -#else - (void)format; /* silence warning */ -#endif -} -#else /* is Haiku */ -/* Haiku provides debug_printf in libroot with OS.h */ -#include -#endif - -/* - * ... isn't portable so we need to pass arguments in parentheses. - * - * usage: - * debug_printf_once(("answer: %i\n", 42)); - */ -#define debug_printf_once(args) \ - do { \ - static boolean once = TRUE; \ - if (once) { \ - once = FALSE; \ - debug_printf args; \ - } \ - } while (0) - -#ifdef DEBUG -#define debug_vprintf(_format, _ap) _debug_vprintf(_format, _ap) -#else -#define debug_vprintf(_format, _ap) ((void)0) -#endif - -#ifdef DEBUG -/** - * Dump a blob in hex to the same place that debug_printf sends its - * messages. - */ -void debug_print_blob(const char *name, const void *blob, unsigned size); - -/* Print a message along with a prettified format string - */ -void debug_print_format(const char *msg, unsigned fmt); -#else -#define debug_print_blob(_name, _blob, _size) ((void)0) -#define debug_print_format(_msg, _fmt) ((void)0) -#endif - -/** - * Disable interactive error message boxes. - * - * Should be called as soon as possible for effectiveness. - */ -void debug_disable_error_message_boxes(void); - -/** - * Hard-coded breakpoint. - */ -#ifdef DEBUG -#define debug_break() os_break() -#else /* !DEBUG */ -#define debug_break() ((void)0) -#endif /* !DEBUG */ - -#ifdef _MSC_VER -__declspec(noreturn) -#endif -void _debug_assert_fail(const char *expr, - const char *file, - unsigned line, - const char *function) -#if defined(__GNUC__) && !defined(DEBUG) - __attribute__((noreturn)) -#endif -; - -/** - * Assert macro - * - * Do not expect that the assert call terminates -- errors must be handled - * regardless of assert behavior. - * - * For non debug builds the assert macro will expand to a no-op, so do not - * call functions with side effects in the assert expression. - */ -#ifdef DEBUG -#define debug_assert(expr) \ - ((expr) ? (void)0 \ - : _debug_assert_fail(#expr, __FILE__, __LINE__, __FUNCTION__)) -#else -#define debug_assert(expr) (void)(0 && (expr)) -#endif - -/** Override standard assert macro */ -#ifdef assert -#undef assert -#endif -#define assert(expr) debug_assert(expr) - -/** - * Output the current function name. - */ -#ifdef DEBUG -#define debug_checkpoint() _debug_printf("%s\n", __FUNCTION__) -#else -#define debug_checkpoint() ((void)0) -#endif - -/** - * Output the full source code position. - */ -#ifdef DEBUG -#define debug_checkpoint_full() \ - _debug_printf("%s:%u:%s\n", __FILE__, __LINE__, __FUNCTION__) -#else -#define debug_checkpoint_full() ((void)0) -#endif - -/** - * Output a warning message. Muted on release version. - */ -#ifdef DEBUG -#define debug_warning(__msg) \ - _debug_printf("%s:%u:%s: warning: %s\n", __FILE__, __LINE__, __FUNCTION__, \ - __msg) -#else -#define debug_warning(__msg) ((void)0) -#endif - -/** - * Emit a warning message, but only once. - */ -#ifdef DEBUG -#define debug_warn_once(__msg) \ - do { \ - static bool warned = FALSE; \ - if (!warned) { \ - _debug_printf("%s:%u:%s: one time warning: %s\n", __FILE__, __LINE__, \ - __FUNCTION__, __msg); \ - warned = TRUE; \ - } \ - } while (0) -#else -#define debug_warn_once(__msg) ((void)0) -#endif - -/** - * Output an error message. Not muted on release version. - */ -#ifdef DEBUG -#define debug_error(__msg) \ - _debug_printf("%s:%u:%s: error: %s\n", __FILE__, __LINE__, __FUNCTION__, \ - __msg) -#else -#define debug_error(__msg) _debug_printf("error: %s\n", __msg) -#endif - -/** - * Used by debug_dump_enum and debug_dump_flags to describe symbols. - */ -struct debug_named_value { - const char *name; - unsigned long value; - const char *desc; -}; - -/** - * Some C pre-processor magic to simplify creating named values. - * - * Example: - * @code - * static const debug_named_value my_names[] = { - * DEBUG_NAMED_VALUE(MY_ENUM_VALUE_X), - * DEBUG_NAMED_VALUE(MY_ENUM_VALUE_Y), - * DEBUG_NAMED_VALUE(MY_ENUM_VALUE_Z), - * DEBUG_NAMED_VALUE_END - * }; - * - * ... - * debug_printf("%s = %s\n", - * name, - * debug_dump_enum(my_names, my_value)); - * ... - * @endcode - */ -#define DEBUG_NAMED_VALUE(__symbol) {#__symbol, (unsigned long)__symbol, NULL} -#define DEBUG_NAMED_VALUE_WITH_DESCRIPTION(__symbol, __desc) \ - {#__symbol, (unsigned long)__symbol, __desc} -#define DEBUG_NAMED_VALUE_END {NULL, 0, NULL} - -/** - * Convert a enum value to a string. - */ -const char *debug_dump_enum(const struct debug_named_value *names, - unsigned long value); - -const char *debug_dump_enum_noprefix(const struct debug_named_value *names, - const char *prefix, unsigned long value); - -/** - * Convert binary flags value to a string. - */ -const char *debug_dump_flags(const struct debug_named_value *names, - unsigned long value); - -/** - * Function enter exit loggers - */ -#ifdef DEBUG -int debug_funclog_enter(const char *f, const int line, const char *file); -void debug_funclog_exit(const char *f, const int line, const char *file); -void debug_funclog_enter_exit(const char *f, const int line, const char *file); - -#define DEBUG_FUNCLOG_ENTER() \ - int __debug_decleration_work_around = \ - debug_funclog_enter(__FUNCTION__, __LINE__, __FILE__) -#define DEBUG_FUNCLOG_EXIT() \ - do { \ - (void)__debug_decleration_work_around; \ - debug_funclog_exit(__FUNCTION__, __LINE__, __FILE__); \ - return; \ - } while (0) -#define DEBUG_FUNCLOG_EXIT_RET(ret) \ - do { \ - (void)__debug_decleration_work_around; \ - debug_funclog_exit(__FUNCTION__, __LINE__, __FILE__); \ - return ret; \ - } while (0) -#define DEBUG_FUNCLOG_ENTER_EXIT() \ - debug_funclog_enter_exit(__FUNCTION__, __LINE__, __FILE__) - -#else -#define DEBUG_FUNCLOG_ENTER() int __debug_decleration_work_around -#define DEBUG_FUNCLOG_EXIT() \ - do { \ - (void)__debug_decleration_work_around; \ - return; \ - } while (0) -#define DEBUG_FUNCLOG_EXIT_RET(ret) \ - do { \ - (void)__debug_decleration_work_around; \ - return ret; \ - } while (0) -#define DEBUG_FUNCLOG_ENTER_EXIT() -#endif - -/** - * Get option. - * - * It is an alias for getenv on Linux. - * - * On Windows it reads C:\gallium.cfg, which is a text file with CR+LF line - * endings with one option per line as - * - * NAME=value - * - * This file must be terminated with an extra empty line. - */ -const char *debug_get_option(const char *name, const char *dfault); - -boolean debug_get_bool_option(const char *name, boolean dfault); - -long debug_get_num_option(const char *name, long dfault); - -unsigned long debug_get_flags_option(const char *name, - const struct debug_named_value *flags, - unsigned long dfault); - -#define DEBUG_GET_ONCE_BOOL_OPTION(sufix, name, dfault) \ - static boolean debug_get_option_##sufix(void) { \ - static boolean first = TRUE; \ - static boolean value; \ - if (first) { \ - first = FALSE; \ - value = debug_get_bool_option(name, dfault); \ - } \ - return value; \ - } - -#define DEBUG_GET_ONCE_NUM_OPTION(sufix, name, dfault) \ - static long debug_get_option_##sufix(void) { \ - static boolean first = TRUE; \ - static long value; \ - if (first) { \ - first = FALSE; \ - value = debug_get_num_option(name, dfault); \ - } \ - return value; \ - } - -#define DEBUG_GET_ONCE_FLAGS_OPTION(sufix, name, flags, dfault) \ - static unsigned long debug_get_option_##sufix(void) { \ - static boolean first = TRUE; \ - static unsigned long value; \ - if (first) { \ - first = FALSE; \ - value = debug_get_flags_option(name, flags, dfault); \ - } \ - return value; \ - } - -unsigned long debug_memory_begin(void); - -void debug_memory_end(unsigned long beginning); - -#ifdef DEBUG -struct pipe_context; -struct pipe_surface; -struct pipe_transfer; -struct pipe_resource; - -void debug_dump_image(const char *prefix, enum pipe_format format, unsigned cpp, - unsigned width, unsigned height, unsigned stride, - const void *data); -void debug_dump_surface(struct pipe_context *pipe, const char *prefix, - struct pipe_surface *surface); -void debug_dump_texture(struct pipe_context *pipe, const char *prefix, - struct pipe_resource *texture); -#else -#define debug_dump_image(prefix, format, cpp, width, height, stride, data) \ - ((void)0) -#define debug_dump_surface(pipe, prefix, surface) ((void)0) -#endif - -void debug_print_transfer_flags(const char *msg, unsigned usage); - -#ifdef __cplusplus -} -#endif - -#endif /* U_DEBUG_H_ */ diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_debug_describe.c b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_debug_describe.c deleted file mode 100644 index 89bb980ef..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_debug_describe.c +++ /dev/null @@ -1,92 +0,0 @@ -/************************************************************************** - * - * Copyright 2010 Luca Barbieri - * - * 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 (including the - * next paragraph) 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 COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -#include "util/u_debug_describe.h" -#include "pipe/p_state.h" -#include "util/u_format.h" -#include "util/u_string.h" - -void debug_describe_reference(char *buf, - UNUSED const struct pipe_reference *ptr) { - strcpy(buf, "pipe_object"); -} - -void debug_describe_resource(char *buf, const struct pipe_resource *ptr) { - switch (ptr->target) { - case PIPE_BUFFER: - util_sprintf(buf, "pipe_buffer<%u>", - (unsigned)util_format_get_stride(ptr->format, ptr->width0)); - break; - case PIPE_TEXTURE_1D: - util_sprintf(buf, "pipe_texture1d<%u,%s,%u>", ptr->width0, - util_format_short_name(ptr->format), ptr->last_level); - break; - case PIPE_TEXTURE_2D: - util_sprintf(buf, "pipe_texture2d<%u,%u,%s,%u>", ptr->width0, ptr->height0, - util_format_short_name(ptr->format), ptr->last_level); - break; - case PIPE_TEXTURE_RECT: - util_sprintf(buf, "pipe_texture_rect<%u,%u,%s>", ptr->width0, ptr->height0, - util_format_short_name(ptr->format)); - break; - case PIPE_TEXTURE_CUBE: - util_sprintf(buf, "pipe_texture_cube<%u,%u,%s,%u>", ptr->width0, - ptr->height0, util_format_short_name(ptr->format), - ptr->last_level); - break; - case PIPE_TEXTURE_3D: - util_sprintf(buf, "pipe_texture3d<%u,%u,%u,%s,%u>", ptr->width0, - ptr->height0, ptr->depth0, util_format_short_name(ptr->format), - ptr->last_level); - break; - default: - util_sprintf(buf, "pipe_martian_resource<%u>", ptr->target); - break; - } -} - -void debug_describe_surface(char *buf, const struct pipe_surface *ptr) { - char res[128]; - debug_describe_resource(res, ptr->texture); - util_sprintf(buf, "pipe_surface<%s,%u,%u,%u>", res, ptr->u.tex.level, - ptr->u.tex.first_layer, ptr->u.tex.last_layer); -} - -void debug_describe_sampler_view(char *buf, - const struct pipe_sampler_view *ptr) { - char res[128]; - debug_describe_resource(res, ptr->texture); - util_sprintf(buf, "pipe_sampler_view<%s,%s>", res, - util_format_short_name(ptr->format)); -} - -void debug_describe_so_target(char *buf, - const struct pipe_stream_output_target *ptr) { - char res[128]; - debug_describe_resource(res, ptr->buffer); - util_sprintf(buf, "pipe_stream_output_target<%s,%u,%u>", res, - ptr->buffer_offset, ptr->buffer_size); -} diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_debug_describe.h b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_debug_describe.h deleted file mode 100644 index 2c020e4dd..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_debug_describe.h +++ /dev/null @@ -1,52 +0,0 @@ -/************************************************************************** - * - * Copyright 2010 Luca Barbieri - * - * 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 (including the - * next paragraph) 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 COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -#ifndef U_DEBUG_DESCRIBE_H_ -#define U_DEBUG_DESCRIBE_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -struct pipe_reference; -struct pipe_resource; -struct pipe_surface; -struct pipe_sampler_view; - -/* a 256-byte buffer is necessary and sufficient */ -void debug_describe_reference(char *buf, const struct pipe_reference *ptr); -void debug_describe_resource(char *buf, const struct pipe_resource *ptr); -void debug_describe_surface(char *buf, const struct pipe_surface *ptr); -void debug_describe_sampler_view(char *buf, - const struct pipe_sampler_view *ptr); -void debug_describe_so_target(char *buf, - const struct pipe_stream_output_target *ptr); - -#ifdef __cplusplus -} -#endif - -#endif /* U_DEBUG_DESCRIBE_H_ */ diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_debug_refcnt.h b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_debug_refcnt.h deleted file mode 100644 index 079d0fe41..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_debug_refcnt.h +++ /dev/null @@ -1,50 +0,0 @@ -/************************************************************************** - * - * Copyright 2010 Luca Barbieri - * - * 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 (including the - * next paragraph) 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 COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -#ifndef U_DEBUG_REFCNT_H_ -#define U_DEBUG_REFCNT_H_ - -#include "pipe/p_config.h" -#include "pipe/p_state.h" - -#include "util/u_debug.h" - -#ifdef __cplusplus -extern "C" { -#endif - -typedef void (*debug_reference_descriptor)(char *, - const struct pipe_reference *); - -static inline void debug_reference(UNUSED const struct pipe_reference *p, - UNUSED debug_reference_descriptor get_desc, - UNUSED int change) {} - -#ifdef __cplusplus -} -#endif - -#endif /* U_DEBUG_REFCNT_H_ */ diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_double_list.h b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_double_list.h deleted file mode 100644 index b3da0909d..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_double_list.h +++ /dev/null @@ -1,137 +0,0 @@ -/************************************************************************** - * - * Copyright 2006 VMware, Inc., Bismarck, ND. USA. - * All Rights Reserved. - * - * 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, sub license, 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 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 NON-INFRINGEMENT. IN NO EVENT SHALL - * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS 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. - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial portions - * of the Software. - * - **************************************************************************/ - -/** - * \file - * List macros heavily inspired by the Linux kernel - * list handling. No list looping yet. - * - * Is not threadsafe, so common operations need to - * be protected using an external mutex. - */ - -#ifndef _U_DOUBLE_LIST_H_ -#define _U_DOUBLE_LIST_H_ - -#include "pipe/p_compiler.h" -#include - -struct list_head { - struct list_head *prev; - struct list_head *next; -}; - -static inline void list_inithead(struct list_head *item) { - item->prev = item; - item->next = item; -} - -static inline void list_add(struct list_head *item, struct list_head *list) { - item->prev = list; - item->next = list->next; - list->next->prev = item; - list->next = item; -} - -static inline void list_addtail(struct list_head *item, - struct list_head *list) { - item->next = list; - item->prev = list->prev; - list->prev->next = item; - list->prev = item; -} - -static inline void list_replace(struct list_head *from, struct list_head *to) { - to->prev = from->prev; - to->next = from->next; - from->next->prev = to; - from->prev->next = to; -} - -static inline void list_del(struct list_head *item) { - item->prev->next = item->next; - item->next->prev = item->prev; - item->prev = item->next = NULL; -} - -static inline void list_delinit(struct list_head *item) { - item->prev->next = item->next; - item->next->prev = item->prev; - item->next = item; - item->prev = item; -} - -#define LIST_INITHEAD(__item) list_inithead(__item) -#define LIST_ADD(__item, __list) list_add(__item, __list) -#define LIST_ADDTAIL(__item, __list) list_addtail(__item, __list) -#define LIST_REPLACE(__from, __to) list_replace(__from, __to) -#define LIST_DEL(__item) list_del(__item) -#define LIST_DELINIT(__item) list_delinit(__item) - -#define LIST_ENTRY(__type, __item, __field) \ - ((__type *)(((char *)(__item)) - offsetof(__type, __field))) - -#define LIST_IS_EMPTY(__list) ((__list)->next == (__list)) - -/** - * Cast from a pointer to a member of a struct back to the containing struct. - * - * 'sample' MUST be initialized, or else the result is undefined! - */ -#ifndef container_of -#define container_of(ptr, sample, member) \ - (void *)((char *)(ptr) - ((char *)&(sample)->member - (char *)(sample))) -#endif - -#define LIST_FOR_EACH_ENTRY(pos, head, member) \ - for (pos = NULL, pos = container_of((head)->next, pos, member); \ - &pos->member != (head); \ - pos = container_of(pos->member.next, pos, member)) - -#define LIST_FOR_EACH_ENTRY_SAFE(pos, storage, head, member) \ - for (pos = NULL, pos = container_of((head)->next, pos, member), \ - storage = container_of(pos->member.next, pos, member); \ - &pos->member != (head); pos = storage, \ - storage = container_of(storage->member.next, storage, member)) - -#define LIST_FOR_EACH_ENTRY_SAFE_REV(pos, storage, head, member) \ - for (pos = NULL, pos = container_of((head)->prev, pos, member), \ - storage = container_of(pos->member.prev, pos, member); \ - &pos->member != (head); pos = storage, \ - storage = container_of(storage->member.prev, storage, member)) - -#define LIST_FOR_EACH_ENTRY_FROM(pos, start, head, member) \ - for (pos = NULL, pos = container_of((start), pos, member); \ - &pos->member != (head); \ - pos = container_of(pos->member.next, pos, member)) - -#define LIST_FOR_EACH_ENTRY_FROM_REV(pos, start, head, member) \ - for (pos = NULL, pos = container_of((start), pos, member); \ - &pos->member != (head); \ - pos = container_of(pos->member.prev, pos, member)) - -#endif /*_U_DOUBLE_LIST_H_*/ diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_dual_blend.h b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_dual_blend.h deleted file mode 100644 index 64506d3d9..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_dual_blend.h +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef U_DUAL_BLEND_H -#define U_DUAL_BLEND_H - -#include "pipe/p_state.h" - -static inline boolean util_blend_factor_is_dual_src(int factor) { - return (factor == PIPE_BLENDFACTOR_SRC1_COLOR) || - (factor == PIPE_BLENDFACTOR_SRC1_ALPHA) || - (factor == PIPE_BLENDFACTOR_INV_SRC1_COLOR) || - (factor == PIPE_BLENDFACTOR_INV_SRC1_ALPHA); -} - -static inline boolean -util_blend_state_is_dual(const struct pipe_blend_state *blend, int index) { - if (util_blend_factor_is_dual_src(blend->rt[index].rgb_src_factor) || - util_blend_factor_is_dual_src(blend->rt[index].alpha_src_factor) || - util_blend_factor_is_dual_src(blend->rt[index].rgb_dst_factor) || - util_blend_factor_is_dual_src(blend->rt[index].alpha_dst_factor)) - return true; - return false; -} - -#endif diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_format.c b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_format.c deleted file mode 100644 index 85f2a1200..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_format.c +++ /dev/null @@ -1,452 +0,0 @@ -/************************************************************************** - * - * Copyright 2010 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -/** - * @file - * Pixel format accessor functions. - * - * @author Jose Fonseca - */ - -#include "u_format.h" -#include "u_format_s3tc.h" -#include "u_math.h" -#include "u_memory.h" -#include "u_surface.h" - -#include "pipe/p_defines.h" - -boolean util_format_s3tc_enabled = FALSE; - -boolean util_format_is_float(enum pipe_format format) { - const struct util_format_description *desc = util_format_description(format); - int i; - - assert(desc); - if (!desc) { - return FALSE; - } - - i = util_format_get_first_non_void_channel(format); - if (i == -1) { - return FALSE; - } - - return desc->channel[i].type == UTIL_FORMAT_TYPE_FLOAT ? TRUE : FALSE; -} - -/** Test if the format contains RGB, but not alpha */ -boolean util_format_has_alpha(enum pipe_format format) { - const struct util_format_description *desc = util_format_description(format); - - return (desc->colorspace == UTIL_FORMAT_COLORSPACE_RGB || - desc->colorspace == UTIL_FORMAT_COLORSPACE_SRGB) && - desc->swizzle[3] != UTIL_FORMAT_SWIZZLE_1; -} - -boolean util_format_is_luminance(enum pipe_format format) { - const struct util_format_description *desc = util_format_description(format); - - if ((desc->colorspace == UTIL_FORMAT_COLORSPACE_RGB || - desc->colorspace == UTIL_FORMAT_COLORSPACE_SRGB) && - desc->swizzle[0] == UTIL_FORMAT_SWIZZLE_X && - desc->swizzle[1] == UTIL_FORMAT_SWIZZLE_X && - desc->swizzle[2] == UTIL_FORMAT_SWIZZLE_X && - desc->swizzle[3] == UTIL_FORMAT_SWIZZLE_1) { - return TRUE; - } - return FALSE; -} - -boolean util_format_is_alpha(enum pipe_format format) { - const struct util_format_description *desc = util_format_description(format); - - if ((desc->colorspace == UTIL_FORMAT_COLORSPACE_RGB || - desc->colorspace == UTIL_FORMAT_COLORSPACE_SRGB) && - desc->swizzle[0] == UTIL_FORMAT_SWIZZLE_0 && - desc->swizzle[1] == UTIL_FORMAT_SWIZZLE_0 && - desc->swizzle[2] == UTIL_FORMAT_SWIZZLE_0 && - desc->swizzle[3] == UTIL_FORMAT_SWIZZLE_X) { - return TRUE; - } - return FALSE; -} - -boolean util_format_is_pure_integer(enum pipe_format format) { - const struct util_format_description *desc = util_format_description(format); - int i; - - /* Find the first non-void channel. */ - i = util_format_get_first_non_void_channel(format); - if (i == -1) - return FALSE; - - return desc->channel[i].pure_integer ? TRUE : FALSE; -} - -boolean util_format_is_pure_sint(enum pipe_format format) { - const struct util_format_description *desc = util_format_description(format); - int i; - - i = util_format_get_first_non_void_channel(format); - if (i == -1) - return FALSE; - - return (desc->channel[i].type == UTIL_FORMAT_TYPE_SIGNED && - desc->channel[i].pure_integer) - ? TRUE - : FALSE; -} - -boolean util_format_is_pure_uint(enum pipe_format format) { - const struct util_format_description *desc = util_format_description(format); - int i; - - i = util_format_get_first_non_void_channel(format); - if (i == -1) - return FALSE; - - return (desc->channel[i].type == UTIL_FORMAT_TYPE_UNSIGNED && - desc->channel[i].pure_integer) - ? TRUE - : FALSE; -} - -/** - * Returns true if all non-void channels are normalized signed. - */ -boolean util_format_is_snorm(enum pipe_format format) { - const struct util_format_description *desc = util_format_description(format); - int i; - - if (desc->is_mixed) - return FALSE; - - i = util_format_get_first_non_void_channel(format); - if (i == -1) - return FALSE; - - return desc->channel[i].type == UTIL_FORMAT_TYPE_SIGNED && - !desc->channel[i].pure_integer && desc->channel[i].normalized; -} - -boolean util_format_is_luminance_alpha(enum pipe_format format) { - const struct util_format_description *desc = util_format_description(format); - - if ((desc->colorspace == UTIL_FORMAT_COLORSPACE_RGB || - desc->colorspace == UTIL_FORMAT_COLORSPACE_SRGB) && - desc->swizzle[0] == UTIL_FORMAT_SWIZZLE_X && - desc->swizzle[1] == UTIL_FORMAT_SWIZZLE_X && - desc->swizzle[2] == UTIL_FORMAT_SWIZZLE_X && - desc->swizzle[3] == UTIL_FORMAT_SWIZZLE_Y) { - return TRUE; - } - return FALSE; -} - -boolean util_format_is_intensity(enum pipe_format format) { - const struct util_format_description *desc = util_format_description(format); - - if ((desc->colorspace == UTIL_FORMAT_COLORSPACE_RGB || - desc->colorspace == UTIL_FORMAT_COLORSPACE_SRGB) && - desc->swizzle[0] == UTIL_FORMAT_SWIZZLE_X && - desc->swizzle[1] == UTIL_FORMAT_SWIZZLE_X && - desc->swizzle[2] == UTIL_FORMAT_SWIZZLE_X && - desc->swizzle[3] == UTIL_FORMAT_SWIZZLE_X) { - return TRUE; - } - return FALSE; -} - -boolean util_format_is_subsampled_422(enum pipe_format format) { - const struct util_format_description *desc = util_format_description(format); - - return desc->layout == UTIL_FORMAT_LAYOUT_SUBSAMPLED && - desc->block.width == 2 && desc->block.height == 1 && - desc->block.bits == 32; -} - -boolean util_format_is_supported(enum pipe_format format, unsigned bind) { - if (util_format_is_s3tc(format) && !util_format_s3tc_enabled) { - return FALSE; - } - -#ifndef TEXTURE_FLOAT_ENABLED - if ((bind & PIPE_BIND_RENDER_TARGET) && - format != PIPE_FORMAT_R9G9B9E5_FLOAT && - format != PIPE_FORMAT_R11G11B10_FLOAT && util_format_is_float(format)) { - return FALSE; - } -#endif - - return TRUE; -} - -/** - * Calculates the MRD for the depth format. MRD is used in depth bias - * for UNORM and unbound depth buffers. When the depth buffer is floating - * point, the depth bias calculation does not use the MRD. However, the - * default MRD will be 1.0 / ((1 << 24) - 1). - */ -double util_get_depth_format_mrd(const struct util_format_description *desc) { - /* - * Depth buffer formats without a depth component OR scenarios - * without a bound depth buffer default to D24. - */ - double mrd = 1.0 / ((1 << 24) - 1); - unsigned depth_channel; - - assert(desc); - - /* - * Some depth formats do not store the depth component in the first - * channel, detect the format and adjust the depth channel. Get the - * swizzled depth component channel. - */ - depth_channel = desc->swizzle[0]; - - if (desc->channel[depth_channel].type == UTIL_FORMAT_TYPE_UNSIGNED && - desc->channel[depth_channel].normalized) { - int depth_bits; - - depth_bits = desc->channel[depth_channel].size; - mrd = 1.0 / ((1ULL << depth_bits) - 1); - } - - return mrd; -} - -boolean -util_is_format_compatible(const struct util_format_description *src_desc, - const struct util_format_description *dst_desc) { - unsigned chan; - - if (src_desc->format == dst_desc->format) { - return TRUE; - } - - if (src_desc->layout != UTIL_FORMAT_LAYOUT_PLAIN || - dst_desc->layout != UTIL_FORMAT_LAYOUT_PLAIN) { - return FALSE; - } - - if (src_desc->block.bits != dst_desc->block.bits || - src_desc->nr_channels != dst_desc->nr_channels || - src_desc->colorspace != dst_desc->colorspace) { - return FALSE; - } - - for (chan = 0; chan < 4; ++chan) { - if (src_desc->channel[chan].size != dst_desc->channel[chan].size) { - return FALSE; - } - } - - for (chan = 0; chan < 4; ++chan) { - enum util_format_swizzle swizzle = dst_desc->swizzle[chan]; - - if (swizzle < 4) { - if (src_desc->swizzle[chan] != swizzle) { - return FALSE; - } - if ((src_desc->channel[swizzle].type != - dst_desc->channel[swizzle].type) || - (src_desc->channel[swizzle].normalized != - dst_desc->channel[swizzle].normalized)) { - return FALSE; - } - } - } - - return TRUE; -} - -boolean -util_format_fits_8unorm(const struct util_format_description *format_desc) { - unsigned chan; - - /* - * After linearized sRGB values require more than 8bits. - */ - - if (format_desc->colorspace == UTIL_FORMAT_COLORSPACE_SRGB) { - return FALSE; - } - - switch (format_desc->layout) { - - case UTIL_FORMAT_LAYOUT_S3TC: - /* - * These are straight forward. - */ - return TRUE; - case UTIL_FORMAT_LAYOUT_RGTC: - if (format_desc->format == PIPE_FORMAT_RGTC1_SNORM || - format_desc->format == PIPE_FORMAT_RGTC2_SNORM || - format_desc->format == PIPE_FORMAT_LATC1_SNORM || - format_desc->format == PIPE_FORMAT_LATC2_SNORM) - return FALSE; - return TRUE; - case UTIL_FORMAT_LAYOUT_BPTC: - if (format_desc->format == PIPE_FORMAT_BPTC_RGBA_UNORM) - return TRUE; - return FALSE; - - case UTIL_FORMAT_LAYOUT_PLAIN: - /* - * For these we can find a generic rule. - */ - - for (chan = 0; chan < format_desc->nr_channels; ++chan) { - switch (format_desc->channel[chan].type) { - case UTIL_FORMAT_TYPE_VOID: - break; - case UTIL_FORMAT_TYPE_UNSIGNED: - if (!format_desc->channel[chan].normalized || - format_desc->channel[chan].size > 8) { - return FALSE; - } - break; - default: - return FALSE; - } - } - return TRUE; - - default: - /* - * Handle all others on a case by case basis. - */ - - switch (format_desc->format) { - case PIPE_FORMAT_R1_UNORM: - case PIPE_FORMAT_UYVY: - case PIPE_FORMAT_YUYV: - case PIPE_FORMAT_R8G8_B8G8_UNORM: - case PIPE_FORMAT_G8R8_G8B8_UNORM: - return TRUE; - - default: - return FALSE; - } - } -} - -void util_format_compose_swizzles(const unsigned char swz1[4], - const unsigned char swz2[4], - unsigned char dst[4]) { - unsigned i; - - for (i = 0; i < 4; i++) { - dst[i] = swz2[i] <= UTIL_FORMAT_SWIZZLE_W ? swz1[swz2[i]] : swz2[i]; - } -} - -void util_format_apply_color_swizzle(union pipe_color_union *dst, - const union pipe_color_union *src, - const unsigned char swz[4], - const boolean is_integer) { - unsigned c; - - if (is_integer) { - for (c = 0; c < 4; ++c) { - switch (swz[c]) { - case PIPE_SWIZZLE_RED: - dst->ui[c] = src->ui[0]; - break; - case PIPE_SWIZZLE_GREEN: - dst->ui[c] = src->ui[1]; - break; - case PIPE_SWIZZLE_BLUE: - dst->ui[c] = src->ui[2]; - break; - case PIPE_SWIZZLE_ALPHA: - dst->ui[c] = src->ui[3]; - break; - default: - dst->ui[c] = (swz[c] == PIPE_SWIZZLE_ONE) ? 1 : 0; - break; - } - } - } else { - for (c = 0; c < 4; ++c) { - switch (swz[c]) { - case PIPE_SWIZZLE_RED: - dst->f[c] = src->f[0]; - break; - case PIPE_SWIZZLE_GREEN: - dst->f[c] = src->f[1]; - break; - case PIPE_SWIZZLE_BLUE: - dst->f[c] = src->f[2]; - break; - case PIPE_SWIZZLE_ALPHA: - dst->f[c] = src->f[3]; - break; - default: - dst->f[c] = (swz[c] == PIPE_SWIZZLE_ONE) ? 1.0f : 0.0f; - break; - } - } - } -} - -void util_format_swizzle_4f(float *dst, const float *src, - const unsigned char swz[4]) { - unsigned i; - - for (i = 0; i < 4; i++) { - if (swz[i] <= UTIL_FORMAT_SWIZZLE_W) - dst[i] = src[swz[i]]; - else if (swz[i] == UTIL_FORMAT_SWIZZLE_0) - dst[i] = 0; - else if (swz[i] == UTIL_FORMAT_SWIZZLE_1) - dst[i] = 1; - } -} - -void util_format_unswizzle_4f(float *dst, const float *src, - const unsigned char swz[4]) { - unsigned i; - - for (i = 0; i < 4; i++) { - switch (swz[i]) { - case UTIL_FORMAT_SWIZZLE_X: - dst[0] = src[i]; - break; - case UTIL_FORMAT_SWIZZLE_Y: - dst[1] = src[i]; - break; - case UTIL_FORMAT_SWIZZLE_Z: - dst[2] = src[i]; - break; - case UTIL_FORMAT_SWIZZLE_W: - dst[3] = src[i]; - break; - } - } -} diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_format.csv b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_format.csv deleted file mode 100644 index a9e0f84e0..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_format.csv +++ /dev/null @@ -1,401 +0,0 @@ -########################################################################### -# -# Copyright 2009-2010 VMware, Inc. -# All Rights Reserved. -# -# 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, sub license, 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 (including the -# next paragraph) 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 NON-INFRINGEMENT. -# IN NO EVENT SHALL THE AUTHORS AND/OR ITS SUPPLIERS 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. -# -########################################################################### - -# This CSV file has the input data for u_format.h's struct -# util_format_description. -# -# Each format entry contains: -# - name, per enum pipe_format -# - layout, per enum util_format_layout, in shortened lower caps -# - pixel block's width -# - pixel block's height -# - channel encoding (only meaningful for plain layout), containing for each -# channel the following information: -# - type, one of -# - 'x': void -# - 'u': unsigned -# - 's': signed -# - 'h': fixed -# - 'f': FLOAT -# - optionally followed by 'n' if it is normalized -# - optionally followed by 'p' if it is pure -# - number of bits -# - channel swizzle -# - color space: rgb, yub, sz -# - (optional) channel encoding for big-endian targets -# - (optional) channel swizzle for big-endian targets -# -# See also: -# - http://msdn.microsoft.com/en-us/library/bb172558.aspx (D3D9) -# - http://msdn.microsoft.com/en-us/library/bb205073.aspx#mapping_texture_formats (D3D9 -> D3D10) -# - http://msdn.microsoft.com/en-us/library/bb173059.aspx (D3D10) -# -# Note that GL doesn't really specify the layout of internal formats. See -# OpenGL 2.1 specification, Table 3.16, on the "Correspondence of sized -# internal formats to base in- ternal formats, and desired component -# resolutions for each sized internal format." - -# None -# Described as regular uint_8 bytes, i.e. PIPE_FORMAT_R8_USCALED -PIPE_FORMAT_NONE , plain, 1, 1, u8 , , , , x001, rgb - -# Typical rendertarget formats -PIPE_FORMAT_B8G8R8A8_UNORM , plain, 1, 1, un8 , un8 , un8 , un8 , zyxw, rgb -PIPE_FORMAT_B8G8R8X8_UNORM , plain, 1, 1, un8 , un8 , un8 , x8 , zyx1, rgb -PIPE_FORMAT_A8R8G8B8_UNORM , plain, 1, 1, un8 , un8 , un8 , un8 , yzwx, rgb -PIPE_FORMAT_X8R8G8B8_UNORM , plain, 1, 1, x8 , un8 , un8 , un8 , yzw1, rgb -PIPE_FORMAT_A8B8G8R8_UNORM , plain, 1, 1, un8 , un8 , un8 , un8 , wzyx, rgb -PIPE_FORMAT_X8B8G8R8_UNORM , plain, 1, 1, x8 , un8 , un8 , un8 , wzy1, rgb -# PIPE_FORMAT_R8G8B8A8_UNORM is below -PIPE_FORMAT_R8G8B8X8_UNORM , plain, 1, 1, un8 , un8 , un8 , x8 , xyz1, rgb -PIPE_FORMAT_B5G5R5X1_UNORM , plain, 1, 1, un5 , un5 , un5 , x1 , zyx1, rgb, x1 , un5 , un5 , un5 , yzw1 -PIPE_FORMAT_B5G5R5A1_UNORM , plain, 1, 1, un5 , un5 , un5 , un1 , zyxw, rgb, un1 , un5 , un5 , un5 , yzwx -PIPE_FORMAT_B4G4R4A4_UNORM , plain, 1, 1, un4 , un4 , un4 , un4 , zyxw, rgb, un4 , un4 , un4 , un4 , yzwx -PIPE_FORMAT_B4G4R4X4_UNORM , plain, 1, 1, un4 , un4 , un4 , x4 , zyx1, rgb, x4 , un4 , un4 , un4 , yzw1 -PIPE_FORMAT_A4B4G4R4_UNORM , plain, 1, 1, un4 , un4 , un4 , un4 , wzyx, rgb, un4 , un4 , un4 , un4 , xyzw -PIPE_FORMAT_B5G6R5_UNORM , plain, 1, 1, un5 , un6 , un5 , , zyx1, rgb, un5 , un6 , un5 , , xyz1 -PIPE_FORMAT_R10G10B10A2_UNORM , plain, 1, 1, un10, un10, un10, un2 , xyzw, rgb, un2 , un10, un10, un10, wzyx -PIPE_FORMAT_R10G10B10X2_UNORM , plain, 1, 1, un10, un10, un10, x2, xyz1, rgb, x2 , un10, un10, un10, wzy1 -PIPE_FORMAT_B10G10R10A2_UNORM , plain, 1, 1, un10, un10, un10, un2 , zyxw, rgb, un2 , un10, un10, un10, yzwx -PIPE_FORMAT_B2G3R3_UNORM , plain, 1, 1, un2 , un3 , un3 , , zyx1, rgb, un3 , un3 , un2 , , xyz1 - -# Luminance/Intensity/Alpha formats -PIPE_FORMAT_L8_UNORM , plain, 1, 1, un8 , , , , xxx1, rgb -PIPE_FORMAT_A8_UNORM , plain, 1, 1, un8 , , , , 000x, rgb -PIPE_FORMAT_I8_UNORM , plain, 1, 1, un8 , , , , xxxx, rgb -PIPE_FORMAT_L4A4_UNORM , plain, 1, 1, un4 , un4 , , , xxxy, rgb, un4 , un4 , , , yyyx -PIPE_FORMAT_L8A8_UNORM , plain, 1, 1, un8 , un8 , , , xxxy, rgb -PIPE_FORMAT_L16_UNORM , plain, 1, 1, un16, , , , xxx1, rgb -PIPE_FORMAT_A16_UNORM , plain, 1, 1, un16, , , , 000x, rgb -PIPE_FORMAT_I16_UNORM , plain, 1, 1, un16, , , , xxxx, rgb -PIPE_FORMAT_L16A16_UNORM , plain, 1, 1, un16, un16, , , xxxy, rgb -PIPE_FORMAT_A8_SNORM , plain, 1, 1, sn8 , , , , 000x, rgb -PIPE_FORMAT_L8_SNORM , plain, 1, 1, sn8 , , , , xxx1, rgb -PIPE_FORMAT_L8A8_SNORM , plain, 1, 1, sn8 , sn8 , , , xxxy, rgb -PIPE_FORMAT_I8_SNORM , plain, 1, 1, sn8 , , , , xxxx, rgb -PIPE_FORMAT_A16_SNORM , plain, 1, 1, sn16, , , , 000x, rgb -PIPE_FORMAT_L16_SNORM , plain, 1, 1, sn16, , , , xxx1, rgb -PIPE_FORMAT_L16A16_SNORM , plain, 1, 1, sn16, sn16, , , xxxy, rgb -PIPE_FORMAT_I16_SNORM , plain, 1, 1, sn16, , , , xxxx, rgb -PIPE_FORMAT_A16_FLOAT , plain, 1, 1, f16 , , , , 000x, rgb -PIPE_FORMAT_L16_FLOAT , plain, 1, 1, f16 , , , , xxx1, rgb -PIPE_FORMAT_L16A16_FLOAT , plain, 1, 1, f16 , f16 , , , xxxy, rgb -PIPE_FORMAT_I16_FLOAT , plain, 1, 1, f16 , , , , xxxx, rgb -PIPE_FORMAT_A32_FLOAT , plain, 1, 1, f32 , , , , 000x, rgb -PIPE_FORMAT_L32_FLOAT , plain, 1, 1, f32 , , , , xxx1, rgb -PIPE_FORMAT_L32A32_FLOAT , plain, 1, 1, f32 , f32 , , , xxxy, rgb -PIPE_FORMAT_I32_FLOAT , plain, 1, 1, f32 , , , , xxxx, rgb - -# SRGB formats -PIPE_FORMAT_L8_SRGB , plain, 1, 1, un8 , , , , xxx1, srgb -PIPE_FORMAT_R8_SRGB , plain, 1, 1, un8 , , , , x001, srgb -PIPE_FORMAT_L8A8_SRGB , plain, 1, 1, un8 , un8 , , , xxxy, srgb -PIPE_FORMAT_R8G8B8_SRGB , plain, 1, 1, un8 , un8 , un8 , , xyz1, srgb -PIPE_FORMAT_R8G8B8A8_SRGB , plain, 1, 1, un8 , un8 , un8 , un8 , xyzw, srgb -PIPE_FORMAT_A8B8G8R8_SRGB , plain, 1, 1, un8 , un8 , un8 , un8 , wzyx, srgb -PIPE_FORMAT_X8B8G8R8_SRGB , plain, 1, 1, x8 , un8 , un8 , un8 , wzy1, srgb -PIPE_FORMAT_B8G8R8A8_SRGB , plain, 1, 1, un8 , un8 , un8 , un8 , zyxw, srgb -PIPE_FORMAT_B8G8R8X8_SRGB , plain, 1, 1, un8 , un8 , un8 , x8 , zyx1, srgb -PIPE_FORMAT_A8R8G8B8_SRGB , plain, 1, 1, un8 , un8 , un8 , un8 , yzwx, srgb -PIPE_FORMAT_X8R8G8B8_SRGB , plain, 1, 1, x8 , un8 , un8 , un8 , yzw1, srgb - -# Mixed-sign formats (typically used for bump map textures) -PIPE_FORMAT_R8SG8SB8UX8U_NORM , plain, 1, 1, sn8 , sn8 , un8 , x8 , xyz1, rgb -PIPE_FORMAT_R10SG10SB10SA2U_NORM , plain, 1, 1, sn10, sn10, sn10, un2 , xyzw, rgb, un2 , sn10, sn10, sn10, wzyx -PIPE_FORMAT_R5SG5SB6U_NORM , plain, 1, 1, sn5 , sn5 , un6 , , xyz1, rgb, un6 , sn5 , sn5 , , zyx1 - -# Depth-stencil formats -PIPE_FORMAT_S8_UINT , plain, 1, 1, up8 , , , , _x__, zs -PIPE_FORMAT_Z16_UNORM , plain, 1, 1, un16, , , , x___, zs -PIPE_FORMAT_Z32_UNORM , plain, 1, 1, un32, , , , x___, zs -PIPE_FORMAT_Z32_FLOAT , plain, 1, 1, f32 , , , , x___, zs -PIPE_FORMAT_Z24_UNORM_S8_UINT , plain, 1, 1, un24, up8 , , , xy__, zs, up8 , un24, , , yx__ -PIPE_FORMAT_S8_UINT_Z24_UNORM , plain, 1, 1, up8 , un24, , , yx__, zs, un24, up8 , , , xy__ -PIPE_FORMAT_X24S8_UINT , plain, 1, 1, x24 , up8 , , , _y__, zs, up8 , x24 , , , _x__ -PIPE_FORMAT_S8X24_UINT , plain, 1, 1, up8 , x24 , , , _x__, zs, x24 , up8 , , , _y__ -PIPE_FORMAT_Z24X8_UNORM , plain, 1, 1, un24, x8 , , , x___, zs, x8 , un24, , , y___ -PIPE_FORMAT_X8Z24_UNORM , plain, 1, 1, x8 , un24, , , y___, zs, un24, x8 , , , x___ -PIPE_FORMAT_Z32_FLOAT_S8X24_UINT , plain, 1, 1, f32 , up8 , x24, , xy__, zs, f32 , x24 , up8, , xz__ -PIPE_FORMAT_X32_S8X24_UINT , plain, 1, 1, x32 , up8 , x24, , _y__, zs, x32 , x24 , up8, , _z__ - -# YUV formats -# http://www.fourcc.org/yuv.php#UYVY -PIPE_FORMAT_UYVY , subsampled, 2, 1, x32 , , , , xyz1, yuv -# http://www.fourcc.org/yuv.php#YUYV (a.k.a http://www.fourcc.org/yuv.php#YUY2) -PIPE_FORMAT_YUYV , subsampled, 2, 1, x32 , , , , xyz1, yuv -# same subsampling but with rgb channels -PIPE_FORMAT_R8G8_B8G8_UNORM , subsampled, 2, 1, x32 , , , , xyz1, rgb -PIPE_FORMAT_G8R8_G8B8_UNORM , subsampled, 2, 1, x32 , , , , xyz1, rgb -PIPE_FORMAT_G8R8_B8R8_UNORM , subsampled, 2, 1, x32 , , , , yxz1, rgb -PIPE_FORMAT_R8G8_R8B8_UNORM , subsampled, 2, 1, x32 , , , , yxz1, rgb - -# some special formats not fitting anywhere else -PIPE_FORMAT_R11G11B10_FLOAT , other, 1, 1, x32 , , , , xyz1, rgb -PIPE_FORMAT_R9G9B9E5_FLOAT , other, 1, 1, x32 , , , , xyz1, rgb -PIPE_FORMAT_R1_UNORM , other, 8, 1, x8 , , , , x001, rgb -# A.k.a. D3DFMT_CxV8U8 -PIPE_FORMAT_R8G8Bx_SNORM , other, 1, 1, sn8 , sn8 , , , xyz1, rgb - -# Compressed formats -# - http://en.wikipedia.org/wiki/S3_Texture_Compression -# - http://www.opengl.org/registry/specs/EXT/texture_compression_s3tc.txt -# - http://www.opengl.org/registry/specs/ARB/texture_compression_rgtc.txt -# - http://www.opengl.org/registry/specs/EXT/texture_compression_latc.txt -# - http://www.opengl.org/registry/specs/ARB/texture_compression_bptc.txt -# - http://www.khronos.org/registry/gles/extensions/OES/OES_compressed_ETC1_RGB8_texture.txt -# - http://msdn.microsoft.com/en-us/library/bb694531.aspx -PIPE_FORMAT_DXT1_RGB , s3tc, 4, 4, x64 , , , , xyz1, rgb -PIPE_FORMAT_DXT1_RGBA , s3tc, 4, 4, x64 , , , , xyzw, rgb -PIPE_FORMAT_DXT3_RGBA , s3tc, 4, 4, x128, , , , xyzw, rgb -PIPE_FORMAT_DXT5_RGBA , s3tc, 4, 4, x128, , , , xyzw, rgb -PIPE_FORMAT_DXT1_SRGB , s3tc, 4, 4, x64 , , , , xyz1, srgb -PIPE_FORMAT_DXT1_SRGBA , s3tc, 4, 4, x64 , , , , xyzw, srgb -PIPE_FORMAT_DXT3_SRGBA , s3tc, 4, 4, x128, , , , xyzw, srgb -PIPE_FORMAT_DXT5_SRGBA , s3tc, 4, 4, x128, , , , xyzw, srgb - -PIPE_FORMAT_RGTC1_UNORM , rgtc, 4, 4, x64, , , , x001, rgb -PIPE_FORMAT_RGTC1_SNORM , rgtc, 4, 4, x64, , , , x001, rgb -PIPE_FORMAT_RGTC2_UNORM , rgtc, 4, 4, x128, , , , xy01, rgb -PIPE_FORMAT_RGTC2_SNORM , rgtc, 4, 4, x128, , , , xy01, rgb - -PIPE_FORMAT_LATC1_UNORM , rgtc, 4, 4, x64, , , , xxx1, rgb -PIPE_FORMAT_LATC1_SNORM , rgtc, 4, 4, x64, , , , xxx1, rgb -PIPE_FORMAT_LATC2_UNORM , rgtc, 4, 4, x128, , , , xxxy, rgb -PIPE_FORMAT_LATC2_SNORM , rgtc, 4, 4, x128, , , , xxxy, rgb - -PIPE_FORMAT_ETC1_RGB8 , etc, 4, 4, x64, , , , xyz1, rgb - -PIPE_FORMAT_BPTC_RGBA_UNORM , bptc, 4, 4, x128, , , , xyzw, rgb -PIPE_FORMAT_BPTC_SRGBA , bptc, 4, 4, x128, , , , xyzw, srgb -PIPE_FORMAT_BPTC_RGB_FLOAT , bptc, 4, 4, x128, , , , xyz1, rgb -PIPE_FORMAT_BPTC_RGB_UFLOAT , bptc, 4, 4, x128, , , , xyz1, rgb - -# Straightforward D3D10-like formats (also used for -# vertex buffer element description) -# -# See also: -# - src/gallium/auxiliary/translate/translate_generic.c -# - src/mesa/state_tracker/st_draw.c -PIPE_FORMAT_R64_FLOAT , plain, 1, 1, f64 , , , , x001, rgb -PIPE_FORMAT_R64G64_FLOAT , plain, 1, 1, f64 , f64 , , , xy01, rgb -PIPE_FORMAT_R64G64B64_FLOAT , plain, 1, 1, f64 , f64 , f64 , , xyz1, rgb -PIPE_FORMAT_R64G64B64A64_FLOAT , plain, 1, 1, f64 , f64 , f64 , f64 , xyzw, rgb -PIPE_FORMAT_R32_FLOAT , plain, 1, 1, f32 , , , , x001, rgb -PIPE_FORMAT_R32G32_FLOAT , plain, 1, 1, f32 , f32 , , , xy01, rgb -PIPE_FORMAT_R32G32B32_FLOAT , plain, 1, 1, f32 , f32 , f32 , , xyz1, rgb -PIPE_FORMAT_R32G32B32A32_FLOAT , plain, 1, 1, f32 , f32 , f32 , f32 , xyzw, rgb -PIPE_FORMAT_R32_UNORM , plain, 1, 1, un32, , , , x001, rgb -PIPE_FORMAT_R32G32_UNORM , plain, 1, 1, un32, un32, , , xy01, rgb -PIPE_FORMAT_R32G32B32_UNORM , plain, 1, 1, un32, un32, un32, , xyz1, rgb -PIPE_FORMAT_R32G32B32A32_UNORM , plain, 1, 1, un32, un32, un32, un32, xyzw, rgb -PIPE_FORMAT_R32_USCALED , plain, 1, 1, u32 , , , , x001, rgb -PIPE_FORMAT_R32G32_USCALED , plain, 1, 1, u32 , u32 , , , xy01, rgb -PIPE_FORMAT_R32G32B32_USCALED , plain, 1, 1, u32 , u32 , u32 , , xyz1, rgb -PIPE_FORMAT_R32G32B32A32_USCALED , plain, 1, 1, u32 , u32 , u32 , u32 , xyzw, rgb -PIPE_FORMAT_R32_SNORM , plain, 1, 1, sn32, , , , x001, rgb -PIPE_FORMAT_R32G32_SNORM , plain, 1, 1, sn32, sn32, , , xy01, rgb -PIPE_FORMAT_R32G32B32_SNORM , plain, 1, 1, sn32, sn32, sn32, , xyz1, rgb -PIPE_FORMAT_R32G32B32A32_SNORM , plain, 1, 1, sn32, sn32, sn32, sn32, xyzw, rgb -PIPE_FORMAT_R32_SSCALED , plain, 1, 1, s32 , , , , x001, rgb -PIPE_FORMAT_R32G32_SSCALED , plain, 1, 1, s32 , s32 , , , xy01, rgb -PIPE_FORMAT_R32G32B32_SSCALED , plain, 1, 1, s32 , s32 , s32 , , xyz1, rgb -PIPE_FORMAT_R32G32B32A32_SSCALED , plain, 1, 1, s32 , s32 , s32 , s32 , xyzw, rgb -PIPE_FORMAT_R16_FLOAT , plain, 1, 1, f16 , , , , x001, rgb -PIPE_FORMAT_R16G16_FLOAT , plain, 1, 1, f16 , f16 , , , xy01, rgb -PIPE_FORMAT_R16G16B16_FLOAT , plain, 1, 1, f16 , f16 , f16 , , xyz1, rgb -PIPE_FORMAT_R16G16B16A16_FLOAT , plain, 1, 1, f16 , f16 , f16 , f16 , xyzw, rgb -PIPE_FORMAT_R16_UNORM , plain, 1, 1, un16, , , , x001, rgb -PIPE_FORMAT_R16G16_UNORM , plain, 1, 1, un16, un16, , , xy01, rgb -PIPE_FORMAT_R16G16B16_UNORM , plain, 1, 1, un16, un16, un16, , xyz1, rgb -PIPE_FORMAT_R16G16B16A16_UNORM , plain, 1, 1, un16, un16, un16, un16, xyzw, rgb -PIPE_FORMAT_R16_USCALED , plain, 1, 1, u16 , , , , x001, rgb -PIPE_FORMAT_R16G16_USCALED , plain, 1, 1, u16 , u16 , , , xy01, rgb -PIPE_FORMAT_R16G16B16_USCALED , plain, 1, 1, u16 , u16 , u16 , , xyz1, rgb -PIPE_FORMAT_R16G16B16A16_USCALED , plain, 1, 1, u16 , u16 , u16 , u16 , xyzw, rgb -PIPE_FORMAT_R16_SNORM , plain, 1, 1, sn16, , , , x001, rgb -PIPE_FORMAT_R16G16_SNORM , plain, 1, 1, sn16, sn16, , , xy01, rgb -PIPE_FORMAT_R16G16B16_SNORM , plain, 1, 1, sn16, sn16, sn16, , xyz1, rgb -PIPE_FORMAT_R16G16B16A16_SNORM , plain, 1, 1, sn16, sn16, sn16, sn16, xyzw, rgb -PIPE_FORMAT_R16_SSCALED , plain, 1, 1, s16 , , , , x001, rgb -PIPE_FORMAT_R16G16_SSCALED , plain, 1, 1, s16 , s16 , , , xy01, rgb -PIPE_FORMAT_R16G16B16_SSCALED , plain, 1, 1, s16 , s16 , s16 , , xyz1, rgb -PIPE_FORMAT_R16G16B16A16_SSCALED , plain, 1, 1, s16 , s16 , s16 , s16 , xyzw, rgb -PIPE_FORMAT_R8_UNORM , plain, 1, 1, un8 , , , , x001, rgb -PIPE_FORMAT_R8G8_UNORM , plain, 1, 1, un8 , un8 , , , xy01, rgb -PIPE_FORMAT_R8G8B8_UNORM , plain, 1, 1, un8 , un8 , un8 , , xyz1, rgb -PIPE_FORMAT_R8G8B8A8_UNORM , plain, 1, 1, un8 , un8 , un8 , un8 , xyzw, rgb -PIPE_FORMAT_R8_USCALED , plain, 1, 1, u8 , , , , x001, rgb -PIPE_FORMAT_R8G8_USCALED , plain, 1, 1, u8 , u8 , , , xy01, rgb -PIPE_FORMAT_R8G8B8_USCALED , plain, 1, 1, u8 , u8 , u8 , , xyz1, rgb -PIPE_FORMAT_R8G8B8A8_USCALED , plain, 1, 1, u8 , u8 , u8 , u8 , xyzw, rgb -PIPE_FORMAT_R8_SNORM , plain, 1, 1, sn8 , , , , x001, rgb -PIPE_FORMAT_R8G8_SNORM , plain, 1, 1, sn8 , sn8 , , , xy01, rgb -PIPE_FORMAT_R8G8B8_SNORM , plain, 1, 1, sn8 , sn8 , sn8 , , xyz1, rgb -PIPE_FORMAT_R8G8B8A8_SNORM , plain, 1, 1, sn8 , sn8 , sn8 , sn8 , xyzw, rgb -PIPE_FORMAT_R8_SSCALED , plain, 1, 1, s8 , , , , x001, rgb -PIPE_FORMAT_R8G8_SSCALED , plain, 1, 1, s8 , s8 , , , xy01, rgb -PIPE_FORMAT_R8G8B8_SSCALED , plain, 1, 1, s8 , s8 , s8 , , xyz1, rgb -PIPE_FORMAT_R8G8B8A8_SSCALED , plain, 1, 1, s8 , s8 , s8 , s8 , xyzw, rgb - -# GL-specific vertex buffer element formats -# A.k.a. GL_FIXED -PIPE_FORMAT_R32_FIXED , plain, 1, 1, h32 , , , , x001, rgb -PIPE_FORMAT_R32G32_FIXED , plain, 1, 1, h32 , h32 , , , xy01, rgb -PIPE_FORMAT_R32G32B32_FIXED , plain, 1, 1, h32 , h32 , h32 , , xyz1, rgb -PIPE_FORMAT_R32G32B32A32_FIXED , plain, 1, 1, h32 , h32 , h32 , h32 , xyzw, rgb - -# D3D9-specific vertex buffer element formats -# See also: -# - http://msdn.microsoft.com/en-us/library/bb172533.aspx -# A.k.a. D3DDECLTYPE_UDEC3 -PIPE_FORMAT_R10G10B10X2_USCALED , plain, 1, 1, u10 , u10 , u10 , x2 , xyz1, rgb, x2 , u10 , u10 , u10 , wzy1 -# A.k.a. D3DDECLTYPE_DEC3N -PIPE_FORMAT_R10G10B10X2_SNORM , plain, 1, 1, sn10, sn10, sn10 , x2 , xyz1, rgb, x2 , sn10, sn10, sn10, wzy1 - -PIPE_FORMAT_YV12 , other, 1, 1, x8 , x8 , x8 , x8 , xyzw, yuv -PIPE_FORMAT_YV16 , other, 1, 1, x8 , x8 , x8 , x8 , xyzw, yuv -PIPE_FORMAT_IYUV , other, 1, 1, x8 , x8 , x8 , x8 , xyzw, yuv -PIPE_FORMAT_NV12 , other, 1, 1, x8 , x8 , x8 , x8 , xyzw, yuv -PIPE_FORMAT_NV21 , other, 1, 1, x8 , x8 , x8 , x8 , xyzw, yuv - -# Usually used to implement IA44 and AI44 formats in video decoding -PIPE_FORMAT_A4R4_UNORM , plain, 1, 1, un4 , un4 , , , y00x, rgb, un4, un4 , , , x00y -PIPE_FORMAT_R4A4_UNORM , plain, 1, 1, un4 , un4 , , , x00y, rgb, un4, un4 , , , y00x -PIPE_FORMAT_R8A8_UNORM , plain, 1, 1, un8 , un8 , , , x00y, rgb -PIPE_FORMAT_A8R8_UNORM , plain, 1, 1, un8 , un8 , , , y00x, rgb - -# ARB_vertex_type_10_10_10_2_REV -PIPE_FORMAT_R10G10B10A2_USCALED , plain, 1, 1, u10 , u10 , u10 , u2 , xyzw, rgb, u2 , u10 , u10 , u10 , wzyx -PIPE_FORMAT_R10G10B10A2_SSCALED , plain, 1, 1, s10 , s10 , s10 , s2 , xyzw, rgb, s2 , s10 , s10 , s10 , wzyx -PIPE_FORMAT_R10G10B10A2_SNORM , plain, 1, 1, sn10, sn10, sn10, sn2 , xyzw, rgb, sn2 , sn10, sn10, sn10, wzyx -PIPE_FORMAT_B10G10R10A2_USCALED , plain, 1, 1, u10 , u10 , u10 , u2 , zyxw, rgb, u2 , u10 , u10 , u10 , yzwx -PIPE_FORMAT_B10G10R10A2_SSCALED , plain, 1, 1, s10 , s10 , s10 , s2 , zyxw, rgb, s2 , s10 , s10 , s10 , yzwx -PIPE_FORMAT_B10G10R10A2_SNORM , plain, 1, 1, sn10, sn10, sn10, sn2 , zyxw, rgb, sn2 , sn10, sn10, sn10, yzwx - -PIPE_FORMAT_R8_UINT , plain, 1, 1, up8, , , , x001, rgb -PIPE_FORMAT_R8G8_UINT , plain, 1, 1, up8, up8, , , xy01, rgb -PIPE_FORMAT_R8G8B8_UINT , plain, 1, 1, up8, up8, up8, , xyz1, rgb -PIPE_FORMAT_R8G8B8A8_UINT , plain, 1, 1, up8, up8, up8, up8, xyzw, rgb - -PIPE_FORMAT_R8_SINT , plain, 1, 1, sp8, , , , x001, rgb -PIPE_FORMAT_R8G8_SINT , plain, 1, 1, sp8, sp8, , , xy01, rgb -PIPE_FORMAT_R8G8B8_SINT , plain, 1, 1, sp8, sp8, sp8, , xyz1, rgb -PIPE_FORMAT_R8G8B8A8_SINT , plain, 1, 1, sp8, sp8, sp8, sp8, xyzw, rgb - -PIPE_FORMAT_R16_UINT , plain, 1, 1, up16, , , , x001, rgb -PIPE_FORMAT_R16G16_UINT , plain, 1, 1, up16, up16, , , xy01, rgb -PIPE_FORMAT_R16G16B16_UINT , plain, 1, 1, up16, up16, up16, , xyz1, rgb -PIPE_FORMAT_R16G16B16A16_UINT , plain, 1, 1, up16, up16, up16, up16, xyzw, rgb - -PIPE_FORMAT_R16_SINT , plain, 1, 1, sp16, , , , x001, rgb -PIPE_FORMAT_R16G16_SINT , plain, 1, 1, sp16, sp16, , , xy01, rgb -PIPE_FORMAT_R16G16B16_SINT , plain, 1, 1, sp16, sp16, sp16, , xyz1, rgb -PIPE_FORMAT_R16G16B16A16_SINT , plain, 1, 1, sp16, sp16, sp16, sp16, xyzw, rgb - -PIPE_FORMAT_R32_UINT , plain, 1, 1, up32, , , , x001, rgb -PIPE_FORMAT_R32G32_UINT , plain, 1, 1, up32, up32, , , xy01, rgb -PIPE_FORMAT_R32G32B32_UINT , plain, 1, 1, up32, up32, up32, , xyz1, rgb -PIPE_FORMAT_R32G32B32A32_UINT , plain, 1, 1, up32, up32, up32, up32, xyzw, rgb - -PIPE_FORMAT_R32_SINT , plain, 1, 1, sp32, , , , x001, rgb -PIPE_FORMAT_R32G32_SINT , plain, 1, 1, sp32, sp32, , , xy01, rgb -PIPE_FORMAT_R32G32B32_SINT , plain, 1, 1, sp32, sp32, sp32, , xyz1, rgb -PIPE_FORMAT_R32G32B32A32_SINT , plain, 1, 1, sp32, sp32, sp32, sp32, xyzw, rgb - -PIPE_FORMAT_A8_UINT , plain, 1, 1, up8, , , , 000x, rgb -PIPE_FORMAT_I8_UINT , plain, 1, 1, up8, , , , xxxx, rgb -PIPE_FORMAT_L8_UINT , plain, 1, 1, up8, , , , xxx1, rgb -PIPE_FORMAT_L8A8_UINT , plain, 1, 1, up8, up8, , , xxxy, rgb - -PIPE_FORMAT_A8_SINT , plain, 1, 1, sp8, , , , 000x, rgb -PIPE_FORMAT_I8_SINT , plain, 1, 1, sp8, , , , xxxx, rgb -PIPE_FORMAT_L8_SINT , plain, 1, 1, sp8, , , , xxx1, rgb -PIPE_FORMAT_L8A8_SINT , plain, 1, 1, sp8, sp8, , , xxxy, rgb - -PIPE_FORMAT_A16_UINT , plain, 1, 1, up16, , , , 000x, rgb -PIPE_FORMAT_I16_UINT , plain, 1, 1, up16, , , , xxxx, rgb -PIPE_FORMAT_L16_UINT , plain, 1, 1, up16, , , , xxx1, rgb -PIPE_FORMAT_L16A16_UINT , plain, 1, 1, up16, up16, , , xxxy, rgb - -PIPE_FORMAT_A16_SINT , plain, 1, 1, sp16, , , , 000x, rgb -PIPE_FORMAT_I16_SINT , plain, 1, 1, sp16, , , , xxxx, rgb -PIPE_FORMAT_L16_SINT , plain, 1, 1, sp16, , , , xxx1, rgb -PIPE_FORMAT_L16A16_SINT , plain, 1, 1, sp16, sp16, , , xxxy, rgb - -PIPE_FORMAT_A32_UINT , plain, 1, 1, up32, , , , 000x, rgb -PIPE_FORMAT_I32_UINT , plain, 1, 1, up32, , , , xxxx, rgb -PIPE_FORMAT_L32_UINT , plain, 1, 1, up32, , , , xxx1, rgb -PIPE_FORMAT_L32A32_UINT , plain, 1, 1, up32, up32, , , xxxy, rgb - -PIPE_FORMAT_A32_SINT , plain, 1, 1, sp32, , , , 000x, rgb -PIPE_FORMAT_I32_SINT , plain, 1, 1, sp32, , , , xxxx, rgb -PIPE_FORMAT_L32_SINT , plain, 1, 1, sp32, , , , xxx1, rgb -PIPE_FORMAT_L32A32_SINT , plain, 1, 1, sp32, sp32, , , xxxy, rgb - -PIPE_FORMAT_B10G10R10A2_UINT , plain, 1, 1, up10, up10, up10, up2, zyxw, rgb, up2 , up10, up10, up10, yzwx - -PIPE_FORMAT_R8G8B8X8_SNORM , plain, 1, 1, sn8, sn8, sn8, x8, xyz1, rgb -PIPE_FORMAT_R8G8B8X8_SRGB , plain, 1, 1, un8, un8, un8, x8, xyz1, srgb -PIPE_FORMAT_R8G8B8X8_UINT , plain, 1, 1, up8, up8, up8, x8, xyz1, rgb -PIPE_FORMAT_R8G8B8X8_SINT , plain, 1, 1, sp8, sp8, sp8, x8, xyz1, rgb -PIPE_FORMAT_B10G10R10X2_UNORM , plain, 1, 1, un10, un10, un10, x2, zyx1, rgb, x2 , un10, un10, un10, yzw1 -PIPE_FORMAT_R16G16B16X16_UNORM , plain, 1, 1, un16, un16, un16, x16, xyz1, rgb -PIPE_FORMAT_R16G16B16X16_SNORM , plain, 1, 1, sn16, sn16, sn16, x16, xyz1, rgb -PIPE_FORMAT_R16G16B16X16_FLOAT , plain, 1, 1, f16, f16, f16, x16, xyz1, rgb -PIPE_FORMAT_R16G16B16X16_UINT , plain, 1, 1, up16, up16, up16, x16, xyz1, rgb -PIPE_FORMAT_R16G16B16X16_SINT , plain, 1, 1, sp16, sp16, sp16, x16, xyz1, rgb -PIPE_FORMAT_R32G32B32X32_FLOAT , plain, 1, 1, f32, f32, f32, x32, xyz1, rgb -PIPE_FORMAT_R32G32B32X32_UINT , plain, 1, 1, up32, up32, up32, x32, xyz1, rgb -PIPE_FORMAT_R32G32B32X32_SINT , plain, 1, 1, sp32, sp32, sp32, x32, xyz1, rgb - -PIPE_FORMAT_R8A8_SNORM , plain, 1, 1, sn8 , sn8 , , , x00y, rgb -PIPE_FORMAT_R16A16_UNORM , plain, 1, 1, un16 , un16 , , , x00y, rgb -PIPE_FORMAT_R16A16_SNORM , plain, 1, 1, sn16 , sn16 , , , x00y, rgb -PIPE_FORMAT_R16A16_FLOAT , plain, 1, 1, f16 , f16 , , , x00y, rgb -PIPE_FORMAT_R32A32_FLOAT , plain, 1, 1, f32 , f32 , , , x00y, rgb -PIPE_FORMAT_R8A8_UINT , plain, 1, 1, up8 , up8 , , , x00y, rgb -PIPE_FORMAT_R8A8_SINT , plain, 1, 1, sp8 , sp8 , , , x00y, rgb -PIPE_FORMAT_R16A16_UINT , plain, 1, 1, up16 , up16 , , , x00y, rgb -PIPE_FORMAT_R16A16_SINT , plain, 1, 1, sp16 , sp16 , , , x00y, rgb -PIPE_FORMAT_R32A32_UINT , plain, 1, 1, up32 , up32 , , , x00y, rgb -PIPE_FORMAT_R32A32_SINT , plain, 1, 1, sp32 , sp32 , , , x00y, rgb -PIPE_FORMAT_R10G10B10A2_UINT , plain, 1, 1, up10 , up10 , up10, up2 , xyzw, rgb, up2 , up10, up10, up10, wzyx - -PIPE_FORMAT_B5G6R5_SRGB , plain, 1, 1, un5 , un6 , un5 , , zyx1, srgb, un5 , un6 , un5 , , xyz1 - -PIPE_FORMAT_A8L8_UNORM , plain, 1, 1, un8 , un8 , , , yyyx, rgb -PIPE_FORMAT_A8L8_SNORM , plain, 1, 1, sn8 , sn8 , , , yyyx, rgb -PIPE_FORMAT_A8L8_SRGB , plain, 1, 1, un8 , un8 , , , yyyx, srgb -PIPE_FORMAT_A16L16_UNORM , plain, 1, 1, un16, un16, , , yyyx, rgb - -PIPE_FORMAT_G8R8_UNORM , plain, 1, 1, un8 , un8 , , , yx01, rgb -PIPE_FORMAT_G8R8_SNORM , plain, 1, 1, sn8 , sn8 , , , yx01, rgb -PIPE_FORMAT_G16R16_UNORM , plain, 1, 1, un16, un16, , , yx01, rgb -PIPE_FORMAT_G16R16_SNORM , plain, 1, 1, sn16, sn16, , , yx01, rgb - -PIPE_FORMAT_A8B8G8R8_SNORM , plain, 1, 1, sn8 , sn8 , sn8 , sn8 , wzyx, rgb -PIPE_FORMAT_X8B8G8R8_SNORM , plain, 1, 1, x8, sn8, sn8, sn8, wzy1, rgb diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_format.h b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_format.h deleted file mode 100644 index 2eab8a1d4..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_format.h +++ /dev/null @@ -1,916 +0,0 @@ -/************************************************************************** - * - * Copyright 2009-2010 Vmware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -#ifndef U_FORMAT_H -#define U_FORMAT_H - -#include "pipe/p_defines.h" -#include "pipe/p_format.h" -#include "util/u_debug.h" - -union pipe_color_union; - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * Describe how to pack/unpack pixels into/from the prescribed format. - * - * XXX: This could be renamed to something like util_format_pack, or broke down - * in flags inside util_format_block that said exactly what we want. - */ -enum util_format_layout { - /** - * Formats with util_format_block::width == util_format_block::height == 1 - * that can be described as an ordinary data structure. - */ - UTIL_FORMAT_LAYOUT_PLAIN = 0, - - /** - * Formats with sub-sampled channels. - * - * This is for formats like YVYU where there is less than one sample per - * pixel. - */ - UTIL_FORMAT_LAYOUT_SUBSAMPLED = 3, - - /** - * S3 Texture Compression formats. - */ - UTIL_FORMAT_LAYOUT_S3TC = 4, - - /** - * Red-Green Texture Compression formats. - */ - UTIL_FORMAT_LAYOUT_RGTC = 5, - - /** - * Ericsson Texture Compression - */ - UTIL_FORMAT_LAYOUT_ETC = 6, - - /** - * BC6/7 Texture Compression - */ - UTIL_FORMAT_LAYOUT_BPTC = 7, - - /** - * Everything else that doesn't fit in any of the above layouts. - */ - UTIL_FORMAT_LAYOUT_OTHER = 8 -}; - -struct util_format_block { - /** Block width in pixels */ - unsigned width; - - /** Block height in pixels */ - unsigned height; - - /** Block size in bits */ - unsigned bits; -}; - -enum util_format_type { - UTIL_FORMAT_TYPE_VOID = 0, - UTIL_FORMAT_TYPE_UNSIGNED = 1, - UTIL_FORMAT_TYPE_SIGNED = 2, - UTIL_FORMAT_TYPE_FIXED = 3, - UTIL_FORMAT_TYPE_FLOAT = 4 -}; - -enum util_format_swizzle { - UTIL_FORMAT_SWIZZLE_X = 0, - UTIL_FORMAT_SWIZZLE_Y = 1, - UTIL_FORMAT_SWIZZLE_Z = 2, - UTIL_FORMAT_SWIZZLE_W = 3, - UTIL_FORMAT_SWIZZLE_0 = 4, - UTIL_FORMAT_SWIZZLE_1 = 5, - UTIL_FORMAT_SWIZZLE_NONE = 6, - UTIL_FORMAT_SWIZZLE_MAX = 7 /**< Number of enums counter (must be last) */ -}; - -enum util_format_colorspace { - UTIL_FORMAT_COLORSPACE_RGB = 0, - UTIL_FORMAT_COLORSPACE_SRGB = 1, - UTIL_FORMAT_COLORSPACE_YUV = 2, - UTIL_FORMAT_COLORSPACE_ZS = 3 -}; - -struct util_format_channel_description { - unsigned type : 5; /**< UTIL_FORMAT_TYPE_x */ - unsigned normalized : 1; - unsigned pure_integer : 1; - unsigned size : 9; /**< bits per channel */ - unsigned shift : 16; /** number of bits from lsb */ -}; - -struct util_format_description { - enum pipe_format format; - - const char *name; - - /** - * Short name, striped of the prefix, lower case. - */ - const char *short_name; - - /** - * Pixel block dimensions. - */ - struct util_format_block block; - - enum util_format_layout layout; - - /** - * The number of channels. - */ - unsigned nr_channels : 3; - - /** - * Whether all channels have the same number of (whole) bytes and type. - */ - unsigned is_array : 1; - - /** - * Whether the pixel format can be described as a bitfield structure. - * - * In particular: - * - pixel depth must be 8, 16, or 32 bits; - * - all channels must be unsigned, signed, or void - */ - unsigned is_bitmask : 1; - - /** - * Whether channels have mixed types (ignoring UTIL_FORMAT_TYPE_VOID). - */ - unsigned is_mixed : 1; - - /** - * Input channel description, in the order XYZW. - * - * Only valid for UTIL_FORMAT_LAYOUT_PLAIN formats. - * - * If each channel is accessed as an individual N-byte value, X is always - * at the lowest address in memory, Y is always next, and so on. For all - * currently-defined formats, the N-byte value has native endianness. - * - * If instead a group of channels is accessed as a single N-byte value, - * the order of the channels within that value depends on endianness. - * For big-endian targets, X is the most significant subvalue, - * otherwise it is the least significant one. - * - * For example, if X is 8 bits and Y is 24 bits, the memory order is: - * - * 0 1 2 3 - * little-endian: X Yl Ym Yu (l = lower, m = middle, u = upper) - * big-endian: X Yu Ym Yl - * - * If X is 5 bits, Y is 5 bits, Z is 5 bits and W is 1 bit, the layout is: - * - * 0 1 - * msb lsb msb lsb - * little-endian: YYYXXXXX WZZZZZYY - * big-endian: XXXXXYYY YYZZZZZW - */ - struct util_format_channel_description channel[4]; - - /** - * Output channel swizzle. - * - * The order is either: - * - RGBA - * - YUV(A) - * - ZS - * depending on the colorspace. - */ - unsigned char swizzle[4]; - - /** - * Colorspace transformation. - */ - enum util_format_colorspace colorspace; -}; - -extern const struct util_format_description util_format_description_table[]; - -const struct util_format_description * -util_format_description(enum pipe_format format); - -/* - * Format query functions. - */ - -static inline const char *util_format_name(enum pipe_format format) { - const struct util_format_description *desc = util_format_description(format); - - assert(desc); - if (!desc) { - return "PIPE_FORMAT_???"; - } - - return desc->name; -} - -static inline const char *util_format_short_name(enum pipe_format format) { - const struct util_format_description *desc = util_format_description(format); - - assert(desc); - if (!desc) { - return "???"; - } - - return desc->short_name; -} - -/** - * Whether this format is plain, see UTIL_FORMAT_LAYOUT_PLAIN for more info. - */ -static inline boolean util_format_is_plain(enum pipe_format format) { - const struct util_format_description *desc = util_format_description(format); - - if (!format) { - return FALSE; - } - - return desc->layout == UTIL_FORMAT_LAYOUT_PLAIN ? TRUE : FALSE; -} - -static inline boolean util_format_is_compressed(enum pipe_format format) { - const struct util_format_description *desc = util_format_description(format); - - assert(desc); - if (!desc) { - return FALSE; - } - - switch (desc->layout) { - case UTIL_FORMAT_LAYOUT_S3TC: - case UTIL_FORMAT_LAYOUT_RGTC: - case UTIL_FORMAT_LAYOUT_ETC: - case UTIL_FORMAT_LAYOUT_BPTC: - /* XXX add other formats in the future */ - return TRUE; - default: - return FALSE; - } -} - -static inline boolean util_format_is_s3tc(enum pipe_format format) { - const struct util_format_description *desc = util_format_description(format); - - assert(desc); - if (!desc) { - return FALSE; - } - - return desc->layout == UTIL_FORMAT_LAYOUT_S3TC ? TRUE : FALSE; -} - -static inline boolean util_format_is_srgb(enum pipe_format format) { - const struct util_format_description *desc = util_format_description(format); - return desc->colorspace == UTIL_FORMAT_COLORSPACE_SRGB; -} - -static inline boolean -util_format_has_depth(const struct util_format_description *desc) { - return desc->colorspace == UTIL_FORMAT_COLORSPACE_ZS && - desc->swizzle[0] != UTIL_FORMAT_SWIZZLE_NONE; -} - -static inline boolean -util_format_has_stencil(const struct util_format_description *desc) { - return desc->colorspace == UTIL_FORMAT_COLORSPACE_ZS && - desc->swizzle[1] != UTIL_FORMAT_SWIZZLE_NONE; -} - -static inline boolean util_format_is_depth_or_stencil(enum pipe_format format) { - const struct util_format_description *desc = util_format_description(format); - - assert(desc); - if (!desc) { - return FALSE; - } - - return util_format_has_depth(desc) || util_format_has_stencil(desc); -} - -static inline boolean -util_format_is_depth_and_stencil(enum pipe_format format) { - const struct util_format_description *desc = util_format_description(format); - - assert(desc); - if (!desc) { - return FALSE; - } - - return util_format_has_depth(desc) && util_format_has_stencil(desc); -} - -/** - * Calculates the depth format type based upon the incoming format description. - */ -static inline unsigned -util_get_depth_format_type(const struct util_format_description *desc) { - unsigned depth_channel = desc->swizzle[0]; - if (desc->colorspace == UTIL_FORMAT_COLORSPACE_ZS && - depth_channel != UTIL_FORMAT_SWIZZLE_NONE) { - return desc->channel[depth_channel].type; - } else { - return UTIL_FORMAT_TYPE_VOID; - } -} - -/** - * Calculates the MRD for the depth format. MRD is used in depth bias - * for UNORM and unbound depth buffers. When the depth buffer is floating - * point, the depth bias calculation does not use the MRD. However, the - * default MRD will be 1.0 / ((1 << 24) - 1). - */ -double util_get_depth_format_mrd(const struct util_format_description *desc); - -/** - * Return whether this is an RGBA, Z, S, or combined ZS format. - * Useful for initializing pipe_blit_info::mask. - */ -static inline unsigned util_format_get_mask(enum pipe_format format) { - const struct util_format_description *desc = util_format_description(format); - - if (!desc) - return 0; - - if (util_format_has_depth(desc)) { - if (util_format_has_stencil(desc)) { - return PIPE_MASK_ZS; - } else { - return PIPE_MASK_Z; - } - } else { - if (util_format_has_stencil(desc)) { - return PIPE_MASK_S; - } else { - return PIPE_MASK_RGBA; - } - } -} - -/** - * Give the RGBA colormask of the channels that can be represented in this - * format. - * - * That is, the channels whose values are preserved. - */ -static inline unsigned -util_format_colormask(const struct util_format_description *desc) { - unsigned colormask; - unsigned chan; - - switch (desc->colorspace) { - case UTIL_FORMAT_COLORSPACE_RGB: - case UTIL_FORMAT_COLORSPACE_SRGB: - case UTIL_FORMAT_COLORSPACE_YUV: - colormask = 0; - for (chan = 0; chan < 4; ++chan) { - if (desc->swizzle[chan] < 4) { - colormask |= (1 << chan); - } - } - return colormask; - case UTIL_FORMAT_COLORSPACE_ZS: - return 0; - default: - assert(0); - return 0; - } -} - -/** - * Checks if color mask covers every channel for the specified format - * - * @param desc a format description to check colormask with - * @param colormask a bit mask for channels, matches format of PIPE_MASK_RGBA - */ -static inline boolean -util_format_colormask_full(const struct util_format_description *desc, - unsigned colormask) { - return (~colormask & util_format_colormask(desc)) == 0; -} - -boolean util_format_is_float(enum pipe_format format); - -boolean util_format_has_alpha(enum pipe_format format); - -boolean util_format_is_luminance(enum pipe_format format); - -boolean util_format_is_alpha(enum pipe_format format); - -boolean util_format_is_luminance_alpha(enum pipe_format format); - -boolean util_format_is_intensity(enum pipe_format format); - -boolean util_format_is_subsampled_422(enum pipe_format format); - -boolean util_format_is_pure_integer(enum pipe_format format); - -boolean util_format_is_pure_sint(enum pipe_format format); - -boolean util_format_is_pure_uint(enum pipe_format format); - -boolean util_format_is_snorm(enum pipe_format format); - -/** - * Check if the src format can be blitted to the destination format with - * a simple memcpy. For example, blitting from RGBA to RGBx is OK, but not - * the reverse. - */ -boolean -util_is_format_compatible(const struct util_format_description *src_desc, - const struct util_format_description *dst_desc); - -/** - * Whether the format is supported by Gallium for the given bindings. - * This covers S3TC textures and floating-point render targets. - */ -boolean util_format_is_supported(enum pipe_format format, unsigned bind); - -/** - * Whether this format is a rgab8 variant. - * - * That is, any format that matches the - * - * PIPE_FORMAT_?8?8?8?8_UNORM - */ -static inline boolean -util_format_is_rgba8_variant(const struct util_format_description *desc) { - unsigned chan; - - if (desc->block.width != 1 || desc->block.height != 1 || - desc->block.bits != 32) - return FALSE; - - for (chan = 0; chan < 4; ++chan) { - if (desc->channel[chan].type != UTIL_FORMAT_TYPE_UNSIGNED && - desc->channel[chan].type != UTIL_FORMAT_TYPE_VOID) - return FALSE; - if (desc->channel[chan].type == UTIL_FORMAT_TYPE_UNSIGNED && - !desc->channel[chan].normalized) - return FALSE; - if (desc->channel[chan].size != 8) - return FALSE; - } - - return TRUE; -} - -/** - * Return total bits needed for the pixel format per block. - */ -static inline uint util_format_get_blocksizebits(enum pipe_format format) { - const struct util_format_description *desc = util_format_description(format); - - assert(desc); - if (!desc) { - return 0; - } - - return desc->block.bits; -} - -/** - * Return bytes per block (not pixel) for the given format. - */ -static inline uint util_format_get_blocksize(enum pipe_format format) { - uint bits = util_format_get_blocksizebits(format); - uint bytes = bits / 8; - - assert(bits % 8 == 0); - assert(bytes > 0); - if (bytes == 0) { - bytes = 1; - } - - return bytes; -} - -static inline uint util_format_get_blockwidth(enum pipe_format format) { - const struct util_format_description *desc = util_format_description(format); - - assert(desc); - if (!desc) { - return 1; - } - - return desc->block.width; -} - -static inline uint util_format_get_blockheight(enum pipe_format format) { - const struct util_format_description *desc = util_format_description(format); - - assert(desc); - if (!desc) { - return 1; - } - - return desc->block.height; -} - -static inline unsigned util_format_get_nblocksx(enum pipe_format format, - unsigned x) { - unsigned blockwidth = util_format_get_blockwidth(format); - return (x + blockwidth - 1) / blockwidth; -} - -static inline unsigned util_format_get_nblocksy(enum pipe_format format, - unsigned y) { - unsigned blockheight = util_format_get_blockheight(format); - return (y + blockheight - 1) / blockheight; -} - -static inline unsigned util_format_get_nblocks(enum pipe_format format, - unsigned width, - unsigned height) { - return util_format_get_nblocksx(format, width) * - util_format_get_nblocksy(format, height); -} - -static inline size_t util_format_get_stride(enum pipe_format format, - unsigned width) { - return util_format_get_nblocksx(format, width) * - util_format_get_blocksize(format); -} - -static inline size_t util_format_get_2d_size(enum pipe_format format, - size_t stride, unsigned height) { - return util_format_get_nblocksy(format, height) * stride; -} - -static inline uint -util_format_get_component_bits(enum pipe_format format, - enum util_format_colorspace colorspace, - uint component) { - const struct util_format_description *desc = util_format_description(format); - enum util_format_colorspace desc_colorspace; - - assert(format); - if (!format) { - return 0; - } - - assert(component < 4); - - /* Treat RGB and SRGB as equivalent. */ - if (colorspace == UTIL_FORMAT_COLORSPACE_SRGB) { - colorspace = UTIL_FORMAT_COLORSPACE_RGB; - } - if (desc->colorspace == UTIL_FORMAT_COLORSPACE_SRGB) { - desc_colorspace = UTIL_FORMAT_COLORSPACE_RGB; - } else { - desc_colorspace = desc->colorspace; - } - - if (desc_colorspace != colorspace) { - return 0; - } - - switch (desc->swizzle[component]) { - case UTIL_FORMAT_SWIZZLE_X: - return desc->channel[0].size; - case UTIL_FORMAT_SWIZZLE_Y: - return desc->channel[1].size; - case UTIL_FORMAT_SWIZZLE_Z: - return desc->channel[2].size; - case UTIL_FORMAT_SWIZZLE_W: - return desc->channel[3].size; - default: - return 0; - } -} - -/** - * Given a linear RGB colorspace format, return the corresponding SRGB - * format, or PIPE_FORMAT_NONE if none. - */ -static inline enum pipe_format util_format_srgb(enum pipe_format format) { - if (util_format_is_srgb(format)) - return format; - - switch (format) { - case PIPE_FORMAT_L8_UNORM: - return PIPE_FORMAT_L8_SRGB; - case PIPE_FORMAT_L8A8_UNORM: - return PIPE_FORMAT_L8A8_SRGB; - case PIPE_FORMAT_R8G8B8_UNORM: - return PIPE_FORMAT_R8G8B8_SRGB; - case PIPE_FORMAT_A8B8G8R8_UNORM: - return PIPE_FORMAT_A8B8G8R8_SRGB; - case PIPE_FORMAT_X8B8G8R8_UNORM: - return PIPE_FORMAT_X8B8G8R8_SRGB; - case PIPE_FORMAT_B8G8R8A8_UNORM: - return PIPE_FORMAT_B8G8R8A8_SRGB; - case PIPE_FORMAT_B8G8R8X8_UNORM: - return PIPE_FORMAT_B8G8R8X8_SRGB; - case PIPE_FORMAT_A8R8G8B8_UNORM: - return PIPE_FORMAT_A8R8G8B8_SRGB; - case PIPE_FORMAT_X8R8G8B8_UNORM: - return PIPE_FORMAT_X8R8G8B8_SRGB; - case PIPE_FORMAT_R8G8B8A8_UNORM: - return PIPE_FORMAT_R8G8B8A8_SRGB; - case PIPE_FORMAT_R8G8B8X8_UNORM: - return PIPE_FORMAT_R8G8B8X8_SRGB; - case PIPE_FORMAT_DXT1_RGB: - return PIPE_FORMAT_DXT1_SRGB; - case PIPE_FORMAT_DXT1_RGBA: - return PIPE_FORMAT_DXT1_SRGBA; - case PIPE_FORMAT_DXT3_RGBA: - return PIPE_FORMAT_DXT3_SRGBA; - case PIPE_FORMAT_DXT5_RGBA: - return PIPE_FORMAT_DXT5_SRGBA; - case PIPE_FORMAT_B5G6R5_UNORM: - return PIPE_FORMAT_B5G6R5_SRGB; - case PIPE_FORMAT_BPTC_RGBA_UNORM: - return PIPE_FORMAT_BPTC_SRGBA; - default: - return PIPE_FORMAT_NONE; - } -} - -/** - * Given an sRGB format, return the corresponding linear colorspace format. - * For non sRGB formats, return the format unchanged. - */ -static inline enum pipe_format util_format_linear(enum pipe_format format) { - switch (format) { - case PIPE_FORMAT_L8_SRGB: - return PIPE_FORMAT_L8_UNORM; - case PIPE_FORMAT_L8A8_SRGB: - return PIPE_FORMAT_L8A8_UNORM; - case PIPE_FORMAT_R8G8B8_SRGB: - return PIPE_FORMAT_R8G8B8_UNORM; - case PIPE_FORMAT_A8B8G8R8_SRGB: - return PIPE_FORMAT_A8B8G8R8_UNORM; - case PIPE_FORMAT_X8B8G8R8_SRGB: - return PIPE_FORMAT_X8B8G8R8_UNORM; - case PIPE_FORMAT_B8G8R8A8_SRGB: - return PIPE_FORMAT_B8G8R8A8_UNORM; - case PIPE_FORMAT_B8G8R8X8_SRGB: - return PIPE_FORMAT_B8G8R8X8_UNORM; - case PIPE_FORMAT_A8R8G8B8_SRGB: - return PIPE_FORMAT_A8R8G8B8_UNORM; - case PIPE_FORMAT_X8R8G8B8_SRGB: - return PIPE_FORMAT_X8R8G8B8_UNORM; - case PIPE_FORMAT_R8G8B8A8_SRGB: - return PIPE_FORMAT_R8G8B8A8_UNORM; - case PIPE_FORMAT_R8G8B8X8_SRGB: - return PIPE_FORMAT_R8G8B8X8_UNORM; - case PIPE_FORMAT_DXT1_SRGB: - return PIPE_FORMAT_DXT1_RGB; - case PIPE_FORMAT_DXT1_SRGBA: - return PIPE_FORMAT_DXT1_RGBA; - case PIPE_FORMAT_DXT3_SRGBA: - return PIPE_FORMAT_DXT3_RGBA; - case PIPE_FORMAT_DXT5_SRGBA: - return PIPE_FORMAT_DXT5_RGBA; - case PIPE_FORMAT_B5G6R5_SRGB: - return PIPE_FORMAT_B5G6R5_UNORM; - case PIPE_FORMAT_BPTC_SRGBA: - return PIPE_FORMAT_BPTC_RGBA_UNORM; - default: - return format; - } -} - -/** - * Given a depth-stencil format, return the corresponding stencil-only format. - * For stencil-only formats, return the format unchanged. - */ -static inline enum pipe_format -util_format_stencil_only(enum pipe_format format) { - switch (format) { - /* mask out the depth component */ - case PIPE_FORMAT_Z24_UNORM_S8_UINT: - return PIPE_FORMAT_X24S8_UINT; - case PIPE_FORMAT_S8_UINT_Z24_UNORM: - return PIPE_FORMAT_S8X24_UINT; - case PIPE_FORMAT_Z32_FLOAT_S8X24_UINT: - return PIPE_FORMAT_X32_S8X24_UINT; - - /* stencil only formats */ - case PIPE_FORMAT_X24S8_UINT: - case PIPE_FORMAT_S8X24_UINT: - case PIPE_FORMAT_X32_S8X24_UINT: - case PIPE_FORMAT_S8_UINT: - return format; - - default: - assert(0); - return PIPE_FORMAT_NONE; - } -} - -/** - * Converts PIPE_FORMAT_*I* to PIPE_FORMAT_*R*. - * This is identity for non-intensity formats. - */ -static inline enum pipe_format -util_format_intensity_to_red(enum pipe_format format) { - switch (format) { - case PIPE_FORMAT_I8_UNORM: - return PIPE_FORMAT_R8_UNORM; - case PIPE_FORMAT_I8_SNORM: - return PIPE_FORMAT_R8_SNORM; - case PIPE_FORMAT_I16_UNORM: - return PIPE_FORMAT_R16_UNORM; - case PIPE_FORMAT_I16_SNORM: - return PIPE_FORMAT_R16_SNORM; - case PIPE_FORMAT_I16_FLOAT: - return PIPE_FORMAT_R16_FLOAT; - case PIPE_FORMAT_I32_FLOAT: - return PIPE_FORMAT_R32_FLOAT; - case PIPE_FORMAT_I8_UINT: - return PIPE_FORMAT_R8_UINT; - case PIPE_FORMAT_I8_SINT: - return PIPE_FORMAT_R8_SINT; - case PIPE_FORMAT_I16_UINT: - return PIPE_FORMAT_R16_UINT; - case PIPE_FORMAT_I16_SINT: - return PIPE_FORMAT_R16_SINT; - case PIPE_FORMAT_I32_UINT: - return PIPE_FORMAT_R32_UINT; - case PIPE_FORMAT_I32_SINT: - return PIPE_FORMAT_R32_SINT; - default: - assert(!util_format_is_intensity(format)); - return format; - } -} - -/** - * Converts PIPE_FORMAT_*L* to PIPE_FORMAT_*R*. - * This is identity for non-luminance formats. - */ -static inline enum pipe_format -util_format_luminance_to_red(enum pipe_format format) { - switch (format) { - case PIPE_FORMAT_L8_UNORM: - return PIPE_FORMAT_R8_UNORM; - case PIPE_FORMAT_L8_SNORM: - return PIPE_FORMAT_R8_SNORM; - case PIPE_FORMAT_L16_UNORM: - return PIPE_FORMAT_R16_UNORM; - case PIPE_FORMAT_L16_SNORM: - return PIPE_FORMAT_R16_SNORM; - case PIPE_FORMAT_L16_FLOAT: - return PIPE_FORMAT_R16_FLOAT; - case PIPE_FORMAT_L32_FLOAT: - return PIPE_FORMAT_R32_FLOAT; - case PIPE_FORMAT_L8_UINT: - return PIPE_FORMAT_R8_UINT; - case PIPE_FORMAT_L8_SINT: - return PIPE_FORMAT_R8_SINT; - case PIPE_FORMAT_L16_UINT: - return PIPE_FORMAT_R16_UINT; - case PIPE_FORMAT_L16_SINT: - return PIPE_FORMAT_R16_SINT; - case PIPE_FORMAT_L32_UINT: - return PIPE_FORMAT_R32_UINT; - case PIPE_FORMAT_L32_SINT: - return PIPE_FORMAT_R32_SINT; - - case PIPE_FORMAT_LATC1_UNORM: - return PIPE_FORMAT_RGTC1_UNORM; - case PIPE_FORMAT_LATC1_SNORM: - return PIPE_FORMAT_RGTC1_SNORM; - - case PIPE_FORMAT_L4A4_UNORM: - return PIPE_FORMAT_R4A4_UNORM; - - case PIPE_FORMAT_L8A8_UNORM: - return PIPE_FORMAT_R8A8_UNORM; - case PIPE_FORMAT_L8A8_SNORM: - return PIPE_FORMAT_R8A8_SNORM; - case PIPE_FORMAT_L16A16_UNORM: - return PIPE_FORMAT_R16A16_UNORM; - case PIPE_FORMAT_L16A16_SNORM: - return PIPE_FORMAT_R16A16_SNORM; - case PIPE_FORMAT_L16A16_FLOAT: - return PIPE_FORMAT_R16A16_FLOAT; - case PIPE_FORMAT_L32A32_FLOAT: - return PIPE_FORMAT_R32A32_FLOAT; - case PIPE_FORMAT_L8A8_UINT: - return PIPE_FORMAT_R8A8_UINT; - case PIPE_FORMAT_L8A8_SINT: - return PIPE_FORMAT_R8A8_SINT; - case PIPE_FORMAT_L16A16_UINT: - return PIPE_FORMAT_R16A16_UINT; - case PIPE_FORMAT_L16A16_SINT: - return PIPE_FORMAT_R16A16_SINT; - case PIPE_FORMAT_L32A32_UINT: - return PIPE_FORMAT_R32A32_UINT; - case PIPE_FORMAT_L32A32_SINT: - return PIPE_FORMAT_R32A32_SINT; - - /* We don't have compressed red-alpha variants for these. */ - case PIPE_FORMAT_LATC2_UNORM: - case PIPE_FORMAT_LATC2_SNORM: - return PIPE_FORMAT_NONE; - - default: - assert(!util_format_is_luminance(format) && - !util_format_is_luminance_alpha(format)); - return format; - } -} - -/** - * Return the number of components stored. - * Formats with block size != 1x1 will always have 1 component (the block). - */ -static inline unsigned util_format_get_nr_components(enum pipe_format format) { - const struct util_format_description *desc = util_format_description(format); - return desc->nr_channels; -} - -/** - * Return the index of the first non-void channel - * -1 if no non-void channels - */ -static inline int -util_format_get_first_non_void_channel(enum pipe_format format) { - const struct util_format_description *desc = util_format_description(format); - int i; - - for (i = 0; i < 4; i++) - if (desc->channel[i].type != UTIL_FORMAT_TYPE_VOID) - break; - - if (i == 4) - return -1; - - return i; -} - -/* - * Generic format conversion; - */ - -boolean -util_format_fits_8unorm(const struct util_format_description *format_desc); - -/* - * Swizzle operations. - */ - -/* Compose two sets of swizzles. - * If V is a 4D vector and the function parameters represent functions that - * swizzle vector components, this holds: - * swz2(swz1(V)) = dst(V) - */ -void util_format_compose_swizzles(const unsigned char swz1[4], - const unsigned char swz2[4], - unsigned char dst[4]); - -/* Apply the swizzle provided in \param swz (which is one of PIPE_SWIZZLE_x) - * to \param src and store the result in \param dst. - * \param is_integer determines the value written for PIPE_SWIZZLE_ONE. - */ -void util_format_apply_color_swizzle(union pipe_color_union *dst, - const union pipe_color_union *src, - const unsigned char swz[4], - const boolean is_integer); - -void util_format_swizzle_4f(float *dst, const float *src, - const unsigned char swz[4]); - -void util_format_unswizzle_4f(float *dst, const float *src, - const unsigned char swz[4]); - -#ifdef __cplusplus -} // extern "C" { -#endif - -#endif /* ! U_FORMAT_H */ diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_format_parse.py b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_format_parse.py deleted file mode 100644 index 401265017..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_format_parse.py +++ /dev/null @@ -1,373 +0,0 @@ - -''' -/************************************************************************** - * - * Copyright 2009 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ -''' - - -from __future__ import division - - -VOID, UNSIGNED, SIGNED, FIXED, FLOAT = range(5) - -SWIZZLE_X, SWIZZLE_Y, SWIZZLE_Z, SWIZZLE_W, SWIZZLE_0, SWIZZLE_1, SWIZZLE_NONE, = range(7) - -PLAIN = 'plain' - -RGB = 'rgb' -SRGB = 'srgb' -YUV = 'yuv' -ZS = 'zs' - - -def is_pot(x): - return (x & (x - 1)) == 0 - - -VERY_LARGE = 99999999999999999999999 - - -class Channel: - '''Describe the channel of a color channel.''' - - def __init__(self, type, norm, pure, size, name = ''): - self.type = type - self.norm = norm - self.pure = pure - self.size = size - self.sign = type in (SIGNED, FIXED, FLOAT) - self.name = name - - def __str__(self): - s = str(self.type) - if self.norm: - s += 'n' - if self.pure: - s += 'p' - s += str(self.size) - return s - - def __eq__(self, other): - if other is None: - return False - - return self.type == other.type and self.norm == other.norm and self.pure == other.pure and self.size == other.size - - def __ne__(self, other): - return not self == other - - def max(self): - '''Maximum representable number.''' - if self.type == FLOAT: - return VERY_LARGE - if self.type == FIXED: - return (1 << (self.size // 2)) - 1 - if self.norm: - return 1 - if self.type == UNSIGNED: - return (1 << self.size) - 1 - if self.type == SIGNED: - return (1 << (self.size - 1)) - 1 - assert False - - def min(self): - '''Minimum representable number.''' - if self.type == FLOAT: - return -VERY_LARGE - if self.type == FIXED: - return -(1 << (self.size // 2)) - if self.type == UNSIGNED: - return 0 - if self.norm: - return -1 - if self.type == SIGNED: - return -(1 << (self.size - 1)) - assert False - - -class Format: - '''Describe a pixel format.''' - - def __init__(self, name, layout, block_width, block_height, le_channels, le_swizzles, be_channels, be_swizzles, colorspace): - self.name = name - self.layout = layout - self.block_width = block_width - self.block_height = block_height - self.le_channels = le_channels - self.le_swizzles = le_swizzles - self.be_channels = be_channels - self.be_swizzles = be_swizzles - self.name = name - self.colorspace = colorspace - - def __str__(self): - return self.name - - def short_name(self): - '''Make up a short norm for a format, suitable to be used as suffix in - function names.''' - - name = self.name - if name.startswith('PIPE_FORMAT_'): - name = name[len('PIPE_FORMAT_'):] - name = name.lower() - return name - - def block_size(self): - size = 0 - for channel in self.le_channels: - size += channel.size - return size - - def nr_channels(self): - nr_channels = 0 - for channel in self.le_channels: - if channel.size: - nr_channels += 1 - return nr_channels - - def array_element(self): - if self.layout != PLAIN: - return None - ref_channel = self.le_channels[0] - if ref_channel.type == VOID: - ref_channel = self.le_channels[1] - for channel in self.le_channels: - if channel.size and (channel.size != ref_channel.size or channel.size % 8): - return None - if channel.type != VOID: - if channel.type != ref_channel.type: - return None - if channel.norm != ref_channel.norm: - return None - if channel.pure != ref_channel.pure: - return None - return ref_channel - - def is_array(self): - return self.array_element() != None - - def is_mixed(self): - if self.layout != PLAIN: - return False - ref_channel = self.le_channels[0] - if ref_channel.type == VOID: - ref_channel = self.le_channels[1] - for channel in self.le_channels[1:]: - if channel.type != VOID: - if channel.type != ref_channel.type: - return True - if channel.norm != ref_channel.norm: - return True - if channel.pure != ref_channel.pure: - return True - return False - - def is_pot(self): - return is_pot(self.block_size()) - - def is_int(self): - if self.layout != PLAIN: - return False - for channel in self.le_channels: - if channel.type not in (VOID, UNSIGNED, SIGNED): - return False - return True - - def is_float(self): - if self.layout != PLAIN: - return False - for channel in self.le_channels: - if channel.type not in (VOID, FLOAT): - return False - return True - - def is_bitmask(self): - if self.layout != PLAIN: - return False - if self.block_size() not in (8, 16, 32): - return False - for channel in self.le_channels: - if channel.type not in (VOID, UNSIGNED, SIGNED): - return False - return True - - def is_pure_color(self): - if self.layout != PLAIN or self.colorspace == ZS: - return False - pures = [channel.pure - for channel in self.le_channels - if channel.type != VOID] - for x in pures: - assert x == pures[0] - return pures[0] - - def channel_type(self): - types = [channel.type - for channel in self.le_channels - if channel.type != VOID] - for x in types: - assert x == types[0] - return types[0] - - def is_pure_signed(self): - return self.is_pure_color() and self.channel_type() == SIGNED - - def is_pure_unsigned(self): - return self.is_pure_color() and self.channel_type() == UNSIGNED - - def has_channel(self, id): - return self.le_swizzles[id] != SWIZZLE_NONE - - def has_depth(self): - return self.colorspace == ZS and self.has_channel(0) - - def has_stencil(self): - return self.colorspace == ZS and self.has_channel(1) - - def stride(self): - return self.block_size()/8 - - -_type_parse_map = { - '': VOID, - 'x': VOID, - 'u': UNSIGNED, - 's': SIGNED, - 'h': FIXED, - 'f': FLOAT, -} - -_swizzle_parse_map = { - 'x': SWIZZLE_X, - 'y': SWIZZLE_Y, - 'z': SWIZZLE_Z, - 'w': SWIZZLE_W, - '0': SWIZZLE_0, - '1': SWIZZLE_1, - '_': SWIZZLE_NONE, -} - -def _parse_channels(fields, layout, colorspace, swizzles): - if layout == PLAIN: - names = ['']*4 - if colorspace in (RGB, SRGB): - for i in range(4): - swizzle = swizzles[i] - if swizzle < 4: - names[swizzle] += 'rgba'[i] - elif colorspace == ZS: - for i in range(4): - swizzle = swizzles[i] - if swizzle < 4: - names[swizzle] += 'zs'[i] - else: - assert False - for i in range(4): - if names[i] == '': - names[i] = 'x' - else: - names = ['x', 'y', 'z', 'w'] - - channels = [] - for i in range(0, 4): - field = fields[i] - if field: - type = _type_parse_map[field[0]] - if field[1] == 'n': - norm = True - pure = False - size = int(field[2:]) - elif field[1] == 'p': - pure = True - norm = False - size = int(field[2:]) - else: - norm = False - pure = False - size = int(field[1:]) - else: - type = VOID - norm = False - pure = False - size = 0 - channel = Channel(type, norm, pure, size, names[i]) - channels.append(channel) - - return channels - -def parse(filename): - '''Parse the format description in CSV format in terms of the - Channel and Format classes above.''' - - stream = open(filename) - formats = [] - for line in stream: - try: - comment = line.index('#') - except ValueError: - pass - else: - line = line[:comment] - line = line.strip() - if not line: - continue - - fields = [field.strip() for field in line.split(',')] - if len (fields) == 10: - fields += fields[4:9] - assert len (fields) == 15 - - name = fields[0] - layout = fields[1] - block_width, block_height = map(int, fields[2:4]) - colorspace = fields[9] - - le_swizzles = [_swizzle_parse_map[swizzle] for swizzle in fields[8]] - le_channels = _parse_channels(fields[4:8], layout, colorspace, le_swizzles) - - be_swizzles = [_swizzle_parse_map[swizzle] for swizzle in fields[14]] - be_channels = _parse_channels(fields[10:14], layout, colorspace, be_swizzles) - - le_shift = 0 - for channel in le_channels: - channel.shift = le_shift - le_shift += channel.size - - be_shift = 0 - for channel in be_channels[3::-1]: - channel.shift = be_shift - be_shift += channel.size - - assert le_shift == be_shift - for i in range(4): - assert (le_swizzles[i] != SWIZZLE_NONE) == (be_swizzles[i] != SWIZZLE_NONE) - - format = Format(name, layout, block_width, block_height, le_channels, le_swizzles, be_channels, be_swizzles, colorspace) - formats.append(format) - return formats - diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_format_s3tc.h b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_format_s3tc.h deleted file mode 100644 index fc0c0de81..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_format_s3tc.h +++ /dev/null @@ -1,44 +0,0 @@ -/************************************************************************** - * - * Copyright 2010 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 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 NON-INFRINGEMENT. IN NO EVENT SHALL - * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS 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. - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial portions - * of the Software. - * - **************************************************************************/ - -#ifndef U_FORMAT_S3TC_H_ -#define U_FORMAT_S3TC_H_ - -#include "pipe/p_compiler.h" - -enum util_format_dxtn { - UTIL_FORMAT_DXT1_RGB = 0x83F0, - UTIL_FORMAT_DXT1_RGBA = 0x83F1, - UTIL_FORMAT_DXT3_RGBA = 0x83F2, - UTIL_FORMAT_DXT5_RGBA = 0x83F3 -}; - -extern boolean util_format_s3tc_enabled; - -void util_format_s3tc_init(void); - -#endif diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_format_table.c b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_format_table.c deleted file mode 100644 index a186e01f1..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_format_table.c +++ /dev/null @@ -1,9785 +0,0 @@ -/* This file is autogenerated by u_format_table.py from u_format.csv. Do not - * edit directly. */ - -/************************************************************************** - * - * Copyright 2010 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -#include "pipe/p_compiler.h" -#include "u_format.h" -#include "u_half.h" -#include "u_math.h" - -const struct util_format_description util_format_none_description = { - PIPE_FORMAT_NONE, - "PIPE_FORMAT_NONE", - "none", - {1, 1, 8}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 8, 0}, /* x = r */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_b8g8r8a8_unorm_description = { - PIPE_FORMAT_B8G8R8A8_UNORM, - "PIPE_FORMAT_B8G8R8A8_UNORM", - "b8g8r8a8_unorm", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 24}, /* x = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 16}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* z = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 0} /* w = a */ - }, -#else - { - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 0}, /* x = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 16}, /* z = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 24} /* w = a */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_Z, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_Z, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_b8g8r8x8_unorm_description = { - PIPE_FORMAT_B8G8R8X8_UNORM, - "PIPE_FORMAT_B8G8R8X8_UNORM", - "b8g8r8x8_unorm", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 24}, /* x = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 16}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* z = r */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 0} /* w = x */ - }, -#else - { - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 0}, /* x = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 16}, /* z = r */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 24} /* w = x */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_Z, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_Z, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_a8r8g8b8_unorm_description = { - PIPE_FORMAT_A8R8G8B8_UNORM, - "PIPE_FORMAT_A8R8G8B8_UNORM", - "a8r8g8b8_unorm", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 24}, /* x = a */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 16}, /* y = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* z = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 0} /* w = b */ - }, -#else - { - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 0}, /* x = a */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* y = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 16}, /* z = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 24} /* w = b */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_Y, /* r */ - UTIL_FORMAT_SWIZZLE_Z, /* g */ - UTIL_FORMAT_SWIZZLE_W, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_Y, /* r */ - UTIL_FORMAT_SWIZZLE_Z, /* g */ - UTIL_FORMAT_SWIZZLE_W, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_x8r8g8b8_unorm_description = { - PIPE_FORMAT_X8R8G8B8_UNORM, - "PIPE_FORMAT_X8R8G8B8_UNORM", - "x8r8g8b8_unorm", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 24}, /* x = x */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 16}, /* y = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* z = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 0} /* w = b */ - }, -#else - { - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 0}, /* x = x */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* y = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 16}, /* z = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 24} /* w = b */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_Y, /* r */ - UTIL_FORMAT_SWIZZLE_Z, /* g */ - UTIL_FORMAT_SWIZZLE_W, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_Y, /* r */ - UTIL_FORMAT_SWIZZLE_Z, /* g */ - UTIL_FORMAT_SWIZZLE_W, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_a8b8g8r8_unorm_description = { - PIPE_FORMAT_A8B8G8R8_UNORM, - "PIPE_FORMAT_A8B8G8R8_UNORM", - "a8b8g8r8_unorm", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 24}, /* x = a */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 16}, /* y = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* z = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 0} /* w = r */ - }, -#else - { - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 0}, /* x = a */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* y = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 16}, /* z = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 24} /* w = r */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_W, /* r */ - UTIL_FORMAT_SWIZZLE_Z, /* g */ - UTIL_FORMAT_SWIZZLE_Y, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_W, /* r */ - UTIL_FORMAT_SWIZZLE_Z, /* g */ - UTIL_FORMAT_SWIZZLE_Y, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_x8b8g8r8_unorm_description = { - PIPE_FORMAT_X8B8G8R8_UNORM, - "PIPE_FORMAT_X8B8G8R8_UNORM", - "x8b8g8r8_unorm", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 24}, /* x = x */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 16}, /* y = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* z = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 0} /* w = r */ - }, -#else - { - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 0}, /* x = x */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* y = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 16}, /* z = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 24} /* w = r */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_W, /* r */ - UTIL_FORMAT_SWIZZLE_Z, /* g */ - UTIL_FORMAT_SWIZZLE_Y, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_W, /* r */ - UTIL_FORMAT_SWIZZLE_Z, /* g */ - UTIL_FORMAT_SWIZZLE_Y, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r8g8b8x8_unorm_description = { - PIPE_FORMAT_R8G8B8X8_UNORM, - "PIPE_FORMAT_R8G8B8X8_UNORM", - "r8g8b8x8_unorm", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 24}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 16}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* z = b */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 0} /* w = x */ - }, -#else - { - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 16}, /* z = b */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 24} /* w = x */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_b5g5r5x1_unorm_description = { - PIPE_FORMAT_B5G5R5X1_UNORM, - "PIPE_FORMAT_B5G5R5X1_UNORM", - "b5g5r5x1_unorm", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - FALSE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 1, 15}, /* x = x */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 5, 10}, /* y = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 5, 5}, /* z = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 5, 0} /* w = b */ - }, -#else - { - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 5, 0}, /* x = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 5, 5}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 5, 10}, /* z = r */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 1, 15} /* w = x */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_Y, /* r */ - UTIL_FORMAT_SWIZZLE_Z, /* g */ - UTIL_FORMAT_SWIZZLE_W, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_Z, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_b5g5r5a1_unorm_description = { - PIPE_FORMAT_B5G5R5A1_UNORM, - "PIPE_FORMAT_B5G5R5A1_UNORM", - "b5g5r5a1_unorm", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - FALSE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 1, 15}, /* x = a */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 5, 10}, /* y = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 5, 5}, /* z = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 5, 0} /* w = b */ - }, -#else - { - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 5, 0}, /* x = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 5, 5}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 5, 10}, /* z = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 1, 15} /* w = a */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_Y, /* r */ - UTIL_FORMAT_SWIZZLE_Z, /* g */ - UTIL_FORMAT_SWIZZLE_W, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_Z, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_b4g4r4a4_unorm_description = { - PIPE_FORMAT_B4G4R4A4_UNORM, - "PIPE_FORMAT_B4G4R4A4_UNORM", - "b4g4r4a4_unorm", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - FALSE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 4, 12}, /* x = a */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 4, 8}, /* y = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 4, 4}, /* z = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 4, 0} /* w = b */ - }, -#else - { - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 4, 0}, /* x = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 4, 4}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 4, 8}, /* z = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 4, 12} /* w = a */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_Y, /* r */ - UTIL_FORMAT_SWIZZLE_Z, /* g */ - UTIL_FORMAT_SWIZZLE_W, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_Z, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_b4g4r4x4_unorm_description = { - PIPE_FORMAT_B4G4R4X4_UNORM, - "PIPE_FORMAT_B4G4R4X4_UNORM", - "b4g4r4x4_unorm", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - FALSE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 4, 12}, /* x = x */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 4, 8}, /* y = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 4, 4}, /* z = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 4, 0} /* w = b */ - }, -#else - { - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 4, 0}, /* x = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 4, 4}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 4, 8}, /* z = r */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 4, 12} /* w = x */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_Y, /* r */ - UTIL_FORMAT_SWIZZLE_Z, /* g */ - UTIL_FORMAT_SWIZZLE_W, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_Z, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_a4b4g4r4_unorm_description = { - PIPE_FORMAT_A4B4G4R4_UNORM, - "PIPE_FORMAT_A4B4G4R4_UNORM", - "a4b4g4r4_unorm", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - FALSE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 4, 12}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 4, 8}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 4, 4}, /* z = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 4, 0} /* w = a */ - }, -#else - { - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 4, 0}, /* x = a */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 4, 4}, /* y = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 4, 8}, /* z = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 4, 12} /* w = r */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_W, /* r */ - UTIL_FORMAT_SWIZZLE_Z, /* g */ - UTIL_FORMAT_SWIZZLE_Y, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_b5g6r5_unorm_description = { - PIPE_FORMAT_B5G6R5_UNORM, - "PIPE_FORMAT_B5G6R5_UNORM", - "b5g6r5_unorm", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 3, /* nr_channels */ - FALSE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 5, 11}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 6, 5}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 5, 0}, /* z = b */ - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 5, 0}, /* x = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 6, 5}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 5, 11}, /* z = r */ - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_Z, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r10g10b10a2_unorm_description = - { - PIPE_FORMAT_R10G10B10A2_UNORM, - "PIPE_FORMAT_R10G10B10A2_UNORM", - "r10g10b10a2_unorm", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - FALSE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 2, 30}, /* x = a */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 10, 20}, /* y = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 10, 10}, /* z = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 10, 0} /* w = r */ - }, -#else - { - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 10, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 10, 10}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 10, 20}, /* z = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 2, 30} /* w = a */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_W, /* r */ - UTIL_FORMAT_SWIZZLE_Z, /* g */ - UTIL_FORMAT_SWIZZLE_Y, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r10g10b10x2_unorm_description = - { - PIPE_FORMAT_R10G10B10X2_UNORM, - "PIPE_FORMAT_R10G10B10X2_UNORM", - "r10g10b10x2_unorm", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - FALSE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 2, 30}, /* x = x */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 10, 20}, /* y = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 10, 10}, /* z = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 10, 0} /* w = r */ - }, -#else - { - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 10, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 10, 10}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 10, 20}, /* z = b */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 2, 30} /* w = x */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_W, /* r */ - UTIL_FORMAT_SWIZZLE_Z, /* g */ - UTIL_FORMAT_SWIZZLE_Y, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_b10g10r10a2_unorm_description = - { - PIPE_FORMAT_B10G10R10A2_UNORM, - "PIPE_FORMAT_B10G10R10A2_UNORM", - "b10g10r10a2_unorm", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - FALSE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 2, 30}, /* x = a */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 10, 20}, /* y = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 10, 10}, /* z = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 10, 0} /* w = b */ - }, -#else - { - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 10, 0}, /* x = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 10, 10}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 10, 20}, /* z = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 2, 30} /* w = a */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_Y, /* r */ - UTIL_FORMAT_SWIZZLE_Z, /* g */ - UTIL_FORMAT_SWIZZLE_W, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_Z, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_b2g3r3_unorm_description = { - PIPE_FORMAT_B2G3R3_UNORM, - "PIPE_FORMAT_B2G3R3_UNORM", - "b2g3r3_unorm", - {1, 1, 8}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 3, /* nr_channels */ - FALSE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 3, 5}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 3, 2}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 2, 0}, /* z = b */ - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 2, 0}, /* x = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 3, 2}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 3, 5}, /* z = r */ - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_Z, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_l8_unorm_description = { - PIPE_FORMAT_L8_UNORM, - "PIPE_FORMAT_L8_UNORM", - "l8_unorm", - {1, 1, 8}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 0}, /* x = rgb */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_a8_unorm_description = { - PIPE_FORMAT_A8_UNORM, - "PIPE_FORMAT_A8_UNORM", - "a8_unorm", - {1, 1, 8}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 0}, /* x = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_0, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_i8_unorm_description = { - PIPE_FORMAT_I8_UNORM, - "PIPE_FORMAT_I8_UNORM", - "i8_unorm", - {1, 1, 8}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 0}, /* x = rgba */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_l4a4_unorm_description = { - PIPE_FORMAT_L4A4_UNORM, - "PIPE_FORMAT_L4A4_UNORM", - "l4a4_unorm", - {1, 1, 8}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - FALSE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 4, 4}, /* x = a */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 4, 0}, /* y = rgb */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 4, 0}, /* x = rgb */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 4, 4}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_Y, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Y, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_l8a8_unorm_description = { - PIPE_FORMAT_L8A8_UNORM, - "PIPE_FORMAT_L8A8_UNORM", - "l8a8_unorm", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* x = rgb */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 0}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 0}, /* x = rgb */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_l16_unorm_description = { - PIPE_FORMAT_L16_UNORM, - "PIPE_FORMAT_L16_UNORM", - "l16_unorm", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 16, 0}, /* x = rgb */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_a16_unorm_description = { - PIPE_FORMAT_A16_UNORM, - "PIPE_FORMAT_A16_UNORM", - "a16_unorm", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 16, 0}, /* x = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_0, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_i16_unorm_description = { - PIPE_FORMAT_I16_UNORM, - "PIPE_FORMAT_I16_UNORM", - "i16_unorm", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 16, 0}, /* x = rgba */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_l16a16_unorm_description = { - PIPE_FORMAT_L16A16_UNORM, - "PIPE_FORMAT_L16A16_UNORM", - "l16a16_unorm", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 16, 16}, /* x = rgb */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 16, 0}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 16, 0}, /* x = rgb */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 16, 16}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_a8_snorm_description = { - PIPE_FORMAT_A8_SNORM, - "PIPE_FORMAT_A8_SNORM", - "a8_snorm", - {1, 1, 8}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 0}, /* x = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_0, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_l8_snorm_description = { - PIPE_FORMAT_L8_SNORM, - "PIPE_FORMAT_L8_SNORM", - "l8_snorm", - {1, 1, 8}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 0}, /* x = rgb */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_l8a8_snorm_description = { - PIPE_FORMAT_L8A8_SNORM, - "PIPE_FORMAT_L8A8_SNORM", - "l8a8_snorm", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 8}, /* x = rgb */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 0}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 0}, /* x = rgb */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 8}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_i8_snorm_description = { - PIPE_FORMAT_I8_SNORM, - "PIPE_FORMAT_I8_SNORM", - "i8_snorm", - {1, 1, 8}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 0}, /* x = rgba */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_a16_snorm_description = { - PIPE_FORMAT_A16_SNORM, - "PIPE_FORMAT_A16_SNORM", - "a16_snorm", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 16, 0}, /* x = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_0, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_l16_snorm_description = { - PIPE_FORMAT_L16_SNORM, - "PIPE_FORMAT_L16_SNORM", - "l16_snorm", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 16, 0}, /* x = rgb */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_l16a16_snorm_description = { - PIPE_FORMAT_L16A16_SNORM, - "PIPE_FORMAT_L16A16_SNORM", - "l16a16_snorm", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 16, 16}, /* x = rgb */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 16, 0}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 16, 0}, /* x = rgb */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 16, 16}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_i16_snorm_description = { - PIPE_FORMAT_I16_SNORM, - "PIPE_FORMAT_I16_SNORM", - "i16_snorm", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 16, 0}, /* x = rgba */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_a16_float_description = { - PIPE_FORMAT_A16_FLOAT, - "PIPE_FORMAT_A16_FLOAT", - "a16_float", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 16, 0}, /* x = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_0, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_l16_float_description = { - PIPE_FORMAT_L16_FLOAT, - "PIPE_FORMAT_L16_FLOAT", - "l16_float", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 16, 0}, /* x = rgb */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_l16a16_float_description = { - PIPE_FORMAT_L16A16_FLOAT, - "PIPE_FORMAT_L16A16_FLOAT", - "l16a16_float", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 16, 16}, /* x = rgb */ - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 16, 0}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 16, 0}, /* x = rgb */ - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 16, 16}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_i16_float_description = { - PIPE_FORMAT_I16_FLOAT, - "PIPE_FORMAT_I16_FLOAT", - "i16_float", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 16, 0}, /* x = rgba */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_a32_float_description = { - PIPE_FORMAT_A32_FLOAT, - "PIPE_FORMAT_A32_FLOAT", - "a32_float", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 32, 0}, /* x = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_0, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_l32_float_description = { - PIPE_FORMAT_L32_FLOAT, - "PIPE_FORMAT_L32_FLOAT", - "l32_float", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 32, 0}, /* x = rgb */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_l32a32_float_description = { - PIPE_FORMAT_L32A32_FLOAT, - "PIPE_FORMAT_L32A32_FLOAT", - "l32a32_float", - {1, 1, 64}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 32, 32}, /* x = rgb */ - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 32, 0}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 32, 0}, /* x = rgb */ - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 32, 32}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_i32_float_description = { - PIPE_FORMAT_I32_FLOAT, - "PIPE_FORMAT_I32_FLOAT", - "i32_float", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 32, 0}, /* x = rgba */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_l8_srgb_description = { - PIPE_FORMAT_L8_SRGB, - "PIPE_FORMAT_L8_SRGB", - "l8_srgb", - {1, 1, 8}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 0}, /* x = rgb */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* sr */ - UTIL_FORMAT_SWIZZLE_X, /* sg */ - UTIL_FORMAT_SWIZZLE_X, /* sb */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_SRGB, -}; - -const struct util_format_description util_format_r8_srgb_description = { - PIPE_FORMAT_R8_SRGB, - "PIPE_FORMAT_R8_SRGB", - "r8_srgb", - {1, 1, 8}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 0}, /* x = r */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* sr */ - UTIL_FORMAT_SWIZZLE_0, /* sg */ - UTIL_FORMAT_SWIZZLE_0, /* sb */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_SRGB, -}; - -const struct util_format_description util_format_l8a8_srgb_description = { - PIPE_FORMAT_L8A8_SRGB, - "PIPE_FORMAT_L8A8_SRGB", - "l8a8_srgb", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* x = rgb */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 0}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 0}, /* x = rgb */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* sr */ - UTIL_FORMAT_SWIZZLE_X, /* sg */ - UTIL_FORMAT_SWIZZLE_X, /* sb */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* sr */ - UTIL_FORMAT_SWIZZLE_X, /* sg */ - UTIL_FORMAT_SWIZZLE_X, /* sb */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_SRGB, -}; - -const struct util_format_description util_format_r8g8b8_srgb_description = { - PIPE_FORMAT_R8G8B8_SRGB, - "PIPE_FORMAT_R8G8B8_SRGB", - "r8g8b8_srgb", - {1, 1, 24}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 3, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 16}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 0}, /* z = b */ - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 16}, /* z = b */ - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* sr */ - UTIL_FORMAT_SWIZZLE_Y, /* sg */ - UTIL_FORMAT_SWIZZLE_Z, /* sb */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* sr */ - UTIL_FORMAT_SWIZZLE_Y, /* sg */ - UTIL_FORMAT_SWIZZLE_Z, /* sb */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_SRGB, -}; - -const struct util_format_description util_format_r8g8b8a8_srgb_description = { - PIPE_FORMAT_R8G8B8A8_SRGB, - "PIPE_FORMAT_R8G8B8A8_SRGB", - "r8g8b8a8_srgb", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 24}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 16}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* z = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 0} /* w = a */ - }, -#else - { - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 16}, /* z = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 24} /* w = a */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* sr */ - UTIL_FORMAT_SWIZZLE_Y, /* sg */ - UTIL_FORMAT_SWIZZLE_Z, /* sb */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* sr */ - UTIL_FORMAT_SWIZZLE_Y, /* sg */ - UTIL_FORMAT_SWIZZLE_Z, /* sb */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_SRGB, -}; - -const struct util_format_description util_format_a8b8g8r8_srgb_description = { - PIPE_FORMAT_A8B8G8R8_SRGB, - "PIPE_FORMAT_A8B8G8R8_SRGB", - "a8b8g8r8_srgb", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 24}, /* x = a */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 16}, /* y = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* z = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 0} /* w = r */ - }, -#else - { - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 0}, /* x = a */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* y = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 16}, /* z = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 24} /* w = r */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_W, /* sr */ - UTIL_FORMAT_SWIZZLE_Z, /* sg */ - UTIL_FORMAT_SWIZZLE_Y, /* sb */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_W, /* sr */ - UTIL_FORMAT_SWIZZLE_Z, /* sg */ - UTIL_FORMAT_SWIZZLE_Y, /* sb */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_SRGB, -}; - -const struct util_format_description util_format_x8b8g8r8_srgb_description = { - PIPE_FORMAT_X8B8G8R8_SRGB, - "PIPE_FORMAT_X8B8G8R8_SRGB", - "x8b8g8r8_srgb", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 24}, /* x = x */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 16}, /* y = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* z = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 0} /* w = r */ - }, -#else - { - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 0}, /* x = x */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* y = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 16}, /* z = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 24} /* w = r */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_W, /* sr */ - UTIL_FORMAT_SWIZZLE_Z, /* sg */ - UTIL_FORMAT_SWIZZLE_Y, /* sb */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_W, /* sr */ - UTIL_FORMAT_SWIZZLE_Z, /* sg */ - UTIL_FORMAT_SWIZZLE_Y, /* sb */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_SRGB, -}; - -const struct util_format_description util_format_b8g8r8a8_srgb_description = { - PIPE_FORMAT_B8G8R8A8_SRGB, - "PIPE_FORMAT_B8G8R8A8_SRGB", - "b8g8r8a8_srgb", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 24}, /* x = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 16}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* z = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 0} /* w = a */ - }, -#else - { - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 0}, /* x = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 16}, /* z = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 24} /* w = a */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_Z, /* sr */ - UTIL_FORMAT_SWIZZLE_Y, /* sg */ - UTIL_FORMAT_SWIZZLE_X, /* sb */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_Z, /* sr */ - UTIL_FORMAT_SWIZZLE_Y, /* sg */ - UTIL_FORMAT_SWIZZLE_X, /* sb */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_SRGB, -}; - -const struct util_format_description util_format_b8g8r8x8_srgb_description = { - PIPE_FORMAT_B8G8R8X8_SRGB, - "PIPE_FORMAT_B8G8R8X8_SRGB", - "b8g8r8x8_srgb", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 24}, /* x = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 16}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* z = r */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 0} /* w = x */ - }, -#else - { - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 0}, /* x = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 16}, /* z = r */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 24} /* w = x */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_Z, /* sr */ - UTIL_FORMAT_SWIZZLE_Y, /* sg */ - UTIL_FORMAT_SWIZZLE_X, /* sb */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_Z, /* sr */ - UTIL_FORMAT_SWIZZLE_Y, /* sg */ - UTIL_FORMAT_SWIZZLE_X, /* sb */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_SRGB, -}; - -const struct util_format_description util_format_a8r8g8b8_srgb_description = { - PIPE_FORMAT_A8R8G8B8_SRGB, - "PIPE_FORMAT_A8R8G8B8_SRGB", - "a8r8g8b8_srgb", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 24}, /* x = a */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 16}, /* y = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* z = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 0} /* w = b */ - }, -#else - { - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 0}, /* x = a */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* y = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 16}, /* z = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 24} /* w = b */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_Y, /* sr */ - UTIL_FORMAT_SWIZZLE_Z, /* sg */ - UTIL_FORMAT_SWIZZLE_W, /* sb */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_Y, /* sr */ - UTIL_FORMAT_SWIZZLE_Z, /* sg */ - UTIL_FORMAT_SWIZZLE_W, /* sb */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_SRGB, -}; - -const struct util_format_description util_format_x8r8g8b8_srgb_description = { - PIPE_FORMAT_X8R8G8B8_SRGB, - "PIPE_FORMAT_X8R8G8B8_SRGB", - "x8r8g8b8_srgb", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 24}, /* x = x */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 16}, /* y = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* z = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 0} /* w = b */ - }, -#else - { - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 0}, /* x = x */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* y = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 16}, /* z = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 24} /* w = b */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_Y, /* sr */ - UTIL_FORMAT_SWIZZLE_Z, /* sg */ - UTIL_FORMAT_SWIZZLE_W, /* sb */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_Y, /* sr */ - UTIL_FORMAT_SWIZZLE_Z, /* sg */ - UTIL_FORMAT_SWIZZLE_W, /* sb */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_SRGB, -}; - -const struct util_format_description util_format_r8sg8sb8ux8u_norm_description = - { - PIPE_FORMAT_R8SG8SB8UX8U_NORM, - "PIPE_FORMAT_R8SG8SB8UX8U_NORM", - "r8sg8sb8ux8u_norm", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - FALSE, /* is_array */ - TRUE, /* is_bitmask */ - TRUE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 24}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 16}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* z = b */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 0} /* w = x */ - }, -#else - { - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 8}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 16}, /* z = b */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 24} /* w = x */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description - util_format_r10sg10sb10sa2u_norm_description = { - PIPE_FORMAT_R10SG10SB10SA2U_NORM, - "PIPE_FORMAT_R10SG10SB10SA2U_NORM", - "r10sg10sb10sa2u_norm", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - FALSE, /* is_array */ - TRUE, /* is_bitmask */ - TRUE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 2, 30}, /* x = a */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 10, 20}, /* y = b */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 10, 10}, /* z = g */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 10, 0} /* w = r */ - }, -#else - { - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 10, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 10, 10}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 10, 20}, /* z = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 2, 30} /* w = a */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_W, /* r */ - UTIL_FORMAT_SWIZZLE_Z, /* g */ - UTIL_FORMAT_SWIZZLE_Y, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r5sg5sb6u_norm_description = { - PIPE_FORMAT_R5SG5SB6U_NORM, - "PIPE_FORMAT_R5SG5SB6U_NORM", - "r5sg5sb6u_norm", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 3, /* nr_channels */ - FALSE, /* is_array */ - TRUE, /* is_bitmask */ - TRUE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 6, 10}, /* x = b */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 5, 5}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 5, 0}, /* z = r */ - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 5, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 5, 5}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 6, 10}, /* z = b */ - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_Z, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_s8_uint_description = { - PIPE_FORMAT_S8_UINT, - "PIPE_FORMAT_S8_UINT", - "s8_uint", - {1, 1, 8}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 8, 0}, /* x = s */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_NONE, /* z */ - UTIL_FORMAT_SWIZZLE_X, /* s */ - UTIL_FORMAT_SWIZZLE_NONE, /* ignored */ - UTIL_FORMAT_SWIZZLE_NONE /* ignored */ - }, - UTIL_FORMAT_COLORSPACE_ZS, -}; - -const struct util_format_description util_format_z16_unorm_description = { - PIPE_FORMAT_Z16_UNORM, - "PIPE_FORMAT_Z16_UNORM", - "z16_unorm", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 16, 0}, /* x = z */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* z */ - UTIL_FORMAT_SWIZZLE_NONE, /* s */ - UTIL_FORMAT_SWIZZLE_NONE, /* ignored */ - UTIL_FORMAT_SWIZZLE_NONE /* ignored */ - }, - UTIL_FORMAT_COLORSPACE_ZS, -}; - -const struct util_format_description util_format_z32_unorm_description = { - PIPE_FORMAT_Z32_UNORM, - "PIPE_FORMAT_Z32_UNORM", - "z32_unorm", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 32, 0}, /* x = z */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* z */ - UTIL_FORMAT_SWIZZLE_NONE, /* s */ - UTIL_FORMAT_SWIZZLE_NONE, /* ignored */ - UTIL_FORMAT_SWIZZLE_NONE /* ignored */ - }, - UTIL_FORMAT_COLORSPACE_ZS, -}; - -const struct util_format_description util_format_z32_float_description = { - PIPE_FORMAT_Z32_FLOAT, - "PIPE_FORMAT_Z32_FLOAT", - "z32_float", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 32, 0}, /* x = z */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* z */ - UTIL_FORMAT_SWIZZLE_NONE, /* s */ - UTIL_FORMAT_SWIZZLE_NONE, /* ignored */ - UTIL_FORMAT_SWIZZLE_NONE /* ignored */ - }, - UTIL_FORMAT_COLORSPACE_ZS, -}; - -const struct util_format_description util_format_z24_unorm_s8_uint_description = - { - PIPE_FORMAT_Z24_UNORM_S8_UINT, - "PIPE_FORMAT_Z24_UNORM_S8_UINT", - "z24_unorm_s8_uint", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - FALSE, /* is_array */ - TRUE, /* is_bitmask */ - TRUE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 8, 24}, /* x = s */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 24, 0}, /* y = z */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 24, 0}, /* x = z */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 8, 24}, /* y = s */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_Y, /* z */ - UTIL_FORMAT_SWIZZLE_X, /* s */ - UTIL_FORMAT_SWIZZLE_NONE, /* ignored */ - UTIL_FORMAT_SWIZZLE_NONE /* ignored */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* z */ - UTIL_FORMAT_SWIZZLE_Y, /* s */ - UTIL_FORMAT_SWIZZLE_NONE, /* ignored */ - UTIL_FORMAT_SWIZZLE_NONE /* ignored */ - }, -#endif - UTIL_FORMAT_COLORSPACE_ZS, -}; - -const struct util_format_description util_format_s8_uint_z24_unorm_description = - { - PIPE_FORMAT_S8_UINT_Z24_UNORM, - "PIPE_FORMAT_S8_UINT_Z24_UNORM", - "s8_uint_z24_unorm", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - FALSE, /* is_array */ - TRUE, /* is_bitmask */ - TRUE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 24, 8}, /* x = z */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 8, 0}, /* y = s */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 8, 0}, /* x = s */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 24, 8}, /* y = z */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* z */ - UTIL_FORMAT_SWIZZLE_Y, /* s */ - UTIL_FORMAT_SWIZZLE_NONE, /* ignored */ - UTIL_FORMAT_SWIZZLE_NONE /* ignored */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_Y, /* z */ - UTIL_FORMAT_SWIZZLE_X, /* s */ - UTIL_FORMAT_SWIZZLE_NONE, /* ignored */ - UTIL_FORMAT_SWIZZLE_NONE /* ignored */ - }, -#endif - UTIL_FORMAT_COLORSPACE_ZS, -}; - -const struct util_format_description util_format_x24s8_uint_description = { - PIPE_FORMAT_X24S8_UINT, - "PIPE_FORMAT_X24S8_UINT", - "x24s8_uint", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - FALSE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 8, 24}, /* x = s */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 24, 0}, /* y = x */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 24, 0}, /* x = x */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 8, 24}, /* y = s */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_NONE, /* z */ - UTIL_FORMAT_SWIZZLE_X, /* s */ - UTIL_FORMAT_SWIZZLE_NONE, /* ignored */ - UTIL_FORMAT_SWIZZLE_NONE /* ignored */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_NONE, /* z */ - UTIL_FORMAT_SWIZZLE_Y, /* s */ - UTIL_FORMAT_SWIZZLE_NONE, /* ignored */ - UTIL_FORMAT_SWIZZLE_NONE /* ignored */ - }, -#endif - UTIL_FORMAT_COLORSPACE_ZS, -}; - -const struct util_format_description util_format_s8x24_uint_description = { - PIPE_FORMAT_S8X24_UINT, - "PIPE_FORMAT_S8X24_UINT", - "s8x24_uint", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - FALSE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 24, 8}, /* x = x */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 8, 0}, /* y = s */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 8, 0}, /* x = s */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 24, 8}, /* y = x */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_NONE, /* z */ - UTIL_FORMAT_SWIZZLE_Y, /* s */ - UTIL_FORMAT_SWIZZLE_NONE, /* ignored */ - UTIL_FORMAT_SWIZZLE_NONE /* ignored */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_NONE, /* z */ - UTIL_FORMAT_SWIZZLE_X, /* s */ - UTIL_FORMAT_SWIZZLE_NONE, /* ignored */ - UTIL_FORMAT_SWIZZLE_NONE /* ignored */ - }, -#endif - UTIL_FORMAT_COLORSPACE_ZS, -}; - -const struct util_format_description util_format_z24x8_unorm_description = { - PIPE_FORMAT_Z24X8_UNORM, - "PIPE_FORMAT_Z24X8_UNORM", - "z24x8_unorm", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - FALSE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 24}, /* x = x */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 24, 0}, /* y = z */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 24, 0}, /* x = z */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 24}, /* y = x */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_Y, /* z */ - UTIL_FORMAT_SWIZZLE_NONE, /* s */ - UTIL_FORMAT_SWIZZLE_NONE, /* ignored */ - UTIL_FORMAT_SWIZZLE_NONE /* ignored */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* z */ - UTIL_FORMAT_SWIZZLE_NONE, /* s */ - UTIL_FORMAT_SWIZZLE_NONE, /* ignored */ - UTIL_FORMAT_SWIZZLE_NONE /* ignored */ - }, -#endif - UTIL_FORMAT_COLORSPACE_ZS, -}; - -const struct util_format_description util_format_x8z24_unorm_description = { - PIPE_FORMAT_X8Z24_UNORM, - "PIPE_FORMAT_X8Z24_UNORM", - "x8z24_unorm", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - FALSE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 24, 8}, /* x = z */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 0}, /* y = x */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 0}, /* x = x */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 24, 8}, /* y = z */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* z */ - UTIL_FORMAT_SWIZZLE_NONE, /* s */ - UTIL_FORMAT_SWIZZLE_NONE, /* ignored */ - UTIL_FORMAT_SWIZZLE_NONE /* ignored */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_Y, /* z */ - UTIL_FORMAT_SWIZZLE_NONE, /* s */ - UTIL_FORMAT_SWIZZLE_NONE, /* ignored */ - UTIL_FORMAT_SWIZZLE_NONE /* ignored */ - }, -#endif - UTIL_FORMAT_COLORSPACE_ZS, -}; - -const struct util_format_description - util_format_z32_float_s8x24_uint_description = { - PIPE_FORMAT_Z32_FLOAT_S8X24_UINT, - "PIPE_FORMAT_Z32_FLOAT_S8X24_UINT", - "z32_float_s8x24_uint", - {1, 1, 64}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 3, /* nr_channels */ - FALSE, /* is_array */ - FALSE, /* is_bitmask */ - TRUE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 32, 32}, /* x = z */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 24, 8}, /* y = x */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 8, 0}, /* z = s */ - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 32, 0}, /* x = z */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 8, 32}, /* y = s */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 24, 40}, /* z = x */ - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* z */ - UTIL_FORMAT_SWIZZLE_Z, /* s */ - UTIL_FORMAT_SWIZZLE_NONE, /* ignored */ - UTIL_FORMAT_SWIZZLE_NONE /* ignored */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* z */ - UTIL_FORMAT_SWIZZLE_Y, /* s */ - UTIL_FORMAT_SWIZZLE_NONE, /* ignored */ - UTIL_FORMAT_SWIZZLE_NONE /* ignored */ - }, -#endif - UTIL_FORMAT_COLORSPACE_ZS, -}; - -const struct util_format_description util_format_x32_s8x24_uint_description = { - PIPE_FORMAT_X32_S8X24_UINT, - "PIPE_FORMAT_X32_S8X24_UINT", - "x32_s8x24_uint", - {1, 1, 64}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 3, /* nr_channels */ - FALSE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 32, 32}, /* x = x */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 24, 8}, /* y = x */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 8, 0}, /* z = s */ - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 32, 0}, /* x = x */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 8, 32}, /* y = s */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 24, 40}, /* z = x */ - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_NONE, /* z */ - UTIL_FORMAT_SWIZZLE_Z, /* s */ - UTIL_FORMAT_SWIZZLE_NONE, /* ignored */ - UTIL_FORMAT_SWIZZLE_NONE /* ignored */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_NONE, /* z */ - UTIL_FORMAT_SWIZZLE_Y, /* s */ - UTIL_FORMAT_SWIZZLE_NONE, /* ignored */ - UTIL_FORMAT_SWIZZLE_NONE /* ignored */ - }, -#endif - UTIL_FORMAT_COLORSPACE_ZS, -}; - -const struct util_format_description util_format_uyvy_description = { - PIPE_FORMAT_UYVY, - "PIPE_FORMAT_UYVY", - "uyvy", - {2, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_SUBSAMPLED, - 1, /* nr_channels */ - FALSE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 32, 0}, /* x = x */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* y */ - UTIL_FORMAT_SWIZZLE_Y, /* u */ - UTIL_FORMAT_SWIZZLE_Z, /* v */ - UTIL_FORMAT_SWIZZLE_1 /* ignored */ - }, - UTIL_FORMAT_COLORSPACE_YUV, -}; - -const struct util_format_description util_format_yuyv_description = { - PIPE_FORMAT_YUYV, - "PIPE_FORMAT_YUYV", - "yuyv", - {2, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_SUBSAMPLED, - 1, /* nr_channels */ - FALSE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 32, 0}, /* x = x */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* y */ - UTIL_FORMAT_SWIZZLE_Y, /* u */ - UTIL_FORMAT_SWIZZLE_Z, /* v */ - UTIL_FORMAT_SWIZZLE_1 /* ignored */ - }, - UTIL_FORMAT_COLORSPACE_YUV, -}; - -const struct util_format_description util_format_r8g8_b8g8_unorm_description = { - PIPE_FORMAT_R8G8_B8G8_UNORM, - "PIPE_FORMAT_R8G8_B8G8_UNORM", - "r8g8_b8g8_unorm", - {2, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_SUBSAMPLED, - 1, /* nr_channels */ - FALSE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 32, 0}, /* x = x */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_g8r8_g8b8_unorm_description = { - PIPE_FORMAT_G8R8_G8B8_UNORM, - "PIPE_FORMAT_G8R8_G8B8_UNORM", - "g8r8_g8b8_unorm", - {2, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_SUBSAMPLED, - 1, /* nr_channels */ - FALSE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 32, 0}, /* x = x */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_g8r8_b8r8_unorm_description = { - PIPE_FORMAT_G8R8_B8R8_UNORM, - "PIPE_FORMAT_G8R8_B8R8_UNORM", - "g8r8_b8r8_unorm", - {2, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_SUBSAMPLED, - 1, /* nr_channels */ - FALSE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 32, 0}, /* x = x */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_Y, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r8g8_r8b8_unorm_description = { - PIPE_FORMAT_R8G8_R8B8_UNORM, - "PIPE_FORMAT_R8G8_R8B8_UNORM", - "r8g8_r8b8_unorm", - {2, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_SUBSAMPLED, - 1, /* nr_channels */ - FALSE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 32, 0}, /* x = x */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_Y, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r11g11b10_float_description = { - PIPE_FORMAT_R11G11B10_FLOAT, - "PIPE_FORMAT_R11G11B10_FLOAT", - "r11g11b10_float", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_OTHER, - 1, /* nr_channels */ - FALSE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 32, 0}, /* x = x */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r9g9b9e5_float_description = { - PIPE_FORMAT_R9G9B9E5_FLOAT, - "PIPE_FORMAT_R9G9B9E5_FLOAT", - "r9g9b9e5_float", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_OTHER, - 1, /* nr_channels */ - FALSE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 32, 0}, /* x = x */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r1_unorm_description = { - PIPE_FORMAT_R1_UNORM, - "PIPE_FORMAT_R1_UNORM", - "r1_unorm", - {8, 1, 8}, /* block */ - UTIL_FORMAT_LAYOUT_OTHER, - 1, /* nr_channels */ - FALSE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 0}, /* x = x */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r8g8bx_snorm_description = { - PIPE_FORMAT_R8G8Bx_SNORM, - "PIPE_FORMAT_R8G8Bx_SNORM", - "r8g8bx_snorm", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_OTHER, - 2, /* nr_channels */ - FALSE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 8}, /* x = x */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 0}, /* y = y */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 0}, /* x = x */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 8}, /* y = y */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_dxt1_rgb_description = { - PIPE_FORMAT_DXT1_RGB, - "PIPE_FORMAT_DXT1_RGB", - "dxt1_rgb", - {4, 4, 64}, /* block */ - UTIL_FORMAT_LAYOUT_S3TC, - 1, /* nr_channels */ - FALSE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 64, 0}, /* x = x */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_dxt1_rgba_description = { - PIPE_FORMAT_DXT1_RGBA, - "PIPE_FORMAT_DXT1_RGBA", - "dxt1_rgba", - {4, 4, 64}, /* block */ - UTIL_FORMAT_LAYOUT_S3TC, - 1, /* nr_channels */ - FALSE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 64, 0}, /* x = x */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_dxt3_rgba_description = { - PIPE_FORMAT_DXT3_RGBA, - "PIPE_FORMAT_DXT3_RGBA", - "dxt3_rgba", - {4, 4, 128}, /* block */ - UTIL_FORMAT_LAYOUT_S3TC, - 1, /* nr_channels */ - FALSE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 128, 0}, /* x = x */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_dxt5_rgba_description = { - PIPE_FORMAT_DXT5_RGBA, - "PIPE_FORMAT_DXT5_RGBA", - "dxt5_rgba", - {4, 4, 128}, /* block */ - UTIL_FORMAT_LAYOUT_S3TC, - 1, /* nr_channels */ - FALSE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 128, 0}, /* x = x */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_dxt1_srgb_description = { - PIPE_FORMAT_DXT1_SRGB, - "PIPE_FORMAT_DXT1_SRGB", - "dxt1_srgb", - {4, 4, 64}, /* block */ - UTIL_FORMAT_LAYOUT_S3TC, - 1, /* nr_channels */ - FALSE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 64, 0}, /* x = x */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* sr */ - UTIL_FORMAT_SWIZZLE_Y, /* sg */ - UTIL_FORMAT_SWIZZLE_Z, /* sb */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_SRGB, -}; - -const struct util_format_description util_format_dxt1_srgba_description = { - PIPE_FORMAT_DXT1_SRGBA, - "PIPE_FORMAT_DXT1_SRGBA", - "dxt1_srgba", - {4, 4, 64}, /* block */ - UTIL_FORMAT_LAYOUT_S3TC, - 1, /* nr_channels */ - FALSE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 64, 0}, /* x = x */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* sr */ - UTIL_FORMAT_SWIZZLE_Y, /* sg */ - UTIL_FORMAT_SWIZZLE_Z, /* sb */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, - UTIL_FORMAT_COLORSPACE_SRGB, -}; - -const struct util_format_description util_format_dxt3_srgba_description = { - PIPE_FORMAT_DXT3_SRGBA, - "PIPE_FORMAT_DXT3_SRGBA", - "dxt3_srgba", - {4, 4, 128}, /* block */ - UTIL_FORMAT_LAYOUT_S3TC, - 1, /* nr_channels */ - FALSE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 128, 0}, /* x = x */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* sr */ - UTIL_FORMAT_SWIZZLE_Y, /* sg */ - UTIL_FORMAT_SWIZZLE_Z, /* sb */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, - UTIL_FORMAT_COLORSPACE_SRGB, -}; - -const struct util_format_description util_format_dxt5_srgba_description = { - PIPE_FORMAT_DXT5_SRGBA, - "PIPE_FORMAT_DXT5_SRGBA", - "dxt5_srgba", - {4, 4, 128}, /* block */ - UTIL_FORMAT_LAYOUT_S3TC, - 1, /* nr_channels */ - FALSE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 128, 0}, /* x = x */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* sr */ - UTIL_FORMAT_SWIZZLE_Y, /* sg */ - UTIL_FORMAT_SWIZZLE_Z, /* sb */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, - UTIL_FORMAT_COLORSPACE_SRGB, -}; - -const struct util_format_description util_format_rgtc1_unorm_description = { - PIPE_FORMAT_RGTC1_UNORM, - "PIPE_FORMAT_RGTC1_UNORM", - "rgtc1_unorm", - {4, 4, 64}, /* block */ - UTIL_FORMAT_LAYOUT_RGTC, - 1, /* nr_channels */ - FALSE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 64, 0}, /* x = x */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_rgtc1_snorm_description = { - PIPE_FORMAT_RGTC1_SNORM, - "PIPE_FORMAT_RGTC1_SNORM", - "rgtc1_snorm", - {4, 4, 64}, /* block */ - UTIL_FORMAT_LAYOUT_RGTC, - 1, /* nr_channels */ - FALSE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 64, 0}, /* x = x */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_rgtc2_unorm_description = { - PIPE_FORMAT_RGTC2_UNORM, - "PIPE_FORMAT_RGTC2_UNORM", - "rgtc2_unorm", - {4, 4, 128}, /* block */ - UTIL_FORMAT_LAYOUT_RGTC, - 1, /* nr_channels */ - FALSE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 128, 0}, /* x = x */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_rgtc2_snorm_description = { - PIPE_FORMAT_RGTC2_SNORM, - "PIPE_FORMAT_RGTC2_SNORM", - "rgtc2_snorm", - {4, 4, 128}, /* block */ - UTIL_FORMAT_LAYOUT_RGTC, - 1, /* nr_channels */ - FALSE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 128, 0}, /* x = x */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_latc1_unorm_description = { - PIPE_FORMAT_LATC1_UNORM, - "PIPE_FORMAT_LATC1_UNORM", - "latc1_unorm", - {4, 4, 64}, /* block */ - UTIL_FORMAT_LAYOUT_RGTC, - 1, /* nr_channels */ - FALSE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 64, 0}, /* x = x */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_latc1_snorm_description = { - PIPE_FORMAT_LATC1_SNORM, - "PIPE_FORMAT_LATC1_SNORM", - "latc1_snorm", - {4, 4, 64}, /* block */ - UTIL_FORMAT_LAYOUT_RGTC, - 1, /* nr_channels */ - FALSE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 64, 0}, /* x = x */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_latc2_unorm_description = { - PIPE_FORMAT_LATC2_UNORM, - "PIPE_FORMAT_LATC2_UNORM", - "latc2_unorm", - {4, 4, 128}, /* block */ - UTIL_FORMAT_LAYOUT_RGTC, - 1, /* nr_channels */ - FALSE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 128, 0}, /* x = x */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_latc2_snorm_description = { - PIPE_FORMAT_LATC2_SNORM, - "PIPE_FORMAT_LATC2_SNORM", - "latc2_snorm", - {4, 4, 128}, /* block */ - UTIL_FORMAT_LAYOUT_RGTC, - 1, /* nr_channels */ - FALSE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 128, 0}, /* x = x */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_etc1_rgb8_description = { - PIPE_FORMAT_ETC1_RGB8, - "PIPE_FORMAT_ETC1_RGB8", - "etc1_rgb8", - {4, 4, 64}, /* block */ - UTIL_FORMAT_LAYOUT_ETC, - 1, /* nr_channels */ - FALSE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 64, 0}, /* x = x */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_bptc_rgba_unorm_description = { - PIPE_FORMAT_BPTC_RGBA_UNORM, - "PIPE_FORMAT_BPTC_RGBA_UNORM", - "bptc_rgba_unorm", - {4, 4, 128}, /* block */ - UTIL_FORMAT_LAYOUT_BPTC, - 1, /* nr_channels */ - FALSE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 128, 0}, /* x = x */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_bptc_srgba_description = { - PIPE_FORMAT_BPTC_SRGBA, - "PIPE_FORMAT_BPTC_SRGBA", - "bptc_srgba", - {4, 4, 128}, /* block */ - UTIL_FORMAT_LAYOUT_BPTC, - 1, /* nr_channels */ - FALSE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 128, 0}, /* x = x */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* sr */ - UTIL_FORMAT_SWIZZLE_Y, /* sg */ - UTIL_FORMAT_SWIZZLE_Z, /* sb */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, - UTIL_FORMAT_COLORSPACE_SRGB, -}; - -const struct util_format_description util_format_bptc_rgb_float_description = { - PIPE_FORMAT_BPTC_RGB_FLOAT, - "PIPE_FORMAT_BPTC_RGB_FLOAT", - "bptc_rgb_float", - {4, 4, 128}, /* block */ - UTIL_FORMAT_LAYOUT_BPTC, - 1, /* nr_channels */ - FALSE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 128, 0}, /* x = x */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_bptc_rgb_ufloat_description = { - PIPE_FORMAT_BPTC_RGB_UFLOAT, - "PIPE_FORMAT_BPTC_RGB_UFLOAT", - "bptc_rgb_ufloat", - {4, 4, 128}, /* block */ - UTIL_FORMAT_LAYOUT_BPTC, - 1, /* nr_channels */ - FALSE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 128, 0}, /* x = x */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r64_float_description = { - PIPE_FORMAT_R64_FLOAT, - "PIPE_FORMAT_R64_FLOAT", - "r64_float", - {1, 1, 64}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 64, 0}, /* x = r */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r64g64_float_description = { - PIPE_FORMAT_R64G64_FLOAT, - "PIPE_FORMAT_R64G64_FLOAT", - "r64g64_float", - {1, 1, 128}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 64, 64}, /* x = r */ - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 64, 0}, /* y = g */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 64, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 64, 64}, /* y = g */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r64g64b64_float_description = { - PIPE_FORMAT_R64G64B64_FLOAT, - "PIPE_FORMAT_R64G64B64_FLOAT", - "r64g64b64_float", - {1, 1, 192}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 3, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 64, 128}, /* x = r */ - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 64, 64}, /* y = g */ - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 64, 0}, /* z = b */ - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 64, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 64, 64}, /* y = g */ - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 64, 128}, /* z = b */ - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description - util_format_r64g64b64a64_float_description = { - PIPE_FORMAT_R64G64B64A64_FLOAT, - "PIPE_FORMAT_R64G64B64A64_FLOAT", - "r64g64b64a64_float", - {1, 1, 256}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 64, 192}, /* x = r */ - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 64, 128}, /* y = g */ - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 64, 64}, /* z = b */ - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 64, 0} /* w = a */ - }, -#else - { - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 64, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 64, 64}, /* y = g */ - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 64, 128}, /* z = b */ - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 64, 192} /* w = a */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r32_float_description = { - PIPE_FORMAT_R32_FLOAT, - "PIPE_FORMAT_R32_FLOAT", - "r32_float", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 32, 0}, /* x = r */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r32g32_float_description = { - PIPE_FORMAT_R32G32_FLOAT, - "PIPE_FORMAT_R32G32_FLOAT", - "r32g32_float", - {1, 1, 64}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 32, 32}, /* x = r */ - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 32, 0}, /* y = g */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 32, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 32, 32}, /* y = g */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r32g32b32_float_description = { - PIPE_FORMAT_R32G32B32_FLOAT, - "PIPE_FORMAT_R32G32B32_FLOAT", - "r32g32b32_float", - {1, 1, 96}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 3, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 32, 64}, /* x = r */ - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 32, 32}, /* y = g */ - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 32, 0}, /* z = b */ - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 32, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 32, 32}, /* y = g */ - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 32, 64}, /* z = b */ - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description - util_format_r32g32b32a32_float_description = { - PIPE_FORMAT_R32G32B32A32_FLOAT, - "PIPE_FORMAT_R32G32B32A32_FLOAT", - "r32g32b32a32_float", - {1, 1, 128}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 32, 96}, /* x = r */ - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 32, 64}, /* y = g */ - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 32, 32}, /* z = b */ - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 32, 0} /* w = a */ - }, -#else - { - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 32, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 32, 32}, /* y = g */ - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 32, 64}, /* z = b */ - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 32, 96} /* w = a */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r32_unorm_description = { - PIPE_FORMAT_R32_UNORM, - "PIPE_FORMAT_R32_UNORM", - "r32_unorm", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 32, 0}, /* x = r */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r32g32_unorm_description = { - PIPE_FORMAT_R32G32_UNORM, - "PIPE_FORMAT_R32G32_UNORM", - "r32g32_unorm", - {1, 1, 64}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 32, 32}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 32, 0}, /* y = g */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 32, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 32, 32}, /* y = g */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r32g32b32_unorm_description = { - PIPE_FORMAT_R32G32B32_UNORM, - "PIPE_FORMAT_R32G32B32_UNORM", - "r32g32b32_unorm", - {1, 1, 96}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 3, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 32, 64}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 32, 32}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 32, 0}, /* z = b */ - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 32, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 32, 32}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 32, 64}, /* z = b */ - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description - util_format_r32g32b32a32_unorm_description = { - PIPE_FORMAT_R32G32B32A32_UNORM, - "PIPE_FORMAT_R32G32B32A32_UNORM", - "r32g32b32a32_unorm", - {1, 1, 128}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 32, 96}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 32, 64}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 32, 32}, /* z = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 32, 0} /* w = a */ - }, -#else - { - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 32, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 32, 32}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 32, 64}, /* z = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 32, 96} /* w = a */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r32_uscaled_description = { - PIPE_FORMAT_R32_USCALED, - "PIPE_FORMAT_R32_USCALED", - "r32_uscaled", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 32, 0}, /* x = r */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r32g32_uscaled_description = { - PIPE_FORMAT_R32G32_USCALED, - "PIPE_FORMAT_R32G32_USCALED", - "r32g32_uscaled", - {1, 1, 64}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 32, 32}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 32, 0}, /* y = g */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 32, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 32, 32}, /* y = g */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r32g32b32_uscaled_description = - { - PIPE_FORMAT_R32G32B32_USCALED, - "PIPE_FORMAT_R32G32B32_USCALED", - "r32g32b32_uscaled", - {1, 1, 96}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 3, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 32, 64}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 32, 32}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 32, 0}, /* z = b */ - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 32, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 32, 32}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 32, 64}, /* z = b */ - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description - util_format_r32g32b32a32_uscaled_description = { - PIPE_FORMAT_R32G32B32A32_USCALED, - "PIPE_FORMAT_R32G32B32A32_USCALED", - "r32g32b32a32_uscaled", - {1, 1, 128}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 32, 96}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 32, 64}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 32, 32}, /* z = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 32, 0} /* w = a */ - }, -#else - { - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 32, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 32, 32}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 32, 64}, /* z = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 32, 96} /* w = a */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r32_snorm_description = { - PIPE_FORMAT_R32_SNORM, - "PIPE_FORMAT_R32_SNORM", - "r32_snorm", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 32, 0}, /* x = r */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r32g32_snorm_description = { - PIPE_FORMAT_R32G32_SNORM, - "PIPE_FORMAT_R32G32_SNORM", - "r32g32_snorm", - {1, 1, 64}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 32, 32}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 32, 0}, /* y = g */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 32, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 32, 32}, /* y = g */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r32g32b32_snorm_description = { - PIPE_FORMAT_R32G32B32_SNORM, - "PIPE_FORMAT_R32G32B32_SNORM", - "r32g32b32_snorm", - {1, 1, 96}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 3, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 32, 64}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 32, 32}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 32, 0}, /* z = b */ - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 32, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 32, 32}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 32, 64}, /* z = b */ - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description - util_format_r32g32b32a32_snorm_description = { - PIPE_FORMAT_R32G32B32A32_SNORM, - "PIPE_FORMAT_R32G32B32A32_SNORM", - "r32g32b32a32_snorm", - {1, 1, 128}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 32, 96}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 32, 64}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 32, 32}, /* z = b */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 32, 0} /* w = a */ - }, -#else - { - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 32, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 32, 32}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 32, 64}, /* z = b */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 32, 96} /* w = a */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r32_sscaled_description = { - PIPE_FORMAT_R32_SSCALED, - "PIPE_FORMAT_R32_SSCALED", - "r32_sscaled", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 32, 0}, /* x = r */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r32g32_sscaled_description = { - PIPE_FORMAT_R32G32_SSCALED, - "PIPE_FORMAT_R32G32_SSCALED", - "r32g32_sscaled", - {1, 1, 64}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 32, 32}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 32, 0}, /* y = g */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 32, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 32, 32}, /* y = g */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r32g32b32_sscaled_description = - { - PIPE_FORMAT_R32G32B32_SSCALED, - "PIPE_FORMAT_R32G32B32_SSCALED", - "r32g32b32_sscaled", - {1, 1, 96}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 3, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 32, 64}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 32, 32}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 32, 0}, /* z = b */ - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 32, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 32, 32}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 32, 64}, /* z = b */ - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description - util_format_r32g32b32a32_sscaled_description = { - PIPE_FORMAT_R32G32B32A32_SSCALED, - "PIPE_FORMAT_R32G32B32A32_SSCALED", - "r32g32b32a32_sscaled", - {1, 1, 128}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 32, 96}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 32, 64}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 32, 32}, /* z = b */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 32, 0} /* w = a */ - }, -#else - { - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 32, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 32, 32}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 32, 64}, /* z = b */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 32, 96} /* w = a */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r16_float_description = { - PIPE_FORMAT_R16_FLOAT, - "PIPE_FORMAT_R16_FLOAT", - "r16_float", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 16, 0}, /* x = r */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r16g16_float_description = { - PIPE_FORMAT_R16G16_FLOAT, - "PIPE_FORMAT_R16G16_FLOAT", - "r16g16_float", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 16, 16}, /* x = r */ - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 16, 0}, /* y = g */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 16, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 16, 16}, /* y = g */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r16g16b16_float_description = { - PIPE_FORMAT_R16G16B16_FLOAT, - "PIPE_FORMAT_R16G16B16_FLOAT", - "r16g16b16_float", - {1, 1, 48}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 3, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 16, 32}, /* x = r */ - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 16, 16}, /* y = g */ - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 16, 0}, /* z = b */ - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 16, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 16, 16}, /* y = g */ - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 16, 32}, /* z = b */ - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description - util_format_r16g16b16a16_float_description = { - PIPE_FORMAT_R16G16B16A16_FLOAT, - "PIPE_FORMAT_R16G16B16A16_FLOAT", - "r16g16b16a16_float", - {1, 1, 64}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 16, 48}, /* x = r */ - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 16, 32}, /* y = g */ - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 16, 16}, /* z = b */ - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 16, 0} /* w = a */ - }, -#else - { - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 16, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 16, 16}, /* y = g */ - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 16, 32}, /* z = b */ - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 16, 48} /* w = a */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r16_unorm_description = { - PIPE_FORMAT_R16_UNORM, - "PIPE_FORMAT_R16_UNORM", - "r16_unorm", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 16, 0}, /* x = r */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r16g16_unorm_description = { - PIPE_FORMAT_R16G16_UNORM, - "PIPE_FORMAT_R16G16_UNORM", - "r16g16_unorm", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 16, 16}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 16, 0}, /* y = g */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 16, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 16, 16}, /* y = g */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r16g16b16_unorm_description = { - PIPE_FORMAT_R16G16B16_UNORM, - "PIPE_FORMAT_R16G16B16_UNORM", - "r16g16b16_unorm", - {1, 1, 48}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 3, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 16, 32}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 16, 16}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 16, 0}, /* z = b */ - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 16, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 16, 16}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 16, 32}, /* z = b */ - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description - util_format_r16g16b16a16_unorm_description = { - PIPE_FORMAT_R16G16B16A16_UNORM, - "PIPE_FORMAT_R16G16B16A16_UNORM", - "r16g16b16a16_unorm", - {1, 1, 64}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 16, 48}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 16, 32}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 16, 16}, /* z = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 16, 0} /* w = a */ - }, -#else - { - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 16, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 16, 16}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 16, 32}, /* z = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 16, 48} /* w = a */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r16_uscaled_description = { - PIPE_FORMAT_R16_USCALED, - "PIPE_FORMAT_R16_USCALED", - "r16_uscaled", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 16, 0}, /* x = r */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r16g16_uscaled_description = { - PIPE_FORMAT_R16G16_USCALED, - "PIPE_FORMAT_R16G16_USCALED", - "r16g16_uscaled", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 16, 16}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 16, 0}, /* y = g */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 16, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 16, 16}, /* y = g */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r16g16b16_uscaled_description = - { - PIPE_FORMAT_R16G16B16_USCALED, - "PIPE_FORMAT_R16G16B16_USCALED", - "r16g16b16_uscaled", - {1, 1, 48}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 3, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 16, 32}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 16, 16}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 16, 0}, /* z = b */ - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 16, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 16, 16}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 16, 32}, /* z = b */ - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description - util_format_r16g16b16a16_uscaled_description = { - PIPE_FORMAT_R16G16B16A16_USCALED, - "PIPE_FORMAT_R16G16B16A16_USCALED", - "r16g16b16a16_uscaled", - {1, 1, 64}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 16, 48}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 16, 32}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 16, 16}, /* z = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 16, 0} /* w = a */ - }, -#else - { - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 16, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 16, 16}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 16, 32}, /* z = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 16, 48} /* w = a */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r16_snorm_description = { - PIPE_FORMAT_R16_SNORM, - "PIPE_FORMAT_R16_SNORM", - "r16_snorm", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 16, 0}, /* x = r */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r16g16_snorm_description = { - PIPE_FORMAT_R16G16_SNORM, - "PIPE_FORMAT_R16G16_SNORM", - "r16g16_snorm", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 16, 16}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 16, 0}, /* y = g */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 16, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 16, 16}, /* y = g */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r16g16b16_snorm_description = { - PIPE_FORMAT_R16G16B16_SNORM, - "PIPE_FORMAT_R16G16B16_SNORM", - "r16g16b16_snorm", - {1, 1, 48}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 3, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 16, 32}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 16, 16}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 16, 0}, /* z = b */ - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 16, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 16, 16}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 16, 32}, /* z = b */ - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description - util_format_r16g16b16a16_snorm_description = { - PIPE_FORMAT_R16G16B16A16_SNORM, - "PIPE_FORMAT_R16G16B16A16_SNORM", - "r16g16b16a16_snorm", - {1, 1, 64}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 16, 48}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 16, 32}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 16, 16}, /* z = b */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 16, 0} /* w = a */ - }, -#else - { - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 16, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 16, 16}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 16, 32}, /* z = b */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 16, 48} /* w = a */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r16_sscaled_description = { - PIPE_FORMAT_R16_SSCALED, - "PIPE_FORMAT_R16_SSCALED", - "r16_sscaled", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 16, 0}, /* x = r */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r16g16_sscaled_description = { - PIPE_FORMAT_R16G16_SSCALED, - "PIPE_FORMAT_R16G16_SSCALED", - "r16g16_sscaled", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 16, 16}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 16, 0}, /* y = g */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 16, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 16, 16}, /* y = g */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r16g16b16_sscaled_description = - { - PIPE_FORMAT_R16G16B16_SSCALED, - "PIPE_FORMAT_R16G16B16_SSCALED", - "r16g16b16_sscaled", - {1, 1, 48}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 3, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 16, 32}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 16, 16}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 16, 0}, /* z = b */ - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 16, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 16, 16}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 16, 32}, /* z = b */ - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description - util_format_r16g16b16a16_sscaled_description = { - PIPE_FORMAT_R16G16B16A16_SSCALED, - "PIPE_FORMAT_R16G16B16A16_SSCALED", - "r16g16b16a16_sscaled", - {1, 1, 64}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 16, 48}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 16, 32}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 16, 16}, /* z = b */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 16, 0} /* w = a */ - }, -#else - { - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 16, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 16, 16}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 16, 32}, /* z = b */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 16, 48} /* w = a */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r8_unorm_description = { - PIPE_FORMAT_R8_UNORM, - "PIPE_FORMAT_R8_UNORM", - "r8_unorm", - {1, 1, 8}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 0}, /* x = r */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r8g8_unorm_description = { - PIPE_FORMAT_R8G8_UNORM, - "PIPE_FORMAT_R8G8_UNORM", - "r8g8_unorm", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 0}, /* y = g */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* y = g */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r8g8b8_unorm_description = { - PIPE_FORMAT_R8G8B8_UNORM, - "PIPE_FORMAT_R8G8B8_UNORM", - "r8g8b8_unorm", - {1, 1, 24}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 3, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 16}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 0}, /* z = b */ - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 16}, /* z = b */ - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r8g8b8a8_unorm_description = { - PIPE_FORMAT_R8G8B8A8_UNORM, - "PIPE_FORMAT_R8G8B8A8_UNORM", - "r8g8b8a8_unorm", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 24}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 16}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* z = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 0} /* w = a */ - }, -#else - { - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 16}, /* z = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 24} /* w = a */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r8_uscaled_description = { - PIPE_FORMAT_R8_USCALED, - "PIPE_FORMAT_R8_USCALED", - "r8_uscaled", - {1, 1, 8}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 8, 0}, /* x = r */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r8g8_uscaled_description = { - PIPE_FORMAT_R8G8_USCALED, - "PIPE_FORMAT_R8G8_USCALED", - "r8g8_uscaled", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 8, 8}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 8, 0}, /* y = g */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 8, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 8, 8}, /* y = g */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r8g8b8_uscaled_description = { - PIPE_FORMAT_R8G8B8_USCALED, - "PIPE_FORMAT_R8G8B8_USCALED", - "r8g8b8_uscaled", - {1, 1, 24}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 3, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 8, 16}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 8, 8}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 8, 0}, /* z = b */ - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 8, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 8, 8}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 8, 16}, /* z = b */ - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r8g8b8a8_uscaled_description = - { - PIPE_FORMAT_R8G8B8A8_USCALED, - "PIPE_FORMAT_R8G8B8A8_USCALED", - "r8g8b8a8_uscaled", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 8, 24}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 8, 16}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 8, 8}, /* z = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 8, 0} /* w = a */ - }, -#else - { - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 8, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 8, 8}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 8, 16}, /* z = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 8, 24} /* w = a */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r8_snorm_description = { - PIPE_FORMAT_R8_SNORM, - "PIPE_FORMAT_R8_SNORM", - "r8_snorm", - {1, 1, 8}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 0}, /* x = r */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r8g8_snorm_description = { - PIPE_FORMAT_R8G8_SNORM, - "PIPE_FORMAT_R8G8_SNORM", - "r8g8_snorm", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 8}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 0}, /* y = g */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 8}, /* y = g */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r8g8b8_snorm_description = { - PIPE_FORMAT_R8G8B8_SNORM, - "PIPE_FORMAT_R8G8B8_SNORM", - "r8g8b8_snorm", - {1, 1, 24}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 3, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 16}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 8}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 0}, /* z = b */ - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 8}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 16}, /* z = b */ - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r8g8b8a8_snorm_description = { - PIPE_FORMAT_R8G8B8A8_SNORM, - "PIPE_FORMAT_R8G8B8A8_SNORM", - "r8g8b8a8_snorm", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 24}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 16}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 8}, /* z = b */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 0} /* w = a */ - }, -#else - { - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 8}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 16}, /* z = b */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 24} /* w = a */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r8_sscaled_description = { - PIPE_FORMAT_R8_SSCALED, - "PIPE_FORMAT_R8_SSCALED", - "r8_sscaled", - {1, 1, 8}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 8, 0}, /* x = r */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r8g8_sscaled_description = { - PIPE_FORMAT_R8G8_SSCALED, - "PIPE_FORMAT_R8G8_SSCALED", - "r8g8_sscaled", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 8, 8}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 8, 0}, /* y = g */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 8, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 8, 8}, /* y = g */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r8g8b8_sscaled_description = { - PIPE_FORMAT_R8G8B8_SSCALED, - "PIPE_FORMAT_R8G8B8_SSCALED", - "r8g8b8_sscaled", - {1, 1, 24}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 3, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 8, 16}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 8, 8}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 8, 0}, /* z = b */ - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 8, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 8, 8}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 8, 16}, /* z = b */ - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r8g8b8a8_sscaled_description = - { - PIPE_FORMAT_R8G8B8A8_SSCALED, - "PIPE_FORMAT_R8G8B8A8_SSCALED", - "r8g8b8a8_sscaled", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 8, 24}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 8, 16}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 8, 8}, /* z = b */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 8, 0} /* w = a */ - }, -#else - { - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 8, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 8, 8}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 8, 16}, /* z = b */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 8, 24} /* w = a */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r32_fixed_description = { - PIPE_FORMAT_R32_FIXED, - "PIPE_FORMAT_R32_FIXED", - "r32_fixed", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_FIXED, FALSE, FALSE, 32, 0}, /* x = r */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r32g32_fixed_description = { - PIPE_FORMAT_R32G32_FIXED, - "PIPE_FORMAT_R32G32_FIXED", - "r32g32_fixed", - {1, 1, 64}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_FIXED, FALSE, FALSE, 32, 32}, /* x = r */ - {UTIL_FORMAT_TYPE_FIXED, FALSE, FALSE, 32, 0}, /* y = g */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_FIXED, FALSE, FALSE, 32, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_FIXED, FALSE, FALSE, 32, 32}, /* y = g */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r32g32b32_fixed_description = { - PIPE_FORMAT_R32G32B32_FIXED, - "PIPE_FORMAT_R32G32B32_FIXED", - "r32g32b32_fixed", - {1, 1, 96}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 3, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_FIXED, FALSE, FALSE, 32, 64}, /* x = r */ - {UTIL_FORMAT_TYPE_FIXED, FALSE, FALSE, 32, 32}, /* y = g */ - {UTIL_FORMAT_TYPE_FIXED, FALSE, FALSE, 32, 0}, /* z = b */ - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_FIXED, FALSE, FALSE, 32, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_FIXED, FALSE, FALSE, 32, 32}, /* y = g */ - {UTIL_FORMAT_TYPE_FIXED, FALSE, FALSE, 32, 64}, /* z = b */ - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description - util_format_r32g32b32a32_fixed_description = { - PIPE_FORMAT_R32G32B32A32_FIXED, - "PIPE_FORMAT_R32G32B32A32_FIXED", - "r32g32b32a32_fixed", - {1, 1, 128}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_FIXED, FALSE, FALSE, 32, 96}, /* x = r */ - {UTIL_FORMAT_TYPE_FIXED, FALSE, FALSE, 32, 64}, /* y = g */ - {UTIL_FORMAT_TYPE_FIXED, FALSE, FALSE, 32, 32}, /* z = b */ - {UTIL_FORMAT_TYPE_FIXED, FALSE, FALSE, 32, 0} /* w = a */ - }, -#else - { - {UTIL_FORMAT_TYPE_FIXED, FALSE, FALSE, 32, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_FIXED, FALSE, FALSE, 32, 32}, /* y = g */ - {UTIL_FORMAT_TYPE_FIXED, FALSE, FALSE, 32, 64}, /* z = b */ - {UTIL_FORMAT_TYPE_FIXED, FALSE, FALSE, 32, 96} /* w = a */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description - util_format_r10g10b10x2_uscaled_description = { - PIPE_FORMAT_R10G10B10X2_USCALED, - "PIPE_FORMAT_R10G10B10X2_USCALED", - "r10g10b10x2_uscaled", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - FALSE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 2, 30}, /* x = x */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 10, 20}, /* y = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 10, 10}, /* z = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 10, 0} /* w = r */ - }, -#else - { - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 10, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 10, 10}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 10, 20}, /* z = b */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 2, 30} /* w = x */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_W, /* r */ - UTIL_FORMAT_SWIZZLE_Z, /* g */ - UTIL_FORMAT_SWIZZLE_Y, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r10g10b10x2_snorm_description = - { - PIPE_FORMAT_R10G10B10X2_SNORM, - "PIPE_FORMAT_R10G10B10X2_SNORM", - "r10g10b10x2_snorm", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - FALSE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 2, 30}, /* x = x */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 10, 20}, /* y = b */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 10, 10}, /* z = g */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 10, 0} /* w = r */ - }, -#else - { - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 10, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 10, 10}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 10, 20}, /* z = b */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 2, 30} /* w = x */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_W, /* r */ - UTIL_FORMAT_SWIZZLE_Z, /* g */ - UTIL_FORMAT_SWIZZLE_Y, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_yv12_description = { - PIPE_FORMAT_YV12, - "PIPE_FORMAT_YV12", - "yv12", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_OTHER, - 4, /* nr_channels */ - FALSE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 24}, /* x = x */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 16}, /* y = y */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 8}, /* z = z */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 0} /* w = w */ - }, -#else - { - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 0}, /* x = x */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 8}, /* y = y */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 16}, /* z = z */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 24} /* w = w */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* y */ - UTIL_FORMAT_SWIZZLE_Y, /* u */ - UTIL_FORMAT_SWIZZLE_Z, /* v */ - UTIL_FORMAT_SWIZZLE_W /* ignored */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* y */ - UTIL_FORMAT_SWIZZLE_Y, /* u */ - UTIL_FORMAT_SWIZZLE_Z, /* v */ - UTIL_FORMAT_SWIZZLE_W /* ignored */ - }, -#endif - UTIL_FORMAT_COLORSPACE_YUV, -}; - -const struct util_format_description util_format_yv16_description = { - PIPE_FORMAT_YV16, - "PIPE_FORMAT_YV16", - "yv16", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_OTHER, - 4, /* nr_channels */ - FALSE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 24}, /* x = x */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 16}, /* y = y */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 8}, /* z = z */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 0} /* w = w */ - }, -#else - { - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 0}, /* x = x */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 8}, /* y = y */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 16}, /* z = z */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 24} /* w = w */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* y */ - UTIL_FORMAT_SWIZZLE_Y, /* u */ - UTIL_FORMAT_SWIZZLE_Z, /* v */ - UTIL_FORMAT_SWIZZLE_W /* ignored */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* y */ - UTIL_FORMAT_SWIZZLE_Y, /* u */ - UTIL_FORMAT_SWIZZLE_Z, /* v */ - UTIL_FORMAT_SWIZZLE_W /* ignored */ - }, -#endif - UTIL_FORMAT_COLORSPACE_YUV, -}; - -const struct util_format_description util_format_iyuv_description = { - PIPE_FORMAT_IYUV, - "PIPE_FORMAT_IYUV", - "iyuv", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_OTHER, - 4, /* nr_channels */ - FALSE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 24}, /* x = x */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 16}, /* y = y */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 8}, /* z = z */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 0} /* w = w */ - }, -#else - { - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 0}, /* x = x */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 8}, /* y = y */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 16}, /* z = z */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 24} /* w = w */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* y */ - UTIL_FORMAT_SWIZZLE_Y, /* u */ - UTIL_FORMAT_SWIZZLE_Z, /* v */ - UTIL_FORMAT_SWIZZLE_W /* ignored */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* y */ - UTIL_FORMAT_SWIZZLE_Y, /* u */ - UTIL_FORMAT_SWIZZLE_Z, /* v */ - UTIL_FORMAT_SWIZZLE_W /* ignored */ - }, -#endif - UTIL_FORMAT_COLORSPACE_YUV, -}; - -const struct util_format_description util_format_nv12_description = { - PIPE_FORMAT_NV12, - "PIPE_FORMAT_NV12", - "nv12", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_OTHER, - 4, /* nr_channels */ - FALSE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 24}, /* x = x */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 16}, /* y = y */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 8}, /* z = z */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 0} /* w = w */ - }, -#else - { - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 0}, /* x = x */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 8}, /* y = y */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 16}, /* z = z */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 24} /* w = w */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* y */ - UTIL_FORMAT_SWIZZLE_Y, /* u */ - UTIL_FORMAT_SWIZZLE_Z, /* v */ - UTIL_FORMAT_SWIZZLE_W /* ignored */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* y */ - UTIL_FORMAT_SWIZZLE_Y, /* u */ - UTIL_FORMAT_SWIZZLE_Z, /* v */ - UTIL_FORMAT_SWIZZLE_W /* ignored */ - }, -#endif - UTIL_FORMAT_COLORSPACE_YUV, -}; - -const struct util_format_description util_format_nv21_description = { - PIPE_FORMAT_NV21, - "PIPE_FORMAT_NV21", - "nv21", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_OTHER, - 4, /* nr_channels */ - FALSE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 24}, /* x = x */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 16}, /* y = y */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 8}, /* z = z */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 0} /* w = w */ - }, -#else - { - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 0}, /* x = x */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 8}, /* y = y */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 16}, /* z = z */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 24} /* w = w */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* y */ - UTIL_FORMAT_SWIZZLE_Y, /* u */ - UTIL_FORMAT_SWIZZLE_Z, /* v */ - UTIL_FORMAT_SWIZZLE_W /* ignored */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* y */ - UTIL_FORMAT_SWIZZLE_Y, /* u */ - UTIL_FORMAT_SWIZZLE_Z, /* v */ - UTIL_FORMAT_SWIZZLE_W /* ignored */ - }, -#endif - UTIL_FORMAT_COLORSPACE_YUV, -}; - -const struct util_format_description util_format_a4r4_unorm_description = { - PIPE_FORMAT_A4R4_UNORM, - "PIPE_FORMAT_A4R4_UNORM", - "a4r4_unorm", - {1, 1, 8}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - FALSE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 4, 4}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 4, 0}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 4, 0}, /* x = a */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 4, 4}, /* y = r */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_Y, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r4a4_unorm_description = { - PIPE_FORMAT_R4A4_UNORM, - "PIPE_FORMAT_R4A4_UNORM", - "r4a4_unorm", - {1, 1, 8}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - FALSE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 4, 4}, /* x = a */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 4, 0}, /* y = r */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 4, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 4, 4}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_Y, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r8a8_unorm_description = { - PIPE_FORMAT_R8A8_UNORM, - "PIPE_FORMAT_R8A8_UNORM", - "r8a8_unorm", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 0}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_a8r8_unorm_description = { - PIPE_FORMAT_A8R8_UNORM, - "PIPE_FORMAT_A8R8_UNORM", - "a8r8_unorm", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* x = a */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 0}, /* y = r */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 0}, /* x = a */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* y = r */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_Y, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_Y, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description - util_format_r10g10b10a2_uscaled_description = { - PIPE_FORMAT_R10G10B10A2_USCALED, - "PIPE_FORMAT_R10G10B10A2_USCALED", - "r10g10b10a2_uscaled", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - FALSE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 2, 30}, /* x = a */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 10, 20}, /* y = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 10, 10}, /* z = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 10, 0} /* w = r */ - }, -#else - { - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 10, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 10, 10}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 10, 20}, /* z = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 2, 30} /* w = a */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_W, /* r */ - UTIL_FORMAT_SWIZZLE_Z, /* g */ - UTIL_FORMAT_SWIZZLE_Y, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description - util_format_r10g10b10a2_sscaled_description = { - PIPE_FORMAT_R10G10B10A2_SSCALED, - "PIPE_FORMAT_R10G10B10A2_SSCALED", - "r10g10b10a2_sscaled", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - FALSE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 2, 30}, /* x = a */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 10, 20}, /* y = b */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 10, 10}, /* z = g */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 10, 0} /* w = r */ - }, -#else - { - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 10, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 10, 10}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 10, 20}, /* z = b */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 2, 30} /* w = a */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_W, /* r */ - UTIL_FORMAT_SWIZZLE_Z, /* g */ - UTIL_FORMAT_SWIZZLE_Y, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r10g10b10a2_snorm_description = - { - PIPE_FORMAT_R10G10B10A2_SNORM, - "PIPE_FORMAT_R10G10B10A2_SNORM", - "r10g10b10a2_snorm", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - FALSE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 2, 30}, /* x = a */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 10, 20}, /* y = b */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 10, 10}, /* z = g */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 10, 0} /* w = r */ - }, -#else - { - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 10, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 10, 10}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 10, 20}, /* z = b */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 2, 30} /* w = a */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_W, /* r */ - UTIL_FORMAT_SWIZZLE_Z, /* g */ - UTIL_FORMAT_SWIZZLE_Y, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description - util_format_b10g10r10a2_uscaled_description = { - PIPE_FORMAT_B10G10R10A2_USCALED, - "PIPE_FORMAT_B10G10R10A2_USCALED", - "b10g10r10a2_uscaled", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - FALSE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 2, 30}, /* x = a */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 10, 20}, /* y = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 10, 10}, /* z = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 10, 0} /* w = b */ - }, -#else - { - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 10, 0}, /* x = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 10, 10}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 10, 20}, /* z = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, FALSE, 2, 30} /* w = a */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_Y, /* r */ - UTIL_FORMAT_SWIZZLE_Z, /* g */ - UTIL_FORMAT_SWIZZLE_W, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_Z, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description - util_format_b10g10r10a2_sscaled_description = { - PIPE_FORMAT_B10G10R10A2_SSCALED, - "PIPE_FORMAT_B10G10R10A2_SSCALED", - "b10g10r10a2_sscaled", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - FALSE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 2, 30}, /* x = a */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 10, 20}, /* y = r */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 10, 10}, /* z = g */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 10, 0} /* w = b */ - }, -#else - { - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 10, 0}, /* x = b */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 10, 10}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 10, 20}, /* z = r */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, FALSE, 2, 30} /* w = a */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_Y, /* r */ - UTIL_FORMAT_SWIZZLE_Z, /* g */ - UTIL_FORMAT_SWIZZLE_W, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_Z, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_b10g10r10a2_snorm_description = - { - PIPE_FORMAT_B10G10R10A2_SNORM, - "PIPE_FORMAT_B10G10R10A2_SNORM", - "b10g10r10a2_snorm", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - FALSE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 2, 30}, /* x = a */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 10, 20}, /* y = r */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 10, 10}, /* z = g */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 10, 0} /* w = b */ - }, -#else - { - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 10, 0}, /* x = b */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 10, 10}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 10, 20}, /* z = r */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 2, 30} /* w = a */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_Y, /* r */ - UTIL_FORMAT_SWIZZLE_Z, /* g */ - UTIL_FORMAT_SWIZZLE_W, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_Z, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r8_uint_description = { - PIPE_FORMAT_R8_UINT, - "PIPE_FORMAT_R8_UINT", - "r8_uint", - {1, 1, 8}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 8, 0}, /* x = r */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r8g8_uint_description = { - PIPE_FORMAT_R8G8_UINT, - "PIPE_FORMAT_R8G8_UINT", - "r8g8_uint", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 8, 8}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 8, 0}, /* y = g */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 8, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 8, 8}, /* y = g */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r8g8b8_uint_description = { - PIPE_FORMAT_R8G8B8_UINT, - "PIPE_FORMAT_R8G8B8_UINT", - "r8g8b8_uint", - {1, 1, 24}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 3, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 8, 16}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 8, 8}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 8, 0}, /* z = b */ - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 8, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 8, 8}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 8, 16}, /* z = b */ - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r8g8b8a8_uint_description = { - PIPE_FORMAT_R8G8B8A8_UINT, - "PIPE_FORMAT_R8G8B8A8_UINT", - "r8g8b8a8_uint", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 8, 24}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 8, 16}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 8, 8}, /* z = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 8, 0} /* w = a */ - }, -#else - { - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 8, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 8, 8}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 8, 16}, /* z = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 8, 24} /* w = a */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r8_sint_description = { - PIPE_FORMAT_R8_SINT, - "PIPE_FORMAT_R8_SINT", - "r8_sint", - {1, 1, 8}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 8, 0}, /* x = r */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r8g8_sint_description = { - PIPE_FORMAT_R8G8_SINT, - "PIPE_FORMAT_R8G8_SINT", - "r8g8_sint", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 8, 8}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 8, 0}, /* y = g */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 8, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 8, 8}, /* y = g */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r8g8b8_sint_description = { - PIPE_FORMAT_R8G8B8_SINT, - "PIPE_FORMAT_R8G8B8_SINT", - "r8g8b8_sint", - {1, 1, 24}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 3, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 8, 16}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 8, 8}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 8, 0}, /* z = b */ - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 8, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 8, 8}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 8, 16}, /* z = b */ - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r8g8b8a8_sint_description = { - PIPE_FORMAT_R8G8B8A8_SINT, - "PIPE_FORMAT_R8G8B8A8_SINT", - "r8g8b8a8_sint", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 8, 24}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 8, 16}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 8, 8}, /* z = b */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 8, 0} /* w = a */ - }, -#else - { - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 8, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 8, 8}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 8, 16}, /* z = b */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 8, 24} /* w = a */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r16_uint_description = { - PIPE_FORMAT_R16_UINT, - "PIPE_FORMAT_R16_UINT", - "r16_uint", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 16, 0}, /* x = r */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r16g16_uint_description = { - PIPE_FORMAT_R16G16_UINT, - "PIPE_FORMAT_R16G16_UINT", - "r16g16_uint", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 16, 16}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 16, 0}, /* y = g */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 16, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 16, 16}, /* y = g */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r16g16b16_uint_description = { - PIPE_FORMAT_R16G16B16_UINT, - "PIPE_FORMAT_R16G16B16_UINT", - "r16g16b16_uint", - {1, 1, 48}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 3, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 16, 32}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 16, 16}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 16, 0}, /* z = b */ - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 16, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 16, 16}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 16, 32}, /* z = b */ - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r16g16b16a16_uint_description = - { - PIPE_FORMAT_R16G16B16A16_UINT, - "PIPE_FORMAT_R16G16B16A16_UINT", - "r16g16b16a16_uint", - {1, 1, 64}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 16, 48}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 16, 32}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 16, 16}, /* z = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 16, 0} /* w = a */ - }, -#else - { - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 16, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 16, 16}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 16, 32}, /* z = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 16, 48} /* w = a */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r16_sint_description = { - PIPE_FORMAT_R16_SINT, - "PIPE_FORMAT_R16_SINT", - "r16_sint", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 16, 0}, /* x = r */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r16g16_sint_description = { - PIPE_FORMAT_R16G16_SINT, - "PIPE_FORMAT_R16G16_SINT", - "r16g16_sint", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 16, 16}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 16, 0}, /* y = g */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 16, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 16, 16}, /* y = g */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r16g16b16_sint_description = { - PIPE_FORMAT_R16G16B16_SINT, - "PIPE_FORMAT_R16G16B16_SINT", - "r16g16b16_sint", - {1, 1, 48}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 3, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 16, 32}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 16, 16}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 16, 0}, /* z = b */ - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 16, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 16, 16}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 16, 32}, /* z = b */ - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r16g16b16a16_sint_description = - { - PIPE_FORMAT_R16G16B16A16_SINT, - "PIPE_FORMAT_R16G16B16A16_SINT", - "r16g16b16a16_sint", - {1, 1, 64}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 16, 48}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 16, 32}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 16, 16}, /* z = b */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 16, 0} /* w = a */ - }, -#else - { - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 16, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 16, 16}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 16, 32}, /* z = b */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 16, 48} /* w = a */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r32_uint_description = { - PIPE_FORMAT_R32_UINT, - "PIPE_FORMAT_R32_UINT", - "r32_uint", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 32, 0}, /* x = r */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r32g32_uint_description = { - PIPE_FORMAT_R32G32_UINT, - "PIPE_FORMAT_R32G32_UINT", - "r32g32_uint", - {1, 1, 64}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 32, 32}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 32, 0}, /* y = g */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 32, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 32, 32}, /* y = g */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r32g32b32_uint_description = { - PIPE_FORMAT_R32G32B32_UINT, - "PIPE_FORMAT_R32G32B32_UINT", - "r32g32b32_uint", - {1, 1, 96}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 3, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 32, 64}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 32, 32}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 32, 0}, /* z = b */ - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 32, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 32, 32}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 32, 64}, /* z = b */ - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r32g32b32a32_uint_description = - { - PIPE_FORMAT_R32G32B32A32_UINT, - "PIPE_FORMAT_R32G32B32A32_UINT", - "r32g32b32a32_uint", - {1, 1, 128}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 32, 96}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 32, 64}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 32, 32}, /* z = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 32, 0} /* w = a */ - }, -#else - { - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 32, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 32, 32}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 32, 64}, /* z = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 32, 96} /* w = a */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r32_sint_description = { - PIPE_FORMAT_R32_SINT, - "PIPE_FORMAT_R32_SINT", - "r32_sint", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 32, 0}, /* x = r */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r32g32_sint_description = { - PIPE_FORMAT_R32G32_SINT, - "PIPE_FORMAT_R32G32_SINT", - "r32g32_sint", - {1, 1, 64}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 32, 32}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 32, 0}, /* y = g */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 32, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 32, 32}, /* y = g */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r32g32b32_sint_description = { - PIPE_FORMAT_R32G32B32_SINT, - "PIPE_FORMAT_R32G32B32_SINT", - "r32g32b32_sint", - {1, 1, 96}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 3, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 32, 64}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 32, 32}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 32, 0}, /* z = b */ - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 32, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 32, 32}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 32, 64}, /* z = b */ - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r32g32b32a32_sint_description = - { - PIPE_FORMAT_R32G32B32A32_SINT, - "PIPE_FORMAT_R32G32B32A32_SINT", - "r32g32b32a32_sint", - {1, 1, 128}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 32, 96}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 32, 64}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 32, 32}, /* z = b */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 32, 0} /* w = a */ - }, -#else - { - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 32, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 32, 32}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 32, 64}, /* z = b */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 32, 96} /* w = a */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_a8_uint_description = { - PIPE_FORMAT_A8_UINT, - "PIPE_FORMAT_A8_UINT", - "a8_uint", - {1, 1, 8}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 8, 0}, /* x = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_0, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_i8_uint_description = { - PIPE_FORMAT_I8_UINT, - "PIPE_FORMAT_I8_UINT", - "i8_uint", - {1, 1, 8}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 8, 0}, /* x = rgba */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_l8_uint_description = { - PIPE_FORMAT_L8_UINT, - "PIPE_FORMAT_L8_UINT", - "l8_uint", - {1, 1, 8}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 8, 0}, /* x = rgb */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_l8a8_uint_description = { - PIPE_FORMAT_L8A8_UINT, - "PIPE_FORMAT_L8A8_UINT", - "l8a8_uint", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 8, 8}, /* x = rgb */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 8, 0}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 8, 0}, /* x = rgb */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 8, 8}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_a8_sint_description = { - PIPE_FORMAT_A8_SINT, - "PIPE_FORMAT_A8_SINT", - "a8_sint", - {1, 1, 8}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 8, 0}, /* x = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_0, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_i8_sint_description = { - PIPE_FORMAT_I8_SINT, - "PIPE_FORMAT_I8_SINT", - "i8_sint", - {1, 1, 8}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 8, 0}, /* x = rgba */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_l8_sint_description = { - PIPE_FORMAT_L8_SINT, - "PIPE_FORMAT_L8_SINT", - "l8_sint", - {1, 1, 8}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 8, 0}, /* x = rgb */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_l8a8_sint_description = { - PIPE_FORMAT_L8A8_SINT, - "PIPE_FORMAT_L8A8_SINT", - "l8a8_sint", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 8, 8}, /* x = rgb */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 8, 0}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 8, 0}, /* x = rgb */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 8, 8}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_a16_uint_description = { - PIPE_FORMAT_A16_UINT, - "PIPE_FORMAT_A16_UINT", - "a16_uint", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 16, 0}, /* x = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_0, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_i16_uint_description = { - PIPE_FORMAT_I16_UINT, - "PIPE_FORMAT_I16_UINT", - "i16_uint", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 16, 0}, /* x = rgba */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_l16_uint_description = { - PIPE_FORMAT_L16_UINT, - "PIPE_FORMAT_L16_UINT", - "l16_uint", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 16, 0}, /* x = rgb */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_l16a16_uint_description = { - PIPE_FORMAT_L16A16_UINT, - "PIPE_FORMAT_L16A16_UINT", - "l16a16_uint", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 16, 16}, /* x = rgb */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 16, 0}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 16, 0}, /* x = rgb */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 16, 16}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_a16_sint_description = { - PIPE_FORMAT_A16_SINT, - "PIPE_FORMAT_A16_SINT", - "a16_sint", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 16, 0}, /* x = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_0, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_i16_sint_description = { - PIPE_FORMAT_I16_SINT, - "PIPE_FORMAT_I16_SINT", - "i16_sint", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 16, 0}, /* x = rgba */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_l16_sint_description = { - PIPE_FORMAT_L16_SINT, - "PIPE_FORMAT_L16_SINT", - "l16_sint", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 16, 0}, /* x = rgb */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_l16a16_sint_description = { - PIPE_FORMAT_L16A16_SINT, - "PIPE_FORMAT_L16A16_SINT", - "l16a16_sint", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 16, 16}, /* x = rgb */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 16, 0}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 16, 0}, /* x = rgb */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 16, 16}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_a32_uint_description = { - PIPE_FORMAT_A32_UINT, - "PIPE_FORMAT_A32_UINT", - "a32_uint", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 32, 0}, /* x = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_0, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_i32_uint_description = { - PIPE_FORMAT_I32_UINT, - "PIPE_FORMAT_I32_UINT", - "i32_uint", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 32, 0}, /* x = rgba */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_l32_uint_description = { - PIPE_FORMAT_L32_UINT, - "PIPE_FORMAT_L32_UINT", - "l32_uint", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 32, 0}, /* x = rgb */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_l32a32_uint_description = { - PIPE_FORMAT_L32A32_UINT, - "PIPE_FORMAT_L32A32_UINT", - "l32a32_uint", - {1, 1, 64}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 32, 32}, /* x = rgb */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 32, 0}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 32, 0}, /* x = rgb */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 32, 32}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_a32_sint_description = { - PIPE_FORMAT_A32_SINT, - "PIPE_FORMAT_A32_SINT", - "a32_sint", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 32, 0}, /* x = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_0, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_i32_sint_description = { - PIPE_FORMAT_I32_SINT, - "PIPE_FORMAT_I32_SINT", - "i32_sint", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 32, 0}, /* x = rgba */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_l32_sint_description = { - PIPE_FORMAT_L32_SINT, - "PIPE_FORMAT_L32_SINT", - "l32_sint", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 1, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ - {{UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 32, 0}, /* x = rgb */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_l32a32_sint_description = { - PIPE_FORMAT_L32A32_SINT, - "PIPE_FORMAT_L32A32_SINT", - "l32a32_sint", - {1, 1, 64}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 32, 32}, /* x = rgb */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 32, 0}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 32, 0}, /* x = rgb */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 32, 32}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_b10g10r10a2_uint_description = - { - PIPE_FORMAT_B10G10R10A2_UINT, - "PIPE_FORMAT_B10G10R10A2_UINT", - "b10g10r10a2_uint", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - FALSE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 2, 30}, /* x = a */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 10, 20}, /* y = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 10, 10}, /* z = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 10, 0} /* w = b */ - }, -#else - { - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 10, 0}, /* x = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 10, 10}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 10, 20}, /* z = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 2, 30} /* w = a */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_Y, /* r */ - UTIL_FORMAT_SWIZZLE_Z, /* g */ - UTIL_FORMAT_SWIZZLE_W, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_Z, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r8g8b8x8_snorm_description = { - PIPE_FORMAT_R8G8B8X8_SNORM, - "PIPE_FORMAT_R8G8B8X8_SNORM", - "r8g8b8x8_snorm", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 24}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 16}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 8}, /* z = b */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 0} /* w = x */ - }, -#else - { - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 8}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 16}, /* z = b */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 24} /* w = x */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r8g8b8x8_srgb_description = { - PIPE_FORMAT_R8G8B8X8_SRGB, - "PIPE_FORMAT_R8G8B8X8_SRGB", - "r8g8b8x8_srgb", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 24}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 16}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* z = b */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 0} /* w = x */ - }, -#else - { - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 16}, /* z = b */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 24} /* w = x */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* sr */ - UTIL_FORMAT_SWIZZLE_Y, /* sg */ - UTIL_FORMAT_SWIZZLE_Z, /* sb */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* sr */ - UTIL_FORMAT_SWIZZLE_Y, /* sg */ - UTIL_FORMAT_SWIZZLE_Z, /* sb */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_SRGB, -}; - -const struct util_format_description util_format_r8g8b8x8_uint_description = { - PIPE_FORMAT_R8G8B8X8_UINT, - "PIPE_FORMAT_R8G8B8X8_UINT", - "r8g8b8x8_uint", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 8, 24}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 8, 16}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 8, 8}, /* z = b */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 0} /* w = x */ - }, -#else - { - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 8, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 8, 8}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 8, 16}, /* z = b */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 24} /* w = x */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r8g8b8x8_sint_description = { - PIPE_FORMAT_R8G8B8X8_SINT, - "PIPE_FORMAT_R8G8B8X8_SINT", - "r8g8b8x8_sint", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 8, 24}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 8, 16}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 8, 8}, /* z = b */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 0} /* w = x */ - }, -#else - { - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 8, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 8, 8}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 8, 16}, /* z = b */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 24} /* w = x */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_b10g10r10x2_unorm_description = - { - PIPE_FORMAT_B10G10R10X2_UNORM, - "PIPE_FORMAT_B10G10R10X2_UNORM", - "b10g10r10x2_unorm", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - FALSE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 2, 30}, /* x = x */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 10, 20}, /* y = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 10, 10}, /* z = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 10, 0} /* w = b */ - }, -#else - { - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 10, 0}, /* x = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 10, 10}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 10, 20}, /* z = r */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 2, 30} /* w = x */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_Y, /* r */ - UTIL_FORMAT_SWIZZLE_Z, /* g */ - UTIL_FORMAT_SWIZZLE_W, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_Z, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_X, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description - util_format_r16g16b16x16_unorm_description = { - PIPE_FORMAT_R16G16B16X16_UNORM, - "PIPE_FORMAT_R16G16B16X16_UNORM", - "r16g16b16x16_unorm", - {1, 1, 64}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 16, 48}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 16, 32}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 16, 16}, /* z = b */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 16, 0} /* w = x */ - }, -#else - { - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 16, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 16, 16}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 16, 32}, /* z = b */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 16, 48} /* w = x */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description - util_format_r16g16b16x16_snorm_description = { - PIPE_FORMAT_R16G16B16X16_SNORM, - "PIPE_FORMAT_R16G16B16X16_SNORM", - "r16g16b16x16_snorm", - {1, 1, 64}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 16, 48}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 16, 32}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 16, 16}, /* z = b */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 16, 0} /* w = x */ - }, -#else - { - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 16, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 16, 16}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 16, 32}, /* z = b */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 16, 48} /* w = x */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description - util_format_r16g16b16x16_float_description = { - PIPE_FORMAT_R16G16B16X16_FLOAT, - "PIPE_FORMAT_R16G16B16X16_FLOAT", - "r16g16b16x16_float", - {1, 1, 64}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 16, 48}, /* x = r */ - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 16, 32}, /* y = g */ - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 16, 16}, /* z = b */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 16, 0} /* w = x */ - }, -#else - { - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 16, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 16, 16}, /* y = g */ - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 16, 32}, /* z = b */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 16, 48} /* w = x */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r16g16b16x16_uint_description = - { - PIPE_FORMAT_R16G16B16X16_UINT, - "PIPE_FORMAT_R16G16B16X16_UINT", - "r16g16b16x16_uint", - {1, 1, 64}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 16, 48}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 16, 32}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 16, 16}, /* z = b */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 16, 0} /* w = x */ - }, -#else - { - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 16, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 16, 16}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 16, 32}, /* z = b */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 16, 48} /* w = x */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r16g16b16x16_sint_description = - { - PIPE_FORMAT_R16G16B16X16_SINT, - "PIPE_FORMAT_R16G16B16X16_SINT", - "r16g16b16x16_sint", - {1, 1, 64}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 16, 48}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 16, 32}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 16, 16}, /* z = b */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 16, 0} /* w = x */ - }, -#else - { - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 16, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 16, 16}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 16, 32}, /* z = b */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 16, 48} /* w = x */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description - util_format_r32g32b32x32_float_description = { - PIPE_FORMAT_R32G32B32X32_FLOAT, - "PIPE_FORMAT_R32G32B32X32_FLOAT", - "r32g32b32x32_float", - {1, 1, 128}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 32, 96}, /* x = r */ - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 32, 64}, /* y = g */ - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 32, 32}, /* z = b */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 32, 0} /* w = x */ - }, -#else - { - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 32, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 32, 32}, /* y = g */ - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 32, 64}, /* z = b */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 32, 96} /* w = x */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r32g32b32x32_uint_description = - { - PIPE_FORMAT_R32G32B32X32_UINT, - "PIPE_FORMAT_R32G32B32X32_UINT", - "r32g32b32x32_uint", - {1, 1, 128}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 32, 96}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 32, 64}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 32, 32}, /* z = b */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 32, 0} /* w = x */ - }, -#else - { - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 32, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 32, 32}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 32, 64}, /* z = b */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 32, 96} /* w = x */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r32g32b32x32_sint_description = - { - PIPE_FORMAT_R32G32B32X32_SINT, - "PIPE_FORMAT_R32G32B32X32_SINT", - "r32g32b32x32_sint", - {1, 1, 128}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 32, 96}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 32, 64}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 32, 32}, /* z = b */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 32, 0} /* w = x */ - }, -#else - { - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 32, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 32, 32}, /* y = g */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 32, 64}, /* z = b */ - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 32, 96} /* w = x */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r8a8_snorm_description = { - PIPE_FORMAT_R8A8_SNORM, - "PIPE_FORMAT_R8A8_SNORM", - "r8a8_snorm", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 8}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 0}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 8}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r16a16_unorm_description = { - PIPE_FORMAT_R16A16_UNORM, - "PIPE_FORMAT_R16A16_UNORM", - "r16a16_unorm", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 16, 16}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 16, 0}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 16, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 16, 16}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r16a16_snorm_description = { - PIPE_FORMAT_R16A16_SNORM, - "PIPE_FORMAT_R16A16_SNORM", - "r16a16_snorm", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 16, 16}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 16, 0}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 16, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 16, 16}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r16a16_float_description = { - PIPE_FORMAT_R16A16_FLOAT, - "PIPE_FORMAT_R16A16_FLOAT", - "r16a16_float", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 16, 16}, /* x = r */ - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 16, 0}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 16, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 16, 16}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r32a32_float_description = { - PIPE_FORMAT_R32A32_FLOAT, - "PIPE_FORMAT_R32A32_FLOAT", - "r32a32_float", - {1, 1, 64}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 32, 32}, /* x = r */ - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 32, 0}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 32, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_FLOAT, FALSE, FALSE, 32, 32}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r8a8_uint_description = { - PIPE_FORMAT_R8A8_UINT, - "PIPE_FORMAT_R8A8_UINT", - "r8a8_uint", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 8, 8}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 8, 0}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 8, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 8, 8}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r8a8_sint_description = { - PIPE_FORMAT_R8A8_SINT, - "PIPE_FORMAT_R8A8_SINT", - "r8a8_sint", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 8, 8}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 8, 0}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 8, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 8, 8}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r16a16_uint_description = { - PIPE_FORMAT_R16A16_UINT, - "PIPE_FORMAT_R16A16_UINT", - "r16a16_uint", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 16, 16}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 16, 0}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 16, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 16, 16}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r16a16_sint_description = { - PIPE_FORMAT_R16A16_SINT, - "PIPE_FORMAT_R16A16_SINT", - "r16a16_sint", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 16, 16}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 16, 0}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 16, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 16, 16}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r32a32_uint_description = { - PIPE_FORMAT_R32A32_UINT, - "PIPE_FORMAT_R32A32_UINT", - "r32a32_uint", - {1, 1, 64}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 32, 32}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 32, 0}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 32, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 32, 32}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r32a32_sint_description = { - PIPE_FORMAT_R32A32_SINT, - "PIPE_FORMAT_R32A32_SINT", - "r32a32_sint", - {1, 1, 64}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - FALSE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 32, 32}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 32, 0}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 32, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_SIGNED, FALSE, TRUE, 32, 32}, /* y = a */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_0, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_Y /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_r10g10b10a2_uint_description = - { - PIPE_FORMAT_R10G10B10A2_UINT, - "PIPE_FORMAT_R10G10B10A2_UINT", - "r10g10b10a2_uint", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - FALSE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 2, 30}, /* x = a */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 10, 20}, /* y = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 10, 10}, /* z = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 10, 0} /* w = r */ - }, -#else - { - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 10, 0}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 10, 10}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 10, 20}, /* z = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, FALSE, TRUE, 2, 30} /* w = a */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_W, /* r */ - UTIL_FORMAT_SWIZZLE_Z, /* g */ - UTIL_FORMAT_SWIZZLE_Y, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_X, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Z, /* b */ - UTIL_FORMAT_SWIZZLE_W /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_b5g6r5_srgb_description = { - PIPE_FORMAT_B5G6R5_SRGB, - "PIPE_FORMAT_B5G6R5_SRGB", - "b5g6r5_srgb", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 3, /* nr_channels */ - FALSE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 5, 11}, /* x = r */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 6, 5}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 5, 0}, /* z = b */ - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 5, 0}, /* x = b */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 6, 5}, /* y = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 5, 11}, /* z = r */ - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_X, /* sr */ - UTIL_FORMAT_SWIZZLE_Y, /* sg */ - UTIL_FORMAT_SWIZZLE_Z, /* sb */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_Z, /* sr */ - UTIL_FORMAT_SWIZZLE_Y, /* sg */ - UTIL_FORMAT_SWIZZLE_X, /* sb */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_SRGB, -}; - -const struct util_format_description util_format_a8l8_unorm_description = { - PIPE_FORMAT_A8L8_UNORM, - "PIPE_FORMAT_A8L8_UNORM", - "a8l8_unorm", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* x = a */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 0}, /* y = rgb */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 0}, /* x = a */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* y = rgb */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_Y, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Y, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_Y, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Y, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_a8l8_snorm_description = { - PIPE_FORMAT_A8L8_SNORM, - "PIPE_FORMAT_A8L8_SNORM", - "a8l8_snorm", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 8}, /* x = a */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 0}, /* y = rgb */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 0}, /* x = a */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 8}, /* y = rgb */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_Y, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Y, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_Y, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Y, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_a8l8_srgb_description = { - PIPE_FORMAT_A8L8_SRGB, - "PIPE_FORMAT_A8L8_SRGB", - "a8l8_srgb", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* x = a */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 0}, /* y = rgb */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 0}, /* x = a */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* y = rgb */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_Y, /* sr */ - UTIL_FORMAT_SWIZZLE_Y, /* sg */ - UTIL_FORMAT_SWIZZLE_Y, /* sb */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_Y, /* sr */ - UTIL_FORMAT_SWIZZLE_Y, /* sg */ - UTIL_FORMAT_SWIZZLE_Y, /* sb */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_SRGB, -}; - -const struct util_format_description util_format_a16l16_unorm_description = { - PIPE_FORMAT_A16L16_UNORM, - "PIPE_FORMAT_A16L16_UNORM", - "a16l16_unorm", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 16, 16}, /* x = a */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 16, 0}, /* y = rgb */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 16, 0}, /* x = a */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 16, 16}, /* y = rgb */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_Y, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Y, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_Y, /* r */ - UTIL_FORMAT_SWIZZLE_Y, /* g */ - UTIL_FORMAT_SWIZZLE_Y, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_g8r8_unorm_description = { - PIPE_FORMAT_G8R8_UNORM, - "PIPE_FORMAT_G8R8_UNORM", - "g8r8_unorm", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* x = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 0}, /* y = r */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 0}, /* x = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 8, 8}, /* y = r */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_Y, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_Y, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_g8r8_snorm_description = { - PIPE_FORMAT_G8R8_SNORM, - "PIPE_FORMAT_G8R8_SNORM", - "g8r8_snorm", - {1, 1, 16}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 8}, /* x = g */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 0}, /* y = r */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 0}, /* x = g */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 8}, /* y = r */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_Y, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_Y, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_g16r16_unorm_description = { - PIPE_FORMAT_G16R16_UNORM, - "PIPE_FORMAT_G16R16_UNORM", - "g16r16_unorm", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 16, 16}, /* x = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 16, 0}, /* y = r */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 16, 0}, /* x = g */ - {UTIL_FORMAT_TYPE_UNSIGNED, TRUE, FALSE, 16, 16}, /* y = r */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_Y, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_Y, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_g16r16_snorm_description = { - PIPE_FORMAT_G16R16_SNORM, - "PIPE_FORMAT_G16R16_SNORM", - "g16r16_snorm", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 2, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - {{UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 16, 16}, /* x = g */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 16, 0}, /* y = r */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#else - {{UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 16, 0}, /* x = g */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 16, 16}, /* y = r */ - {0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0}}, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_Y, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_Y, /* r */ - UTIL_FORMAT_SWIZZLE_X, /* g */ - UTIL_FORMAT_SWIZZLE_0, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_a8b8g8r8_snorm_description = { - PIPE_FORMAT_A8B8G8R8_SNORM, - "PIPE_FORMAT_A8B8G8R8_SNORM", - "a8b8g8r8_snorm", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 24}, /* x = a */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 16}, /* y = b */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 8}, /* z = g */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 0} /* w = r */ - }, -#else - { - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 0}, /* x = a */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 8}, /* y = b */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 16}, /* z = g */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 24} /* w = r */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_W, /* r */ - UTIL_FORMAT_SWIZZLE_Z, /* g */ - UTIL_FORMAT_SWIZZLE_Y, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_W, /* r */ - UTIL_FORMAT_SWIZZLE_Z, /* g */ - UTIL_FORMAT_SWIZZLE_Y, /* b */ - UTIL_FORMAT_SWIZZLE_X /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description util_format_x8b8g8r8_snorm_description = { - PIPE_FORMAT_X8B8G8R8_SNORM, - "PIPE_FORMAT_X8B8G8R8_SNORM", - "x8b8g8r8_snorm", - {1, 1, 32}, /* block */ - UTIL_FORMAT_LAYOUT_PLAIN, - 4, /* nr_channels */ - TRUE, /* is_array */ - TRUE, /* is_bitmask */ - FALSE, /* is_mixed */ -#ifdef PIPE_ARCH_BIG_ENDIAN - { - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 24}, /* x = x */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 16}, /* y = b */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 8}, /* z = g */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 0} /* w = r */ - }, -#else - { - {UTIL_FORMAT_TYPE_VOID, FALSE, FALSE, 8, 0}, /* x = x */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 8}, /* y = b */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 16}, /* z = g */ - {UTIL_FORMAT_TYPE_SIGNED, TRUE, FALSE, 8, 24} /* w = r */ - }, -#endif -#ifdef PIPE_ARCH_BIG_ENDIAN - { - UTIL_FORMAT_SWIZZLE_W, /* r */ - UTIL_FORMAT_SWIZZLE_Z, /* g */ - UTIL_FORMAT_SWIZZLE_Y, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#else - { - UTIL_FORMAT_SWIZZLE_W, /* r */ - UTIL_FORMAT_SWIZZLE_Z, /* g */ - UTIL_FORMAT_SWIZZLE_Y, /* b */ - UTIL_FORMAT_SWIZZLE_1 /* a */ - }, -#endif - UTIL_FORMAT_COLORSPACE_RGB, -}; - -const struct util_format_description * -util_format_description(enum pipe_format format) { - if (format >= PIPE_FORMAT_COUNT) { - return NULL; - } - - switch (format) { - case PIPE_FORMAT_NONE: - return &util_format_none_description; - case PIPE_FORMAT_B8G8R8A8_UNORM: - return &util_format_b8g8r8a8_unorm_description; - case PIPE_FORMAT_B8G8R8X8_UNORM: - return &util_format_b8g8r8x8_unorm_description; - case PIPE_FORMAT_A8R8G8B8_UNORM: - return &util_format_a8r8g8b8_unorm_description; - case PIPE_FORMAT_X8R8G8B8_UNORM: - return &util_format_x8r8g8b8_unorm_description; - case PIPE_FORMAT_A8B8G8R8_UNORM: - return &util_format_a8b8g8r8_unorm_description; - case PIPE_FORMAT_X8B8G8R8_UNORM: - return &util_format_x8b8g8r8_unorm_description; - case PIPE_FORMAT_R8G8B8X8_UNORM: - return &util_format_r8g8b8x8_unorm_description; - case PIPE_FORMAT_B5G5R5X1_UNORM: - return &util_format_b5g5r5x1_unorm_description; - case PIPE_FORMAT_B5G5R5A1_UNORM: - return &util_format_b5g5r5a1_unorm_description; - case PIPE_FORMAT_B4G4R4A4_UNORM: - return &util_format_b4g4r4a4_unorm_description; - case PIPE_FORMAT_B4G4R4X4_UNORM: - return &util_format_b4g4r4x4_unorm_description; - case PIPE_FORMAT_A4B4G4R4_UNORM: - return &util_format_a4b4g4r4_unorm_description; - case PIPE_FORMAT_B5G6R5_UNORM: - return &util_format_b5g6r5_unorm_description; - case PIPE_FORMAT_R10G10B10A2_UNORM: - return &util_format_r10g10b10a2_unorm_description; - case PIPE_FORMAT_R10G10B10X2_UNORM: - return &util_format_r10g10b10x2_unorm_description; - case PIPE_FORMAT_B10G10R10A2_UNORM: - return &util_format_b10g10r10a2_unorm_description; - case PIPE_FORMAT_B2G3R3_UNORM: - return &util_format_b2g3r3_unorm_description; - case PIPE_FORMAT_L8_UNORM: - return &util_format_l8_unorm_description; - case PIPE_FORMAT_A8_UNORM: - return &util_format_a8_unorm_description; - case PIPE_FORMAT_I8_UNORM: - return &util_format_i8_unorm_description; - case PIPE_FORMAT_L4A4_UNORM: - return &util_format_l4a4_unorm_description; - case PIPE_FORMAT_L8A8_UNORM: - return &util_format_l8a8_unorm_description; - case PIPE_FORMAT_L16_UNORM: - return &util_format_l16_unorm_description; - case PIPE_FORMAT_A16_UNORM: - return &util_format_a16_unorm_description; - case PIPE_FORMAT_I16_UNORM: - return &util_format_i16_unorm_description; - case PIPE_FORMAT_L16A16_UNORM: - return &util_format_l16a16_unorm_description; - case PIPE_FORMAT_A8_SNORM: - return &util_format_a8_snorm_description; - case PIPE_FORMAT_L8_SNORM: - return &util_format_l8_snorm_description; - case PIPE_FORMAT_L8A8_SNORM: - return &util_format_l8a8_snorm_description; - case PIPE_FORMAT_I8_SNORM: - return &util_format_i8_snorm_description; - case PIPE_FORMAT_A16_SNORM: - return &util_format_a16_snorm_description; - case PIPE_FORMAT_L16_SNORM: - return &util_format_l16_snorm_description; - case PIPE_FORMAT_L16A16_SNORM: - return &util_format_l16a16_snorm_description; - case PIPE_FORMAT_I16_SNORM: - return &util_format_i16_snorm_description; - case PIPE_FORMAT_A16_FLOAT: - return &util_format_a16_float_description; - case PIPE_FORMAT_L16_FLOAT: - return &util_format_l16_float_description; - case PIPE_FORMAT_L16A16_FLOAT: - return &util_format_l16a16_float_description; - case PIPE_FORMAT_I16_FLOAT: - return &util_format_i16_float_description; - case PIPE_FORMAT_A32_FLOAT: - return &util_format_a32_float_description; - case PIPE_FORMAT_L32_FLOAT: - return &util_format_l32_float_description; - case PIPE_FORMAT_L32A32_FLOAT: - return &util_format_l32a32_float_description; - case PIPE_FORMAT_I32_FLOAT: - return &util_format_i32_float_description; - case PIPE_FORMAT_L8_SRGB: - return &util_format_l8_srgb_description; - case PIPE_FORMAT_R8_SRGB: - return &util_format_r8_srgb_description; - case PIPE_FORMAT_L8A8_SRGB: - return &util_format_l8a8_srgb_description; - case PIPE_FORMAT_R8G8B8_SRGB: - return &util_format_r8g8b8_srgb_description; - case PIPE_FORMAT_R8G8B8A8_SRGB: - return &util_format_r8g8b8a8_srgb_description; - case PIPE_FORMAT_A8B8G8R8_SRGB: - return &util_format_a8b8g8r8_srgb_description; - case PIPE_FORMAT_X8B8G8R8_SRGB: - return &util_format_x8b8g8r8_srgb_description; - case PIPE_FORMAT_B8G8R8A8_SRGB: - return &util_format_b8g8r8a8_srgb_description; - case PIPE_FORMAT_B8G8R8X8_SRGB: - return &util_format_b8g8r8x8_srgb_description; - case PIPE_FORMAT_A8R8G8B8_SRGB: - return &util_format_a8r8g8b8_srgb_description; - case PIPE_FORMAT_X8R8G8B8_SRGB: - return &util_format_x8r8g8b8_srgb_description; - case PIPE_FORMAT_R8SG8SB8UX8U_NORM: - return &util_format_r8sg8sb8ux8u_norm_description; - case PIPE_FORMAT_R10SG10SB10SA2U_NORM: - return &util_format_r10sg10sb10sa2u_norm_description; - case PIPE_FORMAT_R5SG5SB6U_NORM: - return &util_format_r5sg5sb6u_norm_description; - case PIPE_FORMAT_S8_UINT: - return &util_format_s8_uint_description; - case PIPE_FORMAT_Z16_UNORM: - return &util_format_z16_unorm_description; - case PIPE_FORMAT_Z32_UNORM: - return &util_format_z32_unorm_description; - case PIPE_FORMAT_Z32_FLOAT: - return &util_format_z32_float_description; - case PIPE_FORMAT_Z24_UNORM_S8_UINT: - return &util_format_z24_unorm_s8_uint_description; - case PIPE_FORMAT_S8_UINT_Z24_UNORM: - return &util_format_s8_uint_z24_unorm_description; - case PIPE_FORMAT_X24S8_UINT: - return &util_format_x24s8_uint_description; - case PIPE_FORMAT_S8X24_UINT: - return &util_format_s8x24_uint_description; - case PIPE_FORMAT_Z24X8_UNORM: - return &util_format_z24x8_unorm_description; - case PIPE_FORMAT_X8Z24_UNORM: - return &util_format_x8z24_unorm_description; - case PIPE_FORMAT_Z32_FLOAT_S8X24_UINT: - return &util_format_z32_float_s8x24_uint_description; - case PIPE_FORMAT_X32_S8X24_UINT: - return &util_format_x32_s8x24_uint_description; - case PIPE_FORMAT_UYVY: - return &util_format_uyvy_description; - case PIPE_FORMAT_YUYV: - return &util_format_yuyv_description; - case PIPE_FORMAT_R8G8_B8G8_UNORM: - return &util_format_r8g8_b8g8_unorm_description; - case PIPE_FORMAT_G8R8_G8B8_UNORM: - return &util_format_g8r8_g8b8_unorm_description; - case PIPE_FORMAT_G8R8_B8R8_UNORM: - return &util_format_g8r8_b8r8_unorm_description; - case PIPE_FORMAT_R8G8_R8B8_UNORM: - return &util_format_r8g8_r8b8_unorm_description; - case PIPE_FORMAT_R11G11B10_FLOAT: - return &util_format_r11g11b10_float_description; - case PIPE_FORMAT_R9G9B9E5_FLOAT: - return &util_format_r9g9b9e5_float_description; - case PIPE_FORMAT_R1_UNORM: - return &util_format_r1_unorm_description; - case PIPE_FORMAT_R8G8Bx_SNORM: - return &util_format_r8g8bx_snorm_description; - case PIPE_FORMAT_DXT1_RGB: - return &util_format_dxt1_rgb_description; - case PIPE_FORMAT_DXT1_RGBA: - return &util_format_dxt1_rgba_description; - case PIPE_FORMAT_DXT3_RGBA: - return &util_format_dxt3_rgba_description; - case PIPE_FORMAT_DXT5_RGBA: - return &util_format_dxt5_rgba_description; - case PIPE_FORMAT_DXT1_SRGB: - return &util_format_dxt1_srgb_description; - case PIPE_FORMAT_DXT1_SRGBA: - return &util_format_dxt1_srgba_description; - case PIPE_FORMAT_DXT3_SRGBA: - return &util_format_dxt3_srgba_description; - case PIPE_FORMAT_DXT5_SRGBA: - return &util_format_dxt5_srgba_description; - case PIPE_FORMAT_RGTC1_UNORM: - return &util_format_rgtc1_unorm_description; - case PIPE_FORMAT_RGTC1_SNORM: - return &util_format_rgtc1_snorm_description; - case PIPE_FORMAT_RGTC2_UNORM: - return &util_format_rgtc2_unorm_description; - case PIPE_FORMAT_RGTC2_SNORM: - return &util_format_rgtc2_snorm_description; - case PIPE_FORMAT_LATC1_UNORM: - return &util_format_latc1_unorm_description; - case PIPE_FORMAT_LATC1_SNORM: - return &util_format_latc1_snorm_description; - case PIPE_FORMAT_LATC2_UNORM: - return &util_format_latc2_unorm_description; - case PIPE_FORMAT_LATC2_SNORM: - return &util_format_latc2_snorm_description; - case PIPE_FORMAT_ETC1_RGB8: - return &util_format_etc1_rgb8_description; - case PIPE_FORMAT_BPTC_RGBA_UNORM: - return &util_format_bptc_rgba_unorm_description; - case PIPE_FORMAT_BPTC_SRGBA: - return &util_format_bptc_srgba_description; - case PIPE_FORMAT_BPTC_RGB_FLOAT: - return &util_format_bptc_rgb_float_description; - case PIPE_FORMAT_BPTC_RGB_UFLOAT: - return &util_format_bptc_rgb_ufloat_description; - case PIPE_FORMAT_R64_FLOAT: - return &util_format_r64_float_description; - case PIPE_FORMAT_R64G64_FLOAT: - return &util_format_r64g64_float_description; - case PIPE_FORMAT_R64G64B64_FLOAT: - return &util_format_r64g64b64_float_description; - case PIPE_FORMAT_R64G64B64A64_FLOAT: - return &util_format_r64g64b64a64_float_description; - case PIPE_FORMAT_R32_FLOAT: - return &util_format_r32_float_description; - case PIPE_FORMAT_R32G32_FLOAT: - return &util_format_r32g32_float_description; - case PIPE_FORMAT_R32G32B32_FLOAT: - return &util_format_r32g32b32_float_description; - case PIPE_FORMAT_R32G32B32A32_FLOAT: - return &util_format_r32g32b32a32_float_description; - case PIPE_FORMAT_R32_UNORM: - return &util_format_r32_unorm_description; - case PIPE_FORMAT_R32G32_UNORM: - return &util_format_r32g32_unorm_description; - case PIPE_FORMAT_R32G32B32_UNORM: - return &util_format_r32g32b32_unorm_description; - case PIPE_FORMAT_R32G32B32A32_UNORM: - return &util_format_r32g32b32a32_unorm_description; - case PIPE_FORMAT_R32_USCALED: - return &util_format_r32_uscaled_description; - case PIPE_FORMAT_R32G32_USCALED: - return &util_format_r32g32_uscaled_description; - case PIPE_FORMAT_R32G32B32_USCALED: - return &util_format_r32g32b32_uscaled_description; - case PIPE_FORMAT_R32G32B32A32_USCALED: - return &util_format_r32g32b32a32_uscaled_description; - case PIPE_FORMAT_R32_SNORM: - return &util_format_r32_snorm_description; - case PIPE_FORMAT_R32G32_SNORM: - return &util_format_r32g32_snorm_description; - case PIPE_FORMAT_R32G32B32_SNORM: - return &util_format_r32g32b32_snorm_description; - case PIPE_FORMAT_R32G32B32A32_SNORM: - return &util_format_r32g32b32a32_snorm_description; - case PIPE_FORMAT_R32_SSCALED: - return &util_format_r32_sscaled_description; - case PIPE_FORMAT_R32G32_SSCALED: - return &util_format_r32g32_sscaled_description; - case PIPE_FORMAT_R32G32B32_SSCALED: - return &util_format_r32g32b32_sscaled_description; - case PIPE_FORMAT_R32G32B32A32_SSCALED: - return &util_format_r32g32b32a32_sscaled_description; - case PIPE_FORMAT_R16_FLOAT: - return &util_format_r16_float_description; - case PIPE_FORMAT_R16G16_FLOAT: - return &util_format_r16g16_float_description; - case PIPE_FORMAT_R16G16B16_FLOAT: - return &util_format_r16g16b16_float_description; - case PIPE_FORMAT_R16G16B16A16_FLOAT: - return &util_format_r16g16b16a16_float_description; - case PIPE_FORMAT_R16_UNORM: - return &util_format_r16_unorm_description; - case PIPE_FORMAT_R16G16_UNORM: - return &util_format_r16g16_unorm_description; - case PIPE_FORMAT_R16G16B16_UNORM: - return &util_format_r16g16b16_unorm_description; - case PIPE_FORMAT_R16G16B16A16_UNORM: - return &util_format_r16g16b16a16_unorm_description; - case PIPE_FORMAT_R16_USCALED: - return &util_format_r16_uscaled_description; - case PIPE_FORMAT_R16G16_USCALED: - return &util_format_r16g16_uscaled_description; - case PIPE_FORMAT_R16G16B16_USCALED: - return &util_format_r16g16b16_uscaled_description; - case PIPE_FORMAT_R16G16B16A16_USCALED: - return &util_format_r16g16b16a16_uscaled_description; - case PIPE_FORMAT_R16_SNORM: - return &util_format_r16_snorm_description; - case PIPE_FORMAT_R16G16_SNORM: - return &util_format_r16g16_snorm_description; - case PIPE_FORMAT_R16G16B16_SNORM: - return &util_format_r16g16b16_snorm_description; - case PIPE_FORMAT_R16G16B16A16_SNORM: - return &util_format_r16g16b16a16_snorm_description; - case PIPE_FORMAT_R16_SSCALED: - return &util_format_r16_sscaled_description; - case PIPE_FORMAT_R16G16_SSCALED: - return &util_format_r16g16_sscaled_description; - case PIPE_FORMAT_R16G16B16_SSCALED: - return &util_format_r16g16b16_sscaled_description; - case PIPE_FORMAT_R16G16B16A16_SSCALED: - return &util_format_r16g16b16a16_sscaled_description; - case PIPE_FORMAT_R8_UNORM: - return &util_format_r8_unorm_description; - case PIPE_FORMAT_R8G8_UNORM: - return &util_format_r8g8_unorm_description; - case PIPE_FORMAT_R8G8B8_UNORM: - return &util_format_r8g8b8_unorm_description; - case PIPE_FORMAT_R8G8B8A8_UNORM: - return &util_format_r8g8b8a8_unorm_description; - case PIPE_FORMAT_R8_USCALED: - return &util_format_r8_uscaled_description; - case PIPE_FORMAT_R8G8_USCALED: - return &util_format_r8g8_uscaled_description; - case PIPE_FORMAT_R8G8B8_USCALED: - return &util_format_r8g8b8_uscaled_description; - case PIPE_FORMAT_R8G8B8A8_USCALED: - return &util_format_r8g8b8a8_uscaled_description; - case PIPE_FORMAT_R8_SNORM: - return &util_format_r8_snorm_description; - case PIPE_FORMAT_R8G8_SNORM: - return &util_format_r8g8_snorm_description; - case PIPE_FORMAT_R8G8B8_SNORM: - return &util_format_r8g8b8_snorm_description; - case PIPE_FORMAT_R8G8B8A8_SNORM: - return &util_format_r8g8b8a8_snorm_description; - case PIPE_FORMAT_R8_SSCALED: - return &util_format_r8_sscaled_description; - case PIPE_FORMAT_R8G8_SSCALED: - return &util_format_r8g8_sscaled_description; - case PIPE_FORMAT_R8G8B8_SSCALED: - return &util_format_r8g8b8_sscaled_description; - case PIPE_FORMAT_R8G8B8A8_SSCALED: - return &util_format_r8g8b8a8_sscaled_description; - case PIPE_FORMAT_R32_FIXED: - return &util_format_r32_fixed_description; - case PIPE_FORMAT_R32G32_FIXED: - return &util_format_r32g32_fixed_description; - case PIPE_FORMAT_R32G32B32_FIXED: - return &util_format_r32g32b32_fixed_description; - case PIPE_FORMAT_R32G32B32A32_FIXED: - return &util_format_r32g32b32a32_fixed_description; - case PIPE_FORMAT_R10G10B10X2_USCALED: - return &util_format_r10g10b10x2_uscaled_description; - case PIPE_FORMAT_R10G10B10X2_SNORM: - return &util_format_r10g10b10x2_snorm_description; - case PIPE_FORMAT_YV12: - return &util_format_yv12_description; - case PIPE_FORMAT_YV16: - return &util_format_yv16_description; - case PIPE_FORMAT_IYUV: - return &util_format_iyuv_description; - case PIPE_FORMAT_NV12: - return &util_format_nv12_description; - case PIPE_FORMAT_NV21: - return &util_format_nv21_description; - case PIPE_FORMAT_A4R4_UNORM: - return &util_format_a4r4_unorm_description; - case PIPE_FORMAT_R4A4_UNORM: - return &util_format_r4a4_unorm_description; - case PIPE_FORMAT_R8A8_UNORM: - return &util_format_r8a8_unorm_description; - case PIPE_FORMAT_A8R8_UNORM: - return &util_format_a8r8_unorm_description; - case PIPE_FORMAT_R10G10B10A2_USCALED: - return &util_format_r10g10b10a2_uscaled_description; - case PIPE_FORMAT_R10G10B10A2_SSCALED: - return &util_format_r10g10b10a2_sscaled_description; - case PIPE_FORMAT_R10G10B10A2_SNORM: - return &util_format_r10g10b10a2_snorm_description; - case PIPE_FORMAT_B10G10R10A2_USCALED: - return &util_format_b10g10r10a2_uscaled_description; - case PIPE_FORMAT_B10G10R10A2_SSCALED: - return &util_format_b10g10r10a2_sscaled_description; - case PIPE_FORMAT_B10G10R10A2_SNORM: - return &util_format_b10g10r10a2_snorm_description; - case PIPE_FORMAT_R8_UINT: - return &util_format_r8_uint_description; - case PIPE_FORMAT_R8G8_UINT: - return &util_format_r8g8_uint_description; - case PIPE_FORMAT_R8G8B8_UINT: - return &util_format_r8g8b8_uint_description; - case PIPE_FORMAT_R8G8B8A8_UINT: - return &util_format_r8g8b8a8_uint_description; - case PIPE_FORMAT_R8_SINT: - return &util_format_r8_sint_description; - case PIPE_FORMAT_R8G8_SINT: - return &util_format_r8g8_sint_description; - case PIPE_FORMAT_R8G8B8_SINT: - return &util_format_r8g8b8_sint_description; - case PIPE_FORMAT_R8G8B8A8_SINT: - return &util_format_r8g8b8a8_sint_description; - case PIPE_FORMAT_R16_UINT: - return &util_format_r16_uint_description; - case PIPE_FORMAT_R16G16_UINT: - return &util_format_r16g16_uint_description; - case PIPE_FORMAT_R16G16B16_UINT: - return &util_format_r16g16b16_uint_description; - case PIPE_FORMAT_R16G16B16A16_UINT: - return &util_format_r16g16b16a16_uint_description; - case PIPE_FORMAT_R16_SINT: - return &util_format_r16_sint_description; - case PIPE_FORMAT_R16G16_SINT: - return &util_format_r16g16_sint_description; - case PIPE_FORMAT_R16G16B16_SINT: - return &util_format_r16g16b16_sint_description; - case PIPE_FORMAT_R16G16B16A16_SINT: - return &util_format_r16g16b16a16_sint_description; - case PIPE_FORMAT_R32_UINT: - return &util_format_r32_uint_description; - case PIPE_FORMAT_R32G32_UINT: - return &util_format_r32g32_uint_description; - case PIPE_FORMAT_R32G32B32_UINT: - return &util_format_r32g32b32_uint_description; - case PIPE_FORMAT_R32G32B32A32_UINT: - return &util_format_r32g32b32a32_uint_description; - case PIPE_FORMAT_R32_SINT: - return &util_format_r32_sint_description; - case PIPE_FORMAT_R32G32_SINT: - return &util_format_r32g32_sint_description; - case PIPE_FORMAT_R32G32B32_SINT: - return &util_format_r32g32b32_sint_description; - case PIPE_FORMAT_R32G32B32A32_SINT: - return &util_format_r32g32b32a32_sint_description; - case PIPE_FORMAT_A8_UINT: - return &util_format_a8_uint_description; - case PIPE_FORMAT_I8_UINT: - return &util_format_i8_uint_description; - case PIPE_FORMAT_L8_UINT: - return &util_format_l8_uint_description; - case PIPE_FORMAT_L8A8_UINT: - return &util_format_l8a8_uint_description; - case PIPE_FORMAT_A8_SINT: - return &util_format_a8_sint_description; - case PIPE_FORMAT_I8_SINT: - return &util_format_i8_sint_description; - case PIPE_FORMAT_L8_SINT: - return &util_format_l8_sint_description; - case PIPE_FORMAT_L8A8_SINT: - return &util_format_l8a8_sint_description; - case PIPE_FORMAT_A16_UINT: - return &util_format_a16_uint_description; - case PIPE_FORMAT_I16_UINT: - return &util_format_i16_uint_description; - case PIPE_FORMAT_L16_UINT: - return &util_format_l16_uint_description; - case PIPE_FORMAT_L16A16_UINT: - return &util_format_l16a16_uint_description; - case PIPE_FORMAT_A16_SINT: - return &util_format_a16_sint_description; - case PIPE_FORMAT_I16_SINT: - return &util_format_i16_sint_description; - case PIPE_FORMAT_L16_SINT: - return &util_format_l16_sint_description; - case PIPE_FORMAT_L16A16_SINT: - return &util_format_l16a16_sint_description; - case PIPE_FORMAT_A32_UINT: - return &util_format_a32_uint_description; - case PIPE_FORMAT_I32_UINT: - return &util_format_i32_uint_description; - case PIPE_FORMAT_L32_UINT: - return &util_format_l32_uint_description; - case PIPE_FORMAT_L32A32_UINT: - return &util_format_l32a32_uint_description; - case PIPE_FORMAT_A32_SINT: - return &util_format_a32_sint_description; - case PIPE_FORMAT_I32_SINT: - return &util_format_i32_sint_description; - case PIPE_FORMAT_L32_SINT: - return &util_format_l32_sint_description; - case PIPE_FORMAT_L32A32_SINT: - return &util_format_l32a32_sint_description; - case PIPE_FORMAT_B10G10R10A2_UINT: - return &util_format_b10g10r10a2_uint_description; - case PIPE_FORMAT_R8G8B8X8_SNORM: - return &util_format_r8g8b8x8_snorm_description; - case PIPE_FORMAT_R8G8B8X8_SRGB: - return &util_format_r8g8b8x8_srgb_description; - case PIPE_FORMAT_R8G8B8X8_UINT: - return &util_format_r8g8b8x8_uint_description; - case PIPE_FORMAT_R8G8B8X8_SINT: - return &util_format_r8g8b8x8_sint_description; - case PIPE_FORMAT_B10G10R10X2_UNORM: - return &util_format_b10g10r10x2_unorm_description; - case PIPE_FORMAT_R16G16B16X16_UNORM: - return &util_format_r16g16b16x16_unorm_description; - case PIPE_FORMAT_R16G16B16X16_SNORM: - return &util_format_r16g16b16x16_snorm_description; - case PIPE_FORMAT_R16G16B16X16_FLOAT: - return &util_format_r16g16b16x16_float_description; - case PIPE_FORMAT_R16G16B16X16_UINT: - return &util_format_r16g16b16x16_uint_description; - case PIPE_FORMAT_R16G16B16X16_SINT: - return &util_format_r16g16b16x16_sint_description; - case PIPE_FORMAT_R32G32B32X32_FLOAT: - return &util_format_r32g32b32x32_float_description; - case PIPE_FORMAT_R32G32B32X32_UINT: - return &util_format_r32g32b32x32_uint_description; - case PIPE_FORMAT_R32G32B32X32_SINT: - return &util_format_r32g32b32x32_sint_description; - case PIPE_FORMAT_R8A8_SNORM: - return &util_format_r8a8_snorm_description; - case PIPE_FORMAT_R16A16_UNORM: - return &util_format_r16a16_unorm_description; - case PIPE_FORMAT_R16A16_SNORM: - return &util_format_r16a16_snorm_description; - case PIPE_FORMAT_R16A16_FLOAT: - return &util_format_r16a16_float_description; - case PIPE_FORMAT_R32A32_FLOAT: - return &util_format_r32a32_float_description; - case PIPE_FORMAT_R8A8_UINT: - return &util_format_r8a8_uint_description; - case PIPE_FORMAT_R8A8_SINT: - return &util_format_r8a8_sint_description; - case PIPE_FORMAT_R16A16_UINT: - return &util_format_r16a16_uint_description; - case PIPE_FORMAT_R16A16_SINT: - return &util_format_r16a16_sint_description; - case PIPE_FORMAT_R32A32_UINT: - return &util_format_r32a32_uint_description; - case PIPE_FORMAT_R32A32_SINT: - return &util_format_r32a32_sint_description; - case PIPE_FORMAT_R10G10B10A2_UINT: - return &util_format_r10g10b10a2_uint_description; - case PIPE_FORMAT_B5G6R5_SRGB: - return &util_format_b5g6r5_srgb_description; - case PIPE_FORMAT_A8L8_UNORM: - return &util_format_a8l8_unorm_description; - case PIPE_FORMAT_A8L8_SNORM: - return &util_format_a8l8_snorm_description; - case PIPE_FORMAT_A8L8_SRGB: - return &util_format_a8l8_srgb_description; - case PIPE_FORMAT_A16L16_UNORM: - return &util_format_a16l16_unorm_description; - case PIPE_FORMAT_G8R8_UNORM: - return &util_format_g8r8_unorm_description; - case PIPE_FORMAT_G8R8_SNORM: - return &util_format_g8r8_snorm_description; - case PIPE_FORMAT_G16R16_UNORM: - return &util_format_g16r16_unorm_description; - case PIPE_FORMAT_G16R16_SNORM: - return &util_format_g16r16_snorm_description; - case PIPE_FORMAT_A8B8G8R8_SNORM: - return &util_format_a8b8g8r8_snorm_description; - case PIPE_FORMAT_X8B8G8R8_SNORM: - return &util_format_x8b8g8r8_snorm_description; - default: - return NULL; - } -} diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_format_table.py b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_format_table.py deleted file mode 100644 index 5564b72e2..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_format_table.py +++ /dev/null @@ -1,178 +0,0 @@ -#!/usr/bin/env python - -from __future__ import print_function -CopyRight = ''' -/************************************************************************** - * - * Copyright 2010 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ -''' - - -import sys - -from u_format_parse import * - - -def layout_map(layout): - return 'UTIL_FORMAT_LAYOUT_' + str(layout).upper() - - -def colorspace_map(colorspace): - return 'UTIL_FORMAT_COLORSPACE_' + str(colorspace).upper() - - -colorspace_channels_map = { - 'rgb': ['r', 'g', 'b', 'a'], - 'srgb': ['sr', 'sg', 'sb', 'a'], - 'zs': ['z', 's'], - 'yuv': ['y', 'u', 'v'], -} - - -type_map = { - VOID: "UTIL_FORMAT_TYPE_VOID", - UNSIGNED: "UTIL_FORMAT_TYPE_UNSIGNED", - SIGNED: "UTIL_FORMAT_TYPE_SIGNED", - FIXED: "UTIL_FORMAT_TYPE_FIXED", - FLOAT: "UTIL_FORMAT_TYPE_FLOAT", -} - - -def bool_map(value): - if value: - return "TRUE" - else: - return "FALSE" - - -swizzle_map = { - SWIZZLE_X: "UTIL_FORMAT_SWIZZLE_X", - SWIZZLE_Y: "UTIL_FORMAT_SWIZZLE_Y", - SWIZZLE_Z: "UTIL_FORMAT_SWIZZLE_Z", - SWIZZLE_W: "UTIL_FORMAT_SWIZZLE_W", - SWIZZLE_0: "UTIL_FORMAT_SWIZZLE_0", - SWIZZLE_1: "UTIL_FORMAT_SWIZZLE_1", - SWIZZLE_NONE: "UTIL_FORMAT_SWIZZLE_NONE", -} - - -def write_format_table(formats): - print('/* This file is autogenerated by u_format_table.py from u_format.csv. Do not edit directly. */') - print() - # This will print the copyright message on the top of this file - print(CopyRight.strip()) - print() - print('#include "pipe/p_compiler.h"') - print('#include "u_format.h"') - print('#include "u_half.h"') - print('#include "u_math.h"') - print() - - def do_channel_array(channels, swizzles): - print(" {") - for i in range(4): - channel = channels[i] - if i < 3: - sep = "," - else: - sep = "" - if channel.size: - print(" {%s, %s, %s, %u, %u}%s\t/* %s = %s */" % (type_map[channel.type], bool_map(channel.norm), bool_map(channel.pure), channel.size, channel.shift, sep, "xyzw"[i], channel.name)) - else: - print(" {0, 0, 0, 0, 0}%s" % (sep,)) - print(" },") - - def do_swizzle_array(channels, swizzles): - print(" {") - for i in range(4): - swizzle = swizzles[i] - if i < 3: - sep = "," - else: - sep = "" - try: - comment = colorspace_channels_map[format.colorspace][i] - except (KeyError, IndexError): - comment = 'ignored' - print(" %s%s\t/* %s */" % (swizzle_map[swizzle], sep, comment)) - print(" },") - - def print_channels(format, func): - if format.nr_channels() <= 1: - func(format.le_channels, format.le_swizzles) - else: - print('#ifdef PIPE_ARCH_BIG_ENDIAN') - func(format.be_channels, format.be_swizzles) - print('#else') - func(format.le_channels, format.le_swizzles) - print('#endif') - - for format in formats: - print('const struct util_format_description') - print('util_format_%s_description = {' % (format.short_name(),)) - print(" %s," % (format.name,)) - print(" \"%s\"," % (format.name,)) - print(" \"%s\"," % (format.short_name(),)) - print(" {%u, %u, %u},\t/* block */" % (format.block_width, format.block_height, format.block_size())) - print(" %s," % (layout_map(format.layout),)) - print(" %u,\t/* nr_channels */" % (format.nr_channels(),)) - print(" %s,\t/* is_array */" % (bool_map(format.is_array()),)) - print(" %s,\t/* is_bitmask */" % (bool_map(format.is_bitmask()),)) - print(" %s,\t/* is_mixed */" % (bool_map(format.is_mixed()),)) - print_channels(format, do_channel_array) - print_channels(format, do_swizzle_array) - print(" %s," % (colorspace_map(format.colorspace),)) - print("};") - print() - - print("const struct util_format_description *") - print("util_format_description(enum pipe_format format)") - print("{") - print(" if (format >= PIPE_FORMAT_COUNT) {") - print(" return NULL;") - print(" }") - print() - print(" switch (format) {") - for format in formats: - print(" case %s:" % format.name) - print(" return &util_format_%s_description;" % (format.short_name(),)) - print(" default:") - print(" return NULL;") - print(" }") - print("}") - print() - - -def main(): - - formats = [] - for arg in sys.argv[1:]: - formats.extend(parse(arg)) - write_format_table(formats) - - -if __name__ == '__main__': - main() diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_half.h b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_half.h deleted file mode 100644 index d478a6835..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_half.h +++ /dev/null @@ -1,125 +0,0 @@ -/************************************************************************** - * - * Copyright 2010 Luca Barbieri - * - * 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 (including the - * next paragraph) 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 COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -#ifndef U_HALF_H -#define U_HALF_H - -#include "pipe/p_compiler.h" -#include "util/u_math.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/* - * References for float <-> half conversions - * - * http://fgiesen.wordpress.com/2012/03/28/half-to-float-done-quic/ - * https://gist.github.com/2156668 - * https://gist.github.com/2144712 - */ - -static inline uint16_t util_float_to_half(float f) { - uint32_t sign_mask = 0x80000000; - uint32_t round_mask = ~0xfff; - uint32_t f32inf = 0xff << 23; - uint32_t f16inf = 0x1f << 23; - uint32_t sign; - union fi magic; - union fi f32; - uint16_t f16; - - magic.ui = 0xf << 23; - - f32.f = f; - - /* Sign */ - sign = f32.ui & sign_mask; - f32.ui ^= sign; - - if (f32.ui == f32inf) { - /* Inf */ - f16 = 0x7c00; - } else if (f32.ui > f32inf) { - /* NaN */ - f16 = 0x7e00; - } else { - /* Number */ - f32.ui &= round_mask; - f32.f *= magic.f; - f32.ui -= round_mask; - - /* - * Clamp to max finite value if overflowed. - * OpenGL has completely undefined rounding behavior for float to - * half-float conversions, and this matches what is mandated for float - * to fp11/fp10, which recommend round-to-nearest-finite too. - * (d3d10 is deeply unhappy about flushing such values to infinity, and - * while it also mandates round-to-zero it doesn't care nearly as much - * about that.) - */ - if (f32.ui > f16inf) - f32.ui = f16inf - 1; - - f16 = f32.ui >> 13; - } - - /* Sign */ - f16 |= sign >> 16; - - return f16; -} - -static inline float util_half_to_float(uint16_t f16) { - union fi infnan; - union fi magic; - union fi f32; - - infnan.ui = 0x8f << 23; - infnan.f = 65536.0f; - magic.ui = 0xef << 23; - - /* Exponent / Mantissa */ - f32.ui = (f16 & 0x7fff) << 13; - - /* Adjust */ - f32.f *= magic.f; - - /* Inf / NaN */ - if (f32.f >= infnan.f) - f32.ui |= 0xff << 23; - - /* Sign */ - f32.ui |= (f16 & 0x8000) << 16; - - return f32.f; -} - -#ifdef __cplusplus -} -#endif - -#endif /* U_HALF_H */ diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_hash_table.c b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_hash_table.c deleted file mode 100644 index 5d5800fd4..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_hash_table.c +++ /dev/null @@ -1,265 +0,0 @@ -/************************************************************************** - * - * Copyright 2008 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -/** - * @file - * General purpose hash table implementation. - * - * Just uses the cso_hash for now, but it might be better switch to a linear - * probing hash table implementation at some point -- as it is said they have - * better lookup and cache performance and it appears to be possible to write - * a lock-free implementation of such hash tables . - * - * @author José Fonseca - */ - -#include "pipe/p_compiler.h" -#include "util/u_debug.h" - -#include "cso_cache/cso_hash.h" - -#include "util/u_hash_table.h" -#include "util/u_memory.h" - -struct util_hash_table { - struct cso_hash *cso; - - /** Hash function */ - unsigned (*hash)(void *key); - - /** Compare two keys */ - int (*compare)(void *key1, void *key2); - - /** free value */ - void (*destroy)(void *value); -}; - -struct util_hash_table_item { - void *key; - void *value; -}; - -static inline struct util_hash_table_item * -util_hash_table_item(struct cso_hash_iter iter) { - return (struct util_hash_table_item *)cso_hash_iter_data(iter); -} - -struct util_hash_table *util_hash_table_create(unsigned (*hash)(void *key), - int (*compare)(void *key1, - void *key2), - void (*destroy)(void *value)) { - struct util_hash_table *ht; - - ht = MALLOC_STRUCT(util_hash_table); - if (!ht) - return NULL; - - ht->cso = cso_hash_create(); - if (!ht->cso) { - FREE(ht); - return NULL; - } - - ht->hash = hash; - ht->compare = compare; - ht->destroy = destroy; - - return ht; -} - -static inline struct cso_hash_iter -util_hash_table_find_iter(struct util_hash_table *ht, void *key, - unsigned key_hash) { - struct cso_hash_iter iter; - struct util_hash_table_item *item; - - iter = cso_hash_find(ht->cso, key_hash); - while (!cso_hash_iter_is_null(iter)) { - item = (struct util_hash_table_item *)cso_hash_iter_data(iter); - if (!ht->compare(item->key, key)) - break; - iter = cso_hash_iter_next(iter); - } - - return iter; -} - -static inline struct util_hash_table_item * -util_hash_table_find_item(struct util_hash_table *ht, void *key, - unsigned key_hash) { - struct cso_hash_iter iter; - struct util_hash_table_item *item; - - iter = cso_hash_find(ht->cso, key_hash); - while (!cso_hash_iter_is_null(iter)) { - item = (struct util_hash_table_item *)cso_hash_iter_data(iter); - if (!ht->compare(item->key, key)) - return item; - iter = cso_hash_iter_next(iter); - } - - return NULL; -} - -enum pipe_error util_hash_table_set(struct util_hash_table *ht, void *key, - void *value) { - unsigned key_hash; - struct util_hash_table_item *item; - struct cso_hash_iter iter; - - assert(ht); - if (!ht) - return PIPE_ERROR_BAD_INPUT; - - key_hash = ht->hash(key); - - item = util_hash_table_find_item(ht, key, key_hash); - if (item) { - ht->destroy(item->value); - item->value = value; - return PIPE_OK; - } - - item = MALLOC_STRUCT(util_hash_table_item); - if (!item) - return PIPE_ERROR_OUT_OF_MEMORY; - - item->key = key; - item->value = value; - - iter = cso_hash_insert(ht->cso, key_hash, item); - if (cso_hash_iter_is_null(iter)) { - FREE(item); - return PIPE_ERROR_OUT_OF_MEMORY; - } - - return PIPE_OK; -} - -void *util_hash_table_get(struct util_hash_table *ht, void *key) { - unsigned key_hash; - struct util_hash_table_item *item; - - assert(ht); - if (!ht) - return NULL; - - key_hash = ht->hash(key); - - item = util_hash_table_find_item(ht, key, key_hash); - if (!item) - return NULL; - - return item->value; -} - -void util_hash_table_remove(struct util_hash_table *ht, void *key) { - unsigned key_hash; - struct cso_hash_iter iter; - struct util_hash_table_item *item; - - assert(ht); - if (!ht) - return; - - key_hash = ht->hash(key); - - iter = util_hash_table_find_iter(ht, key, key_hash); - if (cso_hash_iter_is_null(iter)) - return; - - item = util_hash_table_item(iter); - assert(item); - ht->destroy(item->value); - FREE(item); - - cso_hash_erase(ht->cso, iter); -} - -void util_hash_table_clear(struct util_hash_table *ht) { - struct cso_hash_iter iter; - struct util_hash_table_item *item; - - assert(ht); - if (!ht) - return; - - iter = cso_hash_first_node(ht->cso); - while (!cso_hash_iter_is_null(iter)) { - item = (struct util_hash_table_item *)cso_hash_take( - ht->cso, cso_hash_iter_key(iter)); - ht->destroy(item->value); - FREE(item); - iter = cso_hash_first_node(ht->cso); - } -} - -enum pipe_error util_hash_table_foreach(struct util_hash_table *ht, - enum pipe_error (*callback)(void *key, - void *value, - void *data), - void *data) { - struct cso_hash_iter iter; - struct util_hash_table_item *item; - enum pipe_error result; - - assert(ht); - if (!ht) - return PIPE_ERROR_BAD_INPUT; - - iter = cso_hash_first_node(ht->cso); - while (!cso_hash_iter_is_null(iter)) { - item = (struct util_hash_table_item *)cso_hash_iter_data(iter); - result = callback(item->key, item->value, data); - if (result != PIPE_OK) - return result; - iter = cso_hash_iter_next(iter); - } - - return PIPE_OK; -} - -void util_hash_table_destroy(struct util_hash_table *ht) { - struct cso_hash_iter iter; - struct util_hash_table_item *item; - - assert(ht); - if (!ht) - return; - - iter = cso_hash_first_node(ht->cso); - while (!cso_hash_iter_is_null(iter)) { - item = (struct util_hash_table_item *)cso_hash_iter_data(iter); - ht->destroy(item->value); - FREE(item); - iter = cso_hash_iter_next(iter); - } - - cso_hash_delete(ht->cso); - - FREE(ht); -} diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_hash_table.h b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_hash_table.h deleted file mode 100644 index 68fc27ef2..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_hash_table.h +++ /dev/null @@ -1,80 +0,0 @@ -/************************************************************************** - * - * Copyright 2008 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -/** - * General purpose hash table. - * - * @author José Fonseca - */ - -#ifndef U_HASH_TABLE_H_ -#define U_HASH_TABLE_H_ - -#include "pipe/p_defines.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * Generic purpose hash table. - */ -struct util_hash_table; - -/** - * Create an hash table. - * - * @param hash hash function - * @param compare should return 0 for two equal keys. - */ -struct util_hash_table *util_hash_table_create(unsigned (*hash)(void *key), - int (*compare)(void *key1, - void *key2), - void (*destroy)(void *value)); - -enum pipe_error util_hash_table_set(struct util_hash_table *ht, void *key, - void *value); - -void *util_hash_table_get(struct util_hash_table *ht, void *key); - -void util_hash_table_remove(struct util_hash_table *ht, void *key); - -void util_hash_table_clear(struct util_hash_table *ht); - -enum pipe_error util_hash_table_foreach(struct util_hash_table *ht, - enum pipe_error (*callback)(void *key, - void *value, - void *data), - void *data); - -void util_hash_table_destroy(struct util_hash_table *ht); - -#ifdef __cplusplus -} -#endif - -#endif /* U_HASH_TABLE_H_ */ diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_inlines.h b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_inlines.h deleted file mode 100644 index 69c1691b7..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_inlines.h +++ /dev/null @@ -1,560 +0,0 @@ -/************************************************************************** - * - * Copyright 2007 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -#ifndef U_INLINES_H -#define U_INLINES_H - -#include "pipe/p_context.h" -#include "pipe/p_defines.h" -#include "pipe/p_screen.h" -#include "pipe/p_shader_tokens.h" -#include "pipe/p_state.h" -#include "util/u_atomic.h" -#include "util/u_box.h" -#include "util/u_debug.h" -#include "util/u_debug_describe.h" -#include "util/u_debug_refcnt.h" -#include "util/u_math.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/* - * Reference counting helper functions. - */ - -static inline void pipe_reference_init(struct pipe_reference *reference, - unsigned count) { - p_atomic_set(&reference->count, count); -} - -static inline boolean pipe_is_referenced(struct pipe_reference *reference) { - return p_atomic_read(&reference->count) != 0; -} - -/** - * Update reference counting. - * The old thing pointed to, if any, will be unreferenced. - * Both 'ptr' and 'reference' may be NULL. - * \return TRUE if the object's refcount hits zero and should be destroyed. - */ -static inline boolean -pipe_reference_described(struct pipe_reference *ptr, - struct pipe_reference *reference, - debug_reference_descriptor get_desc) { - boolean destroy = FALSE; - - if (ptr != reference) { - /* bump the reference.count first */ - if (reference) { - assert(pipe_is_referenced(reference)); - p_atomic_inc(&reference->count); - debug_reference(reference, get_desc, 1); - } - - if (ptr) { - assert(pipe_is_referenced(ptr)); - if (p_atomic_dec_zero(&ptr->count)) { - destroy = TRUE; - } - debug_reference(ptr, get_desc, -1); - } - } - - return destroy; -} - -static inline boolean pipe_reference(struct pipe_reference *ptr, - struct pipe_reference *reference) { - return pipe_reference_described( - ptr, reference, (debug_reference_descriptor)debug_describe_reference); -} - -static inline void pipe_surface_reference(struct pipe_surface **ptr, - struct pipe_surface *surf) { - struct pipe_surface *old_surf = *ptr; - - if (pipe_reference_described( - &(*ptr)->reference, &surf->reference, - (debug_reference_descriptor)debug_describe_surface)) - old_surf->context->surface_destroy(old_surf->context, old_surf); - *ptr = surf; -} - -/** - * Similar to pipe_surface_reference() but always set the pointer to NULL - * and pass in an explicit context. The explicit context avoids the problem - * of using a deleted context's surface_destroy() method when freeing a surface - * that's shared by multiple contexts. - */ -static inline void pipe_surface_release(struct pipe_context *pipe, - struct pipe_surface **ptr) { - if (pipe_reference_described( - &(*ptr)->reference, NULL, - (debug_reference_descriptor)debug_describe_surface)) - pipe->surface_destroy(pipe, *ptr); - *ptr = NULL; -} - -static inline void pipe_resource_reference(struct pipe_resource **ptr, - struct pipe_resource *tex) { - struct pipe_resource *old_tex = *ptr; - - if (pipe_reference_described( - &(*ptr)->reference, &tex->reference, - (debug_reference_descriptor)debug_describe_resource)) - old_tex->screen->resource_destroy(old_tex->screen, old_tex); - *ptr = tex; -} - -static inline void pipe_sampler_view_reference(struct pipe_sampler_view **ptr, - struct pipe_sampler_view *view) { - struct pipe_sampler_view *old_view = *ptr; - - if (pipe_reference_described( - &(*ptr)->reference, &view->reference, - (debug_reference_descriptor)debug_describe_sampler_view)) - old_view->context->sampler_view_destroy(old_view->context, old_view); - *ptr = view; -} - -/** - * Similar to pipe_sampler_view_reference() but always set the pointer to - * NULL and pass in an explicit context. Passing an explicit context is a - * work-around for fixing a dangling context pointer problem when textures - * are shared by multiple contexts. XXX fix this someday. - */ -static inline void pipe_sampler_view_release(struct pipe_context *ctx, - struct pipe_sampler_view **ptr) { - struct pipe_sampler_view *old_view = *ptr; - if (*ptr && (*ptr)->context != ctx) { - debug_printf_once(("context mis-match in pipe_sampler_view_release()\n")); - } - if (pipe_reference_described( - &(*ptr)->reference, NULL, - (debug_reference_descriptor)debug_describe_sampler_view)) { - ctx->sampler_view_destroy(ctx, old_view); - } - *ptr = NULL; -} - -static inline void -pipe_so_target_reference(struct pipe_stream_output_target **ptr, - struct pipe_stream_output_target *target) { - struct pipe_stream_output_target *old = *ptr; - - if (pipe_reference_described( - &(*ptr)->reference, &target->reference, - (debug_reference_descriptor)debug_describe_so_target)) - old->context->stream_output_target_destroy(old->context, old); - *ptr = target; -} - -static inline void pipe_surface_reset(struct pipe_context *ctx, - struct pipe_surface *ps, - struct pipe_resource *pt, unsigned level, - unsigned layer) { - pipe_resource_reference(&ps->texture, pt); - ps->format = pt->format; - ps->width = u_minify(pt->width0, level); - ps->height = u_minify(pt->height0, level); - ps->u.tex.level = level; - ps->u.tex.first_layer = ps->u.tex.last_layer = layer; - ps->context = ctx; -} - -static inline void pipe_surface_init(struct pipe_context *ctx, - struct pipe_surface *ps, - struct pipe_resource *pt, unsigned level, - unsigned layer) { - ps->texture = 0; - pipe_reference_init(&ps->reference, 1); - pipe_surface_reset(ctx, ps, pt, level, layer); -} - -/* Return true if the surfaces are equal. */ -static inline boolean pipe_surface_equal(struct pipe_surface *s1, - struct pipe_surface *s2) { - return s1->texture == s2->texture && s1->format == s2->format && - (s1->texture->target != PIPE_BUFFER || - (s1->u.buf.first_element == s2->u.buf.first_element && - s1->u.buf.last_element == s2->u.buf.last_element)) && - (s1->texture->target == PIPE_BUFFER || - (s1->u.tex.level == s2->u.tex.level && - s1->u.tex.first_layer == s2->u.tex.first_layer && - s1->u.tex.last_layer == s2->u.tex.last_layer)); -} - -/* - * Convenience wrappers for screen buffer functions. - */ - -/** - * Create a new resource. - * \param bind bitmask of PIPE_BIND_x flags - * \param usage bitmask of PIPE_USAGE_x flags - */ -static inline struct pipe_resource * -pipe_buffer_create(struct pipe_screen *screen, unsigned bind, unsigned usage, - unsigned size) { - struct pipe_resource buffer; - memset(&buffer, 0, sizeof buffer); - buffer.target = PIPE_BUFFER; - buffer.format = PIPE_FORMAT_R8_UNORM; /* want TYPELESS or similar */ - buffer.bind = bind; - buffer.usage = usage; - buffer.flags = 0; - buffer.width0 = size; - buffer.height0 = 1; - buffer.depth0 = 1; - buffer.array_size = 1; - return screen->resource_create(screen, &buffer); -} - -/** - * Map a range of a resource. - * \param offset start of region, in bytes - * \param length size of region, in bytes - * \param access bitmask of PIPE_TRANSFER_x flags - * \param transfer returns a transfer object - */ -static inline void *pipe_buffer_map_range(struct pipe_context *pipe, - struct pipe_resource *buffer, - unsigned offset, unsigned length, - unsigned access, - struct pipe_transfer **transfer) { - struct pipe_box box; - void *map; - - assert(offset < buffer->width0); - assert(offset + length <= buffer->width0); - assert(length); - - u_box_1d(offset, length, &box); - - map = pipe->transfer_map(pipe, buffer, 0, access, &box, transfer); - if (map == NULL) { - return NULL; - } - - return map; -} - -/** - * Map whole resource. - * \param access bitmask of PIPE_TRANSFER_x flags - * \param transfer returns a transfer object - */ -static inline void *pipe_buffer_map(struct pipe_context *pipe, - struct pipe_resource *buffer, - unsigned access, - struct pipe_transfer **transfer) { - return pipe_buffer_map_range(pipe, buffer, 0, buffer->width0, access, - transfer); -} - -static inline void pipe_buffer_unmap(struct pipe_context *pipe, - struct pipe_transfer *transfer) { - pipe->transfer_unmap(pipe, transfer); -} - -static inline void -pipe_buffer_flush_mapped_range(struct pipe_context *pipe, - struct pipe_transfer *transfer, unsigned offset, - unsigned length) { - struct pipe_box box; - int transfer_offset; - - assert(length); - assert(transfer->box.x <= (int)offset); - assert((int)(offset + length) <= transfer->box.x + transfer->box.width); - - /* Match old screen->buffer_flush_mapped_range() behaviour, where - * offset parameter is relative to the start of the buffer, not the - * mapped range. - */ - transfer_offset = offset - transfer->box.x; - - u_box_1d(transfer_offset, length, &box); - - pipe->transfer_flush_region(pipe, transfer, &box); -} - -static inline void pipe_buffer_write(struct pipe_context *pipe, - struct pipe_resource *buf, unsigned offset, - unsigned size, const void *data) { - struct pipe_box box; - unsigned access = PIPE_TRANSFER_WRITE; - - if (offset == 0 && size == buf->width0) { - access |= PIPE_TRANSFER_DISCARD_WHOLE_RESOURCE; - } else { - access |= PIPE_TRANSFER_DISCARD_RANGE; - } - - u_box_1d(offset, size, &box); - - pipe->transfer_inline_write(pipe, buf, 0, access, &box, data, size, 0); -} - -/** - * Special case for writing non-overlapping ranges. - * - * We can avoid GPU/CPU synchronization when writing range that has never - * been written before. - */ -static inline void pipe_buffer_write_nooverlap(struct pipe_context *pipe, - struct pipe_resource *buf, - unsigned offset, unsigned size, - const void *data) { - struct pipe_box box; - - u_box_1d(offset, size, &box); - - pipe->transfer_inline_write( - pipe, buf, 0, (PIPE_TRANSFER_WRITE | PIPE_TRANSFER_UNSYNCHRONIZED), &box, - data, 0, 0); -} - -/** - * Create a new resource and immediately put data into it - * \param bind bitmask of PIPE_BIND_x flags - * \param usage bitmask of PIPE_USAGE_x flags - */ -static inline struct pipe_resource * -pipe_buffer_create_with_data(struct pipe_context *pipe, unsigned bind, - unsigned usage, unsigned size, const void *ptr) { - struct pipe_resource *res = - pipe_buffer_create(pipe->screen, bind, usage, size); - pipe_buffer_write_nooverlap(pipe, res, 0, size, ptr); - return res; -} - -static inline void pipe_buffer_read(struct pipe_context *pipe, - struct pipe_resource *buf, unsigned offset, - unsigned size, void *data) { - struct pipe_transfer *src_transfer; - ubyte *map; - - map = (ubyte *)pipe_buffer_map_range(pipe, buf, offset, size, - PIPE_TRANSFER_READ, &src_transfer); - if (!map) - return; - - memcpy(data, map, size); - pipe_buffer_unmap(pipe, src_transfer); -} - -/** - * Map a resource for reading/writing. - * \param access bitmask of PIPE_TRANSFER_x flags - */ -static inline void *pipe_transfer_map(struct pipe_context *context, - struct pipe_resource *resource, - unsigned level, unsigned layer, - unsigned access, unsigned x, unsigned y, - unsigned w, unsigned h, - struct pipe_transfer **transfer) { - struct pipe_box box; - u_box_2d_zslice(x, y, layer, w, h, &box); - return context->transfer_map(context, resource, level, access, &box, - transfer); -} - -/** - * Map a 3D (texture) resource for reading/writing. - * \param access bitmask of PIPE_TRANSFER_x flags - */ -static inline void *pipe_transfer_map_3d(struct pipe_context *context, - struct pipe_resource *resource, - unsigned level, unsigned access, - unsigned x, unsigned y, unsigned z, - unsigned w, unsigned h, unsigned d, - struct pipe_transfer **transfer) { - struct pipe_box box; - u_box_3d(x, y, z, w, h, d, &box); - return context->transfer_map(context, resource, level, access, &box, - transfer); -} - -static inline void pipe_transfer_unmap(struct pipe_context *context, - struct pipe_transfer *transfer) { - context->transfer_unmap(context, transfer); -} - -static inline void pipe_set_constant_buffer(struct pipe_context *pipe, - uint shader, uint index, - struct pipe_resource *buf) { - if (buf) { - struct pipe_constant_buffer cb; - cb.buffer = buf; - cb.buffer_offset = 0; - cb.buffer_size = buf->width0; - cb.user_buffer = NULL; - pipe->set_constant_buffer(pipe, shader, index, &cb); - } else { - pipe->set_constant_buffer(pipe, shader, index, NULL); - } -} - -/** - * Get the polygon offset enable/disable flag for the given polygon fill mode. - * \param fill_mode one of PIPE_POLYGON_MODE_POINT/LINE/FILL - */ -static inline boolean util_get_offset(const struct pipe_rasterizer_state *templ, - unsigned fill_mode) { - switch (fill_mode) { - case PIPE_POLYGON_MODE_POINT: - return templ->offset_point; - case PIPE_POLYGON_MODE_LINE: - return templ->offset_line; - case PIPE_POLYGON_MODE_FILL: - return templ->offset_tri; - default: - assert(0); - return FALSE; - } -} - -static inline float -util_get_min_point_size(const struct pipe_rasterizer_state *state) { - /* The point size should be clamped to this value at the rasterizer stage. - */ - return !state->point_quad_rasterization && !state->point_smooth && - !state->multisample - ? 1.0f - : 0.0f; -} - -static inline void util_query_clear_result(union pipe_query_result *result, - unsigned type) { - switch (type) { - case PIPE_QUERY_OCCLUSION_PREDICATE: - case PIPE_QUERY_SO_OVERFLOW_PREDICATE: - case PIPE_QUERY_GPU_FINISHED: - result->b = FALSE; - break; - case PIPE_QUERY_OCCLUSION_COUNTER: - case PIPE_QUERY_TIMESTAMP: - case PIPE_QUERY_TIME_ELAPSED: - case PIPE_QUERY_PRIMITIVES_GENERATED: - case PIPE_QUERY_PRIMITIVES_EMITTED: - result->u64 = 0; - break; - case PIPE_QUERY_SO_STATISTICS: - memset(&result->so_statistics, 0, sizeof(result->so_statistics)); - break; - case PIPE_QUERY_TIMESTAMP_DISJOINT: - memset(&result->timestamp_disjoint, 0, sizeof(result->timestamp_disjoint)); - break; - case PIPE_QUERY_PIPELINE_STATISTICS: - memset(&result->pipeline_statistics, 0, - sizeof(result->pipeline_statistics)); - break; - default: - memset(result, 0, sizeof(*result)); - } -} - -/** Convert PIPE_TEXTURE_x to TGSI_TEXTURE_x */ -static inline unsigned -util_pipe_tex_to_tgsi_tex(enum pipe_texture_target pipe_tex_target, - unsigned nr_samples) { - switch (pipe_tex_target) { - case PIPE_TEXTURE_1D: - assert(nr_samples <= 1); - return TGSI_TEXTURE_1D; - - case PIPE_TEXTURE_2D: - return nr_samples > 1 ? TGSI_TEXTURE_2D_MSAA : TGSI_TEXTURE_2D; - - case PIPE_TEXTURE_RECT: - assert(nr_samples <= 1); - return TGSI_TEXTURE_RECT; - - case PIPE_TEXTURE_3D: - assert(nr_samples <= 1); - return TGSI_TEXTURE_3D; - - case PIPE_TEXTURE_CUBE: - assert(nr_samples <= 1); - return TGSI_TEXTURE_CUBE; - - case PIPE_TEXTURE_1D_ARRAY: - assert(nr_samples <= 1); - return TGSI_TEXTURE_1D_ARRAY; - - case PIPE_TEXTURE_2D_ARRAY: - return nr_samples > 1 ? TGSI_TEXTURE_2D_ARRAY_MSAA : TGSI_TEXTURE_2D_ARRAY; - - case PIPE_TEXTURE_CUBE_ARRAY: - return TGSI_TEXTURE_CUBE_ARRAY; - - default: - assert(0 && "unexpected texture target"); - return TGSI_TEXTURE_UNKNOWN; - } -} - -static inline void -util_copy_constant_buffer(struct pipe_constant_buffer *dst, - const struct pipe_constant_buffer *src) { - if (src) { - pipe_resource_reference(&dst->buffer, src->buffer); - dst->buffer_offset = src->buffer_offset; - dst->buffer_size = src->buffer_size; - dst->user_buffer = src->user_buffer; - } else { - pipe_resource_reference(&dst->buffer, NULL); - dst->buffer_offset = 0; - dst->buffer_size = 0; - dst->user_buffer = NULL; - } -} - -static inline unsigned util_max_layer(const struct pipe_resource *r, - unsigned level) { - switch (r->target) { - case PIPE_TEXTURE_CUBE: - return 6 - 1; - case PIPE_TEXTURE_3D: - return u_minify(r->depth0, level) - 1; - case PIPE_TEXTURE_1D_ARRAY: - case PIPE_TEXTURE_2D_ARRAY: - case PIPE_TEXTURE_CUBE_ARRAY: - return r->array_size - 1; - default: - return 0; - } -} - -#ifdef __cplusplus -} -#endif - -#endif /* U_INLINES_H */ diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_math.c b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_math.c deleted file mode 100644 index 860bc322f..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_math.c +++ /dev/null @@ -1,127 +0,0 @@ -/************************************************************************** - * - * Copyright 2008 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -#include "util/u_math.h" -#include "pipe/p_config.h" -#include "util/u_cpu_detect.h" - -#if defined(PIPE_ARCH_SSE) -#include -/* This is defined in pmmintrin.h, but it can only be included when -msse3 is - * used, so just define it here to avoid further. */ -#define _MM_DENORMALS_ZERO_MASK 0x0040 -#endif - -#if 0 -/** 2^x, for x in [-1.0, 1.0) */ -float pow2_table[POW2_TABLE_SIZE]; - - -static void -init_pow2_table(void) -{ - int i; - for (i = 0; i < POW2_TABLE_SIZE; i++) - pow2_table[i] = (float) pow(2.0, (i - POW2_TABLE_OFFSET) / POW2_TABLE_SCALE); -} - - -/** log2(x), for x in [1.0, 2.0) */ -float log2_table[LOG2_TABLE_SIZE]; - - -static void -init_log2_table(void) -{ - unsigned i; - for (i = 0; i < LOG2_TABLE_SIZE; i++) - log2_table[i] = (float) log2(1.0 + i * (1.0 / LOG2_TABLE_SCALE)); -} -#endif - -/** - * One time init for math utilities. - */ -void util_init_math(void) { - static boolean initialized = FALSE; - if (!initialized) { - // init_pow2_table(); - /* init_log2_table();*/ - initialized = TRUE; - } -} - -/** - * Fetches the contents of the fpstate (mxcsr on x86) register. - * - * On platforms without support for it just returns 0. - */ -unsigned util_fpstate_get(void) { - unsigned mxcsr = 0; - -#if defined(PIPE_ARCH_SSE) - if (util_cpu_caps.has_sse) { - mxcsr = _mm_getcsr(); - } -#endif - - return mxcsr; -} - -/** - * Make sure that the fp treats the denormalized floating - * point numbers as zero. - * - * This is the behavior required by D3D10. OpenGL doesn't care. - */ -unsigned util_fpstate_set_denorms_to_zero(unsigned current_mxcsr) { -#if defined(PIPE_ARCH_SSE) - if (util_cpu_caps.has_sse) { - /* Enable flush to zero mode */ - current_mxcsr |= _MM_FLUSH_ZERO_MASK; - if (util_cpu_caps.has_daz) { - /* Enable denormals are zero mode */ - current_mxcsr |= _MM_DENORMALS_ZERO_MASK; - } - util_fpstate_set(current_mxcsr); - } -#endif - return current_mxcsr; -} - -/** - * Set the state of the fpstate (mxcsr on x86) register. - * - * On platforms without support for it's a noop. - */ -void util_fpstate_set(unsigned mxcsr) { -#if defined(PIPE_ARCH_SSE) - if (util_cpu_caps.has_sse) { - _mm_setcsr(mxcsr); - } -#endif -} diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_math.h b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_math.h deleted file mode 100644 index df08e9071..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_math.h +++ /dev/null @@ -1,768 +0,0 @@ -/************************************************************************** - * - * Copyright 2008 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -/** - * Math utilities and approximations for common math functions. - * Reduced precision is usually acceptable in shaders... - * - * "fast" is used in the names of functions which are low-precision, - * or at least lower-precision than the normal C lib functions. - */ - -#ifndef U_MATH_H -#define U_MATH_H - -#include "pipe/p_compiler.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include -#include - -#ifdef PIPE_OS_UNIX -#include /* for ffs */ -#endif - -#ifndef M_SQRT2 -#define M_SQRT2 1.41421356237309504880 -#endif - -#if defined(_MSC_VER) - -#if _MSC_VER < 1400 && !defined(__cplusplus) - -static inline float cosf(float f) { return (float)cos((double)f); } - -static inline float sinf(float f) { return (float)sin((double)f); } - -static inline float ceilf(float f) { return (float)ceil((double)f); } - -static inline float floorf(float f) { return (float)floor((double)f); } - -static inline float powf(float f, float g) { - return (float)pow((double)f, (double)g); -} - -static inline float sqrtf(float f) { return (float)sqrt((double)f); } - -static inline float fabsf(float f) { return (float)fabs((double)f); } - -static inline float logf(float f) { return (float)log((double)f); } - -#else -/* Work-around an extra semi-colon in VS 2005 logf definition */ -#ifdef logf -#undef logf -#define logf(x) ((float)log((double)(x))) -#endif /* logf */ - -#if _MSC_VER < 1800 -#define isfinite(x) _finite((double)(x)) -#define isnan(x) _isnan((double)(x)) -#endif /* _MSC_VER < 1800 */ -#endif /* _MSC_VER < 1400 && !defined(__cplusplus) */ - -#if _MSC_VER < 1800 -static inline double log2(double x) { - const double invln2 = 1.442695041; - return log(x) * invln2; -} - -static inline double round(double x) { - return x >= 0.0 ? floor(x + 0.5) : ceil(x - 0.5); -} - -static inline float roundf(float x) { - return x >= 0.0f ? floorf(x + 0.5f) : ceilf(x - 0.5f); -} -#endif - -#ifndef INFINITY -#define INFINITY (DBL_MAX + DBL_MAX) -#endif - -#ifndef NAN -#define NAN (INFINITY - INFINITY) -#endif - -#endif /* _MSC_VER */ - -#if __STDC_VERSION__ < 199901L && (!defined(__cplusplus) || defined(_MSC_VER)) -static inline long int lrint(double d) { - long int rounded = (long int)(d + 0.5); - - if (d - floor(d) == 0.5) { - if (rounded % 2 != 0) - rounded += (d > 0) ? -1 : 1; - } - - return rounded; -} - -static inline long int lrintf(float f) { - long int rounded = (long int)(f + 0.5f); - - if (f - floorf(f) == 0.5f) { - if (rounded % 2 != 0) - rounded += (f > 0) ? -1 : 1; - } - - return rounded; -} - -static inline long long int llrint(double d) { - long long int rounded = (long long int)(d + 0.5); - - if (d - floor(d) == 0.5) { - if (rounded % 2 != 0) - rounded += (d > 0) ? -1 : 1; - } - - return rounded; -} - -static inline long long int llrintf(float f) { - long long int rounded = (long long int)(f + 0.5f); - - if (f - floorf(f) == 0.5f) { - if (rounded % 2 != 0) - rounded += (f > 0) ? -1 : 1; - } - - return rounded; -} -#endif /* C99 */ - -#define POW2_TABLE_SIZE_LOG2 9 -#define POW2_TABLE_SIZE (1 << POW2_TABLE_SIZE_LOG2) -#define POW2_TABLE_OFFSET (POW2_TABLE_SIZE / 2) -#define POW2_TABLE_SCALE ((float)(POW2_TABLE_SIZE / 2)) -extern float pow2_table[POW2_TABLE_SIZE]; - -/** - * Initialize math module. This should be called before using any - * other functions in this module. - */ -extern void util_init_math(void); - -union fi { - float f; - int32_t i; - uint32_t ui; -}; - -union di { - double d; - int64_t i; - uint64_t ui; -}; - -/** - * Extract the IEEE float32 exponent. - */ -static inline signed util_get_float32_exponent(float x) { - union fi f; - - f.f = x; - - return ((f.ui >> 23) & 0xff) - 127; -} - -/** - * Fast version of 2^x - * Identity: exp2(a + b) = exp2(a) * exp2(b) - * Let ipart = int(x) - * Let fpart = x - ipart; - * So, exp2(x) = exp2(ipart) * exp2(fpart) - * Compute exp2(ipart) with i << ipart - * Compute exp2(fpart) with lookup table. - */ -static inline float util_fast_exp2(float x) { - int32_t ipart; - float fpart, mpart; - union fi epart; - - if (x > 129.00000f) - return 3.402823466e+38f; - - if (x < -126.99999f) - return 0.0f; - - ipart = (int32_t)x; - fpart = x - (float)ipart; - - /* same as - * epart.f = (float) (1 << ipart) - * but faster and without integer overflow for ipart > 31 - */ - epart.i = (ipart + 127) << 23; - - mpart = pow2_table[POW2_TABLE_OFFSET + (int)(fpart * POW2_TABLE_SCALE)]; - - return epart.f * mpart; -} - -/** - * Fast approximation to exp(x). - */ -static inline float util_fast_exp(float x) { - const float k = 1.44269f; /* = log2(e) */ - return util_fast_exp2(k * x); -} - -#if 0 - -#define LOG2_TABLE_SIZE_LOG2 16 -#define LOG2_TABLE_SCALE (1 << LOG2_TABLE_SIZE_LOG2) -#define LOG2_TABLE_SIZE (LOG2_TABLE_SCALE + 1) -extern float log2_table[LOG2_TABLE_SIZE]; - - -/** - * Fast approximation to log2(x). - */ -static inline float -util_fast_log2(float x) -{ - union fi num; - float epart, mpart; - num.f = x; - epart = (float)(((num.i & 0x7f800000) >> 23) - 127); - /* mpart = log2_table[mantissa*LOG2_TABLE_SCALE + 0.5] */ - mpart = log2_table[((num.i & 0x007fffff) + (1 << (22 - LOG2_TABLE_SIZE_LOG2))) >> (23 - LOG2_TABLE_SIZE_LOG2)]; - return epart + mpart; -} - - -/** - * Fast approximation to x^y. - */ -static inline float -util_fast_pow(float x, float y) -{ - return util_fast_exp2(util_fast_log2(x) * y); -} -#endif -/* Note that this counts zero as a power of two. - */ -static inline boolean util_is_power_of_two(unsigned v) { - return (v & (v - 1)) == 0; -} - -/** - * Floor(x), returned as int. - */ -static inline int util_ifloor(float f) { - int ai, bi; - double af, bf; - union fi u; - af = (3 << 22) + 0.5 + (double)f; - bf = (3 << 22) + 0.5 - (double)f; - u.f = (float)af; - ai = u.i; - u.f = (float)bf; - bi = u.i; - return (ai - bi) >> 1; -} - -/** - * Round float to nearest int. - */ -static inline int util_iround(float f) { -#if defined(PIPE_CC_GCC) && defined(PIPE_ARCH_X86) - int r; - __asm__("fistpl %0" : "=m"(r) : "t"(f) : "st"); - return r; -#elif defined(PIPE_CC_MSVC) && defined(PIPE_ARCH_X86) - int r; - _asm { - fld f - fistp r - } - return r; -#else - if (f >= 0.0f) - return (int)(f + 0.5f); - else - return (int)(f - 0.5f); -#endif -} - -/** - * Approximate floating point comparison - */ -static inline boolean util_is_approx(float a, float b, float tol) { - return fabs(b - a) <= tol; -} - -/** - * util_is_X_inf_or_nan = test if x is NaN or +/- Inf - * util_is_X_nan = test if x is NaN - * util_X_inf_sign = return +1 for +Inf, -1 for -Inf, or 0 for not Inf - * - * NaN can be checked with x != x, however this fails with the fast math flag - **/ - -/** - * Single-float - */ -static inline boolean util_is_inf_or_nan(float x) { - union fi tmp; - tmp.f = x; - return (tmp.ui & 0x7f800000) == 0x7f800000; -} - -static inline boolean util_is_nan(float x) { - union fi tmp; - tmp.f = x; - return (tmp.ui & 0x7fffffff) > 0x7f800000; -} - -static inline int util_inf_sign(float x) { - union fi tmp; - tmp.f = x; - if ((tmp.ui & 0x7fffffff) != 0x7f800000) { - return 0; - } - - return (x < 0) ? -1 : 1; -} - -/** - * Double-float - */ -static inline boolean util_is_double_inf_or_nan(double x) { - union di tmp; - tmp.d = x; - return (tmp.ui & 0x7ff0000000000000ULL) == 0x7ff0000000000000ULL; -} - -static inline boolean util_is_double_nan(double x) { - union di tmp; - tmp.d = x; - return (tmp.ui & 0x7fffffffffffffffULL) > 0x7ff0000000000000ULL; -} - -static inline int util_double_inf_sign(double x) { - union di tmp; - tmp.d = x; - if ((tmp.ui & 0x7fffffffffffffffULL) != 0x7ff0000000000000ULL) { - return 0; - } - - return (x < 0) ? -1 : 1; -} - -/** - * Half-float - */ -static inline boolean util_is_half_inf_or_nan(int16_t x) { - return (x & 0x7c00) == 0x7c00; -} - -static inline boolean util_is_half_nan(int16_t x) { - return (x & 0x7fff) > 0x7c00; -} - -static inline int util_half_inf_sign(int16_t x) { - if ((x & 0x7fff) != 0x7c00) { - return 0; - } - - return (x < 0) ? -1 : 1; -} - -/** - * Find first bit set in word. Least significant bit is 1. - * Return 0 if no bits set. - */ -#ifndef FFS_DEFINED -#define FFS_DEFINED 1 - -#if defined(_MSC_VER) && _MSC_VER >= 1300 && (_M_IX86 || _M_AMD64 || _M_IA64) -unsigned char _BitScanForward(unsigned long *Index, unsigned long Mask); -#pragma intrinsic(_BitScanForward) -static inline unsigned long ffs(unsigned long u) { - unsigned long i; - if (_BitScanForward(&i, u)) - return i + 1; - else - return 0; -} -#elif defined(PIPE_CC_MSVC) && defined(PIPE_ARCH_X86) -static inline unsigned ffs(unsigned u) { - unsigned i; - - if (u == 0) { - return 0; - } - - __asm bsf eax, [u] __asm inc eax __asm mov[i], - eax - - return i; -} -#elif defined(__MINGW32__) || defined(PIPE_OS_ANDROID) -#define ffs __builtin_ffs -#endif - -#endif /* FFS_DEFINED */ - -/** - * Find last bit set in a word. The least significant bit is 1. - * Return 0 if no bits are set. - */ -static inline unsigned util_last_bit(unsigned u) { -#if defined(__GNUC__) && ((__GNUC__ * 100 + __GNUC_MINOR__) >= 304) - return u == 0 ? 0 : 32 - __builtin_clz(u); -#else - unsigned r = 0; - while (u) { - r++; - u >>= 1; - } - return r; -#endif -} - -/** - * Find last bit in a word that does not match the sign bit. The least - * significant bit is 1. - * Return 0 if no bits are set. - */ -static inline unsigned util_last_bit_signed(int i) { -#if defined(__GNUC__) && ((__GNUC__ * 100 + __GNUC_MINOR__) >= 407) - return 31 - __builtin_clrsb(i); -#else - if (i >= 0) - return util_last_bit(i); - else - return util_last_bit(~(unsigned)i); -#endif -} - -/* Destructively loop over all of the bits in a mask as in: - * - * while (mymask) { - * int i = u_bit_scan(&mymask); - * ... process element i - * } - * - */ -static inline int u_bit_scan(unsigned *mask) { - int i = ffs(*mask) - 1; - *mask &= ~(1 << i); - return i; -} - -/* For looping over a bitmask when you want to loop over consecutive bits - * manually, for example: - * - * while (mask) { - * int start, count, i; - * - * u_bit_scan_consecutive_range(&mask, &start, &count); - * - * for (i = 0; i < count; i++) - * ... process element (start+i) - * } - */ -static inline void u_bit_scan_consecutive_range(unsigned *mask, int *start, - int *count) { - if (*mask == 0xffffffff) { - *start = 0; - *count = 32; - *mask = 0; - return; - } - *start = ffs(*mask) - 1; - *count = ffs(~(*mask >> *start)) - 1; - *mask &= ~(((1u << *count) - 1) << *start); -} - -/** - * Return float bits. - */ -static inline unsigned fui(float f) { - union fi fi; - fi.f = f; - return fi.ui; -} - -/** - * Convert ubyte to float in [0, 1]. - * XXX a 256-entry lookup table would be slightly faster. - */ -static inline float ubyte_to_float(ubyte ub) { - return (float)ub * (1.0f / 255.0f); -} - -/** - * Convert float in [0,1] to ubyte in [0,255] with clamping. - */ -static inline ubyte float_to_ubyte(float f) { - union fi tmp; - - tmp.f = f; - if (tmp.i < 0) { - return (ubyte)0; - } else if (tmp.i >= 0x3f800000 /* 1.0f */) { - return (ubyte)255; - } else { - tmp.f = tmp.f * (255.0f / 256.0f) + 32768.0f; - return (ubyte)tmp.i; - } -} - -static inline float byte_to_float_tex(int8_t b) { - return (b == -128) ? -1.0F : b * 1.0F / 127.0F; -} - -static inline int8_t float_to_byte_tex(float f) { return (int8_t)(127.0F * f); } - -/** - * Calc log base 2 - */ -static inline unsigned util_logbase2(unsigned n) { -#if defined(PIPE_CC_GCC) && (PIPE_CC_GCC_VERSION >= 304) - return ((sizeof(unsigned) * 8 - 1) - __builtin_clz(n | 1)); -#else - unsigned pos = 0; - if (n >= 1 << 16) { - n >>= 16; - pos += 16; - } - if (n >= 1 << 8) { - n >>= 8; - pos += 8; - } - if (n >= 1 << 4) { - n >>= 4; - pos += 4; - } - if (n >= 1 << 2) { - n >>= 2; - pos += 2; - } - if (n >= 1 << 1) { - pos += 1; - } - return pos; -#endif -} - -/** - * Returns the smallest power of two >= x - */ -static inline unsigned util_next_power_of_two(unsigned x) { -#if defined(PIPE_CC_GCC) && (PIPE_CC_GCC_VERSION >= 304) - if (x <= 1) - return 1; - - return (1 << ((sizeof(unsigned) * 8) - __builtin_clz(x - 1))); -#else - unsigned val = x; - - if (x <= 1) - return 1; - - if (util_is_power_of_two(x)) - return x; - - val--; - val = (val >> 1) | val; - val = (val >> 2) | val; - val = (val >> 4) | val; - val = (val >> 8) | val; - val = (val >> 16) | val; - val++; - return val; -#endif -} - -/** - * Return number of bits set in n. - */ -static inline unsigned util_bitcount(unsigned n) { -#if defined(PIPE_CC_GCC) && (PIPE_CC_GCC_VERSION >= 304) - return __builtin_popcount(n); -#else - /* K&R classic bitcount. - * - * For each iteration, clear the LSB from the bitfield. - * Requires only one iteration per set bit, instead of - * one iteration per bit less than highest set bit. - */ - unsigned bits = 0; - for (bits; n; bits++) { - n &= n - 1; - } - return bits; -#endif -} - -/** - * Reverse bits in n - * Algorithm taken from: - * http://stackoverflow.com/questions/9144800/c-reverse-bits-in-unsigned-integer - */ -static inline unsigned util_bitreverse(unsigned n) { - n = ((n >> 1) & 0x55555555u) | ((n & 0x55555555u) << 1); - n = ((n >> 2) & 0x33333333u) | ((n & 0x33333333u) << 2); - n = ((n >> 4) & 0x0f0f0f0fu) | ((n & 0x0f0f0f0fu) << 4); - n = ((n >> 8) & 0x00ff00ffu) | ((n & 0x00ff00ffu) << 8); - n = ((n >> 16) & 0xffffu) | ((n & 0xffffu) << 16); - return n; -} - -/** - * Convert from little endian to CPU byte order. - */ - -#ifdef PIPE_ARCH_BIG_ENDIAN -#define util_le64_to_cpu(x) util_bswap64(x) -#define util_le32_to_cpu(x) util_bswap32(x) -#define util_le16_to_cpu(x) util_bswap16(x) -#else -#define util_le64_to_cpu(x) (x) -#define util_le32_to_cpu(x) (x) -#define util_le16_to_cpu(x) (x) -#endif - -#define util_cpu_to_le64(x) util_le64_to_cpu(x) -#define util_cpu_to_le32(x) util_le32_to_cpu(x) -#define util_cpu_to_le16(x) util_le16_to_cpu(x) - -/** - * Reverse byte order of a 32 bit word. - */ -static inline uint32_t util_bswap32(uint32_t n) { -/* We need the gcc version checks for non-autoconf build system */ -#if defined(HAVE___BUILTIN_BSWAP32) || \ - (defined(PIPE_CC_GCC) && (PIPE_CC_GCC_VERSION >= 403)) - return __builtin_bswap32(n); -#else - return (n >> 24) | ((n >> 8) & 0x0000ff00) | ((n << 8) & 0x00ff0000) | - (n << 24); -#endif -} - -/** - * Reverse byte order of a 64bit word. - */ -static inline uint64_t util_bswap64(uint64_t n) { -#if defined(HAVE___BUILTIN_BSWAP64) - return __builtin_bswap64(n); -#else - return ((uint64_t)util_bswap32(n) << 32) | util_bswap32((n >> 32)); -#endif -} - -/** - * Reverse byte order of a 16 bit word. - */ -static inline uint16_t util_bswap16(uint16_t n) { return (n >> 8) | (n << 8); } - -/** - * Clamp X to [MIN, MAX]. - * This is a macro to allow float, int, uint, etc. types. - */ -#define CLAMP(X, MIN, MAX) ((X) < (MIN) ? (MIN) : ((X) > (MAX) ? (MAX) : (X))) - -#define MIN2(A, B) ((A) < (B) ? (A) : (B)) -#define MAX2(A, B) ((A) > (B) ? (A) : (B)) - -#define MIN3(A, B, C) ((A) < (B) ? MIN2(A, C) : MIN2(B, C)) -#define MAX3(A, B, C) ((A) > (B) ? MAX2(A, C) : MAX2(B, C)) - -#define MIN4(A, B, C, D) ((A) < (B) ? MIN3(A, C, D) : MIN3(B, C, D)) -#define MAX4(A, B, C, D) ((A) > (B) ? MAX3(A, C, D) : MAX3(B, C, D)) - -/** - * Align a value, only works pot alignemnts. - */ -static inline int align(int value, int alignment) { - return (value + alignment - 1) & ~(alignment - 1); -} - -/** - * Works like align but on npot alignments. - */ -static inline size_t util_align_npot(size_t value, size_t alignment) { - if (value % alignment) - return value + (alignment - (value % alignment)); - return value; -} - -static inline unsigned u_minify(unsigned value, unsigned levels) { - return MAX2(1, value >> levels); -} - -#ifndef COPY_4V -#define COPY_4V(DST, SRC) \ - do { \ - (DST)[0] = (SRC)[0]; \ - (DST)[1] = (SRC)[1]; \ - (DST)[2] = (SRC)[2]; \ - (DST)[3] = (SRC)[3]; \ - } while (0) -#endif - -#ifndef COPY_4FV -#define COPY_4FV(DST, SRC) COPY_4V(DST, SRC) -#endif - -#ifndef ASSIGN_4V -#define ASSIGN_4V(DST, V0, V1, V2, V3) \ - do { \ - (DST)[0] = (V0); \ - (DST)[1] = (V1); \ - (DST)[2] = (V2); \ - (DST)[3] = (V3); \ - } while (0) -#endif - -static inline uint32_t util_unsigned_fixed(float value, unsigned frac_bits) { - return value < 0 ? 0 : (uint32_t)(value * (1 << frac_bits)); -} - -static inline int32_t util_signed_fixed(float value, unsigned frac_bits) { - return (int32_t)(value * (1 << frac_bits)); -} - -unsigned util_fpstate_get(void); -unsigned util_fpstate_set_denorms_to_zero(unsigned current_fpstate); -void util_fpstate_set(unsigned fpstate); - -#ifdef __cplusplus -} -#endif - -#endif /* U_MATH_H */ diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_memory.h b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_memory.h deleted file mode 100644 index 528e43d0d..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_memory.h +++ /dev/null @@ -1,91 +0,0 @@ -/************************************************************************** - * - * Copyright 2008 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -/* - * Memory functions - */ - -#ifndef U_MEMORY_H -#define U_MEMORY_H - -#include "os/os_memory.h" -#include "util/u_debug.h" -#include "util/u_pointer.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#define MALLOC(_size) os_malloc(_size) - -#define CALLOC(_count, _size) os_calloc(_count, _size) - -#define FREE(_ptr) os_free(_ptr) - -#define REALLOC(_ptr, _old_size, _size) os_realloc(_ptr, _old_size, _size) - -#define MALLOC_STRUCT(T) (struct T *)MALLOC(sizeof(struct T)) - -#define CALLOC_STRUCT(T) (struct T *)CALLOC(1, sizeof(struct T)) - -#define CALLOC_VARIANT_LENGTH_STRUCT(T, more_size) \ - ((struct T *)CALLOC(1, sizeof(struct T) + more_size)) - -#define align_malloc(_size, _alignment) os_malloc_aligned(_size, _alignment) -#define align_free(_ptr) os_free_aligned(_ptr) - -/** - * Duplicate a block of memory. - */ -static inline void *mem_dup(const void *src, uint size) { - void *dup = MALLOC(size); - if (dup) - memcpy(dup, src, size); - return dup; -} - -/** - * Number of elements in an array. - */ -#ifndef ARRAY_SIZE -#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) -#endif - -#ifndef Elements -#define Elements(x) (sizeof(x) / sizeof((x)[0])) -#endif - -/** - * Offset of a field in a struct, in bytes. - */ -#define Offset(TYPE, MEMBER) ((uintptr_t)&(((TYPE *)NULL)->MEMBER)) - -#ifdef __cplusplus -} -#endif - -#endif /* U_MEMORY_H */ diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_pack_color.h b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_pack_color.h deleted file mode 100644 index fb6d8e472..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_pack_color.h +++ /dev/null @@ -1,73 +0,0 @@ -/************************************************************************** - * - * Copyright 2008 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -/** - * @file - * Functions to produce packed colors/Z from floats. - */ - -#ifndef U_PACK_COLOR_H -#define U_PACK_COLOR_H - -#include "pipe/p_compiler.h" -#include "pipe/p_format.h" -#include "util/u_debug.h" -#include "util/u_format.h" -#include "util/u_math.h" - -/** - * Helper union for packing pixel values. - * Will often contain values in formats which are too complex to be described - * in simple terms, hence might just effectively contain a number of bytes. - * Must be big enough to hold data for all formats (currently 256 bits). - */ -union util_color { - ubyte ub; - ushort us; - uint ui[4]; - ushort h[4]; /* half float */ - float f[4]; - double d[4]; -}; - -/** - * Pack 4 ubytes into a 4-byte word - */ -static inline unsigned pack_ub4(ubyte b0, ubyte b1, ubyte b2, ubyte b3) { - return ((((unsigned int)b0) << 0) | (((unsigned int)b1) << 8) | - (((unsigned int)b2) << 16) | (((unsigned int)b3) << 24)); -} - -/** - * Pack/convert 4 floats into one 4-byte word. - */ -static inline unsigned pack_ui32_float4(float a, float b, float c, float d) { - return pack_ub4(float_to_ubyte(a), float_to_ubyte(b), float_to_ubyte(c), - float_to_ubyte(d)); -} - -#endif /* U_PACK_COLOR_H */ diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_pointer.h b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_pointer.h deleted file mode 100644 index 5a57b513a..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_pointer.h +++ /dev/null @@ -1,113 +0,0 @@ -/************************************************************************** - * - * Copyright 2007-2008 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -#ifndef U_POINTER_H -#define U_POINTER_H - -#include "pipe/p_compiler.h" - -#ifdef __cplusplus -extern "C" { -#endif - -static inline intptr_t pointer_to_intptr(const void *p) { - union { - const void *p; - intptr_t i; - } pi; - pi.p = p; - return pi.i; -} - -static inline void *intptr_to_pointer(intptr_t i) { - union { - void *p; - intptr_t i; - } pi; - pi.i = i; - return pi.p; -} - -static inline uintptr_t pointer_to_uintptr(const void *ptr) { - union { - const void *p; - uintptr_t u; - } pu; - pu.p = ptr; - return pu.u; -} - -static inline void *uintptr_to_pointer(uintptr_t u) { - union { - void *p; - uintptr_t u; - } pu; - pu.u = u; - return pu.p; -} - -/** - * Return a pointer aligned to next multiple of N bytes. - */ -static inline void *align_pointer(const void *unaligned, uintptr_t alignment) { - uintptr_t aligned = - (pointer_to_uintptr(unaligned) + alignment - 1) & ~(alignment - 1); - return uintptr_to_pointer(aligned); -} - -/** - * Return a pointer aligned to next multiple of 16 bytes. - */ -static inline void *align16(void *unaligned) { - return align_pointer(unaligned, 16); -} - -typedef void (*func_pointer)(void); - -static inline func_pointer pointer_to_func(void *p) { - union { - void *p; - func_pointer f; - } pf; - pf.p = p; - return pf.f; -} - -static inline void *func_to_pointer(func_pointer f) { - union { - void *p; - func_pointer f; - } pf; - pf.f = f; - return pf.p; -} - -#ifdef __cplusplus -} -#endif - -#endif /* U_POINTER_H */ diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_prim.h b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_prim.h deleted file mode 100644 index 07134573e..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_prim.h +++ /dev/null @@ -1,251 +0,0 @@ -/************************************************************************** - * - * Copyright 2008 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -#ifndef U_PRIM_H -#define U_PRIM_H - -#include "pipe/p_defines.h" -#include "util/u_debug.h" - -#ifdef __cplusplus -extern "C" { -#endif - -struct u_prim_vertex_count { - unsigned min; - unsigned incr; -}; - -/** - * Decompose a primitive that is a loop, a strip, or a fan. Return the - * original primitive if it is already decomposed. - */ -static inline unsigned u_decomposed_prim(unsigned prim) { - switch (prim) { - case PIPE_PRIM_LINE_LOOP: - case PIPE_PRIM_LINE_STRIP: - return PIPE_PRIM_LINES; - case PIPE_PRIM_TRIANGLE_STRIP: - case PIPE_PRIM_TRIANGLE_FAN: - return PIPE_PRIM_TRIANGLES; - case PIPE_PRIM_QUAD_STRIP: - return PIPE_PRIM_QUADS; - case PIPE_PRIM_LINE_STRIP_ADJACENCY: - return PIPE_PRIM_LINES_ADJACENCY; - case PIPE_PRIM_TRIANGLE_STRIP_ADJACENCY: - return PIPE_PRIM_TRIANGLES_ADJACENCY; - default: - return prim; - } -} - -/** - * Reduce a primitive to one of PIPE_PRIM_POINTS, PIPE_PRIM_LINES, and - * PIPE_PRIM_TRIANGLES. - */ -static inline unsigned u_reduced_prim(unsigned prim) { - switch (prim) { - case PIPE_PRIM_POINTS: - return PIPE_PRIM_POINTS; - case PIPE_PRIM_LINES: - case PIPE_PRIM_LINE_LOOP: - case PIPE_PRIM_LINE_STRIP: - case PIPE_PRIM_LINES_ADJACENCY: - case PIPE_PRIM_LINE_STRIP_ADJACENCY: - return PIPE_PRIM_LINES; - default: - return PIPE_PRIM_TRIANGLES; - } -} - -/** - * Re-assemble a primitive to remove its adjacency. - */ -static inline unsigned u_assembled_prim(unsigned prim) { - switch (prim) { - case PIPE_PRIM_LINES_ADJACENCY: - case PIPE_PRIM_LINE_STRIP_ADJACENCY: - return PIPE_PRIM_LINES; - case PIPE_PRIM_TRIANGLES_ADJACENCY: - case PIPE_PRIM_TRIANGLE_STRIP_ADJACENCY: - return PIPE_PRIM_TRIANGLES; - default: - return prim; - } -} - -/** - * Return the vertex count information for a primitive. - * - * Note that if this function is called directly or indirectly anywhere in a - * source file, it will increase the size of the binary slightly more than - * expected because of the use of a table. - */ -static inline const struct u_prim_vertex_count * -u_prim_vertex_count(unsigned prim) { - static const struct u_prim_vertex_count prim_table[PIPE_PRIM_MAX] = { - {1, 1}, /* PIPE_PRIM_POINTS */ - {2, 2}, /* PIPE_PRIM_LINES */ - {2, 1}, /* PIPE_PRIM_LINE_LOOP */ - {2, 1}, /* PIPE_PRIM_LINE_STRIP */ - {3, 3}, /* PIPE_PRIM_TRIANGLES */ - {3, 1}, /* PIPE_PRIM_TRIANGLE_STRIP */ - {3, 1}, /* PIPE_PRIM_TRIANGLE_FAN */ - {4, 4}, /* PIPE_PRIM_QUADS */ - {4, 2}, /* PIPE_PRIM_QUAD_STRIP */ - {3, 1}, /* PIPE_PRIM_POLYGON */ - {4, 4}, /* PIPE_PRIM_LINES_ADJACENCY */ - {4, 1}, /* PIPE_PRIM_LINE_STRIP_ADJACENCY */ - {6, 6}, /* PIPE_PRIM_TRIANGLES_ADJACENCY */ - {6, 2}, /* PIPE_PRIM_TRIANGLE_STRIP_ADJACENCY */ - }; - - return (likely(prim < PIPE_PRIM_MAX)) ? &prim_table[prim] : NULL; -} - -static inline boolean u_validate_pipe_prim(unsigned pipe_prim, unsigned nr) { - const struct u_prim_vertex_count *count = u_prim_vertex_count(pipe_prim); - - return (count && nr >= count->min); -} - -static inline boolean u_trim_pipe_prim(unsigned pipe_prim, unsigned *nr) { - const struct u_prim_vertex_count *count = u_prim_vertex_count(pipe_prim); - - if (count && *nr >= count->min) { - if (count->incr > 1) - *nr -= (*nr % count->incr); - return TRUE; - } else { - *nr = 0; - return FALSE; - } -} - -static inline unsigned u_vertices_per_prim(int primitive) { - switch (primitive) { - case PIPE_PRIM_POINTS: - return 1; - case PIPE_PRIM_LINES: - case PIPE_PRIM_LINE_LOOP: - case PIPE_PRIM_LINE_STRIP: - return 2; - case PIPE_PRIM_TRIANGLES: - case PIPE_PRIM_TRIANGLE_STRIP: - case PIPE_PRIM_TRIANGLE_FAN: - return 3; - case PIPE_PRIM_LINES_ADJACENCY: - case PIPE_PRIM_LINE_STRIP_ADJACENCY: - return 4; - case PIPE_PRIM_TRIANGLES_ADJACENCY: - case PIPE_PRIM_TRIANGLE_STRIP_ADJACENCY: - return 6; - - /* following primitives should never be used - * with geometry shaders abd their size is - * undefined */ - case PIPE_PRIM_POLYGON: - case PIPE_PRIM_QUADS: - case PIPE_PRIM_QUAD_STRIP: - default: - debug_printf("Unrecognized geometry shader primitive"); - return 3; - } -} - -/** - * Returns the number of decomposed primitives for the given - * vertex count. - * Parts of the pipline are invoked once for each triangle in - * triangle strip, triangle fans and triangles and once - * for each line in line strip, line loop, lines. Also - * statistics depend on knowing the exact number of decomposed - * primitives for a set of vertices. - */ -static inline unsigned u_decomposed_prims_for_vertices(int primitive, - int vertices) { - switch (primitive) { - case PIPE_PRIM_POINTS: - return vertices; - case PIPE_PRIM_LINES: - return vertices / 2; - case PIPE_PRIM_LINE_LOOP: - return (vertices >= 2) ? vertices : 0; - case PIPE_PRIM_LINE_STRIP: - return (vertices >= 2) ? vertices - 1 : 0; - case PIPE_PRIM_TRIANGLES: - return vertices / 3; - case PIPE_PRIM_TRIANGLE_STRIP: - return (vertices >= 3) ? vertices - 2 : 0; - case PIPE_PRIM_TRIANGLE_FAN: - return (vertices >= 3) ? vertices - 2 : 0; - case PIPE_PRIM_LINES_ADJACENCY: - return vertices / 4; - case PIPE_PRIM_LINE_STRIP_ADJACENCY: - return (vertices >= 4) ? vertices - 3 : 0; - case PIPE_PRIM_TRIANGLES_ADJACENCY: - return vertices / 6; - case PIPE_PRIM_TRIANGLE_STRIP_ADJACENCY: - return (vertices >= 6) ? 1 + (vertices - 6) / 2 : 0; - case PIPE_PRIM_QUADS: - return vertices / 4; - case PIPE_PRIM_QUAD_STRIP: - return (vertices >= 4) ? (vertices - 2) / 2 : 0; - /* Polygons can't be decomposed - * because the number of their vertices isn't known so - * for them and whatever else we don't recognize just - * return 1 if the number of vertices is greater than - * or equal to 3 and zero otherwise */ - case PIPE_PRIM_POLYGON: - default: - debug_printf("Invalid decomposition primitive!\n"); - return (vertices >= 3) ? 1 : 0; - } -} - -/** - * Returns the number of reduced/tessellated primitives for the given vertex - * count. Each quad is treated as two triangles. Polygons are treated as - * triangle fans. - */ -static inline unsigned u_reduced_prims_for_vertices(int primitive, - int vertices) { - switch (primitive) { - case PIPE_PRIM_QUADS: - case PIPE_PRIM_QUAD_STRIP: - return u_decomposed_prims_for_vertices(primitive, vertices) * 2; - case PIPE_PRIM_POLYGON: - primitive = PIPE_PRIM_TRIANGLE_FAN; - /* fall through */ - default: - return u_decomposed_prims_for_vertices(primitive, vertices); - } -} - -const char *u_prim_name(unsigned pipe_prim); - -#endif diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_rect.h b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_rect.h deleted file mode 100644 index a7411e92e..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_rect.h +++ /dev/null @@ -1,93 +0,0 @@ -/************************************************************************** - * - * Copyright 2008 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -#ifndef U_RECT_H -#define U_RECT_H - -#include "pipe/p_compiler.h" -#include "util/u_math.h" - -#ifdef __cplusplus -extern "C" { -#endif - -struct u_rect { - int x0, x1; - int y0, y1; -}; - -/* Do two rectangles intersect? - */ -static inline boolean u_rect_test_intersection(const struct u_rect *a, - const struct u_rect *b) { - return (!(a->x1 < b->x0 || b->x1 < a->x0 || a->y1 < b->y0 || b->y1 < a->y0)); -} - -/* Find the intersection of two rectangles known to intersect. - */ -static inline void u_rect_find_intersection(const struct u_rect *a, - struct u_rect *b) { - /* Caller should verify intersection exists before calling. - */ - if (b->x0 < a->x0) - b->x0 = a->x0; - if (b->x1 > a->x1) - b->x1 = a->x1; - if (b->y0 < a->y0) - b->y0 = a->y0; - if (b->y1 > a->y1) - b->y1 = a->y1; -} - -static inline int u_rect_area(const struct u_rect *r) { - return (r->x1 - r->x0) * (r->y1 - r->y0); -} - -static inline void u_rect_possible_intersection(const struct u_rect *a, - struct u_rect *b) { - if (u_rect_test_intersection(a, b)) { - u_rect_find_intersection(a, b); - } else { - b->x0 = b->x1 = b->y0 = b->y1 = 0; - } -} - -/* Set @d to a rectangle that covers both @a and @b. - */ -static inline void u_rect_union(struct u_rect *d, const struct u_rect *a, - const struct u_rect *b) { - d->x0 = MIN2(a->x0, b->x0); - d->y0 = MIN2(a->y0, b->y0); - d->x1 = MAX2(a->x1, b->x1); - d->y1 = MAX2(a->y1, b->y1); -} - -#ifdef __cplusplus -} -#endif - -#endif /* U_RECT_H */ diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_string.h b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_string.h deleted file mode 100644 index 97e0ddeef..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_string.h +++ /dev/null @@ -1,203 +0,0 @@ -/************************************************************************** - * - * Copyright 2008 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -/** - * @file - * Platform independent functions for string manipulation. - * - * @author Jose Fonseca - */ - -#ifndef U_STRING_H_ -#define U_STRING_H_ - -#if !defined(_MSC_VER) && !defined(XF86_LIBC_H) -#include -#endif -#include -#include - -#include "pipe/p_compiler.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#ifdef _GNU_SOURCE - -#define util_strchrnul strchrnul - -#else - -static inline char *util_strchrnul(const char *s, char c) { - for (; *s && *s != c; ++s) - ; - - return (char *)s; -} - -#endif - -#ifdef _MSC_VER - -int util_vsnprintf(char *, size_t, const char *, va_list); -int util_snprintf(char *str, size_t size, const char *format, ...); - -static inline void util_vsprintf(char *str, const char *format, va_list ap) { - util_vsnprintf(str, (size_t)-1, format, ap); -} - -static inline void util_sprintf(char *str, const char *format, ...) { - va_list ap; - va_start(ap, format); - util_vsnprintf(str, (size_t)-1, format, ap); - va_end(ap); -} - -static inline char *util_strchr(const char *s, char c) { - char *p = util_strchrnul(s, c); - - return *p ? p : NULL; -} - -static inline char *util_strncat(char *dst, const char *src, size_t n) { - char *p = dst + strlen(dst); - const char *q = src; - size_t i; - - for (i = 0; i < n && *q != '\0'; ++i) - *p++ = *q++; - *p = '\0'; - - return dst; -} - -static inline int util_strcmp(const char *s1, const char *s2) { - unsigned char u1, u2; - - while (1) { - u1 = (unsigned char)*s1++; - u2 = (unsigned char)*s2++; - if (u1 != u2) - return u1 - u2; - if (u1 == '\0') - return 0; - } - return 0; -} - -static inline int util_strncmp(const char *s1, const char *s2, size_t n) { - unsigned char u1, u2; - - while (n-- > 0) { - u1 = (unsigned char)*s1++; - u2 = (unsigned char)*s2++; - if (u1 != u2) - return u1 - u2; - if (u1 == '\0') - return 0; - } - return 0; -} - -static inline char *util_strstr(const char *haystack, const char *needle) { - const char *p = haystack; - size_t len = strlen(needle); - - for (; (p = util_strchr(p, *needle)) != 0; p++) { - if (util_strncmp(p, needle, len) == 0) { - return (char *)p; - } - } - return NULL; -} - -static inline void *util_memmove(void *dest, const void *src, size_t n) { - char *p = (char *)dest; - const char *q = (const char *)src; - if (dest < src) { - while (n--) - *p++ = *q++; - } else { - p += n; - q += n; - while (n--) - *--p = *--q; - } - return dest; -} - -#else - -#define util_vsnprintf vsnprintf -#define util_snprintf snprintf -#define util_vsprintf vsprintf -#define util_sprintf sprintf -#define util_strchr strchr -#define util_strcmp strcmp -#define util_strncmp strncmp -#define util_strncat strncat -#define util_strstr strstr -#define util_memmove memmove - -#endif - -/** - * Printable string buffer - */ -struct util_strbuf { - char *str; - char *ptr; - size_t left; -}; - -static inline void util_strbuf_init(struct util_strbuf *sbuf, char *str, - size_t size) { - sbuf->str = str; - sbuf->str[0] = 0; - sbuf->ptr = sbuf->str; - sbuf->left = size; -} - -static inline void util_strbuf_printf(struct util_strbuf *sbuf, - const char *format, ...) { - if (sbuf->left > 1) { - size_t written; - va_list ap; - va_start(ap, format); - written = util_vsnprintf(sbuf->ptr, sbuf->left, format, ap); - va_end(ap); - sbuf->ptr += written; - sbuf->left -= written; - } -} - -#ifdef __cplusplus -} -#endif - -#endif /* U_STRING_H_ */ diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_surface.c b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_surface.c deleted file mode 100644 index f3a1c9092..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_surface.c +++ /dev/null @@ -1,391 +0,0 @@ -/************************************************************************** - * - * Copyright 2009 VMware, Inc. All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -/** - * @file - * Surface utility functions. - * - * @author Brian Paul - */ - -#include "pipe/p_defines.h" -#include "pipe/p_screen.h" -#include "pipe/p_state.h" - -#include "util/u_format.h" -#include "util/u_inlines.h" -#include "util/u_pack_color.h" -#include "util/u_rect.h" -#include "util/u_surface.h" - -/** - * Initialize a pipe_surface object. 'view' is considered to have - * uninitialized contents. - */ -void u_surface_default_template(struct pipe_surface *surf, - const struct pipe_resource *texture) { - memset(surf, 0, sizeof(*surf)); - - surf->format = texture->format; -} - -/** - * Copy 2D rect from one place to another. - * Position and sizes are in pixels. - * src_stride may be negative to do vertical flip of pixels from source. - */ -void util_copy_rect(ubyte *dst, enum pipe_format format, unsigned dst_stride, - unsigned dst_x, unsigned dst_y, unsigned width, - unsigned height, const ubyte *src, int src_stride, - unsigned src_x, unsigned src_y) { - unsigned i; - int src_stride_pos = src_stride < 0 ? -src_stride : src_stride; - int blocksize = util_format_get_blocksize(format); - int blockwidth = util_format_get_blockwidth(format); - int blockheight = util_format_get_blockheight(format); - - assert(blocksize > 0); - assert(blockwidth > 0); - assert(blockheight > 0); - - dst_x /= blockwidth; - dst_y /= blockheight; - width = (width + blockwidth - 1) / blockwidth; - height = (height + blockheight - 1) / blockheight; - src_x /= blockwidth; - src_y /= blockheight; - - dst += dst_x * blocksize; - src += src_x * blocksize; - dst += dst_y * dst_stride; - src += src_y * src_stride_pos; - width *= blocksize; - - if (width == dst_stride && (int)width == src_stride) - memcpy(dst, src, height * width); - else { - for (i = 0; i < height; i++) { - memcpy(dst, src, width); - dst += dst_stride; - src += src_stride; - } - } -} - -/** - * Copy 3D box from one place to another. - * Position and sizes are in pixels. - */ -void util_copy_box(ubyte *dst, enum pipe_format format, unsigned dst_stride, - unsigned dst_slice_stride, unsigned dst_x, unsigned dst_y, - unsigned dst_z, unsigned width, unsigned height, - unsigned depth, const ubyte *src, int src_stride, - unsigned src_slice_stride, unsigned src_x, unsigned src_y, - unsigned src_z) { - unsigned z; - dst += dst_z * dst_slice_stride; - src += src_z * src_slice_stride; - for (z = 0; z < depth; ++z) { - util_copy_rect(dst, format, dst_stride, dst_x, dst_y, width, height, src, - src_stride, src_x, src_y); - - dst += dst_slice_stride; - src += src_slice_stride; - } -} - -void util_fill_rect(ubyte *dst, enum pipe_format format, unsigned dst_stride, - unsigned dst_x, unsigned dst_y, unsigned width, - unsigned height, union util_color *uc) { - const struct util_format_description *desc = util_format_description(format); - unsigned i, j; - unsigned width_size; - int blocksize = desc->block.bits / 8; - int blockwidth = desc->block.width; - int blockheight = desc->block.height; - - assert(blocksize > 0); - assert(blockwidth > 0); - assert(blockheight > 0); - - dst_x /= blockwidth; - dst_y /= blockheight; - width = (width + blockwidth - 1) / blockwidth; - height = (height + blockheight - 1) / blockheight; - - dst += dst_x * blocksize; - dst += dst_y * dst_stride; - width_size = width * blocksize; - - switch (blocksize) { - case 1: - if (dst_stride == width_size) - memset(dst, uc->ub, height * width_size); - else { - for (i = 0; i < height; i++) { - memset(dst, uc->ub, width_size); - dst += dst_stride; - } - } - break; - case 2: - for (i = 0; i < height; i++) { - uint16_t *row = (uint16_t *)dst; - for (j = 0; j < width; j++) - *row++ = uc->us; - dst += dst_stride; - } - break; - case 4: - for (i = 0; i < height; i++) { - uint32_t *row = (uint32_t *)dst; - for (j = 0; j < width; j++) - *row++ = uc->ui[0]; - dst += dst_stride; - } - break; - default: - for (i = 0; i < height; i++) { - ubyte *row = dst; - for (j = 0; j < width; j++) { - memcpy(row, uc, blocksize); - row += blocksize; - } - dst += dst_stride; - } - break; - } -} - -void util_fill_box(ubyte *dst, enum pipe_format format, unsigned stride, - unsigned layer_stride, unsigned x, unsigned y, unsigned z, - unsigned width, unsigned height, unsigned depth, - union util_color *uc) { - unsigned layer; - dst += z * layer_stride; - for (layer = z; layer < depth; layer++) { - util_fill_rect(dst, format, stride, x, y, width, height, uc); - dst += layer_stride; - } -} - -/** - * Fallback function for pipe->resource_copy_region(). - * Note: (X,Y)=(0,0) is always the upper-left corner. - */ -void util_resource_copy_region(struct pipe_context *pipe, - struct pipe_resource *dst, unsigned dst_level, - unsigned dst_x, unsigned dst_y, unsigned dst_z, - struct pipe_resource *src, unsigned src_level, - const struct pipe_box *src_box) { - struct pipe_transfer *src_trans, *dst_trans; - uint8_t *dst_map; - const uint8_t *src_map; - MAYBE_UNUSED enum pipe_format src_format; - enum pipe_format dst_format; - struct pipe_box dst_box; - - assert(src && dst); - if (!src || !dst) - return; - - assert((src->target == PIPE_BUFFER && dst->target == PIPE_BUFFER) || - (src->target != PIPE_BUFFER && dst->target != PIPE_BUFFER)); - - src_format = src->format; - dst_format = dst->format; - - assert(util_format_get_blocksize(dst_format) == - util_format_get_blocksize(src_format)); - assert(util_format_get_blockwidth(dst_format) == - util_format_get_blockwidth(src_format)); - assert(util_format_get_blockheight(dst_format) == - util_format_get_blockheight(src_format)); - - src_map = pipe->transfer_map(pipe, src, src_level, PIPE_TRANSFER_READ, - src_box, &src_trans); - assert(src_map); - if (!src_map) { - goto no_src_map; - } - - dst_box.x = dst_x; - dst_box.y = dst_y; - dst_box.z = dst_z; - dst_box.width = src_box->width; - dst_box.height = src_box->height; - dst_box.depth = src_box->depth; - - dst_map = pipe->transfer_map( - pipe, dst, dst_level, PIPE_TRANSFER_WRITE | PIPE_TRANSFER_DISCARD_RANGE, - &dst_box, &dst_trans); - assert(dst_map); - if (!dst_map) { - goto no_dst_map; - } - - if (dst->target == PIPE_BUFFER && src->target == PIPE_BUFFER) { - assert(src_box->height == 1); - assert(src_box->depth == 1); - memcpy(dst_map, src_map, src_box->width); - } else { - util_copy_box(dst_map, dst_format, dst_trans->stride, - dst_trans->layer_stride, 0, 0, 0, src_box->width, - src_box->height, src_box->depth, src_map, src_trans->stride, - src_trans->layer_stride, 0, 0, 0); - } - - pipe->transfer_unmap(pipe, dst_trans); -no_dst_map: - pipe->transfer_unmap(pipe, src_trans); -no_src_map:; -} - -#define UBYTE_TO_USHORT(B) ((B) | ((B) << 8)) - -/* Return if the box is totally inside the resource. - */ -static boolean is_box_inside_resource(const struct pipe_resource *res, - const struct pipe_box *box, - unsigned level) { - unsigned width = 1, height = 1, depth = 1; - - switch (res->target) { - case PIPE_BUFFER: - width = res->width0; - height = 1; - depth = 1; - break; - case PIPE_TEXTURE_1D: - width = u_minify(res->width0, level); - height = 1; - depth = 1; - break; - case PIPE_TEXTURE_2D: - case PIPE_TEXTURE_RECT: - width = u_minify(res->width0, level); - height = u_minify(res->height0, level); - depth = 1; - break; - case PIPE_TEXTURE_3D: - width = u_minify(res->width0, level); - height = u_minify(res->height0, level); - depth = u_minify(res->depth0, level); - break; - case PIPE_TEXTURE_CUBE: - width = u_minify(res->width0, level); - height = u_minify(res->height0, level); - depth = 6; - break; - case PIPE_TEXTURE_1D_ARRAY: - width = u_minify(res->width0, level); - height = 1; - depth = res->array_size; - break; - case PIPE_TEXTURE_2D_ARRAY: - width = u_minify(res->width0, level); - height = u_minify(res->height0, level); - depth = res->array_size; - break; - case PIPE_TEXTURE_CUBE_ARRAY: - width = u_minify(res->width0, level); - height = u_minify(res->height0, level); - depth = res->array_size; - assert(res->array_size % 6 == 0); - break; - case PIPE_MAX_TEXTURE_TYPES:; - } - - return box->x >= 0 && box->x + box->width <= (int)width && box->y >= 0 && - box->y + box->height <= (int)height && box->z >= 0 && - box->z + box->depth <= (int)depth; -} - -static unsigned get_sample_count(const struct pipe_resource *res) { - return res->nr_samples ? res->nr_samples : 1; -} - -/** - * Try to do a blit using resource_copy_region. The function calls - * resource_copy_region if the blit description is compatible with it. - * - * It returns TRUE if the blit was done using resource_copy_region. - * - * It returns FALSE otherwise and the caller must fall back to a more generic - * codepath for the blit operation. (e.g. by using u_blitter) - */ -boolean util_try_blit_via_copy_region(struct pipe_context *ctx, - const struct pipe_blit_info *blit) { - unsigned mask = util_format_get_mask(blit->dst.format); - - /* No format conversions. */ - if (blit->src.resource->format != blit->src.format || - blit->dst.resource->format != blit->dst.format || - !util_is_format_compatible( - util_format_description(blit->src.resource->format), - util_format_description(blit->dst.resource->format))) { - return FALSE; - } - - /* No masks, no filtering, no scissor. */ - if ((blit->mask & mask) != mask || blit->filter != PIPE_TEX_FILTER_NEAREST || - blit->scissor_enable) { - return FALSE; - } - - /* No flipping. */ - if (blit->src.box.width < 0 || blit->src.box.height < 0 || - blit->src.box.depth < 0) { - return FALSE; - } - - /* No scaling. */ - if (blit->src.box.width != blit->dst.box.width || - blit->src.box.height != blit->dst.box.height || - blit->src.box.depth != blit->dst.box.depth) { - return FALSE; - } - - /* No out-of-bounds access. */ - if (!is_box_inside_resource(blit->src.resource, &blit->src.box, - blit->src.level) || - !is_box_inside_resource(blit->dst.resource, &blit->dst.box, - blit->dst.level)) { - return FALSE; - } - - /* Sample counts must match. */ - if (get_sample_count(blit->src.resource) != - get_sample_count(blit->dst.resource)) { - return FALSE; - } - - ctx->resource_copy_region(ctx, blit->dst.resource, blit->dst.level, - blit->dst.box.x, blit->dst.box.y, blit->dst.box.z, - blit->src.resource, blit->src.level, - &blit->src.box); - return TRUE; -} diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_surface.h b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_surface.h deleted file mode 100644 index 462cb8b0b..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_surface.h +++ /dev/null @@ -1,91 +0,0 @@ -/************************************************************************** - * - * Copyright 2009 VMware, Inc. All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -#ifndef U_SURFACE_H -#define U_SURFACE_H - -#include "pipe/p_compiler.h" -#include "pipe/p_state.h" - -#include "util/u_pack_color.h" - -#ifdef __cplusplus -extern "C" { -#endif - -extern void u_surface_default_template(struct pipe_surface *view, - const struct pipe_resource *texture); - -extern void util_copy_rect(ubyte *dst, enum pipe_format format, - unsigned dst_stride, unsigned dst_x, unsigned dst_y, - unsigned width, unsigned height, const ubyte *src, - int src_stride, unsigned src_x, unsigned src_y); - -extern void util_copy_box(ubyte *dst, enum pipe_format format, - unsigned dst_stride, unsigned dst_slice_stride, - unsigned dst_x, unsigned dst_y, unsigned dst_z, - unsigned width, unsigned height, unsigned depth, - const ubyte *src, int src_stride, - unsigned src_slice_stride, unsigned src_x, - unsigned src_y, unsigned src_z); - -extern void util_fill_rect(ubyte *dst, enum pipe_format format, - unsigned dst_stride, unsigned dst_x, unsigned dst_y, - unsigned width, unsigned height, - union util_color *uc); - -extern void util_fill_box(ubyte *dst, enum pipe_format format, unsigned stride, - unsigned layer_stride, unsigned x, unsigned y, - unsigned z, unsigned width, unsigned height, - unsigned depth, union util_color *uc); - -extern void -util_resource_copy_region(struct pipe_context *pipe, struct pipe_resource *dst, - unsigned dst_level, unsigned dst_x, unsigned dst_y, - unsigned dst_z, struct pipe_resource *src, - unsigned src_level, const struct pipe_box *src_box); - -extern void util_clear_render_target(struct pipe_context *pipe, - struct pipe_surface *dst, - const union pipe_color_union *color, - unsigned dstx, unsigned dsty, - unsigned width, unsigned height); - -extern void util_clear_depth_stencil(struct pipe_context *pipe, - struct pipe_surface *dst, - unsigned clear_flags, double depth, - unsigned stencil, unsigned dstx, - unsigned dsty, unsigned width, - unsigned height); - -extern boolean util_try_blit_via_copy_region(struct pipe_context *ctx, - const struct pipe_blit_info *blit); - -#ifdef __cplusplus -} -#endif - -#endif /* U_SURFACE_H */ diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_texture.c b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_texture.c deleted file mode 100644 index 995a01f2f..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_texture.c +++ /dev/null @@ -1,109 +0,0 @@ -/************************************************************************** - * - * Copyright 2008 VMware, Inc. - * All Rights Reserved. - * Copyright 2008 VMware, Inc. All rights reserved. - * Copyright 2009 Marek Olšák - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -/** - * @file - * Texture mapping utility functions. - * - * @author Brian Paul - * Marek Olšák - */ - -#include "pipe/p_defines.h" - -#include "util/u_debug.h" -#include "util/u_texture.h" - -void util_map_texcoords2d_onto_cubemap(unsigned face, const float *in_st, - unsigned in_stride, float *out_str, - unsigned out_stride, - boolean allow_scale) { - int i; - float rx, ry, rz; - - /* loop over quad verts */ - for (i = 0; i < 4; i++) { - /* Compute sc = +/-scale and tc = +/-scale. - * Not +/-1 to avoid cube face selection ambiguity near the edges, - * though that can still sometimes happen with this scale factor... - * - * XXX: Yep, there is no safe scale factor that will prevent sampling - * the neighbouring face when stretching out. A more reliable solution - * would be to clamp (sc, tc) against +/- 1.0-1.0/mipsize, in the shader. - * - * Also, this is not necessary when minifying, or 1:1 blits. - */ - const float scale = allow_scale ? 0.9999f : 1.0f; - const float sc = (2 * in_st[0] - 1) * scale; - const float tc = (2 * in_st[1] - 1) * scale; - - switch (face) { - case PIPE_TEX_FACE_POS_X: - rx = 1; - ry = -tc; - rz = -sc; - break; - case PIPE_TEX_FACE_NEG_X: - rx = -1; - ry = -tc; - rz = sc; - break; - case PIPE_TEX_FACE_POS_Y: - rx = sc; - ry = 1; - rz = tc; - break; - case PIPE_TEX_FACE_NEG_Y: - rx = sc; - ry = -1; - rz = -tc; - break; - case PIPE_TEX_FACE_POS_Z: - rx = sc; - ry = -tc; - rz = 1; - break; - case PIPE_TEX_FACE_NEG_Z: - rx = -sc; - ry = -tc; - rz = -1; - break; - default: - rx = ry = rz = 0; - assert(0); - } - - out_str[0] = rx; /*s*/ - out_str[1] = ry; /*t*/ - out_str[2] = rz; /*r*/ - - in_st += in_stride; - out_str += out_stride; - } -} diff --git a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_texture.h b/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_texture.h deleted file mode 100644 index db7a25a5c..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/auxiliary/util/u_texture.h +++ /dev/null @@ -1,56 +0,0 @@ -/************************************************************************** - * - * Copyright 2009 Marek Olšák - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -#ifndef U_TEXTURE_H -#define U_TEXTURE_H - -#include "pipe/p_compiler.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * Convert 2D texture coordinates of 4 vertices into cubemap coordinates - * in the given face. - * Coordinates must be in the range [0,1]. - * - * \param face Cubemap face. - * \param in_st 4 pairs of 2D texture coordinates to convert. - * \param in_stride Stride of in_st in floats. - * \param out_str STR cubemap texture coordinates to compute. - * \param out_stride Stride of out_str in floats. - */ -void util_map_texcoords2d_onto_cubemap(unsigned face, const float *in_st, - unsigned in_stride, float *out_str, - unsigned out_stride, - boolean allow_scale); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/app/src/main/cpp/virglrenderer/src/gallium/include/c11/threads.h b/app/src/main/cpp/virglrenderer/src/gallium/include/c11/threads.h deleted file mode 100644 index c9030ef4e..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/include/c11/threads.h +++ /dev/null @@ -1,65 +0,0 @@ -/* - * C11 emulation library - * - * (C) Copyright yohhoy 2012. - * Distributed under the Boost Software License, Version 1.0. - * - * Permission is hereby granted, free of charge, to any person or organization - * obtaining a copy of the software and accompanying documentation covered by - * this license (the "Software") to use, reproduce, display, distribute, - * execute, and transmit the Software, and to prepare [[derivative work]]s of - * the Software, and to permit third-parties to whom the Software is furnished - * to do so, all subject to the following: - * - * The copyright notices in the Software and this entire statement, including - * the above license grant, this restriction and the following disclaimer, - * must be included in all copies of the Software, in whole or in part, and - * all derivative works of the Software, unless such copies or derivative - * works are solely in the form of machine-executable object code generated by - * a source language processor. - * - * 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, TITLE AND NON-INFRINGEMENT. IN NO EVENT - * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE - * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, - * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ -#ifndef EMULATED_THREADS_H_INCLUDED_ -#define EMULATED_THREADS_H_INCLUDED_ - -#include - -#ifndef TIME_UTC -#define TIME_UTC 1 -#endif - -#include "c99_compat.h" /* for `inline` */ - -/*---------------------------- types ----------------------------*/ -typedef void (*tss_dtor_t)(void *); -typedef int (*thrd_start_t)(void *); - -struct xtime { - time_t sec; - long nsec; -}; -typedef struct xtime xtime; - -/*-------------------- enumeration constants --------------------*/ -enum { mtx_plain = 0, mtx_try = 1, mtx_timed = 2, mtx_recursive = 4 }; - -enum { - thrd_success = 0, // succeeded - thrd_timeout, // timeout - thrd_error, // failed - thrd_busy, // resource busy - thrd_nomem // out of memory -}; - -/*-------------------------- functions --------------------------*/ - -#include "threads_posix.h" - -#endif /* EMULATED_THREADS_H_INCLUDED_ */ diff --git a/app/src/main/cpp/virglrenderer/src/gallium/include/c11/threads_posix.h b/app/src/main/cpp/virglrenderer/src/gallium/include/c11/threads_posix.h deleted file mode 100644 index ebea7c567..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/include/c11/threads_posix.h +++ /dev/null @@ -1,301 +0,0 @@ -/* - * C11 emulation library - * - * (C) Copyright yohhoy 2012. - * Distributed under the Boost Software License, Version 1.0. - * - * Permission is hereby granted, free of charge, to any person or organization - * obtaining a copy of the software and accompanying documentation covered by - * this license (the "Software") to use, reproduce, display, distribute, - * execute, and transmit the Software, and to prepare [[derivative work]]s of - * the Software, and to permit third-parties to whom the Software is furnished - * to do so, all subject to the following: - * - * The copyright notices in the Software and this entire statement, including - * the above license grant, this restriction and the following disclaimer, - * must be included in all copies of the Software, in whole or in part, and - * all derivative works of the Software, unless such copies or derivative - * works are solely in the form of machine-executable object code generated by - * a source language processor. - * - * 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, TITLE AND NON-INFRINGEMENT. IN NO EVENT - * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE - * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, - * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ -#include -#ifndef assert -#include -#endif -#include -#include -#include -#include /* for intptr_t */ -#include - -/* -Configuration macro: - - EMULATED_THREADS_USE_NATIVE_TIMEDLOCK - Use pthread_mutex_timedlock() for `mtx_timedlock()' - Otherwise use mtx_trylock() + *busy loop* emulation. -*/ -#if !defined(__CYGWIN__) && !defined(__APPLE__) && !defined(__NetBSD__) -#define EMULATED_THREADS_USE_NATIVE_TIMEDLOCK -#endif - -#include - -/*---------------------------- macros ----------------------------*/ -#define ONCE_FLAG_INIT PTHREAD_ONCE_INIT -#ifdef INIT_ONCE_STATIC_INIT -#define TSS_DTOR_ITERATIONS PTHREAD_DESTRUCTOR_ITERATIONS -#else -#define TSS_DTOR_ITERATIONS 1 // assume TSS dtor MAY be called at least once. -#endif - -// FIXME: temporary non-standard hack to ease transition -#define _MTX_INITIALIZER_NP PTHREAD_MUTEX_INITIALIZER - -/*---------------------------- types ----------------------------*/ -typedef pthread_cond_t cnd_t; -typedef pthread_t thrd_t; -typedef pthread_key_t tss_t; -typedef pthread_mutex_t mtx_t; -typedef pthread_once_t once_flag; - -/* -Implementation limits: - - Conditionally emulation for "mutex with timeout" - (see EMULATED_THREADS_USE_NATIVE_TIMEDLOCK macro) -*/ -struct impl_thrd_param { - thrd_start_t func; - void *arg; -}; - -static inline void *impl_thrd_routine(void *p) { - struct impl_thrd_param pack = *((struct impl_thrd_param *)p); - free(p); - return (void *)(intptr_t)pack.func(pack.arg); -} - -/*--------------- 7.25.2 Initialization functions ---------------*/ -// 7.25.2.1 -static inline void call_once(once_flag *flag, void (*func)(void)) { - pthread_once(flag, func); -} - -/*------------- 7.25.3 Condition variable functions -------------*/ -// 7.25.3.1 -static inline int cnd_broadcast(cnd_t *cond) { - assert(cond != NULL); - return (pthread_cond_broadcast(cond) == 0) ? thrd_success : thrd_error; -} - -// 7.25.3.2 -static inline void cnd_destroy(cnd_t *cond) { - assert(cond); - pthread_cond_destroy(cond); -} - -// 7.25.3.3 -static inline int cnd_init(cnd_t *cond) { - assert(cond != NULL); - return (pthread_cond_init(cond, NULL) == 0) ? thrd_success : thrd_error; -} - -// 7.25.3.4 -static inline int cnd_signal(cnd_t *cond) { - assert(cond != NULL); - return (pthread_cond_signal(cond) == 0) ? thrd_success : thrd_error; -} - -// 7.25.3.5 -static inline int cnd_timedwait(cnd_t *cond, mtx_t *mtx, const xtime *xt) { - struct timespec abs_time; - int rt; - - assert(mtx != NULL); - assert(cond != NULL); - assert(xt != NULL); - - abs_time.tv_sec = xt->sec; - abs_time.tv_nsec = xt->nsec; - - rt = pthread_cond_timedwait(cond, mtx, &abs_time); - if (rt == ETIMEDOUT) - return thrd_busy; - return (rt == 0) ? thrd_success : thrd_error; -} - -// 7.25.3.6 -static inline int cnd_wait(cnd_t *cond, mtx_t *mtx) { - assert(mtx != NULL); - assert(cond != NULL); - return (pthread_cond_wait(cond, mtx) == 0) ? thrd_success : thrd_error; -} - -/*-------------------- 7.25.4 Mutex functions --------------------*/ -// 7.25.4.1 -static inline void mtx_destroy(mtx_t *mtx) { - assert(mtx != NULL); - pthread_mutex_destroy(mtx); -} - -// 7.25.4.2 -static inline int mtx_init(mtx_t *mtx, int type) { - pthread_mutexattr_t attr; - assert(mtx != NULL); - if (type != mtx_plain && type != mtx_timed && type != mtx_try && - type != (mtx_plain | mtx_recursive) && - type != (mtx_timed | mtx_recursive) && type != (mtx_try | mtx_recursive)) - return thrd_error; - pthread_mutexattr_init(&attr); - if ((type & mtx_recursive) != 0) - pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); - pthread_mutex_init(mtx, &attr); - pthread_mutexattr_destroy(&attr); - return thrd_success; -} - -// 7.25.4.3 -static inline int mtx_lock(mtx_t *mtx) { - assert(mtx != NULL); - return (pthread_mutex_lock(mtx) == 0) ? thrd_success : thrd_error; -} - -static inline int mtx_trylock(mtx_t *mtx); - -static inline void thrd_yield(void); - -// 7.25.4.4 -static inline int mtx_timedlock(mtx_t *mtx, const xtime *xt) { - assert(mtx != NULL); - assert(xt != NULL); - - { -#ifdef EMULATED_THREADS_USE_NATIVE_TIMEDLOCK - struct timespec ts; - int rt; - ts.tv_sec = xt->sec; - ts.tv_nsec = xt->nsec; - rt = pthread_mutex_timedlock(mtx, &ts); - if (rt == 0) - return thrd_success; - return (rt == ETIMEDOUT) ? thrd_busy : thrd_error; -#else - time_t expire = time(NULL); - expire += xt->sec; - while (mtx_trylock(mtx) != thrd_success) { - time_t now = time(NULL); - if (expire < now) - return thrd_busy; - // busy loop! - thrd_yield(); - } - return thrd_success; -#endif - } -} - -// 7.25.4.5 -static inline int mtx_trylock(mtx_t *mtx) { - assert(mtx != NULL); - return (pthread_mutex_trylock(mtx) == 0) ? thrd_success : thrd_busy; -} - -// 7.25.4.6 -static inline int mtx_unlock(mtx_t *mtx) { - assert(mtx != NULL); - return (pthread_mutex_unlock(mtx) == 0) ? thrd_success : thrd_error; -} - -/*------------------- 7.25.5 Thread functions -------------------*/ -// 7.25.5.1 -static inline int thrd_create(thrd_t *thr, thrd_start_t func, void *arg) { - struct impl_thrd_param *pack; - assert(thr != NULL); - pack = (struct impl_thrd_param *)malloc(sizeof(struct impl_thrd_param)); - if (!pack) - return thrd_nomem; - pack->func = func; - pack->arg = arg; - if (pthread_create(thr, NULL, impl_thrd_routine, pack) != 0) { - free(pack); - return thrd_error; - } - return thrd_success; -} - -// 7.25.5.2 -static inline thrd_t thrd_current(void) { return pthread_self(); } - -// 7.25.5.3 -static inline int thrd_detach(thrd_t thr) { - return (pthread_detach(thr) == 0) ? thrd_success : thrd_error; -} - -// 7.25.5.4 -static inline int thrd_equal(thrd_t thr0, thrd_t thr1) { - return pthread_equal(thr0, thr1); -} - -// 7.25.5.5 -static inline void thrd_exit(int res) { pthread_exit((void *)(intptr_t)res); } - -// 7.25.5.6 -static inline int thrd_join(thrd_t thr, int *res) { - void *code; - if (pthread_join(thr, &code) != 0) - return thrd_error; - if (res) - *res = (int)(intptr_t)code; - return thrd_success; -} - -// 7.25.5.7 -static inline void thrd_sleep(const xtime *xt) { - struct timespec req; - assert(xt); - req.tv_sec = xt->sec; - req.tv_nsec = xt->nsec; - nanosleep(&req, NULL); -} - -// 7.25.5.8 -static inline void thrd_yield(void) { sched_yield(); } - -/*----------- 7.25.6 Thread-specific storage functions -----------*/ -// 7.25.6.1 -static inline int tss_create(tss_t *key, tss_dtor_t dtor) { - assert(key != NULL); - return (pthread_key_create(key, dtor) == 0) ? thrd_success : thrd_error; -} - -// 7.25.6.2 -static inline void tss_delete(tss_t key) { pthread_key_delete(key); } - -// 7.25.6.3 -static inline void *tss_get(tss_t key) { return pthread_getspecific(key); } - -// 7.25.6.4 -static inline int tss_set(tss_t key, void *val) { - return (pthread_setspecific(key, val) == 0) ? thrd_success : thrd_error; -} - -/*-------------------- 7.25.7 Time functions --------------------*/ -// 7.25.6.1 -static inline int xtime_get(xtime *xt, int base) { - if (!xt) - return 0; - if (base == TIME_UTC) { - xt->sec = time(NULL); - xt->nsec = 0; - return base; - } - return 0; -} diff --git a/app/src/main/cpp/virglrenderer/src/gallium/include/c11/threads_win32.h b/app/src/main/cpp/virglrenderer/src/gallium/include/c11/threads_win32.h deleted file mode 100644 index aae6b39bc..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/include/c11/threads_win32.h +++ /dev/null @@ -1,586 +0,0 @@ -/* - * C11 emulation library - * - * (C) Copyright yohhoy 2012. - * Distributed under the Boost Software License, Version 1.0. - * - * Permission is hereby granted, free of charge, to any person or organization - * obtaining a copy of the software and accompanying documentation covered by - * this license (the "Software") to use, reproduce, display, distribute, - * execute, and transmit the Software, and to prepare [[derivative work]]s of - * the Software, and to permit third-parties to whom the Software is furnished - * to do so, all subject to the following: - * - * The copyright notices in the Software and this entire statement, including - * the above license grant, this restriction and the following disclaimer, - * must be included in all copies of the Software, in whole or in part, and - * all derivative works of the Software, unless such copies or derivative - * works are solely in the form of machine-executable object code generated by - * a source language processor. - * - * 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, TITLE AND NON-INFRINGEMENT. IN NO EVENT - * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE - * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, - * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ -#ifndef assert -#include -#endif -#include -#include -#include // MSVCRT -#include - -/* -Configuration macro: - - EMULATED_THREADS_USE_NATIVE_CALL_ONCE - Use native WindowsAPI one-time initialization function. - (requires WinVista or later) - Otherwise emulate by mtx_trylock() + *busy loop* for WinXP. - - EMULATED_THREADS_USE_NATIVE_CV - Use native WindowsAPI condition variable object. - (requires WinVista or later) - Otherwise use emulated implementation for WinXP. - - EMULATED_THREADS_TSS_DTOR_SLOTNUM - Max registerable TSS dtor number. -*/ - -// XXX: Retain XP compatability -#if 0 -#if _WIN32_WINNT >= 0x0600 -// Prefer native WindowsAPI on newer environment. -#if !defined(__MINGW32__) -#define EMULATED_THREADS_USE_NATIVE_CALL_ONCE -#endif -#define EMULATED_THREADS_USE_NATIVE_CV -#endif -#endif -#define EMULATED_THREADS_TSS_DTOR_SLOTNUM 64 // see TLS_MINIMUM_AVAILABLE - -#include - -// check configuration -#if defined(EMULATED_THREADS_USE_NATIVE_CALL_ONCE) && (_WIN32_WINNT < 0x0600) -#error EMULATED_THREADS_USE_NATIVE_CALL_ONCE requires _WIN32_WINNT>=0x0600 -#endif - -#if defined(EMULATED_THREADS_USE_NATIVE_CV) && (_WIN32_WINNT < 0x0600) -#error EMULATED_THREADS_USE_NATIVE_CV requires _WIN32_WINNT>=0x0600 -#endif - -/*---------------------------- macros ----------------------------*/ -#ifdef EMULATED_THREADS_USE_NATIVE_CALL_ONCE -#define ONCE_FLAG_INIT INIT_ONCE_STATIC_INIT -#else -#define ONCE_FLAG_INIT {0} -#endif -#define TSS_DTOR_ITERATIONS 1 - -// FIXME: temporary non-standard hack to ease transition -#define _MTX_INITIALIZER_NP {(PCRITICAL_SECTION_DEBUG) - 1, -1, 0, 0, 0, 0} - -/*---------------------------- types ----------------------------*/ -typedef struct cnd_t { -#ifdef EMULATED_THREADS_USE_NATIVE_CV - CONDITION_VARIABLE condvar; -#else - int blocked; - int gone; - int to_unblock; - HANDLE sem_queue; - HANDLE sem_gate; - CRITICAL_SECTION monitor; -#endif -} cnd_t; - -typedef HANDLE thrd_t; - -typedef DWORD tss_t; - -typedef CRITICAL_SECTION mtx_t; - -#ifdef EMULATED_THREADS_USE_NATIVE_CALL_ONCE -typedef INIT_ONCE once_flag; -#else -typedef struct once_flag_t { - volatile LONG status; -} once_flag; -#endif - -static inline void *tss_get(tss_t key); -static inline void thrd_yield(void); -static inline int mtx_trylock(mtx_t *mtx); -static inline int mtx_lock(mtx_t *mtx); -static inline int mtx_unlock(mtx_t *mtx); - -/* -Implementation limits: - - Conditionally emulation for "Initialization functions" - (see EMULATED_THREADS_USE_NATIVE_CALL_ONCE macro) - - Emulated `mtx_timelock()' with mtx_trylock() + *busy loop* -*/ -static void impl_tss_dtor_invoke(void); // forward decl. - -struct impl_thrd_param { - thrd_start_t func; - void *arg; -}; - -static unsigned __stdcall impl_thrd_routine(void *p) { - struct impl_thrd_param pack; - int code; - memcpy(&pack, p, sizeof(struct impl_thrd_param)); - free(p); - code = pack.func(pack.arg); - impl_tss_dtor_invoke(); - return (unsigned)code; -} - -static DWORD impl_xtime2msec(const xtime *xt) { - return (DWORD)((xt->sec * 1000U) + (xt->nsec / 1000000L)); -} - -#ifdef EMULATED_THREADS_USE_NATIVE_CALL_ONCE -struct impl_call_once_param { - void (*func)(void); -}; -static BOOL CALLBACK impl_call_once_callback(PINIT_ONCE InitOnce, - PVOID Parameter, PVOID *Context) { - struct impl_call_once_param *param = (struct impl_call_once_param *)Parameter; - (param->func)(); - ((void)InitOnce); - ((void)Context); // suppress warning - return TRUE; -} -#endif // ifdef EMULATED_THREADS_USE_NATIVE_CALL_ONCE - -#ifndef EMULATED_THREADS_USE_NATIVE_CV -/* -Note: - The implementation of condition variable is ported from Boost.Interprocess - See http://www.boost.org/boost/interprocess/sync/windows/condition.hpp -*/ -static void impl_cond_do_signal(cnd_t *cond, int broadcast) { - int nsignal = 0; - - EnterCriticalSection(&cond->monitor); - if (cond->to_unblock != 0) { - if (cond->blocked == 0) { - LeaveCriticalSection(&cond->monitor); - return; - } - if (broadcast) { - cond->to_unblock += nsignal = cond->blocked; - cond->blocked = 0; - } else { - nsignal = 1; - cond->to_unblock++; - cond->blocked--; - } - } else if (cond->blocked > cond->gone) { - WaitForSingleObject(cond->sem_gate, INFINITE); - if (cond->gone != 0) { - cond->blocked -= cond->gone; - cond->gone = 0; - } - if (broadcast) { - nsignal = cond->to_unblock = cond->blocked; - cond->blocked = 0; - } else { - nsignal = cond->to_unblock = 1; - cond->blocked--; - } - } - LeaveCriticalSection(&cond->monitor); - - if (0 < nsignal) - ReleaseSemaphore(cond->sem_queue, nsignal, NULL); -} - -static int impl_cond_do_wait(cnd_t *cond, mtx_t *mtx, const xtime *xt) { - int nleft = 0; - int ngone = 0; - int timeout = 0; - DWORD w; - - WaitForSingleObject(cond->sem_gate, INFINITE); - cond->blocked++; - ReleaseSemaphore(cond->sem_gate, 1, NULL); - - mtx_unlock(mtx); - - w = WaitForSingleObject(cond->sem_queue, xt ? impl_xtime2msec(xt) : INFINITE); - timeout = (w == WAIT_TIMEOUT); - - EnterCriticalSection(&cond->monitor); - if ((nleft = cond->to_unblock) != 0) { - if (timeout) { - if (cond->blocked != 0) { - cond->blocked--; - } else { - cond->gone++; - } - } - if (--cond->to_unblock == 0) { - if (cond->blocked != 0) { - ReleaseSemaphore(cond->sem_gate, 1, NULL); - nleft = 0; - } else if ((ngone = cond->gone) != 0) { - cond->gone = 0; - } - } - } else if (++cond->gone == INT_MAX / 2) { - WaitForSingleObject(cond->sem_gate, INFINITE); - cond->blocked -= cond->gone; - ReleaseSemaphore(cond->sem_gate, 1, NULL); - cond->gone = 0; - } - LeaveCriticalSection(&cond->monitor); - - if (nleft == 1) { - while (ngone--) - WaitForSingleObject(cond->sem_queue, INFINITE); - ReleaseSemaphore(cond->sem_gate, 1, NULL); - } - - mtx_lock(mtx); - return timeout ? thrd_busy : thrd_success; -} -#endif // ifndef EMULATED_THREADS_USE_NATIVE_CV - -static struct impl_tss_dtor_entry { - tss_t key; - tss_dtor_t dtor; -} impl_tss_dtor_tbl[EMULATED_THREADS_TSS_DTOR_SLOTNUM]; - -static int impl_tss_dtor_register(tss_t key, tss_dtor_t dtor) { - int i; - for (i = 0; i < EMULATED_THREADS_TSS_DTOR_SLOTNUM; i++) { - if (!impl_tss_dtor_tbl[i].dtor) - break; - } - if (i == EMULATED_THREADS_TSS_DTOR_SLOTNUM) - return 1; - impl_tss_dtor_tbl[i].key = key; - impl_tss_dtor_tbl[i].dtor = dtor; - return 0; -} - -static void impl_tss_dtor_invoke() { - int i; - for (i = 0; i < EMULATED_THREADS_TSS_DTOR_SLOTNUM; i++) { - if (impl_tss_dtor_tbl[i].dtor) { - void *val = tss_get(impl_tss_dtor_tbl[i].key); - if (val) - (impl_tss_dtor_tbl[i].dtor)(val); - } - } -} - -/*--------------- 7.25.2 Initialization functions ---------------*/ -// 7.25.2.1 -static inline void call_once(once_flag *flag, void (*func)(void)) { - assert(flag && func); -#ifdef EMULATED_THREADS_USE_NATIVE_CALL_ONCE - { - struct impl_call_once_param param; - param.func = func; - InitOnceExecuteOnce(flag, impl_call_once_callback, (PVOID)¶m, NULL); - } -#else - if (InterlockedCompareExchange(&flag->status, 1, 0) == 0) { - (func)(); - InterlockedExchange(&flag->status, 2); - } else { - while (flag->status == 1) { - // busy loop! - thrd_yield(); - } - } -#endif -} - -/*------------- 7.25.3 Condition variable functions -------------*/ -// 7.25.3.1 -static inline int cnd_broadcast(cnd_t *cond) { - if (!cond) - return thrd_error; -#ifdef EMULATED_THREADS_USE_NATIVE_CV - WakeAllConditionVariable(&cond->condvar); -#else - impl_cond_do_signal(cond, 1); -#endif - return thrd_success; -} - -// 7.25.3.2 -static inline void cnd_destroy(cnd_t *cond) { - assert(cond); -#ifdef EMULATED_THREADS_USE_NATIVE_CV - // do nothing -#else - CloseHandle(cond->sem_queue); - CloseHandle(cond->sem_gate); - DeleteCriticalSection(&cond->monitor); -#endif -} - -// 7.25.3.3 -static inline int cnd_init(cnd_t *cond) { - if (!cond) - return thrd_error; -#ifdef EMULATED_THREADS_USE_NATIVE_CV - InitializeConditionVariable(&cond->condvar); -#else - cond->blocked = 0; - cond->gone = 0; - cond->to_unblock = 0; - cond->sem_queue = CreateSemaphore(NULL, 0, LONG_MAX, NULL); - cond->sem_gate = CreateSemaphore(NULL, 1, 1, NULL); - InitializeCriticalSection(&cond->monitor); -#endif - return thrd_success; -} - -// 7.25.3.4 -static inline int cnd_signal(cnd_t *cond) { - if (!cond) - return thrd_error; -#ifdef EMULATED_THREADS_USE_NATIVE_CV - WakeConditionVariable(&cond->condvar); -#else - impl_cond_do_signal(cond, 0); -#endif - return thrd_success; -} - -// 7.25.3.5 -static inline int cnd_timedwait(cnd_t *cond, mtx_t *mtx, const xtime *xt) { - if (!cond || !mtx || !xt) - return thrd_error; -#ifdef EMULATED_THREADS_USE_NATIVE_CV - if (SleepConditionVariableCS(&cond->condvar, mtx, impl_xtime2msec(xt))) - return thrd_success; - return (GetLastError() == ERROR_TIMEOUT) ? thrd_busy : thrd_error; -#else - return impl_cond_do_wait(cond, mtx, xt); -#endif -} - -// 7.25.3.6 -static inline int cnd_wait(cnd_t *cond, mtx_t *mtx) { - if (!cond || !mtx) - return thrd_error; -#ifdef EMULATED_THREADS_USE_NATIVE_CV - SleepConditionVariableCS(&cond->condvar, mtx, INFINITE); -#else - impl_cond_do_wait(cond, mtx, NULL); -#endif - return thrd_success; -} - -/*-------------------- 7.25.4 Mutex functions --------------------*/ -// 7.25.4.1 -static inline void mtx_destroy(mtx_t *mtx) { - assert(mtx); - DeleteCriticalSection(mtx); -} - -// 7.25.4.2 -static inline int mtx_init(mtx_t *mtx, int type) { - if (!mtx) - return thrd_error; - if (type != mtx_plain && type != mtx_timed && type != mtx_try && - type != (mtx_plain | mtx_recursive) && - type != (mtx_timed | mtx_recursive) && type != (mtx_try | mtx_recursive)) - return thrd_error; - InitializeCriticalSection(mtx); - return thrd_success; -} - -// 7.25.4.3 -static inline int mtx_lock(mtx_t *mtx) { - if (!mtx) - return thrd_error; - EnterCriticalSection(mtx); - return thrd_success; -} - -// 7.25.4.4 -static inline int mtx_timedlock(mtx_t *mtx, const xtime *xt) { - time_t expire, now; - if (!mtx || !xt) - return thrd_error; - expire = time(NULL); - expire += xt->sec; - while (mtx_trylock(mtx) != thrd_success) { - now = time(NULL); - if (expire < now) - return thrd_busy; - // busy loop! - thrd_yield(); - } - return thrd_success; -} - -// 7.25.4.5 -static inline int mtx_trylock(mtx_t *mtx) { - if (!mtx) - return thrd_error; - return TryEnterCriticalSection(mtx) ? thrd_success : thrd_busy; -} - -// 7.25.4.6 -static inline int mtx_unlock(mtx_t *mtx) { - if (!mtx) - return thrd_error; - LeaveCriticalSection(mtx); - return thrd_success; -} - -/*------------------- 7.25.5 Thread functions -------------------*/ -// 7.25.5.1 -static inline int thrd_create(thrd_t *thr, thrd_start_t func, void *arg) { - struct impl_thrd_param *pack; - uintptr_t handle; - if (!thr) - return thrd_error; - pack = (struct impl_thrd_param *)malloc(sizeof(struct impl_thrd_param)); - if (!pack) - return thrd_nomem; - pack->func = func; - pack->arg = arg; - handle = _beginthreadex(NULL, 0, impl_thrd_routine, pack, 0, NULL); - if (handle == 0) { - if (errno == EAGAIN || errno == EACCES) - return thrd_nomem; - return thrd_error; - } - *thr = (thrd_t)handle; - return thrd_success; -} - -#if 0 -// 7.25.5.2 -static inline thrd_t -thrd_current(void) -{ - HANDLE hCurrentThread; - BOOL bRet; - - /* GetCurrentThread() returns a pseudo-handle, which is useless. We need - * to call DuplicateHandle to get a real handle. However the handle value - * will not match the one returned by thread_create. - * - * Other potential solutions would be: - * - define thrd_t as a thread Ids, but this would mean we'd need to OpenThread for many operations - * - use malloc'ed memory for thrd_t. This would imply using TLS for current thread. - * - * Neither is particularly nice. - * - * Life would be much easier if C11 threads had different abstractions for - * threads and thread IDs, just like C++11 threads does... - */ - - bRet = DuplicateHandle(GetCurrentProcess(), // source process (pseudo) handle - GetCurrentThread(), // source (pseudo) handle - GetCurrentProcess(), // target process - &hCurrentThread, // target handle - 0, - FALSE, - DUPLICATE_SAME_ACCESS); - assert(bRet); - if (!bRet) { - hCurrentThread = GetCurrentThread(); - } - return hCurrentThread; -} -#endif - -// 7.25.5.3 -static inline int thrd_detach(thrd_t thr) { - CloseHandle(thr); - return thrd_success; -} - -// 7.25.5.4 -static inline int thrd_equal(thrd_t thr0, thrd_t thr1) { - return GetThreadId(thr0) == GetThreadId(thr1); -} - -// 7.25.5.5 -static inline void thrd_exit(int res) { - impl_tss_dtor_invoke(); - _endthreadex((unsigned)res); -} - -// 7.25.5.6 -static inline int thrd_join(thrd_t thr, int *res) { - DWORD w, code; - w = WaitForSingleObject(thr, INFINITE); - if (w != WAIT_OBJECT_0) - return thrd_error; - if (res) { - if (!GetExitCodeThread(thr, &code)) { - CloseHandle(thr); - return thrd_error; - } - *res = (int)code; - } - CloseHandle(thr); - return thrd_success; -} - -// 7.25.5.7 -static inline void thrd_sleep(const xtime *xt) { - assert(xt); - Sleep(impl_xtime2msec(xt)); -} - -// 7.25.5.8 -static inline void thrd_yield(void) { SwitchToThread(); } - -/*----------- 7.25.6 Thread-specific storage functions -----------*/ -// 7.25.6.1 -static inline int tss_create(tss_t *key, tss_dtor_t dtor) { - if (!key) - return thrd_error; - *key = TlsAlloc(); - if (dtor) { - if (impl_tss_dtor_register(*key, dtor)) { - TlsFree(*key); - return thrd_error; - } - } - return (*key != 0xFFFFFFFF) ? thrd_success : thrd_error; -} - -// 7.25.6.2 -static inline void tss_delete(tss_t key) { TlsFree(key); } - -// 7.25.6.3 -static inline void *tss_get(tss_t key) { return TlsGetValue(key); } - -// 7.25.6.4 -static inline int tss_set(tss_t key, void *val) { - return TlsSetValue(key, val) ? thrd_success : thrd_error; -} - -/*-------------------- 7.25.7 Time functions --------------------*/ -// 7.25.6.1 -static inline int xtime_get(xtime *xt, int base) { - if (!xt) - return 0; - if (base == TIME_UTC) { - xt->sec = time(NULL); - xt->nsec = 0; - return base; - } - return 0; -} diff --git a/app/src/main/cpp/virglrenderer/src/gallium/include/c99_compat.h b/app/src/main/cpp/virglrenderer/src/gallium/include/c99_compat.h deleted file mode 100644 index 05afdf7d4..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/include/c99_compat.h +++ /dev/null @@ -1,133 +0,0 @@ -/************************************************************************** - * - * Copyright 2007-2013 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -#include "no_extern_c.h" - -#ifndef _C99_COMPAT_H_ -#define _C99_COMPAT_H_ - -/* - * MSVC hacks. - */ -#if defined(_MSC_VER) - -#if _MSC_VER < 1500 -#error "Microsoft Visual Studio 2008 or higher required" -#endif - -/* - * Visual Studio 2012 will complain if we define the `inline` keyword, but - * actually it only supports the keyword on C++. - * - * To avoid this the _ALLOW_KEYWORD_MACROS must be set. - */ -#if (_MSC_VER >= 1700) && !defined(_ALLOW_KEYWORD_MACROS) -#define _ALLOW_KEYWORD_MACROS -#endif - -/* - * XXX: MSVC has a `__restrict` keyword, but it also has a - * `__declspec(restrict)` modifier, so it is impossible to define a - * `restrict` macro without interfering with the latter. Furthermore the - * MSVC standard library uses __declspec(restrict) under the _CRTRESTRICT - * macro. For now resolve this issue by redefining _CRTRESTRICT, but going - * forward we should probably should stop using restrict, especially - * considering that our code does not obbey strict aliasing rules any way. - */ -#include -#undef _CRTRESTRICT -#define _CRTRESTRICT -#endif - -/* - * C99 inline keyword - */ -#ifndef inline -#ifdef __cplusplus -/* C++ supports inline keyword */ -#elif defined(__GNUC__) -#define inline __inline__ -#elif defined(_MSC_VER) -#define inline __inline -#elif defined(__ICL) -#define inline __inline -#elif defined(__INTEL_COMPILER) -/* Intel compiler supports inline keyword */ -#elif defined(__WATCOMC__) && (__WATCOMC__ >= 1100) -#define inline __inline -#elif (__STDC_VERSION__ >= 199901L) -/* C99 supports inline keyword */ -#else -#define inline -#endif -#endif - -/* - * C99 restrict keyword - * - * See also: - * - - * http://cellperformance.beyond3d.com/articles/2006/05/demystifying-the-restrict-keyword.html - */ -#ifndef restrict -#if (__STDC_VERSION__ >= 199901L) -/* C99 */ -#elif defined(__GNUC__) -#define restrict __restrict__ -#elif defined(_MSC_VER) -#define restrict __restrict -#else -#define restrict /* */ -#endif -#endif - -/* - * C99 __func__ macro - */ -#ifndef __func__ -#if (__STDC_VERSION__ >= 199901L) -/* C99 */ -#elif defined(__GNUC__) -#define __func__ __FUNCTION__ -#elif defined(_MSC_VER) -#define __func__ __FUNCTION__ -#else -#define __func__ "" -#endif -#endif - -/* Simple test case for debugging */ -#if 0 -static inline const char * -test_c99_compat_h(const void * restrict a, - const void * restrict b) -{ - return __func__; -} -#endif - -#endif /* _C99_COMPAT_H_ */ diff --git a/app/src/main/cpp/virglrenderer/src/gallium/include/no_extern_c.h b/app/src/main/cpp/virglrenderer/src/gallium/include/no_extern_c.h deleted file mode 100644 index 8d83ce40b..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/include/no_extern_c.h +++ /dev/null @@ -1,47 +0,0 @@ -/************************************************************************** - * - * Copyright 2014 VMware, Inc. - * All Rights Reserved. - * - * 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. - * - **************************************************************************/ - -/* - * Including system's headers inside `extern "C" { ... }` is not safe, as system - * headers may have C++ code in them, and C++ code inside extern "C" - * leads to syntatically incorrect code. - * - * This is because putting code inside extern "C" won't make __cplusplus define - * go away, that is, the system header being included thinks is free to use C++ - * as it sees fits. - * - * Including non-system headers inside extern "C" is not safe either, because - * non-system headers end up including system headers, hence fall in the above - * case too. - * - * Conclusion, includes inside extern "C" is simply not portable. - * - * - * This header helps surface these issues. - */ - -#ifdef __cplusplus -template class _IncludeInsideExternCNotPortable; -#endif diff --git a/app/src/main/cpp/virglrenderer/src/gallium/include/pipe/p_compiler.h b/app/src/main/cpp/virglrenderer/src/gallium/include/pipe/p_compiler.h deleted file mode 100644 index e8791ed7a..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/include/pipe/p_compiler.h +++ /dev/null @@ -1,235 +0,0 @@ -/************************************************************************** - * - * Copyright 2007-2008 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -#ifndef P_COMPILER_H -#define P_COMPILER_H - -#include "p_config.h" - -#include -#include -#include -#include -#include - -#if defined(_WIN32) && !defined(__WIN32__) -#define __WIN32__ -#endif - -#if defined(_MSC_VER) - -/* Avoid 'expression is always true' warning */ -#pragma warning(disable : 4296) - -#endif /* _MSC_VER */ - -/* - * Alternative stdint.h and stdbool.h headers are supplied in include/c99 for - * systems that lack it. - */ -#ifndef __STDC_LIMIT_MACROS -#define __STDC_LIMIT_MACROS 1 -#endif -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#if !defined(__HAIKU__) && !defined(__USE_MISC) -#if !defined(PIPE_OS_ANDROID) -typedef unsigned int uint; -#endif -typedef unsigned short ushort; -#endif -typedef unsigned char ubyte; - -typedef unsigned char boolean; -#ifndef TRUE -#define TRUE true -#endif -#ifndef FALSE -#define FALSE false -#endif - -#ifndef va_copy -#ifdef __va_copy -#define va_copy(dest, src) __va_copy((dest), (src)) -#else -#define va_copy(dest, src) (dest) = (src) -#endif -#endif - -/* Function visibility */ -#ifndef PUBLIC -#if defined(__GNUC__) || (defined(__SUNPRO_C) && (__SUNPRO_C >= 0x590)) -#define PUBLIC __attribute__((visibility("default"))) -#elif defined(_MSC_VER) -#define PUBLIC __declspec(dllexport) -#else -#define PUBLIC -#endif -#endif - -/* XXX: Use standard `__func__` instead */ -#ifndef __FUNCTION__ -#define __FUNCTION__ __func__ -#endif - -/* This should match linux gcc cdecl semantics everywhere, so that we - * just codegen one calling convention on all platforms. - */ -#ifdef _MSC_VER -#define PIPE_CDECL __cdecl -#else -#define PIPE_CDECL -#endif - -#if defined(__GNUC__) -#define PIPE_DEPRECATED __attribute__((__deprecated__)) -#else -#define PIPE_DEPRECATED -#endif - -/* Macros for data alignment. */ -#if defined(__GNUC__) || (defined(__SUNPRO_C) && (__SUNPRO_C >= 0x590)) || \ - defined(__SUNPRO_CC) - -/* See http://gcc.gnu.org/onlinedocs/gcc-4.4.2/gcc/Type-Attributes.html */ -#define PIPE_ALIGN_TYPE(_alignment, _type) \ - _type __attribute__((aligned(_alignment))) - -/* See http://gcc.gnu.org/onlinedocs/gcc-4.4.2/gcc/Variable-Attributes.html */ -#define PIPE_ALIGN_VAR(_alignment) __attribute__((aligned(_alignment))) - -#if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 1)) && \ - !defined(PIPE_ARCH_X86_64) -#define PIPE_ALIGN_STACK __attribute__((force_align_arg_pointer)) -#else -#define PIPE_ALIGN_STACK -#endif - -#elif defined(_MSC_VER) - -/* See http://msdn.microsoft.com/en-us/library/83ythb65.aspx */ -#define PIPE_ALIGN_TYPE(_alignment, _type) __declspec(align(_alignment)) _type -#define PIPE_ALIGN_VAR(_alignment) __declspec(align(_alignment)) - -#define PIPE_ALIGN_STACK - -#elif defined(SWIG) - -#define PIPE_ALIGN_TYPE(_alignment, _type) _type -#define PIPE_ALIGN_VAR(_alignment) - -#define PIPE_ALIGN_STACK - -#else - -#error "Unsupported compiler" - -#endif - -#if defined(__GNUC__) - -#define PIPE_READ_WRITE_BARRIER() __asm__("" ::: "memory") - -#elif defined(_MSC_VER) - -void _ReadWriteBarrier(void); -#pragma intrinsic(_ReadWriteBarrier) -#define PIPE_READ_WRITE_BARRIER() _ReadWriteBarrier() - -#elif defined(__SUNPRO_C) || defined(__SUNPRO_CC) - -#define PIPE_READ_WRITE_BARRIER() __machine_rw_barrier() - -#else - -#warning "Unsupported compiler" -#define PIPE_READ_WRITE_BARRIER() /* */ - -#endif - -/* You should use these macros to mark if blocks where the if condition - * is either likely to be true, or unlikely to be true. - * - * This will inform human readers of this fact, and will also inform - * the compiler, who will in turn inform the CPU. - * - * CPUs often start executing code inside the if or the else blocks - * without knowing whether the condition is true or not, and will have - * to throw the work away if they find out later they executed the - * wrong part of the if. - * - * If these macros are used, the CPU is more likely to correctly predict - * the right path, and will avoid speculatively executing the wrong branch, - * thus not throwing away work, resulting in better performance. - * - * In light of this, it is also a good idea to mark as "likely" a path - * which is not necessarily always more likely, but that will benefit much - * more from performance improvements since it is already much faster than - * the other path, or viceversa with "unlikely". - * - * Example usage: - * if(unlikely(do_we_need_a_software_fallback())) - * do_software_fallback(); - * else - * render_with_gpu(); - * - * The macros follow the Linux kernel convention, and more examples can - * be found there. - * - * Note that profile guided optimization can offer better results, but - * needs an appropriate coverage suite and does not inform human readers. - */ -#ifndef likely -#if defined(__GNUC__) -#define likely(x) __builtin_expect(!!(x), 1) -#define unlikely(x) __builtin_expect(!!(x), 0) -#else -#define likely(x) (x) -#define unlikely(x) (x) -#endif -#endif - -/** - * Static (compile-time) assertion. - * Basically, use COND to dimension an array. If COND is false/zero the - * array size will be -1 and we'll get a compilation error. - */ -#define STATIC_ASSERT(COND) \ - do { \ - (void)sizeof(char[1 - 2 * !(COND)]); \ - } while (0) - -#if defined(__cplusplus) -} -#endif - -#endif /* P_COMPILER_H */ diff --git a/app/src/main/cpp/virglrenderer/src/gallium/include/pipe/p_config.h b/app/src/main/cpp/virglrenderer/src/gallium/include/pipe/p_config.h deleted file mode 100644 index e3f37e2fd..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/include/pipe/p_config.h +++ /dev/null @@ -1,262 +0,0 @@ -/************************************************************************** - * - * Copyright 2008 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -/** - * @file - * Gallium configuration defines. - * - * This header file sets several defines based on the compiler, processor - * architecture, and operating system being used. These defines should be used - * throughout the code to facilitate porting to new platforms. It is likely that - * this file is auto-generated by an autoconf-like tool at some point, as some - * things cannot be determined by pre-defined environment alone. - * - * See also: - * - http://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html - * - echo | gcc -dM -E - | sort - * - http://msdn.microsoft.com/en-us/library/b0084kay.aspx - * - * @author José Fonseca - */ - -#ifndef P_CONFIG_H_ -#define P_CONFIG_H_ - -#include -/* - * Compiler - */ - -#if defined(__GNUC__) -#define PIPE_CC_GCC -#define PIPE_CC_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) -#endif - -/* - * Meaning of _MSC_VER value: - * - 1800: Visual Studio 2013 - * - 1700: Visual Studio 2012 - * - 1600: Visual Studio 2010 - * - 1500: Visual Studio 2008 - * - 1400: Visual C++ 2005 - * - 1310: Visual C++ .NET 2003 - * - 1300: Visual C++ .NET 2002 - * - * __MSC__ seems to be an old macro -- it is not pre-defined on recent MSVC - * versions. - */ -#if defined(_MSC_VER) || defined(__MSC__) -#define PIPE_CC_MSVC -#endif - -#if defined(__ICL) -#define PIPE_CC_ICL -#endif - -#if defined(__SUNPRO_C) || defined(__SUNPRO_CC) -#define PIPE_CC_SUNPRO -#endif - -/* - * Processor architecture - */ - -#if defined(__i386__) /* gcc */ || defined(_M_IX86) /* msvc */ || \ - defined(_X86_) || defined(__386__) || defined(i386) || \ - defined(__i386) /* Sun cc */ -#define PIPE_ARCH_X86 -#endif - -#if defined(__x86_64__) /* gcc */ || defined(_M_X64) /* msvc */ || \ - defined(_M_AMD64) /* msvc */ || defined(__x86_64) /* Sun cc */ -#define PIPE_ARCH_X86_64 -#endif - -#if defined(PIPE_ARCH_X86) || defined(PIPE_ARCH_X86_64) -#if defined(PIPE_CC_GCC) && !defined(__SSE2__) -/* #warning SSE2 support requires -msse -msse2 compiler options */ -#else -#define PIPE_ARCH_SSE -#endif -#if defined(PIPE_CC_GCC) && !defined(__SSSE3__) -/* #warning SSE3 support requires -msse3 compiler options */ -#else -#define PIPE_ARCH_SSSE3 -#endif -#endif - -#if defined(__ppc__) || defined(__ppc64__) || defined(__PPC__) -#define PIPE_ARCH_PPC -#if defined(__ppc64__) || defined(__PPC64__) -#define PIPE_ARCH_PPC_64 -#endif -#endif - -#if defined(__s390x__) -#define PIPE_ARCH_S390 -#endif - -#if defined(__arm__) -#define PIPE_ARCH_ARM -#endif - -#if defined(__aarch64__) -#define PIPE_ARCH_AARCH64 -#endif - -/* - * Endian detection. - */ - -#ifdef __GLIBC__ -#include - -#if __BYTE_ORDER == __LITTLE_ENDIAN -#define PIPE_ARCH_LITTLE_ENDIAN -#elif __BYTE_ORDER == __BIG_ENDIAN -#define PIPE_ARCH_BIG_ENDIAN -#endif - -#elif defined(__APPLE__) -#include - -#if __DARWIN_BYTE_ORDER == __DARWIN_LITTLE_ENDIAN -#define PIPE_ARCH_LITTLE_ENDIAN -#elif __DARWIN_BYTE_ORDER == __DARWIN_BIG_ENDIAN -#define PIPE_ARCH_BIG_ENDIAN -#endif - -#elif defined(__sun) -#include - -#if defined(_LITTLE_ENDIAN) -#define PIPE_ARCH_LITTLE_ENDIAN -#elif defined(_BIG_ENDIAN) -#define PIPE_ARCH_BIG_ENDIAN -#endif - -#else - -#if defined(PIPE_ARCH_X86) || defined(PIPE_ARCH_X86_64) || \ - defined(PIPE_ARCH_ARM) || defined(PIPE_ARCH_AARCH64) -#define PIPE_ARCH_LITTLE_ENDIAN -#elif defined(PIPE_ARCH_PPC) || defined(PIPE_ARCH_PPC_64) || \ - defined(PIPE_ARCH_S390) -#define PIPE_ARCH_BIG_ENDIAN -#endif - -#endif - -#if !defined(PIPE_ARCH_LITTLE_ENDIAN) && !defined(PIPE_ARCH_BIG_ENDIAN) -#error Unknown Endianness -#endif - -/* - * Auto-detect the operating system family. - * - * See subsystem below for a more fine-grained distinction. - */ - -#if defined(__linux__) -#define PIPE_OS_LINUX -#define PIPE_OS_UNIX -#endif - -/* - * Android defines __linux__ so PIPE_OS_LINUX and PIPE_OS_UNIX will also be - * defined. - */ -#if defined(ANDROID) -#define PIPE_OS_ANDROID -#endif - -#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) -#define PIPE_OS_FREEBSD -#define PIPE_OS_BSD -#define PIPE_OS_UNIX -#endif - -#if defined(__OpenBSD__) -#define PIPE_OS_OPENBSD -#define PIPE_OS_BSD -#define PIPE_OS_UNIX -#endif - -#if defined(__NetBSD__) -#define PIPE_OS_NETBSD -#define PIPE_OS_BSD -#define PIPE_OS_UNIX -#endif - -#if defined(__GNU__) -#define PIPE_OS_HURD -#define PIPE_OS_UNIX -#endif - -#if defined(__sun) -#define PIPE_OS_SOLARIS -#define PIPE_OS_UNIX -#endif - -#if defined(__APPLE__) -#define PIPE_OS_APPLE -#define PIPE_OS_UNIX -#endif - -#if defined(_WIN32) || defined(WIN32) -#define PIPE_OS_WINDOWS -#endif - -#if defined(__HAIKU__) -#define PIPE_OS_HAIKU -#define PIPE_OS_UNIX -#endif - -#if defined(__CYGWIN__) -#define PIPE_OS_CYGWIN -#define PIPE_OS_UNIX -#endif - -/* - * Try to auto-detect the subsystem. - * - * NOTE: There is no way to auto-detect most of these. - */ - -#if defined(PIPE_OS_LINUX) || defined(PIPE_OS_BSD) || defined(PIPE_OS_SOLARIS) -#define PIPE_SUBSYSTEM_DRI -#endif /* PIPE_OS_LINUX || PIPE_OS_BSD || PIPE_OS_SOLARIS */ - -#if defined(PIPE_OS_WINDOWS) -#if defined(PIPE_SUBSYSTEM_WINDOWS_USER) -/* Windows User-space Library */ -#else -#define PIPE_SUBSYSTEM_WINDOWS_USER -#endif -#endif /* PIPE_OS_WINDOWS */ - -#endif /* P_CONFIG_H_ */ diff --git a/app/src/main/cpp/virglrenderer/src/gallium/include/pipe/p_context.h b/app/src/main/cpp/virglrenderer/src/gallium/include/pipe/p_context.h deleted file mode 100644 index 982219f7a..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/include/pipe/p_context.h +++ /dev/null @@ -1,509 +0,0 @@ -/************************************************************************** - * - * Copyright 2007 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -#ifndef PIPE_CONTEXT_H -#define PIPE_CONTEXT_H - -#include "p_compiler.h" -#include "p_defines.h" -#include "p_format.h" -#include "p_video_enums.h" - -#ifdef __cplusplus -extern "C" { -#endif - -struct pipe_blend_color; -struct pipe_blend_state; -struct pipe_blit_info; -struct pipe_box; -struct pipe_clip_state; -struct pipe_constant_buffer; -struct pipe_depth_stencil_alpha_state; -struct pipe_draw_info; -struct pipe_fence_handle; -struct pipe_framebuffer_state; -struct pipe_index_buffer; -struct pipe_query; -struct pipe_poly_stipple; -struct pipe_rasterizer_state; -struct pipe_resolve_info; -struct pipe_resource; -struct pipe_sampler_state; -struct pipe_sampler_view; -struct pipe_scissor_state; -struct pipe_shader_state; -struct pipe_stencil_ref; -struct pipe_stream_output_target; -struct pipe_surface; -struct pipe_transfer; -struct pipe_vertex_buffer; -struct pipe_vertex_element; -struct pipe_video_buffer; -struct pipe_video_codec; -struct pipe_viewport_state; -struct pipe_compute_state; -union pipe_color_union; -union pipe_query_result; - -/** - * Gallium rendering context. Basically: - * - state setting functions - * - VBO drawing functions - * - surface functions - */ -struct pipe_context { - struct pipe_screen *screen; - - void *priv; /**< context private data (for DRI for example) */ - void *draw; /**< private, for draw module (temporary?) */ - - void (*destroy)(struct pipe_context *); - - /** - * VBO drawing - */ - /*@{*/ - void (*draw_vbo)(struct pipe_context *pipe, - const struct pipe_draw_info *info); - /*@}*/ - - /** - * Predicate subsequent rendering on occlusion query result - * \param query the query predicate, or NULL if no predicate - * \param condition whether to skip on FALSE or TRUE query results - * \param mode one of PIPE_RENDER_COND_x - */ - void (*render_condition)(struct pipe_context *pipe, struct pipe_query *query, - boolean condition, uint mode); - - /** - * Query objects - */ - /*@{*/ - struct pipe_query *(*create_query)(struct pipe_context *pipe, - unsigned query_type); - - void (*destroy_query)(struct pipe_context *pipe, struct pipe_query *q); - - void (*begin_query)(struct pipe_context *pipe, struct pipe_query *q); - void (*end_query)(struct pipe_context *pipe, struct pipe_query *q); - - /** - * Get results of a query. - * \param wait if true, this query will block until the result is ready - * \return TRUE if results are ready, FALSE otherwise - */ - boolean (*get_query_result)(struct pipe_context *pipe, struct pipe_query *q, - boolean wait, union pipe_query_result *result); - /*@}*/ - - /** - * State functions (create/bind/destroy state objects) - */ - /*@{*/ - void *(*create_blend_state)(struct pipe_context *, - const struct pipe_blend_state *); - void (*bind_blend_state)(struct pipe_context *, void *); - void (*delete_blend_state)(struct pipe_context *, void *); - - void *(*create_sampler_state)(struct pipe_context *, - const struct pipe_sampler_state *); - void (*bind_sampler_states)(struct pipe_context *, unsigned shader, - unsigned start_slot, unsigned num_samplers, - void **samplers); - void (*delete_sampler_state)(struct pipe_context *, void *); - - void *(*create_rasterizer_state)(struct pipe_context *, - const struct pipe_rasterizer_state *); - void (*bind_rasterizer_state)(struct pipe_context *, void *); - void (*delete_rasterizer_state)(struct pipe_context *, void *); - - void *(*create_depth_stencil_alpha_state)( - struct pipe_context *, const struct pipe_depth_stencil_alpha_state *); - void (*bind_depth_stencil_alpha_state)(struct pipe_context *, void *); - void (*delete_depth_stencil_alpha_state)(struct pipe_context *, void *); - - void *(*create_fs_state)(struct pipe_context *, - const struct pipe_shader_state *); - void (*bind_fs_state)(struct pipe_context *, void *); - void (*delete_fs_state)(struct pipe_context *, void *); - - void *(*create_vs_state)(struct pipe_context *, - const struct pipe_shader_state *); - void (*bind_vs_state)(struct pipe_context *, void *); - void (*delete_vs_state)(struct pipe_context *, void *); - - void *(*create_gs_state)(struct pipe_context *, - const struct pipe_shader_state *); - void (*bind_gs_state)(struct pipe_context *, void *); - void (*delete_gs_state)(struct pipe_context *, void *); - - void *(*create_vertex_elements_state)(struct pipe_context *, - unsigned num_elements, - const struct pipe_vertex_element *); - void (*bind_vertex_elements_state)(struct pipe_context *, void *); - void (*delete_vertex_elements_state)(struct pipe_context *, void *); - - /*@}*/ - - /** - * Parameter-like state (or properties) - */ - /*@{*/ - void (*set_blend_color)(struct pipe_context *, - const struct pipe_blend_color *); - - void (*set_stencil_ref)(struct pipe_context *, - const struct pipe_stencil_ref *); - - void (*set_sample_mask)(struct pipe_context *, unsigned sample_mask); - - void (*set_clip_state)(struct pipe_context *, const struct pipe_clip_state *); - - void (*set_constant_buffer)(struct pipe_context *, uint shader, uint index, - struct pipe_constant_buffer *buf); - - void (*set_framebuffer_state)(struct pipe_context *, - const struct pipe_framebuffer_state *); - - void (*set_polygon_stipple)(struct pipe_context *, - const struct pipe_poly_stipple *); - - void (*set_scissor_states)(struct pipe_context *, unsigned start_slot, - unsigned num_scissors, - const struct pipe_scissor_state *); - - void (*set_viewport_states)(struct pipe_context *, unsigned start_slot, - unsigned num_viewports, - const struct pipe_viewport_state *); - - void (*set_sampler_views)(struct pipe_context *, unsigned shader, - unsigned start_slot, unsigned num_views, - struct pipe_sampler_view **); - - /** - * Bind an array of shader resources that will be used by the - * graphics pipeline. Any resources that were previously bound to - * the specified range will be unbound after this call. - * - * \param start first resource to bind. - * \param count number of consecutive resources to bind. - * \param resources array of pointers to the resources to bind, it - * should contain at least \a count elements - * unless it's NULL, in which case no new - * resources will be bound. - */ - void (*set_shader_resources)(struct pipe_context *, unsigned start, - unsigned count, struct pipe_surface **resources); - - void (*set_vertex_buffers)(struct pipe_context *, unsigned start_slot, - unsigned num_buffers, - const struct pipe_vertex_buffer *); - - void (*set_index_buffer)(struct pipe_context *pipe, - const struct pipe_index_buffer *); - - /*@}*/ - - /** - * Stream output functions. - */ - /*@{*/ - - struct pipe_stream_output_target *(*create_stream_output_target)( - struct pipe_context *, struct pipe_resource *, unsigned buffer_offset, - unsigned buffer_size); - - void (*stream_output_target_destroy)(struct pipe_context *, - struct pipe_stream_output_target *); - - void (*set_stream_output_targets)(struct pipe_context *, unsigned num_targets, - struct pipe_stream_output_target **targets, - unsigned append_bitmask); - - /*@}*/ - - /** - * Resource functions for blit-like functionality - * - * If a driver supports multisampling, blit must implement color resolve. - */ - /*@{*/ - - /** - * Copy a block of pixels from one resource to another. - * The resource must be of the same format. - * Resources with nr_samples > 1 are not allowed. - */ - void (*resource_copy_region)(struct pipe_context *pipe, - struct pipe_resource *dst, unsigned dst_level, - unsigned dstx, unsigned dsty, unsigned dstz, - struct pipe_resource *src, unsigned src_level, - const struct pipe_box *src_box); - - /* Optimal hardware path for blitting pixels. - * Scaling, format conversion, up- and downsampling (resolve) are allowed. - */ - void (*blit)(struct pipe_context *pipe, const struct pipe_blit_info *info); - - /*@}*/ - - /** - * Clear the specified set of currently bound buffers to specified values. - * The entire buffers are cleared (no scissor, no colormask, etc). - * - * \param buffers bitfield of PIPE_CLEAR_* values. - * \param color pointer to a union of fiu array for each of r, g, b, a. - * \param depth depth clear value in [0,1]. - * \param stencil stencil clear value - */ - void (*clear)(struct pipe_context *pipe, unsigned buffers, - const union pipe_color_union *color, double depth, - unsigned stencil); - - /** - * Clear a color rendertarget surface. - * \param color pointer to an union of fiu array for each of r, g, b, a. - */ - void (*clear_render_target)(struct pipe_context *pipe, - struct pipe_surface *dst, - const union pipe_color_union *color, - unsigned dstx, unsigned dsty, unsigned width, - unsigned height); - - /** - * Clear a depth-stencil surface. - * \param clear_flags bitfield of PIPE_CLEAR_DEPTH/STENCIL values. - * \param depth depth clear value in [0,1]. - * \param stencil stencil clear value - */ - void (*clear_depth_stencil)(struct pipe_context *pipe, - struct pipe_surface *dst, unsigned clear_flags, - double depth, unsigned stencil, unsigned dstx, - unsigned dsty, unsigned width, unsigned height); - - /** Flush draw commands - * - * \param flags bitfield of enum pipe_flush_flags values. - */ - void (*flush)(struct pipe_context *pipe, struct pipe_fence_handle **fence, - unsigned flags); - - /** - * Create a view on a texture to be used by a shader stage. - */ - struct pipe_sampler_view *(*create_sampler_view)( - struct pipe_context *ctx, struct pipe_resource *texture, - const struct pipe_sampler_view *templat); - - void (*sampler_view_destroy)(struct pipe_context *ctx, - struct pipe_sampler_view *view); - - /** - * Get a surface which is a "view" into a resource, used by - * render target / depth stencil stages. - */ - struct pipe_surface *(*create_surface)(struct pipe_context *ctx, - struct pipe_resource *resource, - const struct pipe_surface *templat); - - void (*surface_destroy)(struct pipe_context *ctx, struct pipe_surface *); - - /** - * Map a resource. - * - * Transfers are (by default) context-private and allow uploads to be - * interleaved with rendering. - * - * out_transfer will contain the transfer object that must be passed - * to all the other transfer functions. It also contains useful - * information (like texture strides). - */ - void *(*transfer_map)(struct pipe_context *, struct pipe_resource *resource, - unsigned level, - unsigned usage, /* a combination of PIPE_TRANSFER_x */ - const struct pipe_box *, - struct pipe_transfer **out_transfer); - - /* If transfer was created with WRITE|FLUSH_EXPLICIT, only the - * regions specified with this call are guaranteed to be written to - * the resource. - */ - void (*transfer_flush_region)(struct pipe_context *, - struct pipe_transfer *transfer, - const struct pipe_box *); - - void (*transfer_unmap)(struct pipe_context *, struct pipe_transfer *transfer); - - /* One-shot transfer operation with data supplied in a user - * pointer. XXX: strides?? - */ - void (*transfer_inline_write)( - struct pipe_context *, struct pipe_resource *, unsigned level, - unsigned usage, /* a combination of PIPE_TRANSFER_x */ - const struct pipe_box *, const void *data, unsigned stride, - unsigned layer_stride); - - /** - * Flush any pending framebuffer writes and invalidate texture caches. - */ - void (*texture_barrier)(struct pipe_context *); - - /** - * Flush caches according to flags. - */ - void (*memory_barrier)(struct pipe_context *, unsigned flags); - - /** - * Creates a video codec for a specific video format/profile - */ - struct pipe_video_codec *(*create_video_codec)( - struct pipe_context *context, const struct pipe_video_codec *templat); - - /** - * Creates a video buffer as decoding target - */ - struct pipe_video_buffer *(*create_video_buffer)( - struct pipe_context *context, const struct pipe_video_buffer *templat); - - /** - * Compute kernel execution - */ - /*@{*/ - /** - * Define the compute program and parameters to be used by - * pipe_context::launch_grid. - */ - void *(*create_compute_state)(struct pipe_context *context, - const struct pipe_compute_state *); - void (*bind_compute_state)(struct pipe_context *, void *); - void (*delete_compute_state)(struct pipe_context *, void *); - - /** - * Bind an array of shader resources that will be used by the - * compute program. Any resources that were previously bound to - * the specified range will be unbound after this call. - * - * \param start first resource to bind. - * \param count number of consecutive resources to bind. - * \param resources array of pointers to the resources to bind, it - * should contain at least \a count elements - * unless it's NULL, in which case no new - * resources will be bound. - */ - void (*set_compute_resources)(struct pipe_context *, unsigned start, - unsigned count, - struct pipe_surface **resources); - - /** - * Bind an array of buffers to be mapped into the address space of - * the GLOBAL resource. Any buffers that were previously bound - * between [first, first + count - 1] are unbound after this call. - * - * \param first first buffer to map. - * \param count number of consecutive buffers to map. - * \param resources array of pointers to the buffers to map, it - * should contain at least \a count elements - * unless it's NULL, in which case no new - * resources will be bound. - * \param handles array of pointers to the memory locations that - * will be updated with the address each buffer - * will be mapped to. The base memory address of - * each of the buffers will be added to the value - * pointed to by its corresponding handle to form - * the final address argument. It should contain - * at least \a count elements, unless \a - * resources is NULL in which case \a handles - * should be NULL as well. - * - * Note that the driver isn't required to make any guarantees about - * the contents of the \a handles array being valid anytime except - * during the subsequent calls to pipe_context::launch_grid. This - * means that the only sensible location handles[i] may point to is - * somewhere within the INPUT buffer itself. This is so to - * accommodate implementations that lack virtual memory but - * nevertheless migrate buffers on the fly, leading to resource - * base addresses that change on each kernel invocation or are - * unknown to the pipe driver. - */ - void (*set_global_binding)(struct pipe_context *context, unsigned first, - unsigned count, struct pipe_resource **resources, - uint32_t **handles); - - /** - * Launch the compute kernel starting from instruction \a pc of the - * currently bound compute program. - * - * \a grid_layout and \a block_layout are arrays of size \a - * PIPE_COMPUTE_CAP_GRID_DIMENSION that determine the layout of the - * grid (in block units) and working block (in thread units) to be - * used, respectively. - * - * \a pc For drivers that use PIPE_SHADER_IR_LLVM as their prefered IR, - * this value will be the index of the kernel in the opencl.kernels - * metadata list. - * - * \a input will be used to initialize the INPUT resource, and it - * should point to a buffer of at least - * pipe_compute_state::req_input_mem bytes. - */ - void (*launch_grid)(struct pipe_context *context, const uint *block_layout, - const uint *grid_layout, uint32_t pc, const void *input); - /*@}*/ - - /** - * Get sample position for an individual sample point. - * - * \param sample_count - total number of samples - * \param sample_index - sample to get the position values for - * \param out_value - return value of 2 floats for x and y position for - * requested sample. - */ - void (*get_sample_position)(struct pipe_context *context, - unsigned sample_count, unsigned sample_index, - float *out_value); - - /** - * Flush the resource cache, so that the resource can be used - * by an external client. Possible usage: - * - flushing a resource before presenting it on the screen - * - flushing a resource if some other process or device wants to use it - * This shouldn't be used to flush caches if the resource is only managed - * by a single pipe_screen and is not shared with another process. - * (i.e. you shouldn't use it to flush caches explicitly if you want to e.g. - * use the resource for texturing) - */ - void (*flush_resource)(struct pipe_context *ctx, - struct pipe_resource *resource); -}; - -#ifdef __cplusplus -} -#endif - -#endif /* PIPE_CONTEXT_H */ diff --git a/app/src/main/cpp/virglrenderer/src/gallium/include/pipe/p_defines.h b/app/src/main/cpp/virglrenderer/src/gallium/include/pipe/p_defines.h deleted file mode 100644 index fce3aa4a4..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/include/pipe/p_defines.h +++ /dev/null @@ -1,785 +0,0 @@ -/************************************************************************** - * - * Copyright 2007 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -#ifndef PIPE_DEFINES_H -#define PIPE_DEFINES_H - -#include "p_compiler.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * Gallium error codes. - * - * - A zero value always means success. - * - A negative value always means failure. - * - The meaning of a positive value is function dependent. - */ -enum pipe_error { - PIPE_OK = 0, - PIPE_ERROR = -1, /**< Generic error */ - PIPE_ERROR_BAD_INPUT = -2, - PIPE_ERROR_OUT_OF_MEMORY = -3, - PIPE_ERROR_RETRY = -4 - /* TODO */ -}; - -#define PIPE_BLENDFACTOR_ONE 0x1 -#define PIPE_BLENDFACTOR_SRC_COLOR 0x2 -#define PIPE_BLENDFACTOR_SRC_ALPHA 0x3 -#define PIPE_BLENDFACTOR_DST_ALPHA 0x4 -#define PIPE_BLENDFACTOR_DST_COLOR 0x5 -#define PIPE_BLENDFACTOR_SRC_ALPHA_SATURATE 0x6 -#define PIPE_BLENDFACTOR_CONST_COLOR 0x7 -#define PIPE_BLENDFACTOR_CONST_ALPHA 0x8 -#define PIPE_BLENDFACTOR_SRC1_COLOR 0x9 -#define PIPE_BLENDFACTOR_SRC1_ALPHA 0x0A -#define PIPE_BLENDFACTOR_ZERO 0x11 -#define PIPE_BLENDFACTOR_INV_SRC_COLOR 0x12 -#define PIPE_BLENDFACTOR_INV_SRC_ALPHA 0x13 -#define PIPE_BLENDFACTOR_INV_DST_ALPHA 0x14 -#define PIPE_BLENDFACTOR_INV_DST_COLOR 0x15 -#define PIPE_BLENDFACTOR_INV_CONST_COLOR 0x17 -#define PIPE_BLENDFACTOR_INV_CONST_ALPHA 0x18 -#define PIPE_BLENDFACTOR_INV_SRC1_COLOR 0x19 -#define PIPE_BLENDFACTOR_INV_SRC1_ALPHA 0x1A - -#define PIPE_BLEND_ADD 0 -#define PIPE_BLEND_SUBTRACT 1 -#define PIPE_BLEND_REVERSE_SUBTRACT 2 -#define PIPE_BLEND_MIN 3 -#define PIPE_BLEND_MAX 4 - -enum pipe_logicop { - PIPE_LOGICOP_CLEAR, - PIPE_LOGICOP_NOR, - PIPE_LOGICOP_AND_INVERTED, - PIPE_LOGICOP_COPY_INVERTED, - PIPE_LOGICOP_AND_REVERSE, - PIPE_LOGICOP_INVERT, - PIPE_LOGICOP_XOR, - PIPE_LOGICOP_NAND, - PIPE_LOGICOP_AND, - PIPE_LOGICOP_EQUIV, - PIPE_LOGICOP_NOOP, - PIPE_LOGICOP_OR_INVERTED, - PIPE_LOGICOP_COPY, - PIPE_LOGICOP_OR_REVERSE, - PIPE_LOGICOP_OR, - PIPE_LOGICOP_SET, -}; - -#define PIPE_MASK_R 0x1 -#define PIPE_MASK_G 0x2 -#define PIPE_MASK_B 0x4 -#define PIPE_MASK_A 0x8 -#define PIPE_MASK_RGBA 0xf -#define PIPE_MASK_Z 0x10 -#define PIPE_MASK_S 0x20 -#define PIPE_MASK_ZS 0x30 -#define PIPE_MASK_RGBAZS (PIPE_MASK_RGBA | PIPE_MASK_ZS) - -/** - * Inequality functions. Used for depth test, stencil compare, alpha - * test, shadow compare, etc. - */ -#define PIPE_FUNC_NEVER 0 -#define PIPE_FUNC_LESS 1 -#define PIPE_FUNC_EQUAL 2 -#define PIPE_FUNC_LEQUAL 3 -#define PIPE_FUNC_GREATER 4 -#define PIPE_FUNC_NOTEQUAL 5 -#define PIPE_FUNC_GEQUAL 6 -#define PIPE_FUNC_ALWAYS 7 - -/** Polygon fill mode */ -#define PIPE_POLYGON_MODE_FILL 0 -#define PIPE_POLYGON_MODE_LINE 1 -#define PIPE_POLYGON_MODE_POINT 2 - -/** Polygon face specification, eg for culling */ -#define PIPE_FACE_NONE 0 -#define PIPE_FACE_FRONT 1 -#define PIPE_FACE_BACK 2 -#define PIPE_FACE_FRONT_AND_BACK (PIPE_FACE_FRONT | PIPE_FACE_BACK) - -/** Stencil ops */ -#define PIPE_STENCIL_OP_KEEP 0 -#define PIPE_STENCIL_OP_ZERO 1 -#define PIPE_STENCIL_OP_REPLACE 2 -#define PIPE_STENCIL_OP_INCR 3 -#define PIPE_STENCIL_OP_DECR 4 -#define PIPE_STENCIL_OP_INCR_WRAP 5 -#define PIPE_STENCIL_OP_DECR_WRAP 6 -#define PIPE_STENCIL_OP_INVERT 7 - -/** Texture types. - * See the documentation for info on PIPE_TEXTURE_RECT vs PIPE_TEXTURE_2D */ -enum pipe_texture_target { - PIPE_BUFFER = 0, - PIPE_TEXTURE_1D = 1, - PIPE_TEXTURE_2D = 2, - PIPE_TEXTURE_3D = 3, - PIPE_TEXTURE_CUBE = 4, - PIPE_TEXTURE_RECT = 5, - PIPE_TEXTURE_1D_ARRAY = 6, - PIPE_TEXTURE_2D_ARRAY = 7, - PIPE_TEXTURE_CUBE_ARRAY = 8, - PIPE_MAX_TEXTURE_TYPES -}; - -#define PIPE_TEX_FACE_POS_X 0 -#define PIPE_TEX_FACE_NEG_X 1 -#define PIPE_TEX_FACE_POS_Y 2 -#define PIPE_TEX_FACE_NEG_Y 3 -#define PIPE_TEX_FACE_POS_Z 4 -#define PIPE_TEX_FACE_NEG_Z 5 -#define PIPE_TEX_FACE_MAX 6 - -#define PIPE_TEX_WRAP_REPEAT 0 -#define PIPE_TEX_WRAP_CLAMP 1 -#define PIPE_TEX_WRAP_CLAMP_TO_EDGE 2 -#define PIPE_TEX_WRAP_CLAMP_TO_BORDER 3 -#define PIPE_TEX_WRAP_MIRROR_REPEAT 4 -#define PIPE_TEX_WRAP_MIRROR_CLAMP 5 -#define PIPE_TEX_WRAP_MIRROR_CLAMP_TO_EDGE 6 -#define PIPE_TEX_WRAP_MIRROR_CLAMP_TO_BORDER 7 - -/* Between mipmaps, ie mipfilter - */ -#define PIPE_TEX_MIPFILTER_NEAREST 0 -#define PIPE_TEX_MIPFILTER_LINEAR 1 -#define PIPE_TEX_MIPFILTER_NONE 2 - -/* Within a mipmap, ie min/mag filter - */ -#define PIPE_TEX_FILTER_NEAREST 0 -#define PIPE_TEX_FILTER_LINEAR 1 - -#define PIPE_TEX_COMPARE_NONE 0 -#define PIPE_TEX_COMPARE_R_TO_TEXTURE 1 - -/** - * Clear buffer bits - */ -#define PIPE_CLEAR_DEPTH (1 << 0) -#define PIPE_CLEAR_STENCIL (1 << 1) -#define PIPE_CLEAR_COLOR0 (1 << 2) -#define PIPE_CLEAR_COLOR1 (1 << 3) -#define PIPE_CLEAR_COLOR2 (1 << 4) -#define PIPE_CLEAR_COLOR3 (1 << 5) -#define PIPE_CLEAR_COLOR4 (1 << 6) -#define PIPE_CLEAR_COLOR5 (1 << 7) -#define PIPE_CLEAR_COLOR6 (1 << 8) -#define PIPE_CLEAR_COLOR7 (1 << 9) -/** Combined flags */ -/** All color buffers currently bound */ -#define PIPE_CLEAR_COLOR \ - (PIPE_CLEAR_COLOR0 | PIPE_CLEAR_COLOR1 | PIPE_CLEAR_COLOR2 | \ - PIPE_CLEAR_COLOR3 | PIPE_CLEAR_COLOR4 | PIPE_CLEAR_COLOR5 | \ - PIPE_CLEAR_COLOR6 | PIPE_CLEAR_COLOR7) -#define PIPE_CLEAR_DEPTHSTENCIL (PIPE_CLEAR_DEPTH | PIPE_CLEAR_STENCIL) - -/** - * Transfer object usage flags - */ -enum pipe_transfer_usage { - /** - * Resource contents read back (or accessed directly) at transfer - * create time. - */ - PIPE_TRANSFER_READ = (1 << 0), - - /** - * Resource contents will be written back at transfer_unmap - * time (or modified as a result of being accessed directly). - */ - PIPE_TRANSFER_WRITE = (1 << 1), - - /** - * Read/modify/write - */ - PIPE_TRANSFER_READ_WRITE = PIPE_TRANSFER_READ | PIPE_TRANSFER_WRITE, - - /** - * The transfer should map the texture storage directly. The driver may - * return NULL if that isn't possible, and the state tracker needs to cope - * with that and use an alternative path without this flag. - * - * E.g. the state tracker could have a simpler path which maps textures and - * does read/modify/write cycles on them directly, and a more complicated - * path which uses minimal read and write transfers. - */ - PIPE_TRANSFER_MAP_DIRECTLY = (1 << 2), - - /** - * Discards the memory within the mapped region. - * - * It should not be used with PIPE_TRANSFER_READ. - * - * See also: - * - OpenGL's ARB_map_buffer_range extension, MAP_INVALIDATE_RANGE_BIT flag. - */ - PIPE_TRANSFER_DISCARD_RANGE = (1 << 8), - - /** - * Fail if the resource cannot be mapped immediately. - * - * See also: - * - Direct3D's D3DLOCK_DONOTWAIT flag. - * - Mesa3D's MESA_MAP_NOWAIT_BIT flag. - * - WDDM's D3DDDICB_LOCKFLAGS.DonotWait flag. - */ - PIPE_TRANSFER_DONTBLOCK = (1 << 9), - - /** - * Do not attempt to synchronize pending operations on the resource when - * mapping. - * - * It should not be used with PIPE_TRANSFER_READ. - * - * See also: - * - OpenGL's ARB_map_buffer_range extension, MAP_UNSYNCHRONIZED_BIT flag. - * - Direct3D's D3DLOCK_NOOVERWRITE flag. - * - WDDM's D3DDDICB_LOCKFLAGS.IgnoreSync flag. - */ - PIPE_TRANSFER_UNSYNCHRONIZED = (1 << 10), - - /** - * Written ranges will be notified later with - * pipe_context::transfer_flush_region. - * - * It should not be used with PIPE_TRANSFER_READ. - * - * See also: - * - pipe_context::transfer_flush_region - * - OpenGL's ARB_map_buffer_range extension, MAP_FLUSH_EXPLICIT_BIT flag. - */ - PIPE_TRANSFER_FLUSH_EXPLICIT = (1 << 11), - - /** - * Discards all memory backing the resource. - * - * It should not be used with PIPE_TRANSFER_READ. - * - * This is equivalent to: - * - OpenGL's ARB_map_buffer_range extension, MAP_INVALIDATE_BUFFER_BIT - * - BufferData(NULL) on a GL buffer - * - Direct3D's D3DLOCK_DISCARD flag. - * - WDDM's D3DDDICB_LOCKFLAGS.Discard flag. - * - D3D10 DDI's D3D10_DDI_MAP_WRITE_DISCARD flag - * - D3D10's D3D10_MAP_WRITE_DISCARD flag. - */ - PIPE_TRANSFER_DISCARD_WHOLE_RESOURCE = (1 << 12), - - /** - * Allows the resource to be used for rendering while mapped. - * - * PIPE_RESOURCE_FLAG_MAP_PERSISTENT must be set when creating - * the resource. - * - * If COHERENT is not set, memory_barrier(PIPE_BARRIER_MAPPED_BUFFER) - * must be called to ensure the device can see what the CPU has written. - */ - PIPE_TRANSFER_PERSISTENT = (1 << 13), - - /** - * If PERSISTENT is set, this ensures any writes done by the device are - * immediately visible to the CPU and vice versa. - * - * PIPE_RESOURCE_FLAG_MAP_COHERENT must be set when creating - * the resource. - */ - PIPE_TRANSFER_COHERENT = (1 << 14) -}; - -/** - * Flags for the flush function. - */ -enum pipe_flush_flags { PIPE_FLUSH_END_OF_FRAME = (1 << 0) }; - -/** - * Flags for pipe_context::memory_barrier. - */ -#define PIPE_BARRIER_MAPPED_BUFFER (1 << 0) -#define PIPE_BARRIER_SHADER_BUFFER (1 << 1) -#define PIPE_BARRIER_QUERY_BUFFER (1 << 2) -#define PIPE_BARRIER_VERTEX_BUFFER (1 << 3) -#define PIPE_BARRIER_INDEX_BUFFER (1 << 4) -#define PIPE_BARRIER_CONSTANT_BUFFER (1 << 5) -#define PIPE_BARRIER_INDIRECT_BUFFER (1 << 6) -#define PIPE_BARRIER_TEXTURE (1 << 7) -#define PIPE_BARRIER_IMAGE (1 << 8) -#define PIPE_BARRIER_FRAMEBUFFER (1 << 9) -#define PIPE_BARRIER_STREAMOUT_BUFFER (1 << 10) -#define PIPE_BARRIER_GLOBAL_BUFFER (1 << 11) -#define PIPE_BARRIER_ALL ((1 << 12) - 1) - -/** - * Flags for pipe_context::texture_barrier. - */ -#define PIPE_TEXTURE_BARRIER_SAMPLER (1 << 0) -#define PIPE_TEXTURE_BARRIER_FRAMEBUFFER (1 << 1) - -/* - * Resource binding flags -- state tracker must specify in advance all - * the ways a resource might be used. - */ -#define PIPE_BIND_DEPTH_STENCIL (1 << 0) /* create_surface */ -#define PIPE_BIND_RENDER_TARGET (1 << 1) /* create_surface */ -#define PIPE_BIND_BLENDABLE (1 << 2) /* create_surface */ -#define PIPE_BIND_SAMPLER_VIEW (1 << 3) /* create_sampler_view */ -#define PIPE_BIND_VERTEX_BUFFER (1 << 4) /* set_vertex_buffers */ -#define PIPE_BIND_INDEX_BUFFER (1 << 5) /* draw_elements */ -#define PIPE_BIND_CONSTANT_BUFFER (1 << 6) /* set_constant_buffer */ -#define PIPE_BIND_DISPLAY_TARGET (1 << 8) /* flush_front_buffer */ -#define PIPE_BIND_TRANSFER_WRITE (1 << 9) /* transfer_map */ -#define PIPE_BIND_TRANSFER_READ (1 << 10) /* transfer_map */ -#define PIPE_BIND_STREAM_OUTPUT (1 << 11) /* set_stream_output_buffers */ -#define PIPE_BIND_CURSOR (1 << 16) /* mouse cursor */ -#define PIPE_BIND_CUSTOM (1 << 17) /* state-tracker/winsys usages */ -#define PIPE_BIND_GLOBAL (1 << 18) /* set_global_binding */ -#define PIPE_BIND_SHADER_RESOURCE (1 << 19) /* set_shader_resources */ -#define PIPE_BIND_COMPUTE_RESOURCE (1 << 20) /* set_compute_resources */ -#define PIPE_BIND_COMMAND_ARGS_BUFFER (1 << 21) /* pipe_draw_info.indirect */ -#define PIPE_BIND_QUERY_BUFFER (1 << 22) /* get_query_result_resource */ - -/* The first two flags above were previously part of the amorphous - * TEXTURE_USAGE, most of which are now descriptions of the ways a - * particular texture can be bound to the gallium pipeline. The two flags - * below do not fit within that and probably need to be migrated to some - * other place. - * - * It seems like scanout is used by the Xorg state tracker to ask for - * a texture suitable for actual scanout (hence the name), which - * implies extra layout constraints on some hardware. It may also - * have some special meaning regarding mouse cursor images. - * - * The shared flag is quite underspecified, but certainly isn't a - * binding flag - it seems more like a message to the winsys to create - * a shareable allocation. - * - * The third flag has been added to be able to force textures to be created - * in linear mode (no tiling). - */ -#define PIPE_BIND_SCANOUT (1 << 14) /* */ -#define PIPE_BIND_SHARED (1 << 15) /* get_texture_handle ??? */ -#define PIPE_BIND_LINEAR (1 << 21) - -/* Flags for the driver about resource behaviour: - */ -#define PIPE_RESOURCE_FLAG_MAP_PERSISTENT (1 << 0) -#define PIPE_RESOURCE_FLAG_MAP_COHERENT (1 << 1) -#define PIPE_RESOURCE_FLAG_DRV_PRIV (1 << 16) /* driver/winsys private */ -#define PIPE_RESOURCE_FLAG_ST_PRIV (1 << 24) /* state-tracker/winsys private \ - */ - -/* Hint about the expected lifecycle of a resource. - * Sorted according to GPU vs CPU access. - */ -#define PIPE_USAGE_DEFAULT 0 /* fast GPU access */ -#define PIPE_USAGE_IMMUTABLE 1 /* fast GPU access, immutable */ -#define PIPE_USAGE_DYNAMIC 2 /* uploaded data is used multiple times */ -#define PIPE_USAGE_STREAM 3 /* uploaded data is used once */ -#define PIPE_USAGE_STAGING 4 /* fast CPU access */ - -/** - * Shaders - */ -#define PIPE_SHADER_VERTEX 0 -#define PIPE_SHADER_FRAGMENT 1 -#define PIPE_SHADER_GEOMETRY 2 -#define PIPE_SHADER_TESS_CTRL 3 -#define PIPE_SHADER_TESS_EVAL 4 -#define PIPE_SHADER_COMPUTE 5 -#define PIPE_SHADER_TYPES 6 - -/** - * Primitive types: - */ -#define PIPE_PRIM_POINTS 0 -#define PIPE_PRIM_LINES 1 -#define PIPE_PRIM_LINE_LOOP 2 -#define PIPE_PRIM_LINE_STRIP 3 -#define PIPE_PRIM_TRIANGLES 4 -#define PIPE_PRIM_TRIANGLE_STRIP 5 -#define PIPE_PRIM_TRIANGLE_FAN 6 -#define PIPE_PRIM_QUADS 7 -#define PIPE_PRIM_QUAD_STRIP 8 -#define PIPE_PRIM_POLYGON 9 -#define PIPE_PRIM_LINES_ADJACENCY 10 -#define PIPE_PRIM_LINE_STRIP_ADJACENCY 11 -#define PIPE_PRIM_TRIANGLES_ADJACENCY 12 -#define PIPE_PRIM_TRIANGLE_STRIP_ADJACENCY 13 -#define PIPE_PRIM_PATCHES 14 -#define PIPE_PRIM_MAX 15 - -/** - * Tessellator spacing types - */ -#define PIPE_TESS_SPACING_FRACTIONAL_ODD 0 -#define PIPE_TESS_SPACING_FRACTIONAL_EVEN 1 -#define PIPE_TESS_SPACING_EQUAL 2 - -/** - * Query object types - */ -#define PIPE_QUERY_OCCLUSION_COUNTER 0 -#define PIPE_QUERY_OCCLUSION_PREDICATE 1 -#define PIPE_QUERY_TIMESTAMP 2 -#define PIPE_QUERY_TIMESTAMP_DISJOINT 3 -#define PIPE_QUERY_TIME_ELAPSED 4 -#define PIPE_QUERY_PRIMITIVES_GENERATED 5 -#define PIPE_QUERY_PRIMITIVES_EMITTED 6 -#define PIPE_QUERY_SO_STATISTICS 7 -#define PIPE_QUERY_SO_OVERFLOW_PREDICATE 8 -#define PIPE_QUERY_GPU_FINISHED 9 -#define PIPE_QUERY_PIPELINE_STATISTICS 10 -#define PIPE_QUERY_OCCLUSION_PREDICATE_CONSERVATIVE 11 -#define PIPE_QUERY_SO_OVERFLOW_ANY_PREDICATE 12 -#define PIPE_QUERY_TYPES 13 - -/* start of driver queries, - * see pipe_screen::get_driver_query_info */ -#define PIPE_QUERY_DRIVER_SPECIFIC 256 - -/** - * Conditional rendering modes - */ -#define PIPE_RENDER_COND_WAIT 0 -#define PIPE_RENDER_COND_NO_WAIT 1 -#define PIPE_RENDER_COND_BY_REGION_WAIT 2 -#define PIPE_RENDER_COND_BY_REGION_NO_WAIT 3 - -/** - * Point sprite coord modes - */ -#define PIPE_SPRITE_COORD_UPPER_LEFT 0 -#define PIPE_SPRITE_COORD_LOWER_LEFT 1 - -/** - * Texture swizzles - */ -#define PIPE_SWIZZLE_RED 0 -#define PIPE_SWIZZLE_GREEN 1 -#define PIPE_SWIZZLE_BLUE 2 -#define PIPE_SWIZZLE_ALPHA 3 -#define PIPE_SWIZZLE_ZERO 4 -#define PIPE_SWIZZLE_ONE 5 - -#define PIPE_TIMEOUT_INFINITE 0xffffffffffffffffull - -/** - * pipe_image_view access flags. - */ -#define PIPE_IMAGE_ACCESS_READ (1 << 0) -#define PIPE_IMAGE_ACCESS_WRITE (1 << 1) -#define PIPE_IMAGE_ACCESS_READ_WRITE \ - (PIPE_IMAGE_ACCESS_READ | PIPE_IMAGE_ACCESS_WRITE) - -/** - * Implementation capabilities/limits which are queried through - * pipe_screen::get_param() - */ -enum pipe_cap { - PIPE_CAP_NPOT_TEXTURES = 1, - PIPE_CAP_TWO_SIDED_STENCIL = 2, - PIPE_CAP_MAX_DUAL_SOURCE_RENDER_TARGETS = 4, - PIPE_CAP_ANISOTROPIC_FILTER = 5, - PIPE_CAP_POINT_SPRITE = 6, - PIPE_CAP_MAX_RENDER_TARGETS = 7, - PIPE_CAP_OCCLUSION_QUERY = 8, - PIPE_CAP_QUERY_TIME_ELAPSED = 9, - PIPE_CAP_TEXTURE_SHADOW_MAP = 10, - PIPE_CAP_TEXTURE_SWIZZLE = 11, - PIPE_CAP_MAX_TEXTURE_2D_LEVELS = 12, - PIPE_CAP_MAX_TEXTURE_3D_LEVELS = 13, - PIPE_CAP_MAX_TEXTURE_CUBE_LEVELS = 14, - PIPE_CAP_TEXTURE_MIRROR_CLAMP = 25, - PIPE_CAP_BLEND_EQUATION_SEPARATE = 28, - PIPE_CAP_SM3 = 29, /*< Shader Model, supported */ - PIPE_CAP_MAX_STREAM_OUTPUT_BUFFERS = 30, - PIPE_CAP_PRIMITIVE_RESTART = 31, - /** blend enables and write masks per rendertarget */ - PIPE_CAP_INDEP_BLEND_ENABLE = 33, - /** different blend funcs per rendertarget */ - PIPE_CAP_INDEP_BLEND_FUNC = 34, - PIPE_CAP_MAX_TEXTURE_ARRAY_LAYERS = 36, - PIPE_CAP_TGSI_FS_COORD_ORIGIN_UPPER_LEFT = 37, - PIPE_CAP_TGSI_FS_COORD_ORIGIN_LOWER_LEFT = 38, - PIPE_CAP_TGSI_FS_COORD_PIXEL_CENTER_HALF_INTEGER = 39, - PIPE_CAP_TGSI_FS_COORD_PIXEL_CENTER_INTEGER = 40, - PIPE_CAP_DEPTH_CLIP_DISABLE = 41, - PIPE_CAP_SHADER_STENCIL_EXPORT = 42, - PIPE_CAP_TGSI_INSTANCEID = 43, - PIPE_CAP_VERTEX_ELEMENT_INSTANCE_DIVISOR = 44, - PIPE_CAP_FRAGMENT_COLOR_CLAMPED = 45, - PIPE_CAP_MIXED_COLORBUFFER_FORMATS = 46, - PIPE_CAP_SEAMLESS_CUBE_MAP = 47, - PIPE_CAP_SEAMLESS_CUBE_MAP_PER_TEXTURE = 48, - PIPE_CAP_MIN_TEXEL_OFFSET = 50, - PIPE_CAP_MAX_TEXEL_OFFSET = 51, - PIPE_CAP_CONDITIONAL_RENDER = 52, - PIPE_CAP_TEXTURE_BARRIER = 53, - PIPE_CAP_MAX_STREAM_OUTPUT_SEPARATE_COMPONENTS = 55, - PIPE_CAP_MAX_STREAM_OUTPUT_INTERLEAVED_COMPONENTS = 56, - PIPE_CAP_STREAM_OUTPUT_PAUSE_RESUME = 57, - PIPE_CAP_TGSI_CAN_COMPACT_CONSTANTS = 59, /* temporary */ - PIPE_CAP_VERTEX_COLOR_UNCLAMPED = 60, - PIPE_CAP_VERTEX_COLOR_CLAMPED = 61, - PIPE_CAP_GLSL_FEATURE_LEVEL = 62, - PIPE_CAP_QUADS_FOLLOW_PROVOKING_VERTEX_CONVENTION = 63, - PIPE_CAP_USER_VERTEX_BUFFERS = 64, - PIPE_CAP_VERTEX_BUFFER_OFFSET_4BYTE_ALIGNED_ONLY = 65, - PIPE_CAP_VERTEX_BUFFER_STRIDE_4BYTE_ALIGNED_ONLY = 66, - PIPE_CAP_VERTEX_ELEMENT_SRC_OFFSET_4BYTE_ALIGNED_ONLY = 67, - PIPE_CAP_COMPUTE = 68, - PIPE_CAP_USER_INDEX_BUFFERS = 69, - PIPE_CAP_USER_CONSTANT_BUFFERS = 70, - PIPE_CAP_CONSTANT_BUFFER_OFFSET_ALIGNMENT = 71, - PIPE_CAP_START_INSTANCE = 72, - PIPE_CAP_QUERY_TIMESTAMP = 73, - PIPE_CAP_TEXTURE_MULTISAMPLE = 74, - PIPE_CAP_MIN_MAP_BUFFER_ALIGNMENT = 75, - PIPE_CAP_CUBE_MAP_ARRAY = 76, - PIPE_CAP_TEXTURE_BUFFER_OBJECTS = 77, - PIPE_CAP_TEXTURE_BUFFER_OFFSET_ALIGNMENT = 78, - PIPE_CAP_TGSI_TEXCOORD = 79, - PIPE_CAP_PREFER_BLIT_BASED_TEXTURE_TRANSFER = 80, - PIPE_CAP_QUERY_PIPELINE_STATISTICS = 81, - PIPE_CAP_TEXTURE_BORDER_COLOR_QUIRK = 82, - PIPE_CAP_MAX_TEXTURE_BUFFER_SIZE = 83, - PIPE_CAP_MAX_VIEWPORTS = 84, - PIPE_CAP_ENDIANNESS = 85, - PIPE_CAP_MIXED_FRAMEBUFFER_SIZES = 86, - PIPE_CAP_TGSI_VS_LAYER_VIEWPORT = 87, - PIPE_CAP_MAX_GEOMETRY_OUTPUT_VERTICES = 88, - PIPE_CAP_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS = 89, - PIPE_CAP_MAX_TEXTURE_GATHER_COMPONENTS = 90, - PIPE_CAP_TEXTURE_GATHER_SM5 = 91, - PIPE_CAP_BUFFER_MAP_PERSISTENT_COHERENT = 92, - PIPE_CAP_FAKE_SW_MSAA = 93, - PIPE_CAP_TEXTURE_QUERY_LOD = 94, - PIPE_CAP_MIN_TEXTURE_GATHER_OFFSET = 95, - PIPE_CAP_MAX_TEXTURE_GATHER_OFFSET = 96, - PIPE_CAP_SAMPLE_SHADING = 97, - PIPE_CAP_TEXTURE_GATHER_OFFSETS = 98, - PIPE_CAP_TGSI_VS_WINDOW_SPACE_POSITION = 99, - PIPE_CAP_MAX_VERTEX_STREAMS = 100, - PIPE_CAP_DRAW_INDIRECT = 101, - PIPE_CAP_TGSI_FS_FINE_DERIVATIVE = 102, - PIPE_CAP_VENDOR_ID = 103, - PIPE_CAP_DEVICE_ID = 104, - PIPE_CAP_ACCELERATED = 105, - PIPE_CAP_VIDEO_MEMORY = 106, - PIPE_CAP_UMA = 107, - PIPE_CAP_CONDITIONAL_RENDER_INVERTED = 108, - PIPE_CAP_MAX_VERTEX_ATTRIB_STRIDE = 109, - PIPE_CAP_SAMPLER_VIEW_TARGET = 110, - PIPE_CAP_CLIP_HALFZ = 111, - PIPE_CAP_VERTEXID_NOBASE = 112, - PIPE_CAP_POLYGON_OFFSET_CLAMP = 113, -}; - -#define PIPE_QUIRK_TEXTURE_BORDER_COLOR_SWIZZLE_NV50 (1 << 0) -#define PIPE_QUIRK_TEXTURE_BORDER_COLOR_SWIZZLE_R600 (1 << 1) - -enum pipe_endian { - PIPE_ENDIAN_LITTLE = 0, - PIPE_ENDIAN_BIG = 1, -#if defined(PIPE_ARCH_LITTLE_ENDIAN) - PIPE_ENDIAN_NATIVE = PIPE_ENDIAN_LITTLE -#elif defined(PIPE_ARCH_BIG_ENDIAN) - PIPE_ENDIAN_NATIVE = PIPE_ENDIAN_BIG -#endif -}; - -/** - * Implementation limits which are queried through - * pipe_screen::get_paramf() - */ -enum pipe_capf { - PIPE_CAPF_MAX_LINE_WIDTH, - PIPE_CAPF_MAX_LINE_WIDTH_AA, - PIPE_CAPF_MAX_POINT_WIDTH, - PIPE_CAPF_MAX_POINT_WIDTH_AA, - PIPE_CAPF_MAX_TEXTURE_ANISOTROPY, - PIPE_CAPF_MAX_TEXTURE_LOD_BIAS, - PIPE_CAPF_GUARD_BAND_LEFT, - PIPE_CAPF_GUARD_BAND_TOP, - PIPE_CAPF_GUARD_BAND_RIGHT, - PIPE_CAPF_GUARD_BAND_BOTTOM -}; - -/* Shader caps not specific to any single stage */ -enum pipe_shader_cap { - PIPE_SHADER_CAP_MAX_INSTRUCTIONS, /* if 0, it means the stage is unsupported - */ - PIPE_SHADER_CAP_MAX_ALU_INSTRUCTIONS, - PIPE_SHADER_CAP_MAX_TEX_INSTRUCTIONS, - PIPE_SHADER_CAP_MAX_TEX_INDIRECTIONS, - PIPE_SHADER_CAP_MAX_CONTROL_FLOW_DEPTH, - PIPE_SHADER_CAP_MAX_INPUTS, - PIPE_SHADER_CAP_MAX_OUTPUTS, - PIPE_SHADER_CAP_MAX_CONST_BUFFER_SIZE, - PIPE_SHADER_CAP_MAX_CONST_BUFFERS, - PIPE_SHADER_CAP_MAX_TEMPS, - PIPE_SHADER_CAP_MAX_PREDS, - /* boolean caps */ - PIPE_SHADER_CAP_TGSI_CONT_SUPPORTED, - PIPE_SHADER_CAP_INDIRECT_INPUT_ADDR, - PIPE_SHADER_CAP_INDIRECT_OUTPUT_ADDR, - PIPE_SHADER_CAP_INDIRECT_TEMP_ADDR, - PIPE_SHADER_CAP_INDIRECT_CONST_ADDR, - PIPE_SHADER_CAP_SUBROUTINES, /* BGNSUB, ENDSUB, CAL, RET */ - PIPE_SHADER_CAP_INTEGERS, - PIPE_SHADER_CAP_MAX_TEXTURE_SAMPLERS, - PIPE_SHADER_CAP_PREFERRED_IR, - PIPE_SHADER_CAP_TGSI_SQRT_SUPPORTED, - PIPE_SHADER_CAP_MAX_SAMPLER_VIEWS, - PIPE_SHADER_CAP_DOUBLES -}; - -/** - * Shader intermediate representation. - */ -enum pipe_shader_ir { - PIPE_SHADER_IR_TGSI, - PIPE_SHADER_IR_LLVM, - PIPE_SHADER_IR_NATIVE -}; - -/** - * Compute-specific implementation capability. They can be queried - * using pipe_screen::get_compute_param. - */ -enum pipe_compute_cap { - PIPE_COMPUTE_CAP_IR_TARGET, - PIPE_COMPUTE_CAP_GRID_DIMENSION, - PIPE_COMPUTE_CAP_MAX_GRID_SIZE, - PIPE_COMPUTE_CAP_MAX_BLOCK_SIZE, - PIPE_COMPUTE_CAP_MAX_THREADS_PER_BLOCK, - PIPE_COMPUTE_CAP_MAX_GLOBAL_SIZE, - PIPE_COMPUTE_CAP_MAX_LOCAL_SIZE, - PIPE_COMPUTE_CAP_MAX_PRIVATE_SIZE, - PIPE_COMPUTE_CAP_MAX_INPUT_SIZE, - PIPE_COMPUTE_CAP_MAX_MEM_ALLOC_SIZE, - PIPE_COMPUTE_CAP_MAX_CLOCK_FREQUENCY, - PIPE_COMPUTE_CAP_MAX_COMPUTE_UNITS, - PIPE_COMPUTE_CAP_IMAGES_SUPPORTED -}; - -/** - * Composite query types - */ - -/** - * Query result for PIPE_QUERY_SO_STATISTICS. - */ -struct pipe_query_data_so_statistics { - uint64_t num_primitives_written; - uint64_t primitives_storage_needed; -}; - -/** - * Query result for PIPE_QUERY_TIMESTAMP_DISJOINT. - */ -struct pipe_query_data_timestamp_disjoint { - uint64_t frequency; - boolean disjoint; -}; - -/** - * Query result for PIPE_QUERY_PIPELINE_STATISTICS. - */ -struct pipe_query_data_pipeline_statistics { - uint64_t ia_vertices; /**< Num vertices read by the vertex fetcher. */ - uint64_t ia_primitives; /**< Num primitives read by the vertex fetcher. */ - uint64_t vs_invocations; /**< Num vertex shader invocations. */ - uint64_t gs_invocations; /**< Num geometry shader invocations. */ - uint64_t gs_primitives; /**< Num primitives output by a geometry shader. */ - uint64_t c_invocations; /**< Num primitives sent to the rasterizer. */ - uint64_t c_primitives; /**< Num primitives that were rendered. */ - uint64_t ps_invocations; /**< Num pixel shader invocations. */ - uint64_t hs_invocations; /**< Num hull shader invocations. */ - uint64_t ds_invocations; /**< Num domain shader invocations. */ - uint64_t cs_invocations; /**< Num compute shader invocations. */ -}; - -/** - * Query result (returned by pipe_context::get_query_result). - */ -union pipe_query_result { - /* PIPE_QUERY_OCCLUSION_PREDICATE */ - /* PIPE_QUERY_SO_OVERFLOW_PREDICATE */ - /* PIPE_QUERY_GPU_FINISHED */ - boolean b; - - /* PIPE_QUERY_OCCLUSION_COUNTER */ - /* PIPE_QUERY_TIMESTAMP */ - /* PIPE_QUERY_TIME_ELAPSED */ - /* PIPE_QUERY_PRIMITIVES_GENERATED */ - /* PIPE_QUERY_PRIMITIVES_EMITTED */ - uint64_t u64; - - /* PIPE_QUERY_SO_STATISTICS */ - struct pipe_query_data_so_statistics so_statistics; - - /* PIPE_QUERY_TIMESTAMP_DISJOINT */ - struct pipe_query_data_timestamp_disjoint timestamp_disjoint; - - /* PIPE_QUERY_PIPELINE_STATISTICS */ - struct pipe_query_data_pipeline_statistics pipeline_statistics; -}; - -enum pipe_query_value_type { - PIPE_QUERY_TYPE_I32, - PIPE_QUERY_TYPE_U32, - PIPE_QUERY_TYPE_I64, - PIPE_QUERY_TYPE_U64, -}; - -union pipe_color_union { - float f[4]; - int i[4]; - unsigned int ui[4]; -}; - -struct pipe_driver_query_info { - const char *name; - unsigned query_type; /* PIPE_QUERY_DRIVER_SPECIFIC + i */ - uint64_t max_value; /* max value that can be returned */ - boolean uses_byte_units; /* whether the result is in bytes */ -}; - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/app/src/main/cpp/virglrenderer/src/gallium/include/pipe/p_format.h b/app/src/main/cpp/virglrenderer/src/gallium/include/pipe/p_format.h deleted file mode 100644 index 196a6f4c5..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/include/pipe/p_format.h +++ /dev/null @@ -1,493 +0,0 @@ -/************************************************************************** - * - * Copyright 2007 VMware, Inc. - * Copyright (c) 2008 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -#ifndef PIPE_FORMAT_H -#define PIPE_FORMAT_H - -#include "p_config.h" -#include "virgl_hw.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#define pipe_format virgl_formats -/** - * Formats for textures, surfaces and vertex data - */ -#define PIPE_FORMAT_NONE VIRGL_FORMAT_NONE -#define PIPE_FORMAT_B8G8R8A8_UNORM VIRGL_FORMAT_B8G8R8A8_UNORM -#define PIPE_FORMAT_B8G8R8X8_UNORM VIRGL_FORMAT_B8G8R8X8_UNORM -#define PIPE_FORMAT_A8R8G8B8_UNORM VIRGL_FORMAT_A8R8G8B8_UNORM -#define PIPE_FORMAT_X8R8G8B8_UNORM VIRGL_FORMAT_X8R8G8B8_UNORM -#define PIPE_FORMAT_B5G5R5A1_UNORM VIRGL_FORMAT_B5G5R5A1_UNORM -#define PIPE_FORMAT_B4G4R4A4_UNORM VIRGL_FORMAT_B4G4R4A4_UNORM -#define PIPE_FORMAT_B5G6R5_UNORM VIRGL_FORMAT_B5G6R5_UNORM -#define PIPE_FORMAT_R10G10B10A2_UNORM VIRGL_FORMAT_R10G10B10A2_UNORM -#define PIPE_FORMAT_L8_UNORM VIRGL_FORMAT_L8_UNORM /**< ubyte luminance */ -#define PIPE_FORMAT_A8_UNORM VIRGL_FORMAT_A8_UNORM /**< ubyte alpha */ -#define PIPE_FORMAT_I8_UNORM VIRGL_FORMAT_I8_UNORM /**< ubyte intensity */ -#define PIPE_FORMAT_L8A8_UNORM \ - VIRGL_FORMAT_L8A8_UNORM /**< ubyte alpha, luminance */ -#define PIPE_FORMAT_L16_UNORM VIRGL_FORMAT_L16_UNORM /**< ushort luminance */ -#define PIPE_FORMAT_UYVY VIRGL_FORMAT_UYVY -#define PIPE_FORMAT_YUYV VIRGL_FORMAT_YUYV -#define PIPE_FORMAT_Z16_UNORM VIRGL_FORMAT_Z16_UNORM -#define PIPE_FORMAT_Z32_UNORM VIRGL_FORMAT_Z32_UNORM -#define PIPE_FORMAT_Z32_FLOAT VIRGL_FORMAT_Z32_FLOAT -#define PIPE_FORMAT_Z24_UNORM_S8_UINT VIRGL_FORMAT_Z24_UNORM_S8_UINT -#define PIPE_FORMAT_S8_UINT_Z24_UNORM VIRGL_FORMAT_S8_UINT_Z24_UNORM -#define PIPE_FORMAT_Z24X8_UNORM VIRGL_FORMAT_Z24X8_UNORM -#define PIPE_FORMAT_X8Z24_UNORM VIRGL_FORMAT_X8Z24_UNORM -#define PIPE_FORMAT_S8_UINT VIRGL_FORMAT_S8_UINT /**< ubyte stencil */ -#define PIPE_FORMAT_R64_FLOAT VIRGL_FORMAT_R64_FLOAT -#define PIPE_FORMAT_R64G64_FLOAT VIRGL_FORMAT_R64G64_FLOAT -#define PIPE_FORMAT_R64G64B64_FLOAT VIRGL_FORMAT_R64G64B64_FLOAT -#define PIPE_FORMAT_R64G64B64A64_FLOAT VIRGL_FORMAT_R64G64B64A64_FLOAT -#define PIPE_FORMAT_R32_FLOAT VIRGL_FORMAT_R32_FLOAT -#define PIPE_FORMAT_R32G32_FLOAT VIRGL_FORMAT_R32G32_FLOAT -#define PIPE_FORMAT_R32G32B32_FLOAT VIRGL_FORMAT_R32G32B32_FLOAT -#define PIPE_FORMAT_R32G32B32A32_FLOAT VIRGL_FORMAT_R32G32B32A32_FLOAT -#define PIPE_FORMAT_R32_UNORM VIRGL_FORMAT_R32_UNORM -#define PIPE_FORMAT_R32G32_UNORM VIRGL_FORMAT_R32G32_UNORM -#define PIPE_FORMAT_R32G32B32_UNORM VIRGL_FORMAT_R32G32B32_UNORM -#define PIPE_FORMAT_R32G32B32A32_UNORM VIRGL_FORMAT_R32G32B32A32_UNORM -#define PIPE_FORMAT_R32_USCALED VIRGL_FORMAT_R32_USCALED -#define PIPE_FORMAT_R32G32_USCALED VIRGL_FORMAT_R32G32_USCALED -#define PIPE_FORMAT_R32G32B32_USCALED VIRGL_FORMAT_R32G32B32_USCALED -#define PIPE_FORMAT_R32G32B32A32_USCALED VIRGL_FORMAT_R32G32B32A32_USCALED -#define PIPE_FORMAT_R32_SNORM VIRGL_FORMAT_R32_SNORM -#define PIPE_FORMAT_R32G32_SNORM VIRGL_FORMAT_R32G32_SNORM -#define PIPE_FORMAT_R32G32B32_SNORM VIRGL_FORMAT_R32G32B32_SNORM -#define PIPE_FORMAT_R32G32B32A32_SNORM VIRGL_FORMAT_R32G32B32A32_SNORM -#define PIPE_FORMAT_R32_SSCALED VIRGL_FORMAT_R32_SSCALED -#define PIPE_FORMAT_R32G32_SSCALED VIRGL_FORMAT_R32G32_SSCALED -#define PIPE_FORMAT_R32G32B32_SSCALED VIRGL_FORMAT_R32G32B32_SSCALED -#define PIPE_FORMAT_R32G32B32A32_SSCALED VIRGL_FORMAT_R32G32B32A32_SSCALED -#define PIPE_FORMAT_R16_UNORM VIRGL_FORMAT_R16_UNORM -#define PIPE_FORMAT_R16G16_UNORM VIRGL_FORMAT_R16G16_UNORM -#define PIPE_FORMAT_R16G16B16_UNORM VIRGL_FORMAT_R16G16B16_UNORM -#define PIPE_FORMAT_R16G16B16A16_UNORM VIRGL_FORMAT_R16G16B16A16_UNORM -#define PIPE_FORMAT_R16_USCALED VIRGL_FORMAT_R16_USCALED -#define PIPE_FORMAT_R16G16_USCALED VIRGL_FORMAT_R16G16_USCALED -#define PIPE_FORMAT_R16G16B16_USCALED VIRGL_FORMAT_R16G16B16_USCALED -#define PIPE_FORMAT_R16G16B16A16_USCALED VIRGL_FORMAT_R16G16B16A16_USCALED -#define PIPE_FORMAT_R16_SNORM VIRGL_FORMAT_R16_SNORM -#define PIPE_FORMAT_R16G16_SNORM VIRGL_FORMAT_R16G16_SNORM -#define PIPE_FORMAT_R16G16B16_SNORM VIRGL_FORMAT_R16G16B16_SNORM -#define PIPE_FORMAT_R16G16B16A16_SNORM VIRGL_FORMAT_R16G16B16A16_SNORM -#define PIPE_FORMAT_R16_SSCALED VIRGL_FORMAT_R16_SSCALED -#define PIPE_FORMAT_R16G16_SSCALED VIRGL_FORMAT_R16G16_SSCALED -#define PIPE_FORMAT_R16G16B16_SSCALED VIRGL_FORMAT_R16G16B16_SSCALED -#define PIPE_FORMAT_R16G16B16A16_SSCALED VIRGL_FORMAT_R16G16B16A16_SSCALED -#define PIPE_FORMAT_R8_UNORM VIRGL_FORMAT_R8_UNORM -#define PIPE_FORMAT_R8G8_UNORM VIRGL_FORMAT_R8G8_UNORM -#define PIPE_FORMAT_R8G8B8_UNORM VIRGL_FORMAT_R8G8B8_UNORM -#define PIPE_FORMAT_R8G8B8A8_UNORM VIRGL_FORMAT_R8G8B8A8_UNORM -#define PIPE_FORMAT_X8B8G8R8_UNORM VIRGL_FORMAT_X8B8G8R8_UNORM -#define PIPE_FORMAT_R8_USCALED VIRGL_FORMAT_R8_USCALED -#define PIPE_FORMAT_R8G8_USCALED VIRGL_FORMAT_R8G8_USCALED -#define PIPE_FORMAT_R8G8B8_USCALED VIRGL_FORMAT_R8G8B8_USCALED -#define PIPE_FORMAT_R8G8B8A8_USCALED VIRGL_FORMAT_R8G8B8A8_USCALED -#define PIPE_FORMAT_R8_SNORM VIRGL_FORMAT_R8_SNORM -#define PIPE_FORMAT_R8G8_SNORM VIRGL_FORMAT_R8G8_SNORM -#define PIPE_FORMAT_R8G8B8_SNORM VIRGL_FORMAT_R8G8B8_SNORM -#define PIPE_FORMAT_R8G8B8A8_SNORM VIRGL_FORMAT_R8G8B8A8_SNORM -#define PIPE_FORMAT_R8_SSCALED VIRGL_FORMAT_R8_SSCALED -#define PIPE_FORMAT_R8G8_SSCALED VIRGL_FORMAT_R8G8_SSCALED -#define PIPE_FORMAT_R8G8B8_SSCALED VIRGL_FORMAT_R8G8B8_SSCALED -#define PIPE_FORMAT_R8G8B8A8_SSCALED VIRGL_FORMAT_R8G8B8A8_SSCALED -#define PIPE_FORMAT_R32_FIXED VIRGL_FORMAT_R32_FIXED -#define PIPE_FORMAT_R32G32_FIXED VIRGL_FORMAT_R32G32_FIXED -#define PIPE_FORMAT_R32G32B32_FIXED VIRGL_FORMAT_R32G32B32_FIXED -#define PIPE_FORMAT_R32G32B32A32_FIXED VIRGL_FORMAT_R32G32B32A32_FIXED -#define PIPE_FORMAT_R16_FLOAT VIRGL_FORMAT_R16_FLOAT -#define PIPE_FORMAT_R16G16_FLOAT VIRGL_FORMAT_R16G16_FLOAT -#define PIPE_FORMAT_R16G16B16_FLOAT VIRGL_FORMAT_R16G16B16_FLOAT -#define PIPE_FORMAT_R16G16B16A16_FLOAT VIRGL_FORMAT_R16G16B16A16_FLOAT - -/* sRGB formats */ -#define PIPE_FORMAT_L8_SRGB VIRGL_FORMAT_L8_SRGB -#define PIPE_FORMAT_L8A8_SRGB VIRGL_FORMAT_L8A8_SRGB -#define PIPE_FORMAT_R8G8B8_SRGB VIRGL_FORMAT_R8G8B8_SRGB -#define PIPE_FORMAT_A8B8G8R8_SRGB VIRGL_FORMAT_A8B8G8R8_SRGB -#define PIPE_FORMAT_X8B8G8R8_SRGB VIRGL_FORMAT_X8B8G8R8_SRGB -#define PIPE_FORMAT_B8G8R8A8_SRGB VIRGL_FORMAT_B8G8R8A8_SRGB -#define PIPE_FORMAT_B8G8R8X8_SRGB VIRGL_FORMAT_B8G8R8X8_SRGB -#define PIPE_FORMAT_A8R8G8B8_SRGB VIRGL_FORMAT_A8R8G8B8_SRGB -#define PIPE_FORMAT_X8R8G8B8_SRGB VIRGL_FORMAT_X8R8G8B8_SRGB -#define PIPE_FORMAT_R8G8B8A8_SRGB VIRGL_FORMAT_R8G8B8A8_SRGB - -/* compressed formats */ -#define PIPE_FORMAT_DXT1_RGB VIRGL_FORMAT_DXT1_RGB -#define PIPE_FORMAT_DXT1_RGBA VIRGL_FORMAT_DXT1_RGBA -#define PIPE_FORMAT_DXT3_RGBA VIRGL_FORMAT_DXT3_RGBA -#define PIPE_FORMAT_DXT5_RGBA VIRGL_FORMAT_DXT5_RGBA - -/* sRGB, compressed */ -#define PIPE_FORMAT_DXT1_SRGB VIRGL_FORMAT_DXT1_SRGB -#define PIPE_FORMAT_DXT1_SRGBA VIRGL_FORMAT_DXT1_SRGBA -#define PIPE_FORMAT_DXT3_SRGBA VIRGL_FORMAT_DXT3_SRGBA -#define PIPE_FORMAT_DXT5_SRGBA VIRGL_FORMAT_DXT5_SRGBA - -/* rgtc compressed */ -#define PIPE_FORMAT_RGTC1_UNORM VIRGL_FORMAT_RGTC1_UNORM -#define PIPE_FORMAT_RGTC1_SNORM VIRGL_FORMAT_RGTC1_SNORM -#define PIPE_FORMAT_RGTC2_UNORM VIRGL_FORMAT_RGTC2_UNORM -#define PIPE_FORMAT_RGTC2_SNORM VIRGL_FORMAT_RGTC2_SNORM - -#define PIPE_FORMAT_R8G8_B8G8_UNORM VIRGL_FORMAT_R8G8_B8G8_UNORM -#define PIPE_FORMAT_G8R8_G8B8_UNORM VIRGL_FORMAT_G8R8_G8B8_UNORM - -/* mixed formats */ -#define PIPE_FORMAT_R8SG8SB8UX8U_NORM VIRGL_FORMAT_R8SG8SB8UX8U_NORM -#define PIPE_FORMAT_R5SG5SB6U_NORM VIRGL_FORMAT_R5SG5SB6U_NORM - -/* TODO: re-order these */ -#define PIPE_FORMAT_A8B8G8R8_UNORM VIRGL_FORMAT_A8B8G8R8_UNORM -#define PIPE_FORMAT_B5G5R5X1_UNORM VIRGL_FORMAT_B5G5R5X1_UNORM -#define PIPE_FORMAT_R10G10B10A2_USCALED VIRGL_FORMAT_R10G10B10A2_USCALED -#define PIPE_FORMAT_R11G11B10_FLOAT VIRGL_FORMAT_R11G11B10_FLOAT -#define PIPE_FORMAT_R9G9B9E5_FLOAT VIRGL_FORMAT_R9G9B9E5_FLOAT -#define PIPE_FORMAT_Z32_FLOAT_S8X24_UINT VIRGL_FORMAT_Z32_FLOAT_S8X24_UINT -#define PIPE_FORMAT_R1_UNORM VIRGL_FORMAT_R1_UNORM -#define PIPE_FORMAT_R10G10B10X2_USCALED VIRGL_FORMAT_R10G10B10X2_USCALED -#define PIPE_FORMAT_R10G10B10X2_SNORM VIRGL_FORMAT_R10G10B10X2_SNORM -#define PIPE_FORMAT_L4A4_UNORM VIRGL_FORMAT_L4A4_UNORM -#define PIPE_FORMAT_B10G10R10A2_UNORM VIRGL_FORMAT_B10G10R10A2_UNORM -#define PIPE_FORMAT_R10SG10SB10SA2U_NORM VIRGL_FORMAT_R10SG10SB10SA2U_NORM -#define PIPE_FORMAT_R8G8Bx_SNORM VIRGL_FORMAT_R8G8Bx_SNORM -#define PIPE_FORMAT_R8G8B8X8_UNORM VIRGL_FORMAT_R8G8B8X8_UNORM -#define PIPE_FORMAT_B4G4R4X4_UNORM VIRGL_FORMAT_B4G4R4X4_UNORM - -/* some stencil samplers formats */ -#define PIPE_FORMAT_X24S8_UINT VIRGL_FORMAT_X24S8_UINT -#define PIPE_FORMAT_S8X24_UINT VIRGL_FORMAT_S8X24_UINT -#define PIPE_FORMAT_X32_S8X24_UINT VIRGL_FORMAT_X32_S8X24_UINT - -#define PIPE_FORMAT_B2G3R3_UNORM VIRGL_FORMAT_B2G3R3_UNORM -#define PIPE_FORMAT_L16A16_UNORM VIRGL_FORMAT_L16A16_UNORM -#define PIPE_FORMAT_A16_UNORM VIRGL_FORMAT_A16_UNORM -#define PIPE_FORMAT_I16_UNORM VIRGL_FORMAT_I16_UNORM - -#define PIPE_FORMAT_LATC1_UNORM VIRGL_FORMAT_LATC1_UNORM -#define PIPE_FORMAT_LATC1_SNORM VIRGL_FORMAT_LATC1_SNORM -#define PIPE_FORMAT_LATC2_UNORM VIRGL_FORMAT_LATC2_UNORM -#define PIPE_FORMAT_LATC2_SNORM VIRGL_FORMAT_LATC2_SNORM - -#define PIPE_FORMAT_A8_SNORM VIRGL_FORMAT_A8_SNORM -#define PIPE_FORMAT_L8_SNORM VIRGL_FORMAT_L8_SNORM -#define PIPE_FORMAT_L8A8_SNORM VIRGL_FORMAT_L8A8_SNORM -#define PIPE_FORMAT_I8_SNORM VIRGL_FORMAT_I8_SNORM -#define PIPE_FORMAT_A16_SNORM VIRGL_FORMAT_A16_SNORM -#define PIPE_FORMAT_L16_SNORM VIRGL_FORMAT_L16_SNORM -#define PIPE_FORMAT_L16A16_SNORM VIRGL_FORMAT_L16A16_SNORM -#define PIPE_FORMAT_I16_SNORM VIRGL_FORMAT_I16_SNORM - -#define PIPE_FORMAT_A16_FLOAT VIRGL_FORMAT_A16_FLOAT -#define PIPE_FORMAT_L16_FLOAT VIRGL_FORMAT_L16_FLOAT -#define PIPE_FORMAT_L16A16_FLOAT VIRGL_FORMAT_L16A16_FLOAT -#define PIPE_FORMAT_I16_FLOAT VIRGL_FORMAT_I16_FLOAT -#define PIPE_FORMAT_A32_FLOAT VIRGL_FORMAT_A32_FLOAT -#define PIPE_FORMAT_L32_FLOAT VIRGL_FORMAT_L32_FLOAT -#define PIPE_FORMAT_L32A32_FLOAT VIRGL_FORMAT_L32A32_FLOAT -#define PIPE_FORMAT_I32_FLOAT VIRGL_FORMAT_I32_FLOAT - -#define PIPE_FORMAT_YV12 VIRGL_FORMAT_YV12 -#define PIPE_FORMAT_YV16 VIRGL_FORMAT_YV16 -#define PIPE_FORMAT_IYUV VIRGL_FORMAT_IYUV /**< aka I420 */ -#define PIPE_FORMAT_NV12 VIRGL_FORMAT_NV12 -#define PIPE_FORMAT_NV21 VIRGL_FORMAT_NV21 - -#define PIPE_FORMAT_A4R4_UNORM VIRGL_FORMAT_A4R4_UNORM -#define PIPE_FORMAT_R4A4_UNORM VIRGL_FORMAT_R4A4_UNORM -#define PIPE_FORMAT_R8A8_UNORM VIRGL_FORMAT_R8A8_UNORM -#define PIPE_FORMAT_A8R8_UNORM VIRGL_FORMAT_A8R8_UNORM - -#define PIPE_FORMAT_R10G10B10A2_SSCALED VIRGL_FORMAT_R10G10B10A2_SSCALED -#define PIPE_FORMAT_R10G10B10A2_SNORM VIRGL_FORMAT_R10G10B10A2_SNORM - -#define PIPE_FORMAT_B10G10R10A2_USCALED VIRGL_FORMAT_B10G10R10A2_USCALED -#define PIPE_FORMAT_B10G10R10A2_SSCALED VIRGL_FORMAT_B10G10R10A2_SSCALED -#define PIPE_FORMAT_B10G10R10A2_SNORM VIRGL_FORMAT_B10G10R10A2_SNORM - -#define PIPE_FORMAT_R8_UINT VIRGL_FORMAT_R8_UINT -#define PIPE_FORMAT_R8G8_UINT VIRGL_FORMAT_R8G8_UINT -#define PIPE_FORMAT_R8G8B8_UINT VIRGL_FORMAT_R8G8B8_UINT -#define PIPE_FORMAT_R8G8B8A8_UINT VIRGL_FORMAT_R8G8B8A8_UINT - -#define PIPE_FORMAT_R8_SINT VIRGL_FORMAT_R8_SINT -#define PIPE_FORMAT_R8G8_SINT VIRGL_FORMAT_R8G8_SINT -#define PIPE_FORMAT_R8G8B8_SINT VIRGL_FORMAT_R8G8B8_SINT -#define PIPE_FORMAT_R8G8B8A8_SINT VIRGL_FORMAT_R8G8B8A8_SINT - -#define PIPE_FORMAT_R16_UINT VIRGL_FORMAT_R16_UINT -#define PIPE_FORMAT_R16G16_UINT VIRGL_FORMAT_R16G16_UINT -#define PIPE_FORMAT_R16G16B16_UINT VIRGL_FORMAT_R16G16B16_UINT -#define PIPE_FORMAT_R16G16B16A16_UINT VIRGL_FORMAT_R16G16B16A16_UINT - -#define PIPE_FORMAT_R16_SINT VIRGL_FORMAT_R16_SINT -#define PIPE_FORMAT_R16G16_SINT VIRGL_FORMAT_R16G16_SINT -#define PIPE_FORMAT_R16G16B16_SINT VIRGL_FORMAT_R16G16B16_SINT -#define PIPE_FORMAT_R16G16B16A16_SINT VIRGL_FORMAT_R16G16B16A16_SINT - -#define PIPE_FORMAT_R32_UINT VIRGL_FORMAT_R32_UINT -#define PIPE_FORMAT_R32G32_UINT VIRGL_FORMAT_R32G32_UINT -#define PIPE_FORMAT_R32G32B32_UINT VIRGL_FORMAT_R32G32B32_UINT -#define PIPE_FORMAT_R32G32B32A32_UINT VIRGL_FORMAT_R32G32B32A32_UINT - -#define PIPE_FORMAT_R32_SINT VIRGL_FORMAT_R32_SINT -#define PIPE_FORMAT_R32G32_SINT VIRGL_FORMAT_R32G32_SINT -#define PIPE_FORMAT_R32G32B32_SINT VIRGL_FORMAT_R32G32B32_SINT -#define PIPE_FORMAT_R32G32B32A32_SINT VIRGL_FORMAT_R32G32B32A32_SINT - -#define PIPE_FORMAT_A8_UINT VIRGL_FORMAT_A8_UINT -#define PIPE_FORMAT_I8_UINT VIRGL_FORMAT_I8_UINT -#define PIPE_FORMAT_L8_UINT VIRGL_FORMAT_L8_UINT -#define PIPE_FORMAT_L8A8_UINT VIRGL_FORMAT_L8A8_UINT - -#define PIPE_FORMAT_A8_SINT VIRGL_FORMAT_A8_SINT -#define PIPE_FORMAT_I8_SINT VIRGL_FORMAT_I8_SINT -#define PIPE_FORMAT_L8_SINT VIRGL_FORMAT_L8_SINT -#define PIPE_FORMAT_L8A8_SINT VIRGL_FORMAT_L8A8_SINT - -#define PIPE_FORMAT_A16_UINT VIRGL_FORMAT_A16_UINT -#define PIPE_FORMAT_I16_UINT VIRGL_FORMAT_I16_UINT -#define PIPE_FORMAT_L16_UINT VIRGL_FORMAT_L16_UINT -#define PIPE_FORMAT_L16A16_UINT VIRGL_FORMAT_L16A16_UINT - -#define PIPE_FORMAT_A16_SINT VIRGL_FORMAT_A16_SINT -#define PIPE_FORMAT_I16_SINT VIRGL_FORMAT_I16_SINT -#define PIPE_FORMAT_L16_SINT VIRGL_FORMAT_L16_SINT -#define PIPE_FORMAT_L16A16_SINT VIRGL_FORMAT_L16A16_SINT - -#define PIPE_FORMAT_A32_UINT VIRGL_FORMAT_A32_UINT -#define PIPE_FORMAT_I32_UINT VIRGL_FORMAT_I32_UINT -#define PIPE_FORMAT_L32_UINT VIRGL_FORMAT_L32_UINT -#define PIPE_FORMAT_L32A32_UINT VIRGL_FORMAT_L32A32_UINT - -#define PIPE_FORMAT_A32_SINT VIRGL_FORMAT_A32_SINT -#define PIPE_FORMAT_I32_SINT VIRGL_FORMAT_I32_SINT -#define PIPE_FORMAT_L32_SINT VIRGL_FORMAT_L32_SINT -#define PIPE_FORMAT_L32A32_SINT VIRGL_FORMAT_L32A32_SINT - -#define PIPE_FORMAT_B10G10R10A2_UINT VIRGL_FORMAT_B10G10R10A2_UINT - -#define PIPE_FORMAT_ETC1_RGB8 VIRGL_FORMAT_ETC1_RGB8 - -#define PIPE_FORMAT_R8G8_R8B8_UNORM VIRGL_FORMAT_R8G8_R8B8_UNORM -#define PIPE_FORMAT_G8R8_B8R8_UNORM VIRGL_FORMAT_G8R8_B8R8_UNORM - -#define PIPE_FORMAT_R8G8B8X8_SNORM VIRGL_FORMAT_R8G8B8X8_SNORM -#define PIPE_FORMAT_R8G8B8X8_SRGB VIRGL_FORMAT_R8G8B8X8_SRGB -#define PIPE_FORMAT_R8G8B8X8_UINT VIRGL_FORMAT_R8G8B8X8_UINT -#define PIPE_FORMAT_R8G8B8X8_SINT VIRGL_FORMAT_R8G8B8X8_SINT -#define PIPE_FORMAT_B10G10R10X2_UNORM VIRGL_FORMAT_B10G10R10X2_UNORM -#define PIPE_FORMAT_R16G16B16X16_UNORM VIRGL_FORMAT_R16G16B16X16_UNORM -#define PIPE_FORMAT_R16G16B16X16_SNORM VIRGL_FORMAT_R16G16B16X16_SNORM -#define PIPE_FORMAT_R16G16B16X16_FLOAT VIRGL_FORMAT_R16G16B16X16_FLOAT -#define PIPE_FORMAT_R16G16B16X16_UINT VIRGL_FORMAT_R16G16B16X16_UINT -#define PIPE_FORMAT_R16G16B16X16_SINT VIRGL_FORMAT_R16G16B16X16_SINT -#define PIPE_FORMAT_R32G32B32X32_FLOAT VIRGL_FORMAT_R32G32B32X32_FLOAT -#define PIPE_FORMAT_R32G32B32X32_UINT VIRGL_FORMAT_R32G32B32X32_UINT -#define PIPE_FORMAT_R32G32B32X32_SINT VIRGL_FORMAT_R32G32B32X32_SINT - -#define PIPE_FORMAT_R8A8_SNORM VIRGL_FORMAT_R8A8_SNORM -#define PIPE_FORMAT_R16A16_UNORM VIRGL_FORMAT_R16A16_UNORM -#define PIPE_FORMAT_R16A16_SNORM VIRGL_FORMAT_R16A16_SNORM -#define PIPE_FORMAT_R16A16_FLOAT VIRGL_FORMAT_R16A16_FLOAT -#define PIPE_FORMAT_R32A32_FLOAT VIRGL_FORMAT_R32A32_FLOAT -#define PIPE_FORMAT_R8A8_UINT VIRGL_FORMAT_R8A8_UINT -#define PIPE_FORMAT_R8A8_SINT VIRGL_FORMAT_R8A8_SINT -#define PIPE_FORMAT_R16A16_UINT VIRGL_FORMAT_R16A16_UINT -#define PIPE_FORMAT_R16A16_SINT VIRGL_FORMAT_R16A16_SINT -#define PIPE_FORMAT_R32A32_UINT VIRGL_FORMAT_R32A32_UINT -#define PIPE_FORMAT_R32A32_SINT VIRGL_FORMAT_R32A32_SINT -#define PIPE_FORMAT_R10G10B10A2_UINT VIRGL_FORMAT_R10G10B10A2_UINT - -#define PIPE_FORMAT_B5G6R5_SRGB VIRGL_FORMAT_B5G6R5_SRGB - -#define PIPE_FORMAT_BPTC_RGBA_UNORM VIRGL_FORMAT_BPTC_RGBA_UNORM -#define PIPE_FORMAT_BPTC_SRGBA VIRGL_FORMAT_BPTC_SRGBA -#define PIPE_FORMAT_BPTC_RGB_FLOAT VIRGL_FORMAT_BPTC_RGB_FLOAT -#define PIPE_FORMAT_BPTC_RGB_UFLOAT VIRGL_FORMAT_BPTC_RGB_UFLOAT - -#define PIPE_FORMAT_A8L8_UNORM VIRGL_FORMAT_A8L8_UNORM -#define PIPE_FORMAT_A8L8_SNORM VIRGL_FORMAT_A8L8_SNORM -#define PIPE_FORMAT_A8L8_SRGB VIRGL_FORMAT_A8L8_SRGB -#define PIPE_FORMAT_A16L16_UNORM VIRGL_FORMAT_A16L16_UNORM - -#define PIPE_FORMAT_G8R8_UNORM VIRGL_FORMAT_G8R8_UNORM -#define PIPE_FORMAT_G8R8_SNORM VIRGL_FORMAT_G8R8_SNORM -#define PIPE_FORMAT_G16R16_UNORM VIRGL_FORMAT_G16R16_UNORM -#define PIPE_FORMAT_G16R16_SNORM VIRGL_FORMAT_G16R16_SNORM - -#define PIPE_FORMAT_A8B8G8R8_SNORM VIRGL_FORMAT_A8B8G8R8_SNORM -#define PIPE_FORMAT_X8B8G8R8_SNORM VIRGL_FORMAT_X8B8G8R8_SNORM - -#define PIPE_FORMAT_ETC2_RGB8 VIRGL_FORMAT_ETC2_RGB8 -#define PIPE_FORMAT_ETC2_SRGB8 VIRGL_FORMAT_ETC2_SRGB8 -#define PIPE_FORMAT_ETC2_RGB8A1 VIRGL_FORMAT_ETC2_RGB8A1 -#define PIPE_FORMAT_ETC2_SRGB8A1 VIRGL_FORMAT_ETC2_SRGB8A1 -#define PIPE_FORMAT_ETC2_RGBA8 VIRGL_FORMAT_ETC2_RGBA8 -#define PIPE_FORMAT_ETC2_SRGBA8 VIRGL_FORMAT_ETC2_SRGBA8 -#define PIPE_FORMAT_ETC2_R11_UNORM VIRGL_FORMAT_ETC2_R11_UNORM -#define PIPE_FORMAT_ETC2_R11_SNORM VIRGL_FORMAT_ETC2_R11_SNORM -#define PIPE_FORMAT_ETC2_RG11_UNORM VIRGL_FORMAT_ETC2_RG11_UNORM -#define PIPE_FORMAT_ETC2_RG11_SNORM VIRGL_FORMAT_ETC2_RG11_SNORM - -#define PIPE_FORMAT_ASTC_4x4 VIRGL_FORMAT_ASTC_4x4 -#define PIPE_FORMAT_ASTC_5x4 VIRGL_FORMAT_ASTC_5x4 -#define PIPE_FORMAT_ASTC_5x5 VIRGL_FORMAT_ASTC_5x5 -#define PIPE_FORMAT_ASTC_6x5 VIRGL_FORMAT_ASTC_6x5 -#define PIPE_FORMAT_ASTC_6x6 VIRGL_FORMAT_ASTC_6x6 -#define PIPE_FORMAT_ASTC_8x5 VIRGL_FORMAT_ASTC_8x5 -#define PIPE_FORMAT_ASTC_8x6 VIRGL_FORMAT_ASTC_8x6 -#define PIPE_FORMAT_ASTC_8x8 VIRGL_FORMAT_ASTC_8x8 -#define PIPE_FORMAT_ASTC_10x5 VIRGL_FORMAT_ASTC_10x5 -#define PIPE_FORMAT_ASTC_10x6 VIRGL_FORMAT_ASTC_10x6 -#define PIPE_FORMAT_ASTC_10x8 VIRGL_FORMAT_ASTC_10x8 -#define PIPE_FORMAT_ASTC_10x10 VIRGL_FORMAT_ASTC_10x10 -#define PIPE_FORMAT_ASTC_12x10 VIRGL_FORMAT_ASTC_12x10 -#define PIPE_FORMAT_ASTC_12x12 VIRGL_FORMAT_ASTC_12x12 - -#define PIPE_FORMAT_ASTC_4x4_SRGB VIRGL_FORMAT_ASTC_4x4_SRGB -#define PIPE_FORMAT_ASTC_5x4_SRGB VIRGL_FORMAT_ASTC_5x4_SRGB -#define PIPE_FORMAT_ASTC_5x5_SRGB VIRGL_FORMAT_ASTC_5x5_SRGB -#define PIPE_FORMAT_ASTC_6x5_SRGB VIRGL_FORMAT_ASTC_6x5_SRGB -#define PIPE_FORMAT_ASTC_6x6_SRGB VIRGL_FORMAT_ASTC_6x6_SRGB -#define PIPE_FORMAT_ASTC_8x5_SRGB VIRGL_FORMAT_ASTC_8x5_SRGB -#define PIPE_FORMAT_ASTC_8x6_SRGB VIRGL_FORMAT_ASTC_8x6_SRGB -#define PIPE_FORMAT_ASTC_8x8_SRGB VIRGL_FORMAT_ASTC_8x8_SRGB -#define PIPE_FORMAT_ASTC_10x5_SRGB VIRGL_FORMAT_ASTC_10x5_SRGB -#define PIPE_FORMAT_ASTC_10x6_SRGB VIRGL_FORMAT_ASTC_10x6_SRGB -#define PIPE_FORMAT_ASTC_10x8_SRGB VIRGL_FORMAT_ASTC_10x8_SRGB -#define PIPE_FORMAT_ASTC_10x10_SRGB VIRGL_FORMAT_ASTC_10x10_SRGB -#define PIPE_FORMAT_ASTC_12x10_SRGB VIRGL_FORMAT_ASTC_12x10_SRGB -#define PIPE_FORMAT_ASTC_12x12_SRGB VIRGL_FORMAT_ASTC_12x12_SRGB - -#define PIPE_FORMAT_P016 VIRGL_FORMAT_P016 - -#define PIPE_FORMAT_R10G10B10X2_UNORM VIRGL_FORMAT_R10G10B10X2_UNORM -#define PIPE_FORMAT_A1B5G5R5_UNORM VIRGL_FORMAT_A1B5G5R5_UNORM -#define PIPE_FORMAT_X1B5G5R5_UNORM VIRGL_FORMAT_X1B5G5R5_UNORM -#define PIPE_FORMAT_A4B4G4R4_UNORM VIRGL_FORMAT_A4B4G4R4_UNORM - -#define PIPE_FORMAT_R8_SRGB VIRGL_FORMAT_R8_SRGB - -#define PIPE_FORMAT_COUNT VIRGL_FORMAT_MAX - -#if defined(PIPE_ARCH_LITTLE_ENDIAN) -#define PIPE_FORMAT_RGBA8888_UNORM PIPE_FORMAT_R8G8B8A8_UNORM -#define PIPE_FORMAT_RGBX8888_UNORM PIPE_FORMAT_R8G8B8X8_UNORM -#define PIPE_FORMAT_BGRA8888_UNORM PIPE_FORMAT_B8G8R8A8_UNORM -#define PIPE_FORMAT_BGRX8888_UNORM PIPE_FORMAT_B8G8R8X8_UNORM -#define PIPE_FORMAT_ARGB8888_UNORM PIPE_FORMAT_A8R8G8B8_UNORM -#define PIPE_FORMAT_XRGB8888_UNORM PIPE_FORMAT_X8R8G8B8_UNORM -#define PIPE_FORMAT_ABGR8888_UNORM PIPE_FORMAT_A8B8G8R8_UNORM -#define PIPE_FORMAT_XBGR8888_UNORM PIPE_FORMAT_X8B8G8R8_UNORM -#define PIPE_FORMAT_RGBA8888_SNORM PIPE_FORMAT_R8G8B8A8_SNORM -#define PIPE_FORMAT_RGBX8888_SNORM PIPE_FORMAT_R8G8B8X8_SNORM -#define PIPE_FORMAT_ABGR8888_SNORM PIPE_FORMAT_A8B8G8R8_SNORM -#define PIPE_FORMAT_XBGR8888_SNORM PIPE_FORMAT_X8B8G8R8_SNORM -#define PIPE_FORMAT_RGBA8888_SRGB PIPE_FORMAT_R8G8B8A8_SRGB -#define PIPE_FORMAT_RGBX8888_SRGB PIPE_FORMAT_R8G8B8X8_SRGB -#define PIPE_FORMAT_BGRA8888_SRGB PIPE_FORMAT_B8G8R8A8_SRGB -#define PIPE_FORMAT_BGRX8888_SRGB PIPE_FORMAT_B8G8R8X8_SRGB -#define PIPE_FORMAT_ARGB8888_SRGB PIPE_FORMAT_A8R8G8B8_SRGB -#define PIPE_FORMAT_XRGB8888_SRGB PIPE_FORMAT_X8R8G8B8_SRGB -#define PIPE_FORMAT_ABGR8888_SRGB PIPE_FORMAT_A8B8G8R8_SRGB -#define PIPE_FORMAT_XBGR8888_SRGB PIPE_FORMAT_X8B8G8R8_SRGB -#define PIPE_FORMAT_LA88_UNORM PIPE_FORMAT_L8A8_UNORM -#define PIPE_FORMAT_AL88_UNORM PIPE_FORMAT_A8L8_UNORM -#define PIPE_FORMAT_LA88_SNORM PIPE_FORMAT_L8A8_SNORM -#define PIPE_FORMAT_AL88_SNORM PIPE_FORMAT_A8L8_SNORM -#define PIPE_FORMAT_LA88_SRGB PIPE_FORMAT_L8A8_SRGB -#define PIPE_FORMAT_AL88_SRGB PIPE_FORMAT_A8L8_SRGB -#define PIPE_FORMAT_LA1616_UNORM PIPE_FORMAT_L16A16_UNORM -#define PIPE_FORMAT_AL1616_UNORM PIPE_FORMAT_A16L16_UNORM -#define PIPE_FORMAT_RG88_UNORM PIPE_FORMAT_R8G8_UNORM -#define PIPE_FORMAT_GR88_UNORM PIPE_FORMAT_G8R8_UNORM -#define PIPE_FORMAT_RG88_SNORM PIPE_FORMAT_R8G8_SNORM -#define PIPE_FORMAT_GR88_SNORM PIPE_FORMAT_G8R8_SNORM -#define PIPE_FORMAT_RG1616_UNORM PIPE_FORMAT_R16G16_UNORM -#define PIPE_FORMAT_GR1616_UNORM PIPE_FORMAT_G16R16_UNORM -#define PIPE_FORMAT_RG1616_SNORM PIPE_FORMAT_R16G16_SNORM -#define PIPE_FORMAT_GR1616_SNORM PIPE_FORMAT_G16R16_SNORM -#elif defined(PIPE_ARCH_BIG_ENDIAN) -#define PIPE_FORMAT_ABGR8888_UNORM PIPE_FORMAT_R8G8B8A8_UNORM -#define PIPE_FORMAT_XBGR8888_UNORM PIPE_FORMAT_R8G8B8X8_UNORM -#define PIPE_FORMAT_ARGB8888_UNORM PIPE_FORMAT_B8G8R8A8_UNORM -#define PIPE_FORMAT_XRGB8888_UNORM PIPE_FORMAT_B8G8R8X8_UNORM -#define PIPE_FORMAT_BGRA8888_UNORM PIPE_FORMAT_A8R8G8B8_UNORM -#define PIPE_FORMAT_BGRX8888_UNORM PIPE_FORMAT_X8R8G8B8_UNORM -#define PIPE_FORMAT_RGBA8888_UNORM PIPE_FORMAT_A8B8G8R8_UNORM -#define PIPE_FORMAT_RGBX8888_UNORM PIPE_FORMAT_X8B8G8R8_UNORM -#define PIPE_FORMAT_ABGR8888_SNORM PIPE_FORMAT_R8G8B8A8_SNORM -#define PIPE_FORMAT_XBGR8888_SNORM PIPE_FORMAT_R8G8B8X8_SNORM -#define PIPE_FORMAT_RGBA8888_SNORM PIPE_FORMAT_A8B8G8R8_SNORM -#define PIPE_FORMAT_RGBX8888_SNORM PIPE_FORMAT_X8B8G8R8_SNORM -#define PIPE_FORMAT_ABGR8888_SRGB PIPE_FORMAT_R8G8B8A8_SRGB -#define PIPE_FORMAT_XBGR8888_SRGB PIPE_FORMAT_R8G8B8X8_SRGB -#define PIPE_FORMAT_ARGB8888_SRGB PIPE_FORMAT_B8G8R8A8_SRGB -#define PIPE_FORMAT_XRGB8888_SRGB PIPE_FORMAT_B8G8R8X8_SRGB -#define PIPE_FORMAT_BGRA8888_SRGB PIPE_FORMAT_A8R8G8B8_SRGB -#define PIPE_FORMAT_BGRX8888_SRGB PIPE_FORMAT_X8R8G8B8_SRGB -#define PIPE_FORMAT_RGBA8888_SRGB PIPE_FORMAT_A8B8G8R8_SRGB -#define PIPE_FORMAT_RGBX8888_SRGB PIPE_FORMAT_X8B8G8R8_SRGB -#define PIPE_FORMAT_LA88_UNORM PIPE_FORMAT_A8L8_UNORM -#define PIPE_FORMAT_AL88_UNORM PIPE_FORMAT_L8A8_UNORM -#define PIPE_FORMAT_LA88_SNORM PIPE_FORMAT_A8L8_SNORM -#define PIPE_FORMAT_AL88_SNORM PIPE_FORMAT_L8A8_SNORM -#define PIPE_FORMAT_LA88_SRGB PIPE_FORMAT_A8L8_SRGB -#define PIPE_FORMAT_AL88_SRGB PIPE_FORMAT_L8A8_SRGB -#define PIPE_FORMAT_LA1616_UNORM PIPE_FORMAT_A16L16_UNORM -#define PIPE_FORMAT_AL1616_UNORM PIPE_FORMAT_L16A16_UNORM -#define PIPE_FORMAT_RG88_UNORM PIPE_FORMAT_G8R8_UNORM -#define PIPE_FORMAT_GR88_UNORM PIPE_FORMAT_R8G8_UNORM -#define PIPE_FORMAT_RG88_SNORM PIPE_FORMAT_G8R8_SNORM -#define PIPE_FORMAT_GR88_SNORM PIPE_FORMAT_R8G8_SNORM -#define PIPE_FORMAT_RG1616_UNORM PIPE_FORMAT_G16R16_UNORM -#define PIPE_FORMAT_GR1616_UNORM PIPE_FORMAT_R16G16_UNORM -#define PIPE_FORMAT_RG1616_SNORM PIPE_FORMAT_G16R16_SNORM -#define PIPE_FORMAT_GR1616_SNORM PIPE_FORMAT_R16G16_SNORM -#endif - -enum pipe_video_chroma_format { - PIPE_VIDEO_CHROMA_FORMAT_400, - PIPE_VIDEO_CHROMA_FORMAT_420, - PIPE_VIDEO_CHROMA_FORMAT_422, - PIPE_VIDEO_CHROMA_FORMAT_444, - PIPE_VIDEO_CHROMA_FORMAT_NONE -}; - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/app/src/main/cpp/virglrenderer/src/gallium/include/pipe/p_screen.h b/app/src/main/cpp/virglrenderer/src/gallium/include/pipe/p_screen.h deleted file mode 100644 index 4cc64f025..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/include/pipe/p_screen.h +++ /dev/null @@ -1,213 +0,0 @@ -/************************************************************************** - * - * Copyright 2007 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -/** - * @file - * - * Screen, Adapter or GPU - * - * These are driver functions/facilities that are context independent. - */ - -#ifndef P_SCREEN_H -#define P_SCREEN_H - -#include "pipe/p_compiler.h" -#include "pipe/p_defines.h" -#include "pipe/p_format.h" -#include "pipe/p_video_enums.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** Opaque types */ -struct winsys_handle; -struct pipe_fence_handle; -struct pipe_resource; -struct pipe_surface; -struct pipe_transfer; -struct pipe_box; - -/** - * Gallium screen/adapter context. Basically everything - * hardware-specific that doesn't actually require a rendering - * context. - */ -struct pipe_screen { - void (*destroy)(struct pipe_screen *); - - const char *(*get_name)(struct pipe_screen *); - - const char *(*get_vendor)(struct pipe_screen *); - - /** - * Query an integer-valued capability/parameter/limit - * \param param one of PIPE_CAP_x - */ - int (*get_param)(struct pipe_screen *, enum pipe_cap param); - - /** - * Query a float-valued capability/parameter/limit - * \param param one of PIPE_CAP_x - */ - float (*get_paramf)(struct pipe_screen *, enum pipe_capf param); - - /** - * Query a per-shader-stage integer-valued capability/parameter/limit - * \param param one of PIPE_CAP_x - */ - int (*get_shader_param)(struct pipe_screen *, unsigned shader, - enum pipe_shader_cap param); - - /** - * Query an integer-valued capability/parameter/limit for a codec/profile - * \param param one of PIPE_VIDEO_CAP_x - */ - int (*get_video_param)(struct pipe_screen *, enum pipe_video_profile profile, - enum pipe_video_entrypoint entrypoint, - enum pipe_video_cap param); - - /** - * Query a compute-specific capability/parameter/limit. - * \param param one of PIPE_COMPUTE_CAP_x - * \param ret pointer to a preallocated buffer that will be - * initialized to the parameter value, or NULL. - * \return size in bytes of the parameter value that would be - * returned. - */ - int (*get_compute_param)(struct pipe_screen *, enum pipe_compute_cap param, - void *ret); - - /** - * Query a timestamp in nanoseconds. The returned value should match - * PIPE_QUERY_TIMESTAMP. This function returns immediately and doesn't - * wait for rendering to complete (which cannot be achieved with queries). - */ - uint64_t (*get_timestamp)(struct pipe_screen *); - - struct pipe_context *(*context_create)(struct pipe_screen *, void *priv); - - /** - * Check if the given pipe_format is supported as a texture or - * drawing surface. - * \param bindings bitmask of PIPE_BIND_* - */ - boolean (*is_format_supported)(struct pipe_screen *, enum pipe_format format, - enum pipe_texture_target target, - unsigned sample_count, unsigned bindings); - - /** - * Check if the given pipe_format is supported as output for this - * codec/profile. - * \param profile profile to check, may also be PIPE_VIDEO_PROFILE_UNKNOWN - */ - boolean (*is_video_format_supported)(struct pipe_screen *, - enum pipe_format format, - enum pipe_video_profile profile, - enum pipe_video_entrypoint entrypoint); - - /** - * Check if we can actually create the given resource (test the dimension, - * overall size, etc). Used to implement proxy textures. - * \return TRUE if size is OK, FALSE if too large. - */ - boolean (*can_create_resource)(struct pipe_screen *screen, - const struct pipe_resource *templat); - - /** - * Create a new texture object, using the given template info. - */ - struct pipe_resource *(*resource_create)(struct pipe_screen *, - const struct pipe_resource *templat); - - /** - * Create a texture from a winsys_handle. The handle is often created in - * another process by first creating a pipe texture and then calling - * resource_get_handle. - */ - struct pipe_resource *(*resource_from_handle)( - struct pipe_screen *, const struct pipe_resource *templat, - struct winsys_handle *handle); - - /** - * Get a winsys_handle from a texture. Some platforms/winsys requires - * that the texture is created with a special usage flag like - * DISPLAYTARGET or PRIMARY. - */ - boolean (*resource_get_handle)(struct pipe_screen *, - struct pipe_resource *tex, - struct winsys_handle *handle); - - void (*resource_destroy)(struct pipe_screen *, struct pipe_resource *pt); - - /** - * Do any special operations to ensure frontbuffer contents are - * displayed, eg copy fake frontbuffer. - * \param winsys_drawable_handle an opaque handle that the calling context - * gets out-of-band - * \param subbox an optional sub region to flush - */ - void (*flush_frontbuffer)(struct pipe_screen *screen, - struct pipe_resource *resource, unsigned level, - unsigned layer, void *winsys_drawable_handle, - struct pipe_box *subbox); - - /** Set ptr = fence, with reference counting */ - void (*fence_reference)(struct pipe_screen *screen, - struct pipe_fence_handle **ptr, - struct pipe_fence_handle *fence); - - /** - * Checks whether the fence has been signalled. - */ - boolean (*fence_signalled)(struct pipe_screen *screen, - struct pipe_fence_handle *fence); - - /** - * Wait for the fence to finish. - * \param timeout in nanoseconds (may be PIPE_TIMEOUT_INFINITE). - */ - boolean (*fence_finish)(struct pipe_screen *screen, - struct pipe_fence_handle *fence, uint64_t timeout); - - /** - * Returns a driver-specific query. - * - * If \p info is NULL, the number of available queries is returned. - * Otherwise, the driver query at the specified \p index is returned - * in \p info. The function returns non-zero on success. - */ - int (*get_driver_query_info)(struct pipe_screen *screen, unsigned index, - struct pipe_driver_query_info *info); -}; - -#ifdef __cplusplus -} -#endif - -#endif /* P_SCREEN_H */ diff --git a/app/src/main/cpp/virglrenderer/src/gallium/include/pipe/p_shader_tokens.h b/app/src/main/cpp/virglrenderer/src/gallium/include/pipe/p_shader_tokens.h deleted file mode 100644 index f2c0a2915..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/include/pipe/p_shader_tokens.h +++ /dev/null @@ -1,780 +0,0 @@ -/************************************************************************** - * - * Copyright 2008 VMware, Inc. - * Copyright 2009-2010 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -#ifndef P_SHADER_TOKENS_H -#define P_SHADER_TOKENS_H - -#ifdef __cplusplus -extern "C" { -#endif - -struct tgsi_header { - unsigned HeaderSize : 8; - unsigned BodySize : 24; -}; - -#define TGSI_PROCESSOR_FRAGMENT 0 -#define TGSI_PROCESSOR_VERTEX 1 -#define TGSI_PROCESSOR_GEOMETRY 2 -#define TGSI_PROCESSOR_TESS_CTRL 3 -#define TGSI_PROCESSOR_TESS_EVAL 4 -#define TGSI_PROCESSOR_COMPUTE 5 - -struct tgsi_processor { - unsigned Processor : 4; /* TGSI_PROCESSOR_ */ - unsigned Padding : 28; -}; - -#define TGSI_TOKEN_TYPE_DECLARATION 0 -#define TGSI_TOKEN_TYPE_IMMEDIATE 1 -#define TGSI_TOKEN_TYPE_INSTRUCTION 2 -#define TGSI_TOKEN_TYPE_PROPERTY 3 - -struct tgsi_token { - unsigned Type : 4; /**< TGSI_TOKEN_TYPE_x */ - unsigned NrTokens : 8; /**< UINT */ - unsigned Padding : 20; -}; - -enum tgsi_file_type { - TGSI_FILE_NULL = 0, - TGSI_FILE_CONSTANT = 1, - TGSI_FILE_INPUT = 2, - TGSI_FILE_OUTPUT = 3, - TGSI_FILE_TEMPORARY = 4, - TGSI_FILE_SAMPLER = 5, - TGSI_FILE_ADDRESS = 6, - TGSI_FILE_IMMEDIATE = 7, - TGSI_FILE_PREDICATE = 8, - TGSI_FILE_SYSTEM_VALUE = 9, - TGSI_FILE_IMAGE = 10, - TGSI_FILE_SAMPLER_VIEW = 11, - TGSI_FILE_BUFFER, - TGSI_FILE_MEMORY, - TGSI_FILE_HW_ATOMIC, - TGSI_FILE_COUNT /**< how many TGSI_FILE_ types */ -}; - -#define TGSI_WRITEMASK_NONE 0x00 -#define TGSI_WRITEMASK_X 0x01 -#define TGSI_WRITEMASK_Y 0x02 -#define TGSI_WRITEMASK_XY 0x03 -#define TGSI_WRITEMASK_Z 0x04 -#define TGSI_WRITEMASK_XZ 0x05 -#define TGSI_WRITEMASK_YZ 0x06 -#define TGSI_WRITEMASK_XYZ 0x07 -#define TGSI_WRITEMASK_W 0x08 -#define TGSI_WRITEMASK_XW 0x09 -#define TGSI_WRITEMASK_YW 0x0A -#define TGSI_WRITEMASK_XYW 0x0B -#define TGSI_WRITEMASK_ZW 0x0C -#define TGSI_WRITEMASK_XZW 0x0D -#define TGSI_WRITEMASK_YZW 0x0E -#define TGSI_WRITEMASK_XYZW 0x0F - -#define TGSI_INTERPOLATE_CONSTANT 0 -#define TGSI_INTERPOLATE_LINEAR 1 -#define TGSI_INTERPOLATE_PERSPECTIVE 2 -#define TGSI_INTERPOLATE_COLOR 3 /* special color case for smooth/flat */ -#define TGSI_INTERPOLATE_COUNT 4 - -#define TGSI_INTERPOLATE_LOC_CENTER 0 -#define TGSI_INTERPOLATE_LOC_CENTROID 1 -#define TGSI_INTERPOLATE_LOC_SAMPLE 2 -#define TGSI_INTERPOLATE_LOC_COUNT 3 - -#define TGSI_CYLINDRICAL_WRAP_X (1 << 0) -#define TGSI_CYLINDRICAL_WRAP_Y (1 << 1) -#define TGSI_CYLINDRICAL_WRAP_Z (1 << 2) -#define TGSI_CYLINDRICAL_WRAP_W (1 << 3) - -enum tgsi_memory_type { - TGSI_MEMORY_TYPE_GLOBAL, /* OpenCL global */ - TGSI_MEMORY_TYPE_SHARED, /* OpenCL local / GLSL shared */ - TGSI_MEMORY_TYPE_PRIVATE, /* OpenCL private */ - TGSI_MEMORY_TYPE_INPUT, /* OpenCL kernel input params */ - TGSI_MEMORY_TYPE_COUNT, -}; - -struct tgsi_declaration { - unsigned Type : 4; /**< TGSI_TOKEN_TYPE_DECLARATION */ - unsigned NrTokens : 8; /**< UINT */ - unsigned File : 4; /**< one of TGSI_FILE_x */ - unsigned UsageMask : 4; /**< bitmask of TGSI_WRITEMASK_x flags */ - unsigned Dimension : 1; /**< any extra dimension info? */ - unsigned Semantic : 1; /**< BOOL, any semantic info? */ - unsigned Interpolate : 1; /**< any interpolation info? */ - unsigned Invariant : 1; /**< invariant optimization? */ - unsigned Local : 1; /**< optimize as subroutine local variable? */ - unsigned Array : 1; /**< extra array info? */ - unsigned Atomic : 1; /**< atomic only? for TGSI_FILE_BUFFER */ - unsigned MemType : 2; /**< TGSI_MEMORY_TYPE_x for TGSI_FILE_MEMORY */ - unsigned Padding : 3; -}; - -struct tgsi_declaration_range { - unsigned First : 16; /**< UINT */ - unsigned Last : 16; /**< UINT */ -}; - -struct tgsi_declaration_dimension { - unsigned Index2D : 16; /**< UINT */ - unsigned Padding : 16; -}; - -struct tgsi_declaration_interp { - unsigned Interpolate : 4; /**< one of TGSI_INTERPOLATE_x */ - unsigned Location : 2; /**< one of TGSI_INTERPOLATE_LOC_x */ - unsigned CylindricalWrap : 4; /**< TGSI_CYLINDRICAL_WRAP_x flags */ - unsigned Padding : 22; -}; - -#define TGSI_SEMANTIC_POSITION 0 -#define TGSI_SEMANTIC_COLOR 1 -#define TGSI_SEMANTIC_BCOLOR 2 /**< back-face color */ -#define TGSI_SEMANTIC_FOG 3 -#define TGSI_SEMANTIC_PSIZE 4 -#define TGSI_SEMANTIC_GENERIC 5 -#define TGSI_SEMANTIC_NORMAL 6 -#define TGSI_SEMANTIC_FACE 7 -#define TGSI_SEMANTIC_EDGEFLAG 8 -#define TGSI_SEMANTIC_PRIMID 9 -#define TGSI_SEMANTIC_INSTANCEID 10 /**< doesn't include start_instance */ -#define TGSI_SEMANTIC_VERTEXID 11 -#define TGSI_SEMANTIC_STENCIL 12 -#define TGSI_SEMANTIC_CLIPDIST 13 -#define TGSI_SEMANTIC_CLIPVERTEX 14 -#define TGSI_SEMANTIC_GRID_SIZE 15 /**< grid size in blocks */ -#define TGSI_SEMANTIC_BLOCK_ID 16 /**< id of the current block */ -#define TGSI_SEMANTIC_BLOCK_SIZE 17 /**< block size in threads */ -#define TGSI_SEMANTIC_THREAD_ID \ - 18 /**< block-relative id of the current thread */ -#define TGSI_SEMANTIC_TEXCOORD 19 /**< texture or sprite coordinates */ -#define TGSI_SEMANTIC_PCOORD 20 /**< point sprite coordinate */ -#define TGSI_SEMANTIC_VIEWPORT_INDEX 21 /**< viewport index */ -#define TGSI_SEMANTIC_LAYER 22 /**< layer (rendertarget index) */ -#define TGSI_SEMANTIC_CULLDIST 23 -#define TGSI_SEMANTIC_SAMPLEID 24 -#define TGSI_SEMANTIC_SAMPLEPOS 25 -#define TGSI_SEMANTIC_SAMPLEMASK 26 -#define TGSI_SEMANTIC_INVOCATIONID 27 -#define TGSI_SEMANTIC_VERTEXID_NOBASE 28 -#define TGSI_SEMANTIC_BASEVERTEX 29 -#define TGSI_SEMANTIC_PATCH 30 /**< generic per-patch semantic */ -#define TGSI_SEMANTIC_TESSCOORD 31 /**< coordinate being processed by tess */ -#define TGSI_SEMANTIC_TESSOUTER 32 /**< outer tessellation levels */ -#define TGSI_SEMANTIC_TESSINNER 33 /**< inner tessellation levels */ -#define TGSI_SEMANTIC_VERTICESIN 34 /**< number of input vertices */ -#define TGSI_SEMANTIC_HELPER_INVOCATION 35 /**< current invocation is helper \ - */ -#define TGSI_SEMANTIC_COUNT 36 /**< number of semantic values */ - -struct tgsi_declaration_semantic { - unsigned Name : 8; /**< one of TGSI_SEMANTIC_x */ - unsigned Index : 16; /**< UINT */ - unsigned StreamX : 2; /**< vertex stream (for GS output) */ - unsigned StreamY : 2; - unsigned StreamZ : 2; - unsigned StreamW : 2; -}; - -struct tgsi_declaration_image { - unsigned Resource : 8; /**< one of TGSI_TEXTURE_ */ - unsigned Raw : 1; - unsigned Writable : 1; - unsigned Format : 10; /**< one of PIPE_FORMAT_ */ - unsigned Padding : 12; -}; - -enum tgsi_return_type { - TGSI_RETURN_TYPE_UNORM = 0, - TGSI_RETURN_TYPE_SNORM, - TGSI_RETURN_TYPE_SINT, - TGSI_RETURN_TYPE_UINT, - TGSI_RETURN_TYPE_FLOAT, - TGSI_RETURN_TYPE_COUNT -}; - -struct tgsi_declaration_sampler_view { - unsigned Resource : 8; /**< one of TGSI_TEXTURE_ */ - unsigned ReturnTypeX : 6; /**< one of enum tgsi_return_type */ - unsigned ReturnTypeY : 6; /**< one of enum tgsi_return_type */ - unsigned ReturnTypeZ : 6; /**< one of enum tgsi_return_type */ - unsigned ReturnTypeW : 6; /**< one of enum tgsi_return_type */ -}; - -struct tgsi_declaration_array { - unsigned ArrayID : 10; - unsigned Padding : 22; -}; - -/* - * Special resources that don't need to be declared. They map to the - * GLOBAL/LOCAL/PRIVATE/INPUT compute memory spaces. - */ -#define TGSI_RESOURCE_GLOBAL 0x7fff -#define TGSI_RESOURCE_LOCAL 0x7ffe -#define TGSI_RESOURCE_PRIVATE 0x7ffd -#define TGSI_RESOURCE_INPUT 0x7ffc - -#define TGSI_IMM_FLOAT32 0 -#define TGSI_IMM_UINT32 1 -#define TGSI_IMM_INT32 2 -#define TGSI_IMM_FLOAT64 3 - -struct tgsi_immediate { - unsigned Type : 4; /**< TGSI_TOKEN_TYPE_IMMEDIATE */ - unsigned NrTokens : 14; /**< UINT */ - unsigned DataType : 4; /**< one of TGSI_IMM_x */ - unsigned Padding : 10; -}; - -union tgsi_immediate_data { - float Float; - unsigned Uint; - int Int; -}; - -#define TGSI_PROPERTY_GS_INPUT_PRIM 0 -#define TGSI_PROPERTY_GS_OUTPUT_PRIM 1 -#define TGSI_PROPERTY_GS_MAX_OUTPUT_VERTICES 2 -#define TGSI_PROPERTY_FS_COORD_ORIGIN 3 -#define TGSI_PROPERTY_FS_COORD_PIXEL_CENTER 4 -#define TGSI_PROPERTY_FS_COLOR0_WRITES_ALL_CBUFS 5 -#define TGSI_PROPERTY_FS_DEPTH_LAYOUT 6 -#define TGSI_PROPERTY_VS_PROHIBIT_UCPS 7 -#define TGSI_PROPERTY_GS_INVOCATIONS 8 -#define TGSI_PROPERTY_VS_WINDOW_SPACE_POSITION 9 -#define TGSI_PROPERTY_TCS_VERTICES_OUT 10 -#define TGSI_PROPERTY_TES_PRIM_MODE 11 -#define TGSI_PROPERTY_TES_SPACING 12 -#define TGSI_PROPERTY_TES_VERTEX_ORDER_CW 13 -#define TGSI_PROPERTY_TES_POINT_MODE 14 -#define TGSI_PROPERTY_NUM_CLIPDIST_ENABLED 15 -#define TGSI_PROPERTY_NUM_CULLDIST_ENABLED 16 -#define TGSI_PROPERTY_FS_EARLY_DEPTH_STENCIL 17 -#define TGSI_PROPERTY_FS_POST_DEPTH_COVERAGE 18 -#define TGSI_PROPERTY_NEXT_SHADER 19 -#define TGSI_PROPERTY_CS_FIXED_BLOCK_WIDTH 20 -#define TGSI_PROPERTY_CS_FIXED_BLOCK_HEIGHT 21 -#define TGSI_PROPERTY_CS_FIXED_BLOCK_DEPTH 22 -#define TGSI_PROPERTY_MUL_ZERO_WINS 23 -#define TGSI_PROPERTY_COUNT 24 - -struct tgsi_property { - unsigned Type : 4; /**< TGSI_TOKEN_TYPE_PROPERTY */ - unsigned NrTokens : 8; /**< UINT */ - unsigned PropertyName : 8; /**< one of TGSI_PROPERTY */ - unsigned Padding : 12; -}; - -#define TGSI_FS_COORD_ORIGIN_UPPER_LEFT 0 -#define TGSI_FS_COORD_ORIGIN_LOWER_LEFT 1 - -#define TGSI_FS_COORD_PIXEL_CENTER_HALF_INTEGER 0 -#define TGSI_FS_COORD_PIXEL_CENTER_INTEGER 1 - -#define TGSI_FS_DEPTH_LAYOUT_NONE 0 -#define TGSI_FS_DEPTH_LAYOUT_ANY 1 -#define TGSI_FS_DEPTH_LAYOUT_GREATER 2 -#define TGSI_FS_DEPTH_LAYOUT_LESS 3 -#define TGSI_FS_DEPTH_LAYOUT_UNCHANGED 4 - -struct tgsi_property_data { - unsigned Data; -}; - -/* TGSI opcodes. - * - * For more information on semantics of opcodes and - * which APIs are known to use which opcodes, see - * gallium/docs/source/tgsi.rst - */ -/* VIRGLRENDERER specific - DON'T SYNC WITH MESA - * OR REMOVE OPCODES - FILL in and REWRITE tgsi_info - * accordingly. - */ -#define TGSI_OPCODE_ARL 0 -#define TGSI_OPCODE_MOV 1 -#define TGSI_OPCODE_LIT 2 -#define TGSI_OPCODE_RCP 3 -#define TGSI_OPCODE_RSQ 4 -#define TGSI_OPCODE_EXP 5 -#define TGSI_OPCODE_LOG 6 -#define TGSI_OPCODE_MUL 7 -#define TGSI_OPCODE_ADD 8 -#define TGSI_OPCODE_DP3 9 -#define TGSI_OPCODE_DP4 10 -#define TGSI_OPCODE_DST 11 -#define TGSI_OPCODE_MIN 12 -#define TGSI_OPCODE_MAX 13 -#define TGSI_OPCODE_SLT 14 -#define TGSI_OPCODE_SGE 15 -#define TGSI_OPCODE_MAD 16 -#define TGSI_OPCODE_SUB 17 -#define TGSI_OPCODE_LRP 18 -#define TGSI_OPCODE_FMA 19 -#define TGSI_OPCODE_SQRT 20 -/* gap */ -#define TGSI_OPCODE_FRC 24 -/* gap */ -#define TGSI_OPCODE_FLR 26 -#define TGSI_OPCODE_ROUND 27 -#define TGSI_OPCODE_EX2 28 -#define TGSI_OPCODE_LG2 29 -#define TGSI_OPCODE_POW 30 -#define TGSI_OPCODE_XPD 31 -/* gap */ -#define TGSI_OPCODE_ABS 33 -/* gap */ -#define TGSI_OPCODE_DPH 35 -#define TGSI_OPCODE_COS 36 -#define TGSI_OPCODE_DDX 37 -#define TGSI_OPCODE_DDY 38 -#define TGSI_OPCODE_KILL 39 /* unconditional */ -#define TGSI_OPCODE_PK2H 40 -#define TGSI_OPCODE_PK2US 41 -#define TGSI_OPCODE_PK4B 42 -#define TGSI_OPCODE_PK4UB 43 -/* gap */ -#define TGSI_OPCODE_SEQ 45 -/* gap */ -#define TGSI_OPCODE_SGT 47 -#define TGSI_OPCODE_SIN 48 -#define TGSI_OPCODE_SLE 49 -#define TGSI_OPCODE_SNE 50 -/* gap */ -#define TGSI_OPCODE_TEX 52 -#define TGSI_OPCODE_TXD 53 -#define TGSI_OPCODE_TXP 54 -#define TGSI_OPCODE_UP2H 55 -#define TGSI_OPCODE_UP2US 56 -#define TGSI_OPCODE_UP4B 57 -#define TGSI_OPCODE_UP4UB 58 -/* gap */ -#define TGSI_OPCODE_ARR 61 -/* gap */ -#define TGSI_OPCODE_CAL 63 -#define TGSI_OPCODE_RET 64 -#define TGSI_OPCODE_SSG 65 /* SGN */ -#define TGSI_OPCODE_CMP 66 -#define TGSI_OPCODE_SCS 67 -#define TGSI_OPCODE_TXB 68 -#define TGSI_OPCODE_FBFETCH 69 -#define TGSI_OPCODE_DIV 70 -#define TGSI_OPCODE_DP2 71 -#define TGSI_OPCODE_TXL 72 -#define TGSI_OPCODE_BRK 73 -#define TGSI_OPCODE_IF 74 -#define TGSI_OPCODE_UIF 75 -#define TGSI_OPCODE_ELSE 77 -#define TGSI_OPCODE_ENDIF 78 - -#define TGSI_OPCODE_DDX_FINE 79 -#define TGSI_OPCODE_DDY_FINE 80 -/* gap */ -#define TGSI_OPCODE_CEIL 83 -#define TGSI_OPCODE_I2F 84 -#define TGSI_OPCODE_NOT 85 -#define TGSI_OPCODE_TRUNC 86 -#define TGSI_OPCODE_SHL 87 -/* gap */ -#define TGSI_OPCODE_AND 89 -#define TGSI_OPCODE_OR 90 -#define TGSI_OPCODE_MOD 91 -#define TGSI_OPCODE_XOR 92 -/* gap */ -#define TGSI_OPCODE_TXF 94 -#define TGSI_OPCODE_TXQ 95 -#define TGSI_OPCODE_CONT 96 -#define TGSI_OPCODE_EMIT 97 -#define TGSI_OPCODE_ENDPRIM 98 -#define TGSI_OPCODE_BGNLOOP 99 -#define TGSI_OPCODE_BGNSUB 100 -#define TGSI_OPCODE_ENDLOOP 101 -#define TGSI_OPCODE_ENDSUB 102 -/* gap */ -#define TGSI_OPCODE_TXQS 104 -#define TGSI_OPCODE_RESQ 105 -/* gap */ -#define TGSI_OPCODE_NOP 107 - -#define TGSI_OPCODE_FSEQ 108 -#define TGSI_OPCODE_FSGE 109 -#define TGSI_OPCODE_FSLT 110 -#define TGSI_OPCODE_FSNE 111 - -#define TGSI_OPCODE_MEMBAR 112 -/* gap */ -#define TGSI_OPCODE_KILL_IF 116 /* conditional kill */ -#define TGSI_OPCODE_END 117 /* aka HALT */ -#define TGSI_OPCODE_DFMA 118 -#define TGSI_OPCODE_F2I 119 -#define TGSI_OPCODE_IDIV 120 -#define TGSI_OPCODE_IMAX 121 -#define TGSI_OPCODE_IMIN 122 -#define TGSI_OPCODE_INEG 123 -#define TGSI_OPCODE_ISGE 124 -#define TGSI_OPCODE_ISHR 125 -#define TGSI_OPCODE_ISLT 126 -#define TGSI_OPCODE_F2U 127 -#define TGSI_OPCODE_U2F 128 -#define TGSI_OPCODE_UADD 129 -#define TGSI_OPCODE_UDIV 130 -#define TGSI_OPCODE_UMAD 131 -#define TGSI_OPCODE_UMAX 132 -#define TGSI_OPCODE_UMIN 133 -#define TGSI_OPCODE_UMOD 134 -#define TGSI_OPCODE_UMUL 135 -#define TGSI_OPCODE_USEQ 136 -#define TGSI_OPCODE_USGE 137 -#define TGSI_OPCODE_USHR 138 -#define TGSI_OPCODE_USLT 139 -#define TGSI_OPCODE_USNE 140 -#define TGSI_OPCODE_SWITCH 141 -#define TGSI_OPCODE_CASE 142 -#define TGSI_OPCODE_DEFAULT 143 -#define TGSI_OPCODE_ENDSWITCH 144 - -/* resource related opcodes */ -#define TGSI_OPCODE_SAMPLE 145 -#define TGSI_OPCODE_SAMPLE_I 146 -#define TGSI_OPCODE_SAMPLE_I_MS 147 -#define TGSI_OPCODE_SAMPLE_B 148 -#define TGSI_OPCODE_SAMPLE_C 149 -#define TGSI_OPCODE_SAMPLE_C_LZ 150 -#define TGSI_OPCODE_SAMPLE_D 151 -#define TGSI_OPCODE_SAMPLE_L 152 -#define TGSI_OPCODE_GATHER4 153 -#define TGSI_OPCODE_SVIEWINFO 154 -#define TGSI_OPCODE_SAMPLE_POS 155 -#define TGSI_OPCODE_SAMPLE_INFO 156 - -#define TGSI_OPCODE_UARL 157 -#define TGSI_OPCODE_UCMP 158 -#define TGSI_OPCODE_IABS 159 -#define TGSI_OPCODE_ISSG 160 - -#define TGSI_OPCODE_LOAD 161 -#define TGSI_OPCODE_STORE 162 - -/* gap */ -#define TGSI_OPCODE_BARRIER 166 - -#define TGSI_OPCODE_ATOMUADD 167 -#define TGSI_OPCODE_ATOMXCHG 168 -#define TGSI_OPCODE_ATOMCAS 169 -#define TGSI_OPCODE_ATOMAND 170 -#define TGSI_OPCODE_ATOMOR 171 -#define TGSI_OPCODE_ATOMXOR 172 -#define TGSI_OPCODE_ATOMUMIN 173 -#define TGSI_OPCODE_ATOMUMAX 174 -#define TGSI_OPCODE_ATOMIMIN 175 -#define TGSI_OPCODE_ATOMIMAX 176 - -/* to be used for shadow cube map compares */ -#define TGSI_OPCODE_TEX2 177 -#define TGSI_OPCODE_TXB2 178 -#define TGSI_OPCODE_TXL2 179 - -#define TGSI_OPCODE_IMUL_HI 180 -#define TGSI_OPCODE_UMUL_HI 181 - -#define TGSI_OPCODE_TG4 182 - -#define TGSI_OPCODE_LODQ 183 - -#define TGSI_OPCODE_IBFE 184 -#define TGSI_OPCODE_UBFE 185 -#define TGSI_OPCODE_BFI 186 -#define TGSI_OPCODE_BREV 187 -#define TGSI_OPCODE_POPC 188 -#define TGSI_OPCODE_LSB 189 -#define TGSI_OPCODE_IMSB 190 -#define TGSI_OPCODE_UMSB 191 - -#define TGSI_OPCODE_INTERP_CENTROID 192 -#define TGSI_OPCODE_INTERP_SAMPLE 193 -#define TGSI_OPCODE_INTERP_OFFSET 194 - -/* sm5 marked opcodes are supported in D3D11 optionally - also DMOV, DMOVC */ -#define TGSI_OPCODE_F2D 195 /* SM5 */ -#define TGSI_OPCODE_D2F 196 -#define TGSI_OPCODE_DABS 197 -#define TGSI_OPCODE_DNEG 198 /* SM5 */ -#define TGSI_OPCODE_DADD 199 /* SM5 */ -#define TGSI_OPCODE_DMUL 200 /* SM5 */ -#define TGSI_OPCODE_DMAX 201 /* SM5 */ -#define TGSI_OPCODE_DMIN 202 /* SM5 */ -#define TGSI_OPCODE_DSLT 203 /* SM5 */ -#define TGSI_OPCODE_DSGE 204 /* SM5 */ -#define TGSI_OPCODE_DSEQ 205 /* SM5 */ -#define TGSI_OPCODE_DSNE 206 /* SM5 */ -#define TGSI_OPCODE_DRCP 207 /* eg, cayman */ -#define TGSI_OPCODE_DSQRT 208 /* eg, cayman also has DRSQ */ -#define TGSI_OPCODE_DMAD 209 -#define TGSI_OPCODE_DFRAC 210 /* eg, cayman */ -#define TGSI_OPCODE_DLDEXP 211 /* eg, cayman */ -#define TGSI_OPCODE_DFRACEXP 212 /* eg, cayman */ -#define TGSI_OPCODE_D2I 213 -#define TGSI_OPCODE_I2D 214 -#define TGSI_OPCODE_D2U 215 -#define TGSI_OPCODE_U2D 216 -#define TGSI_OPCODE_DRSQ 217 /* eg, cayman also has DRSQ */ -#define TGSI_OPCODE_DTRUNC 218 /* nvc0 */ -#define TGSI_OPCODE_DCEIL 219 /* nvc0 */ -#define TGSI_OPCODE_DFLR 220 /* nvc0 */ -#define TGSI_OPCODE_DROUND 221 /* nvc0 */ -#define TGSI_OPCODE_DSSG 222 -#define TGSI_OPCODE_DDIV 223 -#define TGSI_OPCODE_CLOCK 224 - -/* opcodes for ARB_gpu_shader_int64 */ -#define TGSI_OPCODE_I64ABS 225 -#define TGSI_OPCODE_I64NEG 226 -#define TGSI_OPCODE_I64SSG 227 -#define TGSI_OPCODE_I64SLT 228 -#define TGSI_OPCODE_I64SGE 229 -#define TGSI_OPCODE_I64MIN 230 -#define TGSI_OPCODE_I64MAX 231 -#define TGSI_OPCODE_I64SHR 232 -#define TGSI_OPCODE_I64DIV 233 -#define TGSI_OPCODE_I64MOD 234 -#define TGSI_OPCODE_F2I64 235 -#define TGSI_OPCODE_U2I64 236 -#define TGSI_OPCODE_I2I64 237 -#define TGSI_OPCODE_D2I64 238 -#define TGSI_OPCODE_I642F 239 -#define TGSI_OPCODE_I642D 240 - -#define TGSI_OPCODE_U64ADD 241 -#define TGSI_OPCODE_U64MUL 242 -#define TGSI_OPCODE_U64SEQ 243 -#define TGSI_OPCODE_U64SNE 244 -#define TGSI_OPCODE_U64SLT 245 -#define TGSI_OPCODE_U64SGE 246 -#define TGSI_OPCODE_U64MIN 247 -#define TGSI_OPCODE_U64MAX 248 -#define TGSI_OPCODE_U64SHL 249 -#define TGSI_OPCODE_U64SHR 250 -#define TGSI_OPCODE_U64DIV 251 -#define TGSI_OPCODE_U64MOD 252 -#define TGSI_OPCODE_F2U64 253 -#define TGSI_OPCODE_D2U64 254 -#define TGSI_OPCODE_U642F 255 -#define TGSI_OPCODE_U642D 256 - -#define TGSI_OPCODE_LAST 257 - -/** - * Opcode is the operation code to execute. A given operation defines the - * semantics how the source registers (if any) are interpreted and what is - * written to the destination registers (if any) as a result of execution. - * - * NumDstRegs and NumSrcRegs is the number of destination and source registers, - * respectively. For a given operation code, those numbers are fixed and are - * present here only for convenience. - * - * Saturate controls how are final results in destination registers modified. - */ - -/* - * VIRGLRENDERER specific - - * we no long keep this in sync with mesa, we had to increase the NrTokens - * as mesa can remove old opcodes, but the renderer cannot. - */ -struct tgsi_instruction { - unsigned Type : 4; /* TGSI_TOKEN_TYPE_INSTRUCTION */ - unsigned NrTokens : 9; /* UINT */ - unsigned Opcode : 8; /* TGSI_OPCODE_ */ - unsigned Saturate : 1; /* BOOL */ - unsigned NumDstRegs : 2; /* UINT */ - unsigned NumSrcRegs : 4; /* UINT */ - unsigned Label : 1; - unsigned Texture : 1; - unsigned Memory : 1; - unsigned Precise : 1; -}; - -/* - * If tgsi_instruction::Label is TRUE, tgsi_instruction_label follows. - * - * If tgsi_instruction::Texture is TRUE, tgsi_instruction_texture follows. - * if texture instruction has a number of offsets, - * then tgsi_instruction::Texture::NumOffset of tgsi_texture_offset follow. - * - * Then, tgsi_instruction::NumDstRegs of tgsi_dst_register follow. - * - * Then, tgsi_instruction::NumSrcRegs of tgsi_src_register follow. - * - * tgsi_instruction::NrTokens contains the total number of words that make the - * instruction, including the instruction word. - */ - -#define TGSI_SWIZZLE_X 0 -#define TGSI_SWIZZLE_Y 1 -#define TGSI_SWIZZLE_Z 2 -#define TGSI_SWIZZLE_W 3 - -struct tgsi_instruction_label { - unsigned Label : 24; /* UINT */ - unsigned Padding : 8; -}; - -#define TGSI_TEXTURE_BUFFER 0 -#define TGSI_TEXTURE_1D 1 -#define TGSI_TEXTURE_2D 2 -#define TGSI_TEXTURE_3D 3 -#define TGSI_TEXTURE_CUBE 4 -#define TGSI_TEXTURE_RECT 5 -#define TGSI_TEXTURE_SHADOW1D 6 -#define TGSI_TEXTURE_SHADOW2D 7 -#define TGSI_TEXTURE_SHADOWRECT 8 -#define TGSI_TEXTURE_1D_ARRAY 9 -#define TGSI_TEXTURE_2D_ARRAY 10 -#define TGSI_TEXTURE_SHADOW1D_ARRAY 11 -#define TGSI_TEXTURE_SHADOW2D_ARRAY 12 -#define TGSI_TEXTURE_SHADOWCUBE 13 -#define TGSI_TEXTURE_2D_MSAA 14 -#define TGSI_TEXTURE_2D_ARRAY_MSAA 15 -#define TGSI_TEXTURE_CUBE_ARRAY 16 -#define TGSI_TEXTURE_SHADOWCUBE_ARRAY 17 -#define TGSI_TEXTURE_UNKNOWN 18 -#define TGSI_TEXTURE_COUNT 19 - -struct tgsi_instruction_texture { - unsigned Texture : 8; /* TGSI_TEXTURE_ */ - unsigned NumOffsets : 4; - unsigned Padding : 20; -}; - -/* for texture offsets in GLSL and DirectX. - * Generally these always come from TGSI_FILE_IMMEDIATE, - * however DX11 appears to have the capability to do - * non-constant texture offsets. - */ -struct tgsi_texture_offset { - int Index : 16; - unsigned File : 4; /**< one of TGSI_FILE_x */ - unsigned SwizzleX : 2; /* TGSI_SWIZZLE_x */ - unsigned SwizzleY : 2; /* TGSI_SWIZZLE_x */ - unsigned SwizzleZ : 2; /* TGSI_SWIZZLE_x */ - unsigned Padding : 6; -}; - -/** - * File specifies the register array to access. - * - * Index specifies the element number of a register in the register file. - * - * If Indirect is TRUE, Index should be offset by the X component of the - * indirect register that follows. The register can be now fetched into local - * storage for further processing. - * - * If Negate is TRUE, all components of the fetched register are negated. - * - * The fetched register components are swizzled according to SwizzleX, SwizzleY, - * SwizzleZ and SwizzleW. - * - */ - -struct tgsi_src_register { - unsigned File : 4; /* TGSI_FILE_ */ - unsigned Indirect : 1; /* BOOL */ - unsigned Dimension : 1; /* BOOL */ - int Index : 16; /* SINT */ - unsigned SwizzleX : 2; /* TGSI_SWIZZLE_ */ - unsigned SwizzleY : 2; /* TGSI_SWIZZLE_ */ - unsigned SwizzleZ : 2; /* TGSI_SWIZZLE_ */ - unsigned SwizzleW : 2; /* TGSI_SWIZZLE_ */ - unsigned Absolute : 1; /* BOOL */ - unsigned Negate : 1; /* BOOL */ -}; - -/** - * If tgsi_src_register::Indirect is TRUE, tgsi_ind_register follows. - * - * File, Index and Swizzle are handled the same as in tgsi_src_register. - * - * If ArrayID is zero the whole register file might be indirectly addressed, - * if not only the Declaration with this ArrayID is accessed by this operand. - * - */ - -struct tgsi_ind_register { - unsigned File : 4; /* TGSI_FILE_ */ - int Index : 16; /* SINT */ - unsigned Swizzle : 2; /* TGSI_SWIZZLE_ */ - unsigned ArrayID : 10; /* UINT */ -}; - -/** - * If tgsi_src_register::Dimension is TRUE, tgsi_dimension follows. - */ - -struct tgsi_dimension { - unsigned Indirect : 1; /* BOOL */ - unsigned Dimension : 1; /* BOOL */ - unsigned Padding : 14; - int Index : 16; /* SINT */ -}; - -struct tgsi_dst_register { - unsigned File : 4; /* TGSI_FILE_ */ - unsigned WriteMask : 4; /* TGSI_WRITEMASK_ */ - unsigned Indirect : 1; /* BOOL */ - unsigned Dimension : 1; /* BOOL */ - int Index : 16; /* SINT */ - unsigned Padding : 6; -}; - -#define TGSI_MEMORY_COHERENT (1 << 0) -#define TGSI_MEMORY_RESTRICT (1 << 1) -#define TGSI_MEMORY_VOLATILE (1 << 2) - -/** - * Specifies the type of memory access to do for the LOAD/STORE instruction. - */ -struct tgsi_instruction_memory { - unsigned Qualifier : 3; /* TGSI_MEMORY_ */ - unsigned Texture : 8; /* only for images: TGSI_TEXTURE_ */ - unsigned Format : 10; /* only for images: PIPE_FORMAT_ */ - unsigned Padding : 11; -}; - -#define TGSI_MEMBAR_SHADER_BUFFER (1 << 0) -#define TGSI_MEMBAR_ATOMIC_BUFFER (1 << 1) -#define TGSI_MEMBAR_SHADER_IMAGE (1 << 2) -#define TGSI_MEMBAR_SHARED (1 << 3) -#define TGSI_MEMBAR_THREAD_GROUP (1 << 4) - -#ifdef __cplusplus -} -#endif - -#endif /* P_SHADER_TOKENS_H */ diff --git a/app/src/main/cpp/virglrenderer/src/gallium/include/pipe/p_state.h b/app/src/main/cpp/virglrenderer/src/gallium/include/pipe/p_state.h deleted file mode 100644 index c945c045b..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/include/pipe/p_state.h +++ /dev/null @@ -1,598 +0,0 @@ -/************************************************************************** - * - * Copyright 2007 VMware, Inc. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -/** - * @file - * - * Abstract graphics pipe state objects. - * - * Basic notes: - * 1. Want compact representations, so we use bitfields. - * 2. Put bitfields before other (GLfloat) fields. - */ - -#ifndef PIPE_STATE_H -#define PIPE_STATE_H - -#include "p_compiler.h" -#include "p_defines.h" -#include "p_format.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * Implementation limits - */ -#define PIPE_MAX_ATTRIBS 32 -#define PIPE_MAX_CLIP_PLANES 8 -#define PIPE_MAX_COLOR_BUFS 8 -#define PIPE_MAX_CONSTANT_BUFFERS 32 -#define PIPE_MAX_SAMPLERS 16 -#define PIPE_MAX_SHADER_INPUTS 80 /* 32 GENERIC + 32 PATCH + 16 others */ -#define PIPE_MAX_SHADER_OUTPUTS 80 /* 32 GENERIC + 32 PATCH + 16 others */ -#define PIPE_MAX_SHADER_SAMPLER_VIEWS 32 -#define PIPE_MAX_SHADER_BUFFERS 32 -#define PIPE_MAX_SHADER_IMAGES 32 -#define PIPE_MAX_TEXTURE_LEVELS 16 -#define PIPE_MAX_SO_BUFFERS 4 -#define PIPE_MAX_SO_OUTPUTS 64 -#define PIPE_MAX_VIEWPORTS 16 -#define PIPE_MAX_CLIP_OR_CULL_DISTANCE_COUNT 8 -#define PIPE_MAX_CLIP_OR_CULL_DISTANCE_ELEMENT_COUNT 2 -#define PIPE_MAX_HW_ATOMIC_BUFFERS 32 - -struct pipe_reference { - int32_t count; /* atomic */ -}; - -/** - * Primitive (point/line/tri) rasterization info - */ -struct pipe_rasterizer_state { - unsigned flatshade : 1; - unsigned light_twoside : 1; - unsigned clamp_vertex_color : 1; - unsigned clamp_fragment_color : 1; - unsigned front_ccw : 1; - unsigned cull_face : 2; /**< PIPE_FACE_x */ - unsigned fill_front : 2; /**< PIPE_POLYGON_MODE_x */ - unsigned fill_back : 2; /**< PIPE_POLYGON_MODE_x */ - unsigned offset_point : 1; - unsigned offset_line : 1; - unsigned offset_tri : 1; - unsigned scissor : 1; - unsigned poly_smooth : 1; - unsigned poly_stipple_enable : 1; - unsigned point_smooth : 1; - unsigned sprite_coord_mode : 1; /**< PIPE_SPRITE_COORD_ */ - unsigned - point_quad_rasterization : 1; /** points rasterized as quads or points */ - unsigned point_tri_clip : 1; /** large points clipped as tris or points */ - unsigned point_size_per_vertex : 1; /**< size computed in vertex shader */ - unsigned multisample : 1; /* XXX maybe more ms state in future */ - unsigned force_persample_interp : 1; - unsigned line_smooth : 1; - unsigned line_stipple_enable : 1; - unsigned line_last_pixel : 1; - - /** - * Use the first vertex of a primitive as the provoking vertex for - * flat shading. - */ - unsigned flatshade_first : 1; - - unsigned half_pixel_center : 1; - unsigned bottom_edge_rule : 1; - - /** - * When true, rasterization is disabled and no pixels are written. - * This only makes sense with the Stream Out functionality. - */ - unsigned rasterizer_discard : 1; - - /** - * When false, depth clipping is disabled and the depth value will be - * clamped later at the per-pixel level before depth testing. - * This depends on PIPE_CAP_DEPTH_CLIP_DISABLE. - */ - unsigned depth_clip : 1; - - /** - * When true clip space in the z axis goes from [0..1] (D3D). When false - * [-1, 1] (GL). - * - * NOTE: D3D will always use depth clamping. - */ - unsigned clip_halfz : 1; - - /** - * Enable bits for clipping half-spaces. - * This applies to both user clip planes and shader clip distances. - * Note that if the bound shader exports any clip distances, these - * replace all user clip planes, and clip half-spaces enabled here - * but not written by the shader count as disabled. - */ - unsigned clip_plane_enable : PIPE_MAX_CLIP_PLANES; - - unsigned line_stipple_factor : 8; /**< [1..256] actually */ - unsigned line_stipple_pattern : 16; - - uint32_t sprite_coord_enable; /* referring to 32 TEXCOORD/GENERIC inputs */ - - float line_width; - float point_size; /**< used when no per-vertex size */ - float offset_units; - float offset_scale; - float offset_clamp; -}; - -struct pipe_poly_stipple { - unsigned stipple[32]; -}; - -struct pipe_viewport_state { - float scale[3]; - float translate[3]; -}; - -struct pipe_scissor_state { - unsigned minx : 16; - unsigned miny : 16; - unsigned maxx : 16; - unsigned maxy : 16; -}; - -struct pipe_clip_state { - float ucp[PIPE_MAX_CLIP_PLANES][4]; -}; - -/** - * Stream output for vertex transform feedback. - */ -struct pipe_stream_output_info { - unsigned num_outputs; - /** stride for an entire vertex for each buffer in dwords */ - unsigned stride[PIPE_MAX_SO_BUFFERS]; - - /** - * Array of stream outputs, in the order they are to be written in. - * Selected components are tightly packed into the output buffer. - */ - struct { - unsigned register_index : 8; /**< 0 to PIPE_MAX_SHADER_OUTPUTS */ - unsigned start_component : 2; /** 0 to 3 */ - unsigned num_components : 3; /** 1 to 4 */ - unsigned output_buffer : 3; /**< 0 to PIPE_MAX_SO_BUFFERS */ - unsigned dst_offset : 16; /**< offset into the buffer in dwords */ - unsigned stream : 2; - unsigned need_temp : 1; - } output[PIPE_MAX_SO_OUTPUTS]; -}; - -struct pipe_shader_state { - const struct tgsi_token *tokens; - struct pipe_stream_output_info stream_output; -}; - -struct pipe_depth_state { - unsigned enabled : 1; /**< depth test enabled? */ - unsigned writemask : 1; /**< allow depth buffer writes? */ - unsigned func : 3; /**< depth test func (PIPE_FUNC_x) */ -}; - -struct pipe_stencil_state { - unsigned enabled : 1; /**< stencil[0]: stencil enabled, stencil[1]: two-side - enabled */ - unsigned func : 3; /**< PIPE_FUNC_x */ - unsigned fail_op : 3; /**< PIPE_STENCIL_OP_x */ - unsigned zpass_op : 3; /**< PIPE_STENCIL_OP_x */ - unsigned zfail_op : 3; /**< PIPE_STENCIL_OP_x */ - unsigned valuemask : 8; - unsigned writemask : 8; -}; - -struct pipe_alpha_state { - unsigned enabled : 1; - unsigned func : 3; /**< PIPE_FUNC_x */ - float ref_value; /**< reference value */ -}; - -struct pipe_depth_stencil_alpha_state { - struct pipe_depth_state depth; - struct pipe_stencil_state stencil[2]; /**< [0] = front, [1] = back */ - struct pipe_alpha_state alpha; -}; - -struct pipe_rt_blend_state { - unsigned blend_enable : 1; - - unsigned rgb_func : 3; /**< PIPE_BLEND_x */ - unsigned rgb_src_factor : 5; /**< PIPE_BLENDFACTOR_x */ - unsigned rgb_dst_factor : 5; /**< PIPE_BLENDFACTOR_x */ - - unsigned alpha_func : 3; /**< PIPE_BLEND_x */ - unsigned alpha_src_factor : 5; /**< PIPE_BLENDFACTOR_x */ - unsigned alpha_dst_factor : 5; /**< PIPE_BLENDFACTOR_x */ - - unsigned colormask : 4; /**< bitmask of PIPE_MASK_R/G/B/A */ -}; - -struct pipe_blend_state { - unsigned independent_blend_enable : 1; - unsigned logicop_enable : 1; - unsigned logicop_func : 4; /**< PIPE_LOGICOP_x */ - unsigned dither : 1; - unsigned alpha_to_coverage : 1; - unsigned alpha_to_one : 1; - struct pipe_rt_blend_state rt[PIPE_MAX_COLOR_BUFS]; -}; - -struct pipe_blend_color { - float color[4]; -}; - -struct pipe_stencil_ref { - ubyte ref_value[2]; -}; - -struct pipe_framebuffer_state { - unsigned width, height; - - /** multiple color buffers for multiple render targets */ - unsigned nr_cbufs; - struct pipe_surface *cbufs[PIPE_MAX_COLOR_BUFS]; - - struct pipe_surface *zsbuf; /**< Z/stencil buffer */ -}; - -/** - * Texture sampler state. - */ -struct pipe_sampler_state { - unsigned wrap_s : 3; /**< PIPE_TEX_WRAP_x */ - unsigned wrap_t : 3; /**< PIPE_TEX_WRAP_x */ - unsigned wrap_r : 3; /**< PIPE_TEX_WRAP_x */ - unsigned min_img_filter : 2; /**< PIPE_TEX_FILTER_x */ - unsigned min_mip_filter : 2; /**< PIPE_TEX_MIPFILTER_x */ - unsigned mag_img_filter : 2; /**< PIPE_TEX_FILTER_x */ - unsigned compare_mode : 1; /**< PIPE_TEX_COMPARE_x */ - unsigned compare_func : 3; /**< PIPE_FUNC_x */ - unsigned normalized_coords : 1; /**< Are coords normalized to [0,1]? */ - unsigned max_anisotropy : 6; - unsigned seamless_cube_map : 1; - float lod_bias; /**< LOD/lambda bias */ - float min_lod, max_lod; /**< LOD clamp range, after bias */ - union pipe_color_union border_color; -}; - -/** - * A view into a texture that can be bound to a color render target / - * depth stencil attachment point. - */ -struct pipe_surface { - struct pipe_reference reference; - struct pipe_resource *texture; /**< resource into which this is a view */ - struct pipe_context *context; /**< context this surface belongs to */ - enum pipe_format format; - - /* XXX width/height should be removed */ - unsigned width; /**< logical width in pixels */ - unsigned height; /**< logical height in pixels */ - - unsigned writable : 1; /**< writable shader resource */ - - union { - struct { - unsigned level; - unsigned first_layer : 16; - unsigned last_layer : 16; - } tex; - struct { - unsigned first_element; - unsigned last_element; - } buf; - } u; -}; - -/** - * A view into a texture that can be bound to a shader stage. - */ -struct pipe_sampler_view { - struct pipe_reference reference; - enum pipe_format format; /**< typed PIPE_FORMAT_x */ - struct pipe_resource *texture; /**< texture into which this is a view */ - struct pipe_context *context; /**< context this view belongs to */ - union { - struct { - unsigned first_layer : 16; /**< first layer to use for array textures */ - unsigned last_layer : 16; /**< last layer to use for array textures */ - unsigned first_level : 8; /**< first mipmap level to use */ - unsigned last_level : 8; /**< last mipmap level to use */ - } tex; - struct { - unsigned first_element; - unsigned last_element; - } buf; - } u; - unsigned swizzle_r : 3; /**< PIPE_SWIZZLE_x for red component */ - unsigned swizzle_g : 3; /**< PIPE_SWIZZLE_x for green component */ - unsigned swizzle_b : 3; /**< PIPE_SWIZZLE_x for blue component */ - unsigned swizzle_a : 3; /**< PIPE_SWIZZLE_x for alpha component */ -}; - -/** - * Subregion of 1D/2D/3D image resource. - */ -struct pipe_box { - int x; - int y; - int z; - int width; - int height; - int depth; -}; - -/** - * A memory object/resource such as a vertex buffer or texture. - */ -struct pipe_resource { - struct pipe_reference reference; - struct pipe_screen *screen; /**< screen that this texture belongs to */ - enum pipe_texture_target target; /**< PIPE_TEXTURE_x */ - enum pipe_format format; /**< PIPE_FORMAT_x */ - - unsigned width0; - unsigned height0; - unsigned depth0; - unsigned array_size; - - unsigned last_level : 8; /**< Index of last mipmap level present/defined */ - unsigned nr_samples : 8; /**< for multisampled surfaces, nr of samples */ - unsigned usage : 8; /**< PIPE_USAGE_x (not a bitmask) */ - - unsigned bind; /**< bitmask of PIPE_BIND_x */ - unsigned flags; /**< bitmask of PIPE_RESOURCE_FLAG_x */ -}; - -/** - * Transfer object. For data transfer to/from a resource. - */ -struct pipe_transfer { - struct pipe_resource *resource; /**< resource to transfer to/from */ - unsigned level; /**< texture mipmap level */ - enum pipe_transfer_usage usage; - struct pipe_box box; /**< region of the resource to access */ - unsigned stride; /**< row stride in bytes */ - unsigned layer_stride; /**< image/layer stride in bytes */ -}; - -/** - * A vertex buffer. Typically, all the vertex data/attributes for - * drawing something will be in one buffer. But it's also possible, for - * example, to put colors in one buffer and texcoords in another. - */ -struct pipe_vertex_buffer { - unsigned stride; /**< stride to same attrib in next vertex, in bytes */ - unsigned buffer_offset; /**< offset to start of data in buffer, in bytes */ - struct pipe_resource *buffer; /**< the actual buffer */ - const void *user_buffer; /**< pointer to a user buffer if buffer == NULL */ -}; - -/** - * A constant buffer. A subrange of an existing buffer can be set - * as a constant buffer. - */ -struct pipe_constant_buffer { - struct pipe_resource *buffer; /**< the actual buffer */ - unsigned buffer_offset; /**< offset to start of data in buffer, in bytes */ - unsigned buffer_size; /**< how much data can be read in shader */ - const void *user_buffer; /**< pointer to a user buffer if buffer == NULL */ -}; - -/** - * A stream output target. The structure specifies the range vertices can - * be written to. - * - * In addition to that, the structure should internally maintain the offset - * into the buffer, which should be incremented everytime something is written - * (appended) to it. The internal offset is buffer_offset + how many bytes - * have been written. The internal offset can be stored on the device - * and the CPU actually doesn't have to query it. - * - * Note that the buffer_size variable is actually specifying the available - * space in the buffer, not the size of the attached buffer. - * In other words in majority of cases buffer_size would simply be - * 'buffer->width0 - buffer_offset', so buffer_size refers to the size - * of the buffer left, after accounting for buffer offset, for stream output - * to write to. - * - * Use PIPE_QUERY_SO_STATISTICS to know how many primitives have - * actually been written. - */ -struct pipe_stream_output_target { - struct pipe_reference reference; - struct pipe_resource *buffer; /**< the output buffer */ - struct pipe_context *context; /**< context this SO target belongs to */ - - unsigned buffer_offset; /**< offset where data should be written, in bytes */ - unsigned buffer_size; /**< how much data is allowed to be written */ -}; - -/** - * Information to describe a vertex attribute (position, color, etc) - */ -struct pipe_vertex_element { - /** Offset of this attribute, in bytes, from the start of the vertex */ - unsigned src_offset; - - /** Instance data rate divisor. 0 means this is per-vertex data, - * n means per-instance data used for n consecutive instances (n > 0). - */ - unsigned instance_divisor; - - /** Which vertex_buffer (as given to pipe->set_vertex_buffer()) does - * this attribute live in? - */ - unsigned vertex_buffer_index; - - enum pipe_format src_format; -}; - -/** - * An index buffer. When an index buffer is bound, all indices to vertices - * will be looked up in the buffer. - */ -struct pipe_index_buffer { - unsigned index_size; /**< size of an index, in bytes */ - unsigned offset; /**< offset to start of data in buffer, in bytes */ - struct pipe_resource *buffer; /**< the actual buffer */ - const void *user_buffer; /**< pointer to a user buffer if buffer == NULL */ -}; - -struct pipe_draw_indirect_info { - unsigned offset; /**< must be 4 byte aligned */ - unsigned stride; /**< must be 4 byte aligned */ - unsigned draw_count; /**< number of indirect draws */ - unsigned indirect_draw_count_offset; /**< must be 4 byte aligned */ - - /* Indirect draw parameters resource is laid out as follows: - * - * if using indexed drawing: - * struct { - * uint32_t count; - * uint32_t instance_count; - * uint32_t start; - * int32_t index_bias; - * uint32_t start_instance; - * }; - * otherwise: - * struct { - * uint32_t count; - * uint32_t instance_count; - * uint32_t start; - * uint32_t start_instance; - * }; - */ - struct pipe_resource *buffer; - - /* Indirect draw count resource: If not NULL, contains a 32-bit value which - * is to be used as the real draw_count. - */ - struct pipe_resource *indirect_draw_count; -}; - -/** - * Information to describe a draw_vbo call. - */ -struct pipe_draw_info { - boolean indexed; /**< use index buffer */ - ubyte vertices_per_patch; /**< the number of vertices per patch */ - - unsigned mode; /**< the mode of the primitive */ - unsigned start; /**< the index of the first vertex */ - unsigned count; /**< number of vertices */ - - unsigned start_instance; /**< first instance id */ - unsigned instance_count; /**< number of instances */ - - unsigned drawid; /**< id of this draw in a multidraw */ - /** - * For indexed drawing, these fields apply after index lookup. - */ - int index_bias; /**< a bias to be added to each index */ - unsigned min_index; /**< the min index */ - unsigned max_index; /**< the max index */ - - /** - * Primitive restart enable/index (only applies to indexed drawing) - */ - boolean primitive_restart; - unsigned restart_index; - - struct pipe_draw_indirect_info indirect; - /** - * Stream output target. If not NULL, it's used to provide the 'count' - * parameter based on the number vertices captured by the stream output - * stage. (or generally, based on the number of bytes captured) - * - * Only 'mode', 'start_instance', and 'instance_count' are taken into - * account, all the other variables from pipe_draw_info are ignored. - * - * 'start' is implicitly 0 and 'count' is set as discussed above. - * The draw command is non-indexed. - * - * Note that this only provides the count. The vertex buffers must - * be set via set_vertex_buffers manually. - */ - struct pipe_stream_output_target *count_from_stream_output; -}; - -/** - * Information to describe a blit call. - */ -struct pipe_blit_info { - struct { - struct pipe_resource *resource; - unsigned level; - struct pipe_box box; /**< negative width, height only legal for src */ - /* For pipe_surface-like format casting: */ - enum pipe_format format; /**< must be supported for sampling (src) - or rendering (dst), ZS is always supported */ - } dst, src; - - unsigned mask; /**< bitmask of PIPE_MASK_R/G/B/A/Z/S */ - unsigned filter; /**< PIPE_TEX_FILTER_* */ - - boolean scissor_enable; - struct pipe_scissor_state scissor; - - boolean render_condition_enable; /**< whether the blit should honor the - current render condition */ - boolean alpha_blend; /* dst.rgb = src.rgb * src.a + dst.rgb * (1 - src.a) */ -}; - -/** - * Structure used as a header for serialized LLVM programs. - */ -struct pipe_llvm_program_header { - uint32_t num_bytes; /**< Number of bytes in the LLVM bytecode program. */ -}; - -struct pipe_compute_state { - const void *prog; /**< Compute program to be executed. */ - unsigned req_local_mem; /**< Required size of the LOCAL resource. */ - unsigned req_private_mem; /**< Required size of the PRIVATE resource. */ - unsigned req_input_mem; /**< Required size of the INPUT resource. */ -}; - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/app/src/main/cpp/virglrenderer/src/gallium/include/pipe/p_video_enums.h b/app/src/main/cpp/virglrenderer/src/gallium/include/pipe/p_video_enums.h deleted file mode 100644 index bd6c0547f..000000000 --- a/app/src/main/cpp/virglrenderer/src/gallium/include/pipe/p_video_enums.h +++ /dev/null @@ -1,75 +0,0 @@ -/************************************************************************** - * - * Copyright 2009 Younes Manton. - * All Rights Reserved. - * - * 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, sub license, 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 (including the - * next paragraph) 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 NON-INFRINGEMENT. - * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. - * - **************************************************************************/ - -#ifndef PIPE_VIDEO_ENUMS_H -#define PIPE_VIDEO_ENUMS_H - -enum pipe_video_format { - PIPE_VIDEO_FORMAT_UNKNOWN = 0, - PIPE_VIDEO_FORMAT_MPEG12, /**< MPEG1, MPEG2 */ - PIPE_VIDEO_FORMAT_MPEG4, /**< DIVX, XVID */ - PIPE_VIDEO_FORMAT_VC1, /**< WMV */ - PIPE_VIDEO_FORMAT_MPEG4_AVC /**< H.264 */ -}; - -enum pipe_video_profile { - PIPE_VIDEO_PROFILE_UNKNOWN, - PIPE_VIDEO_PROFILE_MPEG1, - PIPE_VIDEO_PROFILE_MPEG2_SIMPLE, - PIPE_VIDEO_PROFILE_MPEG2_MAIN, - PIPE_VIDEO_PROFILE_MPEG4_SIMPLE, - PIPE_VIDEO_PROFILE_MPEG4_ADVANCED_SIMPLE, - PIPE_VIDEO_PROFILE_VC1_SIMPLE, - PIPE_VIDEO_PROFILE_VC1_MAIN, - PIPE_VIDEO_PROFILE_VC1_ADVANCED, - PIPE_VIDEO_PROFILE_MPEG4_AVC_BASELINE, - PIPE_VIDEO_PROFILE_MPEG4_AVC_MAIN, - PIPE_VIDEO_PROFILE_MPEG4_AVC_HIGH -}; - -/* Video caps, can be different for each codec/profile */ -enum pipe_video_cap { - PIPE_VIDEO_CAP_SUPPORTED = 0, - PIPE_VIDEO_CAP_NPOT_TEXTURES = 1, - PIPE_VIDEO_CAP_MAX_WIDTH = 2, - PIPE_VIDEO_CAP_MAX_HEIGHT = 3, - PIPE_VIDEO_CAP_PREFERED_FORMAT = 4, - PIPE_VIDEO_CAP_PREFERS_INTERLACED = 5, - PIPE_VIDEO_CAP_SUPPORTS_PROGRESSIVE = 6, - PIPE_VIDEO_CAP_SUPPORTS_INTERLACED = 7, - PIPE_VIDEO_CAP_MAX_LEVEL = 8 -}; - -enum pipe_video_entrypoint { - PIPE_VIDEO_ENTRYPOINT_UNKNOWN, - PIPE_VIDEO_ENTRYPOINT_BITSTREAM, - PIPE_VIDEO_ENTRYPOINT_IDCT, - PIPE_VIDEO_ENTRYPOINT_MC, - PIPE_VIDEO_ENTRYPOINT_ENCODE -}; - -#endif /* PIPE_VIDEO_ENUMS_H */ diff --git a/app/src/main/cpp/virglrenderer/src/iov.c b/app/src/main/cpp/virglrenderer/src/iov.c deleted file mode 100644 index ed7c69806..000000000 --- a/app/src/main/cpp/virglrenderer/src/iov.c +++ /dev/null @@ -1,201 +0,0 @@ -/* - * this code is taken from Michael - the qemu code is GPLv2 so I don't want - * to reuse it. - * I've adapted it to handle offsets and callback - */ - -// -// iovec.c -// -// Scatter/gather utility routines -// -// Copyright (C) 2002 Michael Ringgaard. All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions -// are met: -// -// 1. Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// 3. Neither the name of the project nor the names of its contributors -// may be used to endorse or promote products derived from this software -// without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. -// - -#include "vrend_iov.h" -#include -#include -#include -#include - -size_t vrend_get_iovec_size(const struct iovec *iov, int iovlen) { - size_t size = 0; - - while (iovlen > 0) { - size += iov->iov_len; - iov++; - iovlen--; - } - - return size; -} - -size_t vrend_read_from_iovec(const struct iovec *iov, int iovlen, size_t offset, - char *buf, size_t count) { - size_t read = 0; - size_t len; - - while (count > 0 && iovlen > 0) { - if (iov->iov_len > offset) { - len = iov->iov_len - offset; - - if (count < len) - len = count; - - memcpy(buf, (char *)iov->iov_base + offset, len); - read += len; - - buf += len; - count -= len; - offset = 0; - } else { - offset -= iov->iov_len; - } - - iov++; - iovlen--; - } - assert(offset == 0); - return read; -} - -size_t vrend_write_to_iovec(const struct iovec *iov, int iovlen, size_t offset, - const char *buf, size_t count) { - size_t written = 0; - size_t len; - - while (count > 0 && iovlen > 0) { - if (iov->iov_len > offset) { - len = iov->iov_len - offset; - - if (count < len) - len = count; - - memcpy((char *)iov->iov_base + offset, buf, len); - written += len; - - offset = 0; - buf += len; - count -= len; - } else { - offset -= iov->iov_len; - } - iov++; - iovlen--; - } - assert(offset == 0); - return written; -} - -size_t vrend_read_from_iovec_cb(const struct iovec *iov, int iovlen, - size_t offset, size_t count, iov_cb iocb, - void *cookie) { - size_t read = 0; - size_t len; - - while (count > 0 && iovlen > 0) { - if (iov->iov_len > offset) { - len = iov->iov_len - offset; - - if (count < len) - len = count; - - (*iocb)(cookie, read, (char *)iov->iov_base + offset, len); - read += len; - - count -= len; - offset = 0; - } else { - offset -= iov->iov_len; - } - iov++; - iovlen--; - } - assert(offset == 0); - return read; -} - -/** - * Copy data from one iovec to another iovec. - * - * TODO: Implement iovec copy without copy to intermediate buffer. - * - * \param src_iov The source iov. - * \param src_iovlen The number of memory regions in the source iov. - * \param src_offset The byte offset in the source iov to start reading from. - * \param dst_iov The destination iov. - * \param dst_iovlen The number of memory regions in the destination iov. - * \param dst_offset The byte offset in the destination iov to start writing to. - * \param count The number of bytes to copy - * \param buf If not NULL, a pointer to a buffer of at least count size - * to use a temporary storage for the copy operation. - * \return -1 on failure, 0 on success - */ -int vrend_copy_iovec(const struct iovec *src_iov, int src_iovlen, - size_t src_offset, const struct iovec *dst_iov, - int dst_iovlen, size_t dst_offset, size_t count, - char *buf) { - int ret = 0; - bool needs_free; - size_t nread; - size_t nwritten; - - if (src_iov == NULL || dst_iov == NULL) - return -1; - - if (src_iov == dst_iov && src_offset == dst_offset) - return 0; - - if (!buf) { - buf = malloc(count); - needs_free = true; - } else { - needs_free = false; - } - - if (!buf) - return -1; - - nread = vrend_read_from_iovec(src_iov, src_iovlen, src_offset, buf, count); - if (nread != count) { - ret = -1; - goto out; - } - - nwritten = vrend_write_to_iovec(dst_iov, dst_iovlen, dst_offset, buf, count); - if (nwritten != count) { - ret = -1; - goto out; - } - -out: - if (needs_free) - free(buf); - - return ret; -} diff --git a/app/src/main/cpp/virglrenderer/src/virgl_hw.h b/app/src/main/cpp/virglrenderer/src/virgl_hw.h deleted file mode 100644 index 666e23b6e..000000000 --- a/app/src/main/cpp/virglrenderer/src/virgl_hw.h +++ /dev/null @@ -1,535 +0,0 @@ -/* - * Copyright 2014, 2015 Red Hat. - * - * 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 - * on the rights to use, copy, modify, merge, publish, distribute, sub - * license, 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 (including the next - * paragraph) 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 NON-INFRINGEMENT. IN NO EVENT SHALL - * THE AUTHOR(S) AND/OR THEIR SUPPLIERS 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. - */ -#ifndef VIRGL_HW_H -#define VIRGL_HW_H - -#include - -/* formats known by the HW device - based on gallium subset */ -enum virgl_formats { - VIRGL_FORMAT_NONE = 0, - VIRGL_FORMAT_B8G8R8A8_UNORM = 1, - VIRGL_FORMAT_B8G8R8X8_UNORM = 2, - VIRGL_FORMAT_A8R8G8B8_UNORM = 3, - VIRGL_FORMAT_X8R8G8B8_UNORM = 4, - VIRGL_FORMAT_B5G5R5A1_UNORM = 5, - VIRGL_FORMAT_B4G4R4A4_UNORM = 6, - VIRGL_FORMAT_B5G6R5_UNORM = 7, - VIRGL_FORMAT_R10G10B10A2_UNORM = 8, - VIRGL_FORMAT_L8_UNORM = 9, /**< ubyte luminance */ - VIRGL_FORMAT_A8_UNORM = 10, /**< ubyte alpha */ - VIRGL_FORMAT_I8_UNORM = 11, - VIRGL_FORMAT_L8A8_UNORM = 12, /**< ubyte alpha, luminance */ - VIRGL_FORMAT_L16_UNORM = 13, /**< ushort luminance */ - VIRGL_FORMAT_UYVY = 14, - VIRGL_FORMAT_YUYV = 15, - VIRGL_FORMAT_Z16_UNORM = 16, - VIRGL_FORMAT_Z32_UNORM = 17, - VIRGL_FORMAT_Z32_FLOAT = 18, - VIRGL_FORMAT_Z24_UNORM_S8_UINT = 19, - VIRGL_FORMAT_S8_UINT_Z24_UNORM = 20, - VIRGL_FORMAT_Z24X8_UNORM = 21, - VIRGL_FORMAT_X8Z24_UNORM = 22, - VIRGL_FORMAT_S8_UINT = 23, /**< ubyte stencil */ - VIRGL_FORMAT_R64_FLOAT = 24, - VIRGL_FORMAT_R64G64_FLOAT = 25, - VIRGL_FORMAT_R64G64B64_FLOAT = 26, - VIRGL_FORMAT_R64G64B64A64_FLOAT = 27, - VIRGL_FORMAT_R32_FLOAT = 28, - VIRGL_FORMAT_R32G32_FLOAT = 29, - VIRGL_FORMAT_R32G32B32_FLOAT = 30, - VIRGL_FORMAT_R32G32B32A32_FLOAT = 31, - - VIRGL_FORMAT_R32_UNORM = 32, - VIRGL_FORMAT_R32G32_UNORM = 33, - VIRGL_FORMAT_R32G32B32_UNORM = 34, - VIRGL_FORMAT_R32G32B32A32_UNORM = 35, - VIRGL_FORMAT_R32_USCALED = 36, - VIRGL_FORMAT_R32G32_USCALED = 37, - VIRGL_FORMAT_R32G32B32_USCALED = 38, - VIRGL_FORMAT_R32G32B32A32_USCALED = 39, - VIRGL_FORMAT_R32_SNORM = 40, - VIRGL_FORMAT_R32G32_SNORM = 41, - VIRGL_FORMAT_R32G32B32_SNORM = 42, - VIRGL_FORMAT_R32G32B32A32_SNORM = 43, - VIRGL_FORMAT_R32_SSCALED = 44, - VIRGL_FORMAT_R32G32_SSCALED = 45, - VIRGL_FORMAT_R32G32B32_SSCALED = 46, - VIRGL_FORMAT_R32G32B32A32_SSCALED = 47, - - VIRGL_FORMAT_R16_UNORM = 48, - VIRGL_FORMAT_R16G16_UNORM = 49, - VIRGL_FORMAT_R16G16B16_UNORM = 50, - VIRGL_FORMAT_R16G16B16A16_UNORM = 51, - - VIRGL_FORMAT_R16_USCALED = 52, - VIRGL_FORMAT_R16G16_USCALED = 53, - VIRGL_FORMAT_R16G16B16_USCALED = 54, - VIRGL_FORMAT_R16G16B16A16_USCALED = 55, - - VIRGL_FORMAT_R16_SNORM = 56, - VIRGL_FORMAT_R16G16_SNORM = 57, - VIRGL_FORMAT_R16G16B16_SNORM = 58, - VIRGL_FORMAT_R16G16B16A16_SNORM = 59, - - VIRGL_FORMAT_R16_SSCALED = 60, - VIRGL_FORMAT_R16G16_SSCALED = 61, - VIRGL_FORMAT_R16G16B16_SSCALED = 62, - VIRGL_FORMAT_R16G16B16A16_SSCALED = 63, - - VIRGL_FORMAT_R8_UNORM = 64, - VIRGL_FORMAT_R8G8_UNORM = 65, - VIRGL_FORMAT_R8G8B8_UNORM = 66, - VIRGL_FORMAT_R8G8B8A8_UNORM = 67, - VIRGL_FORMAT_X8B8G8R8_UNORM = 68, - - VIRGL_FORMAT_R8_USCALED = 69, - VIRGL_FORMAT_R8G8_USCALED = 70, - VIRGL_FORMAT_R8G8B8_USCALED = 71, - VIRGL_FORMAT_R8G8B8A8_USCALED = 72, - - VIRGL_FORMAT_R8_SNORM = 74, - VIRGL_FORMAT_R8G8_SNORM = 75, - VIRGL_FORMAT_R8G8B8_SNORM = 76, - VIRGL_FORMAT_R8G8B8A8_SNORM = 77, - - VIRGL_FORMAT_R8_SSCALED = 82, - VIRGL_FORMAT_R8G8_SSCALED = 83, - VIRGL_FORMAT_R8G8B8_SSCALED = 84, - VIRGL_FORMAT_R8G8B8A8_SSCALED = 85, - - VIRGL_FORMAT_R32_FIXED = 87, - VIRGL_FORMAT_R32G32_FIXED = 88, - VIRGL_FORMAT_R32G32B32_FIXED = 89, - VIRGL_FORMAT_R32G32B32A32_FIXED = 90, - - VIRGL_FORMAT_R16_FLOAT = 91, - VIRGL_FORMAT_R16G16_FLOAT = 92, - VIRGL_FORMAT_R16G16B16_FLOAT = 93, - VIRGL_FORMAT_R16G16B16A16_FLOAT = 94, - - VIRGL_FORMAT_L8_SRGB = 95, - VIRGL_FORMAT_L8A8_SRGB = 96, - VIRGL_FORMAT_R8G8B8_SRGB = 97, - VIRGL_FORMAT_A8B8G8R8_SRGB = 98, - VIRGL_FORMAT_X8B8G8R8_SRGB = 99, - VIRGL_FORMAT_B8G8R8A8_SRGB = 100, - VIRGL_FORMAT_B8G8R8X8_SRGB = 101, - VIRGL_FORMAT_A8R8G8B8_SRGB = 102, - VIRGL_FORMAT_X8R8G8B8_SRGB = 103, - VIRGL_FORMAT_R8G8B8A8_SRGB = 104, - - /* compressed formats */ - VIRGL_FORMAT_DXT1_RGB = 105, - VIRGL_FORMAT_DXT1_RGBA = 106, - VIRGL_FORMAT_DXT3_RGBA = 107, - VIRGL_FORMAT_DXT5_RGBA = 108, - - /* sRGB, compressed */ - VIRGL_FORMAT_DXT1_SRGB = 109, - VIRGL_FORMAT_DXT1_SRGBA = 110, - VIRGL_FORMAT_DXT3_SRGBA = 111, - VIRGL_FORMAT_DXT5_SRGBA = 112, - - /* rgtc compressed */ - VIRGL_FORMAT_RGTC1_UNORM = 113, - VIRGL_FORMAT_RGTC1_SNORM = 114, - VIRGL_FORMAT_RGTC2_UNORM = 115, - VIRGL_FORMAT_RGTC2_SNORM = 116, - - VIRGL_FORMAT_R8G8_B8G8_UNORM = 117, - VIRGL_FORMAT_G8R8_G8B8_UNORM = 118, - - VIRGL_FORMAT_R8SG8SB8UX8U_NORM = 119, - VIRGL_FORMAT_R5SG5SB6U_NORM = 120, - - VIRGL_FORMAT_A8B8G8R8_UNORM = 121, - VIRGL_FORMAT_B5G5R5X1_UNORM = 122, - VIRGL_FORMAT_R10G10B10A2_USCALED = 123, - VIRGL_FORMAT_R11G11B10_FLOAT = 124, - VIRGL_FORMAT_R9G9B9E5_FLOAT = 125, - VIRGL_FORMAT_Z32_FLOAT_S8X24_UINT = 126, - VIRGL_FORMAT_R1_UNORM = 127, - VIRGL_FORMAT_R10G10B10X2_USCALED = 128, - VIRGL_FORMAT_R10G10B10X2_SNORM = 129, - - VIRGL_FORMAT_L4A4_UNORM = 130, - VIRGL_FORMAT_B10G10R10A2_UNORM = 131, - VIRGL_FORMAT_R10SG10SB10SA2U_NORM = 132, - VIRGL_FORMAT_R8G8Bx_SNORM = 133, - VIRGL_FORMAT_R8G8B8X8_UNORM = 134, - VIRGL_FORMAT_B4G4R4X4_UNORM = 135, - VIRGL_FORMAT_X24S8_UINT = 136, - VIRGL_FORMAT_S8X24_UINT = 137, - VIRGL_FORMAT_X32_S8X24_UINT = 138, - VIRGL_FORMAT_B2G3R3_UNORM = 139, - - VIRGL_FORMAT_L16A16_UNORM = 140, - VIRGL_FORMAT_A16_UNORM = 141, - VIRGL_FORMAT_I16_UNORM = 142, - - VIRGL_FORMAT_LATC1_UNORM = 143, - VIRGL_FORMAT_LATC1_SNORM = 144, - VIRGL_FORMAT_LATC2_UNORM = 145, - VIRGL_FORMAT_LATC2_SNORM = 146, - - VIRGL_FORMAT_A8_SNORM = 147, - VIRGL_FORMAT_L8_SNORM = 148, - VIRGL_FORMAT_L8A8_SNORM = 149, - VIRGL_FORMAT_I8_SNORM = 150, - VIRGL_FORMAT_A16_SNORM = 151, - VIRGL_FORMAT_L16_SNORM = 152, - VIRGL_FORMAT_L16A16_SNORM = 153, - VIRGL_FORMAT_I16_SNORM = 154, - - VIRGL_FORMAT_A16_FLOAT = 155, - VIRGL_FORMAT_L16_FLOAT = 156, - VIRGL_FORMAT_L16A16_FLOAT = 157, - VIRGL_FORMAT_I16_FLOAT = 158, - VIRGL_FORMAT_A32_FLOAT = 159, - VIRGL_FORMAT_L32_FLOAT = 160, - VIRGL_FORMAT_L32A32_FLOAT = 161, - VIRGL_FORMAT_I32_FLOAT = 162, - - VIRGL_FORMAT_YV12 = 163, - VIRGL_FORMAT_YV16 = 164, - VIRGL_FORMAT_IYUV = 165, /**< aka I420 */ - VIRGL_FORMAT_NV12 = 166, - VIRGL_FORMAT_NV21 = 167, - - VIRGL_FORMAT_A4R4_UNORM = 168, - VIRGL_FORMAT_R4A4_UNORM = 169, - VIRGL_FORMAT_R8A8_UNORM = 170, - VIRGL_FORMAT_A8R8_UNORM = 171, - - VIRGL_FORMAT_R10G10B10A2_SSCALED = 172, - VIRGL_FORMAT_R10G10B10A2_SNORM = 173, - VIRGL_FORMAT_B10G10R10A2_USCALED = 174, - VIRGL_FORMAT_B10G10R10A2_SSCALED = 175, - VIRGL_FORMAT_B10G10R10A2_SNORM = 176, - - VIRGL_FORMAT_R8_UINT = 177, - VIRGL_FORMAT_R8G8_UINT = 178, - VIRGL_FORMAT_R8G8B8_UINT = 179, - VIRGL_FORMAT_R8G8B8A8_UINT = 180, - - VIRGL_FORMAT_R8_SINT = 181, - VIRGL_FORMAT_R8G8_SINT = 182, - VIRGL_FORMAT_R8G8B8_SINT = 183, - VIRGL_FORMAT_R8G8B8A8_SINT = 184, - - VIRGL_FORMAT_R16_UINT = 185, - VIRGL_FORMAT_R16G16_UINT = 186, - VIRGL_FORMAT_R16G16B16_UINT = 187, - VIRGL_FORMAT_R16G16B16A16_UINT = 188, - - VIRGL_FORMAT_R16_SINT = 189, - VIRGL_FORMAT_R16G16_SINT = 190, - VIRGL_FORMAT_R16G16B16_SINT = 191, - VIRGL_FORMAT_R16G16B16A16_SINT = 192, - VIRGL_FORMAT_R32_UINT = 193, - VIRGL_FORMAT_R32G32_UINT = 194, - VIRGL_FORMAT_R32G32B32_UINT = 195, - VIRGL_FORMAT_R32G32B32A32_UINT = 196, - - VIRGL_FORMAT_R32_SINT = 197, - VIRGL_FORMAT_R32G32_SINT = 198, - VIRGL_FORMAT_R32G32B32_SINT = 199, - VIRGL_FORMAT_R32G32B32A32_SINT = 200, - - VIRGL_FORMAT_A8_UINT = 201, - VIRGL_FORMAT_I8_UINT = 202, - VIRGL_FORMAT_L8_UINT = 203, - VIRGL_FORMAT_L8A8_UINT = 204, - - VIRGL_FORMAT_A8_SINT = 205, - VIRGL_FORMAT_I8_SINT = 206, - VIRGL_FORMAT_L8_SINT = 207, - VIRGL_FORMAT_L8A8_SINT = 208, - - VIRGL_FORMAT_A16_UINT = 209, - VIRGL_FORMAT_I16_UINT = 210, - VIRGL_FORMAT_L16_UINT = 211, - VIRGL_FORMAT_L16A16_UINT = 212, - - VIRGL_FORMAT_A16_SINT = 213, - VIRGL_FORMAT_I16_SINT = 214, - VIRGL_FORMAT_L16_SINT = 215, - VIRGL_FORMAT_L16A16_SINT = 216, - - VIRGL_FORMAT_A32_UINT = 217, - VIRGL_FORMAT_I32_UINT = 218, - VIRGL_FORMAT_L32_UINT = 219, - VIRGL_FORMAT_L32A32_UINT = 220, - - VIRGL_FORMAT_A32_SINT = 221, - VIRGL_FORMAT_I32_SINT = 222, - VIRGL_FORMAT_L32_SINT = 223, - VIRGL_FORMAT_L32A32_SINT = 224, - - VIRGL_FORMAT_B10G10R10A2_UINT = 225, - VIRGL_FORMAT_ETC1_RGB8 = 226, - VIRGL_FORMAT_R8G8_R8B8_UNORM = 227, - VIRGL_FORMAT_G8R8_B8R8_UNORM = 228, - VIRGL_FORMAT_R8G8B8X8_SNORM = 229, - - VIRGL_FORMAT_R8G8B8X8_SRGB = 230, - - VIRGL_FORMAT_R8G8B8X8_UINT = 231, - VIRGL_FORMAT_R8G8B8X8_SINT = 232, - VIRGL_FORMAT_B10G10R10X2_UNORM = 233, - VIRGL_FORMAT_R16G16B16X16_UNORM = 234, - VIRGL_FORMAT_R16G16B16X16_SNORM = 235, - VIRGL_FORMAT_R16G16B16X16_FLOAT = 236, - VIRGL_FORMAT_R16G16B16X16_UINT = 237, - VIRGL_FORMAT_R16G16B16X16_SINT = 238, - VIRGL_FORMAT_R32G32B32X32_FLOAT = 239, - VIRGL_FORMAT_R32G32B32X32_UINT = 240, - VIRGL_FORMAT_R32G32B32X32_SINT = 241, - VIRGL_FORMAT_R8A8_SNORM = 242, - VIRGL_FORMAT_R16A16_UNORM = 243, - VIRGL_FORMAT_R16A16_SNORM = 244, - VIRGL_FORMAT_R16A16_FLOAT = 245, - VIRGL_FORMAT_R32A32_FLOAT = 246, - VIRGL_FORMAT_R8A8_UINT = 247, - VIRGL_FORMAT_R8A8_SINT = 248, - VIRGL_FORMAT_R16A16_UINT = 249, - VIRGL_FORMAT_R16A16_SINT = 250, - VIRGL_FORMAT_R32A32_UINT = 251, - VIRGL_FORMAT_R32A32_SINT = 252, - - VIRGL_FORMAT_R10G10B10A2_UINT = 253, - VIRGL_FORMAT_B5G6R5_SRGB = 254, - - VIRGL_FORMAT_BPTC_RGBA_UNORM = 255, - VIRGL_FORMAT_BPTC_SRGBA = 256, - VIRGL_FORMAT_BPTC_RGB_FLOAT = 257, - VIRGL_FORMAT_BPTC_RGB_UFLOAT = 258, - - VIRGL_FORMAT_A16L16_UNORM = 262, - - VIRGL_FORMAT_G8R8_UNORM = 263, - VIRGL_FORMAT_G8R8_SNORM = 264, - VIRGL_FORMAT_G16R16_UNORM = 265, - VIRGL_FORMAT_G16R16_SNORM = 266, - VIRGL_FORMAT_A8B8G8R8_SNORM = 267, - - VIRGL_FORMAT_A8L8_UNORM = 259, - VIRGL_FORMAT_A8L8_SNORM = 260, - VIRGL_FORMAT_A8L8_SRGB = 261, - - VIRGL_FORMAT_X8B8G8R8_SNORM = 268, - - VIRGL_FORMAT_R10G10B10X2_UNORM = 308, - VIRGL_FORMAT_A4B4G4R4_UNORM = 311, - - VIRGL_FORMAT_R8_SRGB = 312, - VIRGL_FORMAT_MAX /* = PIPE_FORMAT_COUNT */, - - /* Below formats must not be used in the guest. */ - VIRGL_FORMAT_B8G8R8X8_UNORM_EMULATED, - VIRGL_FORMAT_B8G8R8A8_UNORM_EMULATED, - VIRGL_FORMAT_MAX_EXTENDED -}; - -/* These are used by the capability_bits field in virgl_caps_v2. */ -#define VIRGL_CAP_NONE 0 -#define VIRGL_CAP_TGSI_INVARIANT (1 << 0) -#define VIRGL_CAP_TEXTURE_VIEW (1 << 1) -#define VIRGL_CAP_SET_MIN_SAMPLES (1 << 2) -#define VIRGL_CAP_COPY_IMAGE (1 << 3) -#define VIRGL_CAP_TGSI_PRECISE (1 << 4) -#define VIRGL_CAP_TXQS (1 << 5) -#define VIRGL_CAP_MEMORY_BARRIER (1 << 6) -#define VIRGL_CAP_COMPUTE_SHADER (1 << 7) -#define VIRGL_CAP_FB_NO_ATTACH (1 << 8) -#define VIRGL_CAP_ROBUST_BUFFER_ACCESS (1 << 9) -#define VIRGL_CAP_TGSI_FBFETCH (1 << 10) -#define VIRGL_CAP_SHADER_CLOCK (1 << 11) -#define VIRGL_CAP_TEXTURE_BARRIER (1 << 12) -#define VIRGL_CAP_TGSI_COMPONENTS (1 << 13) -#define VIRGL_CAP_GUEST_MAY_INIT_LOG (1 << 14) -#define VIRGL_CAP_SRGB_WRITE_CONTROL (1 << 15) -#define VIRGL_CAP_QBO (1 << 16) -#define VIRGL_CAP_TRANSFER (1 << 17) -#define VIRGL_CAP_FBO_MIXED_COLOR_FORMATS (1 << 18) -#define VIRGL_CAP_FAKE_FP64 (1 << 19) -#define VIRGL_CAP_BIND_COMMAND_ARGS (1 << 20) -#define VIRGL_CAP_MULTI_DRAW_INDIRECT (1 << 21) -#define VIRGL_CAP_INDIRECT_PARAMS (1 << 22) -#define VIRGL_CAP_TRANSFORM_FEEDBACK3 (1 << 23) -#define VIRGL_CAP_3D_ASTC (1 << 24) -#define VIRGL_CAP_INDIRECT_INPUT_ADDR (1 << 25) -#define VIRGL_CAP_COPY_TRANSFER (1 << 26) -#define VIRGL_CAP_CLIP_HALFZ (1 << 27) -#define VIRGL_CAP_APP_TWEAK_SUPPORT (1 << 28) -#define VIRGL_CAP_BGRA_SRGB_IS_EMULATED (1 << 29) - -/* virgl bind flags - these are compatible with mesa 10.5 gallium. - * but are fixed, no other should be passed to virgl either. - */ -#define VIRGL_BIND_DEPTH_STENCIL (1 << 0) -#define VIRGL_BIND_RENDER_TARGET (1 << 1) -#define VIRGL_BIND_SAMPLER_VIEW (1 << 3) -#define VIRGL_BIND_VERTEX_BUFFER (1 << 4) -#define VIRGL_BIND_INDEX_BUFFER (1 << 5) -#define VIRGL_BIND_CONSTANT_BUFFER (1 << 6) -#define VIRGL_BIND_DISPLAY_TARGET (1 << 7) -#define VIRGL_BIND_COMMAND_ARGS (1 << 8) -#define VIRGL_BIND_STREAM_OUTPUT (1 << 11) -#define VIRGL_BIND_SHADER_BUFFER (1 << 14) -#define VIRGL_BIND_QUERY_BUFFER (1 << 15) -#define VIRGL_BIND_CURSOR (1 << 16) -#define VIRGL_BIND_CUSTOM (1 << 17) -#define VIRGL_BIND_SCANOUT (1 << 18) -/* Used for buffers that are backed by guest storage and - * are only read by the host. - */ -#define VIRGL_BIND_STAGING (1 << 19) -#define VIRGL_BIND_SHARED (1 << 20) - -#define VIRGL_BIND_PREFER_EMULATED_BGRA (1 << 21) - -#define VIRGL_BIND_LINEAR (1 << 22) - -struct virgl_caps_bool_set1 { - unsigned indep_blend_enable : 1; - unsigned indep_blend_func : 1; - unsigned cube_map_array : 1; - unsigned shader_stencil_export : 1; - unsigned conditional_render : 1; - unsigned start_instance : 1; - unsigned primitive_restart : 1; - unsigned blend_eq_sep : 1; - unsigned instanceid : 1; - unsigned vertex_element_instance_divisor : 1; - unsigned seamless_cube_map : 1; - unsigned occlusion_query : 1; - unsigned timer_query : 1; - unsigned streamout_pause_resume : 1; - unsigned texture_multisample : 1; - unsigned fragment_coord_conventions : 1; - unsigned depth_clip_disable : 1; - unsigned seamless_cube_map_per_texture : 1; - unsigned ubo : 1; - unsigned color_clamping : 1; /* not in GL 3.1 core profile */ - unsigned poly_stipple : 1; /* not in GL 3.1 core profile */ - unsigned mirror_clamp : 1; - unsigned texture_query_lod : 1; - unsigned has_fp64 : 1; - unsigned has_tessellation_shaders : 1; - unsigned has_indirect_draw : 1; - unsigned has_sample_shading : 1; - unsigned has_cull : 1; - unsigned conditional_render_inverted : 1; - unsigned derivative_control : 1; - unsigned polygon_offset_clamp : 1; - unsigned transform_feedback_overflow_query : 1; - /* DO NOT ADD ANYMORE MEMBERS - need to add another 32-bit to v2 caps */ -}; - -/* endless expansion capabilites - current gallium has 252 formats */ -struct virgl_supported_format_mask { - uint32_t bitmask[16]; -}; -/* capabilities set 2 - version 1 - 32-bit and float values */ -struct virgl_caps_v1 { - uint32_t max_version; - struct virgl_supported_format_mask sampler; - struct virgl_supported_format_mask render; - struct virgl_supported_format_mask depthstencil; - struct virgl_supported_format_mask vertexbuffer; - struct virgl_caps_bool_set1 bset; - uint32_t glsl_level; - uint32_t max_texture_array_layers; - uint32_t max_streamout_buffers; - uint32_t max_dual_source_render_targets; - uint32_t max_render_targets; - uint32_t max_samples; - uint32_t prim_mask; - uint32_t max_tbo_size; - uint32_t max_uniform_blocks; - uint32_t max_viewports; - uint32_t max_texture_gather_components; -}; - -/* - * This struct should be growable when used in capset 2, - * so we shouldn't have to add a v3 ever. - */ -struct virgl_caps_v2 { - struct virgl_caps_v1 v1; - float min_aliased_point_size; - float max_aliased_point_size; - float min_smooth_point_size; - float max_smooth_point_size; - float min_aliased_line_width; - float max_aliased_line_width; - float min_smooth_line_width; - float max_smooth_line_width; - float max_texture_lod_bias; - uint32_t max_geom_output_vertices; - uint32_t max_geom_total_output_components; - uint32_t max_vertex_outputs; - uint32_t max_vertex_attribs; - uint32_t max_shader_patch_varyings; - int32_t min_texel_offset; - int32_t max_texel_offset; - int32_t min_texture_gather_offset; - int32_t max_texture_gather_offset; - uint32_t texture_buffer_offset_alignment; - uint32_t uniform_buffer_offset_alignment; - uint32_t shader_buffer_offset_alignment; - uint32_t capability_bits; - uint32_t sample_locations[8]; - uint32_t max_vertex_attrib_stride; - uint32_t max_shader_buffer_frag_compute; - uint32_t max_shader_buffer_other_stages; - uint32_t max_shader_image_frag_compute; - uint32_t max_shader_image_other_stages; - uint32_t max_image_samples; - uint32_t max_compute_work_group_invocations; - uint32_t max_compute_shared_memory_size; - uint32_t max_compute_grid_size[3]; - uint32_t max_compute_block_size[3]; - uint32_t max_texture_2d_size; - uint32_t max_texture_3d_size; - uint32_t max_texture_cube_size; - uint32_t max_combined_shader_buffers; - uint32_t max_atomic_counters[6]; - uint32_t max_atomic_counter_buffers[6]; - uint32_t max_combined_atomic_counters; - uint32_t max_combined_atomic_counter_buffers; - uint32_t host_feature_check_version; - struct virgl_supported_format_mask supported_readback_formats; - struct virgl_supported_format_mask scanout; -}; - -union virgl_caps { - uint32_t max_version; - struct virgl_caps_v1 v1; - struct virgl_caps_v2 v2; -}; - -#define VIRGL_RESOURCE_Y_0_TOP (1 << 0) -#endif diff --git a/app/src/main/cpp/virglrenderer/src/virgl_protocol.h b/app/src/main/cpp/virglrenderer/src/virgl_protocol.h deleted file mode 100644 index 8df35d491..000000000 --- a/app/src/main/cpp/virglrenderer/src/virgl_protocol.h +++ /dev/null @@ -1,606 +0,0 @@ -/* - * Copyright 2014, 2015 Red Hat. - * - * 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 - * on the rights to use, copy, modify, merge, publish, distribute, sub - * license, 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 (including the next - * paragraph) 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 NON-INFRINGEMENT. IN NO EVENT SHALL - * THE AUTHOR(S) AND/OR THEIR SUPPLIERS 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. - */ -#ifndef VIRGL_PROTOCOL_H -#define VIRGL_PROTOCOL_H - -#include - -#define VIRGL_QUERY_STATE_NEW 0 -#define VIRGL_QUERY_STATE_DONE 1 -#define VIRGL_QUERY_STATE_WAIT_HOST 2 - -#include - -struct virgl_host_query_state { - uint32_t query_state; - uint32_t result_size; - uint64_t result; -}; - -enum virgl_object_type { - VIRGL_OBJECT_NULL, - VIRGL_OBJECT_BLEND, - VIRGL_OBJECT_RASTERIZER, - VIRGL_OBJECT_DSA, - VIRGL_OBJECT_SHADER, - VIRGL_OBJECT_VERTEX_ELEMENTS, - VIRGL_OBJECT_SAMPLER_VIEW, - VIRGL_OBJECT_SAMPLER_STATE, - VIRGL_OBJECT_SURFACE, - VIRGL_OBJECT_QUERY, - VIRGL_OBJECT_STREAMOUT_TARGET, - VIRGL_MAX_OBJECTS, -}; - -/* context cmds to be encoded in the command stream */ -enum virgl_context_cmd { - VIRGL_CCMD_NOP = 0, - VIRGL_CCMD_CREATE_OBJECT = 1, - VIRGL_CCMD_BIND_OBJECT, - VIRGL_CCMD_DESTROY_OBJECT, - VIRGL_CCMD_SET_VIEWPORT_STATE, - VIRGL_CCMD_SET_FRAMEBUFFER_STATE, - VIRGL_CCMD_SET_VERTEX_BUFFERS, - VIRGL_CCMD_CLEAR, - VIRGL_CCMD_DRAW_VBO, - VIRGL_CCMD_RESOURCE_INLINE_WRITE, - VIRGL_CCMD_SET_SAMPLER_VIEWS, - VIRGL_CCMD_SET_INDEX_BUFFER, - VIRGL_CCMD_SET_CONSTANT_BUFFER, - VIRGL_CCMD_SET_STENCIL_REF, - VIRGL_CCMD_SET_BLEND_COLOR, - VIRGL_CCMD_SET_SCISSOR_STATE, - VIRGL_CCMD_BLIT, - VIRGL_CCMD_RESOURCE_COPY_REGION, - VIRGL_CCMD_BIND_SAMPLER_STATES, - VIRGL_CCMD_BEGIN_QUERY, - VIRGL_CCMD_END_QUERY, - VIRGL_CCMD_GET_QUERY_RESULT, - VIRGL_CCMD_SET_POLYGON_STIPPLE, - VIRGL_CCMD_SET_CLIP_STATE, - VIRGL_CCMD_SET_SAMPLE_MASK, - VIRGL_CCMD_SET_STREAMOUT_TARGETS, - VIRGL_CCMD_SET_RENDER_CONDITION, - VIRGL_CCMD_SET_UNIFORM_BUFFER, - - VIRGL_CCMD_SET_SUB_CTX, - VIRGL_CCMD_CREATE_SUB_CTX, - VIRGL_CCMD_DESTROY_SUB_CTX, - VIRGL_CCMD_BIND_SHADER, - VIRGL_CCMD_SET_TESS_STATE, - VIRGL_CCMD_SET_MIN_SAMPLES, - VIRGL_CCMD_SET_SHADER_BUFFERS, - VIRGL_CCMD_SET_SHADER_IMAGES, - VIRGL_CCMD_MEMORY_BARRIER, - VIRGL_CCMD_LAUNCH_GRID, - VIRGL_CCMD_SET_FRAMEBUFFER_STATE_NO_ATTACH, - VIRGL_CCMD_TEXTURE_BARRIER, - VIRGL_CCMD_SET_ATOMIC_BUFFERS, - VIRGL_CCMD_SET_DEBUG_FLAGS, - VIRGL_CCMD_GET_QUERY_RESULT_QBO, - VIRGL_CCMD_TRANSFER3D, - VIRGL_CCMD_END_TRANSFERS, - VIRGL_CCMD_COPY_TRANSFER3D, - VIRGL_CCMD_SET_TWEAKS, - VIRGL_MAX_COMMANDS -}; - -/* - 8-bit cmd headers - 8-bit object type - 16-bit length -*/ - -#define VIRGL_CMD0(cmd, obj, len) ((cmd) | ((obj) << 8) | ((len) << 16)) - -/* hw specification */ -#define VIRGL_MAX_COLOR_BUFS 8 -#define VIRGL_MAX_CLIP_PLANES 8 - -#define VIRGL_OBJ_CREATE_HEADER 0 -#define VIRGL_OBJ_CREATE_HANDLE 1 - -#define VIRGL_OBJ_BIND_HEADER 0 -#define VIRGL_OBJ_BIND_HANDLE 1 - -#define VIRGL_OBJ_DESTROY_HANDLE 1 - -/* some of these defines are a specification - not used in the code */ -/* bit offsets for blend state object */ -#define VIRGL_OBJ_BLEND_SIZE (VIRGL_MAX_COLOR_BUFS + 3) -#define VIRGL_OBJ_BLEND_HANDLE 1 -#define VIRGL_OBJ_BLEND_S0 2 -#define VIRGL_OBJ_BLEND_S0_INDEPENDENT_BLEND_ENABLE(x) ((x) & 0x1 << 0) -#define VIRGL_OBJ_BLEND_S0_LOGICOP_ENABLE(x) (((x) & 0x1) << 1) -#define VIRGL_OBJ_BLEND_S0_DITHER(x) (((x) & 0x1) << 2) -#define VIRGL_OBJ_BLEND_S0_ALPHA_TO_COVERAGE(x) (((x) & 0x1) << 3) -#define VIRGL_OBJ_BLEND_S0_ALPHA_TO_ONE(x) (((x) & 0x1) << 4) -#define VIRGL_OBJ_BLEND_S1 3 -#define VIRGL_OBJ_BLEND_S1_LOGICOP_FUNC(x) (((x) & 0xf) << 0) -/* repeated once per number of cbufs */ - -#define VIRGL_OBJ_BLEND_S2(cbuf) (4 + (cbuf)) -#define VIRGL_OBJ_BLEND_S2_RT_BLEND_ENABLE(x) (((x) & 0x1) << 0) -#define VIRGL_OBJ_BLEND_S2_RT_RGB_FUNC(x) (((x) & 0x7) << 1) -#define VIRGL_OBJ_BLEND_S2_RT_RGB_SRC_FACTOR(x) (((x) & 0x1f) << 4) -#define VIRGL_OBJ_BLEND_S2_RT_RGB_DST_FACTOR(x) (((x) & 0x1f) << 9) -#define VIRGL_OBJ_BLEND_S2_RT_ALPHA_FUNC(x) (((x) & 0x7) << 14) -#define VIRGL_OBJ_BLEND_S2_RT_ALPHA_SRC_FACTOR(x) (((x) & 0x1f) << 17) -#define VIRGL_OBJ_BLEND_S2_RT_ALPHA_DST_FACTOR(x) (((x) & 0x1f) << 22) -#define VIRGL_OBJ_BLEND_S2_RT_COLORMASK(x) (((x) & 0xf) << 27) - -/* bit offsets for DSA state */ -#define VIRGL_OBJ_DSA_SIZE 5 -#define VIRGL_OBJ_DSA_HANDLE 1 -#define VIRGL_OBJ_DSA_S0 2 -#define VIRGL_OBJ_DSA_S0_DEPTH_ENABLE(x) (((x) & 0x1) << 0) -#define VIRGL_OBJ_DSA_S0_DEPTH_WRITEMASK(x) (((x) & 0x1) << 1) -#define VIRGL_OBJ_DSA_S0_DEPTH_FUNC(x) (((x) & 0x7) << 2) -#define VIRGL_OBJ_DSA_S0_ALPHA_ENABLED(x) (((x) & 0x1) << 8) -#define VIRGL_OBJ_DSA_S0_ALPHA_FUNC(x) (((x) & 0x7) << 9) -#define VIRGL_OBJ_DSA_S1 3 -#define VIRGL_OBJ_DSA_S2 4 -#define VIRGL_OBJ_DSA_S1_STENCIL_ENABLED(x) (((x) & 0x1) << 0) -#define VIRGL_OBJ_DSA_S1_STENCIL_FUNC(x) (((x) & 0x7) << 1) -#define VIRGL_OBJ_DSA_S1_STENCIL_FAIL_OP(x) (((x) & 0x7) << 4) -#define VIRGL_OBJ_DSA_S1_STENCIL_ZPASS_OP(x) (((x) & 0x7) << 7) -#define VIRGL_OBJ_DSA_S1_STENCIL_ZFAIL_OP(x) (((x) & 0x7) << 10) -#define VIRGL_OBJ_DSA_S1_STENCIL_VALUEMASK(x) (((x) & 0xff) << 13) -#define VIRGL_OBJ_DSA_S1_STENCIL_WRITEMASK(x) (((x) & 0xff) << 21) -#define VIRGL_OBJ_DSA_ALPHA_REF 5 - -/* offsets for rasterizer state */ -#define VIRGL_OBJ_RS_SIZE 9 -#define VIRGL_OBJ_RS_HANDLE 1 -#define VIRGL_OBJ_RS_S0 2 -#define VIRGL_OBJ_RS_S0_FLATSHADE(x) (((x) & 0x1) << 0) -#define VIRGL_OBJ_RS_S0_DEPTH_CLIP(x) (((x) & 0x1) << 1) -#define VIRGL_OBJ_RS_S0_CLIP_HALFZ(x) (((x) & 0x1) << 2) -#define VIRGL_OBJ_RS_S0_RASTERIZER_DISCARD(x) (((x) & 0x1) << 3) -#define VIRGL_OBJ_RS_S0_FLATSHADE_FIRST(x) (((x) & 0x1) << 4) -#define VIRGL_OBJ_RS_S0_LIGHT_TWOSIZE(x) (((x) & 0x1) << 5) -#define VIRGL_OBJ_RS_S0_SPRITE_COORD_MODE(x) (((x) & 0x1) << 6) -#define VIRGL_OBJ_RS_S0_POINT_QUAD_RASTERIZATION(x) (((x) & 0x1) << 7) -#define VIRGL_OBJ_RS_S0_CULL_FACE(x) (((x) & 0x3) << 8) -#define VIRGL_OBJ_RS_S0_FILL_FRONT(x) (((x) & 0x3) << 10) -#define VIRGL_OBJ_RS_S0_FILL_BACK(x) (((x) & 0x3) << 12) -#define VIRGL_OBJ_RS_S0_SCISSOR(x) (((x) & 0x1) << 14) -#define VIRGL_OBJ_RS_S0_FRONT_CCW(x) (((x) & 0x1) << 15) -#define VIRGL_OBJ_RS_S0_CLAMP_VERTEX_COLOR(x) (((x) & 0x1) << 16) -#define VIRGL_OBJ_RS_S0_CLAMP_FRAGMENT_COLOR(x) (((x) & 0x1) << 17) -#define VIRGL_OBJ_RS_S0_OFFSET_LINE(x) (((x) & 0x1) << 18) -#define VIRGL_OBJ_RS_S0_OFFSET_POINT(x) (((x) & 0x1) << 19) -#define VIRGL_OBJ_RS_S0_OFFSET_TRI(x) (((x) & 0x1) << 20) -#define VIRGL_OBJ_RS_S0_POLY_SMOOTH(x) (((x) & 0x1) << 21) -#define VIRGL_OBJ_RS_S0_POLY_STIPPLE_ENABLE(x) (((x) & 0x1) << 22) -#define VIRGL_OBJ_RS_S0_POINT_SMOOTH(x) (((x) & 0x1) << 23) -#define VIRGL_OBJ_RS_S0_POINT_SIZE_PER_VERTEX(x) (((x) & 0x1) << 24) -#define VIRGL_OBJ_RS_S0_MULTISAMPLE(x) (((x) & 0x1) << 25) -#define VIRGL_OBJ_RS_S0_LINE_SMOOTH(x) (((x) & 0x1) << 26) -#define VIRGL_OBJ_RS_S0_LINE_STIPPLE_ENABLE(x) (((x) & 0x1) << 27) -#define VIRGL_OBJ_RS_S0_LINE_LAST_PIXEL(x) (((x) & 0x1) << 28) -#define VIRGL_OBJ_RS_S0_HALF_PIXEL_CENTER(x) (((x) & 0x1) << 29) -#define VIRGL_OBJ_RS_S0_BOTTOM_EDGE_RULE(x) (((x) & 0x1) << 30) -#define VIRGL_OBJ_RS_S0_FORCE_PERSAMPLE_INTERP(x) (((x) & 0x1) << 31) - -#define VIRGL_OBJ_RS_POINT_SIZE 3 -#define VIRGL_OBJ_RS_SPRITE_COORD_ENABLE 4 -#define VIRGL_OBJ_RS_S3 5 - -#define VIRGL_OBJ_RS_S3_LINE_STIPPLE_PATTERN(x) (((x) & 0xffff) << 0) -#define VIRGL_OBJ_RS_S3_LINE_STIPPLE_FACTOR(x) (((x) & 0xff) << 16) -#define VIRGL_OBJ_RS_S3_CLIP_PLANE_ENABLE(x) (((x) & 0xff) << 24) -#define VIRGL_OBJ_RS_LINE_WIDTH 6 -#define VIRGL_OBJ_RS_OFFSET_UNITS 7 -#define VIRGL_OBJ_RS_OFFSET_SCALE 8 -#define VIRGL_OBJ_RS_OFFSET_CLAMP 9 - -#define VIRGL_OBJ_CLEAR_SIZE 8 -#define VIRGL_OBJ_CLEAR_BUFFERS 1 -#define VIRGL_OBJ_CLEAR_COLOR_0 2 /* color is 4 * u32/f32/i32 */ -#define VIRGL_OBJ_CLEAR_COLOR_1 3 -#define VIRGL_OBJ_CLEAR_COLOR_2 4 -#define VIRGL_OBJ_CLEAR_COLOR_3 5 -#define VIRGL_OBJ_CLEAR_DEPTH_0 6 /* depth is a double precision float */ -#define VIRGL_OBJ_CLEAR_DEPTH_1 7 -#define VIRGL_OBJ_CLEAR_STENCIL 8 - -/* shader object */ -#define VIRGL_OBJ_SHADER_HDR_SIZE(nso) (5 + ((nso) ? (2 * nso) + 4 : 0)) -#define VIRGL_OBJ_SHADER_HANDLE 1 -#define VIRGL_OBJ_SHADER_TYPE 2 -#define VIRGL_OBJ_SHADER_OFFSET 3 -#define VIRGL_OBJ_SHADER_OFFSET_VAL(x) (((x) & 0x7fffffff) << 0) -/* start contains full length in VAL - also implies continuations */ -/* continuation contains offset in VAL */ -#define VIRGL_OBJ_SHADER_OFFSET_CONT (0x1u << 31) -#define VIRGL_OBJ_SHADER_NUM_TOKENS 4 -#define VIRGL_OBJ_SHADER_SO_NUM_OUTPUTS 5 -#define VIRGL_OBJ_SHADER_SO_STRIDE(x) (6 + (x)) -#define VIRGL_OBJ_SHADER_SO_OUTPUT0(x) (10 + (x * 2)) -#define VIRGL_OBJ_SHADER_SO_OUTPUT_REGISTER_INDEX(x) (((x) & 0xff) << 0) -#define VIRGL_OBJ_SHADER_SO_OUTPUT_START_COMPONENT(x) (((x) & 0x3) << 8) -#define VIRGL_OBJ_SHADER_SO_OUTPUT_NUM_COMPONENTS(x) (((x) & 0x7) << 10) -#define VIRGL_OBJ_SHADER_SO_OUTPUT_BUFFER(x) (((x) & 0x7) << 13) -#define VIRGL_OBJ_SHADER_SO_OUTPUT_DST_OFFSET(x) (((x) & 0xffff) << 16) -#define VIRGL_OBJ_SHADER_SO_OUTPUT0_SO(x) (11 + (x * 2)) -#define VIRGL_OBJ_SHADER_SO_OUTPUT_STREAM(x) (((x) & 0x03) << 0) - -/* viewport state */ -#define VIRGL_SET_VIEWPORT_STATE_SIZE(num_viewports) ((6 * num_viewports) + 1) -#define VIRGL_SET_VIEWPORT_START_SLOT 1 -#define VIRGL_SET_VIEWPORT_STATE_SCALE_0(x) (2 + (x * 6)) -#define VIRGL_SET_VIEWPORT_STATE_SCALE_1(x) (3 + (x * 6)) -#define VIRGL_SET_VIEWPORT_STATE_SCALE_2(x) (4 + (x * 6)) -#define VIRGL_SET_VIEWPORT_STATE_TRANSLATE_0(x) (5 + (x * 6)) -#define VIRGL_SET_VIEWPORT_STATE_TRANSLATE_1(x) (6 + (x * 6)) -#define VIRGL_SET_VIEWPORT_STATE_TRANSLATE_2(x) (7 + (x * 6)) - -/* framebuffer state */ -#define VIRGL_SET_FRAMEBUFFER_STATE_SIZE(nr_cbufs) (nr_cbufs + 2) -#define VIRGL_SET_FRAMEBUFFER_STATE_NR_CBUFS 1 -#define VIRGL_SET_FRAMEBUFFER_STATE_NR_ZSURF_HANDLE 2 -#define VIRGL_SET_FRAMEBUFFER_STATE_CBUF_HANDLE(x) ((x) + 3) - -/* vertex elements object */ -#define VIRGL_OBJ_VERTEX_ELEMENTS_SIZE(num_elements) (((num_elements) * 4) + 1) -#define VIRGL_OBJ_VERTEX_ELEMENTS_HANDLE 1 -#define VIRGL_OBJ_VERTEX_ELEMENTS_V0_SRC_OFFSET(x) \ - (((x) * 4) + 2) /* repeated per VE */ -#define VIRGL_OBJ_VERTEX_ELEMENTS_V0_INSTANCE_DIVISOR(x) (((x) * 4) + 3) -#define VIRGL_OBJ_VERTEX_ELEMENTS_V0_VERTEX_BUFFER_INDEX(x) (((x) * 4) + 4) -#define VIRGL_OBJ_VERTEX_ELEMENTS_V0_SRC_FORMAT(x) (((x) * 4) + 5) - -/* vertex buffers */ -#define VIRGL_SET_VERTEX_BUFFERS_SIZE(num_buffers) ((num_buffers) * 3) -#define VIRGL_SET_VERTEX_BUFFER_STRIDE(x) (((x) * 3) + 1) -#define VIRGL_SET_VERTEX_BUFFER_OFFSET(x) (((x) * 3) + 2) -#define VIRGL_SET_VERTEX_BUFFER_HANDLE(x) (((x) * 3) + 3) - -/* index buffer */ -#define VIRGL_SET_INDEX_BUFFER_SIZE(ib) (((ib) ? 2 : 0) + 1) -#define VIRGL_SET_INDEX_BUFFER_HANDLE 1 -#define VIRGL_SET_INDEX_BUFFER_INDEX_SIZE 2 /* only if sending an IB handle */ -#define VIRGL_SET_INDEX_BUFFER_OFFSET 3 /* only if sending an IB handle */ - -/* constant buffer */ -#define VIRGL_SET_CONSTANT_BUFFER_SHADER_TYPE 1 -#define VIRGL_SET_CONSTANT_BUFFER_INDEX 2 -#define VIRGL_SET_CONSTANT_BUFFER_DATA_START 3 - -#define VIRGL_SET_UNIFORM_BUFFER_SIZE 5 -#define VIRGL_SET_UNIFORM_BUFFER_SHADER_TYPE 1 -#define VIRGL_SET_UNIFORM_BUFFER_INDEX 2 -#define VIRGL_SET_UNIFORM_BUFFER_OFFSET 3 -#define VIRGL_SET_UNIFORM_BUFFER_LENGTH 4 -#define VIRGL_SET_UNIFORM_BUFFER_RES_HANDLE 5 - -/* draw VBO */ -#define VIRGL_DRAW_VBO_SIZE 12 -#define VIRGL_DRAW_VBO_SIZE_TESS 14 -#define VIRGL_DRAW_VBO_SIZE_INDIRECT 20 -#define VIRGL_DRAW_VBO_START 1 -#define VIRGL_DRAW_VBO_COUNT 2 -#define VIRGL_DRAW_VBO_MODE 3 -#define VIRGL_DRAW_VBO_INDEXED 4 -#define VIRGL_DRAW_VBO_INSTANCE_COUNT 5 -#define VIRGL_DRAW_VBO_INDEX_BIAS 6 -#define VIRGL_DRAW_VBO_START_INSTANCE 7 -#define VIRGL_DRAW_VBO_PRIMITIVE_RESTART 8 -#define VIRGL_DRAW_VBO_RESTART_INDEX 9 -#define VIRGL_DRAW_VBO_MIN_INDEX 10 -#define VIRGL_DRAW_VBO_MAX_INDEX 11 -#define VIRGL_DRAW_VBO_COUNT_FROM_SO 12 -/* tess packet */ -#define VIRGL_DRAW_VBO_VERTICES_PER_PATCH 13 -#define VIRGL_DRAW_VBO_DRAWID 14 -/* indirect packet */ -#define VIRGL_DRAW_VBO_INDIRECT_HANDLE 15 -#define VIRGL_DRAW_VBO_INDIRECT_OFFSET 16 -#define VIRGL_DRAW_VBO_INDIRECT_STRIDE 17 -#define VIRGL_DRAW_VBO_INDIRECT_DRAW_COUNT 18 -#define VIRGL_DRAW_VBO_INDIRECT_DRAW_COUNT_OFFSET 19 -#define VIRGL_DRAW_VBO_INDIRECT_DRAW_COUNT_HANDLE 20 - -/* create surface */ -#define VIRGL_OBJ_SURFACE_SIZE 5 -#define VIRGL_OBJ_SURFACE_HANDLE 1 -#define VIRGL_OBJ_SURFACE_RES_HANDLE 2 -#define VIRGL_OBJ_SURFACE_FORMAT 3 -#define VIRGL_OBJ_SURFACE_BUFFER_FIRST_ELEMENT 4 -#define VIRGL_OBJ_SURFACE_BUFFER_LAST_ELEMENT 5 -#define VIRGL_OBJ_SURFACE_TEXTURE_LEVEL 4 -#define VIRGL_OBJ_SURFACE_TEXTURE_LAYERS 5 - -/* create streamout target */ -#define VIRGL_OBJ_STREAMOUT_SIZE 4 -#define VIRGL_OBJ_STREAMOUT_HANDLE 1 -#define VIRGL_OBJ_STREAMOUT_RES_HANDLE 2 -#define VIRGL_OBJ_STREAMOUT_BUFFER_OFFSET 3 -#define VIRGL_OBJ_STREAMOUT_BUFFER_SIZE 4 - -/* sampler state */ -#define VIRGL_OBJ_SAMPLER_STATE_SIZE 9 -#define VIRGL_OBJ_SAMPLER_STATE_HANDLE 1 -#define VIRGL_OBJ_SAMPLER_STATE_S0 2 -#define VIRGL_OBJ_SAMPLE_STATE_S0_WRAP_S(x) (((x) & 0x7) << 0) -#define VIRGL_OBJ_SAMPLE_STATE_S0_WRAP_T(x) (((x) & 0x7) << 3) -#define VIRGL_OBJ_SAMPLE_STATE_S0_WRAP_R(x) (((x) & 0x7) << 6) -#define VIRGL_OBJ_SAMPLE_STATE_S0_MIN_IMG_FILTER(x) (((x) & 0x3) << 9) -#define VIRGL_OBJ_SAMPLE_STATE_S0_MIN_MIP_FILTER(x) (((x) & 0x3) << 11) -#define VIRGL_OBJ_SAMPLE_STATE_S0_MAG_IMG_FILTER(x) (((x) & 0x3) << 13) -#define VIRGL_OBJ_SAMPLE_STATE_S0_COMPARE_MODE(x) (((x) & 0x1) << 15) -#define VIRGL_OBJ_SAMPLE_STATE_S0_COMPARE_FUNC(x) (((x) & 0x7) << 16) -#define VIRGL_OBJ_SAMPLE_STATE_S0_SEAMLESS_CUBE_MAP(x) (((x) & 0x1) << 19) - -#define VIRGL_OBJ_SAMPLER_STATE_LOD_BIAS 3 -#define VIRGL_OBJ_SAMPLER_STATE_MIN_LOD 4 -#define VIRGL_OBJ_SAMPLER_STATE_MAX_LOD 5 -#define VIRGL_OBJ_SAMPLER_STATE_BORDER_COLOR(x) ((x) + 6) /* 6 - 9 */ - -/* sampler view */ -#define VIRGL_OBJ_SAMPLER_VIEW_SIZE 6 -#define VIRGL_OBJ_SAMPLER_VIEW_HANDLE 1 -#define VIRGL_OBJ_SAMPLER_VIEW_RES_HANDLE 2 -#define VIRGL_OBJ_SAMPLER_VIEW_FORMAT 3 -#define VIRGL_OBJ_SAMPLER_VIEW_BUFFER_FIRST_ELEMENT 4 -#define VIRGL_OBJ_SAMPLER_VIEW_BUFFER_LAST_ELEMENT 5 -#define VIRGL_OBJ_SAMPLER_VIEW_TEXTURE_LAYER 4 -#define VIRGL_OBJ_SAMPLER_VIEW_TEXTURE_LEVEL 5 -#define VIRGL_OBJ_SAMPLER_VIEW_SWIZZLE 6 -#define VIRGL_OBJ_SAMPLER_VIEW_SWIZZLE_R(x) (((x) & 0x7) << 0) -#define VIRGL_OBJ_SAMPLER_VIEW_SWIZZLE_G(x) (((x) & 0x7) << 3) -#define VIRGL_OBJ_SAMPLER_VIEW_SWIZZLE_B(x) (((x) & 0x7) << 6) -#define VIRGL_OBJ_SAMPLER_VIEW_SWIZZLE_A(x) (((x) & 0x7) << 9) - -/* set sampler views */ -#define VIRGL_SET_SAMPLER_VIEWS_SIZE(num_views) ((num_views) + 2) -#define VIRGL_SET_SAMPLER_VIEWS_SHADER_TYPE 1 -#define VIRGL_SET_SAMPLER_VIEWS_START_SLOT 2 -#define VIRGL_SET_SAMPLER_VIEWS_V0_HANDLE 3 - -/* bind sampler states */ -#define VIRGL_BIND_SAMPLER_STATES(num_states) ((num_states) + 2) -#define VIRGL_BIND_SAMPLER_STATES_SHADER_TYPE 1 -#define VIRGL_BIND_SAMPLER_STATES_START_SLOT 2 -#define VIRGL_BIND_SAMPLER_STATES_S0_HANDLE 3 - -/* set stencil reference */ -#define VIRGL_SET_STENCIL_REF_SIZE 1 -#define VIRGL_SET_STENCIL_REF 1 -#define VIRGL_STENCIL_REF_VAL(f, s) ((f & 0xff) | (((s & 0xff) << 8))) - -/* set blend color */ -#define VIRGL_SET_BLEND_COLOR_SIZE 4 -#define VIRGL_SET_BLEND_COLOR(x) ((x) + 1) - -/* set scissor state */ -#define VIRGL_SET_SCISSOR_STATE_SIZE(x) (1 + 2 * x) -#define VIRGL_SET_SCISSOR_START_SLOT 1 -#define VIRGL_SET_SCISSOR_MINX_MINY(x) (2 + (x * 2)) -#define VIRGL_SET_SCISSOR_MAXX_MAXY(x) (3 + (x * 2)) - -/* resource copy region */ -#define VIRGL_CMD_RESOURCE_COPY_REGION_SIZE 13 -#define VIRGL_CMD_RCR_DST_RES_HANDLE 1 -#define VIRGL_CMD_RCR_DST_LEVEL 2 -#define VIRGL_CMD_RCR_DST_X 3 -#define VIRGL_CMD_RCR_DST_Y 4 -#define VIRGL_CMD_RCR_DST_Z 5 -#define VIRGL_CMD_RCR_SRC_RES_HANDLE 6 -#define VIRGL_CMD_RCR_SRC_LEVEL 7 -#define VIRGL_CMD_RCR_SRC_X 8 -#define VIRGL_CMD_RCR_SRC_Y 9 -#define VIRGL_CMD_RCR_SRC_Z 10 -#define VIRGL_CMD_RCR_SRC_W 11 -#define VIRGL_CMD_RCR_SRC_H 12 -#define VIRGL_CMD_RCR_SRC_D 13 - -/* blit */ -#define VIRGL_CMD_BLIT_SIZE 21 -#define VIRGL_CMD_BLIT_S0 1 -#define VIRGL_CMD_BLIT_S0_MASK(x) (((x) & 0xff) << 0) -#define VIRGL_CMD_BLIT_S0_FILTER(x) (((x) & 0x3) << 8) -#define VIRGL_CMD_BLIT_S0_SCISSOR_ENABLE(x) (((x) & 0x1) << 10) -#define VIRGL_CMD_BLIT_S0_RENDER_CONDITION_ENABLE(x) (((x) & 0x1) << 11) -#define VIRGL_CMD_BLIT_S0_ALPHA_BLEND(x) (((x) & 0x1) << 12) -#define VIRGL_CMD_BLIT_SCISSOR_MINX_MINY 2 -#define VIRGL_CMD_BLIT_SCISSOR_MAXX_MAXY 3 -#define VIRGL_CMD_BLIT_DST_RES_HANDLE 4 -#define VIRGL_CMD_BLIT_DST_LEVEL 5 -#define VIRGL_CMD_BLIT_DST_FORMAT 6 -#define VIRGL_CMD_BLIT_DST_X 7 -#define VIRGL_CMD_BLIT_DST_Y 8 -#define VIRGL_CMD_BLIT_DST_Z 9 -#define VIRGL_CMD_BLIT_DST_W 10 -#define VIRGL_CMD_BLIT_DST_H 11 -#define VIRGL_CMD_BLIT_DST_D 12 -#define VIRGL_CMD_BLIT_SRC_RES_HANDLE 13 -#define VIRGL_CMD_BLIT_SRC_LEVEL 14 -#define VIRGL_CMD_BLIT_SRC_FORMAT 15 -#define VIRGL_CMD_BLIT_SRC_X 16 -#define VIRGL_CMD_BLIT_SRC_Y 17 -#define VIRGL_CMD_BLIT_SRC_Z 18 -#define VIRGL_CMD_BLIT_SRC_W 19 -#define VIRGL_CMD_BLIT_SRC_H 20 -#define VIRGL_CMD_BLIT_SRC_D 21 - -/* query object */ -#define VIRGL_OBJ_QUERY_SIZE 4 -#define VIRGL_OBJ_QUERY_HANDLE 1 -#define VIRGL_OBJ_QUERY_TYPE_INDEX 2 -#define VIRGL_OBJ_QUERY_TYPE(x) (x & 0xffff) -#define VIRGL_OBJ_QUERY_INDEX(x) ((x & 0xffff) << 16) -#define VIRGL_OBJ_QUERY_OFFSET 3 -#define VIRGL_OBJ_QUERY_RES_HANDLE 4 - -#define VIRGL_QUERY_BEGIN_HANDLE 1 - -#define VIRGL_QUERY_END_HANDLE 1 - -#define VIRGL_QUERY_RESULT_HANDLE 1 -#define VIRGL_QUERY_RESULT_WAIT 2 - -/* render condition */ -#define VIRGL_RENDER_CONDITION_SIZE 3 -#define VIRGL_RENDER_CONDITION_HANDLE 1 -#define VIRGL_RENDER_CONDITION_CONDITION 2 -#define VIRGL_RENDER_CONDITION_MODE 3 - -/* resource inline write */ -#define VIRGL_RESOURCE_IW_RES_HANDLE 1 -#define VIRGL_RESOURCE_IW_LEVEL 2 -#define VIRGL_RESOURCE_IW_USAGE 3 -#define VIRGL_RESOURCE_IW_STRIDE 4 -#define VIRGL_RESOURCE_IW_LAYER_STRIDE 5 -#define VIRGL_RESOURCE_IW_X 6 -#define VIRGL_RESOURCE_IW_Y 7 -#define VIRGL_RESOURCE_IW_Z 8 -#define VIRGL_RESOURCE_IW_W 9 -#define VIRGL_RESOURCE_IW_H 10 -#define VIRGL_RESOURCE_IW_D 11 -#define VIRGL_RESOURCE_IW_DATA_START 12 - -/* set streamout targets */ -#define VIRGL_SET_STREAMOUT_TARGETS_APPEND_BITMASK 1 -#define VIRGL_SET_STREAMOUT_TARGETS_H0 2 - -/* set sample mask */ -#define VIRGL_SET_SAMPLE_MASK_SIZE 1 -#define VIRGL_SET_SAMPLE_MASK_MASK 1 - -/* set clip state */ -#define VIRGL_SET_CLIP_STATE_SIZE 32 -#define VIRGL_SET_CLIP_STATE_C0 1 - -/* polygon stipple */ -#define VIRGL_POLYGON_STIPPLE_SIZE 32 -#define VIRGL_POLYGON_STIPPLE_P0 1 - -#define VIRGL_BIND_SHADER_SIZE 2 -#define VIRGL_BIND_SHADER_HANDLE 1 -#define VIRGL_BIND_SHADER_TYPE 2 - -/* tess state */ -#define VIRGL_TESS_STATE_SIZE 6 - -/* set min samples */ -#define VIRGL_SET_MIN_SAMPLES_SIZE 1 -#define VIRGL_SET_MIN_SAMPLES_MASK 1 - -/* set shader buffers */ -#define VIRGL_SET_SHADER_BUFFER_ELEMENT_SIZE 3 -#define VIRGL_SET_SHADER_BUFFER_SIZE(x) \ - (VIRGL_SET_SHADER_BUFFER_ELEMENT_SIZE * (x)) + 2 -#define VIRGL_SET_SHADER_BUFFER_SHADER_TYPE 1 -#define VIRGL_SET_SHADER_BUFFER_START_SLOT 2 -#define VIRGL_SET_SHADER_BUFFER_OFFSET(x) \ - ((x) * VIRGL_SET_SHADER_BUFFER_ELEMENT_SIZE + 3) -#define VIRGL_SET_SHADER_BUFFER_LENGTH(x) \ - ((x) * VIRGL_SET_SHADER_BUFFER_ELEMENT_SIZE + 4) -#define VIRGL_SET_SHADER_BUFFER_RES_HANDLE(x) \ - ((x) * VIRGL_SET_SHADER_BUFFER_ELEMENT_SIZE + 5) - -/* set shader images */ -#define VIRGL_SET_SHADER_IMAGE_ELEMENT_SIZE 5 -#define VIRGL_SET_SHADER_IMAGE_SIZE(x) \ - (VIRGL_SET_SHADER_IMAGE_ELEMENT_SIZE * (x)) + 2 -#define VIRGL_SET_SHADER_IMAGE_SHADER_TYPE 1 -#define VIRGL_SET_SHADER_IMAGE_START_SLOT 2 -#define VIRGL_SET_SHADER_IMAGE_FORMAT(x) \ - ((x) * VIRGL_SET_SHADER_IMAGE_ELEMENT_SIZE + 3) -#define VIRGL_SET_SHADER_IMAGE_ACCESS(x) \ - ((x) * VIRGL_SET_SHADER_IMAGE_ELEMENT_SIZE + 4) -#define VIRGL_SET_SHADER_IMAGE_LAYER_OFFSET(x) \ - ((x) * VIRGL_SET_SHADER_IMAGE_ELEMENT_SIZE + 5) -#define VIRGL_SET_SHADER_IMAGE_LEVEL_SIZE(x) \ - ((x) * VIRGL_SET_SHADER_IMAGE_ELEMENT_SIZE + 6) -#define VIRGL_SET_SHADER_IMAGE_RES_HANDLE(x) \ - ((x) * VIRGL_SET_SHADER_IMAGE_ELEMENT_SIZE + 7) - -/* memory barrier */ -#define VIRGL_MEMORY_BARRIER_SIZE 1 -#define VIRGL_MEMORY_BARRIER_FLAGS 1 - -/* launch grid */ -#define VIRGL_LAUNCH_GRID_SIZE 8 -#define VIRGL_LAUNCH_BLOCK_X 1 -#define VIRGL_LAUNCH_BLOCK_Y 2 -#define VIRGL_LAUNCH_BLOCK_Z 3 -#define VIRGL_LAUNCH_GRID_X 4 -#define VIRGL_LAUNCH_GRID_Y 5 -#define VIRGL_LAUNCH_GRID_Z 6 -#define VIRGL_LAUNCH_INDIRECT_HANDLE 7 -#define VIRGL_LAUNCH_INDIRECT_OFFSET 8 - -/* framebuffer state no attachment */ -#define VIRGL_SET_FRAMEBUFFER_STATE_NO_ATTACH_SIZE 2 -#define VIRGL_SET_FRAMEBUFFER_STATE_NO_ATTACH_WIDTH_HEIGHT 1 -#define VIRGL_SET_FRAMEBUFFER_STATE_NO_ATTACH_WIDTH(x) (x & 0xffff) -#define VIRGL_SET_FRAMEBUFFER_STATE_NO_ATTACH_HEIGHT(x) ((x >> 16) & 0xffff) -#define VIRGL_SET_FRAMEBUFFER_STATE_NO_ATTACH_LAYERS_SAMPLES 2 -#define VIRGL_SET_FRAMEBUFFER_STATE_NO_ATTACH_LAYERS(x) (x & 0xffff) -#define VIRGL_SET_FRAMEBUFFER_STATE_NO_ATTACH_SAMPLES(x) ((x >> 16) & 0xff) - -/* texture barrier */ -#define VIRGL_TEXTURE_BARRIER_SIZE 1 -#define VIRGL_TEXTURE_BARRIER_FLAGS 1 - -/* hw atomics */ -#define VIRGL_SET_ATOMIC_BUFFER_ELEMENT_SIZE 3 -#define VIRGL_SET_ATOMIC_BUFFER_SIZE(x) \ - (VIRGL_SET_ATOMIC_BUFFER_ELEMENT_SIZE * (x)) + 1 -#define VIRGL_SET_ATOMIC_BUFFER_START_SLOT 1 -#define VIRGL_SET_ATOMIC_BUFFER_OFFSET(x) \ - ((x) * VIRGL_SET_ATOMIC_BUFFER_ELEMENT_SIZE + 2) -#define VIRGL_SET_ATOMIC_BUFFER_LENGTH(x) \ - ((x) * VIRGL_SET_ATOMIC_BUFFER_ELEMENT_SIZE + 3) -#define VIRGL_SET_ATOMIC_BUFFER_RES_HANDLE(x) \ - ((x) * VIRGL_SET_ATOMIC_BUFFER_ELEMENT_SIZE + 4) - -/* query buffer object */ -#define VIRGL_QUERY_RESULT_QBO_SIZE 6 -#define VIRGL_QUERY_RESULT_QBO_HANDLE 1 -#define VIRGL_QUERY_RESULT_QBO_QBO_HANDLE 2 -#define VIRGL_QUERY_RESULT_QBO_WAIT 3 -#define VIRGL_QUERY_RESULT_QBO_RESULT_TYPE 4 -#define VIRGL_QUERY_RESULT_QBO_OFFSET 5 -#define VIRGL_QUERY_RESULT_QBO_INDEX 6 - -#define VIRGL_TRANSFER_TO_HOST 1 -#define VIRGL_TRANSFER_FROM_HOST 2 - -/* Transfer */ -#define VIRGL_TRANSFER3D_SIZE 13 -/* The first 11 dwords are the same as VIRGL_RESOURCE_IW_* */ -#define VIRGL_TRANSFER3D_DATA_OFFSET 12 -#define VIRGL_TRANSFER3D_DIRECTION 13 - -/* Copy transfer */ -#define VIRGL_COPY_TRANSFER3D_SIZE 14 -/* The first 11 dwords are the same as VIRGL_RESOURCE_IW_* */ -#define VIRGL_COPY_TRANSFER3D_SRC_RES_HANDLE 12 -#define VIRGL_COPY_TRANSFER3D_SRC_RES_OFFSET 13 -#define VIRGL_COPY_TRANSFER3D_SYNCHRONIZED 14 - -#endif \ No newline at end of file diff --git a/app/src/main/cpp/virglrenderer/src/vrend_blitter.c b/app/src/main/cpp/virglrenderer/src/vrend_blitter.c deleted file mode 100644 index d4b4ea94e..000000000 --- a/app/src/main/cpp/virglrenderer/src/vrend_blitter.c +++ /dev/null @@ -1,846 +0,0 @@ -/************************************************************************** - * - * Copyright (C) 2014 Red Hat Inc. - * - * 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. - * - **************************************************************************/ - -/* gallium blitter implementation in GL */ -/* for when we can't use glBlitFramebuffer */ -#include "pipe/p_shader_tokens.h" -#include - -#include "pipe/p_context.h" -#include "pipe/p_defines.h" -#include "pipe/p_screen.h" -#include "pipe/p_state.h" -#include "util/u_dual_blend.h" -#include "util/u_inlines.h" -#include "util/u_memory.h" - -#include "tgsi/tgsi_parse.h" -#include "util/u_double_list.h" -#include "util/u_format.h" -#include "util/u_texture.h" - -#include "vrend_object.h" -#include "vrend_shader.h" - -#include "vrend_renderer.h" - -#include "vrend_blitter.h" - -#define DEST_SWIZZLE_SNIPPET_SIZE 64 - -struct vrend_blitter_ctx { - virgl_gl_context gl_context; - bool initialised; - - GLuint vaoid; - - GLuint vs; - GLuint fs_texfetch_col[PIPE_MAX_TEXTURE_TYPES]; - GLuint fs_texfetch_depth[PIPE_MAX_TEXTURE_TYPES]; - GLuint fs_texfetch_depth_msaa[PIPE_MAX_TEXTURE_TYPES]; - GLuint fs_texfetch_col_swizzle; - GLuint fb_id; - - unsigned dst_width; - unsigned dst_height; - - GLuint vbo_id; - GLfloat vertices[4][2][4]; /**< {pos, color} or {pos, texcoord} */ -}; - -struct vrend_blitter_point { - int x; - int y; -}; - -struct vrend_blitter_delta { - int dx; - int dy; -}; - -static bool build_and_check(GLuint id, const char *buf) { - GLint param; - glShaderSource(id, 1, (const char **)&buf, NULL); - glCompileShader(id); - - glGetShaderiv(id, GL_COMPILE_STATUS, ¶m); - if (param == GL_FALSE) - return false; - return true; -} - -static bool blit_build_vs_passthrough(struct vrend_blitter_ctx *blit_ctx) { - blit_ctx->vs = glCreateShader(GL_VERTEX_SHADER); - - if (!build_and_check(blit_ctx->vs, VS_PASSTHROUGH_GLES)) { - glDeleteShader(blit_ctx->vs); - blit_ctx->vs = 0; - return false; - } - return true; -} - -static void -create_dest_swizzle_snippet(const uint8_t swizzle[4], - char snippet[DEST_SWIZZLE_SNIPPET_SIZE]) { - static const uint8_t invalid_swizzle = 0xff; - ssize_t si = 0; - uint8_t inverse[4] = {invalid_swizzle, invalid_swizzle, invalid_swizzle, - invalid_swizzle}; - - for (int i = 0; i < 4; ++i) { - if (swizzle[i] > 3) - continue; - if (inverse[swizzle[i]] == invalid_swizzle) - inverse[swizzle[i]] = i; - } - - for (int i = 0; i < 4; ++i) { - int res = -1; - if (inverse[i] > 3) { - /* Use 0.0f for unused color values, 1.0f for an unused alpha value */ - res = snprintf(&snippet[si], DEST_SWIZZLE_SNIPPET_SIZE - si, - i < 3 ? "0.0f, " : "1.0f"); - } else { - res = snprintf(&snippet[si], DEST_SWIZZLE_SNIPPET_SIZE - si, "texel.%c%s", - "rgba"[inverse[i]], i < 3 ? ", " : ""); - } - si += res > 0 ? res : 0; - } -} - -static const char *vec4_type_for_tgsi_ret(enum tgsi_return_type tgsi_ret) { - switch (tgsi_ret) { - case TGSI_RETURN_TYPE_SINT: - return "ivec4"; - case TGSI_RETURN_TYPE_UINT: - return "uvec4"; - default: - return "vec4"; - } -} - -static enum tgsi_return_type tgsi_ret_for_format(enum virgl_formats format) { - if (util_format_is_pure_uint(format)) - return TGSI_RETURN_TYPE_UINT; - else if (util_format_is_pure_sint(format)) - return TGSI_RETURN_TYPE_SINT; - - return TGSI_RETURN_TYPE_UNORM; -} - -static GLuint blit_build_frag_tex_col(struct vrend_blitter_ctx *blit_ctx, - int tgsi_tex_target, - enum tgsi_return_type tgsi_ret, - const uint8_t swizzle[4]) { - GLuint fs_id; - char shader_buf[4096]; - const char *twm; - const char *ext_str = ""; - char dest_swizzle_snippet[DEST_SWIZZLE_SNIPPET_SIZE] = "texel"; - switch (tgsi_tex_target) { - case TGSI_TEXTURE_1D: - case TGSI_TEXTURE_BUFFER: - twm = ".x"; - break; - case TGSI_TEXTURE_1D_ARRAY: - case TGSI_TEXTURE_2D: - case TGSI_TEXTURE_RECT: - case TGSI_TEXTURE_2D_MSAA: - default: - twm = ".xy"; - break; - case TGSI_TEXTURE_SHADOW1D: - case TGSI_TEXTURE_SHADOW2D: - case TGSI_TEXTURE_SHADOW1D_ARRAY: - case TGSI_TEXTURE_SHADOWRECT: - case TGSI_TEXTURE_3D: - case TGSI_TEXTURE_CUBE: - case TGSI_TEXTURE_2D_ARRAY: - case TGSI_TEXTURE_2D_ARRAY_MSAA: - twm = ".xyz"; - break; - case TGSI_TEXTURE_SHADOWCUBE: - case TGSI_TEXTURE_SHADOW2D_ARRAY: - case TGSI_TEXTURE_SHADOWCUBE_ARRAY: - case TGSI_TEXTURE_CUBE_ARRAY: - twm = ""; - break; - } - - if (tgsi_tex_target == TGSI_TEXTURE_CUBE_ARRAY || - tgsi_tex_target == TGSI_TEXTURE_SHADOWCUBE_ARRAY) - ext_str = "#extension GL_ARB_texture_cube_map_array : require\n"; - - if (swizzle) - create_dest_swizzle_snippet(swizzle, dest_swizzle_snippet); - - snprintf(shader_buf, 4096, - tgsi_tex_target == TGSI_TEXTURE_1D ? FS_TEXFETCH_COL_GLES_1D - : FS_TEXFETCH_COL_GLES, - ext_str, vec4_type_for_tgsi_ret(tgsi_ret), - vrend_shader_samplerreturnconv(tgsi_ret), - vrend_shader_samplertypeconv(tgsi_tex_target), twm, - dest_swizzle_snippet); - - fs_id = glCreateShader(GL_FRAGMENT_SHADER); - - if (!build_and_check(fs_id, shader_buf)) { - glDeleteShader(fs_id); - return 0; - } - - return fs_id; -} - -static GLuint blit_build_frag_tex_col_msaa(struct vrend_blitter_ctx *blit_ctx, - int tgsi_tex_target, - enum tgsi_return_type tgsi_ret, - const uint8_t swizzle[4], - int nr_samples) { - GLuint fs_id; - char shader_buf[4096]; - const char *twm; - const char *ivec; - char dest_swizzle_snippet[DEST_SWIZZLE_SNIPPET_SIZE] = "texel"; - const char *ext_str = ""; - - bool is_array = false; - switch (tgsi_tex_target) { - case TGSI_TEXTURE_2D_MSAA: - twm = ".xy"; - ivec = "ivec2"; - break; - case TGSI_TEXTURE_2D_ARRAY_MSAA: - twm = ".xyz"; - ivec = "ivec3"; - is_array = true; - break; - default: - return 0; - } - - if (swizzle) - create_dest_swizzle_snippet(swizzle, dest_swizzle_snippet); - - snprintf(shader_buf, 4096, - is_array ? FS_TEXFETCH_COL_MSAA_ARRAY_GLES - : FS_TEXFETCH_COL_MSAA_GLES, - ext_str, vec4_type_for_tgsi_ret(tgsi_ret), - vrend_shader_samplerreturnconv(tgsi_ret), - vrend_shader_samplertypeconv(tgsi_tex_target), nr_samples, ivec, twm, - dest_swizzle_snippet); - - fs_id = glCreateShader(GL_FRAGMENT_SHADER); - - if (!build_and_check(fs_id, shader_buf)) { - glDeleteShader(fs_id); - return 0; - } - - return fs_id; -} - -static GLuint blit_build_frag_tex_writedepth(struct vrend_blitter_ctx *blit_ctx, - int tgsi_tex_target) { - GLuint fs_id; - char shader_buf[4096]; - const char *twm; - - switch (tgsi_tex_target) { - case TGSI_TEXTURE_1D: - twm = ".xy"; - break; - /* fallthrough */ - case TGSI_TEXTURE_BUFFER: - twm = ".x"; - break; - case TGSI_TEXTURE_1D_ARRAY: - case TGSI_TEXTURE_2D: - case TGSI_TEXTURE_RECT: - case TGSI_TEXTURE_2D_MSAA: - default: - twm = ".xy"; - break; - case TGSI_TEXTURE_SHADOW1D: - case TGSI_TEXTURE_SHADOW2D: - case TGSI_TEXTURE_SHADOW1D_ARRAY: - case TGSI_TEXTURE_SHADOWRECT: - case TGSI_TEXTURE_3D: - case TGSI_TEXTURE_CUBE: - case TGSI_TEXTURE_2D_ARRAY: - case TGSI_TEXTURE_2D_ARRAY_MSAA: - twm = ".xyz"; - break; - case TGSI_TEXTURE_SHADOWCUBE: - case TGSI_TEXTURE_SHADOW2D_ARRAY: - case TGSI_TEXTURE_SHADOWCUBE_ARRAY: - case TGSI_TEXTURE_CUBE_ARRAY: - twm = ""; - break; - } - - snprintf(shader_buf, 4096, FS_TEXFETCH_DS_GLES, - vrend_shader_samplertypeconv(tgsi_tex_target), twm); - - fs_id = glCreateShader(GL_FRAGMENT_SHADER); - - if (!build_and_check(fs_id, shader_buf)) { - glDeleteShader(fs_id); - return 0; - } - - return fs_id; -} - -static GLuint -blit_build_frag_blit_msaa_depth(struct vrend_blitter_ctx *blit_ctx, - int tgsi_tex_target) { - GLuint fs_id; - char shader_buf[4096]; - const char *twm; - const char *ivec; - - bool is_array = false; - switch (tgsi_tex_target) { - case TGSI_TEXTURE_2D_MSAA: - twm = ".xy"; - ivec = "ivec2"; - break; - case TGSI_TEXTURE_2D_ARRAY_MSAA: - twm = ".xyz"; - ivec = "ivec3"; - is_array = true; - break; - default: - return 0; - } - - snprintf(shader_buf, 4096, - is_array ? FS_TEXFETCH_DS_MSAA_ARRAY_GLES : FS_TEXFETCH_DS_MSAA_GLES, - vrend_shader_samplertypeconv(tgsi_tex_target), ivec, twm); - - fs_id = glCreateShader(GL_FRAGMENT_SHADER); - - if (!build_and_check(fs_id, shader_buf)) { - glDeleteShader(fs_id); - return 0; - } - - return fs_id; -} - -static GLuint blit_get_frag_tex_writedepth(struct vrend_blitter_ctx *blit_ctx, - int pipe_tex_target, - unsigned nr_samples) { - assert(pipe_tex_target < PIPE_MAX_TEXTURE_TYPES); - - if (nr_samples > 0) { - GLuint *shader = &blit_ctx->fs_texfetch_depth_msaa[pipe_tex_target]; - - if (!*shader) { - unsigned tgsi_tex = - util_pipe_tex_to_tgsi_tex(pipe_tex_target, nr_samples); - - *shader = blit_build_frag_blit_msaa_depth(blit_ctx, tgsi_tex); - } - return *shader; - - } else { - GLuint *shader = &blit_ctx->fs_texfetch_depth[pipe_tex_target]; - - if (!*shader) { - unsigned tgsi_tex = util_pipe_tex_to_tgsi_tex(pipe_tex_target, 0); - - *shader = blit_build_frag_tex_writedepth(blit_ctx, tgsi_tex); - } - return *shader; - } -} - -static GLuint blit_get_frag_tex_col(struct vrend_blitter_ctx *blit_ctx, - int pipe_tex_target, unsigned nr_samples, - const struct vrend_format_table *src_entry, - const struct vrend_format_table *dst_entry, - bool skip_dest_swizzle) { - assert(pipe_tex_target < PIPE_MAX_TEXTURE_TYPES); - - bool needs_swizzle = - !skip_dest_swizzle && (dst_entry->flags & VIRGL_TEXTURE_NEED_SWIZZLE); - - if (needs_swizzle || nr_samples > 1) { - const uint8_t *swizzle = needs_swizzle ? dst_entry->swizzle : NULL; - GLuint *shader = &blit_ctx->fs_texfetch_col_swizzle; - if (shader) { - glDeleteShader(*shader); - } - - unsigned tgsi_tex = util_pipe_tex_to_tgsi_tex(pipe_tex_target, nr_samples); - enum tgsi_return_type tgsi_ret = tgsi_ret_for_format(src_entry->format); - - if (nr_samples > 0) { - // Integer textures are resolved using just one sample - int msaa_samples = tgsi_ret == TGSI_RETURN_TYPE_UNORM ? nr_samples : 1; - *shader = blit_build_frag_tex_col_msaa(blit_ctx, tgsi_tex, tgsi_ret, - swizzle, msaa_samples); - } else { - *shader = blit_build_frag_tex_col(blit_ctx, tgsi_tex, tgsi_ret, swizzle); - } - - return *shader; - } else { - GLuint *shader = &blit_ctx->fs_texfetch_col[pipe_tex_target]; - - if (!*shader) { - unsigned tgsi_tex = util_pipe_tex_to_tgsi_tex(pipe_tex_target, 0); - enum tgsi_return_type tgsi_ret = tgsi_ret_for_format(src_entry->format); - - *shader = blit_build_frag_tex_col(blit_ctx, tgsi_tex, tgsi_ret, NULL); - } - return *shader; - } -} - -static void vrend_renderer_init_blit_ctx(struct virgl_client *client, - struct vrend_blitter_ctx *blit_ctx) { - int i; - if (blit_ctx->initialised) { - vrend_clicbs->make_current(client, blit_ctx->gl_context); - return; - } - - blit_ctx->initialised = true; - - blit_ctx->gl_context = vrend_clicbs->create_gl_context(client); - - vrend_clicbs->make_current(client, blit_ctx->gl_context); - glGenVertexArrays(1, &blit_ctx->vaoid); - glGenFramebuffers(1, &blit_ctx->fb_id); - - glGenBuffers(1, &blit_ctx->vbo_id); - blit_build_vs_passthrough(blit_ctx); - - for (i = 0; i < 4; i++) - blit_ctx->vertices[i][0][3] = 1; /*v.w*/ - glBindVertexArray(blit_ctx->vaoid); - glBindBuffer(GL_ARRAY_BUFFER, blit_ctx->vbo_id); -} - -static inline GLenum convert_mag_filter(unsigned int filter) { - if (filter == PIPE_TEX_FILTER_NEAREST) - return GL_NEAREST; - return GL_LINEAR; -} - -static void blitter_set_dst_dim(struct vrend_blitter_ctx *blit_ctx, - unsigned width, unsigned height) { - blit_ctx->dst_width = width; - blit_ctx->dst_height = height; -} - -static void blitter_set_rectangle(struct vrend_blitter_ctx *blit_ctx, int x1, - int y1, int x2, int y2, float depth) { - int i; - - /* set vertex positions */ - blit_ctx->vertices[0][0][0] = - (float)x1 / blit_ctx->dst_width * 2.0f - 1.0f; /*v0.x*/ - blit_ctx->vertices[0][0][1] = - (float)y1 / blit_ctx->dst_height * 2.0f - 1.0f; /*v0.y*/ - - blit_ctx->vertices[1][0][0] = - (float)x2 / blit_ctx->dst_width * 2.0f - 1.0f; /*v1.x*/ - blit_ctx->vertices[1][0][1] = - (float)y1 / blit_ctx->dst_height * 2.0f - 1.0f; /*v1.y*/ - - blit_ctx->vertices[2][0][0] = - (float)x2 / blit_ctx->dst_width * 2.0f - 1.0f; /*v2.x*/ - blit_ctx->vertices[2][0][1] = - (float)y2 / blit_ctx->dst_height * 2.0f - 1.0f; /*v2.y*/ - - blit_ctx->vertices[3][0][0] = - (float)x1 / blit_ctx->dst_width * 2.0f - 1.0f; /*v3.x*/ - blit_ctx->vertices[3][0][1] = - (float)y2 / blit_ctx->dst_height * 2.0f - 1.0f; /*v3.y*/ - - for (i = 0; i < 4; i++) - blit_ctx->vertices[i][0][2] = depth; /*z*/ - - glViewport(0, 0, blit_ctx->dst_width, blit_ctx->dst_height); -} - -static void get_texcoords(struct vrend_blitter_ctx *blit_ctx, - struct vrend_resource *src_res, int src_level, int x1, - int y1, int x2, int y2, float out[4]) { - bool normalized = src_res->base.nr_samples < 1; - - if (normalized) { - out[0] = x1 / (float)u_minify(src_res->base.width0, src_level); - out[1] = y1 / (float)u_minify(src_res->base.height0, src_level); - out[2] = x2 / (float)u_minify(src_res->base.width0, src_level); - out[3] = y2 / (float)u_minify(src_res->base.height0, src_level); - } else { - out[0] = (float)x1; - out[1] = (float)y1; - out[2] = (float)x2; - out[3] = (float)y2; - } -} -static void set_texcoords_in_vertices(const float coord[4], float *out, - unsigned stride) { - out[0] = coord[0]; /*t0.s*/ - out[1] = coord[1]; /*t0.t*/ - out += stride; - out[0] = coord[2]; /*t1.s*/ - out[1] = coord[1]; /*t1.t*/ - out += stride; - out[0] = coord[2]; /*t2.s*/ - out[1] = coord[3]; /*t2.t*/ - out += stride; - out[0] = coord[0]; /*t3.s*/ - out[1] = coord[3]; /*t3.t*/ -} - -static void blitter_set_texcoords(struct vrend_blitter_ctx *blit_ctx, - struct vrend_resource *src_res, int level, - float layer, unsigned sample, int x1, int y1, - int x2, int y2) { - float coord[4]; - float face_coord[4][2]; - int i; - get_texcoords(blit_ctx, src_res, level, x1, y1, x2, y2, coord); - - if (src_res->base.target == PIPE_TEXTURE_CUBE || - src_res->base.target == PIPE_TEXTURE_CUBE_ARRAY) { - set_texcoords_in_vertices(coord, &face_coord[0][0], 2); - util_map_texcoords2d_onto_cubemap((unsigned)layer % 6, - /* pointer, stride in floats */ - &face_coord[0][0], 2, - &blit_ctx->vertices[0][1][0], 8, FALSE); - } else { - set_texcoords_in_vertices(coord, &blit_ctx->vertices[0][1][0], 8); - } - - switch (src_res->base.target) { - case PIPE_TEXTURE_3D: { - float r = layer / (float)u_minify(src_res->base.depth0, level); - for (i = 0; i < 4; i++) - blit_ctx->vertices[i][1][2] = r; /*r*/ - } break; - - case PIPE_TEXTURE_1D_ARRAY: - for (i = 0; i < 4; i++) - blit_ctx->vertices[i][1][1] = (float)layer; /*t*/ - break; - - case PIPE_TEXTURE_2D_ARRAY: - for (i = 0; i < 4; i++) { - blit_ctx->vertices[i][1][2] = (float)layer; /*r*/ - blit_ctx->vertices[i][1][3] = (float)sample; /*q*/ - } - break; - case PIPE_TEXTURE_CUBE_ARRAY: - for (i = 0; i < 4; i++) - blit_ctx->vertices[i][1][3] = (float)((unsigned)layer / 6); /*w*/ - break; - case PIPE_TEXTURE_2D: - for (i = 0; i < 4; i++) { - blit_ctx->vertices[i][1][3] = (float)sample; /*r*/ - } - break; - default:; - } -} - -static void set_dsa_write_depth_keep_stencil(void) { - glDisable(GL_STENCIL_TEST); - glEnable(GL_DEPTH_TEST); - glDepthFunc(GL_ALWAYS); - glDepthMask(GL_TRUE); -} - -static inline GLenum to_gl_swizzle(int swizzle) { - switch (swizzle) { - case PIPE_SWIZZLE_RED: - return GL_RED; - case PIPE_SWIZZLE_GREEN: - return GL_GREEN; - case PIPE_SWIZZLE_BLUE: - return GL_BLUE; - case PIPE_SWIZZLE_ALPHA: - return GL_ALPHA; - case PIPE_SWIZZLE_ZERO: - return GL_ZERO; - case PIPE_SWIZZLE_ONE: - return GL_ONE; - default: - return 0; - } -} - -/* Calculate the delta required to keep 'v' within [0, max] */ -static int calc_delta_for_bound(int v, int max) { - int delta = 0; - - if (v < 0) - delta = -v; - else if (v > max) - delta = -(v - max); - - return delta; -} - -/* Calculate the deltas for the source blit region points in order to bound - * them within the source resource extents */ -static void calc_src_deltas_for_bounds(struct vrend_resource *src_res, - const struct pipe_blit_info *info, - struct vrend_blitter_delta *src0_delta, - struct vrend_blitter_delta *src1_delta) { - int max_x = u_minify(src_res->base.width0, info->src.level) - 1; - int max_y = u_minify(src_res->base.height0, info->src.level) - 1; - - /* Whether the bounds for the coordinates of a point are inclusive or - * exclusive depends on the direction of the blit read. Adjust the max - * bounds accordingly, with an adjustment of 0 for inclusive, and 1 for - * exclusive. */ - int src0_x_excl = info->src.box.width < 0; - int src0_y_excl = info->src.box.height < 0; - - src0_delta->dx = calc_delta_for_bound(info->src.box.x, max_x + src0_x_excl); - src0_delta->dy = calc_delta_for_bound(info->src.box.y, max_y + src0_y_excl); - - src1_delta->dx = calc_delta_for_bound(info->src.box.x + info->src.box.width, - max_x + !src0_x_excl); - src1_delta->dy = calc_delta_for_bound(info->src.box.y + info->src.box.height, - max_y + !src0_y_excl); -} - -/* Calculate dst delta values to adjust the dst points for any changes in the - * src points */ -static void -calc_dst_deltas_from_src(const struct pipe_blit_info *info, - const struct vrend_blitter_delta *src0_delta, - const struct vrend_blitter_delta *src1_delta, - struct vrend_blitter_delta *dst0_delta, - struct vrend_blitter_delta *dst1_delta) { - float scale_x = (float)info->dst.box.width / (float)info->src.box.width; - float scale_y = (float)info->dst.box.height / (float)info->src.box.height; - - dst0_delta->dx = src0_delta->dx * scale_x; - dst0_delta->dy = src0_delta->dy * scale_y; - - dst1_delta->dx = src1_delta->dx * scale_x; - dst1_delta->dy = src1_delta->dy * scale_y; -} - -/* implement blitting using OpenGL. */ -void vrend_renderer_blit_gl( - struct virgl_client *client, struct vrend_resource *src_res, - struct vrend_resource *dst_res, GLenum blit_views[2], - const struct pipe_blit_info *info, bool has_texture_srgb_decode, - bool has_srgb_write_control, bool skip_dest_swizzle) { - struct vrend_blitter_ctx *blit_ctx; - - if (!client->vrend_blit_ctx) - client->vrend_blit_ctx = CALLOC_STRUCT(vrend_blitter_ctx); - - blit_ctx = client->vrend_blit_ctx; - - GLuint buffers; - GLuint prog_id; - GLuint fs_id; - GLint lret; - GLenum filter; - GLuint pos_loc, tc_loc; - GLuint samp_loc; - bool has_depth, has_stencil; - bool blit_stencil, blit_depth; - int dst_z; - struct vrend_blitter_delta src0_delta, src1_delta, dst0_delta, dst1_delta; - struct vrend_blitter_point src0, src1, dst0, dst1; - const struct util_format_description *src_desc = - util_format_description(src_res->base.format); - const struct util_format_description *dst_desc = - util_format_description(dst_res->base.format); - const struct vrend_format_table *src_entry = - vrend_get_format_table_entry(info->src.format); - const struct vrend_format_table *dst_entry = - vrend_get_format_table_entry(info->dst.format); - - has_depth = - util_format_has_depth(src_desc) && util_format_has_depth(dst_desc); - has_stencil = - util_format_has_stencil(src_desc) && util_format_has_stencil(dst_desc); - - blit_depth = has_depth && (info->mask & PIPE_MASK_Z); - blit_stencil = has_stencil && (info->mask & PIPE_MASK_S) & 0; - - filter = convert_mag_filter(info->filter); - vrend_renderer_init_blit_ctx(client, blit_ctx); - - blitter_set_dst_dim(blit_ctx, u_minify(dst_res->base.width0, info->dst.level), - u_minify(dst_res->base.height0, info->dst.level)); - - /* Calculate src and dst points taking deltas into account */ - calc_src_deltas_for_bounds(src_res, info, &src0_delta, &src1_delta); - calc_dst_deltas_from_src(info, &src0_delta, &src1_delta, &dst0_delta, - &dst1_delta); - - src0.x = info->src.box.x + src0_delta.dx; - src0.y = info->src.box.y + src0_delta.dy; - src1.x = info->src.box.x + info->src.box.width + src1_delta.dx; - src1.y = info->src.box.y + info->src.box.height + src1_delta.dy; - - dst0.x = info->dst.box.x + dst0_delta.dx; - dst0.y = info->dst.box.y + dst0_delta.dy; - dst1.x = info->dst.box.x + info->dst.box.width + dst1_delta.dx; - dst1.y = info->dst.box.y + info->dst.box.height + dst1_delta.dy; - - blitter_set_rectangle(blit_ctx, dst0.x, dst0.y, dst1.x, dst1.y, 0); - - prog_id = glCreateProgram(); - glAttachShader(prog_id, blit_ctx->vs); - - if (blit_depth || blit_stencil) { - fs_id = blit_get_frag_tex_writedepth(blit_ctx, src_res->base.target, - src_res->base.nr_samples); - } else { - fs_id = blit_get_frag_tex_col(blit_ctx, src_res->base.target, - src_res->base.nr_samples, src_entry, - dst_entry, skip_dest_swizzle); - } - glAttachShader(prog_id, fs_id); - - glLinkProgram(prog_id); - glGetProgramiv(prog_id, GL_LINK_STATUS, &lret); - if (lret == GL_FALSE) { - glDeleteProgram(prog_id); - return; - } - - glUseProgram(prog_id); - - glBindFramebuffer(GL_FRAMEBUFFER, blit_ctx->fb_id); - vrend_fb_bind_texture_id(dst_res, blit_views[1], 0, info->dst.level, - info->dst.box.z); - - buffers = GL_COLOR_ATTACHMENT0; - glDrawBuffers(1, &buffers); - - glBindTexture(src_res->target, blit_views[0]); - - if (src_entry->flags & VIRGL_TEXTURE_NEED_SWIZZLE) { - glTexParameteri(src_res->target, GL_TEXTURE_SWIZZLE_R, - to_gl_swizzle(src_entry->swizzle[0])); - glTexParameteri(src_res->target, GL_TEXTURE_SWIZZLE_G, - to_gl_swizzle(src_entry->swizzle[1])); - glTexParameteri(src_res->target, GL_TEXTURE_SWIZZLE_B, - to_gl_swizzle(src_entry->swizzle[2])); - glTexParameteri(src_res->target, GL_TEXTURE_SWIZZLE_A, - to_gl_swizzle(src_entry->swizzle[3])); - } - - /* Just make sure that no stale state disabled decoding */ - if (has_texture_srgb_decode && util_format_is_srgb(info->src.format) && - src_res->base.nr_samples < 1) - glTexParameteri(src_res->target, GL_TEXTURE_SRGB_DECODE_EXT, GL_DECODE_EXT); - - if (src_res->base.nr_samples < 1) { - glTexParameteri(src_res->target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(src_res->target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(src_res->target, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); - } - - glTexParameteri(src_res->target, GL_TEXTURE_BASE_LEVEL, info->src.level); - glTexParameteri(src_res->target, GL_TEXTURE_MAX_LEVEL, info->src.level); - - if (src_res->base.nr_samples < 1) { - glTexParameterf(src_res->target, GL_TEXTURE_MAG_FILTER, filter); - glTexParameterf(src_res->target, GL_TEXTURE_MIN_FILTER, filter); - } - pos_loc = glGetAttribLocation(prog_id, "arg0"); - tc_loc = glGetAttribLocation(prog_id, "arg1"); - samp_loc = glGetUniformLocation(prog_id, "samp"); - - glUniform1i(samp_loc, 0); - - glVertexAttribPointer(pos_loc, 4, GL_FLOAT, GL_FALSE, 8 * sizeof(float), - (void *)0); - glVertexAttribPointer(tc_loc, 4, GL_FLOAT, GL_FALSE, 8 * sizeof(float), - (void *)(4 * sizeof(float))); - - glEnableVertexAttribArray(pos_loc); - glEnableVertexAttribArray(tc_loc); - - set_dsa_write_depth_keep_stencil(); - - if (info->scissor_enable) { - glScissor(info->scissor.minx, info->scissor.miny, - info->scissor.maxx - info->scissor.minx, - info->scissor.maxy - info->scissor.miny); - glEnable(GL_SCISSOR_TEST); - } else - glDisable(GL_SCISSOR_TEST); - - for (dst_z = 0; dst_z < info->dst.box.depth; dst_z++) { - float dst2src_scale = info->src.box.depth / (float)info->dst.box.depth; - float dst_offset = ((info->src.box.depth - 1) - - (info->dst.box.depth - 1) * dst2src_scale) * - 0.5; - float src_z = (dst_z + dst_offset) * dst2src_scale; - uint32_t layer = - (dst_res->target == GL_TEXTURE_CUBE_MAP) ? info->dst.box.z : dst_z; - - glBindFramebuffer(GL_FRAMEBUFFER, blit_ctx->fb_id); - vrend_fb_bind_texture_id(dst_res, blit_views[1], 0, info->dst.level, layer); - - buffers = GL_COLOR_ATTACHMENT0; - glDrawBuffers(1, &buffers); - blitter_set_texcoords(blit_ctx, src_res, info->src.level, - info->src.box.z + src_z, 0, src0.x, src0.y, src1.x, - src1.y); - - glBufferData(GL_ARRAY_BUFFER, sizeof(blit_ctx->vertices), - blit_ctx->vertices, GL_STATIC_DRAW); - glDrawArrays(GL_TRIANGLE_FAN, 0, 4); - } - - glUseProgram(0); - glDeleteProgram(prog_id); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, - GL_TEXTURE_2D, 0, 0); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, - 0); - glBindTexture(src_res->target, 0); -} - -void vrend_blitter_fini(struct virgl_client *client) { - if (!client->vrend_blit_ctx) - return; - - client->vrend_blit_ctx->initialised = false; - vrend_clicbs->destroy_gl_context(client, client->vrend_blit_ctx->gl_context); - memset(client->vrend_blit_ctx, 0, sizeof(client->vrend_blit_ctx)); - client->vrend_blit_ctx = NULL; -} diff --git a/app/src/main/cpp/virglrenderer/src/vrend_blitter.h b/app/src/main/cpp/virglrenderer/src/vrend_blitter.h deleted file mode 100644 index bac296096..000000000 --- a/app/src/main/cpp/virglrenderer/src/vrend_blitter.h +++ /dev/null @@ -1,115 +0,0 @@ -/************************************************************************** - * - * Copyright (C) 2014 Red Hat Inc. - * - * 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. - * - **************************************************************************/ -#ifndef VREND_BLITTER_H -#define VREND_BLITTER_H - -/* shaders for blitting */ - -#define HEADER_GLES \ - "// Blitter\n" \ - "#version 310 es\n" \ - "precision mediump float;\n" - -#define HEADER_GLES_MS_ARRAY \ - "// Blitter\n" \ - "#version 310 es\n" \ - "#extension GL_OES_texture_storage_multisample_2d_array: require\n" \ - "precision mediump float;\n" - -#define VS_PASSTHROUGH_BODY \ - "in vec4 arg0;\n" \ - "in vec4 arg1;\n" \ - "out vec4 tc;\n" \ - "void main() {\n" \ - " gl_Position = arg0;\n" \ - " tc = arg1;\n" \ - "}\n" - -#define VS_PASSTHROUGH_GLES HEADER_GLES VS_PASSTHROUGH_BODY - -#define FS_TEXFETCH_COL_BODY \ - "%s" \ - "#define cvec4 %s\n" \ - "uniform mediump %csampler%s samp;\n" \ - "in vec4 tc;\n" \ - "out cvec4 FragColor;\n" \ - "void main() {\n" \ - " cvec4 texel = texture(samp, tc%s);\n" \ - " FragColor = cvec4(%s);\n" \ - "}\n" - -#define FS_TEXFETCH_COL_GLES_1D_BODY \ - "%s" \ - "#define cvec4 %s\n" \ - "uniform mediump %csampler%s samp;\n" \ - "in vec4 tc;\n" \ - "out cvec4 FragColor;\n" \ - "void main() {\n" \ - " cvec4 texel = texture(samp, vec2(tc%s, 0.5));\n" \ - " FragColor = cvec4(%s);\n" \ - "}\n" - -#define FS_TEXFETCH_COL_GLES HEADER_GLES FS_TEXFETCH_COL_BODY -#define FS_TEXFETCH_COL_GLES_1D HEADER_GLES FS_TEXFETCH_COL_GLES_1D_BODY - -#define FS_TEXFETCH_COL_MSAA_BODY \ - "%s" \ - "#define cvec4 %s\n" \ - "uniform mediump %csampler%s samp;\n" \ - "in vec4 tc;\n" \ - "out cvec4 FragColor;\n" \ - "void main() {\n" \ - " const int num_samples = %d;\n" \ - " cvec4 texel = cvec4(0);\n" \ - " for (int i = 0; i < num_samples; ++i) \n" \ - " texel += texelFetch(samp, %s(tc%s), i);\n" \ - " texel = texel / cvec4(num_samples);\n" \ - " FragColor = cvec4(%s);\n" \ - "}\n" - -#define FS_TEXFETCH_COL_MSAA_GLES HEADER_GLES FS_TEXFETCH_COL_MSAA_BODY -#define FS_TEXFETCH_COL_MSAA_ARRAY_GLES \ - HEADER_GLES_MS_ARRAY FS_TEXFETCH_COL_MSAA_BODY - -#define FS_TEXFETCH_DS_BODY \ - "uniform mediump sampler%s samp;\n" \ - "in vec4 tc;\n" \ - "void main() {\n" \ - " gl_FragDepth = float(texture(samp, tc%s).x);\n" \ - "}\n" - -#define FS_TEXFETCH_DS_GLES HEADER_GLES FS_TEXFETCH_DS_BODY - -#define FS_TEXFETCH_DS_MSAA_BODY_GLES \ - "uniform mediump sampler%s samp;\n" \ - "in vec4 tc;\n" \ - "void main() {\n" \ - " gl_FragDepth = float(texelFetch(samp, %s(tc%s), int(tc.z)).x);\n" \ - "}\n" - -#define FS_TEXFETCH_DS_MSAA_GLES HEADER_GLES FS_TEXFETCH_DS_MSAA_BODY_GLES -#define FS_TEXFETCH_DS_MSAA_ARRAY_GLES \ - HEADER_GLES_MS_ARRAY FS_TEXFETCH_DS_MSAA_BODY_GLES - -#endif diff --git a/app/src/main/cpp/virglrenderer/src/vrend_decode.c b/app/src/main/cpp/virglrenderer/src/vrend_decode.c deleted file mode 100644 index e9ca86235..000000000 --- a/app/src/main/cpp/virglrenderer/src/vrend_decode.c +++ /dev/null @@ -1,1580 +0,0 @@ -/************************************************************************** - * - * Copyright (C) 2014 Red Hat Inc. - * - * 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. - * - **************************************************************************/ -#include -#include -#include -#include - -#include "pipe/p_defines.h" -#include "pipe/p_shader_tokens.h" -#include "pipe/p_state.h" -#include "tgsi/tgsi_text.h" -#include "util/u_memory.h" -#include "vrend_object.h" -#include "vrend_renderer.h" - -struct vrend_decoder_state { - uint32_t *buf; - uint32_t buf_total; - uint32_t buf_offset; -}; - -struct vrend_decode_ctx { - struct vrend_decoder_state ids, *ds; - struct vrend_context *grctx; -}; - -static inline uint32_t get_buf_entry(struct vrend_decode_ctx *ctx, - uint32_t offset) { - return ctx->ds->buf[ctx->ds->buf_offset + offset]; -} - -static inline void *get_buf_ptr(struct vrend_decode_ctx *ctx, uint32_t offset) { - return &ctx->ds->buf[ctx->ds->buf_offset + offset]; -} - -static int vrend_decode_create_shader(struct vrend_decode_ctx *ctx, - uint32_t handle, uint16_t length) { - struct pipe_stream_output_info so_info; - uint i; - int ret; - uint32_t shader_offset, req_local_mem = 0; - unsigned num_tokens, num_so_outputs, offlen; - uint8_t *shd_text; - uint32_t type; - - if (length < VIRGL_OBJ_SHADER_HDR_SIZE(0)) - return EINVAL; - - type = get_buf_entry(ctx, VIRGL_OBJ_SHADER_TYPE); - num_tokens = get_buf_entry(ctx, VIRGL_OBJ_SHADER_NUM_TOKENS); - offlen = get_buf_entry(ctx, VIRGL_OBJ_SHADER_OFFSET); - - if (type == PIPE_SHADER_COMPUTE) { - req_local_mem = get_buf_entry(ctx, VIRGL_OBJ_SHADER_SO_NUM_OUTPUTS); - num_so_outputs = 0; - } else { - num_so_outputs = get_buf_entry(ctx, VIRGL_OBJ_SHADER_SO_NUM_OUTPUTS); - if (length < VIRGL_OBJ_SHADER_HDR_SIZE(num_so_outputs)) - return EINVAL; - - if (num_so_outputs > PIPE_MAX_SO_OUTPUTS) - return EINVAL; - } - - shader_offset = 6; - if (num_so_outputs) { - so_info.num_outputs = num_so_outputs; - if (so_info.num_outputs) { - for (i = 0; i < 4; i++) - so_info.stride[i] = get_buf_entry(ctx, VIRGL_OBJ_SHADER_SO_STRIDE(i)); - for (i = 0; i < so_info.num_outputs; i++) { - uint32_t tmp = get_buf_entry(ctx, VIRGL_OBJ_SHADER_SO_OUTPUT0(i)); - - so_info.output[i].register_index = tmp & 0xff; - so_info.output[i].start_component = (tmp >> 8) & 0x3; - so_info.output[i].num_components = (tmp >> 10) & 0x7; - so_info.output[i].output_buffer = (tmp >> 13) & 0x7; - so_info.output[i].dst_offset = (tmp >> 16) & 0xffff; - tmp = get_buf_entry(ctx, VIRGL_OBJ_SHADER_SO_OUTPUT0_SO(i)); - so_info.output[i].stream = (tmp & 0x3); - so_info.output[i].need_temp = so_info.output[i].num_components < 4; - } - - for (i = 0; i < so_info.num_outputs - 1; i++) { - for (unsigned j = i + 1; j < so_info.num_outputs; j++) { - so_info.output[j].need_temp |= (so_info.output[i].register_index == - so_info.output[j].register_index); - } - } - } - shader_offset += 4 + (2 * num_so_outputs); - } else - memset(&so_info, 0, sizeof(so_info)); - - shd_text = get_buf_ptr(ctx, shader_offset); - ret = vrend_create_shader(ctx->grctx, handle, &so_info, req_local_mem, - (const char *)shd_text, offlen, num_tokens, type, - length - shader_offset + 1); - - return ret; -} - -static int -vrend_decode_create_stream_output_target(struct vrend_decode_ctx *ctx, - uint32_t handle, uint16_t length) { - uint32_t res_handle, buffer_size, buffer_offset; - - if (length != VIRGL_OBJ_STREAMOUT_SIZE) - return EINVAL; - - res_handle = get_buf_entry(ctx, VIRGL_OBJ_STREAMOUT_RES_HANDLE); - buffer_offset = get_buf_entry(ctx, VIRGL_OBJ_STREAMOUT_BUFFER_OFFSET); - buffer_size = get_buf_entry(ctx, VIRGL_OBJ_STREAMOUT_BUFFER_SIZE); - - return vrend_create_so_target(ctx->grctx, handle, res_handle, buffer_offset, - buffer_size); -} - -static int vrend_decode_set_framebuffer_state(struct vrend_decode_ctx *ctx, - int length) { - if (length < 2) - return EINVAL; - - int32_t nr_cbufs = get_buf_entry(ctx, VIRGL_SET_FRAMEBUFFER_STATE_NR_CBUFS); - uint32_t zsurf_handle = - get_buf_entry(ctx, VIRGL_SET_FRAMEBUFFER_STATE_NR_ZSURF_HANDLE); - uint32_t surf_handle[8]; - int i; - - if (length != (2 + nr_cbufs)) - return EINVAL; - - if (nr_cbufs > 8) - return EINVAL; - - for (i = 0; i < nr_cbufs; i++) - surf_handle[i] = - get_buf_entry(ctx, VIRGL_SET_FRAMEBUFFER_STATE_CBUF_HANDLE(i)); - vrend_set_framebuffer_state(ctx->grctx, nr_cbufs, surf_handle, zsurf_handle); - return 0; -} - -static int -vrend_decode_set_framebuffer_state_no_attach(struct vrend_decode_ctx *ctx, - int length) { - uint32_t width, height; - uint32_t layers, samples; - uint32_t tmp; - - if (length != VIRGL_SET_FRAMEBUFFER_STATE_NO_ATTACH_SIZE) - return EINVAL; - - tmp = get_buf_entry(ctx, VIRGL_SET_FRAMEBUFFER_STATE_NO_ATTACH_WIDTH_HEIGHT); - width = VIRGL_SET_FRAMEBUFFER_STATE_NO_ATTACH_WIDTH(tmp); - height = VIRGL_SET_FRAMEBUFFER_STATE_NO_ATTACH_HEIGHT(tmp); - - tmp = - get_buf_entry(ctx, VIRGL_SET_FRAMEBUFFER_STATE_NO_ATTACH_LAYERS_SAMPLES); - layers = VIRGL_SET_FRAMEBUFFER_STATE_NO_ATTACH_LAYERS(tmp); - samples = VIRGL_SET_FRAMEBUFFER_STATE_NO_ATTACH_SAMPLES(tmp); - - vrend_set_framebuffer_state_no_attach(ctx->grctx, width, height, layers, - samples); - return 0; -} - -static int vrend_decode_clear(struct vrend_decode_ctx *ctx, int length) { - union pipe_color_union color; - double depth; - unsigned stencil, buffers; - int i; - - if (length != VIRGL_OBJ_CLEAR_SIZE) - return EINVAL; - buffers = get_buf_entry(ctx, VIRGL_OBJ_CLEAR_BUFFERS); - for (i = 0; i < 4; i++) - color.ui[i] = get_buf_entry(ctx, VIRGL_OBJ_CLEAR_COLOR_0 + i); - double *depth_ptr = - (double *)(uint64_t *)get_buf_ptr(ctx, VIRGL_OBJ_CLEAR_DEPTH_0); - memcpy(&depth, depth_ptr, sizeof(double)); - stencil = get_buf_entry(ctx, VIRGL_OBJ_CLEAR_STENCIL); - - vrend_clear(ctx->grctx, buffers, &color, depth, stencil); - return 0; -} - -static float uif(unsigned int ui) { - union { - float f; - unsigned int ui; - } myuif; - myuif.ui = ui; - return myuif.f; -} - -static int vrend_decode_set_viewport_state(struct vrend_decode_ctx *ctx, - int length) { - struct pipe_viewport_state vps[PIPE_MAX_VIEWPORTS]; - uint i, v; - uint32_t num_viewports, start_slot; - if (length < 1) - return EINVAL; - - if ((length - 1) % 6) - return EINVAL; - - num_viewports = (length - 1) / 6; - start_slot = get_buf_entry(ctx, VIRGL_SET_VIEWPORT_START_SLOT); - - if (num_viewports > PIPE_MAX_VIEWPORTS || - start_slot > (PIPE_MAX_VIEWPORTS - num_viewports)) - return EINVAL; - - for (v = 0; v < num_viewports; v++) { - for (i = 0; i < 3; i++) - vps[v].scale[i] = - uif(get_buf_entry(ctx, VIRGL_SET_VIEWPORT_STATE_SCALE_0(v) + i)); - for (i = 0; i < 3; i++) - vps[v].translate[i] = - uif(get_buf_entry(ctx, VIRGL_SET_VIEWPORT_STATE_TRANSLATE_0(v) + i)); - } - - vrend_set_viewport_states(ctx->grctx, start_slot, num_viewports, vps); - return 0; -} - -static int vrend_decode_set_index_buffer(struct vrend_decode_ctx *ctx, - int length) { - if (length != 1 && length != 3) - return EINVAL; - vrend_set_index_buffer( - ctx->grctx, get_buf_entry(ctx, VIRGL_SET_INDEX_BUFFER_HANDLE), - (length == 3) ? get_buf_entry(ctx, VIRGL_SET_INDEX_BUFFER_INDEX_SIZE) : 0, - (length == 3) ? get_buf_entry(ctx, VIRGL_SET_INDEX_BUFFER_OFFSET) : 0); - return 0; -} - -static int vrend_decode_set_constant_buffer(struct vrend_decode_ctx *ctx, - uint16_t length) { - uint32_t shader; - uint32_t index; - int nc = (length - 2); - - if (length < 2) - return EINVAL; - - shader = get_buf_entry(ctx, VIRGL_SET_CONSTANT_BUFFER_SHADER_TYPE); - index = get_buf_entry(ctx, VIRGL_SET_CONSTANT_BUFFER_INDEX); - - if (shader >= PIPE_SHADER_TYPES) - return EINVAL; - - vrend_set_constants(ctx->grctx, shader, index, nc, - get_buf_ptr(ctx, VIRGL_SET_CONSTANT_BUFFER_DATA_START)); - return 0; -} - -static int vrend_decode_set_uniform_buffer(struct vrend_decode_ctx *ctx, - int length) { - if (length != VIRGL_SET_UNIFORM_BUFFER_SIZE) - return EINVAL; - - uint32_t shader = get_buf_entry(ctx, VIRGL_SET_UNIFORM_BUFFER_SHADER_TYPE); - uint32_t index = get_buf_entry(ctx, VIRGL_SET_UNIFORM_BUFFER_INDEX); - uint32_t offset = get_buf_entry(ctx, VIRGL_SET_UNIFORM_BUFFER_OFFSET); - uint32_t blength = get_buf_entry(ctx, VIRGL_SET_UNIFORM_BUFFER_LENGTH); - uint32_t handle = get_buf_entry(ctx, VIRGL_SET_UNIFORM_BUFFER_RES_HANDLE); - - if (shader >= PIPE_SHADER_TYPES) - return EINVAL; - - if (index >= PIPE_MAX_CONSTANT_BUFFERS) - return EINVAL; - - vrend_set_uniform_buffer(ctx->grctx, shader, index, offset, blength, handle); - return 0; -} - -static int vrend_decode_set_vertex_buffers(struct vrend_decode_ctx *ctx, - uint16_t length) { - int num_vbo; - int i; - - /* must be a multiple of 3 */ - if (length && (length % 3)) - return EINVAL; - - num_vbo = (length / 3); - if (num_vbo > PIPE_MAX_ATTRIBS) - return EINVAL; - - for (i = 0; i < num_vbo; i++) { - vrend_set_single_vbo(ctx->grctx, i, - get_buf_entry(ctx, VIRGL_SET_VERTEX_BUFFER_STRIDE(i)), - get_buf_entry(ctx, VIRGL_SET_VERTEX_BUFFER_OFFSET(i)), - get_buf_entry(ctx, VIRGL_SET_VERTEX_BUFFER_HANDLE(i))); - } - vrend_set_num_vbo(ctx->grctx, num_vbo); - return 0; -} - -static int vrend_decode_set_sampler_views(struct vrend_decode_ctx *ctx, - uint16_t length) { - uint32_t num_samps; - uint32_t i; - uint32_t shader_type; - uint32_t start_slot; - - if (length < 2) - return EINVAL; - num_samps = length - 2; - shader_type = get_buf_entry(ctx, VIRGL_SET_SAMPLER_VIEWS_SHADER_TYPE); - start_slot = get_buf_entry(ctx, VIRGL_SET_SAMPLER_VIEWS_START_SLOT); - - if (shader_type >= PIPE_SHADER_TYPES) - return EINVAL; - - if (num_samps > PIPE_MAX_SHADER_SAMPLER_VIEWS || - start_slot > (PIPE_MAX_SHADER_SAMPLER_VIEWS - num_samps)) - return EINVAL; - - for (i = 0; i < num_samps; i++) { - uint32_t handle = get_buf_entry(ctx, VIRGL_SET_SAMPLER_VIEWS_V0_HANDLE + i); - vrend_set_single_sampler_view(ctx->grctx, shader_type, i + start_slot, - handle); - } - vrend_set_num_sampler_views(ctx->grctx, shader_type, start_slot, num_samps); - return 0; -} - -static void vrend_decode_transfer_common(struct vrend_decode_ctx *ctx, - struct vrend_transfer_info *info) { - info->handle = get_buf_entry(ctx, VIRGL_RESOURCE_IW_RES_HANDLE); - info->level = get_buf_entry(ctx, VIRGL_RESOURCE_IW_LEVEL); - info->stride = get_buf_entry(ctx, VIRGL_RESOURCE_IW_STRIDE); - info->layer_stride = get_buf_entry(ctx, VIRGL_RESOURCE_IW_LAYER_STRIDE); - info->box->x = get_buf_entry(ctx, VIRGL_RESOURCE_IW_X); - info->box->y = get_buf_entry(ctx, VIRGL_RESOURCE_IW_Y); - info->box->z = get_buf_entry(ctx, VIRGL_RESOURCE_IW_Z); - info->box->width = get_buf_entry(ctx, VIRGL_RESOURCE_IW_W); - info->box->height = get_buf_entry(ctx, VIRGL_RESOURCE_IW_H); - info->box->depth = get_buf_entry(ctx, VIRGL_RESOURCE_IW_D); -} - -static int vrend_decode_resource_inline_write(struct vrend_decode_ctx *ctx, - uint16_t length) { - struct pipe_box box; - struct vrend_transfer_info info; - uint32_t data_len; - struct iovec dataiovec; - void *data; - - if (length < 12) - return EINVAL; - - if (length + ctx->ds->buf_offset > ctx->ds->buf_total) - return EINVAL; - - memset(&info, 0, sizeof(info)); - info.box = &box; - vrend_decode_transfer_common(ctx, &info); - data_len = (length - 11) * 4; - data = get_buf_ptr(ctx, VIRGL_RESOURCE_IW_DATA_START); - - info.ctx_id = 0; - info.offset = 0; - - dataiovec.iov_base = data; - dataiovec.iov_len = data_len; - - info.iovec = &dataiovec; - info.iovec_cnt = 1; - return vrend_transfer_inline_write(ctx->grctx, &info); -} - -static int vrend_decode_draw_vbo(struct vrend_decode_ctx *ctx, int length) { - struct pipe_draw_info info; - uint32_t cso; - uint32_t handle = 0, indirect_draw_count_handle = 0; - if (length != VIRGL_DRAW_VBO_SIZE && length != VIRGL_DRAW_VBO_SIZE_TESS && - length != VIRGL_DRAW_VBO_SIZE_INDIRECT) - return EINVAL; - memset(&info, 0, sizeof(struct pipe_draw_info)); - - info.start = get_buf_entry(ctx, VIRGL_DRAW_VBO_START); - info.count = get_buf_entry(ctx, VIRGL_DRAW_VBO_COUNT); - info.mode = get_buf_entry(ctx, VIRGL_DRAW_VBO_MODE); - info.indexed = get_buf_entry(ctx, VIRGL_DRAW_VBO_INDEXED); - info.instance_count = get_buf_entry(ctx, VIRGL_DRAW_VBO_INSTANCE_COUNT); - info.index_bias = get_buf_entry(ctx, VIRGL_DRAW_VBO_INDEX_BIAS); - info.start_instance = get_buf_entry(ctx, VIRGL_DRAW_VBO_START_INSTANCE); - info.primitive_restart = get_buf_entry(ctx, VIRGL_DRAW_VBO_PRIMITIVE_RESTART); - info.restart_index = get_buf_entry(ctx, VIRGL_DRAW_VBO_RESTART_INDEX); - info.min_index = get_buf_entry(ctx, VIRGL_DRAW_VBO_MIN_INDEX); - info.max_index = get_buf_entry(ctx, VIRGL_DRAW_VBO_MAX_INDEX); - - if (length >= VIRGL_DRAW_VBO_SIZE_TESS) { - info.vertices_per_patch = - get_buf_entry(ctx, VIRGL_DRAW_VBO_VERTICES_PER_PATCH); - info.drawid = get_buf_entry(ctx, VIRGL_DRAW_VBO_DRAWID); - } - - if (length == VIRGL_DRAW_VBO_SIZE_INDIRECT) { - handle = get_buf_entry(ctx, VIRGL_DRAW_VBO_INDIRECT_HANDLE); - info.indirect.offset = get_buf_entry(ctx, VIRGL_DRAW_VBO_INDIRECT_OFFSET); - info.indirect.stride = get_buf_entry(ctx, VIRGL_DRAW_VBO_INDIRECT_STRIDE); - info.indirect.draw_count = - get_buf_entry(ctx, VIRGL_DRAW_VBO_INDIRECT_DRAW_COUNT); - info.indirect.indirect_draw_count_offset = - get_buf_entry(ctx, VIRGL_DRAW_VBO_INDIRECT_DRAW_COUNT_OFFSET); - indirect_draw_count_handle = - get_buf_entry(ctx, VIRGL_DRAW_VBO_INDIRECT_DRAW_COUNT_HANDLE); - } - - cso = get_buf_entry(ctx, VIRGL_DRAW_VBO_COUNT_FROM_SO); - - return vrend_draw_vbo(ctx->grctx, &info, cso, handle, - indirect_draw_count_handle); -} - -static int vrend_decode_create_blend(struct vrend_decode_ctx *ctx, - uint32_t handle, uint16_t length) { - struct pipe_blend_state *blend_state; - uint32_t tmp; - int i; - - if (length != VIRGL_OBJ_BLEND_SIZE) { - return EINVAL; - } - - blend_state = CALLOC_STRUCT(pipe_blend_state); - if (!blend_state) - return ENOMEM; - - tmp = get_buf_entry(ctx, VIRGL_OBJ_BLEND_S0); - blend_state->independent_blend_enable = (tmp & 1); - blend_state->logicop_enable = (tmp >> 1) & 0x1; - blend_state->dither = (tmp >> 2) & 0x1; - blend_state->alpha_to_coverage = (tmp >> 3) & 0x1; - blend_state->alpha_to_one = (tmp >> 4) & 0x1; - - tmp = get_buf_entry(ctx, VIRGL_OBJ_BLEND_S1); - blend_state->logicop_func = tmp & 0xf; - - for (i = 0; i < PIPE_MAX_COLOR_BUFS; i++) { - tmp = get_buf_entry(ctx, VIRGL_OBJ_BLEND_S2(i)); - blend_state->rt[i].blend_enable = tmp & 0x1; - blend_state->rt[i].rgb_func = (tmp >> 1) & 0x7; - blend_state->rt[i].rgb_src_factor = (tmp >> 4) & 0x1f; - blend_state->rt[i].rgb_dst_factor = (tmp >> 9) & 0x1f; - blend_state->rt[i].alpha_func = (tmp >> 14) & 0x7; - blend_state->rt[i].alpha_src_factor = (tmp >> 17) & 0x1f; - blend_state->rt[i].alpha_dst_factor = (tmp >> 22) & 0x1f; - blend_state->rt[i].colormask = (tmp >> 27) & 0xf; - } - - tmp = vrend_renderer_object_insert(ctx->grctx, blend_state, - sizeof(struct pipe_blend_state), handle, - VIRGL_OBJECT_BLEND); - if (tmp == 0) { - FREE(blend_state); - return ENOMEM; - } - return 0; -} - -static int vrend_decode_create_dsa(struct vrend_decode_ctx *ctx, - uint32_t handle, uint16_t length) { - int i; - struct pipe_depth_stencil_alpha_state *dsa_state; - uint32_t tmp; - - if (length != VIRGL_OBJ_DSA_SIZE) - return EINVAL; - - dsa_state = CALLOC_STRUCT(pipe_depth_stencil_alpha_state); - if (!dsa_state) - return ENOMEM; - - tmp = get_buf_entry(ctx, VIRGL_OBJ_DSA_S0); - dsa_state->depth.enabled = tmp & 0x1; - dsa_state->depth.writemask = (tmp >> 1) & 0x1; - dsa_state->depth.func = (tmp >> 2) & 0x7; - - dsa_state->alpha.enabled = (tmp >> 8) & 0x1; - dsa_state->alpha.func = (tmp >> 9) & 0x7; - - for (i = 0; i < 2; i++) { - tmp = get_buf_entry(ctx, VIRGL_OBJ_DSA_S1 + i); - dsa_state->stencil[i].enabled = tmp & 0x1; - dsa_state->stencil[i].func = (tmp >> 1) & 0x7; - dsa_state->stencil[i].fail_op = (tmp >> 4) & 0x7; - dsa_state->stencil[i].zpass_op = (tmp >> 7) & 0x7; - dsa_state->stencil[i].zfail_op = (tmp >> 10) & 0x7; - dsa_state->stencil[i].valuemask = (tmp >> 13) & 0xff; - dsa_state->stencil[i].writemask = (tmp >> 21) & 0xff; - } - - tmp = get_buf_entry(ctx, VIRGL_OBJ_DSA_ALPHA_REF); - dsa_state->alpha.ref_value = uif(tmp); - - tmp = vrend_renderer_object_insert( - ctx->grctx, dsa_state, sizeof(struct pipe_depth_stencil_alpha_state), - handle, VIRGL_OBJECT_DSA); - if (tmp == 0) { - FREE(dsa_state); - return ENOMEM; - } - return 0; -} - -static int vrend_decode_create_rasterizer(struct vrend_decode_ctx *ctx, - uint32_t handle, uint16_t length) { - struct pipe_rasterizer_state *rs_state; - uint32_t tmp; - - if (length != VIRGL_OBJ_RS_SIZE) - return EINVAL; - - rs_state = CALLOC_STRUCT(pipe_rasterizer_state); - if (!rs_state) - return ENOMEM; - - tmp = get_buf_entry(ctx, VIRGL_OBJ_RS_S0); -#define ebit(name, bit) rs_state->name = (tmp >> bit) & 0x1 -#define emask(name, bit, mask) rs_state->name = (tmp >> bit) & mask - - ebit(flatshade, 0); - ebit(depth_clip, 1); - ebit(clip_halfz, 2); - ebit(rasterizer_discard, 3); - ebit(flatshade_first, 4); - ebit(light_twoside, 5); - ebit(sprite_coord_mode, 6); - ebit(point_quad_rasterization, 7); - emask(cull_face, 8, 0x3); - emask(fill_front, 10, 0x3); - emask(fill_back, 12, 0x3); - ebit(scissor, 14); - ebit(front_ccw, 15); - ebit(clamp_vertex_color, 16); - ebit(clamp_fragment_color, 17); - ebit(offset_line, 18); - ebit(offset_point, 19); - ebit(offset_tri, 20); - ebit(poly_smooth, 21); - ebit(poly_stipple_enable, 22); - ebit(point_smooth, 23); - ebit(point_size_per_vertex, 24); - ebit(multisample, 25); - ebit(line_smooth, 26); - ebit(line_stipple_enable, 27); - ebit(line_last_pixel, 28); - ebit(half_pixel_center, 29); - ebit(bottom_edge_rule, 30); - ebit(force_persample_interp, 31); - rs_state->point_size = uif(get_buf_entry(ctx, VIRGL_OBJ_RS_POINT_SIZE)); - rs_state->sprite_coord_enable = - get_buf_entry(ctx, VIRGL_OBJ_RS_SPRITE_COORD_ENABLE); - tmp = get_buf_entry(ctx, VIRGL_OBJ_RS_S3); - emask(line_stipple_pattern, 0, 0xffff); - emask(line_stipple_factor, 16, 0xff); - emask(clip_plane_enable, 24, 0xff); - - rs_state->line_width = uif(get_buf_entry(ctx, VIRGL_OBJ_RS_LINE_WIDTH)); - rs_state->offset_units = uif(get_buf_entry(ctx, VIRGL_OBJ_RS_OFFSET_UNITS)); - rs_state->offset_scale = uif(get_buf_entry(ctx, VIRGL_OBJ_RS_OFFSET_SCALE)); - rs_state->offset_clamp = uif(get_buf_entry(ctx, VIRGL_OBJ_RS_OFFSET_CLAMP)); - - tmp = vrend_renderer_object_insert(ctx->grctx, rs_state, - sizeof(struct pipe_rasterizer_state), - handle, VIRGL_OBJECT_RASTERIZER); - if (tmp == 0) { - FREE(rs_state); - return ENOMEM; - } - return 0; -} - -static int vrend_decode_create_surface(struct vrend_decode_ctx *ctx, - uint32_t handle, uint16_t length) { - uint32_t res_handle, format, val0, val1; - int ret; - - if (length != VIRGL_OBJ_SURFACE_SIZE) - return EINVAL; - - res_handle = get_buf_entry(ctx, VIRGL_OBJ_SURFACE_RES_HANDLE); - format = get_buf_entry(ctx, VIRGL_OBJ_SURFACE_FORMAT); - /* decide later if these are texture or buffer */ - val0 = get_buf_entry(ctx, VIRGL_OBJ_SURFACE_BUFFER_FIRST_ELEMENT); - val1 = get_buf_entry(ctx, VIRGL_OBJ_SURFACE_BUFFER_LAST_ELEMENT); - ret = - vrend_create_surface(ctx->grctx, handle, res_handle, format, val0, val1); - return ret; -} - -static int vrend_decode_create_sampler_view(struct vrend_decode_ctx *ctx, - uint32_t handle, uint16_t length) { - uint32_t res_handle, format, val0, val1, swizzle_packed; - - if (length != VIRGL_OBJ_SAMPLER_VIEW_SIZE) - return EINVAL; - - res_handle = get_buf_entry(ctx, VIRGL_OBJ_SAMPLER_VIEW_RES_HANDLE); - format = get_buf_entry(ctx, VIRGL_OBJ_SAMPLER_VIEW_FORMAT); - val0 = get_buf_entry(ctx, VIRGL_OBJ_SAMPLER_VIEW_BUFFER_FIRST_ELEMENT); - val1 = get_buf_entry(ctx, VIRGL_OBJ_SAMPLER_VIEW_BUFFER_LAST_ELEMENT); - swizzle_packed = get_buf_entry(ctx, VIRGL_OBJ_SAMPLER_VIEW_SWIZZLE); - return vrend_create_sampler_view(ctx->grctx, handle, res_handle, format, val0, - val1, swizzle_packed); -} - -static int vrend_decode_create_sampler_state(struct vrend_decode_ctx *ctx, - uint32_t handle, uint16_t length) { - struct pipe_sampler_state state; - int i; - uint32_t tmp; - - if (length != VIRGL_OBJ_SAMPLER_STATE_SIZE) - return EINVAL; - tmp = get_buf_entry(ctx, VIRGL_OBJ_SAMPLER_STATE_S0); - state.wrap_s = tmp & 0x7; - state.wrap_t = (tmp >> 3) & 0x7; - state.wrap_r = (tmp >> 6) & 0x7; - state.min_img_filter = (tmp >> 9) & 0x3; - state.min_mip_filter = (tmp >> 11) & 0x3; - state.mag_img_filter = (tmp >> 13) & 0x3; - state.compare_mode = (tmp >> 15) & 0x1; - state.compare_func = (tmp >> 16) & 0x7; - state.seamless_cube_map = (tmp >> 19) & 0x1; - - state.lod_bias = uif(get_buf_entry(ctx, VIRGL_OBJ_SAMPLER_STATE_LOD_BIAS)); - state.min_lod = uif(get_buf_entry(ctx, VIRGL_OBJ_SAMPLER_STATE_MIN_LOD)); - state.max_lod = uif(get_buf_entry(ctx, VIRGL_OBJ_SAMPLER_STATE_MAX_LOD)); - - for (i = 0; i < 4; i++) - state.border_color.ui[i] = - get_buf_entry(ctx, VIRGL_OBJ_SAMPLER_STATE_BORDER_COLOR(i)); - - if (state.min_mip_filter != PIPE_TEX_MIPFILTER_NONE && - state.min_mip_filter != PIPE_TEX_MIPFILTER_LINEAR && - state.min_mip_filter != PIPE_TEX_MIPFILTER_NEAREST) - return EINVAL; - - return vrend_create_sampler_state(ctx->grctx, handle, &state); -} - -static int vrend_decode_create_ve(struct vrend_decode_ctx *ctx, uint32_t handle, - uint16_t length) { - struct pipe_vertex_element *ve = NULL; - int num_elements; - int i; - int ret; - - if (length < 1) - return EINVAL; - - if ((length - 1) % 4) - return EINVAL; - - num_elements = (length - 1) / 4; - - if (num_elements) { - ve = calloc(num_elements, sizeof(struct pipe_vertex_element)); - - if (!ve) - return ENOMEM; - - for (i = 0; i < num_elements; i++) { - ve[i].src_offset = - get_buf_entry(ctx, VIRGL_OBJ_VERTEX_ELEMENTS_V0_SRC_OFFSET(i)); - ve[i].instance_divisor = - get_buf_entry(ctx, VIRGL_OBJ_VERTEX_ELEMENTS_V0_INSTANCE_DIVISOR(i)); - ve[i].vertex_buffer_index = get_buf_entry( - ctx, VIRGL_OBJ_VERTEX_ELEMENTS_V0_VERTEX_BUFFER_INDEX(i)); - - if (ve[i].vertex_buffer_index >= PIPE_MAX_ATTRIBS) { - FREE(ve); - return EINVAL; - } - - ve[i].src_format = - get_buf_entry(ctx, VIRGL_OBJ_VERTEX_ELEMENTS_V0_SRC_FORMAT(i)); - } - } - - ret = - vrend_create_vertex_elements_state(ctx->grctx, handle, num_elements, ve); - - FREE(ve); - return ret; -} - -static int vrend_decode_create_query(struct vrend_decode_ctx *ctx, - uint32_t handle, uint16_t length) { - uint32_t query_type; - uint32_t query_index; - uint32_t res_handle; - uint32_t offset; - uint32_t tmp; - - if (length != VIRGL_OBJ_QUERY_SIZE) - return EINVAL; - - tmp = get_buf_entry(ctx, VIRGL_OBJ_QUERY_TYPE_INDEX); - query_type = VIRGL_OBJ_QUERY_TYPE(tmp); - query_index = (tmp >> 16) & 0xffff; - - offset = get_buf_entry(ctx, VIRGL_OBJ_QUERY_OFFSET); - res_handle = get_buf_entry(ctx, VIRGL_OBJ_QUERY_RES_HANDLE); - - return vrend_create_query(ctx->grctx, handle, query_type, query_index, - res_handle, offset); -} - -static int vrend_decode_create_object(struct vrend_decode_ctx *ctx, - int length) { - if (length < 1) - return EINVAL; - - uint32_t header = get_buf_entry(ctx, VIRGL_OBJ_CREATE_HEADER); - uint32_t handle = get_buf_entry(ctx, VIRGL_OBJ_CREATE_HANDLE); - uint8_t obj_type = (header >> 8) & 0xff; - int ret = 0; - - if (handle == 0) - return EINVAL; - - switch (obj_type) { - case VIRGL_OBJECT_BLEND: - ret = vrend_decode_create_blend(ctx, handle, length); - break; - case VIRGL_OBJECT_DSA: - ret = vrend_decode_create_dsa(ctx, handle, length); - break; - case VIRGL_OBJECT_RASTERIZER: - ret = vrend_decode_create_rasterizer(ctx, handle, length); - break; - case VIRGL_OBJECT_SHADER: - ret = vrend_decode_create_shader(ctx, handle, length); - break; - case VIRGL_OBJECT_VERTEX_ELEMENTS: - ret = vrend_decode_create_ve(ctx, handle, length); - break; - case VIRGL_OBJECT_SURFACE: - ret = vrend_decode_create_surface(ctx, handle, length); - break; - case VIRGL_OBJECT_SAMPLER_VIEW: - ret = vrend_decode_create_sampler_view(ctx, handle, length); - break; - case VIRGL_OBJECT_SAMPLER_STATE: - ret = vrend_decode_create_sampler_state(ctx, handle, length); - break; - case VIRGL_OBJECT_QUERY: - ret = vrend_decode_create_query(ctx, handle, length); - break; - case VIRGL_OBJECT_STREAMOUT_TARGET: - ret = vrend_decode_create_stream_output_target(ctx, handle, length); - break; - default: - return EINVAL; - } - - return ret; -} - -static int vrend_decode_bind_object(struct vrend_decode_ctx *ctx, - uint16_t length) { - if (length != 1) - return EINVAL; - - uint32_t header = get_buf_entry(ctx, VIRGL_OBJ_BIND_HEADER); - uint32_t handle = get_buf_entry(ctx, VIRGL_OBJ_BIND_HANDLE); - uint8_t obj_type = (header >> 8) & 0xff; - - switch (obj_type) { - case VIRGL_OBJECT_BLEND: - vrend_object_bind_blend(ctx->grctx, handle); - break; - case VIRGL_OBJECT_DSA: - vrend_object_bind_dsa(ctx->grctx, handle); - break; - case VIRGL_OBJECT_RASTERIZER: - vrend_object_bind_rasterizer(ctx->grctx, handle); - break; - case VIRGL_OBJECT_VERTEX_ELEMENTS: - vrend_bind_vertex_elements_state(ctx->grctx, handle); - break; - default: - return EINVAL; - } - - return 0; -} - -static int vrend_decode_destroy_object(struct vrend_decode_ctx *ctx, - int length) { - if (length != 1) - return EINVAL; - - uint32_t handle = get_buf_entry(ctx, VIRGL_OBJ_DESTROY_HANDLE); - - vrend_renderer_object_destroy(ctx->grctx, handle); - return 0; -} - -static int vrend_decode_set_stencil_ref(struct vrend_decode_ctx *ctx, - int length) { - if (length != VIRGL_SET_STENCIL_REF_SIZE) - return EINVAL; - - struct pipe_stencil_ref ref; - uint32_t val = get_buf_entry(ctx, VIRGL_SET_STENCIL_REF); - - ref.ref_value[0] = val & 0xff; - ref.ref_value[1] = (val >> 8) & 0xff; - vrend_set_stencil_ref(ctx->grctx, &ref); - return 0; -} - -static int vrend_decode_set_blend_color(struct vrend_decode_ctx *ctx, - int length) { - struct pipe_blend_color color; - int i; - - if (length != VIRGL_SET_BLEND_COLOR_SIZE) - return EINVAL; - - for (i = 0; i < 4; i++) - color.color[i] = uif(get_buf_entry(ctx, VIRGL_SET_BLEND_COLOR(i))); - - vrend_set_blend_color(ctx->grctx, &color); - return 0; -} - -static int vrend_decode_set_scissor_state(struct vrend_decode_ctx *ctx, - int length) { - struct pipe_scissor_state ss[PIPE_MAX_VIEWPORTS]; - uint32_t temp; - int32_t num_scissor; - uint32_t start_slot; - int s; - if (length < 1) - return EINVAL; - - if ((length - 1) % 2) - return EINVAL; - - num_scissor = (length - 1) / 2; - if (num_scissor > PIPE_MAX_VIEWPORTS) - return EINVAL; - - start_slot = get_buf_entry(ctx, VIRGL_SET_SCISSOR_START_SLOT); - - for (s = 0; s < num_scissor; s++) { - temp = get_buf_entry(ctx, VIRGL_SET_SCISSOR_MINX_MINY(s)); - ss[s].minx = temp & 0xffff; - ss[s].miny = (temp >> 16) & 0xffff; - - temp = get_buf_entry(ctx, VIRGL_SET_SCISSOR_MAXX_MAXY(s)); - ss[s].maxx = temp & 0xffff; - ss[s].maxy = (temp >> 16) & 0xffff; - } - - vrend_set_scissor_state(ctx->grctx, start_slot, num_scissor, ss); - return 0; -} - -static int vrend_decode_set_polygon_stipple(struct vrend_decode_ctx *ctx, - int length) { - struct pipe_poly_stipple ps; - int i; - - if (length != VIRGL_POLYGON_STIPPLE_SIZE) - return EINVAL; - - for (i = 0; i < 32; i++) - ps.stipple[i] = get_buf_entry(ctx, VIRGL_POLYGON_STIPPLE_P0 + i); - - vrend_set_polygon_stipple(ctx->grctx, &ps); - return 0; -} - -static int vrend_decode_set_clip_state(struct vrend_decode_ctx *ctx, - int length) { - struct pipe_clip_state clip; - int i, j; - - if (length != VIRGL_SET_CLIP_STATE_SIZE) - return EINVAL; - - for (i = 0; i < 8; i++) - for (j = 0; j < 4; j++) - clip.ucp[i][j] = - uif(get_buf_entry(ctx, VIRGL_SET_CLIP_STATE_C0 + (i * 4) + j)); - vrend_set_clip_state(ctx->grctx, &clip); - return 0; -} - -static int vrend_decode_set_sample_mask(struct vrend_decode_ctx *ctx, - int length) { - unsigned mask; - - if (length != VIRGL_SET_SAMPLE_MASK_SIZE) - return EINVAL; - mask = get_buf_entry(ctx, VIRGL_SET_SAMPLE_MASK_MASK); - vrend_set_sample_mask(ctx->grctx, mask); - return 0; -} - -static int vrend_decode_set_min_samples(struct vrend_decode_ctx *ctx, - int length) { - unsigned min_samples; - - if (length != VIRGL_SET_MIN_SAMPLES_SIZE) - return EINVAL; - min_samples = get_buf_entry(ctx, VIRGL_SET_MIN_SAMPLES_MASK); - vrend_set_min_samples(ctx->grctx, min_samples); - return 0; -} - -static int vrend_decode_resource_copy_region(struct vrend_decode_ctx *ctx, - int length) { - struct pipe_box box; - uint32_t dst_handle, src_handle; - uint32_t dst_level, dstx, dsty, dstz; - uint32_t src_level; - - if (length != VIRGL_CMD_RESOURCE_COPY_REGION_SIZE) - return EINVAL; - - dst_handle = get_buf_entry(ctx, VIRGL_CMD_RCR_DST_RES_HANDLE); - dst_level = get_buf_entry(ctx, VIRGL_CMD_RCR_DST_LEVEL); - dstx = get_buf_entry(ctx, VIRGL_CMD_RCR_DST_X); - dsty = get_buf_entry(ctx, VIRGL_CMD_RCR_DST_Y); - dstz = get_buf_entry(ctx, VIRGL_CMD_RCR_DST_Z); - src_handle = get_buf_entry(ctx, VIRGL_CMD_RCR_SRC_RES_HANDLE); - src_level = get_buf_entry(ctx, VIRGL_CMD_RCR_SRC_LEVEL); - box.x = get_buf_entry(ctx, VIRGL_CMD_RCR_SRC_X); - box.y = get_buf_entry(ctx, VIRGL_CMD_RCR_SRC_Y); - box.z = get_buf_entry(ctx, VIRGL_CMD_RCR_SRC_Z); - box.width = get_buf_entry(ctx, VIRGL_CMD_RCR_SRC_W); - box.height = get_buf_entry(ctx, VIRGL_CMD_RCR_SRC_H); - box.depth = get_buf_entry(ctx, VIRGL_CMD_RCR_SRC_D); - - vrend_renderer_resource_copy_region(ctx->grctx, dst_handle, dst_level, dstx, - dsty, dstz, src_handle, src_level, &box); - return 0; -} - -static int vrend_decode_blit(struct vrend_decode_ctx *ctx, int length) { - struct pipe_blit_info info; - uint32_t dst_handle, src_handle, temp; - - if (length != VIRGL_CMD_BLIT_SIZE) - return EINVAL; - temp = get_buf_entry(ctx, VIRGL_CMD_BLIT_S0); - info.mask = temp & 0xff; - info.filter = (temp >> 8) & 0x3; - info.scissor_enable = (temp >> 10) & 0x1; - info.render_condition_enable = (temp >> 11) & 0x1; - info.alpha_blend = (temp >> 12) & 0x1; - temp = get_buf_entry(ctx, VIRGL_CMD_BLIT_SCISSOR_MINX_MINY); - info.scissor.minx = temp & 0xffff; - info.scissor.miny = (temp >> 16) & 0xffff; - temp = get_buf_entry(ctx, VIRGL_CMD_BLIT_SCISSOR_MAXX_MAXY); - info.scissor.maxx = temp & 0xffff; - info.scissor.maxy = (temp >> 16) & 0xffff; - dst_handle = get_buf_entry(ctx, VIRGL_CMD_BLIT_DST_RES_HANDLE); - info.dst.level = get_buf_entry(ctx, VIRGL_CMD_BLIT_DST_LEVEL); - info.dst.format = get_buf_entry(ctx, VIRGL_CMD_BLIT_DST_FORMAT); - info.dst.box.x = get_buf_entry(ctx, VIRGL_CMD_BLIT_DST_X); - info.dst.box.y = get_buf_entry(ctx, VIRGL_CMD_BLIT_DST_Y); - info.dst.box.z = get_buf_entry(ctx, VIRGL_CMD_BLIT_DST_Z); - info.dst.box.width = get_buf_entry(ctx, VIRGL_CMD_BLIT_DST_W); - info.dst.box.height = get_buf_entry(ctx, VIRGL_CMD_BLIT_DST_H); - info.dst.box.depth = get_buf_entry(ctx, VIRGL_CMD_BLIT_DST_D); - - src_handle = get_buf_entry(ctx, VIRGL_CMD_BLIT_SRC_RES_HANDLE); - info.src.level = get_buf_entry(ctx, VIRGL_CMD_BLIT_SRC_LEVEL); - info.src.format = get_buf_entry(ctx, VIRGL_CMD_BLIT_SRC_FORMAT); - info.src.box.x = get_buf_entry(ctx, VIRGL_CMD_BLIT_SRC_X); - info.src.box.y = get_buf_entry(ctx, VIRGL_CMD_BLIT_SRC_Y); - info.src.box.z = get_buf_entry(ctx, VIRGL_CMD_BLIT_SRC_Z); - info.src.box.width = get_buf_entry(ctx, VIRGL_CMD_BLIT_SRC_W); - info.src.box.height = get_buf_entry(ctx, VIRGL_CMD_BLIT_SRC_H); - info.src.box.depth = get_buf_entry(ctx, VIRGL_CMD_BLIT_SRC_D); - - vrend_renderer_blit(ctx->grctx, dst_handle, src_handle, &info); - return 0; -} - -static int vrend_decode_bind_sampler_states(struct vrend_decode_ctx *ctx, - int length) { - if (length < 2) - return EINVAL; - - uint32_t shader_type = - get_buf_entry(ctx, VIRGL_BIND_SAMPLER_STATES_SHADER_TYPE); - uint32_t start_slot = - get_buf_entry(ctx, VIRGL_BIND_SAMPLER_STATES_START_SLOT); - uint32_t num_states = length - 2; - - if (shader_type >= PIPE_SHADER_TYPES) - return EINVAL; - - vrend_bind_sampler_states( - ctx->grctx, shader_type, start_slot, num_states, - get_buf_ptr(ctx, VIRGL_BIND_SAMPLER_STATES_S0_HANDLE)); - return 0; -} - -static int vrend_decode_begin_query(struct vrend_decode_ctx *ctx, int length) { - if (length != 1) - return EINVAL; - - uint32_t handle = get_buf_entry(ctx, VIRGL_QUERY_BEGIN_HANDLE); - - return vrend_begin_query(ctx->grctx, handle); -} - -static int vrend_decode_end_query(struct vrend_decode_ctx *ctx, int length) { - if (length != 1) - return EINVAL; - - uint32_t handle = get_buf_entry(ctx, VIRGL_QUERY_END_HANDLE); - - return vrend_end_query(ctx->grctx, handle); -} - -static int vrend_decode_get_query_result(struct vrend_decode_ctx *ctx, - int length) { - if (length != 2) - return EINVAL; - - uint32_t handle = get_buf_entry(ctx, VIRGL_QUERY_RESULT_HANDLE); - uint32_t wait = get_buf_entry(ctx, VIRGL_QUERY_RESULT_WAIT); - - vrend_get_query_result(ctx->grctx, handle, wait); - return 0; -} - -static int vrend_decode_set_sub_ctx(struct vrend_decode_ctx *ctx, int length) { - if (length != 1) - return EINVAL; - - uint32_t ctx_sub_id = get_buf_entry(ctx, 1); - - vrend_renderer_set_sub_ctx(ctx->grctx, ctx_sub_id); - return 0; -} - -static int vrend_decode_create_sub_ctx(struct vrend_decode_ctx *ctx, - int length) { - if (length != 1) - return EINVAL; - - uint32_t ctx_sub_id = get_buf_entry(ctx, 1); - - vrend_renderer_create_sub_ctx(ctx->grctx, ctx_sub_id); - return 0; -} - -static int vrend_decode_destroy_sub_ctx(struct vrend_decode_ctx *ctx, - int length) { - if (length != 1) - return EINVAL; - - uint32_t ctx_sub_id = get_buf_entry(ctx, 1); - - vrend_renderer_destroy_sub_ctx(ctx->grctx, ctx_sub_id); - return 0; -} - -static int vrend_decode_bind_shader(struct vrend_decode_ctx *ctx, int length) { - uint32_t handle, type; - if (length != VIRGL_BIND_SHADER_SIZE) - return EINVAL; - - handle = get_buf_entry(ctx, VIRGL_BIND_SHADER_HANDLE); - type = get_buf_entry(ctx, VIRGL_BIND_SHADER_TYPE); - - vrend_bind_shader(ctx->grctx, handle, type); - return 0; -} - -static int vrend_decode_set_tess_state(struct vrend_decode_ctx *ctx, - int length) { - float tess_factors[6]; - int i; - - if (length != VIRGL_TESS_STATE_SIZE) - return EINVAL; - - for (i = 0; i < 6; i++) { - tess_factors[i] = uif(get_buf_entry(ctx, i + 1)); - } - vrend_set_tess_state(ctx->grctx, tess_factors); - return 0; -} - -static int vrend_decode_set_shader_buffers(struct vrend_decode_ctx *ctx, - uint16_t length) { - uint32_t num_ssbo; - uint32_t shader_type, start_slot; - - if (length < 2) - return EINVAL; - - num_ssbo = (length - 2) / VIRGL_SET_SHADER_BUFFER_ELEMENT_SIZE; - shader_type = get_buf_entry(ctx, VIRGL_SET_SHADER_BUFFER_SHADER_TYPE); - start_slot = get_buf_entry(ctx, VIRGL_SET_SHADER_BUFFER_START_SLOT); - if (shader_type >= PIPE_SHADER_TYPES) - return EINVAL; - - if (num_ssbo < 1) - return 0; - - if (start_slot > PIPE_MAX_SHADER_BUFFERS || - start_slot > PIPE_MAX_SHADER_BUFFERS - num_ssbo) - return EINVAL; - - for (uint32_t i = 0; i < num_ssbo; i++) { - uint32_t offset = get_buf_entry(ctx, VIRGL_SET_SHADER_BUFFER_OFFSET(i)); - uint32_t buf_len = get_buf_entry(ctx, VIRGL_SET_SHADER_BUFFER_LENGTH(i)); - uint32_t handle = get_buf_entry(ctx, VIRGL_SET_SHADER_BUFFER_RES_HANDLE(i)); - vrend_set_single_ssbo(ctx->grctx, shader_type, start_slot + i, offset, - buf_len, handle); - } - return 0; -} - -static int vrend_decode_set_atomic_buffers(struct vrend_decode_ctx *ctx, - uint16_t length) { - uint32_t num_abo; - uint32_t start_slot; - - if (length < 2) - return EINVAL; - - num_abo = (length - 1) / VIRGL_SET_ATOMIC_BUFFER_ELEMENT_SIZE; - start_slot = get_buf_entry(ctx, VIRGL_SET_ATOMIC_BUFFER_START_SLOT); - if (num_abo < 1) - return 0; - - if (start_slot > PIPE_MAX_HW_ATOMIC_BUFFERS || - start_slot > PIPE_MAX_HW_ATOMIC_BUFFERS - num_abo) - return EINVAL; - - for (uint32_t i = 0; i < num_abo; i++) { - uint32_t offset = - get_buf_entry(ctx, i * VIRGL_SET_ATOMIC_BUFFER_ELEMENT_SIZE + 2); - uint32_t buf_len = - get_buf_entry(ctx, i * VIRGL_SET_ATOMIC_BUFFER_ELEMENT_SIZE + 3); - uint32_t handle = - get_buf_entry(ctx, i * VIRGL_SET_ATOMIC_BUFFER_ELEMENT_SIZE + 4); - vrend_set_single_abo(ctx->grctx, start_slot + i, offset, buf_len, handle); - } - - return 0; -} - -static int vrend_decode_set_shader_images(struct vrend_decode_ctx *ctx, - uint16_t length) { - uint32_t num_images; - uint32_t shader_type, start_slot; - if (length < 2) - return EINVAL; - - num_images = (length - 2) / VIRGL_SET_SHADER_IMAGE_ELEMENT_SIZE; - shader_type = get_buf_entry(ctx, VIRGL_SET_SHADER_IMAGE_SHADER_TYPE); - start_slot = get_buf_entry(ctx, VIRGL_SET_SHADER_IMAGE_START_SLOT); - if (shader_type >= PIPE_SHADER_TYPES) - return EINVAL; - - if (num_images < 1) { - return 0; - } - if (start_slot > PIPE_MAX_SHADER_IMAGES || - start_slot > PIPE_MAX_SHADER_IMAGES - num_images) - return EINVAL; - - for (uint32_t i = 0; i < num_images; i++) { - uint32_t format = get_buf_entry(ctx, VIRGL_SET_SHADER_IMAGE_FORMAT(i)); - uint32_t access = get_buf_entry(ctx, VIRGL_SET_SHADER_IMAGE_ACCESS(i)); - uint32_t layer_offset = - get_buf_entry(ctx, VIRGL_SET_SHADER_IMAGE_LAYER_OFFSET(i)); - uint32_t level_size = - get_buf_entry(ctx, VIRGL_SET_SHADER_IMAGE_LEVEL_SIZE(i)); - uint32_t handle = get_buf_entry(ctx, VIRGL_SET_SHADER_IMAGE_RES_HANDLE(i)); - vrend_set_single_image_view(ctx->grctx, shader_type, start_slot + i, format, - access, layer_offset, level_size, handle); - } - return 0; -} - -static int vrend_decode_memory_barrier(struct vrend_decode_ctx *ctx, - uint16_t length) { - if (length != VIRGL_MEMORY_BARRIER_SIZE) - return EINVAL; - - unsigned flags = get_buf_entry(ctx, VIRGL_MEMORY_BARRIER_FLAGS); - vrend_memory_barrier(ctx->grctx, flags); - return 0; -} - -static int vrend_decode_launch_grid(struct vrend_decode_ctx *ctx, - uint16_t length) { - uint32_t block[3], grid[3]; - uint32_t indirect_handle, indirect_offset; - if (length != VIRGL_LAUNCH_GRID_SIZE) - return EINVAL; - - block[0] = get_buf_entry(ctx, VIRGL_LAUNCH_BLOCK_X); - block[1] = get_buf_entry(ctx, VIRGL_LAUNCH_BLOCK_Y); - block[2] = get_buf_entry(ctx, VIRGL_LAUNCH_BLOCK_Z); - grid[0] = get_buf_entry(ctx, VIRGL_LAUNCH_GRID_X); - grid[1] = get_buf_entry(ctx, VIRGL_LAUNCH_GRID_Y); - grid[2] = get_buf_entry(ctx, VIRGL_LAUNCH_GRID_Z); - indirect_handle = get_buf_entry(ctx, VIRGL_LAUNCH_INDIRECT_HANDLE); - indirect_offset = get_buf_entry(ctx, VIRGL_LAUNCH_INDIRECT_OFFSET); - vrend_launch_grid(ctx->grctx, block, grid, indirect_handle, indirect_offset); - return 0; -} - -static int vrend_decode_set_streamout_targets(struct vrend_decode_ctx *ctx, - uint16_t length) { - uint32_t handles[16]; - uint32_t num_handles = length - 1; - uint32_t append_bitmask; - uint i; - - if (length < 1) - return EINVAL; - if (num_handles > ARRAY_SIZE(handles)) - return EINVAL; - - append_bitmask = - get_buf_entry(ctx, VIRGL_SET_STREAMOUT_TARGETS_APPEND_BITMASK); - for (i = 0; i < num_handles; i++) - handles[i] = get_buf_entry(ctx, VIRGL_SET_STREAMOUT_TARGETS_H0 + i); - vrend_set_streamout_targets(ctx->grctx, append_bitmask, num_handles, handles); - return 0; -} - -static int vrend_decode_transfer3d(struct virgl_client *client, - struct vrend_decode_ctx *ctx, int length, - uint32_t ctx_id) { - struct pipe_box box; - struct vrend_transfer_info info; - - if (length < VIRGL_TRANSFER3D_SIZE) - return EINVAL; - - memset(&info, 0, sizeof(info)); - info.box = &box; - info.ctx_id = ctx_id; - vrend_decode_transfer_common(ctx, &info); - info.offset = get_buf_entry(ctx, VIRGL_TRANSFER3D_DATA_OFFSET); - int transfer_mode = get_buf_entry(ctx, VIRGL_TRANSFER3D_DIRECTION); - info.context0 = false; - - if (transfer_mode != VIRGL_TRANSFER_TO_HOST && - transfer_mode != VIRGL_TRANSFER_FROM_HOST) - return EINVAL; - - return vrend_renderer_transfer_iov(client, &info, transfer_mode); -} - -static int vrend_decode_copy_transfer3d(struct vrend_decode_ctx *ctx, - int length) { - struct pipe_box box; - struct vrend_transfer_info info; - uint32_t src_handle; - - if (length != VIRGL_COPY_TRANSFER3D_SIZE) - return EINVAL; - - memset(&info, 0, sizeof(info)); - info.box = &box; - vrend_decode_transfer_common(ctx, &info); - info.offset = get_buf_entry(ctx, VIRGL_COPY_TRANSFER3D_SRC_RES_OFFSET); - info.synchronized = - (get_buf_entry(ctx, VIRGL_COPY_TRANSFER3D_SYNCHRONIZED) != 0); - - src_handle = get_buf_entry(ctx, VIRGL_COPY_TRANSFER3D_SRC_RES_HANDLE); - - return vrend_renderer_copy_transfer3d(ctx->grctx, &info, src_handle); -} - -void vrend_renderer_context_create_internal(struct virgl_client *client, - uint32_t handle) { - struct vrend_decode_ctx *dctx; - - if (handle >= VREND_MAX_CTX) - return; - - dctx = client->dec_ctx[handle]; - if (dctx) - return; - - dctx = malloc(sizeof(struct vrend_decode_ctx)); - if (!dctx) - return; - - dctx->grctx = vrend_create_context(client, handle); - if (!dctx->grctx) { - free(dctx); - return; - } - - dctx->ds = &dctx->ids; - client->dec_ctx[handle] = dctx; -} - -int vrend_renderer_context_create(struct virgl_client *client, - uint32_t handle) { - if (handle >= VREND_MAX_CTX) - return EINVAL; - - /* context 0 is always available with no guarantees */ - if (handle == 0) - return EINVAL; - - vrend_renderer_context_create_internal(client, handle); - return 0; -} - -void vrend_renderer_context_destroy(struct virgl_client *client, - uint32_t handle) { - struct vrend_decode_ctx *ctx; - bool ret; - - if (handle >= VREND_MAX_CTX) - return; - - /* never destroy context 0 here, it will be destroyed in - * vrend_decode_reset()*/ - if (handle == 0) { - return; - } - - ctx = client->dec_ctx[handle]; - if (!ctx) - return; - - client->dec_ctx[handle] = NULL; - ret = vrend_destroy_context(ctx->grctx); - free(ctx); - /* switch to ctx 0 */ - if (ret && handle != 0) - vrend_hw_switch_context(client->dec_ctx[0]->grctx, true); -} - -struct vrend_context *vrend_lookup_renderer_ctx(struct virgl_client *client, - uint32_t ctx_id) { - if (ctx_id >= VREND_MAX_CTX) - return NULL; - - if (!client->dec_ctx[ctx_id]) - return NULL; - - return client->dec_ctx[ctx_id]->grctx; -} - -int vrend_decode_block(struct virgl_client *client, uint32_t ctx_id, - uint32_t *block, int ndw) { - struct vrend_decode_ctx *gdctx; - bool bret; - int ret; - if (ctx_id >= VREND_MAX_CTX) - return EINVAL; - - if (client->dec_ctx[ctx_id] == NULL) - return EINVAL; - - gdctx = client->dec_ctx[ctx_id]; - - bret = vrend_hw_switch_context(gdctx->grctx, true); - if (bret == false) - return EINVAL; - - gdctx->ds->buf = block; - gdctx->ds->buf_total = ndw; - gdctx->ds->buf_offset = 0; - - while (gdctx->ds->buf_offset < gdctx->ds->buf_total) { - uint32_t header = gdctx->ds->buf[gdctx->ds->buf_offset]; - uint32_t len = header >> 16; - - ret = 0; - /* check if the guest is doing something bad */ - if (gdctx->ds->buf_offset + len + 1 > gdctx->ds->buf_total) - break; - - switch (header & 0xff) { - case VIRGL_CCMD_CREATE_OBJECT: - ret = vrend_decode_create_object(gdctx, len); - break; - case VIRGL_CCMD_BIND_OBJECT: - ret = vrend_decode_bind_object(gdctx, len); - break; - case VIRGL_CCMD_DESTROY_OBJECT: - ret = vrend_decode_destroy_object(gdctx, len); - break; - case VIRGL_CCMD_CLEAR: - ret = vrend_decode_clear(gdctx, len); - break; - case VIRGL_CCMD_DRAW_VBO: - ret = vrend_decode_draw_vbo(gdctx, len); - break; - case VIRGL_CCMD_SET_FRAMEBUFFER_STATE: - ret = vrend_decode_set_framebuffer_state(gdctx, len); - break; - case VIRGL_CCMD_SET_VERTEX_BUFFERS: - ret = vrend_decode_set_vertex_buffers(gdctx, len); - break; - case VIRGL_CCMD_RESOURCE_INLINE_WRITE: - ret = vrend_decode_resource_inline_write(gdctx, len); - break; - case VIRGL_CCMD_SET_VIEWPORT_STATE: - ret = vrend_decode_set_viewport_state(gdctx, len); - break; - case VIRGL_CCMD_SET_SAMPLER_VIEWS: - ret = vrend_decode_set_sampler_views(gdctx, len); - break; - case VIRGL_CCMD_SET_INDEX_BUFFER: - ret = vrend_decode_set_index_buffer(gdctx, len); - break; - case VIRGL_CCMD_SET_CONSTANT_BUFFER: - ret = vrend_decode_set_constant_buffer(gdctx, len); - break; - case VIRGL_CCMD_SET_STENCIL_REF: - ret = vrend_decode_set_stencil_ref(gdctx, len); - break; - case VIRGL_CCMD_SET_BLEND_COLOR: - ret = vrend_decode_set_blend_color(gdctx, len); - break; - case VIRGL_CCMD_SET_SCISSOR_STATE: - ret = vrend_decode_set_scissor_state(gdctx, len); - break; - case VIRGL_CCMD_BLIT: - ret = vrend_decode_blit(gdctx, len); - break; - case VIRGL_CCMD_RESOURCE_COPY_REGION: - ret = vrend_decode_resource_copy_region(gdctx, len); - break; - case VIRGL_CCMD_BIND_SAMPLER_STATES: - ret = vrend_decode_bind_sampler_states(gdctx, len); - break; - case VIRGL_CCMD_BEGIN_QUERY: - ret = vrend_decode_begin_query(gdctx, len); - break; - case VIRGL_CCMD_END_QUERY: - ret = vrend_decode_end_query(gdctx, len); - break; - case VIRGL_CCMD_GET_QUERY_RESULT: - ret = vrend_decode_get_query_result(gdctx, len); - break; - case VIRGL_CCMD_SET_POLYGON_STIPPLE: - ret = vrend_decode_set_polygon_stipple(gdctx, len); - break; - case VIRGL_CCMD_SET_CLIP_STATE: - ret = vrend_decode_set_clip_state(gdctx, len); - break; - case VIRGL_CCMD_SET_SAMPLE_MASK: - ret = vrend_decode_set_sample_mask(gdctx, len); - break; - case VIRGL_CCMD_SET_MIN_SAMPLES: - ret = vrend_decode_set_min_samples(gdctx, len); - break; - case VIRGL_CCMD_SET_STREAMOUT_TARGETS: - ret = vrend_decode_set_streamout_targets(gdctx, len); - break; - case VIRGL_CCMD_SET_UNIFORM_BUFFER: - ret = vrend_decode_set_uniform_buffer(gdctx, len); - break; - case VIRGL_CCMD_SET_SUB_CTX: - ret = vrend_decode_set_sub_ctx(gdctx, len); - break; - case VIRGL_CCMD_CREATE_SUB_CTX: - ret = vrend_decode_create_sub_ctx(gdctx, len); - break; - case VIRGL_CCMD_DESTROY_SUB_CTX: - ret = vrend_decode_destroy_sub_ctx(gdctx, len); - break; - case VIRGL_CCMD_BIND_SHADER: - ret = vrend_decode_bind_shader(gdctx, len); - break; - case VIRGL_CCMD_SET_TESS_STATE: - ret = vrend_decode_set_tess_state(gdctx, len); - break; - case VIRGL_CCMD_SET_SHADER_BUFFERS: - ret = vrend_decode_set_shader_buffers(gdctx, len); - break; - case VIRGL_CCMD_SET_SHADER_IMAGES: - ret = vrend_decode_set_shader_images(gdctx, len); - break; - case VIRGL_CCMD_SET_ATOMIC_BUFFERS: - ret = vrend_decode_set_atomic_buffers(gdctx, len); - break; - case VIRGL_CCMD_MEMORY_BARRIER: - ret = vrend_decode_memory_barrier(gdctx, len); - break; - case VIRGL_CCMD_LAUNCH_GRID: - ret = vrend_decode_launch_grid(gdctx, len); - break; - case VIRGL_CCMD_SET_FRAMEBUFFER_STATE_NO_ATTACH: - ret = vrend_decode_set_framebuffer_state_no_attach(gdctx, len); - break; - case VIRGL_CCMD_TRANSFER3D: - ret = vrend_decode_transfer3d(client, gdctx, len, ctx_id); - break; - case VIRGL_CCMD_COPY_TRANSFER3D: - ret = vrend_decode_copy_transfer3d(gdctx, len); - break; - case VIRGL_CCMD_END_TRANSFERS: - ret = 0; - break; - case VIRGL_CCMD_SET_TWEAKS: - case VIRGL_CCMD_SET_DEBUG_FLAGS: - break; - default: - ret = EINVAL; - } - - if (ret == EINVAL || ret == ENOMEM) - goto out; - gdctx->ds->buf_offset += (len) + 1; - } - return 0; -out: - return ret; -} - -void vrend_decode_reset(struct virgl_client *client, bool ctx_0_only) { - int i; - - vrend_hw_switch_context(client->dec_ctx[0]->grctx, true); - - if (ctx_0_only == false) { - for (i = 1; i < VREND_MAX_CTX; i++) { - if (!client->dec_ctx[i]) - continue; - - if (!client->dec_ctx[i]->grctx) - continue; - - vrend_destroy_context(client->dec_ctx[i]->grctx); - free(client->dec_ctx[i]); - client->dec_ctx[i] = NULL; - } - } else { - vrend_destroy_context(client->dec_ctx[0]->grctx); - free(client->dec_ctx[0]); - client->dec_ctx[0] = NULL; - } -} diff --git a/app/src/main/cpp/virglrenderer/src/vrend_formats.c b/app/src/main/cpp/virglrenderer/src/vrend_formats.c deleted file mode 100644 index 0107c8e9a..000000000 --- a/app/src/main/cpp/virglrenderer/src/vrend_formats.c +++ /dev/null @@ -1,585 +0,0 @@ -/************************************************************************** - * - * Copyright (C) 2014 Red Hat Inc. - * - * 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. - * - **************************************************************************/ -#include "util/u_format.h" -#include "util/u_memory.h" -#include "vrend_renderer.h" -#include "vrend_util.h" - -#define SWIZZLE_INVALID 0xff -#define NO_SWIZZLE \ - {SWIZZLE_INVALID, SWIZZLE_INVALID, SWIZZLE_INVALID, SWIZZLE_INVALID} -#define RRR1_SWIZZLE \ - {PIPE_SWIZZLE_RED, PIPE_SWIZZLE_RED, PIPE_SWIZZLE_RED, PIPE_SWIZZLE_ONE} -#define RGB1_SWIZZLE \ - {PIPE_SWIZZLE_RED, PIPE_SWIZZLE_GREEN, PIPE_SWIZZLE_BLUE, PIPE_SWIZZLE_ONE} -#define ZZZR_SWIZZLE \ - {PIPE_SWIZZLE_ZERO, PIPE_SWIZZLE_ZERO, PIPE_SWIZZLE_ZERO, PIPE_SWIZZLE_RED} - -static struct vrend_format_table base_rgba_formats[] = { - {VIRGL_FORMAT_R8G8B8X8_UNORM, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, - RGB1_SWIZZLE}, - {VIRGL_FORMAT_R8G8B8A8_UNORM, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, - NO_SWIZZLE}, - {VIRGL_FORMAT_B8G8R8X8_UNORM, GL_BGRA_EXT, GL_BGRA_EXT, GL_UNSIGNED_BYTE, - RGB1_SWIZZLE}, - {VIRGL_FORMAT_B8G8R8A8_UNORM, GL_BGRA_EXT, GL_BGRA_EXT, GL_UNSIGNED_BYTE, - NO_SWIZZLE}, - - {VIRGL_FORMAT_A4B4G4R4_UNORM, GL_RGBA4, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, - NO_SWIZZLE}, - {VIRGL_FORMAT_B5G6R5_UNORM, GL_RGB565, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, - NO_SWIZZLE}, - {VIRGL_FORMAT_R16G16B16X16_UNORM, GL_RGBA16UI, GL_RGBA, GL_UNSIGNED_SHORT, - RGB1_SWIZZLE}, - {VIRGL_FORMAT_R16G16B16A16_UNORM, GL_RGBA16UI, GL_RGBA, GL_UNSIGNED_SHORT, - NO_SWIZZLE}, -}; - -static struct vrend_format_table base_depth_formats[] = { - {VIRGL_FORMAT_Z16_UNORM, GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, - GL_UNSIGNED_SHORT, NO_SWIZZLE}, - {VIRGL_FORMAT_Z32_UNORM, GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT, - GL_UNSIGNED_INT, NO_SWIZZLE}, - {VIRGL_FORMAT_S8_UINT_Z24_UNORM, GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, - GL_UNSIGNED_INT_24_8, NO_SWIZZLE}, - {VIRGL_FORMAT_Z24X8_UNORM, GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT, - GL_UNSIGNED_INT, NO_SWIZZLE}, - {VIRGL_FORMAT_Z32_FLOAT, GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, - GL_FLOAT, NO_SWIZZLE}, - {VIRGL_FORMAT_Z32_FLOAT_S8X24_UINT, GL_DEPTH32F_STENCIL8, GL_DEPTH_STENCIL, - GL_FLOAT_32_UNSIGNED_INT_24_8_REV, NO_SWIZZLE}, - {VIRGL_FORMAT_X24S8_UINT, GL_STENCIL_INDEX8, GL_STENCIL_INDEX, - GL_UNSIGNED_BYTE, NO_SWIZZLE}, -}; - -static struct vrend_format_table base_la_formats[] = { - {VIRGL_FORMAT_A8_UNORM, GL_R8, GL_RED, GL_UNSIGNED_BYTE, ZZZR_SWIZZLE}, - {VIRGL_FORMAT_L8_UNORM, GL_R8, GL_RED, GL_UNSIGNED_BYTE, RRR1_SWIZZLE}, - {VIRGL_FORMAT_A16_UNORM, GL_R16_EXT, GL_RED, GL_UNSIGNED_SHORT, - ZZZR_SWIZZLE}, - {VIRGL_FORMAT_L16_UNORM, GL_R16_EXT, GL_RED, GL_UNSIGNED_SHORT, - RRR1_SWIZZLE}, -}; - -static struct vrend_format_table rg_base_formats[] = { - {VIRGL_FORMAT_R8_UNORM, GL_R8, GL_RED, GL_UNSIGNED_BYTE, NO_SWIZZLE}, - {VIRGL_FORMAT_R8G8_UNORM, GL_RG8, GL_RG, GL_UNSIGNED_BYTE, NO_SWIZZLE}, - {VIRGL_FORMAT_R16_UNORM, GL_R16_EXT, GL_RED, GL_UNSIGNED_SHORT, NO_SWIZZLE}, - {VIRGL_FORMAT_R16G16_UNORM, GL_R16_EXT, GL_RG, GL_UNSIGNED_SHORT, - NO_SWIZZLE}, -}; - -static struct vrend_format_table integer_base_formats[] = { - {VIRGL_FORMAT_R8G8B8A8_UINT, GL_RGBA8UI, GL_RGBA_INTEGER, GL_UNSIGNED_BYTE, - NO_SWIZZLE}, - {VIRGL_FORMAT_R8G8B8A8_SINT, GL_RGBA8I, GL_RGBA_INTEGER, GL_BYTE, - NO_SWIZZLE}, - - {VIRGL_FORMAT_R16G16B16A16_UINT, GL_RGBA16UI, GL_RGBA_INTEGER, - GL_UNSIGNED_SHORT, NO_SWIZZLE}, - {VIRGL_FORMAT_R16G16B16A16_SINT, GL_RGBA16I, GL_RGBA_INTEGER, GL_SHORT, - NO_SWIZZLE}, - - {VIRGL_FORMAT_R32G32B32A32_UINT, GL_RGBA32UI, GL_RGBA_INTEGER, - GL_UNSIGNED_INT, NO_SWIZZLE}, - {VIRGL_FORMAT_R32G32B32A32_SINT, GL_RGBA32I, GL_RGBA_INTEGER, GL_INT, - NO_SWIZZLE}, -}; - -static struct vrend_format_table integer_3comp_formats[] = { - {VIRGL_FORMAT_R8G8B8X8_UINT, GL_RGBA8UI, GL_RGBA_INTEGER, GL_UNSIGNED_BYTE, - RGB1_SWIZZLE}, - {VIRGL_FORMAT_R8G8B8X8_SINT, GL_RGBA8I, GL_RGBA_INTEGER, GL_BYTE, - RGB1_SWIZZLE}, - {VIRGL_FORMAT_R16G16B16X16_UINT, GL_RGBA16UI, GL_RGBA_INTEGER, - GL_UNSIGNED_SHORT, RGB1_SWIZZLE}, - {VIRGL_FORMAT_R16G16B16X16_SINT, GL_RGBA16I, GL_RGBA_INTEGER, GL_SHORT, - RGB1_SWIZZLE}, - {VIRGL_FORMAT_R32G32B32_UINT, GL_RGB32UI, GL_RGB_INTEGER, GL_UNSIGNED_INT, - NO_SWIZZLE}, - {VIRGL_FORMAT_R32G32B32_SINT, GL_RGB32I, GL_RGB_INTEGER, GL_INT, - NO_SWIZZLE}, -}; - -static struct vrend_format_table float_base_formats[] = { - {VIRGL_FORMAT_R16G16B16A16_FLOAT, GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT, - NO_SWIZZLE}, - {VIRGL_FORMAT_R32G32B32A32_FLOAT, GL_RGBA32F, GL_RGBA, GL_FLOAT, - NO_SWIZZLE}, -}; - -static struct vrend_format_table float_la_formats[] = { - {VIRGL_FORMAT_L16_FLOAT, GL_R16F, GL_RED, GL_HALF_FLOAT, RRR1_SWIZZLE}, - {VIRGL_FORMAT_L32_FLOAT, GL_R32F, GL_RED, GL_FLOAT, RRR1_SWIZZLE}, -}; - -static struct vrend_format_table integer_rg_formats[] = { - {VIRGL_FORMAT_R8_UINT, GL_R8UI, GL_RED_INTEGER, GL_UNSIGNED_BYTE, - NO_SWIZZLE}, - {VIRGL_FORMAT_R8G8_UINT, GL_RG8UI, GL_RG_INTEGER, GL_UNSIGNED_BYTE, - NO_SWIZZLE}, - {VIRGL_FORMAT_R8_SINT, GL_R8I, GL_RED_INTEGER, GL_BYTE, NO_SWIZZLE}, - {VIRGL_FORMAT_R8G8_SINT, GL_RG8I, GL_RG_INTEGER, GL_BYTE, NO_SWIZZLE}, - - {VIRGL_FORMAT_R16_UINT, GL_R16UI, GL_RED_INTEGER, GL_UNSIGNED_SHORT, - NO_SWIZZLE}, - {VIRGL_FORMAT_R16G16_UINT, GL_RG16UI, GL_RG_INTEGER, GL_UNSIGNED_SHORT, - NO_SWIZZLE}, - {VIRGL_FORMAT_R16_SINT, GL_R16I, GL_RED_INTEGER, GL_SHORT, NO_SWIZZLE}, - {VIRGL_FORMAT_R16G16_SINT, GL_RG16I, GL_RG_INTEGER, GL_SHORT, NO_SWIZZLE}, - - {VIRGL_FORMAT_R32_UINT, GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT, - NO_SWIZZLE}, - {VIRGL_FORMAT_R32G32_UINT, GL_RG32UI, GL_RG_INTEGER, GL_UNSIGNED_INT, - NO_SWIZZLE}, - {VIRGL_FORMAT_R32_SINT, GL_R32I, GL_RED_INTEGER, GL_INT, NO_SWIZZLE}, - {VIRGL_FORMAT_R32G32_SINT, GL_RG32I, GL_RG_INTEGER, GL_INT, NO_SWIZZLE}, -}; - -static struct vrend_format_table float_rg_formats[] = { - {VIRGL_FORMAT_R16_FLOAT, GL_R16F, GL_RED, GL_HALF_FLOAT, NO_SWIZZLE}, - {VIRGL_FORMAT_R16G16_FLOAT, GL_RG16F, GL_RG, GL_HALF_FLOAT, NO_SWIZZLE}, - {VIRGL_FORMAT_R32_FLOAT, GL_R32F, GL_RED, GL_FLOAT, NO_SWIZZLE}, - {VIRGL_FORMAT_R32G32_FLOAT, GL_RG32F, GL_RG, GL_FLOAT, NO_SWIZZLE}, -}; - -static struct vrend_format_table float_3comp_formats[] = { - {VIRGL_FORMAT_R16G16B16X16_FLOAT, GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT, - RGB1_SWIZZLE}, - {VIRGL_FORMAT_R32G32B32_FLOAT, GL_RGB32F, GL_RGB, GL_FLOAT, NO_SWIZZLE}, -}; - -static struct vrend_format_table integer_la_formats[] = { - {VIRGL_FORMAT_L8_UINT, GL_R8UI, GL_RED_INTEGER, GL_UNSIGNED_BYTE, - RRR1_SWIZZLE}, - {VIRGL_FORMAT_L8_SINT, GL_R8I, GL_RED_INTEGER, GL_BYTE, RRR1_SWIZZLE}, - - {VIRGL_FORMAT_L16_UINT, GL_R16UI, GL_RED_INTEGER, GL_UNSIGNED_SHORT, - RRR1_SWIZZLE}, - - {VIRGL_FORMAT_L16_SINT, GL_R16I, GL_RED_INTEGER, GL_SHORT, RRR1_SWIZZLE}, - - {VIRGL_FORMAT_L32_UINT, GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT, - RRR1_SWIZZLE}, - - {VIRGL_FORMAT_L32_SINT, GL_R32I, GL_RED_INTEGER, GL_INT, RRR1_SWIZZLE}, -}; - -static struct vrend_format_table snorm_formats[] = { - {VIRGL_FORMAT_R8_SNORM, GL_R8_SNORM, GL_RED, GL_BYTE, NO_SWIZZLE}, - {VIRGL_FORMAT_R8G8_SNORM, GL_RG8_SNORM, GL_RG, GL_BYTE, NO_SWIZZLE}, - - {VIRGL_FORMAT_R8G8B8A8_SNORM, GL_RGBA8_SNORM, GL_RGBA, GL_BYTE, NO_SWIZZLE}, - {VIRGL_FORMAT_R8G8B8X8_SNORM, GL_RGBA8_SNORM, GL_RGBA, GL_BYTE, - RGB1_SWIZZLE}, -}; - -static struct vrend_format_table snorm_la_formats[] = { - {VIRGL_FORMAT_A8_SNORM, GL_R8_SNORM, GL_ALPHA, GL_BYTE, ZZZR_SWIZZLE}, - {VIRGL_FORMAT_L8_SNORM, GL_R8_SNORM, GL_RED, GL_BYTE, RRR1_SWIZZLE}, -}; - -static struct vrend_format_table srgb_formats[] = { - {VIRGL_FORMAT_R8G8B8X8_SRGB, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, - RGB1_SWIZZLE}, - {VIRGL_FORMAT_R8G8B8A8_SRGB, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, - NO_SWIZZLE}, - - {VIRGL_FORMAT_L8_SRGB, GL_RED, GL_RED, GL_UNSIGNED_BYTE, RRR1_SWIZZLE}, - {VIRGL_FORMAT_R8_SRGB, GL_RED, GL_RED, GL_UNSIGNED_BYTE, NO_SWIZZLE}, -}; - -static struct vrend_format_table bit10_formats[] = { - {VIRGL_FORMAT_B10G10R10X2_UNORM, GL_RGB10_A2, GL_RGBA, - GL_UNSIGNED_INT_2_10_10_10_REV, RGB1_SWIZZLE}, - {VIRGL_FORMAT_B10G10R10A2_UNORM, GL_RGB10_A2, GL_RGBA, - GL_UNSIGNED_INT_2_10_10_10_REV, NO_SWIZZLE}, - {VIRGL_FORMAT_R10G10B10X2_UNORM, GL_RGB10_A2, GL_RGBA, - GL_UNSIGNED_INT_2_10_10_10_REV, RGB1_SWIZZLE}, - {VIRGL_FORMAT_R10G10B10A2_UNORM, GL_RGB10_A2, GL_RGBA, - GL_UNSIGNED_INT_2_10_10_10_REV, NO_SWIZZLE}, - {VIRGL_FORMAT_R10G10B10A2_UINT, GL_RGB10_A2UI, GL_RGBA_INTEGER, - GL_UNSIGNED_INT_2_10_10_10_REV, NO_SWIZZLE}, -}; - -static struct vrend_format_table packed_float_formats[] = { - {VIRGL_FORMAT_R11G11B10_FLOAT, GL_R11F_G11F_B10F, GL_RGB, - GL_UNSIGNED_INT_10F_11F_11F_REV, NO_SWIZZLE}, -}; - -static struct vrend_format_table exponent_float_formats[] = { - {VIRGL_FORMAT_R9G9B9E5_FLOAT, GL_RGB9_E5, GL_RGB, - GL_UNSIGNED_INT_5_9_9_9_REV, NO_SWIZZLE}, -}; - -static bool color_format_can_readback(struct vrend_format_table *virgl_format, - int gles_ver) { - GLint imp = 0; - - if (virgl_format->format == VIRGL_FORMAT_R8G8B8A8_UNORM) - return true; - - if (gles_ver >= 30 && - (virgl_format->format == VIRGL_FORMAT_R32G32B32A32_SINT || - virgl_format->format == VIRGL_FORMAT_R32G32B32A32_UINT)) - return true; - - if ((virgl_format->format == VIRGL_FORMAT_R32G32B32A32_FLOAT) && - (gles_ver >= 32 || vrend_has_gl_extension("GL_EXT_color_buffer_float"))) - return true; - - /* Hotfix for the CI, on GLES these formats are defined like - * VIRGL_FORMAT_R10G10B10.2_UNORM, and seems to be incorrect for direct - * readback but the blit workaround seems to work, so disable the - * direct readback for these two formats. */ - if (virgl_format->format == VIRGL_FORMAT_B10G10R10A2_UNORM || - virgl_format->format == VIRGL_FORMAT_B10G10R10X2_UNORM) - return false; - - /* Check implementation specific readback formats */ - glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, &imp); - if (imp == (GLint)virgl_format->gltype) { - glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &imp); - if (imp == (GLint)virgl_format->glformat) - return true; - } - return false; -} - -static void vrend_add_formats(struct vrend_format_table *table, - int num_entries) { - int i; - const int gles_ver = vrend_gl_version(); - - for (i = 0; i < num_entries; i++) { - GLenum status; - bool is_depth = false; - uint32_t flags = 0; - uint32_t binding = 0; - GLuint buffers; - GLuint tex_id, fb_id; - - glGenTextures(1, &tex_id); - glGenFramebuffers(1, &fb_id); - - glBindTexture(GL_TEXTURE_2D, tex_id); - glBindFramebuffer(GL_FRAMEBUFFER, fb_id); - - glTexImage2D(GL_TEXTURE_2D, 0, table[i].internalformat, 32, 32, 0, - table[i].glformat, table[i].gltype, NULL); - status = glGetError(); - if (status != GL_NO_ERROR) { - glDeleteTextures(1, &tex_id); - glDeleteFramebuffers(1, &fb_id); - continue; - } - - if (table[i].format < VIRGL_FORMAT_MAX && - util_format_is_depth_or_stencil(table[i].format)) { - GLenum attachment; - - if (table[i].format == VIRGL_FORMAT_Z24X8_UNORM || - table[i].format == VIRGL_FORMAT_Z32_UNORM || - table[i].format == VIRGL_FORMAT_Z16_UNORM || - table[i].format == VIRGL_FORMAT_Z32_FLOAT) - attachment = GL_DEPTH_ATTACHMENT; - else - attachment = GL_DEPTH_STENCIL_ATTACHMENT; - glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, GL_TEXTURE_2D, tex_id, - 0); - - is_depth = true; - - buffers = GL_NONE; - glDrawBuffers(1, &buffers); - } else { - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, - GL_TEXTURE_2D, tex_id, 0); - - buffers = GL_COLOR_ATTACHMENT0; - glDrawBuffers(1, &buffers); - } - - status = glCheckFramebufferStatus(GL_FRAMEBUFFER); - binding = VIRGL_BIND_SAMPLER_VIEW; - if (status == GL_FRAMEBUFFER_COMPLETE) { - binding |= is_depth ? VIRGL_BIND_DEPTH_STENCIL : VIRGL_BIND_RENDER_TARGET; - - if (color_format_can_readback(&table[i], gles_ver)) - flags |= VIRGL_TEXTURE_CAN_READBACK; - } - - glDeleteTextures(1, &tex_id); - glDeleteFramebuffers(1, &fb_id); - - if (table[i].swizzle[0] != SWIZZLE_INVALID) - vrend_insert_format_swizzle(table[i].format, &table[i], binding, - table[i].swizzle, flags); - else - vrend_insert_format(&table[i], binding, flags); - } -} - -#define add_formats(x) vrend_add_formats((x), ARRAY_SIZE((x))) - -void vrend_build_format_list(void) { - add_formats(base_rgba_formats); - add_formats(base_depth_formats); - add_formats(base_la_formats); - - /* float support */ - add_formats(float_base_formats); - add_formats(float_la_formats); - add_formats(float_3comp_formats); - - /* texture integer support ? */ - add_formats(integer_base_formats); - add_formats(integer_la_formats); - add_formats(integer_3comp_formats); - - /* RG support? */ - add_formats(rg_base_formats); - /* integer + rg */ - add_formats(integer_rg_formats); - /* float + rg */ - add_formats(float_rg_formats); - - /* snorm */ - add_formats(snorm_formats); - add_formats(snorm_la_formats); - - add_formats(srgb_formats); - - add_formats(bit10_formats); - - add_formats(packed_float_formats); - add_formats(exponent_float_formats); -} - -/* glTexStorage may not support all that is supported by glTexImage, - * so add a flag to indicate when it can be used. - */ -void vrend_check_texture_storage(struct vrend_format_table *table) { - int i; - GLuint tex_id; - for (i = 0; i < VIRGL_FORMAT_MAX_EXTENDED; i++) { - - if (table[i].internalformat != 0 && - !(table[i].flags & VIRGL_TEXTURE_CAN_TEXTURE_STORAGE)) { - glGenTextures(1, &tex_id); - glBindTexture(GL_TEXTURE_2D, tex_id); - glTexStorage2D(GL_TEXTURE_2D, 1, table[i].internalformat, 32, 32); - if (glGetError() == GL_NO_ERROR) - table[i].flags |= VIRGL_TEXTURE_CAN_TEXTURE_STORAGE; - glDeleteTextures(1, &tex_id); - } - } -} - -bool vrend_check_framebuffer_mixed_color_attachements() { - GLuint tex_id[2]; - GLuint fb_id; - bool retval = false; - - glGenTextures(2, tex_id); - glGenFramebuffers(1, &fb_id); - - glBindTexture(GL_TEXTURE_2D, tex_id[0]); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 32, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, - NULL); - - glBindFramebuffer(GL_FRAMEBUFFER, fb_id); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, - tex_id[0], 0); - - glBindTexture(GL_TEXTURE_2D, tex_id[1]); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, 32, 32, 0, GL_RED, GL_UNSIGNED_BYTE, - NULL); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, - tex_id[1], 0); - - retval = glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE; - - glDeleteFramebuffers(1, &fb_id); - glDeleteTextures(2, tex_id); - - return retval; -} - -unsigned vrend_renderer_query_multisample_caps(unsigned max_samples, - struct virgl_caps_v2 *caps) { - GLuint tex; - GLuint fbo; - GLenum status; - - uint max_samples_confirmed = 1; - uint test_num_samples[4] = {2, 4, 8, 16}; - int out_buf_offsets[4] = {0, 1, 2, 4}; - int lowest_working_ms_count_idx = -1; - - assert(glGetError() == GL_NO_ERROR && "Stale error state detected, please " - "check for failures in initialization"); - - glGenFramebuffers(1, &fbo); - memset(caps->sample_locations, 0, 8 * sizeof(uint32_t)); - - for (int i = 3; i >= 0; i--) { - if (test_num_samples[i] > max_samples) - continue; - glGenTextures(1, &tex); - glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, tex); - glTexStorage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, test_num_samples[i], - GL_RGBA32F, 64, 64, GL_TRUE); - status = glGetError(); - if (status == GL_NO_ERROR) { - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, - GL_TEXTURE_2D_MULTISAMPLE, tex, 0); - status = glCheckFramebufferStatus(GL_FRAMEBUFFER); - if (status == GL_FRAMEBUFFER_COMPLETE) { - if (max_samples_confirmed < test_num_samples[i]) - max_samples_confirmed = test_num_samples[i]; - - for (uint k = 0; k < test_num_samples[i]; ++k) { - float msp[2]; - uint32_t compressed; - glGetMultisamplefv(GL_SAMPLE_POSITION, k, msp); - compressed = ((unsigned)(floor(msp[0] * 16.0f)) & 0xf) << 4; - compressed |= ((unsigned)(floor(msp[1] * 16.0f)) & 0xf); - caps->sample_locations[out_buf_offsets[i] + (k >> 2)] |= - compressed << (8 * (k & 3)); - } - lowest_working_ms_count_idx = i; - } else { - /* If a framebuffer doesn't support low sample counts, - * use the sample position from the last working larger count. */ - if (lowest_working_ms_count_idx > 0) { - for (uint k = 0; k < test_num_samples[i]; ++k) { - caps->sample_locations[out_buf_offsets[i] + (k >> 2)] = - caps->sample_locations - [out_buf_offsets[lowest_working_ms_count_idx] + (k >> 2)]; - } - } - } - glBindFramebuffer(GL_FRAMEBUFFER, 0); - } - glDeleteTextures(1, &tex); - } - glDeleteFramebuffers(1, &fbo); - return max_samples_confirmed; -} - -/* returns: 1 = compatible, -1 = not compatible, 0 = undecided */ -static int -format_uncompressed_compressed_copy_compatible(enum virgl_formats src, - enum virgl_formats dst) { - switch (src) { - case VIRGL_FORMAT_R32G32B32A32_UINT: - case VIRGL_FORMAT_R32G32B32A32_SINT: - case VIRGL_FORMAT_R32G32B32A32_FLOAT: - case VIRGL_FORMAT_R32G32B32A32_SNORM: - case VIRGL_FORMAT_R32G32B32A32_UNORM: - switch (dst) { - case VIRGL_FORMAT_DXT3_RGBA: - case VIRGL_FORMAT_DXT3_SRGBA: - case VIRGL_FORMAT_DXT5_RGBA: - case VIRGL_FORMAT_DXT5_SRGBA: - case VIRGL_FORMAT_RGTC2_UNORM: - case VIRGL_FORMAT_RGTC2_SNORM: - case VIRGL_FORMAT_BPTC_RGBA_UNORM: - case VIRGL_FORMAT_BPTC_SRGBA: - case VIRGL_FORMAT_BPTC_RGB_FLOAT: - case VIRGL_FORMAT_BPTC_RGB_UFLOAT: - return 1; - default: - return -1; - } - case VIRGL_FORMAT_R16G16B16A16_UINT: - case VIRGL_FORMAT_R16G16B16A16_SINT: - case VIRGL_FORMAT_R16G16B16A16_FLOAT: - case VIRGL_FORMAT_R16G16B16A16_SNORM: - case VIRGL_FORMAT_R16G16B16A16_UNORM: - case VIRGL_FORMAT_R32G32_UINT: - case VIRGL_FORMAT_R32G32_SINT: - case VIRGL_FORMAT_R32G32_FLOAT: - case VIRGL_FORMAT_R32G32_UNORM: - case VIRGL_FORMAT_R32G32_SNORM: - switch (dst) { - case VIRGL_FORMAT_DXT1_RGBA: - case VIRGL_FORMAT_DXT1_SRGBA: - case VIRGL_FORMAT_DXT1_RGB: - case VIRGL_FORMAT_DXT1_SRGB: - case VIRGL_FORMAT_RGTC1_UNORM: - case VIRGL_FORMAT_RGTC1_SNORM: - return 1; - default: - return -1; - } - default: - return 0; - } -} - -static boolean -format_compressed_compressed_copy_compatible(enum virgl_formats src, - enum virgl_formats dst) { - if ((src == VIRGL_FORMAT_RGTC1_UNORM && dst == VIRGL_FORMAT_RGTC1_SNORM) || - (src == VIRGL_FORMAT_RGTC2_UNORM && dst == VIRGL_FORMAT_RGTC2_SNORM) || - (src == VIRGL_FORMAT_BPTC_RGBA_UNORM && dst == VIRGL_FORMAT_BPTC_SRGBA) || - (src == VIRGL_FORMAT_BPTC_RGB_FLOAT && - dst == VIRGL_FORMAT_BPTC_RGB_UFLOAT)) - return true; - return false; -} - -boolean format_is_copy_compatible(enum virgl_formats src, - enum virgl_formats dst, - boolean allow_compressed) { - int r; - - if (src == dst) - return true; - - if (util_format_is_plain(src) && util_format_is_plain(dst)) { - const struct util_format_description *src_desc = - util_format_description(src); - const struct util_format_description *dst_desc = - util_format_description(dst); - return util_is_format_compatible(src_desc, dst_desc); - } - - if (!allow_compressed) - return false; - - /* compressed-uncompressed */ - r = format_uncompressed_compressed_copy_compatible(src, dst); - if (r) - return r > 0; - - r = format_uncompressed_compressed_copy_compatible(dst, src); - if (r) - return r > 0; - - return format_compressed_compressed_copy_compatible(dst, src); -} diff --git a/app/src/main/cpp/virglrenderer/src/vrend_iov.h b/app/src/main/cpp/virglrenderer/src/vrend_iov.h deleted file mode 100644 index 542d71cea..000000000 --- a/app/src/main/cpp/virglrenderer/src/vrend_iov.h +++ /dev/null @@ -1,62 +0,0 @@ -/************************************************************************** - * - * Copyright (C) 2014 Red Hat Inc. - * - * 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. - * - **************************************************************************/ -#ifndef VREND_IOV_H -#define VREND_IOV_H - -#include -#include -#include - -struct vrend_transfer_info { - uint32_t handle; - uint32_t ctx_id; - int level; - uint32_t stride; - uint32_t layer_stride; - unsigned int iovec_cnt; - struct iovec *iovec; - uint64_t offset; - bool context0; - struct pipe_box *box; - bool synchronized; -}; - -typedef void (*iov_cb)(void *cookie, unsigned int doff, void *src, int len); - -size_t vrend_get_iovec_size(const struct iovec *iov, int iovlen); -size_t vrend_read_from_iovec(const struct iovec *iov, int iov_cnt, - size_t offset, char *buf, size_t bytes); -size_t vrend_write_to_iovec(const struct iovec *iov, int iov_cnt, size_t offset, - const char *buf, size_t bytes); - -size_t vrend_read_from_iovec_cb(const struct iovec *iov, int iov_cnt, - size_t offset, size_t bytes, iov_cb iocb, - void *cookie); - -int vrend_copy_iovec(const struct iovec *src_iov, int src_iovlen, - size_t src_offset, const struct iovec *dst_iov, - int dst_iovlen, size_t dst_offset, size_t count, - char *buf); - -#endif diff --git a/app/src/main/cpp/virglrenderer/src/vrend_object.c b/app/src/main/cpp/virglrenderer/src/vrend_object.c deleted file mode 100644 index 3d424d8f7..000000000 --- a/app/src/main/cpp/virglrenderer/src/vrend_object.c +++ /dev/null @@ -1,182 +0,0 @@ -/************************************************************************** - * - * Copyright (C) 2014 Red Hat Inc. - * - * 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. - * - **************************************************************************/ - -#include "util/u_hash_table.h" -#include "util/u_memory.h" -#include "util/u_pointer.h" - -#include "vrend_object.h" - -struct vrend_object_types { - void (*unref)(void *); -} obj_types[VIRGL_MAX_OBJECTS]; - -static void (*resource_unref)(void *); - -void vrend_object_set_destroy_callback(int type, void (*cb)(void *)) { - obj_types[type].unref = cb; -} - -void vrend_resource_set_destroy_callback(void (*cb)(void *)) { - resource_unref = cb; -} - -static unsigned hash_func(void *key) { - intptr_t ip = pointer_to_intptr(key); - return (unsigned)(ip & 0xffffffff); -} - -static int compare(void *key1, void *key2) { - if (key1 < key2) - return -1; - if (key1 > key2) - return 1; - else - return 0; -} - -struct vrend_object { - enum virgl_object_type type; - uint32_t handle; - void *data; - bool free_data; -}; - -static void free_object(void *value) { - struct vrend_object *obj = value; - - if (obj->free_data) { - if (obj_types[obj->type].unref) - obj_types[obj->type].unref(obj->data); - else { - /* for objects with no callback just free them */ - free(obj->data); - } - } - free(obj); -} - -struct util_hash_table *vrend_object_init_ctx_table(void) { - struct util_hash_table *ctx_hash; - ctx_hash = util_hash_table_create(hash_func, compare, free_object); - return ctx_hash; -} - -void vrend_object_fini_ctx_table(struct util_hash_table *ctx_hash) { - if (!ctx_hash) - return; - - util_hash_table_destroy(ctx_hash); -} - -static void free_res(void *value) { - struct vrend_object *obj = value; - (*resource_unref)(obj->data); - free(obj); -} - -void vrend_object_init_resource_table(struct virgl_client *client) { - if (!client->res_hash) - client->res_hash = util_hash_table_create(hash_func, compare, free_res); -} - -void vrend_object_fini_resource_table(struct virgl_client *client) { - if (client->res_hash) - util_hash_table_destroy(client->res_hash); - - client->res_hash = NULL; -} - -uint32_t vrend_object_insert_nofree(struct util_hash_table *handle_hash, - void *data, UNUSED uint32_t length, - uint32_t handle, - enum virgl_object_type type, - bool free_data) { - struct vrend_object *obj = CALLOC_STRUCT(vrend_object); - - if (!obj) - return 0; - obj->handle = handle; - obj->data = data; - obj->type = type; - obj->free_data = free_data; - util_hash_table_set(handle_hash, intptr_to_pointer(obj->handle), obj); - return obj->handle; -} - -uint32_t vrend_object_insert(struct util_hash_table *handle_hash, void *data, - uint32_t length, uint32_t handle, - enum virgl_object_type type) { - return vrend_object_insert_nofree(handle_hash, data, length, handle, type, - true); -} - -void vrend_object_remove(struct util_hash_table *handle_hash, uint32_t handle, - UNUSED enum virgl_object_type type) { - util_hash_table_remove(handle_hash, intptr_to_pointer(handle)); -} - -void *vrend_object_lookup(struct util_hash_table *handle_hash, uint32_t handle, - enum virgl_object_type type) { - struct vrend_object *obj; - - obj = util_hash_table_get(handle_hash, intptr_to_pointer(handle)); - if (!obj) { - return NULL; - } - - if (obj->type != type) - return NULL; - return obj->data; -} - -int vrend_resource_insert(struct virgl_client *client, void *data, - uint32_t handle) { - struct vrend_object *obj; - - if (!handle) - return 0; - - obj = CALLOC_STRUCT(vrend_object); - if (!obj) - return 0; - - obj->handle = handle; - obj->data = data; - util_hash_table_set(client->res_hash, intptr_to_pointer(obj->handle), obj); - return obj->handle; -} - -void vrend_resource_remove(struct virgl_client *client, uint32_t handle) { - util_hash_table_remove(client->res_hash, intptr_to_pointer(handle)); -} - -void *vrend_resource_lookup(struct virgl_client *client, uint32_t handle, - UNUSED uint32_t ctx_id) { - struct vrend_object *obj; - obj = util_hash_table_get(client->res_hash, intptr_to_pointer(handle)); - if (!obj) - return NULL; - return obj->data; -} diff --git a/app/src/main/cpp/virglrenderer/src/vrend_object.h b/app/src/main/cpp/virglrenderer/src/vrend_object.h deleted file mode 100644 index 7dacf76a8..000000000 --- a/app/src/main/cpp/virglrenderer/src/vrend_object.h +++ /dev/null @@ -1,59 +0,0 @@ -/************************************************************************** - * - * Copyright (C) 2014 Red Hat Inc. - * - * 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. - * - **************************************************************************/ - -#ifndef VREND_OBJECT_H -#define VREND_OBJECT_H - -#include "virgl_protocol.h" -#include "virgl_server.h" - -void vrend_object_init_resource_table(struct virgl_client *client); -void vrend_object_fini_resource_table(struct virgl_client *client); - -struct util_hash_table *vrend_object_init_ctx_table(void); -void vrend_object_fini_ctx_table(struct util_hash_table *ctx_hash); - -void vrend_object_remove(struct util_hash_table *handle_hash, uint32_t handle, - enum virgl_object_type obj); -void *vrend_object_lookup(struct util_hash_table *handle_hash, uint32_t handle, - enum virgl_object_type obj); -uint32_t vrend_object_insert(struct util_hash_table *handle_hash, void *data, - uint32_t length, uint32_t handle, - enum virgl_object_type type); -uint32_t vrend_object_insert_nofree(struct util_hash_table *handle_hash, - void *data, uint32_t length, - uint32_t handle, - enum virgl_object_type type, - bool free_data); -/* resources are global */ -int vrend_resource_insert(struct virgl_client *client, void *data, - uint32_t handle); - -void vrend_resource_remove(struct virgl_client *client, uint32_t handle); -void *vrend_resource_lookup(struct virgl_client *client, uint32_t handle, - uint32_t ctx_id); - -void vrend_object_set_destroy_callback(int type, void (*cb)(void *)); -void vrend_resource_set_destroy_callback(void (*cb)(void *)); -#endif diff --git a/app/src/main/cpp/virglrenderer/src/vrend_renderer.c b/app/src/main/cpp/virglrenderer/src/vrend_renderer.c deleted file mode 100644 index bd6b1deba..000000000 --- a/app/src/main/cpp/virglrenderer/src/vrend_renderer.c +++ /dev/null @@ -1,7474 +0,0 @@ -/************************************************************************** - * - * Copyright (C) 2014 Red Hat Inc. - * - * 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. - * - **************************************************************************/ - -#include "pipe/p_shader_tokens.h" -#include -#include -#include - -#include "pipe/p_context.h" -#include "pipe/p_defines.h" -#include "pipe/p_screen.h" -#include "pipe/p_state.h" -#include "util/u_dual_blend.h" -#include "util/u_inlines.h" -#include "util/u_memory.h" - -#include "tgsi/tgsi_parse.h" -#include "util/u_format.h" - -#include "vrend_object.h" -#include "vrend_shader.h" - -#include "vrend_renderer.h" - -#include "vrend_util.h" - -#include "virgl_hw.h" - -#include "tgsi/tgsi_text.h" - -#include - -static const uint32_t fake_occlusion_query_samples_passed_default = 1024; - -struct vrend_if_cbs *vrend_clicbs; - -struct vrend_fence { - uint32_t fence_id; - uint32_t ctx_id; - GLsync syncobj; - struct list_head fences; -}; - -struct vrend_query { - struct list_head waiting_queries; - - GLuint id; - GLuint type; - GLuint index; - GLuint gltype; - int ctx_id; - struct vrend_resource *res; - bool fake_samples_passed; -}; - -enum features_id { - feat_arb_or_gles_ext_texture_buffer, - feat_atomic_counters, - feat_barrier, - feat_clip_control, - feat_compute_shader, - feat_copy_image, - feat_cube_map_array, - feat_draw_instance, - feat_fb_no_attach, - feat_framebuffer_fetch, - feat_geometry_shader, - feat_gles31_compatibility, - feat_gles31_vertex_attrib_binding, - feat_gpu_shader5, - feat_images, - feat_indep_blend, - feat_indep_blend_func, - feat_indirect_draw, - feat_multisample, - feat_occlusion_query_boolean, - feat_robust_buffer_access, - feat_sample_mask, - feat_sample_shading, - feat_samplers, - feat_separate_shader_objects, - feat_ssbo, - feat_ssbo_barrier, - feat_srgb_write_control, - feat_stencil_texturing, - feat_storage_multisample, - feat_tessellation, - feat_texture_array, - feat_texture_buffer_range, - feat_texture_gather, - feat_texture_multisample, - feat_texture_srgb_decode, - feat_texture_storage, - feat_transform_feedback, - feat_transform_feedback2, - feat_ubo, - feat_last, -}; - -#define FEAT_MAX_EXTS 4 -#define UNAVAIL INT_MAX - -#define FEAT(NAME, GLVER, GLESVER, ...) \ - [feat_##NAME] = {GLVER, GLESVER, {__VA_ARGS__}, #NAME} - -static const struct { - int gl_ver; - int gles_ver; - const char *gl_ext[FEAT_MAX_EXTS]; - const char *log_name; -} feature_list[] = { - FEAT(arb_or_gles_ext_texture_buffer, 31, UNAVAIL, - "GL_ARB_texture_buffer_object", "GL_EXT_texture_buffer", NULL), - FEAT(atomic_counters, 42, 31, "GL_ARB_shader_atomic_counters"), - FEAT(barrier, 42, 31, NULL), - FEAT(clip_control, 45, UNAVAIL, "GL_ARB_clip_control", - "GL_EXT_clip_control"), - FEAT(compute_shader, 43, 31, "GL_ARB_compute_shader"), - FEAT(copy_image, 43, 32, "GL_ARB_copy_image", "GL_EXT_copy_image", - "GL_OES_copy_image"), - FEAT(cube_map_array, 40, 32, "GL_ARB_texture_cube_map_array", - "GL_EXT_texture_cube_map_array", "GL_OES_texture_cube_map_array"), - FEAT(draw_instance, 31, 30, "GL_ARB_draw_instanced"), - FEAT(fb_no_attach, 43, 31, "GL_ARB_framebuffer_no_attachments"), - FEAT(framebuffer_fetch, UNAVAIL, UNAVAIL, - "GL_EXT_shader_framebuffer_fetch"), - FEAT(geometry_shader, 32, 32, "GL_EXT_geometry_shader", - "GL_OES_geometry_shader"), - FEAT(gles31_compatibility, 45, 31, "ARB_ES3_1_compatibility"), - FEAT(gles31_vertex_attrib_binding, 43, 31, "GL_ARB_vertex_attrib_binding"), - FEAT(gpu_shader5, 40, 32, "GL_ARB_gpu_shader5", "GL_EXT_gpu_shader5", - "GL_OES_gpu_shader5"), - FEAT(images, 42, 31, "GL_ARB_shader_image_load_store"), - FEAT(indep_blend, 30, 32, "GL_EXT_draw_buffers2", - "GL_OES_draw_buffers_indexed"), - FEAT(indep_blend_func, 40, 32, "GL_ARB_draw_buffers_blend", - "GL_OES_draw_buffers_indexed"), - FEAT(indirect_draw, 40, 31, "GL_ARB_draw_indirect"), - FEAT(multisample, 32, 30, "GL_ARB_texture_multisample"), - FEAT(occlusion_query_boolean, 33, 30, "GL_EXT_occlusion_query_boolean", - "GL_ARB_occlusion_query2"), - FEAT(robust_buffer_access, 43, UNAVAIL, - "GL_ARB_robust_buffer_access_behavior", - "GL_KHR_robust_buffer_access_behavior"), - FEAT(sample_mask, 32, 31, "GL_ARB_texture_multisample"), - FEAT(sample_shading, 40, 32, "GL_ARB_sample_shading", - "GL_OES_sample_shading"), - FEAT(samplers, 33, 30, "GL_ARB_sampler_objects"), - FEAT(separate_shader_objects, 41, 31, "GL_ARB_seperate_shader_objects"), - FEAT(ssbo, 43, 31, "GL_ARB_shader_storage_buffer_object"), - FEAT(ssbo_barrier, 43, 31, NULL), - FEAT(srgb_write_control, 30, UNAVAIL, "GL_EXT_sRGB_write_control"), - FEAT(stencil_texturing, 43, 31, "GL_ARB_stencil_texturing"), - FEAT(storage_multisample, 43, 31, "GL_ARB_texture_storage_multisample"), - FEAT(tessellation, 40, 32, "GL_ARB_tessellation_shader", - "GL_OES_tessellation_shader", "GL_EXT_tessellation_shader"), - FEAT(texture_array, 30, 30, "GL_EXT_texture_array"), - FEAT(texture_buffer_range, 43, 32, "GL_ARB_texture_buffer_range"), - FEAT(texture_gather, 40, 31, "GL_ARB_texture_gather"), - FEAT(texture_multisample, 32, 30, "GL_ARB_texture_multisample"), - FEAT(texture_srgb_decode, UNAVAIL, UNAVAIL, "GL_EXT_texture_sRGB_decode"), - FEAT(texture_storage, 42, 30, "GL_ARB_texture_storage"), - FEAT(transform_feedback, 30, 30, "GL_EXT_transform_feedback"), - FEAT(transform_feedback2, 40, 30, "GL_ARB_transform_feedback2"), - FEAT(ubo, 31, 30, "GL_ARB_uniform_buffer_object"), -}; - -static bool features[feat_last]; -static bool features_initialized = false; - -static inline bool has_feature(enum features_id feature_id) { - return features[feature_id]; -} - -static inline void set_feature(enum features_id feature_id) { - features[feature_id] = true; -} - -struct vrend_linked_shader_program { - struct list_head head; - struct list_head sl[PIPE_SHADER_TYPES]; - GLuint id; - - bool dual_src_linked; - struct vrend_shader *ss[PIPE_SHADER_TYPES]; - - uint32_t ubo_used_mask[PIPE_SHADER_TYPES]; - uint32_t samplers_used_mask[PIPE_SHADER_TYPES]; - - GLuint *shadow_samp_mask_locs[PIPE_SHADER_TYPES]; - GLuint *shadow_samp_add_locs[PIPE_SHADER_TYPES]; - - GLint const_location[PIPE_SHADER_TYPES]; - - GLuint *attrib_locs; - uint32_t shadow_samp_mask[PIPE_SHADER_TYPES]; - - GLuint vs_ws_adjust_loc; - float viewport_neg_val; - - GLint fs_stipple_loc; - - GLuint clip_locs[8]; - - uint32_t images_used_mask[PIPE_SHADER_TYPES]; - GLint *img_locs[PIPE_SHADER_TYPES]; - - GLuint *ssbo_locs[PIPE_SHADER_TYPES]; - - struct vrend_sub_context *ref_context; -}; - -struct vrend_shader { - struct vrend_shader *next_variant; - struct vrend_shader_selector *sel; - - struct vrend_strarray glsl_strings; - GLuint id; - GLuint compiled_fs_id; - struct vrend_shader_key key; - struct list_head programs; -}; - -struct vrend_shader_selector { - struct pipe_reference reference; - - unsigned num_shaders; - unsigned type; - struct vrend_shader_info sinfo; - - struct vrend_shader *current; - struct tgsi_token *tokens; - - uint32_t req_local_mem; - char *tmp_buf; - uint32_t buf_len; - uint32_t buf_offset; -}; - -struct vrend_texture { - struct vrend_resource base; - struct pipe_sampler_state state; - GLenum cur_swizzle_r; - GLenum cur_swizzle_g; - GLenum cur_swizzle_b; - GLenum cur_swizzle_a; - GLuint cur_srgb_decode; - GLuint cur_base, cur_max; -}; - -struct vrend_surface { - struct pipe_reference reference; - GLuint id; - GLuint res_handle; - GLuint format; - GLuint val0, val1; - struct vrend_resource *texture; -}; - -struct vrend_sampler_state { - struct pipe_sampler_state base; - GLuint ids[2]; -}; - -struct vrend_so_target { - struct pipe_reference reference; - GLuint res_handle; - unsigned buffer_offset; - unsigned buffer_size; - struct vrend_resource *buffer; - struct vrend_sub_context *sub_ctx; -}; - -struct vrend_sampler_view { - struct pipe_reference reference; - GLuint id; - enum virgl_formats format; - GLenum target; - GLuint val0, val1; - GLuint gl_swizzle_r; - GLuint gl_swizzle_g; - GLuint gl_swizzle_b; - GLuint gl_swizzle_a; - GLuint srgb_decode; - struct vrend_resource *texture; -}; - -struct vrend_image_view { - GLuint id; - GLenum access; - GLenum format; - union { - struct { - unsigned first_layer : 16; /**< first layer to use for array textures */ - unsigned last_layer : 16; /**< last layer to use for array textures */ - unsigned level : 8; /**< mipmap level to use */ - } tex; - struct { - unsigned offset; /**< offset in bytes */ - unsigned size; /**< size of the accessible sub-range in bytes */ - } buf; - } u; - struct vrend_resource *texture; -}; - -struct vrend_ssbo { - struct vrend_resource *res; - unsigned buffer_size; - unsigned buffer_offset; -}; - -struct vrend_abo { - struct vrend_resource *res; - unsigned buffer_size; - unsigned buffer_offset; -}; - -struct vrend_vertex_element { - struct pipe_vertex_element base; - GLenum type; - GLboolean norm; - GLuint nr_chan; -}; - -struct vrend_vertex_element_array { - unsigned count; - struct vrend_vertex_element elements[PIPE_MAX_ATTRIBS]; - GLuint id; -}; - -struct vrend_constants { - unsigned int *consts; - uint32_t num_consts; - uint32_t num_allocated_consts; -}; - -struct vrend_shader_view { - int num_views; - struct vrend_sampler_view *views[PIPE_MAX_SHADER_SAMPLER_VIEWS]; - uint32_t res_id[PIPE_MAX_SHADER_SAMPLER_VIEWS]; - uint32_t old_ids[PIPE_MAX_SHADER_SAMPLER_VIEWS]; -}; - -struct vrend_viewport { - GLint cur_x, cur_y; - GLsizei width, height; - GLfloat near_val, far_val; -}; - -/* create a streamout object to support pause/resume */ -struct vrend_streamout_object { - GLuint id; - uint32_t num_targets; - uint32_t handles[16]; - struct list_head head; - int xfb_state; - struct vrend_so_target *so_targets[16]; -}; - -#define XFB_STATE_OFF 0 -#define XFB_STATE_STARTED_NEED_BEGIN 1 -#define XFB_STATE_STARTED 2 -#define XFB_STATE_PAUSED 3 - -struct vrend_sub_context { - struct list_head head; - - virgl_gl_context gl_context; - - int sub_ctx_id; - - GLuint vaoid; - uint32_t enabled_attribs_bitmask; - - struct list_head programs; - struct util_hash_table *object_hash; - - struct vrend_vertex_element_array *ve; - int num_vbos; - int old_num_vbos; /* for cleaning up */ - struct pipe_vertex_buffer vbo[PIPE_MAX_ATTRIBS]; - uint32_t vbo_res_ids[PIPE_MAX_ATTRIBS]; - - struct pipe_index_buffer ib; - uint32_t index_buffer_res_id; - - bool vbo_dirty; - bool shader_dirty; - bool cs_shader_dirty; - bool stencil_state_dirty; - bool image_state_dirty; - bool blend_state_dirty; - - uint32_t long_shader_in_progress_handle[PIPE_SHADER_TYPES]; - struct vrend_shader_selector *shaders[PIPE_SHADER_TYPES]; - struct vrend_linked_shader_program *prog; - - int prog_ids[PIPE_SHADER_TYPES]; - struct vrend_shader_view views[PIPE_SHADER_TYPES]; - - struct vrend_constants consts[PIPE_SHADER_TYPES]; - bool const_dirty[PIPE_SHADER_TYPES]; - struct vrend_sampler_state - *sampler_state[PIPE_SHADER_TYPES][PIPE_MAX_SAMPLERS]; - - struct pipe_constant_buffer cbs[PIPE_SHADER_TYPES][PIPE_MAX_CONSTANT_BUFFERS]; - uint32_t const_bufs_used_mask[PIPE_SHADER_TYPES]; - uint32_t const_bufs_dirty[PIPE_SHADER_TYPES]; - - int num_sampler_states[PIPE_SHADER_TYPES]; - - uint32_t sampler_views_dirty[PIPE_SHADER_TYPES]; - - uint32_t fb_id; - int nr_cbufs, old_nr_cbufs; - struct vrend_surface *zsurf; - struct vrend_surface *surf[PIPE_MAX_COLOR_BUFS]; - - struct vrend_viewport vps[PIPE_MAX_VIEWPORTS]; - /* viewport is negative */ - uint32_t scissor_state_dirty; - uint32_t viewport_state_dirty; - uint32_t viewport_state_initialized; - - uint32_t fb_height; - - struct pipe_scissor_state ss[PIPE_MAX_VIEWPORTS]; - - struct pipe_blend_state blend_state; - struct pipe_depth_stencil_alpha_state dsa_state; - struct pipe_rasterizer_state rs_state; - - uint8_t stencil_refs[2]; - bool viewport_is_negative; - /* this is set if the contents of the FBO look upside down when viewed - with 0,0 as the bottom corner */ - bool inverted_fbo_content; - - GLuint blit_fb_ids[2]; - - struct pipe_depth_stencil_alpha_state *dsa; - - struct pipe_clip_state ucp_state; - - bool depth_test_enabled; - bool stencil_test_enabled; - bool framebuffer_srgb_enabled; - - GLuint program_id; - int last_shader_idx; - - GLint draw_indirect_buffer; - - GLint draw_indirect_params_buffer; - - struct pipe_rasterizer_state hw_rs_state; - struct pipe_blend_state hw_blend_state; - - struct list_head streamout_list; - struct vrend_streamout_object *current_so; - - struct pipe_blend_color blend_color; - - uint32_t cond_render_q_id; - GLenum cond_render_gl_mode; - - struct vrend_image_view image_views[PIPE_SHADER_TYPES] - [PIPE_MAX_SHADER_IMAGES]; - uint32_t images_used_mask[PIPE_SHADER_TYPES]; - - struct vrend_ssbo ssbo[PIPE_SHADER_TYPES][PIPE_MAX_SHADER_BUFFERS]; - uint32_t ssbo_used_mask[PIPE_SHADER_TYPES]; - - struct vrend_abo abo[PIPE_MAX_HW_ATOMIC_BUFFERS]; - uint32_t abo_used_mask; - uint8_t swizzle_output_rgb_to_bgr; -}; - -struct vrend_context { - struct list_head sub_ctxs; - - struct vrend_sub_context *sub; - struct vrend_sub_context *sub0; - - int ctx_id; - /* has this ctx gotten an error? */ - bool in_error; - bool ctx_switch_pending; - bool pstip_inited; - - GLuint pstipple_tex_id; - - /* resource bounds to this context */ - struct util_hash_table *res_hash; - - struct list_head active_nontimer_query_list; - struct list_head ctx_entry; - - struct vrend_shader_cfg shader_cfg; - struct virgl_client *client; -}; - -static void vrend_update_viewport_state(struct vrend_context *ctx); -static void vrend_update_scissor_state(struct vrend_context *ctx); -static void vrend_destroy_query_object(void *obj_ptr); -static void vrend_finish_context_switch(struct vrend_context *ctx); -static void vrend_patch_blend_state(struct vrend_context *ctx); -static void vrend_update_frontface_state(struct vrend_context *ctx); -static void vrend_destroy_resource_object(void *obj_ptr); -static void vrend_renderer_detach_res_ctx(struct vrend_context *ctx, - int res_handle); -static void vrend_destroy_program(struct vrend_linked_shader_program *ent); -static void vrend_apply_sampler_state(struct vrend_context *ctx, - struct vrend_resource *res, - uint32_t shader_type, int id, - int sampler_id, - struct vrend_sampler_view *tview); -static GLenum tgsitargettogltarget(const enum pipe_texture_target target, - int nr_samples); - -void vrend_update_stencil_state(struct vrend_context *ctx); - -static struct vrend_format_table tex_conv_table[VIRGL_FORMAT_MAX_EXTENDED]; -static bool tex_conv_table_initialized = false; - -static inline bool vrend_format_can_sample(enum virgl_formats format) { - return tex_conv_table[format].bindings & VIRGL_BIND_SAMPLER_VIEW; -} - -static inline bool vrend_format_can_readback(enum virgl_formats format) { - return tex_conv_table[format].flags & VIRGL_TEXTURE_CAN_READBACK; -} - -static inline bool vrend_format_can_render(enum virgl_formats format) { - return tex_conv_table[format].bindings & VIRGL_BIND_RENDER_TARGET; -} - -static inline bool vrend_format_is_ds(enum virgl_formats format) { - return tex_conv_table[format].bindings & VIRGL_BIND_DEPTH_STENCIL; -} - -static bool vrend_blit_needs_swizzle(enum virgl_formats src, - enum virgl_formats dst) { - for (int i = 0; i < 4; ++i) { - if (tex_conv_table[src].swizzle[i] != tex_conv_table[dst].swizzle[i]) - return true; - } - return false; -} - -static inline GLenum translate_gles_emulation_texture_target(GLenum target) { - switch (target) { - case GL_TEXTURE_1D: - case GL_TEXTURE_RECTANGLE: - return GL_TEXTURE_2D; - case GL_TEXTURE_1D_ARRAY: - return GL_TEXTURE_2D_ARRAY; - default: - return target; - } -} - -static inline const char *pipe_shader_to_prefix(int shader_type) { - switch (shader_type) { - case PIPE_SHADER_VERTEX: - return "vs"; - case PIPE_SHADER_FRAGMENT: - return "fs"; - case PIPE_SHADER_GEOMETRY: - return "gs"; - case PIPE_SHADER_TESS_CTRL: - return "tc"; - case PIPE_SHADER_TESS_EVAL: - return "te"; - case PIPE_SHADER_COMPUTE: - return "cs"; - default: - return NULL; - }; -} - -static void init_features(int gles_ver) { - for (enum features_id id = 0; id < feat_last; id++) { - if (gles_ver >= feature_list[id].gles_ver) { - set_feature(id); - } else { - for (uint32_t i = 0; i < FEAT_MAX_EXTS; i++) { - if (!feature_list[id].gl_ext[i]) - break; - if (vrend_has_gl_extension(feature_list[id].gl_ext[i])) { - set_feature(id); - break; - } - } - } - } -} - -static void vrend_destroy_surface(struct vrend_surface *surf) { - if (surf->id != surf->texture->id) - glDeleteTextures(1, &surf->id); - vrend_resource_reference(&surf->texture, NULL); - free(surf); -} - -static inline void vrend_surface_reference(struct vrend_surface **ptr, - struct vrend_surface *surf) { - struct vrend_surface *old_surf = *ptr; - - if (pipe_reference(&(*ptr)->reference, &surf->reference)) - vrend_destroy_surface(old_surf); - *ptr = surf; -} - -static void vrend_destroy_sampler_view(struct vrend_sampler_view *samp) { - if (samp->texture->id != samp->id) - glDeleteTextures(1, &samp->id); - vrend_resource_reference(&samp->texture, NULL); - free(samp); -} - -static inline void -vrend_sampler_view_reference(struct vrend_sampler_view **ptr, - struct vrend_sampler_view *view) { - struct vrend_sampler_view *old_view = *ptr; - - if (pipe_reference(&(*ptr)->reference, &view->reference)) - vrend_destroy_sampler_view(old_view); - *ptr = view; -} - -static void vrend_destroy_so_target(struct vrend_so_target *target) { - vrend_resource_reference(&target->buffer, NULL); - free(target); -} - -static inline void vrend_so_target_reference(struct vrend_so_target **ptr, - struct vrend_so_target *target) { - struct vrend_so_target *old_target = *ptr; - - if (pipe_reference(&(*ptr)->reference, &target->reference)) - vrend_destroy_so_target(old_target); - *ptr = target; -} - -static void vrend_shader_destroy(struct vrend_shader *shader) { - struct vrend_linked_shader_program *ent, *tmp; - - LIST_FOR_EACH_ENTRY_SAFE(ent, tmp, &shader->programs, sl[shader->sel->type]) { - vrend_destroy_program(ent); - } - - glDeleteShader(shader->id); - strarray_free(&shader->glsl_strings, true); - free(shader); -} - -static void vrend_destroy_shader_selector(struct vrend_shader_selector *sel) { - struct vrend_shader *p = sel->current, *c; - unsigned i; - while (p) { - c = p->next_variant; - vrend_shader_destroy(p); - p = c; - } - if (sel->sinfo.so_names) - for (i = 0; i < sel->sinfo.so_info.num_outputs; i++) - free(sel->sinfo.so_names[i]); - free(sel->tmp_buf); - free(sel->sinfo.so_names); - free(sel->sinfo.interpinfo); - free(sel->sinfo.sampler_arrays); - free(sel->sinfo.image_arrays); - free(sel->tokens); - free(sel); -} - -static bool vrend_compile_shader(struct vrend_context *ctx, - struct vrend_shader *shader) { - GLint param; - const char *shader_parts[SHADER_MAX_STRINGS]; - - for (int i = 0; i < shader->glsl_strings.num_strings; i++) - shader_parts[i] = shader->glsl_strings.strings[i].buf; - glShaderSource(shader->id, shader->glsl_strings.num_strings, shader_parts, - NULL); - glCompileShader(shader->id); - glGetShaderiv(shader->id, GL_COMPILE_STATUS, ¶m); - if (param == GL_FALSE) - return false; - return true; -} - -static inline void -vrend_shader_state_reference(struct vrend_shader_selector **ptr, - struct vrend_shader_selector *shader) { - struct vrend_shader_selector *old_shader = *ptr; - - if (pipe_reference(&(*ptr)->reference, &shader->reference)) - vrend_destroy_shader_selector(old_shader); - *ptr = shader; -} - -void vrend_insert_format(struct vrend_format_table *entry, uint32_t bindings, - uint32_t flags) { - tex_conv_table[entry->format] = *entry; - tex_conv_table[entry->format].bindings = bindings; - tex_conv_table[entry->format].flags = flags; -} - -void vrend_insert_format_swizzle(int override_format, - struct vrend_format_table *entry, - uint32_t bindings, uint8_t swizzle[4], - uint32_t flags) { - int i; - tex_conv_table[override_format] = *entry; - tex_conv_table[override_format].bindings = bindings; - tex_conv_table[override_format].flags = flags | VIRGL_TEXTURE_NEED_SWIZZLE; - for (i = 0; i < 4; i++) - tex_conv_table[override_format].swizzle[i] = swizzle[i]; -} - -const struct vrend_format_table * -vrend_get_format_table_entry(enum virgl_formats format) { - return &tex_conv_table[format]; -} - -static void vrend_use_program(struct vrend_context *ctx, GLuint program_id) { - if (ctx->sub->program_id != program_id) { - glUseProgram(program_id); - ctx->sub->program_id = program_id; - } -} - -static void vrend_init_pstipple_texture(struct vrend_context *ctx) { - glGenTextures(1, &ctx->pstipple_tex_id); - glBindTexture(GL_TEXTURE_2D, ctx->pstipple_tex_id); - glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, 32, 32, 0, GL_RED, GL_UNSIGNED_BYTE, - NULL); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - - ctx->pstip_inited = true; -} - -static void vrend_depth_test_enable(struct vrend_context *ctx, - bool depth_test_enable) { - if (ctx->sub->depth_test_enabled != depth_test_enable) { - ctx->sub->depth_test_enabled = depth_test_enable; - if (depth_test_enable) - glEnable(GL_DEPTH_TEST); - else - glDisable(GL_DEPTH_TEST); - } -} - -static void vrend_stencil_test_enable(struct vrend_context *ctx, - bool stencil_test_enable) { - if (ctx->sub->stencil_test_enabled != stencil_test_enable) { - ctx->sub->stencil_test_enabled = stencil_test_enable; - if (stencil_test_enable) - glEnable(GL_STENCIL_TEST); - else - glDisable(GL_STENCIL_TEST); - } -} - -static int bind_sampler_locs(struct vrend_linked_shader_program *sprog, int id, - int next_sampler_id) { - if (sprog->ss[id]->sel->sinfo.samplers_used_mask) { - uint32_t mask = sprog->ss[id]->sel->sinfo.samplers_used_mask; - int nsamp = util_bitcount(sprog->ss[id]->sel->sinfo.samplers_used_mask); - int index; - sprog->shadow_samp_mask[id] = sprog->ss[id]->sel->sinfo.shadow_samp_mask; - if (sprog->ss[id]->sel->sinfo.shadow_samp_mask) { - sprog->shadow_samp_mask_locs[id] = calloc(nsamp, sizeof(uint32_t)); - sprog->shadow_samp_add_locs[id] = calloc(nsamp, sizeof(uint32_t)); - } else { - sprog->shadow_samp_mask_locs[id] = sprog->shadow_samp_add_locs[id] = NULL; - } - const char *prefix = pipe_shader_to_prefix(id); - index = 0; - while (mask) { - uint32_t i = u_bit_scan(&mask); - char name[64]; - if (sprog->ss[id]->sel->sinfo.num_sampler_arrays) { - int arr_idx = - vrend_shader_lookup_sampler_array(&sprog->ss[id]->sel->sinfo, i); - snprintf(name, 32, "%ssamp%d[%d]", prefix, arr_idx, i - arr_idx); - } else - snprintf(name, 32, "%ssamp%d", prefix, i); - - glUniform1i(glGetUniformLocation(sprog->id, name), next_sampler_id++); - - if (sprog->ss[id]->sel->sinfo.shadow_samp_mask & (1 << i)) { - snprintf(name, 32, "%sshadmask%d", prefix, i); - sprog->shadow_samp_mask_locs[id][index] = - glGetUniformLocation(sprog->id, name); - snprintf(name, 32, "%sshadadd%d", prefix, i); - sprog->shadow_samp_add_locs[id][index] = - glGetUniformLocation(sprog->id, name); - } - index++; - } - } else { - sprog->shadow_samp_mask_locs[id] = NULL; - sprog->shadow_samp_add_locs[id] = NULL; - sprog->shadow_samp_mask[id] = 0; - } - sprog->samplers_used_mask[id] = sprog->ss[id]->sel->sinfo.samplers_used_mask; - - return next_sampler_id; -} - -static void bind_const_locs(struct vrend_linked_shader_program *sprog, int id) { - if (sprog->ss[id]->sel->sinfo.num_consts) { - char name[32]; - snprintf(name, 32, "%sconst0", pipe_shader_to_prefix(id)); - sprog->const_location[id] = glGetUniformLocation(sprog->id, name); - } else - sprog->const_location[id] = -1; -} - -static int bind_ubo_locs(struct vrend_linked_shader_program *sprog, int id, - int next_ubo_id) { - if (!has_feature(feat_ubo)) - return next_ubo_id; - if (sprog->ss[id]->sel->sinfo.ubo_used_mask) { - const char *prefix = pipe_shader_to_prefix(id); - - unsigned mask = sprog->ss[id]->sel->sinfo.ubo_used_mask; - while (mask) { - uint32_t ubo_idx = u_bit_scan(&mask); - char name[32]; - if (sprog->ss[id]->sel->sinfo.ubo_indirect) - snprintf(name, 32, "%subo[%d]", prefix, ubo_idx - 1); - else - snprintf(name, 32, "%subo%d", prefix, ubo_idx); - - GLuint loc = glGetUniformBlockIndex(sprog->id, name); - glUniformBlockBinding(sprog->id, loc, next_ubo_id++); - } - } - - sprog->ubo_used_mask[id] = sprog->ss[id]->sel->sinfo.ubo_used_mask; - - return next_ubo_id; -} - -static void bind_ssbo_locs(struct vrend_linked_shader_program *sprog, int id) { - int i; - char name[32]; - if (!has_feature(feat_ssbo)) - return; - if (sprog->ss[id]->sel->sinfo.ssbo_used_mask) { - const char *prefix = pipe_shader_to_prefix(id); - uint32_t mask = sprog->ss[id]->sel->sinfo.ssbo_used_mask; - sprog->ssbo_locs[id] = calloc(util_last_bit(mask), sizeof(uint32_t)); - - while (mask) { - i = u_bit_scan(&mask); - snprintf(name, 32, "%sssbo%d", prefix, i); - sprog->ssbo_locs[id][i] = - glGetProgramResourceIndex(sprog->id, GL_SHADER_STORAGE_BLOCK, name); - } - } else - sprog->ssbo_locs[id] = NULL; -} - -static void bind_image_locs(struct vrend_linked_shader_program *sprog, int id) { - int i; - char name[32]; - const char *prefix = pipe_shader_to_prefix(id); - - uint32_t mask = sprog->ss[id]->sel->sinfo.images_used_mask; - if (!mask && !sprog->ss[id]->sel->sinfo.num_image_arrays) - return; - - if (!has_feature(feat_images)) - return; - - int nsamp = util_last_bit(mask); - if (nsamp) { - sprog->img_locs[id] = calloc(nsamp, sizeof(GLint)); - if (!sprog->img_locs[id]) - return; - } else - sprog->img_locs[id] = NULL; - - if (sprog->ss[id]->sel->sinfo.num_image_arrays) { - for (i = 0; i < sprog->ss[id]->sel->sinfo.num_image_arrays; i++) { - struct vrend_array *img_array = - &sprog->ss[id]->sel->sinfo.image_arrays[i]; - for (int j = 0; j < img_array->array_size; j++) { - snprintf(name, 32, "%simg%d[%d]", prefix, img_array->first, j); - sprog->img_locs[id][img_array->first + j] = - glGetUniformLocation(sprog->id, name); - } - } - } else if (mask) { - for (i = 0; i < nsamp; i++) { - if (mask & (1 << i)) { - snprintf(name, 32, "%simg%d", prefix, i); - sprog->img_locs[id][i] = glGetUniformLocation(sprog->id, name); - } else { - sprog->img_locs[id][i] = -1; - } - } - } - sprog->images_used_mask[id] = mask; -} - -static struct vrend_linked_shader_program * -add_cs_shader_program(struct vrend_context *ctx, struct vrend_shader *cs) { - struct vrend_linked_shader_program *sprog = - CALLOC_STRUCT(vrend_linked_shader_program); - GLuint prog_id; - GLint lret; - prog_id = glCreateProgram(); - glAttachShader(prog_id, cs->id); - glLinkProgram(prog_id); - - glGetProgramiv(prog_id, GL_LINK_STATUS, &lret); - if (lret == GL_FALSE) { - glDeleteProgram(prog_id); - free(sprog); - return NULL; - } - sprog->ss[PIPE_SHADER_COMPUTE] = cs; - - list_add(&sprog->sl[PIPE_SHADER_COMPUTE], &cs->programs); - sprog->id = prog_id; - list_addtail(&sprog->head, &ctx->sub->programs); - - vrend_use_program(ctx, prog_id); - - bind_sampler_locs(sprog, PIPE_SHADER_COMPUTE, 0); - bind_ubo_locs(sprog, PIPE_SHADER_COMPUTE, 0); - bind_ssbo_locs(sprog, PIPE_SHADER_COMPUTE); - bind_const_locs(sprog, PIPE_SHADER_COMPUTE); - bind_image_locs(sprog, PIPE_SHADER_COMPUTE); - return sprog; -} - -static struct vrend_linked_shader_program * -add_shader_program(struct vrend_context *ctx, struct vrend_shader *vs, - struct vrend_shader *fs, struct vrend_shader *gs, - struct vrend_shader *tcs, struct vrend_shader *tes) { - struct vrend_linked_shader_program *sprog = - CALLOC_STRUCT(vrend_linked_shader_program); - char name[64]; - int i; - GLuint prog_id; - GLint lret; - int id; - int last_shader; - bool do_patch = false; - if (!sprog) - return NULL; - - /* need to rewrite VS code to add interpolation params */ - if (gs && gs->compiled_fs_id != fs->id) - do_patch = true; - if (!gs && tes && tes->compiled_fs_id != fs->id) - do_patch = true; - if (!gs && !tes && vs->compiled_fs_id != fs->id) - do_patch = true; - - if (do_patch) { - bool ret; - - if (gs) - vrend_patch_vertex_shader_interpolants( - ctx, &ctx->shader_cfg, &gs->glsl_strings, &gs->sel->sinfo, - &fs->sel->sinfo, "gso", fs->key.flatshade); - else if (tes) - vrend_patch_vertex_shader_interpolants( - ctx, &ctx->shader_cfg, &tes->glsl_strings, &tes->sel->sinfo, - &fs->sel->sinfo, "teo", fs->key.flatshade); - else - vrend_patch_vertex_shader_interpolants( - ctx, &ctx->shader_cfg, &vs->glsl_strings, &vs->sel->sinfo, - &fs->sel->sinfo, "vso", fs->key.flatshade); - ret = vrend_compile_shader(ctx, gs ? gs : (tes ? tes : vs)); - if (ret == false) { - glDeleteShader(gs ? gs->id : (tes ? tes->id : vs->id)); - free(sprog); - return NULL; - } - if (gs) - gs->compiled_fs_id = fs->id; - else if (tes) - tes->compiled_fs_id = fs->id; - else - vs->compiled_fs_id = fs->id; - } - - prog_id = glCreateProgram(); - glAttachShader(prog_id, vs->id); - if (tcs && tcs->id > 0) - glAttachShader(prog_id, tcs->id); - if (tes && tes->id > 0) - glAttachShader(prog_id, tes->id); - - glAttachShader(prog_id, fs->id); - - sprog->dual_src_linked = false; - - if (has_feature(feat_gles31_vertex_attrib_binding)) { - uint32_t mask = vs->sel->sinfo.attrib_input_mask; - while (mask) { - i = u_bit_scan(&mask); - snprintf(name, 32, "in_%d", i); - glBindAttribLocation(prog_id, i, name); - } - } - - glLinkProgram(prog_id); - - glGetProgramiv(prog_id, GL_LINK_STATUS, &lret); - if (lret == GL_FALSE) { - glDeleteProgram(prog_id); - free(sprog); - return NULL; - } - - sprog->ss[PIPE_SHADER_VERTEX] = vs; - sprog->ss[PIPE_SHADER_FRAGMENT] = fs; - sprog->ss[PIPE_SHADER_GEOMETRY] = gs; - sprog->ss[PIPE_SHADER_TESS_CTRL] = tcs; - sprog->ss[PIPE_SHADER_TESS_EVAL] = tes; - - list_add(&sprog->sl[PIPE_SHADER_VERTEX], &vs->programs); - list_add(&sprog->sl[PIPE_SHADER_FRAGMENT], &fs->programs); - if (gs) - list_add(&sprog->sl[PIPE_SHADER_GEOMETRY], &gs->programs); - if (tcs) - list_add(&sprog->sl[PIPE_SHADER_TESS_CTRL], &tcs->programs); - if (tes) - list_add(&sprog->sl[PIPE_SHADER_TESS_EVAL], &tes->programs); - - last_shader = tes ? PIPE_SHADER_TESS_EVAL - : (gs ? PIPE_SHADER_GEOMETRY : PIPE_SHADER_FRAGMENT); - sprog->id = prog_id; - - list_addtail(&sprog->head, &ctx->sub->programs); - - if (fs->key.pstipple_tex) - sprog->fs_stipple_loc = glGetUniformLocation(prog_id, "pstipple_sampler"); - else - sprog->fs_stipple_loc = -1; - sprog->vs_ws_adjust_loc = glGetUniformLocation(prog_id, "winsys_adjust_y"); - - vrend_use_program(ctx, prog_id); - - int next_ubo_id = 0, next_sampler_id = 0; - for (id = PIPE_SHADER_VERTEX; id <= last_shader; id++) { - if (!sprog->ss[id]) - continue; - - next_sampler_id = bind_sampler_locs(sprog, id, next_sampler_id); - bind_const_locs(sprog, id); - next_ubo_id = bind_ubo_locs(sprog, id, next_ubo_id); - bind_image_locs(sprog, id); - bind_ssbo_locs(sprog, id); - } - - if (!has_feature(feat_gles31_vertex_attrib_binding)) { - if (vs->sel->sinfo.num_inputs) { - sprog->attrib_locs = calloc(vs->sel->sinfo.num_inputs, sizeof(uint32_t)); - if (sprog->attrib_locs) { - for (i = 0; i < vs->sel->sinfo.num_inputs; i++) { - snprintf(name, 32, "in_%d", i); - sprog->attrib_locs[i] = glGetAttribLocation(prog_id, name); - } - } - } else - sprog->attrib_locs = NULL; - } - - if (vs->sel->sinfo.num_ucp) { - for (i = 0; i < vs->sel->sinfo.num_ucp; i++) { - snprintf(name, 32, "clipp[%d]", i); - sprog->clip_locs[i] = glGetUniformLocation(prog_id, name); - } - } - return sprog; -} - -static struct vrend_linked_shader_program * -lookup_cs_shader_program(struct vrend_context *ctx, GLuint cs_id) { - struct vrend_linked_shader_program *ent; - LIST_FOR_EACH_ENTRY(ent, &ctx->sub->programs, head) { - if (!ent->ss[PIPE_SHADER_COMPUTE]) - continue; - if (ent->ss[PIPE_SHADER_COMPUTE]->id == cs_id) - return ent; - } - return NULL; -} - -static struct vrend_linked_shader_program * -lookup_shader_program(struct vrend_context *ctx, GLuint vs_id, GLuint fs_id, - GLuint gs_id, GLuint tcs_id, GLuint tes_id, - bool dual_src) { - struct vrend_linked_shader_program *ent; - LIST_FOR_EACH_ENTRY(ent, &ctx->sub->programs, head) { - if (ent->dual_src_linked != dual_src) - continue; - if (ent->ss[PIPE_SHADER_COMPUTE]) - continue; - if (ent->ss[PIPE_SHADER_VERTEX]->id != vs_id) - continue; - if (ent->ss[PIPE_SHADER_FRAGMENT]->id != fs_id) - continue; - if (ent->ss[PIPE_SHADER_GEOMETRY] && - ent->ss[PIPE_SHADER_GEOMETRY]->id != gs_id) - continue; - if (ent->ss[PIPE_SHADER_TESS_CTRL] && - ent->ss[PIPE_SHADER_TESS_CTRL]->id != tcs_id) - continue; - if (ent->ss[PIPE_SHADER_TESS_EVAL] && - ent->ss[PIPE_SHADER_TESS_EVAL]->id != tes_id) - continue; - return ent; - } - return NULL; -} - -static void vrend_destroy_program(struct vrend_linked_shader_program *ent) { - int i; - if (ent->ref_context && ent->ref_context->prog == ent) - ent->ref_context->prog = NULL; - - glDeleteProgram(ent->id); - list_del(&ent->head); - - for (i = PIPE_SHADER_VERTEX; i <= PIPE_SHADER_COMPUTE; i++) { - if (ent->ss[i]) - list_del(&ent->sl[i]); - free(ent->shadow_samp_mask_locs[i]); - free(ent->shadow_samp_add_locs[i]); - free(ent->ssbo_locs[i]); - free(ent->img_locs[i]); - } - free(ent->attrib_locs); - free(ent); -} - -static void vrend_free_programs(struct vrend_sub_context *sub) { - struct vrend_linked_shader_program *ent, *tmp; - - if (LIST_IS_EMPTY(&sub->programs)) - return; - - LIST_FOR_EACH_ENTRY_SAFE(ent, tmp, &sub->programs, head) { - vrend_destroy_program(ent); - } -} - -static void vrend_destroy_streamout_object(struct vrend_streamout_object *obj) { - unsigned i; - list_del(&obj->head); - for (i = 0; i < obj->num_targets; i++) - vrend_so_target_reference(&obj->so_targets[i], NULL); - if (has_feature(feat_transform_feedback2)) - glDeleteTransformFeedbacks(1, &obj->id); - FREE(obj); -} - -int vrend_create_surface(struct vrend_context *ctx, uint32_t handle, - uint32_t res_handle, uint32_t format, uint32_t val0, - uint32_t val1) { - struct vrend_surface *surf; - struct vrend_resource *res; - uint32_t ret_handle; - - if (format >= PIPE_FORMAT_COUNT) { - return EINVAL; - } - - res = vrend_renderer_ctx_res_lookup(ctx, res_handle); - if (!res) - return EINVAL; - - surf = CALLOC_STRUCT(vrend_surface); - if (!surf) - return ENOMEM; - - surf->res_handle = res_handle; - surf->format = format; - - surf->val0 = val0; - surf->val1 = val1; - surf->id = res->id; - - pipe_reference_init(&surf->reference, 1); - - vrend_resource_reference(&surf->texture, res); - - ret_handle = vrend_renderer_object_insert(ctx, surf, sizeof(*surf), handle, - VIRGL_OBJECT_SURFACE); - if (ret_handle == 0) { - FREE(surf); - return ENOMEM; - } - return 0; -} - -static void vrend_destroy_surface_object(void *obj_ptr) { - struct vrend_surface *surface = obj_ptr; - - vrend_surface_reference(&surface, NULL); -} - -static void vrend_destroy_sampler_view_object(void *obj_ptr) { - struct vrend_sampler_view *samp = obj_ptr; - - vrend_sampler_view_reference(&samp, NULL); -} - -static void vrend_destroy_so_target_object(void *obj_ptr) { - struct vrend_so_target *target = obj_ptr; - struct vrend_sub_context *sub_ctx = target->sub_ctx; - struct vrend_streamout_object *obj, *tmp; - bool found; - unsigned i; - - LIST_FOR_EACH_ENTRY_SAFE(obj, tmp, &sub_ctx->streamout_list, head) { - found = false; - for (i = 0; i < obj->num_targets; i++) { - if (obj->so_targets[i] == target) { - found = true; - break; - } - } - if (found) { - if (obj == sub_ctx->current_so) - sub_ctx->current_so = NULL; - if (obj->xfb_state == XFB_STATE_PAUSED) { - if (has_feature(feat_transform_feedback2)) - glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, obj->id); - glEndTransformFeedback(); - if (sub_ctx->current_so && has_feature(feat_transform_feedback2)) - glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, - sub_ctx->current_so->id); - } - vrend_destroy_streamout_object(obj); - } - } - - vrend_so_target_reference(&target, NULL); -} - -static void vrend_destroy_vertex_elements_object(void *obj_ptr) { - struct vrend_vertex_element_array *v = obj_ptr; - - if (has_feature(feat_gles31_vertex_attrib_binding)) { - glDeleteVertexArrays(1, &v->id); - } - FREE(v); -} - -static void vrend_destroy_sampler_state_object(void *obj_ptr) { - struct vrend_sampler_state *state = obj_ptr; - - if (has_feature(feat_samplers)) - glDeleteSamplers(2, state->ids); - FREE(state); -} - -static GLuint convert_wrap(int wrap) { - switch (wrap) { - case PIPE_TEX_WRAP_REPEAT: - return GL_REPEAT; - case PIPE_TEX_WRAP_CLAMP:; - case PIPE_TEX_WRAP_CLAMP_TO_EDGE: - return GL_CLAMP_TO_EDGE; - case PIPE_TEX_WRAP_CLAMP_TO_BORDER: - return GL_CLAMP_TO_BORDER; - - case PIPE_TEX_WRAP_MIRROR_REPEAT: - return GL_MIRRORED_REPEAT; - case PIPE_TEX_WRAP_MIRROR_CLAMP: - case PIPE_TEX_WRAP_MIRROR_CLAMP_TO_EDGE: - case PIPE_TEX_WRAP_MIRROR_CLAMP_TO_BORDER: - return GL_MIRROR_CLAMP_TO_EDGE_EXT; - default: - assert(0); - return -1; - } -} - -static inline GLenum convert_mag_filter(unsigned int filter) { - if (filter == PIPE_TEX_FILTER_NEAREST) - return GL_NEAREST; - return GL_LINEAR; -} - -static inline GLenum convert_min_filter(unsigned int filter, - unsigned int mip_filter) { - if (mip_filter == PIPE_TEX_MIPFILTER_NONE) - return convert_mag_filter(filter); - else if (mip_filter == PIPE_TEX_MIPFILTER_LINEAR) { - if (filter == PIPE_TEX_FILTER_NEAREST) - return GL_NEAREST_MIPMAP_LINEAR; - else - return GL_LINEAR_MIPMAP_LINEAR; - } else if (mip_filter == PIPE_TEX_MIPFILTER_NEAREST) { - if (filter == PIPE_TEX_FILTER_NEAREST) - return GL_NEAREST_MIPMAP_NEAREST; - else - return GL_LINEAR_MIPMAP_NEAREST; - } - assert(0); - return 0; -} - -int vrend_create_sampler_state(struct vrend_context *ctx, uint32_t handle, - struct pipe_sampler_state *templ) { - struct vrend_sampler_state *state = CALLOC_STRUCT(vrend_sampler_state); - int ret_handle; - - if (!state) - return ENOMEM; - - state->base = *templ; - - if (has_feature(feat_samplers)) { - glGenSamplers(2, state->ids); - - for (int i = 0; i < 2; ++i) { - glSamplerParameteri(state->ids[i], GL_TEXTURE_WRAP_S, - convert_wrap(templ->wrap_s)); - glSamplerParameteri(state->ids[i], GL_TEXTURE_WRAP_T, - convert_wrap(templ->wrap_t)); - glSamplerParameteri(state->ids[i], GL_TEXTURE_WRAP_R, - convert_wrap(templ->wrap_r)); - glSamplerParameterf( - state->ids[i], GL_TEXTURE_MIN_FILTER, - convert_min_filter(templ->min_img_filter, templ->min_mip_filter)); - glSamplerParameterf(state->ids[i], GL_TEXTURE_MAG_FILTER, - convert_mag_filter(templ->mag_img_filter)); - glSamplerParameterf(state->ids[i], GL_TEXTURE_MIN_LOD, templ->min_lod); - glSamplerParameterf(state->ids[i], GL_TEXTURE_MAX_LOD, templ->max_lod); - glSamplerParameteri(state->ids[i], GL_TEXTURE_COMPARE_FUNC, - GL_NEVER + templ->compare_func); - glSamplerParameterIuiv(state->ids[i], GL_TEXTURE_BORDER_COLOR, - templ->border_color.ui); - glSamplerParameteri(state->ids[i], GL_TEXTURE_SRGB_DECODE_EXT, - i == 0 ? GL_SKIP_DECODE_EXT : GL_DECODE_EXT); - } - } - ret_handle = vrend_renderer_object_insert(ctx, state, - sizeof(struct vrend_sampler_state), - handle, VIRGL_OBJECT_SAMPLER_STATE); - if (!ret_handle) { - if (has_feature(feat_samplers)) - glDeleteSamplers(2, state->ids); - FREE(state); - return ENOMEM; - } - return 0; -} - -static inline GLenum to_gl_swizzle(int swizzle) { - switch (swizzle) { - case PIPE_SWIZZLE_RED: - return GL_RED; - case PIPE_SWIZZLE_GREEN: - return GL_GREEN; - case PIPE_SWIZZLE_BLUE: - return GL_BLUE; - case PIPE_SWIZZLE_ALPHA: - return GL_ALPHA; - case PIPE_SWIZZLE_ZERO: - return GL_ZERO; - case PIPE_SWIZZLE_ONE: - return GL_ONE; - default: - assert(0); - return 0; - } -} - -int vrend_create_sampler_view(struct vrend_context *ctx, uint32_t handle, - uint32_t res_handle, uint32_t format, - uint32_t val0, uint32_t val1, - uint32_t swizzle_packed) { - struct vrend_sampler_view *view; - struct vrend_resource *res; - int ret_handle; - uint8_t swizzle[4]; - - res = vrend_renderer_ctx_res_lookup(ctx, res_handle); - if (!res) - return EINVAL; - - view = CALLOC_STRUCT(vrend_sampler_view); - if (!view) - return ENOMEM; - - pipe_reference_init(&view->reference, 1); - view->format = format & 0xffffff; - - if (!view->format || view->format >= VIRGL_FORMAT_MAX) { - FREE(view); - return EINVAL; - } - - uint32_t pipe_target = (format >> 24) & 0xff; - if (pipe_target >= PIPE_MAX_TEXTURE_TYPES) { - FREE(view); - return EINVAL; - } - - view->target = tgsitargettogltarget(pipe_target, res->base.nr_samples); - view->target = translate_gles_emulation_texture_target(view->target); - - view->val0 = val0; - view->val1 = val1; - - swizzle[0] = swizzle_packed & 0x7; - swizzle[1] = (swizzle_packed >> 3) & 0x7; - swizzle[2] = (swizzle_packed >> 6) & 0x7; - swizzle[3] = (swizzle_packed >> 9) & 0x7; - - vrend_resource_reference(&view->texture, res); - - view->id = view->texture->id; - if (view->target == PIPE_BUFFER) - view->target = view->texture->target; - - view->srgb_decode = GL_DECODE_EXT; - if (view->format != view->texture->base.format) { - if (util_format_is_srgb(view->texture->base.format) && - !util_format_is_srgb(view->format)) - view->srgb_decode = GL_SKIP_DECODE_EXT; - } - - if (!(util_format_has_alpha(view->format) || - util_format_is_depth_or_stencil(view->format))) { - if (swizzle[0] == PIPE_SWIZZLE_ALPHA) - swizzle[0] = PIPE_SWIZZLE_ONE; - if (swizzle[1] == PIPE_SWIZZLE_ALPHA) - swizzle[1] = PIPE_SWIZZLE_ONE; - if (swizzle[2] == PIPE_SWIZZLE_ALPHA) - swizzle[2] = PIPE_SWIZZLE_ONE; - if (swizzle[3] == PIPE_SWIZZLE_ALPHA) - swizzle[3] = PIPE_SWIZZLE_ONE; - } - - if (tex_conv_table[view->format].flags & VIRGL_TEXTURE_NEED_SWIZZLE) { - if (swizzle[0] <= PIPE_SWIZZLE_ALPHA) - swizzle[0] = tex_conv_table[view->format].swizzle[swizzle[0]]; - if (swizzle[1] <= PIPE_SWIZZLE_ALPHA) - swizzle[1] = tex_conv_table[view->format].swizzle[swizzle[1]]; - if (swizzle[2] <= PIPE_SWIZZLE_ALPHA) - swizzle[2] = tex_conv_table[view->format].swizzle[swizzle[2]]; - if (swizzle[3] <= PIPE_SWIZZLE_ALPHA) - swizzle[3] = tex_conv_table[view->format].swizzle[swizzle[3]]; - } - - view->gl_swizzle_r = to_gl_swizzle(swizzle[0]); - view->gl_swizzle_g = to_gl_swizzle(swizzle[1]); - view->gl_swizzle_b = to_gl_swizzle(swizzle[2]); - view->gl_swizzle_a = to_gl_swizzle(swizzle[3]); - - ret_handle = vrend_renderer_object_insert(ctx, view, sizeof(*view), handle, - VIRGL_OBJECT_SAMPLER_VIEW); - if (ret_handle == 0) { - FREE(view); - return ENOMEM; - } - return 0; -} - -void vrend_fb_bind_texture_id(struct vrend_resource *res, int id, int idx, - uint32_t level, uint32_t layer) { - const struct util_format_description *desc = - util_format_description(res->base.format); - GLenum attachment = GL_COLOR_ATTACHMENT0 + idx; - - if (vrend_format_is_ds(res->base.format)) { - if (util_format_has_stencil(desc)) { - if (util_format_has_depth(desc)) - attachment = GL_DEPTH_STENCIL_ATTACHMENT; - else - attachment = GL_STENCIL_ATTACHMENT; - } else - attachment = GL_DEPTH_ATTACHMENT; - } - - switch (res->target) { - case GL_TEXTURE_1D_ARRAY: - case GL_TEXTURE_2D_ARRAY: - case GL_TEXTURE_2D_MULTISAMPLE_ARRAY: - case GL_TEXTURE_CUBE_MAP_ARRAY: - if (layer == 0xffffffff) - glFramebufferTexture(GL_FRAMEBUFFER, attachment, id, level); - else - glFramebufferTextureLayer(GL_FRAMEBUFFER, attachment, id, level, layer); - break; - case GL_TEXTURE_3D: - if (layer == 0xffffffff) - glFramebufferTexture(GL_FRAMEBUFFER, attachment, id, level); - break; - case GL_TEXTURE_CUBE_MAP: - if (layer == 0xffffffff) - glFramebufferTexture(GL_FRAMEBUFFER, attachment, id, level); - else - glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, - GL_TEXTURE_CUBE_MAP_POSITIVE_X + layer, id, level); - break; - case GL_TEXTURE_1D: - case GL_TEXTURE_2D: - default: - glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, res->target, id, level); - break; - } - - if (attachment == GL_DEPTH_ATTACHMENT) { - switch (res->target) { - case GL_TEXTURE_1D: - case GL_TEXTURE_2D: - default: - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, - GL_TEXTURE_2D, 0, 0); - break; - } - } -} - -void vrend_fb_bind_texture(struct vrend_resource *res, int idx, uint32_t level, - uint32_t layer) { - vrend_fb_bind_texture_id(res, res->id, idx, level, layer); -} - -static void vrend_hw_set_zsurf_texture(struct vrend_context *ctx) { - struct vrend_surface *surf = ctx->sub->zsurf; - - if (!surf) { - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, - GL_TEXTURE_2D, 0, 0); - } else { - uint32_t first_layer = surf->val1 & 0xffff; - uint32_t last_layer = (surf->val1 >> 16) & 0xffff; - - if (!surf->texture) - return; - - vrend_fb_bind_texture_id(surf->texture, surf->id, 0, surf->val0, - first_layer != last_layer ? 0xffffffff - : first_layer); - } -} - -static void vrend_hw_set_color_surface(struct vrend_context *ctx, int index) { - struct vrend_surface *surf = ctx->sub->surf[index]; - - if (!surf) { - GLenum attachment = GL_COLOR_ATTACHMENT0 + index; - - glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, GL_TEXTURE_2D, 0, 0); - } else { - uint32_t first_layer = ctx->sub->surf[index]->val1 & 0xffff; - uint32_t last_layer = (ctx->sub->surf[index]->val1 >> 16) & 0xffff; - - vrend_fb_bind_texture_id(surf->texture, surf->id, index, surf->val0, - first_layer != last_layer ? 0xffffffff - : first_layer); - } -} - -static void vrend_hw_emit_framebuffer_state(struct vrend_context *ctx) { - static const GLenum buffers[8] = { - GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2, - GL_COLOR_ATTACHMENT3, GL_COLOR_ATTACHMENT4, GL_COLOR_ATTACHMENT5, - GL_COLOR_ATTACHMENT6, GL_COLOR_ATTACHMENT7, - }; - - if (ctx->sub->nr_cbufs == 0) { - glReadBuffer(GL_NONE); - if (has_feature(feat_srgb_write_control)) { - glDisable(GL_FRAMEBUFFER_SRGB_EXT); - ctx->sub->framebuffer_srgb_enabled = false; - } - } else if (has_feature(feat_srgb_write_control)) { - struct vrend_surface *surf = NULL; - bool use_srgb = false; - int i; - for (i = 0; i < ctx->sub->nr_cbufs; i++) { - if (ctx->sub->surf[i]) { - surf = ctx->sub->surf[i]; - if (util_format_is_srgb(surf->format)) { - use_srgb = true; - } - } - } - if (use_srgb) { - glEnable(GL_FRAMEBUFFER_SRGB_EXT); - } else { - glDisable(GL_FRAMEBUFFER_SRGB_EXT); - } - ctx->sub->framebuffer_srgb_enabled = use_srgb; - } - - glDrawBuffers(ctx->sub->nr_cbufs, buffers); -} - -void vrend_set_framebuffer_state(struct vrend_context *ctx, uint32_t nr_cbufs, - uint32_t surf_handle[PIPE_MAX_COLOR_BUFS], - uint32_t zsurf_handle) { - struct vrend_surface *surf, *zsurf; - int i; - int old_num; - GLint new_height = -1; - bool new_ibf = false; - - glBindFramebuffer(GL_FRAMEBUFFER, ctx->sub->fb_id); - - if (zsurf_handle) { - zsurf = vrend_object_lookup(ctx->sub->object_hash, zsurf_handle, - VIRGL_OBJECT_SURFACE); - if (!zsurf) - return; - } else - zsurf = NULL; - - if (ctx->sub->zsurf != zsurf) { - vrend_surface_reference(&ctx->sub->zsurf, zsurf); - vrend_hw_set_zsurf_texture(ctx); - } - - old_num = ctx->sub->nr_cbufs; - ctx->sub->nr_cbufs = nr_cbufs; - ctx->sub->old_nr_cbufs = old_num; - - for (i = 0; i < (int)nr_cbufs; i++) { - if (surf_handle[i] != 0) { - surf = vrend_object_lookup(ctx->sub->object_hash, surf_handle[i], - VIRGL_OBJECT_SURFACE); - if (!surf) - return; - } else - surf = NULL; - - if (ctx->sub->surf[i] != surf) { - vrend_surface_reference(&ctx->sub->surf[i], surf); - vrend_hw_set_color_surface(ctx, i); - } - } - - if (old_num > ctx->sub->nr_cbufs) { - for (i = ctx->sub->nr_cbufs; i < old_num; i++) { - vrend_surface_reference(&ctx->sub->surf[i], NULL); - vrend_hw_set_color_surface(ctx, i); - } - } - - /* find a buffer to set fb_height from */ - if (ctx->sub->nr_cbufs == 0 && !ctx->sub->zsurf) { - new_height = 0; - new_ibf = false; - } else if (ctx->sub->nr_cbufs == 0) { - new_height = - u_minify(ctx->sub->zsurf->texture->base.height0, ctx->sub->zsurf->val0); - new_ibf = ctx->sub->zsurf->texture->y_0_top ? true : false; - } else { - surf = NULL; - for (i = 0; i < ctx->sub->nr_cbufs; i++) { - if (ctx->sub->surf[i]) { - surf = ctx->sub->surf[i]; - break; - } - } - if (surf == NULL) - return; - new_height = u_minify(surf->texture->base.height0, surf->val0); - new_ibf = surf->texture->y_0_top ? true : false; - } - - if (new_height != -1) { - if (ctx->sub->fb_height != (uint32_t)new_height || - ctx->sub->inverted_fbo_content != new_ibf) { - ctx->sub->fb_height = new_height; - ctx->sub->inverted_fbo_content = new_ibf; - ctx->sub->viewport_state_dirty = (1 << 0); - } - } - - vrend_hw_emit_framebuffer_state(ctx); - - ctx->sub->shader_dirty = true; - ctx->sub->blend_state_dirty = true; -} - -void vrend_set_framebuffer_state_no_attach(UNUSED struct vrend_context *ctx, - uint32_t width, uint32_t height, - uint32_t layers, uint32_t samples) { - if (has_feature(feat_fb_no_attach)) { - glFramebufferParameteri(GL_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_WIDTH, - width); - glFramebufferParameteri(GL_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_HEIGHT, - height); - glFramebufferParameteri(GL_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_LAYERS, - layers); - glFramebufferParameteri(GL_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_SAMPLES, - samples); - } -} - -/* - * if the viewport Y scale factor is > 0 then we are rendering to - * an FBO already so don't need to invert rendering? - */ -void vrend_set_viewport_states(struct vrend_context *ctx, uint32_t start_slot, - uint32_t num_viewports, - const struct pipe_viewport_state *state) { - /* convert back to glViewport */ - GLint x, y; - GLsizei width, height; - GLfloat near_val, far_val; - bool viewport_is_negative = (state[0].scale[1] < 0) ? true : false; - uint i, idx; - - if (num_viewports > PIPE_MAX_VIEWPORTS || - start_slot > (PIPE_MAX_VIEWPORTS - num_viewports)) - return; - - for (i = 0; i < num_viewports; i++) { - GLfloat abs_s1 = fabsf(state[i].scale[1]); - - idx = start_slot + i; - width = state[i].scale[0] * 2.0f; - height = abs_s1 * 2.0f; - x = state[i].translate[0] - state[i].scale[0]; - y = state[i].translate[1] - state[i].scale[1]; - - if (!ctx->sub->rs_state.clip_halfz) { - near_val = state[i].translate[2] - state[i].scale[2]; - far_val = near_val + (state[i].scale[2] * 2.0); - } else { - near_val = state[i].translate[2]; - far_val = state[i].scale[2] + state[i].translate[2]; - } - - if (ctx->sub->vps[idx].cur_x != x || ctx->sub->vps[idx].cur_y != y || - ctx->sub->vps[idx].width != width || - ctx->sub->vps[idx].height != height || - ctx->sub->vps[idx].near_val != near_val || - ctx->sub->vps[idx].far_val != far_val || - (!(ctx->sub->viewport_state_initialized &= (1 << idx)))) { - ctx->sub->vps[idx].cur_x = x; - ctx->sub->vps[idx].cur_y = y; - ctx->sub->vps[idx].width = width; - ctx->sub->vps[idx].height = height; - ctx->sub->vps[idx].near_val = near_val; - ctx->sub->vps[idx].far_val = far_val; - ctx->sub->viewport_state_dirty |= (1 << idx); - } - - if (idx == 0) { - if (ctx->sub->viewport_is_negative != viewport_is_negative) - ctx->sub->viewport_is_negative = viewport_is_negative; - } - } -} - -int vrend_create_vertex_elements_state( - struct vrend_context *ctx, uint32_t handle, unsigned num_elements, - const struct pipe_vertex_element *elements) { - struct vrend_vertex_element_array *v; - const struct util_format_description *desc; - GLenum type; - uint i; - uint32_t ret_handle; - - if (num_elements > PIPE_MAX_ATTRIBS) - return EINVAL; - - v = CALLOC_STRUCT(vrend_vertex_element_array); - if (!v) - return ENOMEM; - - v->count = num_elements; - for (i = 0; i < num_elements; i++) { - memcpy(&v->elements[i].base, &elements[i], - sizeof(struct pipe_vertex_element)); - - desc = util_format_description(elements[i].src_format); - if (!desc) { - FREE(v); - return EINVAL; - } - - type = GL_FALSE; - if (desc->channel[0].type == UTIL_FORMAT_TYPE_FLOAT) { - if (desc->channel[0].size == 32) - type = GL_FLOAT; - else if (desc->channel[0].size == 64) - type = GL_FLOAT; - else if (desc->channel[0].size == 16) - type = GL_HALF_FLOAT; - } else if (desc->channel[0].type == UTIL_FORMAT_TYPE_UNSIGNED && - desc->channel[0].size == 8) - type = GL_UNSIGNED_BYTE; - else if (desc->channel[0].type == UTIL_FORMAT_TYPE_SIGNED && - desc->channel[0].size == 8) - type = GL_BYTE; - else if (desc->channel[0].type == UTIL_FORMAT_TYPE_UNSIGNED && - desc->channel[0].size == 16) - type = GL_UNSIGNED_SHORT; - else if (desc->channel[0].type == UTIL_FORMAT_TYPE_SIGNED && - desc->channel[0].size == 16) - type = GL_SHORT; - else if (desc->channel[0].type == UTIL_FORMAT_TYPE_UNSIGNED && - desc->channel[0].size == 32) - type = GL_UNSIGNED_INT; - else if (desc->channel[0].type == UTIL_FORMAT_TYPE_SIGNED && - desc->channel[0].size == 32) - type = GL_INT; - else if (elements[i].src_format == PIPE_FORMAT_R10G10B10A2_SSCALED || - elements[i].src_format == PIPE_FORMAT_R10G10B10A2_SNORM || - elements[i].src_format == PIPE_FORMAT_B10G10R10A2_SNORM) - type = GL_INT_2_10_10_10_REV; - else if (elements[i].src_format == PIPE_FORMAT_R10G10B10A2_USCALED || - elements[i].src_format == PIPE_FORMAT_R10G10B10A2_UNORM || - elements[i].src_format == PIPE_FORMAT_B10G10R10A2_UNORM) - type = GL_UNSIGNED_INT_2_10_10_10_REV; - else if (elements[i].src_format == PIPE_FORMAT_R11G11B10_FLOAT) - type = GL_UNSIGNED_INT_10F_11F_11F_REV; - - if (type == GL_FALSE) { - FREE(v); - return EINVAL; - } - - v->elements[i].type = type; - if (desc->channel[0].normalized) - v->elements[i].norm = GL_TRUE; - - if (desc->nr_channels == 4 && desc->swizzle[0] == UTIL_FORMAT_SWIZZLE_Z) - v->elements[i].nr_chan = 4; - else if (elements[i].src_format == PIPE_FORMAT_R11G11B10_FLOAT) - v->elements[i].nr_chan = 3; - else - v->elements[i].nr_chan = desc->nr_channels; - } - - if (has_feature(feat_gles31_vertex_attrib_binding)) { - glGenVertexArrays(1, &v->id); - glBindVertexArray(v->id); - for (i = 0; i < num_elements; i++) { - struct vrend_vertex_element *ve = &v->elements[i]; - - if (util_format_is_pure_integer(ve->base.src_format)) - glVertexAttribIFormat(i, ve->nr_chan, ve->type, ve->base.src_offset); - else - glVertexAttribFormat(i, ve->nr_chan, ve->type, ve->norm, - ve->base.src_offset); - glVertexAttribBinding(i, ve->base.vertex_buffer_index); - glVertexBindingDivisor(i, ve->base.instance_divisor); - glEnableVertexAttribArray(i); - } - } - ret_handle = - vrend_renderer_object_insert(ctx, v, sizeof(struct vrend_vertex_element), - handle, VIRGL_OBJECT_VERTEX_ELEMENTS); - if (!ret_handle) { - FREE(v); - return ENOMEM; - } - return 0; -} - -void vrend_bind_vertex_elements_state(struct vrend_context *ctx, - uint32_t handle) { - struct vrend_vertex_element_array *v; - - if (!handle) { - ctx->sub->ve = NULL; - return; - } - v = vrend_object_lookup(ctx->sub->object_hash, handle, - VIRGL_OBJECT_VERTEX_ELEMENTS); - if (!v) - return; - - if (ctx->sub->ve != v) - ctx->sub->vbo_dirty = true; - ctx->sub->ve = v; -} - -void vrend_set_constants(struct vrend_context *ctx, uint32_t shader, - UNUSED uint32_t index, uint32_t num_constant, - float *data) { - struct vrend_constants *consts; - - consts = &ctx->sub->consts[shader]; - ctx->sub->const_dirty[shader] = true; - - /* avoid reallocations by only growing the buffer */ - if (consts->num_allocated_consts < num_constant) { - free(consts->consts); - consts->consts = malloc(num_constant * sizeof(float)); - if (!consts->consts) - return; - consts->num_allocated_consts = num_constant; - } - - memcpy(consts->consts, data, num_constant * sizeof(unsigned int)); - consts->num_consts = num_constant; -} - -void vrend_set_uniform_buffer(struct vrend_context *ctx, uint32_t shader, - uint32_t index, uint32_t offset, uint32_t length, - uint32_t res_handle) { - struct vrend_resource *res; - - if (!has_feature(feat_ubo)) - return; - - if (res_handle) { - res = vrend_renderer_ctx_res_lookup(ctx, res_handle); - - if (!res) - return; - ctx->sub->cbs[shader][index].buffer = (struct pipe_resource *)res; - ctx->sub->cbs[shader][index].buffer_offset = offset; - ctx->sub->cbs[shader][index].buffer_size = length; - - ctx->sub->const_bufs_used_mask[shader] |= (1u << index); - } else { - ctx->sub->cbs[shader][index].buffer = NULL; - ctx->sub->cbs[shader][index].buffer_offset = 0; - ctx->sub->cbs[shader][index].buffer_size = 0; - ctx->sub->const_bufs_used_mask[shader] &= ~(1u << index); - } - ctx->sub->const_bufs_dirty[shader] |= (1u << index); -} - -void vrend_set_index_buffer(struct vrend_context *ctx, uint32_t res_handle, - uint32_t index_size, uint32_t offset) { - struct vrend_resource *res; - - ctx->sub->ib.index_size = index_size; - ctx->sub->ib.offset = offset; - if (res_handle) { - if (ctx->sub->index_buffer_res_id != res_handle) { - res = vrend_renderer_ctx_res_lookup(ctx, res_handle); - if (!res) { - vrend_resource_reference((struct vrend_resource **)&ctx->sub->ib.buffer, - NULL); - ctx->sub->index_buffer_res_id = 0; - return; - } - vrend_resource_reference((struct vrend_resource **)&ctx->sub->ib.buffer, - res); - ctx->sub->index_buffer_res_id = res_handle; - } - } else { - vrend_resource_reference((struct vrend_resource **)&ctx->sub->ib.buffer, - NULL); - ctx->sub->index_buffer_res_id = 0; - } -} - -void vrend_set_single_vbo(struct vrend_context *ctx, uint32_t index, - uint32_t stride, uint32_t buffer_offset, - uint32_t res_handle) { - struct vrend_resource *res; - - if (ctx->sub->vbo[index].stride != stride || - ctx->sub->vbo[index].buffer_offset != buffer_offset || - ctx->sub->vbo_res_ids[index] != res_handle) - ctx->sub->vbo_dirty = true; - - ctx->sub->vbo[index].stride = stride; - ctx->sub->vbo[index].buffer_offset = buffer_offset; - - if (res_handle == 0) { - vrend_resource_reference( - (struct vrend_resource **)&ctx->sub->vbo[index].buffer, NULL); - ctx->sub->vbo_res_ids[index] = 0; - } else if (ctx->sub->vbo_res_ids[index] != res_handle) { - res = vrend_renderer_ctx_res_lookup(ctx, res_handle); - if (!res) { - ctx->sub->vbo_res_ids[index] = 0; - return; - } - vrend_resource_reference( - (struct vrend_resource **)&ctx->sub->vbo[index].buffer, res); - ctx->sub->vbo_res_ids[index] = res_handle; - } -} - -void vrend_set_num_vbo(struct vrend_context *ctx, int num_vbo) { - int old_num = ctx->sub->num_vbos; - int i; - - ctx->sub->num_vbos = num_vbo; - ctx->sub->old_num_vbos = old_num; - - if (old_num != num_vbo) - ctx->sub->vbo_dirty = true; - - for (i = num_vbo; i < old_num; i++) { - vrend_resource_reference((struct vrend_resource **)&ctx->sub->vbo[i].buffer, - NULL); - ctx->sub->vbo_res_ids[i] = 0; - } -} - -void vrend_set_single_sampler_view(struct vrend_context *ctx, - uint32_t shader_type, uint32_t index, - uint32_t handle) { - struct vrend_sampler_view *view = NULL; - struct vrend_texture *tex; - - if (handle) { - view = vrend_object_lookup(ctx->sub->object_hash, handle, - VIRGL_OBJECT_SAMPLER_VIEW); - if (!view) { - ctx->sub->views[shader_type].views[index] = NULL; - return; - } - if (ctx->sub->views[shader_type].views[index] == view) { - return; - } - /* we should have a reference to this texture taken at create time */ - tex = (struct vrend_texture *)view->texture; - if (!tex) { - return; - } - - ctx->sub->sampler_views_dirty[shader_type] |= 1u << index; - - if (!has_bit(view->texture->storage_bits, VREND_STORAGE_GL_BUFFER)) { - if (view->texture->id == view->id) { - glBindTexture(view->target, view->id); - - if (util_format_is_depth_or_stencil(view->format)) { - if (has_feature(feat_stencil_texturing)) { - const struct util_format_description *desc = - util_format_description(view->format); - if (!util_format_has_depth(desc)) { - glTexParameteri(view->texture->target, - GL_DEPTH_STENCIL_TEXTURE_MODE, GL_STENCIL_INDEX); - } else { - glTexParameteri(view->texture->target, - GL_DEPTH_STENCIL_TEXTURE_MODE, - GL_DEPTH_COMPONENT); - } - } - } - - GLuint base_level = view->val1 & 0xff; - GLuint max_level = (view->val1 >> 8) & 0xff; - - if (tex->cur_base != base_level) { - glTexParameteri(view->texture->target, GL_TEXTURE_BASE_LEVEL, - base_level); - tex->cur_base = base_level; - } - if (tex->cur_max != max_level) { - glTexParameteri(view->texture->target, GL_TEXTURE_MAX_LEVEL, - max_level); - tex->cur_max = max_level; - } - if (tex->cur_swizzle_r != view->gl_swizzle_r) { - glTexParameteri(view->texture->target, GL_TEXTURE_SWIZZLE_R, - view->gl_swizzle_r); - tex->cur_swizzle_r = view->gl_swizzle_r; - } - if (tex->cur_swizzle_g != view->gl_swizzle_g) { - glTexParameteri(view->texture->target, GL_TEXTURE_SWIZZLE_G, - view->gl_swizzle_g); - tex->cur_swizzle_g = view->gl_swizzle_g; - } - if (tex->cur_swizzle_b != view->gl_swizzle_b) { - glTexParameteri(view->texture->target, GL_TEXTURE_SWIZZLE_B, - view->gl_swizzle_b); - tex->cur_swizzle_b = view->gl_swizzle_b; - } - if (tex->cur_swizzle_a != view->gl_swizzle_a) { - glTexParameteri(view->texture->target, GL_TEXTURE_SWIZZLE_A, - view->gl_swizzle_a); - tex->cur_swizzle_a = view->gl_swizzle_a; - } - if (tex->cur_srgb_decode != view->srgb_decode && - util_format_is_srgb(tex->base.base.format)) { - if (has_feature(feat_samplers)) - ctx->sub->sampler_views_dirty[shader_type] |= (1u << index); - else if (has_feature(feat_texture_srgb_decode)) { - glTexParameteri(view->texture->target, GL_TEXTURE_SRGB_DECODE_EXT, - view->srgb_decode); - tex->cur_srgb_decode = view->srgb_decode; - } - } - } - } else { - GLenum internalformat; - - if (!view->texture->tbo_tex_id) - glGenTextures(1, &view->texture->tbo_tex_id); - - glBindTexture(GL_TEXTURE_BUFFER, view->texture->tbo_tex_id); - internalformat = tex_conv_table[view->format].internalformat; - if (has_feature(feat_texture_buffer_range)) { - unsigned offset = view->val0; - unsigned size = view->val1 - view->val0 + 1; - int blsize = util_format_get_blocksize(view->format); - - offset *= blsize; - size *= blsize; - glTexBufferRange(GL_TEXTURE_BUFFER, internalformat, view->texture->id, - offset, size); - } else - glTexBuffer(GL_TEXTURE_BUFFER, internalformat, view->texture->id); - } - } - - vrend_sampler_view_reference(&ctx->sub->views[shader_type].views[index], - view); -} - -void vrend_set_num_sampler_views(struct vrend_context *ctx, - uint32_t shader_type, uint32_t start_slot, - uint32_t num_sampler_views) { - int last_slot = start_slot + num_sampler_views; - int i; - - for (i = last_slot; i < ctx->sub->views[shader_type].num_views; i++) - vrend_sampler_view_reference(&ctx->sub->views[shader_type].views[i], NULL); - - ctx->sub->views[shader_type].num_views = last_slot; -} - -void vrend_set_single_image_view(struct vrend_context *ctx, - uint32_t shader_type, uint32_t index, - uint32_t format, uint32_t access, - uint32_t layer_offset, uint32_t level_size, - uint32_t handle) { - struct vrend_image_view *iview = &ctx->sub->image_views[shader_type][index]; - struct vrend_resource *res; - - if (handle) { - if (!has_feature(feat_images)) - return; - - res = vrend_renderer_ctx_res_lookup(ctx, handle); - if (!res) - return; - iview->texture = res; - iview->format = tex_conv_table[format].internalformat; - iview->access = access; - iview->u.buf.offset = layer_offset; - iview->u.buf.size = level_size; - ctx->sub->images_used_mask[shader_type] |= (1u << index); - } else { - iview->texture = NULL; - iview->format = 0; - ctx->sub->images_used_mask[shader_type] &= ~(1u << index); - } -} - -void vrend_set_single_ssbo(struct vrend_context *ctx, uint32_t shader_type, - uint32_t index, uint32_t offset, uint32_t length, - uint32_t handle) { - struct vrend_ssbo *ssbo = &ctx->sub->ssbo[shader_type][index]; - struct vrend_resource *res; - - if (!has_feature(feat_ssbo)) - return; - - if (handle) { - res = vrend_renderer_ctx_res_lookup(ctx, handle); - if (!res) - return; - ssbo->res = res; - ssbo->buffer_offset = offset; - ssbo->buffer_size = length; - ctx->sub->ssbo_used_mask[shader_type] |= (1u << index); - } else { - ssbo->res = 0; - ssbo->buffer_offset = 0; - ssbo->buffer_size = 0; - ctx->sub->ssbo_used_mask[shader_type] &= ~(1u << index); - } -} - -void vrend_set_single_abo(struct vrend_context *ctx, uint32_t index, - uint32_t offset, uint32_t length, uint32_t handle) { - struct vrend_abo *abo = &ctx->sub->abo[index]; - struct vrend_resource *res; - - if (!has_feature(feat_atomic_counters)) - return; - - if (handle) { - res = vrend_renderer_ctx_res_lookup(ctx, handle); - if (!res) - return; - abo->res = res; - abo->buffer_offset = offset; - abo->buffer_size = length; - ctx->sub->abo_used_mask |= (1u << index); - } else { - abo->res = 0; - abo->buffer_offset = 0; - abo->buffer_size = 0; - ctx->sub->abo_used_mask &= ~(1u << index); - } -} - -void vrend_memory_barrier(UNUSED struct vrend_context *ctx, unsigned flags) { - GLbitfield gl_barrier = 0; - - if (!has_feature(feat_barrier)) - return; - - if ((flags & PIPE_BARRIER_ALL) == PIPE_BARRIER_ALL) - gl_barrier = GL_ALL_BARRIER_BITS; - else { - if (flags & PIPE_BARRIER_VERTEX_BUFFER) - gl_barrier |= GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT; - if (flags & PIPE_BARRIER_INDEX_BUFFER) - gl_barrier |= GL_ELEMENT_ARRAY_BARRIER_BIT; - if (flags & PIPE_BARRIER_CONSTANT_BUFFER) - gl_barrier |= GL_UNIFORM_BARRIER_BIT; - if (flags & PIPE_BARRIER_TEXTURE) - gl_barrier |= GL_TEXTURE_FETCH_BARRIER_BIT | GL_PIXEL_BUFFER_BARRIER_BIT; - if (flags & PIPE_BARRIER_IMAGE) - gl_barrier |= GL_SHADER_IMAGE_ACCESS_BARRIER_BIT; - if (flags & PIPE_BARRIER_INDIRECT_BUFFER) - gl_barrier |= GL_COMMAND_BARRIER_BIT; - if (flags & PIPE_BARRIER_FRAMEBUFFER) - gl_barrier |= GL_FRAMEBUFFER_BARRIER_BIT; - if (flags & PIPE_BARRIER_STREAMOUT_BUFFER) - gl_barrier |= GL_TRANSFORM_FEEDBACK_BARRIER_BIT; - if (flags & PIPE_BARRIER_SHADER_BUFFER) { - gl_barrier |= GL_ATOMIC_COUNTER_BARRIER_BIT; - if (has_feature(feat_ssbo_barrier)) - gl_barrier |= GL_SHADER_STORAGE_BARRIER_BIT; - } - } - glMemoryBarrier(gl_barrier); -} - -static void vrend_destroy_shader_object(void *obj_ptr) { - struct vrend_shader_selector *state = obj_ptr; - - vrend_shader_state_reference(&state, NULL); -} - -static inline bool can_emulate_logicop(enum pipe_logicop op) { - if (has_feature(feat_framebuffer_fetch)) - return true; - - /* These ops don't need to read back from the framebuffer */ - switch (op) { - case PIPE_LOGICOP_CLEAR: - case PIPE_LOGICOP_COPY: - case PIPE_LOGICOP_SET: - case PIPE_LOGICOP_COPY_INVERTED: - return true; - default: - return false; - } -} - -static inline void vrend_fill_shader_key(struct vrend_context *ctx, - struct vrend_shader_selector *sel, - struct vrend_shader_key *key) { - unsigned type = sel->type; - - int i; - bool add_alpha_test = true; - key->cbufs_are_a8_bitmask = 0; - for (i = 0; i < ctx->sub->nr_cbufs; i++) { - if (!ctx->sub->surf[i]) - continue; - if (util_format_is_pure_integer(ctx->sub->surf[i]->format)) - add_alpha_test = false; - key->surface_component_bits[i] = util_format_get_component_bits( - ctx->sub->surf[i]->format, UTIL_FORMAT_COLORSPACE_RGB, 0); - } - if (add_alpha_test) { - key->add_alpha_test = ctx->sub->dsa_state.alpha.enabled; - key->alpha_test = ctx->sub->dsa_state.alpha.func; - key->alpha_ref_val = ctx->sub->dsa_state.alpha.ref_value; - } - - key->pstipple_tex = ctx->sub->rs_state.poly_stipple_enable; - key->color_two_side = ctx->sub->rs_state.light_twoside; - - key->clip_plane_enable = ctx->sub->rs_state.clip_plane_enable; - key->flatshade = ctx->sub->rs_state.flatshade ? true : false; - - if (type == PIPE_SHADER_FRAGMENT && - can_emulate_logicop(ctx->sub->blend_state.logicop_func)) { - key->fs_logicop_enabled = ctx->sub->blend_state.logicop_enable; - key->fs_logicop_func = ctx->sub->blend_state.logicop_func; - key->fs_logicop_emulate_coherent = true; - } - - key->coord_replace = ctx->sub->rs_state.point_quad_rasterization - ? ctx->sub->rs_state.sprite_coord_enable - : 0; - - if (type == PIPE_SHADER_FRAGMENT) - key->fs_swizzle_output_rgb_to_bgr = ctx->sub->swizzle_output_rgb_to_bgr; - - if (ctx->sub->shaders[PIPE_SHADER_GEOMETRY]) - key->gs_present = true; - if (ctx->sub->shaders[PIPE_SHADER_TESS_CTRL]) - key->tcs_present = true; - if (ctx->sub->shaders[PIPE_SHADER_TESS_EVAL]) - key->tes_present = true; - - int prev_type = -1; - - /* Gallium sends and binds the shaders in the reverse order, so if an - * old shader is still bound we should ignore the "previous" (as in - * execution order) shader when the key is evaluated, unless the currently - * bound shader selector is actually refers to the current shader. */ - if (ctx->sub->shaders[type] == sel) { - switch (type) { - case PIPE_SHADER_GEOMETRY: - if (key->tcs_present || key->tes_present) - prev_type = PIPE_SHADER_TESS_EVAL; - else - prev_type = PIPE_SHADER_VERTEX; - break; - case PIPE_SHADER_FRAGMENT: - if (key->gs_present) - prev_type = PIPE_SHADER_GEOMETRY; - else if (key->tcs_present || key->tes_present) - prev_type = PIPE_SHADER_TESS_EVAL; - else - prev_type = PIPE_SHADER_VERTEX; - break; - case PIPE_SHADER_TESS_EVAL: - if (key->tcs_present) - prev_type = PIPE_SHADER_TESS_CTRL; - else - prev_type = PIPE_SHADER_VERTEX; - break; - case PIPE_SHADER_TESS_CTRL: - prev_type = PIPE_SHADER_VERTEX; - break; - default: - break; - } - } - - if (prev_type != -1 && ctx->sub->shaders[prev_type]) { - key->prev_stage_pervertex_out = - ctx->sub->shaders[prev_type]->sinfo.has_pervertex_out; - key->prev_stage_num_clip_out = - ctx->sub->shaders[prev_type]->sinfo.num_clip_out; - key->prev_stage_num_cull_out = - ctx->sub->shaders[prev_type]->sinfo.num_cull_out; - key->num_indirect_generic_inputs = - ctx->sub->shaders[prev_type]->sinfo.num_indirect_generic_outputs; - key->num_indirect_patch_inputs = - ctx->sub->shaders[prev_type]->sinfo.num_indirect_patch_outputs; - key->num_prev_generic_and_patch_outputs = - ctx->sub->shaders[prev_type]->sinfo.num_generic_and_patch_outputs; - key->guest_sent_io_arrays = - ctx->sub->shaders[prev_type]->sinfo.guest_sent_io_arrays; - - memcpy(key->prev_stage_generic_and_patch_outputs_layout, - ctx->sub->shaders[prev_type]->sinfo.generic_outputs_layout, - 64 * sizeof(struct vrend_layout_info)); - } - - int next_type = -1; - switch (type) { - case PIPE_SHADER_VERTEX: - if (key->tcs_present) - next_type = PIPE_SHADER_TESS_CTRL; - else if (key->gs_present) - next_type = PIPE_SHADER_GEOMETRY; - else if (key->tes_present) - next_type = PIPE_SHADER_TESS_CTRL; - else - next_type = PIPE_SHADER_FRAGMENT; - break; - case PIPE_SHADER_TESS_CTRL: - next_type = PIPE_SHADER_TESS_EVAL; - break; - case PIPE_SHADER_GEOMETRY: - next_type = PIPE_SHADER_FRAGMENT; - break; - case PIPE_SHADER_TESS_EVAL: - if (key->gs_present) - next_type = PIPE_SHADER_GEOMETRY; - else - next_type = PIPE_SHADER_FRAGMENT; - } - - if (next_type != -1 && ctx->sub->shaders[next_type]) { - key->num_indirect_generic_outputs = - ctx->sub->shaders[next_type]->sinfo.num_indirect_generic_inputs; - key->num_indirect_patch_outputs = - ctx->sub->shaders[next_type]->sinfo.num_indirect_patch_inputs; - key->generic_outputs_expected_mask = - ctx->sub->shaders[next_type]->sinfo.generic_inputs_emitted_mask; - } -} - -static inline int conv_shader_type(int type) { - switch (type) { - case PIPE_SHADER_VERTEX: - return GL_VERTEX_SHADER; - case PIPE_SHADER_FRAGMENT: - return GL_FRAGMENT_SHADER; - case PIPE_SHADER_GEOMETRY: - return GL_GEOMETRY_SHADER; - case PIPE_SHADER_TESS_CTRL: - return GL_TESS_CONTROL_SHADER; - case PIPE_SHADER_TESS_EVAL: - return GL_TESS_EVALUATION_SHADER; - case PIPE_SHADER_COMPUTE: - return GL_COMPUTE_SHADER; - default: - return 0; - } -} - -static int vrend_shader_create(struct vrend_context *ctx, - struct vrend_shader *shader, - struct vrend_shader_key key) { - - shader->id = glCreateShader(conv_shader_type(shader->sel->type)); - shader->compiled_fs_id = 0; - - if (shader->sel->tokens) { - bool ret = vrend_convert_shader(ctx, &ctx->shader_cfg, shader->sel->tokens, - shader->sel->req_local_mem, &key, - &shader->sel->sinfo, &shader->glsl_strings); - if (!ret) { - glDeleteShader(shader->id); - return -1; - } - } - - shader->key = key; - bool ret; - - ret = vrend_compile_shader(ctx, shader); - if (!ret) { - glDeleteShader(shader->id); - strarray_free(&shader->glsl_strings, true); - return -1; - } - return 0; -} - -static int vrend_shader_select(struct vrend_context *ctx, - struct vrend_shader_selector *sel, bool *dirty) { - struct vrend_shader_key key; - struct vrend_shader *shader = NULL; - int r; - - memset(&key, 0, sizeof(key)); - vrend_fill_shader_key(ctx, sel, &key); - - if (sel->current && !memcmp(&sel->current->key, &key, sizeof(key))) - return 0; - - if (sel->num_shaders > 1) { - struct vrend_shader *p = sel->current; - struct vrend_shader *c = p->next_variant; - while (c && memcmp(&c->key, &key, sizeof(key)) != 0) { - p = c; - c = c->next_variant; - } - if (c) { - p->next_variant = c->next_variant; - shader = c; - } - } - - if (!shader) { - shader = CALLOC_STRUCT(vrend_shader); - shader->sel = sel; - list_inithead(&shader->programs); - strarray_alloc(&shader->glsl_strings, SHADER_MAX_STRINGS); - - r = vrend_shader_create(ctx, shader, key); - if (r) { - sel->current = NULL; - FREE(shader); - return r; - } - sel->num_shaders++; - } - if (dirty) - *dirty = true; - - shader->next_variant = sel->current; - sel->current = shader; - return 0; -} - -static void * -vrend_create_shader_state(UNUSED struct vrend_context *ctx, - const struct pipe_stream_output_info *so_info, - uint32_t req_local_mem, unsigned pipe_shader_type) { - struct vrend_shader_selector *sel = CALLOC_STRUCT(vrend_shader_selector); - - if (!sel) - return NULL; - - sel->req_local_mem = req_local_mem; - sel->type = pipe_shader_type; - sel->sinfo.so_info = *so_info; - pipe_reference_init(&sel->reference, 1); - - return sel; -} - -static int vrend_finish_shader(struct vrend_context *ctx, - struct vrend_shader_selector *sel, - const struct tgsi_token *tokens) { - int r; - - sel->tokens = tgsi_dup_tokens(tokens); - - r = vrend_shader_select(ctx, sel, NULL); - if (r) { - return EINVAL; - } - return 0; -} - -int vrend_create_shader(struct vrend_context *ctx, uint32_t handle, - const struct pipe_stream_output_info *so_info, - uint32_t req_local_mem, const char *shd_text, - uint32_t offlen, uint32_t num_tokens, uint32_t type, - uint32_t pkt_length) { - struct vrend_shader_selector *sel = NULL; - int ret_handle; - bool new_shader = true, long_shader = false; - bool finished = false; - int ret; - - if (type > PIPE_SHADER_COMPUTE) - return EINVAL; - - if (type == PIPE_SHADER_GEOMETRY && !has_feature(feat_geometry_shader)) - return EINVAL; - - if ((type == PIPE_SHADER_TESS_CTRL || type == PIPE_SHADER_TESS_EVAL) && - !has_feature(feat_tessellation)) - return EINVAL; - - if (type == PIPE_SHADER_COMPUTE && !has_feature(feat_compute_shader)) - return EINVAL; - - if (offlen & VIRGL_OBJ_SHADER_OFFSET_CONT) - new_shader = false; - else if (((offlen + 3) / 4) > pkt_length) - long_shader = true; - - /* if we have an in progress one - don't allow a new shader - of that type or a different handle. */ - if (ctx->sub->long_shader_in_progress_handle[type]) { - if (new_shader == true) - return EINVAL; - if (handle != ctx->sub->long_shader_in_progress_handle[type]) - return EINVAL; - } - - if (new_shader) { - sel = vrend_create_shader_state(ctx, so_info, req_local_mem, type); - if (sel == NULL) - return ENOMEM; - - if (long_shader) { - sel->buf_len = ((offlen + 3) / 4) * 4; /* round up buffer size */ - sel->tmp_buf = malloc(sel->buf_len); - if (!sel->tmp_buf) { - ret = ENOMEM; - goto error; - } - memcpy(sel->tmp_buf, shd_text, pkt_length * 4); - sel->buf_offset = pkt_length * 4; - ctx->sub->long_shader_in_progress_handle[type] = handle; - } else - finished = true; - } else { - sel = - vrend_object_lookup(ctx->sub->object_hash, handle, VIRGL_OBJECT_SHADER); - if (!sel) { - ret = EINVAL; - goto error; - } - - offlen &= ~VIRGL_OBJ_SHADER_OFFSET_CONT; - if (offlen != sel->buf_offset) { - ret = EINVAL; - goto error; - } - - /*make sure no overflow */ - if (pkt_length * 4 < pkt_length || - pkt_length * 4 + sel->buf_offset < pkt_length * 4 || - pkt_length * 4 + sel->buf_offset < sel->buf_offset) { - ret = EINVAL; - goto error; - } - - if ((pkt_length * 4 + sel->buf_offset) > sel->buf_len) { - ret = EINVAL; - goto error; - } - - memcpy(sel->tmp_buf + sel->buf_offset, shd_text, pkt_length * 4); - - sel->buf_offset += pkt_length * 4; - if (sel->buf_offset >= sel->buf_len) { - finished = true; - shd_text = sel->tmp_buf; - } - } - - if (finished) { - struct tgsi_token *tokens; - - /* check for null termination */ - uint32_t last_chunk_offset = - sel->buf_offset ? sel->buf_offset : pkt_length * 4; - if (last_chunk_offset < 4 || - !memchr(shd_text + last_chunk_offset - 4, '\0', 4)) { - ret = EINVAL; - goto error; - } - - tokens = calloc(num_tokens + 10, sizeof(struct tgsi_token)); - if (!tokens) { - ret = ENOMEM; - goto error; - } - - if (!tgsi_text_translate((const char *)shd_text, tokens, num_tokens + 10)) { - free(tokens); - ret = EINVAL; - goto error; - } - - if (vrend_finish_shader(ctx, sel, tokens)) { - free(tokens); - ret = EINVAL; - goto error; - } else { - free(sel->tmp_buf); - sel->tmp_buf = NULL; - } - free(tokens); - ctx->sub->long_shader_in_progress_handle[type] = 0; - } - - if (new_shader) { - ret_handle = vrend_renderer_object_insert(ctx, sel, sizeof(*sel), handle, - VIRGL_OBJECT_SHADER); - if (ret_handle == 0) { - ret = ENOMEM; - goto error; - } - } - - return 0; - -error: - if (new_shader) - vrend_destroy_shader_selector(sel); - else - vrend_renderer_object_destroy(ctx, handle); - - return ret; -} - -void vrend_bind_shader(struct vrend_context *ctx, uint32_t handle, - uint32_t type) { - struct vrend_shader_selector *sel; - - if (type > PIPE_SHADER_COMPUTE) - return; - - if (handle == 0) { - if (type == PIPE_SHADER_COMPUTE) - ctx->sub->cs_shader_dirty = true; - else - ctx->sub->shader_dirty = true; - vrend_shader_state_reference(&ctx->sub->shaders[type], NULL); - return; - } - - sel = vrend_object_lookup(ctx->sub->object_hash, handle, VIRGL_OBJECT_SHADER); - if (!sel) - return; - - if (sel->type != type) - return; - - if (ctx->sub->shaders[sel->type] != sel) { - if (type == PIPE_SHADER_COMPUTE) - ctx->sub->cs_shader_dirty = true; - else - ctx->sub->shader_dirty = true; - ctx->sub->prog_ids[sel->type] = 0; - } - - vrend_shader_state_reference(&ctx->sub->shaders[sel->type], sel); -} - -void vrend_clear(struct vrend_context *ctx, unsigned buffers, - const union pipe_color_union *color, double depth, - unsigned stencil) { - GLbitfield bits = 0; - - if (ctx->in_error) - return; - - if (ctx->ctx_switch_pending) - vrend_finish_context_switch(ctx); - - vrend_update_frontface_state(ctx); - if (ctx->sub->stencil_state_dirty) - vrend_update_stencil_state(ctx); - if (ctx->sub->scissor_state_dirty) - vrend_update_scissor_state(ctx); - if (ctx->sub->viewport_state_dirty) - vrend_update_viewport_state(ctx); - - vrend_use_program(ctx, 0); - - glDisable(GL_SCISSOR_TEST); - - if (buffers & PIPE_CLEAR_COLOR) { - glClearColor(color->f[0], color->f[1], color->f[2], color->f[3]); - - /* This function implements Gallium's full clear callback (st->pipe->clear) - on the host. This callback requires no color component be masked. We must - unmask all components before calling glClear* and restore the previous - colormask afterwards, as Gallium expects. */ - if (ctx->sub->hw_blend_state.independent_blend_enable && - has_feature(feat_indep_blend)) { - int i; - for (i = 0; i < PIPE_MAX_COLOR_BUFS; i++) - glColorMaski(i, GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - } else - glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - } - - if (buffers & PIPE_CLEAR_DEPTH) { - /* gallium clears don't respect depth mask */ - glDepthMask(GL_TRUE); - glClearDepthf(depth); - } - - if (buffers & PIPE_CLEAR_STENCIL) { - glStencilMask(~0u); - glClearStencil(stencil); - } - - if (ctx->sub->hw_rs_state.rasterizer_discard) - glDisable(GL_RASTERIZER_DISCARD); - - if (buffers & PIPE_CLEAR_COLOR) { - uint32_t mask = 0; - int i; - for (i = 0; i < ctx->sub->nr_cbufs; i++) { - if (ctx->sub->surf[i]) - mask |= (1 << i); - } - if (mask != (buffers >> 2)) { - mask = buffers >> 2; - while (mask) { - i = u_bit_scan(&mask); - if (i < PIPE_MAX_COLOR_BUFS && ctx->sub->surf[i] && - util_format_is_pure_uint(ctx->sub->surf[i] && - ctx->sub->surf[i]->format)) - glClearBufferuiv(GL_COLOR, i, (GLuint *)color); - else if (i < PIPE_MAX_COLOR_BUFS && ctx->sub->surf[i] && - util_format_is_pure_sint(ctx->sub->surf[i] && - ctx->sub->surf[i]->format)) - glClearBufferiv(GL_COLOR, i, (GLint *)color); - else - glClearBufferfv(GL_COLOR, i, (GLfloat *)color); - } - } else - bits |= GL_COLOR_BUFFER_BIT; - } - if (buffers & PIPE_CLEAR_DEPTH) - bits |= GL_DEPTH_BUFFER_BIT; - if (buffers & PIPE_CLEAR_STENCIL) - bits |= GL_STENCIL_BUFFER_BIT; - - if (bits) - glClear(bits); - - /* Is it really necessary to restore the old states? The only reason we - * get here is because the guest cleared all those states but gallium - * didn't forward them before calling the clear command - */ - if (ctx->sub->hw_rs_state.rasterizer_discard) - glEnable(GL_RASTERIZER_DISCARD); - - if (buffers & PIPE_CLEAR_DEPTH) { - if (!ctx->sub->dsa_state.depth.writemask) - glDepthMask(GL_FALSE); - } - - /* Restore previous stencil buffer write masks for both front and back faces - */ - if (buffers & PIPE_CLEAR_STENCIL) { - glStencilMaskSeparate(GL_FRONT, ctx->sub->dsa_state.stencil[0].writemask); - glStencilMaskSeparate(GL_BACK, ctx->sub->dsa_state.stencil[1].writemask); - } - - /* Restore previous colormask */ - if (buffers & PIPE_CLEAR_COLOR) { - glColorMask( - ctx->sub->hw_blend_state.rt[0].colormask & PIPE_MASK_R ? GL_TRUE - : GL_FALSE, - ctx->sub->hw_blend_state.rt[0].colormask & PIPE_MASK_G ? GL_TRUE - : GL_FALSE, - ctx->sub->hw_blend_state.rt[0].colormask & PIPE_MASK_B ? GL_TRUE - : GL_FALSE, - ctx->sub->hw_blend_state.rt[0].colormask & PIPE_MASK_A ? GL_TRUE - : GL_FALSE); - } - if (ctx->sub->hw_rs_state.scissor) - glEnable(GL_SCISSOR_TEST); - else - glDisable(GL_SCISSOR_TEST); -} - -static void vrend_update_scissor_state(struct vrend_context *ctx) { - struct pipe_scissor_state *ss; - GLint y; - GLuint idx; - unsigned mask = ctx->sub->scissor_state_dirty; - - while (mask) { - idx = u_bit_scan(&mask); - if (idx >= PIPE_MAX_VIEWPORTS) - break; - ss = &ctx->sub->ss[idx]; - y = ss->miny; - - glScissor(ss->minx, y, ss->maxx - ss->minx, ss->maxy - ss->miny); - } - ctx->sub->scissor_state_dirty = 0; -} - -static void vrend_update_viewport_state(struct vrend_context *ctx) { - GLint cy; - unsigned mask = ctx->sub->viewport_state_dirty; - int idx; - while (mask) { - idx = u_bit_scan(&mask); - - if (ctx->sub->viewport_is_negative) - cy = ctx->sub->vps[idx].cur_y - ctx->sub->vps[idx].height; - else - cy = ctx->sub->vps[idx].cur_y; - - glViewport(ctx->sub->vps[idx].cur_x, cy, ctx->sub->vps[idx].width, - ctx->sub->vps[idx].height); - glDepthRangef(ctx->sub->vps[idx].near_val, ctx->sub->vps[idx].far_val); - } - - ctx->sub->viewport_state_dirty = 0; -} - -static GLenum get_gs_xfb_mode(GLenum mode) { - switch (mode) { - case GL_POINTS: - return GL_POINTS; - case GL_LINE_STRIP: - return GL_LINES; - case GL_TRIANGLE_STRIP: - return GL_TRIANGLES; - default: - return GL_POINTS; - } -} - -static GLenum get_tess_xfb_mode(int mode, bool is_point_mode) { - if (is_point_mode) - return GL_POINTS; - switch (mode) { - case GL_QUADS: - case GL_TRIANGLES: - return GL_TRIANGLES; - case GL_LINES: - return GL_LINES; - default: - return GL_POINTS; - } -} - -static GLenum get_xfb_mode(GLenum mode) { - switch (mode) { - case GL_POINTS: - return GL_POINTS; - case GL_TRIANGLES: - case GL_TRIANGLE_STRIP: - case GL_TRIANGLE_FAN: - case GL_QUADS: - case GL_QUAD_STRIP: - case GL_POLYGON: - return GL_TRIANGLES; - case GL_LINES: - case GL_LINE_LOOP: - case GL_LINE_STRIP: - return GL_LINES; - default: - return GL_POINTS; - } -} - -static void -vrend_draw_bind_vertex_legacy(struct vrend_context *ctx, - struct vrend_vertex_element_array *va) { - uint32_t enable_bitmask; - uint32_t disable_bitmask; - int i; - - enable_bitmask = 0; - disable_bitmask = ~((1ull << va->count) - 1); - for (i = 0; i < (int)va->count; i++) { - struct vrend_vertex_element *ve = &va->elements[i]; - int vbo_index = ve->base.vertex_buffer_index; - struct vrend_resource *res; - GLint loc; - - if (i >= ctx->sub->prog->ss[PIPE_SHADER_VERTEX]->sel->sinfo.num_inputs) - break; - res = (struct vrend_resource *)ctx->sub->vbo[vbo_index].buffer; - - if (!res) - continue; - - if (ctx->client->vrend_state->use_explicit_locations || - has_feature(feat_gles31_vertex_attrib_binding)) { - loc = i; - } else { - if (ctx->sub->prog->attrib_locs) { - loc = ctx->sub->prog->attrib_locs[i]; - } else - loc = -1; - - if (loc == -1) { - if (i == 0) - return; - continue; - } - } - - if (ve->type == GL_FALSE) - return; - - glBindBuffer(GL_ARRAY_BUFFER, res->id); - - if (ctx->sub->vbo[vbo_index].stride == 0) { - void *data; - /* for 0 stride we are kinda screwed */ - data = glMapBufferRange(GL_ARRAY_BUFFER, - ctx->sub->vbo[vbo_index].buffer_offset, - ve->nr_chan * sizeof(GLfloat), GL_MAP_READ_BIT); - - switch (ve->nr_chan) { - case 1: - glVertexAttrib1fv(loc, data); - break; - case 2: - glVertexAttrib2fv(loc, data); - break; - case 3: - glVertexAttrib3fv(loc, data); - break; - case 4: - default: - glVertexAttrib4fv(loc, data); - break; - } - glUnmapBuffer(GL_ARRAY_BUFFER); - disable_bitmask |= (1 << loc); - } else { - enable_bitmask |= (1 << loc); - if (util_format_is_pure_integer(ve->base.src_format)) { - glVertexAttribIPointer( - loc, ve->nr_chan, ve->type, ctx->sub->vbo[vbo_index].stride, - (void *)(unsigned long)(ve->base.src_offset + - ctx->sub->vbo[vbo_index].buffer_offset)); - } else { - glVertexAttribPointer( - loc, ve->nr_chan, ve->type, ve->norm, - ctx->sub->vbo[vbo_index].stride, - (void *)(unsigned long)(ve->base.src_offset + - ctx->sub->vbo[vbo_index].buffer_offset)); - } - glVertexAttribDivisor(loc, ve->base.instance_divisor); - } - } - if (ctx->sub->enabled_attribs_bitmask != enable_bitmask) { - uint32_t mask = ctx->sub->enabled_attribs_bitmask & disable_bitmask; - - while (mask) { - i = u_bit_scan(&mask); - glDisableVertexAttribArray(i); - } - ctx->sub->enabled_attribs_bitmask &= ~disable_bitmask; - - mask = ctx->sub->enabled_attribs_bitmask ^ enable_bitmask; - while (mask) { - i = u_bit_scan(&mask); - glEnableVertexAttribArray(i); - } - - ctx->sub->enabled_attribs_bitmask = enable_bitmask; - } -} - -static void -vrend_draw_bind_vertex_binding(struct vrend_context *ctx, - struct vrend_vertex_element_array *va) { - int i; - - glBindVertexArray(va->id); - - if (ctx->sub->vbo_dirty) { - GLsizei count = 0; - GLuint buffers[PIPE_MAX_ATTRIBS]; - GLintptr offsets[PIPE_MAX_ATTRIBS]; - GLsizei strides[PIPE_MAX_ATTRIBS]; - - for (i = 0; i < ctx->sub->num_vbos; i++) { - struct vrend_resource *res = - (struct vrend_resource *)ctx->sub->vbo[i].buffer; - if (!res) { - buffers[count] = 0; - offsets[count] = 0; - strides[count++] = 0; - } else { - buffers[count] = res->id; - offsets[count] = ctx->sub->vbo[i].buffer_offset, - strides[count++] = ctx->sub->vbo[i].stride; - } - } - for (i = ctx->sub->num_vbos; i < ctx->sub->old_num_vbos; i++) { - buffers[count] = 0; - offsets[count] = 0; - strides[count++] = 0; - } - - for (i = 0; i < count; ++i) - glBindVertexBuffer(i, buffers[i], offsets[i], strides[i]); - - ctx->sub->vbo_dirty = false; - } -} - -static int vrend_draw_bind_samplers_shader(struct vrend_context *ctx, - int shader_type, - int next_sampler_id) { - int index = 0; - - uint32_t dirty = ctx->sub->sampler_views_dirty[shader_type]; - - uint32_t mask = ctx->sub->prog->samplers_used_mask[shader_type]; - while (mask) { - int i = u_bit_scan(&mask); - - struct vrend_sampler_view *tview = ctx->sub->views[shader_type].views[i]; - if (dirty & (1 << i) && tview) { - if (ctx->sub->prog->shadow_samp_mask[shader_type] & (1 << i)) { - glUniform4f( - ctx->sub->prog->shadow_samp_mask_locs[shader_type][index], - (tview->gl_swizzle_r == GL_ZERO || tview->gl_swizzle_r == GL_ONE) - ? 0.0 - : 1.0, - (tview->gl_swizzle_g == GL_ZERO || tview->gl_swizzle_g == GL_ONE) - ? 0.0 - : 1.0, - (tview->gl_swizzle_b == GL_ZERO || tview->gl_swizzle_b == GL_ONE) - ? 0.0 - : 1.0, - (tview->gl_swizzle_a == GL_ZERO || tview->gl_swizzle_a == GL_ONE) - ? 0.0 - : 1.0); - glUniform4f(ctx->sub->prog->shadow_samp_add_locs[shader_type][index], - tview->gl_swizzle_r == GL_ONE ? 1.0 : 0.0, - tview->gl_swizzle_g == GL_ONE ? 1.0 : 0.0, - tview->gl_swizzle_b == GL_ONE ? 1.0 : 0.0, - tview->gl_swizzle_a == GL_ONE ? 1.0 : 0.0); - } - - if (tview->texture) { - GLuint id; - struct vrend_resource *texture = tview->texture; - GLenum target = tview->target; - - if (has_bit(tview->texture->storage_bits, VREND_STORAGE_GL_BUFFER)) { - id = texture->tbo_tex_id; - target = GL_TEXTURE_BUFFER; - } else - id = tview->id; - - glActiveTexture(GL_TEXTURE0 + next_sampler_id); - glBindTexture(target, id); - - if (ctx->sub->views[shader_type].old_ids[i] != id || - ctx->sub->sampler_views_dirty[shader_type] & (1 << i)) { - vrend_apply_sampler_state(ctx, texture, shader_type, i, - next_sampler_id, tview); - ctx->sub->views[shader_type].old_ids[i] = id; - } - dirty &= ~(1 << i); - } - } - next_sampler_id++; - index++; - } - ctx->sub->sampler_views_dirty[shader_type] = dirty; - - return next_sampler_id; -} - -static int vrend_draw_bind_ubo_shader(struct vrend_context *ctx, - int shader_type, int next_ubo_id) { - uint32_t mask, dirty, update; - struct pipe_constant_buffer *cb; - struct vrend_resource *res; - - if (!has_feature(feat_ubo)) - return next_ubo_id; - - mask = ctx->sub->prog->ubo_used_mask[shader_type]; - dirty = ctx->sub->const_bufs_dirty[shader_type]; - update = dirty & ctx->sub->const_bufs_used_mask[shader_type]; - - if (!update) - return next_ubo_id + util_bitcount(mask); - - while (mask) { - /* The const_bufs_used_mask stores the gallium uniform buffer indices */ - int i = u_bit_scan(&mask); - - if (update & (1 << i)) { - /* The cbs array is indexed using the gallium uniform buffer index */ - cb = &ctx->sub->cbs[shader_type][i]; - res = (struct vrend_resource *)cb->buffer; - - glBindBufferRange(GL_UNIFORM_BUFFER, next_ubo_id, res->id, - cb->buffer_offset, cb->buffer_size); - dirty &= ~(1 << i); - } - next_ubo_id++; - } - ctx->sub->const_bufs_dirty[shader_type] = dirty; - - return next_ubo_id; -} - -static void vrend_draw_bind_const_shader(struct vrend_context *ctx, - int shader_type, bool new_program) { - if (ctx->sub->consts[shader_type].consts && ctx->sub->shaders[shader_type] && - (ctx->sub->prog->const_location[shader_type] != -1) && - (ctx->sub->const_dirty[shader_type] || new_program)) { - glUniform4uiv(ctx->sub->prog->const_location[shader_type], - ctx->sub->shaders[shader_type]->sinfo.num_consts, - ctx->sub->consts[shader_type].consts); - ctx->sub->const_dirty[shader_type] = false; - } -} - -static void vrend_draw_bind_ssbo_shader(struct vrend_context *ctx, - int shader_type) { - uint32_t mask; - struct vrend_ssbo *ssbo; - struct vrend_resource *res; - int i; - - if (!has_feature(feat_ssbo)) - return; - - if (!ctx->sub->prog->ssbo_locs[shader_type]) - return; - - if (!ctx->sub->ssbo_used_mask[shader_type]) - return; - - mask = ctx->sub->ssbo_used_mask[shader_type]; - while (mask) { - i = u_bit_scan(&mask); - - ssbo = &ctx->sub->ssbo[shader_type][i]; - res = (struct vrend_resource *)ssbo->res; - glBindBufferRange(GL_SHADER_STORAGE_BUFFER, i, res->id, ssbo->buffer_offset, - ssbo->buffer_size); - } -} - -static void vrend_draw_bind_abo_shader(struct vrend_context *ctx) { - uint32_t mask; - struct vrend_abo *abo; - struct vrend_resource *res; - int i; - - if (!has_feature(feat_atomic_counters)) - return; - - mask = ctx->sub->abo_used_mask; - while (mask) { - i = u_bit_scan(&mask); - - abo = &ctx->sub->abo[i]; - res = (struct vrend_resource *)abo->res; - glBindBufferRange(GL_ATOMIC_COUNTER_BUFFER, i, res->id, abo->buffer_offset, - abo->buffer_size); - } -} - -static void vrend_draw_bind_images_shader(struct vrend_context *ctx, - int shader_type) { - GLenum access; - GLboolean layered; - struct vrend_image_view *iview; - uint32_t mask, tex_id, level, first_layer; - - if (!ctx->sub->images_used_mask[shader_type]) - return; - - if (!ctx->sub->prog->img_locs[shader_type]) - return; - - if (!has_feature(feat_images)) - return; - - mask = ctx->sub->images_used_mask[shader_type]; - while (mask) { - unsigned i = u_bit_scan(&mask); - - if (!(ctx->sub->prog->images_used_mask[shader_type] & (1 << i))) - continue; - iview = &ctx->sub->image_views[shader_type][i]; - tex_id = iview->texture->id; - if (has_bit(iview->texture->storage_bits, VREND_STORAGE_GL_BUFFER)) { - if (!iview->texture->tbo_tex_id) - glGenTextures(1, &iview->texture->tbo_tex_id); - - /* glTexBuffer doesn't accept GL_RGBA8_SNORM, find an appropriate - * replacement. */ - uint32_t format = - (iview->format == GL_RGBA8_SNORM) ? GL_RGBA8UI : iview->format; - - glBindBuffer(GL_TEXTURE_BUFFER, iview->texture->id); - glBindTexture(GL_TEXTURE_BUFFER, iview->texture->tbo_tex_id); - - if (has_feature(feat_arb_or_gles_ext_texture_buffer)) - glTexBuffer(GL_TEXTURE_BUFFER, format, iview->texture->id); - - tex_id = iview->texture->tbo_tex_id; - level = first_layer = 0; - layered = GL_TRUE; - } else { - level = iview->u.tex.level; - first_layer = iview->u.tex.first_layer; - layered = !((iview->texture->base.array_size > 1 || - iview->texture->base.depth0 > 1) && - (iview->u.tex.first_layer == iview->u.tex.last_layer)); - } - - switch (iview->access) { - case PIPE_IMAGE_ACCESS_READ: - access = GL_READ_ONLY; - break; - case PIPE_IMAGE_ACCESS_WRITE: - access = GL_WRITE_ONLY; - break; - case PIPE_IMAGE_ACCESS_READ_WRITE: - access = GL_READ_WRITE; - break; - default: - return; - } - - glBindImageTexture(i, tex_id, level, layered, first_layer, access, - iview->format); - } -} - -static void vrend_draw_bind_objects(struct vrend_context *ctx, - bool new_program) { - int next_ubo_id = 0, next_sampler_id = 0; - for (int shader_type = PIPE_SHADER_VERTEX; - shader_type <= ctx->sub->last_shader_idx; shader_type++) { - next_ubo_id = vrend_draw_bind_ubo_shader(ctx, shader_type, next_ubo_id); - vrend_draw_bind_const_shader(ctx, shader_type, new_program); - next_sampler_id = - vrend_draw_bind_samplers_shader(ctx, shader_type, next_sampler_id); - vrend_draw_bind_images_shader(ctx, shader_type); - vrend_draw_bind_ssbo_shader(ctx, shader_type); - } - - vrend_draw_bind_abo_shader(ctx); - - if (ctx->sub->prog->fs_stipple_loc != -1) { - glActiveTexture(GL_TEXTURE0 + next_sampler_id); - glBindTexture(GL_TEXTURE_2D, ctx->pstipple_tex_id); - glUniform1i(ctx->sub->prog->fs_stipple_loc, next_sampler_id); - } -} - -static void vrend_inject_tcs(struct vrend_context *ctx, - int vertices_per_patch) { - struct pipe_stream_output_info so_info; - - memset(&so_info, 0, sizeof(so_info)); - struct vrend_shader_selector *sel = - vrend_create_shader_state(ctx, &so_info, false, PIPE_SHADER_TESS_CTRL); - struct vrend_shader *shader; - shader = CALLOC_STRUCT(vrend_shader); - vrend_fill_shader_key(ctx, sel, &shader->key); - - shader->sel = sel; - list_inithead(&shader->programs); - strarray_alloc(&shader->glsl_strings, SHADER_MAX_STRINGS); - - vrend_shader_create_passthrough_tcs( - ctx, &ctx->shader_cfg, ctx->sub->shaders[PIPE_SHADER_VERTEX]->tokens, - &shader->key, ctx->client->vrend_state->tess_factors, &sel->sinfo, - &shader->glsl_strings, vertices_per_patch); - // Need to add inject the selected shader to the shader selector and then the - // code below can continue - sel->tokens = NULL; - sel->current = shader; - ctx->sub->shaders[PIPE_SHADER_TESS_CTRL] = sel; - ctx->sub->shaders[PIPE_SHADER_TESS_CTRL]->num_shaders = 1; - - shader->id = glCreateShader(conv_shader_type(shader->sel->type)); - vrend_compile_shader(ctx, shader); -} - -int vrend_draw_vbo(struct vrend_context *ctx, const struct pipe_draw_info *info, - uint32_t cso, uint32_t indirect_handle, - uint32_t indirect_draw_count_handle) { - int i; - bool new_program = false; - struct vrend_resource *indirect_res = NULL; - - if (ctx->in_error) - return 0; - - if (info->instance_count && !has_feature(feat_draw_instance)) - return EINVAL; - - if (info->start_instance || info->indirect.draw_count > 1) - return EINVAL; - - if (indirect_handle) { - if (!has_feature(feat_indirect_draw)) - return EINVAL; - - indirect_res = vrend_renderer_ctx_res_lookup(ctx, indirect_handle); - if (!indirect_res) - return 0; - } - - if (indirect_draw_count_handle) - return EINVAL; - - if (ctx->ctx_switch_pending) - vrend_finish_context_switch(ctx); - - vrend_update_frontface_state(ctx); - if (ctx->sub->stencil_state_dirty) - vrend_update_stencil_state(ctx); - if (ctx->sub->scissor_state_dirty) - vrend_update_scissor_state(ctx); - - if (ctx->sub->viewport_state_dirty) - vrend_update_viewport_state(ctx); - - if (ctx->sub->blend_state_dirty) - vrend_patch_blend_state(ctx); - - if (ctx->sub->shader_dirty || ctx->sub->swizzle_output_rgb_to_bgr) { - struct vrend_linked_shader_program *prog; - bool fs_dirty, vs_dirty, gs_dirty, tcs_dirty, tes_dirty; - bool dual_src = util_blend_state_is_dual(&ctx->sub->blend_state, 0); - bool same_prog; - - ctx->sub->shader_dirty = false; - - if (!ctx->sub->shaders[PIPE_SHADER_VERTEX] || - !ctx->sub->shaders[PIPE_SHADER_FRAGMENT]) - return 0; - - vrend_shader_select(ctx, ctx->sub->shaders[PIPE_SHADER_VERTEX], &vs_dirty); - - if (ctx->sub->shaders[PIPE_SHADER_TESS_CTRL] && - ctx->sub->shaders[PIPE_SHADER_TESS_CTRL]->tokens) - vrend_shader_select(ctx, ctx->sub->shaders[PIPE_SHADER_TESS_CTRL], - &tcs_dirty); - else if (ctx->sub->shaders[PIPE_SHADER_TESS_EVAL]) { - vrend_inject_tcs(ctx, info->vertices_per_patch); - - vrend_shader_select(ctx, ctx->sub->shaders[PIPE_SHADER_VERTEX], - &vs_dirty); - } - - if (ctx->sub->shaders[PIPE_SHADER_TESS_EVAL]) - vrend_shader_select(ctx, ctx->sub->shaders[PIPE_SHADER_TESS_EVAL], - &tes_dirty); - if (ctx->sub->shaders[PIPE_SHADER_GEOMETRY]) - vrend_shader_select(ctx, ctx->sub->shaders[PIPE_SHADER_GEOMETRY], - &gs_dirty); - vrend_shader_select(ctx, ctx->sub->shaders[PIPE_SHADER_FRAGMENT], - &fs_dirty); - - if (!ctx->sub->shaders[PIPE_SHADER_VERTEX]->current || - !ctx->sub->shaders[PIPE_SHADER_FRAGMENT]->current || - (ctx->sub->shaders[PIPE_SHADER_GEOMETRY] && - !ctx->sub->shaders[PIPE_SHADER_GEOMETRY]->current) || - (ctx->sub->shaders[PIPE_SHADER_TESS_CTRL] && - !ctx->sub->shaders[PIPE_SHADER_TESS_CTRL]->current) || - (ctx->sub->shaders[PIPE_SHADER_TESS_EVAL] && - !ctx->sub->shaders[PIPE_SHADER_TESS_EVAL]->current)) { - return 0; - } - same_prog = true; - if (ctx->sub->shaders[PIPE_SHADER_VERTEX]->current->id != - (GLuint)ctx->sub->prog_ids[PIPE_SHADER_VERTEX]) - same_prog = false; - if (ctx->sub->shaders[PIPE_SHADER_FRAGMENT]->current->id != - (GLuint)ctx->sub->prog_ids[PIPE_SHADER_FRAGMENT]) - same_prog = false; - if (ctx->sub->shaders[PIPE_SHADER_GEOMETRY] && - ctx->sub->shaders[PIPE_SHADER_GEOMETRY]->current->id != - (GLuint)ctx->sub->prog_ids[PIPE_SHADER_GEOMETRY]) - same_prog = false; - if (ctx->sub->prog && ctx->sub->prog->dual_src_linked != dual_src) - same_prog = false; - if (ctx->sub->shaders[PIPE_SHADER_TESS_CTRL] && - ctx->sub->shaders[PIPE_SHADER_TESS_CTRL]->current->id != - (GLuint)ctx->sub->prog_ids[PIPE_SHADER_TESS_CTRL]) - same_prog = false; - if (ctx->sub->shaders[PIPE_SHADER_TESS_EVAL] && - ctx->sub->shaders[PIPE_SHADER_TESS_EVAL]->current->id != - (GLuint)ctx->sub->prog_ids[PIPE_SHADER_TESS_EVAL]) - same_prog = false; - - if (!same_prog) { - prog = lookup_shader_program( - ctx, ctx->sub->shaders[PIPE_SHADER_VERTEX]->current->id, - ctx->sub->shaders[PIPE_SHADER_FRAGMENT]->current->id, - ctx->sub->shaders[PIPE_SHADER_GEOMETRY] - ? ctx->sub->shaders[PIPE_SHADER_GEOMETRY]->current->id - : 0, - ctx->sub->shaders[PIPE_SHADER_TESS_CTRL] - ? ctx->sub->shaders[PIPE_SHADER_TESS_CTRL]->current->id - : 0, - ctx->sub->shaders[PIPE_SHADER_TESS_EVAL] - ? ctx->sub->shaders[PIPE_SHADER_TESS_EVAL]->current->id - : 0, - dual_src); - if (!prog) { - prog = add_shader_program( - ctx, ctx->sub->shaders[PIPE_SHADER_VERTEX]->current, - ctx->sub->shaders[PIPE_SHADER_FRAGMENT]->current, - ctx->sub->shaders[PIPE_SHADER_GEOMETRY] - ? ctx->sub->shaders[PIPE_SHADER_GEOMETRY]->current - : NULL, - ctx->sub->shaders[PIPE_SHADER_TESS_CTRL] - ? ctx->sub->shaders[PIPE_SHADER_TESS_CTRL]->current - : NULL, - ctx->sub->shaders[PIPE_SHADER_TESS_EVAL] - ? ctx->sub->shaders[PIPE_SHADER_TESS_EVAL]->current - : NULL); - if (!prog) - return 0; - } - - ctx->sub->last_shader_idx = ctx->sub->shaders[PIPE_SHADER_TESS_EVAL] - ? PIPE_SHADER_TESS_EVAL - : (ctx->sub->shaders[PIPE_SHADER_GEOMETRY] - ? PIPE_SHADER_GEOMETRY - : PIPE_SHADER_FRAGMENT); - } else - prog = ctx->sub->prog; - if (ctx->sub->prog != prog) { - new_program = true; - ctx->sub->prog_ids[PIPE_SHADER_VERTEX] = - ctx->sub->shaders[PIPE_SHADER_VERTEX]->current->id; - ctx->sub->prog_ids[PIPE_SHADER_FRAGMENT] = - ctx->sub->shaders[PIPE_SHADER_FRAGMENT]->current->id; - if (ctx->sub->shaders[PIPE_SHADER_GEOMETRY]) - ctx->sub->prog_ids[PIPE_SHADER_GEOMETRY] = - ctx->sub->shaders[PIPE_SHADER_GEOMETRY]->current->id; - if (ctx->sub->shaders[PIPE_SHADER_TESS_CTRL]) - ctx->sub->prog_ids[PIPE_SHADER_TESS_CTRL] = - ctx->sub->shaders[PIPE_SHADER_TESS_CTRL]->current->id; - if (ctx->sub->shaders[PIPE_SHADER_TESS_EVAL]) - ctx->sub->prog_ids[PIPE_SHADER_TESS_EVAL] = - ctx->sub->shaders[PIPE_SHADER_TESS_EVAL]->current->id; - ctx->sub->prog_ids[PIPE_SHADER_COMPUTE] = -1; - ctx->sub->prog = prog; - - /* mark all constbufs and sampler views as dirty */ - for (int stage = PIPE_SHADER_VERTEX; stage <= PIPE_SHADER_FRAGMENT; - stage++) { - ctx->sub->const_bufs_dirty[stage] = ~0; - ctx->sub->sampler_views_dirty[stage] = ~0; - } - - prog->ref_context = ctx->sub; - } - } - if (!ctx->sub->prog) - return 0; - - vrend_use_program(ctx, ctx->sub->prog->id); - - vrend_draw_bind_objects(ctx, new_program); - - if (!ctx->sub->ve) - return 0; - float viewport_neg_val = ctx->sub->viewport_is_negative ? -1.0 : 1.0; - if (ctx->sub->prog->viewport_neg_val != viewport_neg_val) { - glUniform1f(ctx->sub->prog->vs_ws_adjust_loc, viewport_neg_val); - ctx->sub->prog->viewport_neg_val = viewport_neg_val; - } - - if (ctx->sub->rs_state.clip_plane_enable) { - for (i = 0; i < 8; i++) { - glUniform4fv(ctx->sub->prog->clip_locs[i], 1, - (const GLfloat *)&ctx->sub->ucp_state.ucp[i]); - } - } - - if (has_feature(feat_gles31_vertex_attrib_binding)) - vrend_draw_bind_vertex_binding(ctx, ctx->sub->ve); - else - vrend_draw_bind_vertex_legacy(ctx, ctx->sub->ve); - - for (i = 0; i < ctx->sub->prog->ss[PIPE_SHADER_VERTEX]->sel->sinfo.num_inputs; - i++) { - struct vrend_vertex_element_array *va = ctx->sub->ve; - struct vrend_vertex_element *ve = &va->elements[i]; - int vbo_index = ve->base.vertex_buffer_index; - if (!ctx->sub->vbo[vbo_index].buffer) - return 0; - } - - if (info->indexed) { - struct vrend_resource *res = (struct vrend_resource *)ctx->sub->ib.buffer; - if (!res) - return 0; - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, res->id); - } else - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - - if (ctx->sub->current_so) { - if (ctx->sub->current_so->xfb_state == XFB_STATE_STARTED_NEED_BEGIN) { - if (ctx->sub->shaders[PIPE_SHADER_GEOMETRY]) - glBeginTransformFeedback(get_gs_xfb_mode( - ctx->sub->shaders[PIPE_SHADER_GEOMETRY]->sinfo.gs_out_prim)); - else if (ctx->sub->shaders[PIPE_SHADER_TESS_EVAL]) - glBeginTransformFeedback(get_tess_xfb_mode( - ctx->sub->shaders[PIPE_SHADER_TESS_EVAL]->sinfo.tes_prim, - ctx->sub->shaders[PIPE_SHADER_TESS_EVAL]->sinfo.tes_point_mode)); - else - glBeginTransformFeedback(get_xfb_mode(info->mode)); - ctx->sub->current_so->xfb_state = XFB_STATE_STARTED; - } else if (ctx->sub->current_so->xfb_state == XFB_STATE_PAUSED) { - glResumeTransformFeedback(); - ctx->sub->current_so->xfb_state = XFB_STATE_STARTED; - } - } - - if (info->primitive_restart) { - glEnable(GL_PRIMITIVE_RESTART_FIXED_INDEX); - } - - if (has_feature(feat_indirect_draw)) { - GLint buf = indirect_res ? indirect_res->id : 0; - if (ctx->sub->draw_indirect_buffer != buf) { - glBindBuffer(GL_DRAW_INDIRECT_BUFFER, buf); - ctx->sub->draw_indirect_buffer = buf; - } - } - - if (info->vertices_per_patch && has_feature(feat_tessellation)) - glPatchParameteri(GL_PATCH_VERTICES, info->vertices_per_patch); - - /* set the vertex state up now on a delay */ - if (!info->indexed) { - GLenum mode = info->mode; - int count = cso ? cso : info->count; - int start = cso ? 0 : info->start; - - if (indirect_handle) { - glDrawArraysIndirect( - mode, (GLvoid const *)(unsigned long)info->indirect.offset); - } else if (info->instance_count <= 1) - glDrawArrays(mode, start, count); - else - glDrawArraysInstanced(mode, start, count, info->instance_count); - } else { - GLenum elsz; - GLenum mode = info->mode; - switch (ctx->sub->ib.index_size) { - case 1: - elsz = GL_UNSIGNED_BYTE; - break; - case 2: - elsz = GL_UNSIGNED_SHORT; - break; - case 4: - default: - elsz = GL_UNSIGNED_INT; - break; - } - - if (indirect_handle) { - glDrawElementsIndirect( - mode, elsz, (GLvoid const *)(unsigned long)info->indirect.offset); - } else if (info->index_bias) { - if (info->instance_count > 1) - glDrawElementsInstancedBaseVertex( - mode, info->count, elsz, (void *)(unsigned long)ctx->sub->ib.offset, - info->instance_count, info->index_bias); - else if (info->min_index != 0 || info->max_index != (unsigned)-1) - glDrawRangeElementsBaseVertex( - mode, info->min_index, info->max_index, info->count, elsz, - (void *)(unsigned long)ctx->sub->ib.offset, info->index_bias); - else - glDrawElementsBaseVertex(mode, info->count, elsz, - (void *)(unsigned long)ctx->sub->ib.offset, - info->index_bias); - } else if (info->instance_count > 1) { - glDrawElementsInstanced(mode, info->count, elsz, - (void *)(unsigned long)ctx->sub->ib.offset, - info->instance_count); - } else if (info->min_index != 0 || info->max_index != (unsigned)-1) - glDrawRangeElements(mode, info->min_index, info->max_index, info->count, - elsz, (void *)(unsigned long)ctx->sub->ib.offset); - else - glDrawElements(mode, info->count, elsz, - (void *)(unsigned long)ctx->sub->ib.offset); - } - - if (info->primitive_restart) { - glEnable(GL_PRIMITIVE_RESTART_FIXED_INDEX); - } - - if (ctx->sub->current_so && has_feature(feat_transform_feedback2)) { - if (ctx->sub->current_so->xfb_state == XFB_STATE_STARTED) { - glPauseTransformFeedback(); - ctx->sub->current_so->xfb_state = XFB_STATE_PAUSED; - } - } - return 0; -} - -void vrend_launch_grid(struct vrend_context *ctx, UNUSED uint32_t *block, - uint32_t *grid, uint32_t indirect_handle, - uint32_t indirect_offset) { - bool new_program = false; - struct vrend_resource *indirect_res = NULL; - - if (!has_feature(feat_compute_shader)) - return; - - if (ctx->sub->cs_shader_dirty) { - struct vrend_linked_shader_program *prog; - bool cs_dirty; - - ctx->sub->cs_shader_dirty = false; - - if (!ctx->sub->shaders[PIPE_SHADER_COMPUTE]) - return; - - vrend_shader_select(ctx, ctx->sub->shaders[PIPE_SHADER_COMPUTE], &cs_dirty); - if (!ctx->sub->shaders[PIPE_SHADER_COMPUTE]->current) - return; - if (ctx->sub->shaders[PIPE_SHADER_COMPUTE]->current->id != - (GLuint)ctx->sub->prog_ids[PIPE_SHADER_COMPUTE]) { - prog = lookup_cs_shader_program( - ctx, ctx->sub->shaders[PIPE_SHADER_COMPUTE]->current->id); - if (!prog) { - prog = add_cs_shader_program( - ctx, ctx->sub->shaders[PIPE_SHADER_COMPUTE]->current); - if (!prog) - return; - } - } else - prog = ctx->sub->prog; - - if (ctx->sub->prog != prog) { - new_program = true; - ctx->sub->prog_ids[PIPE_SHADER_VERTEX] = -1; - ctx->sub->prog_ids[PIPE_SHADER_COMPUTE] = - ctx->sub->shaders[PIPE_SHADER_COMPUTE]->current->id; - ctx->sub->prog = prog; - prog->ref_context = ctx->sub; - } - ctx->sub->shader_dirty = true; - } - vrend_use_program(ctx, ctx->sub->prog->id); - - vrend_draw_bind_ubo_shader(ctx, PIPE_SHADER_COMPUTE, 0); - vrend_draw_bind_const_shader(ctx, PIPE_SHADER_COMPUTE, new_program); - vrend_draw_bind_samplers_shader(ctx, PIPE_SHADER_COMPUTE, 0); - vrend_draw_bind_images_shader(ctx, PIPE_SHADER_COMPUTE); - vrend_draw_bind_ssbo_shader(ctx, PIPE_SHADER_COMPUTE); - vrend_draw_bind_abo_shader(ctx); - - if (indirect_handle) { - indirect_res = vrend_renderer_ctx_res_lookup(ctx, indirect_handle); - if (!indirect_res) - return; - } - - if (indirect_res) - glBindBuffer(GL_DISPATCH_INDIRECT_BUFFER, indirect_res->id); - else - glBindBuffer(GL_DISPATCH_INDIRECT_BUFFER, 0); - - if (indirect_res) { - glDispatchComputeIndirect(indirect_offset); - } else { - glDispatchCompute(grid[0], grid[1], grid[2]); - } -} - -static GLenum translate_blend_func(uint32_t pipe_blend) { - switch (pipe_blend) { - case PIPE_BLEND_ADD: - return GL_FUNC_ADD; - case PIPE_BLEND_SUBTRACT: - return GL_FUNC_SUBTRACT; - case PIPE_BLEND_REVERSE_SUBTRACT: - return GL_FUNC_REVERSE_SUBTRACT; - case PIPE_BLEND_MIN: - return GL_MIN; - case PIPE_BLEND_MAX: - return GL_MAX; - default: - assert("invalid blend token()" == NULL); - return 0; - } -} - -static GLenum translate_blend_factor(uint32_t pipe_factor) { - switch (pipe_factor) { - case PIPE_BLENDFACTOR_ONE: - return GL_ONE; - case PIPE_BLENDFACTOR_SRC_COLOR: - return GL_SRC_COLOR; - case PIPE_BLENDFACTOR_SRC_ALPHA: - return GL_SRC_ALPHA; - - case PIPE_BLENDFACTOR_DST_COLOR: - return GL_DST_COLOR; - case PIPE_BLENDFACTOR_DST_ALPHA: - return GL_DST_ALPHA; - - case PIPE_BLENDFACTOR_CONST_COLOR: - return GL_CONSTANT_COLOR; - case PIPE_BLENDFACTOR_CONST_ALPHA: - return GL_CONSTANT_ALPHA; - - case PIPE_BLENDFACTOR_SRC_ALPHA_SATURATE: - return GL_SRC_ALPHA_SATURATE; - case PIPE_BLENDFACTOR_ZERO: - return GL_ZERO; - - case PIPE_BLENDFACTOR_INV_SRC_COLOR: - return GL_ONE_MINUS_SRC_COLOR; - case PIPE_BLENDFACTOR_INV_SRC_ALPHA: - return GL_ONE_MINUS_SRC_ALPHA; - - case PIPE_BLENDFACTOR_INV_DST_COLOR: - return GL_ONE_MINUS_DST_COLOR; - case PIPE_BLENDFACTOR_INV_DST_ALPHA: - return GL_ONE_MINUS_DST_ALPHA; - - case PIPE_BLENDFACTOR_INV_CONST_COLOR: - return GL_ONE_MINUS_CONSTANT_COLOR; - case PIPE_BLENDFACTOR_INV_CONST_ALPHA: - return GL_ONE_MINUS_CONSTANT_ALPHA; - - default: - assert("invalid blend token()" == NULL); - return 0; - } -} - -static GLenum translate_stencil_op(GLuint op) { - switch (op) { -#define CASE(x) \ - case PIPE_STENCIL_OP_##x: \ - return GL_##x - CASE(KEEP); - CASE(ZERO); - CASE(REPLACE); - CASE(INCR); - CASE(DECR); - CASE(INCR_WRAP); - CASE(DECR_WRAP); - CASE(INVERT); - default: - assert("invalid stencilop token()" == NULL); - return 0; - } -#undef CASE -} - -static inline bool is_dst_blend(int blend_factor) { - return (blend_factor == PIPE_BLENDFACTOR_DST_ALPHA || - blend_factor == PIPE_BLENDFACTOR_INV_DST_ALPHA); -} - -static inline int conv_dst_blend(int blend_factor) { - if (blend_factor == PIPE_BLENDFACTOR_DST_ALPHA) - return PIPE_BLENDFACTOR_ONE; - if (blend_factor == PIPE_BLENDFACTOR_INV_DST_ALPHA) - return PIPE_BLENDFACTOR_ZERO; - return blend_factor; -} - -static void vrend_hw_emit_blend(struct vrend_context *ctx, - struct pipe_blend_state *state) { - if (state->logicop_enable != ctx->sub->hw_blend_state.logicop_enable) { - ctx->sub->hw_blend_state.logicop_enable = state->logicop_enable; - if (can_emulate_logicop(state->logicop_func)) - ctx->sub->shader_dirty = true; - } - - if (state->independent_blend_enable && has_feature(feat_indep_blend) && - has_feature(feat_indep_blend_func)) { - /* ARB_draw_buffers_blend is required for this */ - int i; - - for (i = 0; i < PIPE_MAX_COLOR_BUFS; i++) { - if (state->rt[i].blend_enable) { - glBlendFuncSeparatei( - i, translate_blend_factor(state->rt[i].rgb_src_factor), - translate_blend_factor(state->rt[i].rgb_dst_factor), - translate_blend_factor(state->rt[i].alpha_src_factor), - translate_blend_factor(state->rt[i].alpha_dst_factor)); - glBlendEquationSeparatei(i, translate_blend_func(state->rt[i].rgb_func), - translate_blend_func(state->rt[i].alpha_func)); - glEnablei(GL_BLEND, i); - } else { - glDisablei(GL_BLEND, i); - } - - if (state->rt[i].colormask != ctx->sub->hw_blend_state.rt[i].colormask) { - ctx->sub->hw_blend_state.rt[i].colormask = state->rt[i].colormask; - glColorMaski(i, - state->rt[i].colormask & PIPE_MASK_R ? GL_TRUE : GL_FALSE, - state->rt[i].colormask & PIPE_MASK_G ? GL_TRUE : GL_FALSE, - state->rt[i].colormask & PIPE_MASK_B ? GL_TRUE : GL_FALSE, - state->rt[i].colormask & PIPE_MASK_A ? GL_TRUE : GL_FALSE); - } - } - } else { - if (state->rt[0].blend_enable) { - glBlendFuncSeparate( - translate_blend_factor(state->rt[0].rgb_src_factor), - translate_blend_factor(state->rt[0].rgb_dst_factor), - translate_blend_factor(state->rt[0].alpha_src_factor), - translate_blend_factor(state->rt[0].alpha_dst_factor)); - glBlendEquationSeparate(translate_blend_func(state->rt[0].rgb_func), - translate_blend_func(state->rt[0].alpha_func)); - glEnable(GL_BLEND); - } else { - glDisable(GL_BLEND); - } - - if (state->rt[0].colormask != ctx->sub->hw_blend_state.rt[0].colormask) { - int i; - for (i = 0; i < PIPE_MAX_COLOR_BUFS; i++) - ctx->sub->hw_blend_state.rt[i].colormask = state->rt[i].colormask; - glColorMask(state->rt[0].colormask & PIPE_MASK_R ? GL_TRUE : GL_FALSE, - state->rt[0].colormask & PIPE_MASK_G ? GL_TRUE : GL_FALSE, - state->rt[0].colormask & PIPE_MASK_B ? GL_TRUE : GL_FALSE, - state->rt[0].colormask & PIPE_MASK_A ? GL_TRUE : GL_FALSE); - } - } - ctx->sub->hw_blend_state.independent_blend_enable = - state->independent_blend_enable; - - if (has_feature(feat_multisample)) { - if (state->alpha_to_coverage) - glEnable(GL_SAMPLE_ALPHA_TO_COVERAGE); - else - glDisable(GL_SAMPLE_ALPHA_TO_COVERAGE); - } - - if (state->dither) - glEnable(GL_DITHER); - else - glDisable(GL_DITHER); -} - -/* there are a few reasons we might need to patch the blend state. - a) patching blend factors for dst with no alpha - b) patching colormask/blendcolor/blendfactors for A8/A16 format - emulation using GL_R8/GL_R16. -*/ -static void vrend_patch_blend_state(struct vrend_context *ctx) { - struct pipe_blend_state new_state = ctx->sub->blend_state; - struct pipe_blend_state *state = &ctx->sub->blend_state; - bool swizzle_blend_color = false; - struct pipe_blend_color blend_color = ctx->sub->blend_color; - int i; - - if (ctx->sub->nr_cbufs == 0) { - ctx->sub->blend_state_dirty = false; - return; - } - - for (i = 0; i < (state->independent_blend_enable ? PIPE_MAX_COLOR_BUFS : 1); - i++) { - if (i < ctx->sub->nr_cbufs && ctx->sub->surf[i]) { - if (!util_format_has_alpha(ctx->sub->surf[i]->format)) { - if (!(is_dst_blend(state->rt[i].rgb_src_factor) || - is_dst_blend(state->rt[i].rgb_dst_factor) || - is_dst_blend(state->rt[i].alpha_src_factor) || - is_dst_blend(state->rt[i].alpha_dst_factor))) - continue; - new_state.rt[i].rgb_src_factor = - conv_dst_blend(state->rt[i].rgb_src_factor); - new_state.rt[i].rgb_dst_factor = - conv_dst_blend(state->rt[i].rgb_dst_factor); - new_state.rt[i].alpha_src_factor = - conv_dst_blend(state->rt[i].alpha_src_factor); - new_state.rt[i].alpha_dst_factor = - conv_dst_blend(state->rt[i].alpha_dst_factor); - } - } - } - - vrend_hw_emit_blend(ctx, &new_state); - - if (swizzle_blend_color) { - blend_color.color[0] = blend_color.color[3]; - blend_color.color[1] = 0.0f; - blend_color.color[2] = 0.0f; - blend_color.color[3] = 0.0f; - } - - glBlendColor(blend_color.color[0], blend_color.color[1], blend_color.color[2], - blend_color.color[3]); - - ctx->sub->blend_state_dirty = false; -} - -void vrend_object_bind_blend(struct vrend_context *ctx, uint32_t handle) { - struct pipe_blend_state *state; - - if (handle == 0) { - memset(&ctx->sub->blend_state, 0, sizeof(ctx->sub->blend_state)); - glDisable(GL_BLEND); - return; - } - state = - vrend_object_lookup(ctx->sub->object_hash, handle, VIRGL_OBJECT_BLEND); - if (!state) - return; - - ctx->sub->shader_dirty = true; - ctx->sub->blend_state = *state; - - ctx->sub->blend_state_dirty = true; -} - -static void vrend_hw_emit_dsa(struct vrend_context *ctx) { - struct pipe_depth_stencil_alpha_state *state = &ctx->sub->dsa_state; - - if (state->depth.enabled) { - vrend_depth_test_enable(ctx, true); - glDepthFunc(GL_NEVER + state->depth.func); - if (state->depth.writemask) - glDepthMask(GL_TRUE); - else - glDepthMask(GL_FALSE); - } else - vrend_depth_test_enable(ctx, false); -} -void vrend_object_bind_dsa(struct vrend_context *ctx, uint32_t handle) { - struct pipe_depth_stencil_alpha_state *state; - - if (handle == 0) { - memset(&ctx->sub->dsa_state, 0, sizeof(ctx->sub->dsa_state)); - ctx->sub->dsa = NULL; - ctx->sub->stencil_state_dirty = true; - ctx->sub->shader_dirty = true; - vrend_hw_emit_dsa(ctx); - return; - } - - state = vrend_object_lookup(ctx->sub->object_hash, handle, VIRGL_OBJECT_DSA); - if (!state) - return; - - if (ctx->sub->dsa != state) { - ctx->sub->stencil_state_dirty = true; - ctx->sub->shader_dirty = true; - } - ctx->sub->dsa_state = *state; - ctx->sub->dsa = state; - - vrend_hw_emit_dsa(ctx); -} - -static void vrend_update_frontface_state(struct vrend_context *ctx) { - struct pipe_rasterizer_state *state = &ctx->sub->rs_state; - int front_ccw = state->front_ccw; - - front_ccw ^= (ctx->sub->inverted_fbo_content ? 0 : 1); - if (front_ccw) - glFrontFace(GL_CCW); - else - glFrontFace(GL_CW); -} - -void vrend_update_stencil_state(struct vrend_context *ctx) { - struct pipe_depth_stencil_alpha_state *state = ctx->sub->dsa; - int i; - if (!state) - return; - - if (!state->stencil[1].enabled) { - if (state->stencil[0].enabled) { - vrend_stencil_test_enable(ctx, true); - - glStencilOp(translate_stencil_op(state->stencil[0].fail_op), - translate_stencil_op(state->stencil[0].zfail_op), - translate_stencil_op(state->stencil[0].zpass_op)); - - glStencilFunc(GL_NEVER + state->stencil[0].func, - ctx->sub->stencil_refs[0], state->stencil[0].valuemask); - glStencilMask(state->stencil[0].writemask); - } else - vrend_stencil_test_enable(ctx, false); - } else { - vrend_stencil_test_enable(ctx, true); - - for (i = 0; i < 2; i++) { - GLenum face = (i == 1) ? GL_BACK : GL_FRONT; - glStencilOpSeparate(face, translate_stencil_op(state->stencil[i].fail_op), - translate_stencil_op(state->stencil[i].zfail_op), - translate_stencil_op(state->stencil[i].zpass_op)); - - glStencilFuncSeparate(face, GL_NEVER + state->stencil[i].func, - ctx->sub->stencil_refs[i], - state->stencil[i].valuemask); - glStencilMaskSeparate(face, state->stencil[i].writemask); - } - } - ctx->sub->stencil_state_dirty = false; -} - -static void vrend_hw_emit_rs(struct vrend_context *ctx) { - struct pipe_rasterizer_state *state = &ctx->sub->rs_state; - int i; - - /* line_width < 0 is invalid, the guest sometimes forgot to set it. */ - glLineWidth(state->line_width <= 0 ? 1.0f : state->line_width); - - if (state->rasterizer_discard != ctx->sub->hw_rs_state.rasterizer_discard) { - ctx->sub->hw_rs_state.rasterizer_discard = state->rasterizer_discard; - if (state->rasterizer_discard) - glEnable(GL_RASTERIZER_DISCARD); - else - glDisable(GL_RASTERIZER_DISCARD); - } - - if (state->offset_tri) { - glEnable(GL_POLYGON_OFFSET_FILL); - } else { - glDisable(GL_POLYGON_OFFSET_FILL); - } - - if (state->flatshade != ctx->sub->hw_rs_state.flatshade) { - ctx->sub->hw_rs_state.flatshade = state->flatshade; - } - - if (state->flatshade_first != ctx->sub->hw_rs_state.flatshade_first) { - ctx->sub->hw_rs_state.flatshade_first = state->flatshade_first; - } - - glPolygonOffset(state->offset_scale, state->offset_units); - - if (state->poly_stipple_enable && !ctx->pstip_inited) { - vrend_init_pstipple_texture(ctx); - } - - if (state->cull_face != PIPE_FACE_NONE) { - switch (state->cull_face) { - case PIPE_FACE_FRONT: - glCullFace(GL_FRONT); - break; - case PIPE_FACE_BACK: - glCullFace(GL_BACK); - break; - case PIPE_FACE_FRONT_AND_BACK: - glCullFace(GL_FRONT_AND_BACK); - break; - } - glEnable(GL_CULL_FACE); - } else - glDisable(GL_CULL_FACE); - - if (has_feature(feat_multisample)) { - if (has_feature(feat_sample_mask)) { - if (state->multisample) - glEnable(GL_SAMPLE_MASK); - else - glDisable(GL_SAMPLE_MASK); - } - - if (has_feature(feat_sample_shading)) { - if (state->force_persample_interp) - glEnable(GL_SAMPLE_SHADING); - else - glDisable(GL_SAMPLE_SHADING); - } - } - - if (state->scissor) - glEnable(GL_SCISSOR_TEST); - else - glDisable(GL_SCISSOR_TEST); - ctx->sub->hw_rs_state.scissor = state->scissor; -} - -void vrend_object_bind_rasterizer(struct vrend_context *ctx, uint32_t handle) { - struct pipe_rasterizer_state *state; - - if (handle == 0) { - memset(&ctx->sub->rs_state, 0, sizeof(ctx->sub->rs_state)); - return; - } - - state = vrend_object_lookup(ctx->sub->object_hash, handle, - VIRGL_OBJECT_RASTERIZER); - - if (!state) - return; - - ctx->sub->rs_state = *state; - ctx->sub->shader_dirty = true; - vrend_hw_emit_rs(ctx); -} - -void vrend_bind_sampler_states(struct vrend_context *ctx, uint32_t shader_type, - uint32_t start_slot, uint32_t num_states, - uint32_t *handles) { - uint32_t i; - struct vrend_sampler_state *state; - - if (shader_type >= PIPE_SHADER_TYPES) - return; - - if (num_states > PIPE_MAX_SAMPLERS || - start_slot > (PIPE_MAX_SAMPLERS - num_states)) - return; - - ctx->sub->num_sampler_states[shader_type] = num_states; - - uint32_t dirty = 0; - for (i = 0; i < num_states; i++) { - if (handles[i] == 0) - state = NULL; - else - state = vrend_object_lookup(ctx->sub->object_hash, handles[i], - VIRGL_OBJECT_SAMPLER_STATE); - - ctx->sub->sampler_state[shader_type][i + start_slot] = state; - dirty |= 1 << (start_slot + i); - } - ctx->sub->sampler_views_dirty[shader_type] |= dirty; -} - -static bool -get_swizzled_border_color(enum virgl_formats fmt, - union pipe_color_union *in_border_color, - union pipe_color_union *out_border_color) { - const struct vrend_format_table *fmt_entry = - vrend_get_format_table_entry(fmt); - if ((fmt_entry->flags & VIRGL_TEXTURE_CAN_TEXTURE_STORAGE) && - (fmt_entry->bindings & VIRGL_BIND_PREFER_EMULATED_BGRA)) { - for (int i = 0; i < 4; ++i) { - int swz = fmt_entry->swizzle[i]; - switch (swz) { - case PIPE_SWIZZLE_ZERO: - out_border_color->ui[i] = 0; - break; - case PIPE_SWIZZLE_ONE: - out_border_color->ui[i] = 1; - break; - default: - out_border_color->ui[i] = in_border_color->ui[swz]; - } - } - return true; - } - return false; -} - -static void vrend_apply_sampler_state(struct vrend_context *ctx, - struct vrend_resource *res, - uint32_t shader_type, int id, - int sampler_id, - struct vrend_sampler_view *tview) { - struct vrend_texture *tex = (struct vrend_texture *)res; - struct vrend_sampler_state *vstate = ctx->sub->sampler_state[shader_type][id]; - struct pipe_sampler_state *state = &vstate->base; - bool set_all = false; - GLenum target = tex->base.target; - - if (!state) - return; - if (res->base.nr_samples > 0) { - tex->state = *state; - return; - } - - if (has_bit(tex->base.storage_bits, VREND_STORAGE_GL_BUFFER)) { - tex->state = *state; - return; - } - - if (has_feature(feat_samplers)) { - int sampler = vstate->ids[tview->srgb_decode == GL_SKIP_DECODE_EXT ? 0 : 1]; - union pipe_color_union border_color; - if (get_swizzled_border_color(tview->format, &state->border_color, - &border_color)) - glSamplerParameterIuiv(sampler, GL_TEXTURE_BORDER_COLOR, border_color.ui); - - glBindSampler(sampler_id, sampler); - return; - } - - if (tex->state.max_lod == -1) - set_all = true; - - if (tex->state.wrap_s != state->wrap_s || set_all) - glTexParameteri(target, GL_TEXTURE_WRAP_S, convert_wrap(state->wrap_s)); - if (tex->state.wrap_t != state->wrap_t || set_all) - glTexParameteri(target, GL_TEXTURE_WRAP_T, convert_wrap(state->wrap_t)); - if (tex->state.wrap_r != state->wrap_r || set_all) - glTexParameteri(target, GL_TEXTURE_WRAP_R, convert_wrap(state->wrap_r)); - if (tex->state.min_img_filter != state->min_img_filter || - tex->state.min_mip_filter != state->min_mip_filter || set_all) - glTexParameterf( - target, GL_TEXTURE_MIN_FILTER, - convert_min_filter(state->min_img_filter, state->min_mip_filter)); - if (tex->state.mag_img_filter != state->mag_img_filter || set_all) - glTexParameterf(target, GL_TEXTURE_MAG_FILTER, - convert_mag_filter(state->mag_img_filter)); - if (res->target != GL_TEXTURE_RECTANGLE) { - if (tex->state.min_lod != state->min_lod || set_all) - glTexParameterf(target, GL_TEXTURE_MIN_LOD, state->min_lod); - if (tex->state.max_lod != state->max_lod || set_all) - glTexParameterf(target, GL_TEXTURE_MAX_LOD, state->max_lod); - } - - if (tex->state.compare_func != state->compare_func || set_all) - glTexParameteri(target, GL_TEXTURE_COMPARE_FUNC, - GL_NEVER + state->compare_func); - - if (memcmp(&tex->state.border_color, &state->border_color, 16) || set_all) { - union pipe_color_union border_color; - if (get_swizzled_border_color(tview->format, &state->border_color, - &border_color)) - glTexParameterIuiv(target, GL_TEXTURE_BORDER_COLOR, border_color.ui); - else - glTexParameterIuiv(target, GL_TEXTURE_BORDER_COLOR, - state->border_color.ui); - } - tex->state = *state; -} - -static GLenum tgsitargettogltarget(const enum pipe_texture_target target, - int nr_samples) { - switch (target) { - case PIPE_TEXTURE_1D: - return GL_TEXTURE_1D; - case PIPE_TEXTURE_2D: - return (nr_samples > 0) ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D; - case PIPE_TEXTURE_3D: - return GL_TEXTURE_3D; - case PIPE_TEXTURE_RECT: - return GL_TEXTURE_RECTANGLE; - case PIPE_TEXTURE_CUBE: - return GL_TEXTURE_CUBE_MAP; - - case PIPE_TEXTURE_1D_ARRAY: - return GL_TEXTURE_1D_ARRAY; - case PIPE_TEXTURE_2D_ARRAY: - return (nr_samples > 0) ? GL_TEXTURE_2D_MULTISAMPLE_ARRAY - : GL_TEXTURE_2D_ARRAY; - case PIPE_TEXTURE_CUBE_ARRAY: - return GL_TEXTURE_CUBE_MAP_ARRAY; - case PIPE_BUFFER: - default: - return PIPE_BUFFER; - } - return PIPE_BUFFER; -} - -static ssize_t write_full(int fd, const void *ptr, size_t count) { - const char *buf = ptr; - ssize_t ret = 0; - ssize_t total = 0; - - while (count) { - ret = write(fd, buf, count); - if (ret < 0) { - if (errno == EINTR) - continue; - break; - } - count -= ret; - buf += ret; - total += ret; - } - return total; -} - -int vrend_renderer_init(struct virgl_client *client, struct vrend_if_cbs *cbs) { - int gles_ver; - - client->vrend_state = CALLOC_STRUCT(vrend_state); - - vrend_object_init_resource_table(client); - - if (!vrend_clicbs) - vrend_clicbs = cbs; - - /* Give some defaults to be able to run the tests */ - client->vrend_state->max_texture_2d_size = - client->vrend_state->max_texture_3d_size = - client->vrend_state->max_texture_cube_size = 16384; - - gles_ver = vrend_gl_version(); - - if (!features_initialized) { - features_initialized = true; - init_features(gles_ver); - } - - glGetIntegerv(GL_MAX_DRAW_BUFFERS, - (GLint *)&client->vrend_state->max_draw_buffers); - - vrend_resource_set_destroy_callback(vrend_destroy_resource_object); - vrend_object_set_destroy_callback(VIRGL_OBJECT_QUERY, - vrend_destroy_query_object); - vrend_object_set_destroy_callback(VIRGL_OBJECT_SURFACE, - vrend_destroy_surface_object); - vrend_object_set_destroy_callback(VIRGL_OBJECT_SHADER, - vrend_destroy_shader_object); - vrend_object_set_destroy_callback(VIRGL_OBJECT_SAMPLER_VIEW, - vrend_destroy_sampler_view_object); - vrend_object_set_destroy_callback(VIRGL_OBJECT_STREAMOUT_TARGET, - vrend_destroy_so_target_object); - vrend_object_set_destroy_callback(VIRGL_OBJECT_SAMPLER_STATE, - vrend_destroy_sampler_state_object); - vrend_object_set_destroy_callback(VIRGL_OBJECT_VERTEX_ELEMENTS, - vrend_destroy_vertex_elements_object); - - if (!tex_conv_table_initialized) { - tex_conv_table_initialized = true; - vrend_build_format_list(); - vrend_check_texture_storage(tex_conv_table); - } - - list_inithead(&client->vrend_state->fence_list); - list_inithead(&client->vrend_state->fence_wait_list); - list_inithead(&client->vrend_state->waiting_query_list); - list_inithead(&client->vrend_state->active_ctx_list); - /* create 0 context */ - vrend_renderer_context_create_internal(client, 0); - - return 0; -} - -void vrend_renderer_fini(struct virgl_client *client) { - if (!client->vrend_state) - return; - - typedef void (*destroy_callback)(void *); - vrend_resource_set_destroy_callback( - (destroy_callback)vrend_renderer_resource_destroy); - - vrend_blitter_fini(client); - vrend_decode_reset(client, false); - vrend_object_fini_resource_table(client); - vrend_decode_reset(client, true); - - client->vrend_state->current_ctx = NULL; - client->vrend_state->current_hw_ctx = NULL; -} - -static void vrend_destroy_sub_context(struct virgl_client *client, - struct vrend_sub_context *sub) { - int i, j; - struct vrend_streamout_object *obj, *tmp; - - if (sub->fb_id) - glDeleteFramebuffers(1, &sub->fb_id); - - if (sub->blit_fb_ids[0]) - glDeleteFramebuffers(2, sub->blit_fb_ids); - - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - - if (!has_feature(feat_gles31_vertex_attrib_binding)) { - while (sub->enabled_attribs_bitmask) { - i = u_bit_scan(&sub->enabled_attribs_bitmask); - - glDisableVertexAttribArray(i); - } - glDeleteVertexArrays(1, &sub->vaoid); - } - - glBindVertexArray(0); - - if (sub->current_so) - glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, 0); - - LIST_FOR_EACH_ENTRY_SAFE(obj, tmp, &sub->streamout_list, head) { - vrend_destroy_streamout_object(obj); - } - - vrend_shader_state_reference(&sub->shaders[PIPE_SHADER_VERTEX], NULL); - vrend_shader_state_reference(&sub->shaders[PIPE_SHADER_FRAGMENT], NULL); - vrend_shader_state_reference(&sub->shaders[PIPE_SHADER_GEOMETRY], NULL); - vrend_shader_state_reference(&sub->shaders[PIPE_SHADER_TESS_CTRL], NULL); - vrend_shader_state_reference(&sub->shaders[PIPE_SHADER_TESS_EVAL], NULL); - vrend_shader_state_reference(&sub->shaders[PIPE_SHADER_COMPUTE], NULL); - - if (sub->prog) - sub->prog->ref_context = NULL; - - vrend_free_programs(sub); - for (i = 0; i < PIPE_SHADER_TYPES; i++) { - free(sub->consts[i].consts); - sub->consts[i].consts = NULL; - - for (j = 0; j < PIPE_MAX_SHADER_SAMPLER_VIEWS; j++) { - vrend_sampler_view_reference(&sub->views[i].views[j], NULL); - } - } - - if (sub->zsurf) - vrend_surface_reference(&sub->zsurf, NULL); - - for (i = 0; i < sub->nr_cbufs; i++) { - if (!sub->surf[i]) - continue; - vrend_surface_reference(&sub->surf[i], NULL); - } - - vrend_resource_reference((struct vrend_resource **)&sub->ib.buffer, NULL); - - vrend_object_fini_ctx_table(sub->object_hash); - vrend_clicbs->destroy_gl_context(client, sub->gl_context); - - list_del(&sub->head); - FREE(sub); -} - -bool vrend_destroy_context(struct vrend_context *ctx) { - bool switch_0 = (ctx == ctx->client->vrend_state->current_ctx); - struct vrend_context *cur = ctx->client->vrend_state->current_ctx; - struct vrend_sub_context *sub, *tmp; - if (switch_0) { - ctx->client->vrend_state->current_ctx = NULL; - ctx->client->vrend_state->current_hw_ctx = NULL; - } - - if (ctx->pstip_inited) - glDeleteTextures(1, &ctx->pstipple_tex_id); - ctx->pstip_inited = false; - - /* reset references on framebuffers */ - vrend_set_framebuffer_state(ctx, 0, NULL, 0); - - vrend_set_num_sampler_views(ctx, PIPE_SHADER_VERTEX, 0, 0); - vrend_set_num_sampler_views(ctx, PIPE_SHADER_FRAGMENT, 0, 0); - vrend_set_num_sampler_views(ctx, PIPE_SHADER_GEOMETRY, 0, 0); - vrend_set_num_sampler_views(ctx, PIPE_SHADER_TESS_CTRL, 0, 0); - vrend_set_num_sampler_views(ctx, PIPE_SHADER_TESS_EVAL, 0, 0); - vrend_set_num_sampler_views(ctx, PIPE_SHADER_COMPUTE, 0, 0); - - vrend_set_streamout_targets(ctx, 0, 0, NULL); - vrend_set_num_vbo(ctx, 0); - - vrend_set_index_buffer(ctx, 0, 0, 0); - - vrend_renderer_force_ctx_0(ctx->client); - LIST_FOR_EACH_ENTRY_SAFE(sub, tmp, &ctx->sub_ctxs, head) - vrend_destroy_sub_context(ctx->client, sub); - - vrend_object_fini_ctx_table(ctx->res_hash); - - list_del(&ctx->ctx_entry); - - FREE(ctx); - - if (!switch_0 && cur) - vrend_hw_switch_context(cur, true); - - return switch_0; -} - -struct vrend_context *vrend_create_context(struct virgl_client *client, - int id) { - struct vrend_context *grctx = CALLOC_STRUCT(vrend_context); - - if (!grctx) - return NULL; - - grctx->ctx_id = id; - grctx->client = client; - - list_inithead(&grctx->sub_ctxs); - list_inithead(&grctx->active_nontimer_query_list); - - grctx->res_hash = vrend_object_init_ctx_table(); - - grctx->shader_cfg.use_explicit_locations = - client->vrend_state->use_explicit_locations; - grctx->shader_cfg.max_draw_buffers = client->vrend_state->max_draw_buffers; - grctx->shader_cfg.has_es31_compat = has_feature(feat_gles31_compatibility); - - vrend_renderer_create_sub_ctx(grctx, 0); - vrend_renderer_set_sub_ctx(grctx, 0); - - vrend_get_glsl_version(&grctx->shader_cfg.glsl_version); - - list_addtail(&grctx->ctx_entry, &client->vrend_state->active_ctx_list); - - return grctx; -} - -int vrend_renderer_resource_attach_iov(struct virgl_client *client, - int res_handle, struct iovec *iov, - int num_iovs) { - struct vrend_resource *res; - - res = vrend_resource_lookup(client, res_handle, 0); - if (!res) - return EINVAL; - - if (res->iov) - return 0; - - /* work out size and max resource size */ - res->iov = iov; - res->num_iovs = num_iovs; - - if (has_bit(res->storage_bits, VREND_STORAGE_HOST_SYSTEM_MEMORY)) { - vrend_write_to_iovec(res->iov, res->num_iovs, 0, res->ptr, - res->base.width0); - } - - return 0; -} - -void vrend_renderer_resource_detach_iov(struct virgl_client *client, - int res_handle, struct iovec **iov_p, - int *num_iovs_p) { - struct vrend_resource *res; - res = vrend_resource_lookup(client, res_handle, 0); - if (!res) { - return; - } - if (iov_p) - *iov_p = res->iov; - if (num_iovs_p) - *num_iovs_p = res->num_iovs; - - if (has_bit(res->storage_bits, VREND_STORAGE_HOST_SYSTEM_MEMORY)) { - vrend_read_from_iovec(res->iov, res->num_iovs, 0, res->ptr, - res->base.width0); - } - - res->iov = NULL; - res->num_iovs = 0; -} - -static int -check_resource_valid(struct virgl_client *client, - struct vrend_renderer_resource_create_args *args) { - /* do not accept handle 0 */ - if (args->handle == 0) - return -1; - - /* limit the target */ - if (args->target >= PIPE_MAX_TEXTURE_TYPES) - return -1; - - if (args->format >= VIRGL_FORMAT_MAX) - return -1; - - bool format_can_texture_storage = - has_feature(feat_texture_storage) && - (tex_conv_table[args->format].flags & VIRGL_TEXTURE_CAN_TEXTURE_STORAGE); - - /* only texture 2d and 2d array can have multiple samples */ - if (args->nr_samples > 0) { - if (!has_feature(feat_texture_multisample)) - return -1; - - if (args->target != PIPE_TEXTURE_2D && - args->target != PIPE_TEXTURE_2D_ARRAY) - return -1; - - /* multisample can't have miplevels */ - if (args->last_level > 0) - return -1; - } - - if (args->last_level > 0) { - /* buffer and rect textures can't have mipmaps */ - if (args->target == PIPE_BUFFER) - return -1; - - if (args->target == PIPE_TEXTURE_RECT) - return -1; - - if (args->last_level > (floor(log2(MAX2(args->width, args->height))) + 1)) - return -1; - } - - if (args->flags != 0 && args->flags != VIRGL_RESOURCE_Y_0_TOP) - return -1; - - if (args->flags & VIRGL_RESOURCE_Y_0_TOP) { - if (args->target != PIPE_TEXTURE_2D && args->target != PIPE_TEXTURE_RECT) - return -1; - } - - /* array size for array textures only */ - if (args->target == PIPE_TEXTURE_CUBE) { - if (args->array_size != 6) - return -1; - } else if (args->target == PIPE_TEXTURE_CUBE_ARRAY) { - if (!has_feature(feat_cube_map_array)) - return -1; - - if (args->array_size % 6) - return -1; - } else if (args->array_size > 1) { - if (args->target != PIPE_TEXTURE_2D_ARRAY && - args->target != PIPE_TEXTURE_1D_ARRAY) - return -1; - - if (!has_feature(feat_texture_array)) - return -1; - } - - if (format_can_texture_storage && !args->width) - return -1; - - if (args->bind == 0 || args->bind == VIRGL_BIND_CUSTOM || - args->bind == VIRGL_BIND_STAGING || - args->bind == VIRGL_BIND_INDEX_BUFFER || - args->bind == VIRGL_BIND_STREAM_OUTPUT || - args->bind == VIRGL_BIND_VERTEX_BUFFER || - args->bind == VIRGL_BIND_CONSTANT_BUFFER || - args->bind == VIRGL_BIND_QUERY_BUFFER || - args->bind == VIRGL_BIND_COMMAND_ARGS || - args->bind == VIRGL_BIND_SHADER_BUFFER) { - if (args->target != PIPE_BUFFER) - return -1; - - if (args->height != 1 || args->depth != 1) - return -1; - - if (args->bind == VIRGL_BIND_QUERY_BUFFER) - return -1; - - if (args->bind == VIRGL_BIND_COMMAND_ARGS && - !has_feature(feat_indirect_draw)) - return -1; - } else { - if (!((args->bind & VIRGL_BIND_SAMPLER_VIEW) || - (args->bind & VIRGL_BIND_DEPTH_STENCIL) || - (args->bind & VIRGL_BIND_RENDER_TARGET) || - (args->bind & VIRGL_BIND_CURSOR) || - (args->bind & VIRGL_BIND_SHARED) || (args->bind & VIRGL_BIND_LINEAR))) - return -1; - - if (args->target == PIPE_TEXTURE_2D || args->target == PIPE_TEXTURE_RECT || - args->target == PIPE_TEXTURE_CUBE || - args->target == PIPE_TEXTURE_2D_ARRAY || - args->target == PIPE_TEXTURE_CUBE_ARRAY) { - if (args->depth != 1) - return -1; - - if (format_can_texture_storage && !args->height) - return -1; - } - if (args->target == PIPE_TEXTURE_1D || - args->target == PIPE_TEXTURE_1D_ARRAY) { - if (args->height != 1 || args->depth != 1) - return -1; - - if (args->width > client->vrend_state->max_texture_2d_size) - return -1; - } - - if (args->target == PIPE_TEXTURE_2D || args->target == PIPE_TEXTURE_RECT || - args->target == PIPE_TEXTURE_2D_ARRAY) { - if (args->width > client->vrend_state->max_texture_2d_size || - args->height > client->vrend_state->max_texture_2d_size) - return -1; - } - - if (args->target == PIPE_TEXTURE_3D) { - if (format_can_texture_storage && (!args->height || !args->depth)) - return -1; - - if (args->width > client->vrend_state->max_texture_3d_size || - args->height > client->vrend_state->max_texture_3d_size || - args->depth > client->vrend_state->max_texture_3d_size) - return -1; - } - if (args->target == PIPE_TEXTURE_2D_ARRAY || - args->target == PIPE_TEXTURE_CUBE_ARRAY || - args->target == PIPE_TEXTURE_1D_ARRAY) { - if (format_can_texture_storage && !args->array_size) - return -1; - } - if (args->target == PIPE_TEXTURE_CUBE || - args->target == PIPE_TEXTURE_CUBE_ARRAY) { - if (args->width != args->height) - return -1; - - if (args->width > client->vrend_state->max_texture_cube_size) - return -1; - } - } - return 0; -} - -static void vrend_create_buffer(struct vrend_resource *gr, uint32_t width) { - gr->storage_bits |= VREND_STORAGE_GL_BUFFER; - - glGenBuffers(1, &gr->id); - glBindBuffer(gr->target, gr->id); - glBufferData(gr->target, width, NULL, GL_STREAM_DRAW); - glBindBuffer(gr->target, 0); -} - -static inline void vrend_renderer_resource_copy_args( - struct vrend_renderer_resource_create_args *args, - struct vrend_resource *gr) { - assert(gr); - assert(args); - - gr->handle = args->handle; - gr->base.bind = args->bind; - gr->base.width0 = args->width; - gr->base.height0 = args->height; - gr->base.depth0 = args->depth; - gr->base.format = args->format; - gr->base.target = args->target; - gr->base.last_level = args->last_level; - gr->base.nr_samples = args->nr_samples; - gr->base.array_size = args->array_size; -} - -static int vrend_renderer_resource_allocate_texture(struct vrend_resource *gr) { - uint level; - GLenum internalformat, glformat, gltype; - enum virgl_formats format = gr->base.format; - struct vrend_texture *gt = (struct vrend_texture *)gr; - struct pipe_resource *pr = &gr->base; - - if (pr->width0 == 0) - return EINVAL; - - bool format_can_texture_storage = - has_feature(feat_texture_storage) && - (tex_conv_table[format].flags & VIRGL_TEXTURE_CAN_TEXTURE_STORAGE); - - /* On GLES there is no support for glTexImage*DMultisample and - * BGRA surfaces are also unlikely to support glTexStorage2DMultisample - * so we try to emulate here - */ - if (pr->nr_samples > 0 && !format_can_texture_storage) - gr->base.bind |= VIRGL_BIND_PREFER_EMULATED_BGRA; - - format_can_texture_storage = - has_feature(feat_texture_storage) && - (tex_conv_table[format].flags & VIRGL_TEXTURE_CAN_TEXTURE_STORAGE); - - if (format_can_texture_storage) - gr->storage_bits |= VREND_STORAGE_GL_IMMUTABLE; - - gr->target = tgsitargettogltarget(pr->target, pr->nr_samples); - gr->target = translate_gles_emulation_texture_target(gr->target); - gr->storage_bits |= VREND_STORAGE_GL_TEXTURE; - - glGenTextures(1, &gr->id); - glBindTexture(gr->target, gr->id); - - internalformat = tex_conv_table[format].internalformat; - glformat = tex_conv_table[format].glformat; - gltype = tex_conv_table[format].gltype; - - if (internalformat == 0) { - glBindTexture(gr->target, 0); - FREE(gt); - return EINVAL; - } - - if (pr->nr_samples > 0) { - if (format_can_texture_storage) { - if (gr->target == GL_TEXTURE_2D_MULTISAMPLE) { - glTexStorage2DMultisample(gr->target, pr->nr_samples, internalformat, - pr->width0, pr->height0, GL_TRUE); - } else { - glTexStorage3DMultisample(gr->target, pr->nr_samples, internalformat, - pr->width0, pr->height0, pr->array_size, - GL_TRUE); - } - } - } else if (gr->target == GL_TEXTURE_CUBE_MAP) { - int i; - if (format_can_texture_storage) - glTexStorage2D(GL_TEXTURE_CUBE_MAP, pr->last_level + 1, internalformat, - pr->width0, pr->height0); - else { - for (i = 0; i < 6; i++) { - GLenum ctarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X + i; - for (level = 0; level <= pr->last_level; level++) { - unsigned mwidth = u_minify(pr->width0, level); - unsigned mheight = u_minify(pr->height0, level); - - glTexImage2D(ctarget, level, internalformat, mwidth, mheight, 0, - glformat, gltype, NULL); - } - } - } - } else if (gr->target == GL_TEXTURE_3D || gr->target == GL_TEXTURE_2D_ARRAY || - gr->target == GL_TEXTURE_CUBE_MAP_ARRAY) { - if (format_can_texture_storage) { - unsigned depth_param = (gr->target == GL_TEXTURE_2D_ARRAY || - gr->target == GL_TEXTURE_CUBE_MAP_ARRAY) - ? pr->array_size - : pr->depth0; - glTexStorage3D(gr->target, pr->last_level + 1, internalformat, pr->width0, - pr->height0, depth_param); - } else { - for (level = 0; level <= pr->last_level; level++) { - unsigned depth_param = (gr->target == GL_TEXTURE_2D_ARRAY || - gr->target == GL_TEXTURE_CUBE_MAP_ARRAY) - ? pr->array_size - : u_minify(pr->depth0, level); - unsigned mwidth = u_minify(pr->width0, level); - unsigned mheight = u_minify(pr->height0, level); - glTexImage3D(gr->target, level, internalformat, mwidth, mheight, - depth_param, 0, glformat, gltype, NULL); - } - } - } else { - if (format_can_texture_storage) - glTexStorage2D(gr->target, pr->last_level + 1, internalformat, pr->width0, - gr->target == GL_TEXTURE_1D_ARRAY ? pr->array_size - : pr->height0); - else { - for (level = 0; level <= pr->last_level; level++) { - unsigned mwidth = u_minify(pr->width0, level); - unsigned mheight = u_minify(pr->height0, level); - glTexImage2D(gr->target, level, internalformat, mwidth, - gr->target == GL_TEXTURE_1D_ARRAY ? pr->array_size - : mheight, - 0, glformat, gltype, NULL); - } - } - } - - if (!format_can_texture_storage) { - glTexParameteri(gr->target, GL_TEXTURE_BASE_LEVEL, 0); - glTexParameteri(gr->target, GL_TEXTURE_MAX_LEVEL, pr->last_level); - } - - glBindTexture(gr->target, 0); - - gt->state.max_lod = -1; - gt->cur_swizzle_r = gt->cur_swizzle_g = gt->cur_swizzle_b = - gt->cur_swizzle_a = -1; - gt->cur_base = -1; - gt->cur_max = 10000; - return 0; -} - -int vrend_renderer_resource_create( - struct virgl_client *client, - struct vrend_renderer_resource_create_args *args, struct iovec *iov, - uint32_t num_iovs) { - struct vrend_resource *gr; - int ret; - - ret = check_resource_valid(client, args); - if (ret) - return EINVAL; - - gr = (struct vrend_resource *)CALLOC_STRUCT(vrend_texture); - if (!gr) - return ENOMEM; - - vrend_renderer_resource_copy_args(args, gr); - gr->iov = iov; - gr->num_iovs = num_iovs; - gr->storage_bits = VREND_STORAGE_GUEST_MEMORY; - - if (args->flags & VIRGL_RESOURCE_Y_0_TOP) - gr->y_0_top = true; - - pipe_reference_init(&gr->base.reference, 1); - - if (args->target == PIPE_BUFFER) { - if (args->bind == VIRGL_BIND_CUSTOM) { - /* use iovec directly when attached */ - gr->storage_bits |= VREND_STORAGE_HOST_SYSTEM_MEMORY; - gr->ptr = malloc(args->width); - if (!gr->ptr) { - FREE(gr); - return ENOMEM; - } - } else if (args->bind == VIRGL_BIND_STAGING) { - /* staging buffers only use guest memory -- nothing to do. */ - } else if (args->bind == VIRGL_BIND_INDEX_BUFFER) { - gr->target = GL_ELEMENT_ARRAY_BUFFER; - vrend_create_buffer(gr, args->width); - } else if (args->bind == VIRGL_BIND_STREAM_OUTPUT) { - gr->target = GL_TRANSFORM_FEEDBACK_BUFFER; - vrend_create_buffer(gr, args->width); - } else if (args->bind == VIRGL_BIND_VERTEX_BUFFER) { - gr->target = GL_ARRAY_BUFFER; - vrend_create_buffer(gr, args->width); - } else if (args->bind == VIRGL_BIND_CONSTANT_BUFFER) { - gr->target = GL_UNIFORM_BUFFER; - vrend_create_buffer(gr, args->width); - } else if (args->bind == VIRGL_BIND_COMMAND_ARGS) { - gr->target = GL_DRAW_INDIRECT_BUFFER; - vrend_create_buffer(gr, args->width); - } else if (args->bind == 0 || args->bind == VIRGL_BIND_SHADER_BUFFER) { - gr->target = GL_ARRAY_BUFFER; - vrend_create_buffer(gr, args->width); - } else if (args->bind & VIRGL_BIND_SAMPLER_VIEW) { - /* - * On Desktop we use GL_ARB_texture_buffer_object on GLES we use - * GL_EXT_texture_buffer (it is in the ANDRIOD extension pack). - */ -#if GL_TEXTURE_BUFFER != GL_TEXTURE_BUFFER_EXT -#error "GL_TEXTURE_BUFFER enums differ, they shouldn't." -#endif - - /* need to check GL version here */ - if (has_feature(feat_arb_or_gles_ext_texture_buffer)) { - gr->target = GL_TEXTURE_BUFFER; - } else { - gr->target = GL_PIXEL_PACK_BUFFER; - } - vrend_create_buffer(gr, args->width); - } else { - FREE(gr); - return EINVAL; - } - } else { - int r = vrend_renderer_resource_allocate_texture(gr); - if (r) { - FREE(gr); - return r; - } - } - - ret = vrend_resource_insert(client, gr, args->handle); - if (ret == 0) { - vrend_renderer_resource_destroy(gr); - return ENOMEM; - } - return 0; -} - -void vrend_renderer_resource_destroy(struct vrend_resource *res) { - if (res->readback_fb_id) - glDeleteFramebuffers(1, &res->readback_fb_id); - - if (has_bit(res->storage_bits, VREND_STORAGE_GL_TEXTURE)) { - glDeleteTextures(1, &res->id); - } else if (has_bit(res->storage_bits, VREND_STORAGE_GL_BUFFER)) { - glDeleteBuffers(1, &res->id); - if (res->tbo_tex_id) - glDeleteTextures(1, &res->tbo_tex_id); - } else if (has_bit(res->storage_bits, VREND_STORAGE_HOST_SYSTEM_MEMORY)) { - free(res->ptr); - } - - free(res); -} - -static void vrend_destroy_resource_object(void *obj_ptr) { - struct vrend_resource *res = obj_ptr; - - if (pipe_reference(&res->base.reference, NULL)) - vrend_renderer_resource_destroy(res); -} - -void vrend_renderer_resource_unref(struct virgl_client *client, - uint32_t res_handle) { - struct vrend_resource *res; - struct vrend_context *ctx; - - res = vrend_resource_lookup(client, res_handle, 0); - if (!res) - return; - - /* find in all contexts and detach also */ - - /* remove from any contexts */ - LIST_FOR_EACH_ENTRY(ctx, &client->vrend_state->active_ctx_list, ctx_entry) { - vrend_renderer_detach_res_ctx(ctx, res->handle); - } - - vrend_resource_remove(client, res->handle); -} - -struct virgl_sub_upload_data { - GLenum target; - struct pipe_box *box; -}; - -static void iov_buffer_upload(void *cookie, uint32_t doff, void *src, int len) { - struct virgl_sub_upload_data *d = cookie; - glBufferSubData(d->target, d->box->x + doff, len, src); -} - -static void vrend_scale_depth(void *ptr, int size, float scale_val) { - GLuint *ival = ptr; - const GLfloat myscale = 1.0f / 0xffffff; - int i; - for (i = 0; i < size / 4; i++) { - GLuint value = ival[i]; - GLfloat d = ((float)(value >> 8) * myscale) * scale_val; - d = CLAMP(d, 0.0F, 1.0F); - ival[i] = (int)(d / myscale) << 8; - } -} - -static void read_transfer_data(struct iovec *iov, unsigned int num_iovs, - char *data, enum virgl_formats format, - uint64_t offset, uint32_t src_stride, - uint32_t src_layer_stride, struct pipe_box *box, - bool invert) { - int blsize = util_format_get_blocksize(format); - uint32_t size = vrend_get_iovec_size(iov, num_iovs); - uint32_t send_size = - util_format_get_nblocks(format, box->width, box->height) * blsize * - box->depth; - uint32_t bwx = util_format_get_nblocksx(format, box->width) * blsize; - int32_t bh = util_format_get_nblocksy(format, box->height); - int d, h; - - if ((send_size == size || bh == 1) && !invert && box->depth == 1) - vrend_read_from_iovec(iov, num_iovs, offset, data, send_size); - else { - if (invert) { - for (d = 0; d < box->depth; d++) { - uint32_t myoffset = offset + d * src_layer_stride; - for (h = bh - 1; h >= 0; h--) { - void *ptr = data + (h * bwx) + d * (bh * bwx); - vrend_read_from_iovec(iov, num_iovs, myoffset, ptr, bwx); - myoffset += src_stride; - } - } - } else { - for (d = 0; d < box->depth; d++) { - uint32_t myoffset = offset + d * src_layer_stride; - for (h = 0; h < bh; h++) { - void *ptr = data + (h * bwx) + d * (bh * bwx); - vrend_read_from_iovec(iov, num_iovs, myoffset, ptr, bwx); - myoffset += src_stride; - } - } - } - } -} - -static void write_transfer_data(struct pipe_resource *res, struct iovec *iov, - unsigned num_iovs, char *data, - uint32_t dst_stride, struct pipe_box *box, - uint32_t level, uint64_t offset, bool invert) { - int blsize = util_format_get_blocksize(res->format); - uint32_t size = vrend_get_iovec_size(iov, num_iovs); - uint32_t send_size = - util_format_get_nblocks(res->format, box->width, box->height) * blsize * - box->depth; - uint32_t bwx = util_format_get_nblocksx(res->format, box->width) * blsize; - int32_t bh = util_format_get_nblocksy(res->format, box->height); - int d, h; - uint32_t stride = - dst_stride ? dst_stride - : util_format_get_nblocksx(res->format, - u_minify(res->width0, level)) * - blsize; - - if ((send_size == size || bh == 1) && !invert && box->depth == 1) { - vrend_write_to_iovec(iov, num_iovs, offset, data, send_size); - } else if (invert) { - for (d = 0; d < box->depth; d++) { - uint32_t myoffset = offset + d * stride * u_minify(res->height0, level); - for (h = bh - 1; h >= 0; h--) { - void *ptr = data + (h * bwx) + d * (bh * bwx); - vrend_write_to_iovec(iov, num_iovs, myoffset, ptr, bwx); - myoffset += stride; - } - } - } else { - for (d = 0; d < box->depth; d++) { - uint32_t myoffset = offset + d * stride * u_minify(res->height0, level); - for (h = 0; h < bh; h++) { - void *ptr = data + (h * bwx) + d * (bh * bwx); - vrend_write_to_iovec(iov, num_iovs, myoffset, ptr, bwx); - myoffset += stride; - } - } - } -} - -static bool check_transfer_bounds(struct vrend_resource *res, - const struct vrend_transfer_info *info) { - int lwidth, lheight; - - /* check mipmap level is in bounds */ - if (info->level > res->base.last_level) - return false; - if (info->box->x < 0 || info->box->y < 0) - return false; - /* these will catch bad y/z/w/d with 1D textures etc */ - lwidth = u_minify(res->base.width0, info->level); - if (info->box->width > lwidth || info->box->width < 0) - return false; - if (info->box->x > lwidth) - return false; - if (info->box->width + info->box->x > lwidth) - return false; - - lheight = u_minify(res->base.height0, info->level); - if (info->box->height > lheight || info->box->height < 0) - return false; - if (info->box->y > lheight) - return false; - if (info->box->height + info->box->y > lheight) - return false; - - if (res->base.target == PIPE_TEXTURE_3D) { - int ldepth = u_minify(res->base.depth0, info->level); - if (info->box->depth > ldepth || info->box->depth < 0) - return false; - if (info->box->z > ldepth) - return false; - if (info->box->z + info->box->depth > ldepth) - return false; - } else { - if (info->box->depth > (int)res->base.array_size) - return false; - if (info->box->z > (int)res->base.array_size) - return false; - if (info->box->z + info->box->depth > (int)res->base.array_size) - return false; - } - - return true; -} - -/* Calculate the size of the memory needed to hold all the data of a - * transfer for particular stride values. - */ -static uint64_t vrend_transfer_size(struct vrend_resource *vres, - const struct vrend_transfer_info *info, - uint32_t stride, uint32_t layer_stride) { - struct pipe_resource *pres = &vres->base; - struct pipe_box *box = info->box; - uint64_t size; - /* For purposes of size calculation, assume that invalid dimension values - * correspond to 1. - */ - int w = box->width > 0 ? box->width : 1; - int h = box->height > 0 ? box->height : 1; - int d = box->depth > 0 ? box->depth : 1; - int nblocksx = util_format_get_nblocksx(pres->format, w); - int nblocksy = util_format_get_nblocksy(pres->format, h); - - /* Calculate the box size, not including the last layer. The last layer - * is the only one which may be incomplete, and is the only layer for - * non 3d/2d-array formats. - */ - size = (d - 1) * layer_stride; - /* Calculate the size of the last (or only) layer, not including the last - * block row. The last block row is the only one which may be incomplete and - * is the only block row for non 2d/1d-array formats. - */ - size += (nblocksy - 1) * stride; - /* Calculate the size of the the last (or only) block row. */ - size += nblocksx * util_format_get_blocksize(pres->format); - - return size; -} - -static bool check_iov_bounds(struct vrend_resource *res, - const struct vrend_transfer_info *info, - struct iovec *iov, int num_iovs) { - GLuint transfer_size; - GLuint iovsize = vrend_get_iovec_size(iov, num_iovs); - GLuint valid_stride, valid_layer_stride; - - /* If the transfer specifies a stride, verify that it's at least as large as - * the minimum required for the transfer. If no stride is specified use the - * image stride for the specified level. - */ - if (info->stride) { - GLuint min_stride = - util_format_get_stride(res->base.format, info->box->width); - if (info->stride < min_stride) - return false; - valid_stride = info->stride; - } else { - valid_stride = util_format_get_stride( - res->base.format, u_minify(res->base.width0, info->level)); - } - - /* If the transfer specifies a layer_stride, verify that it's at least as - * large as the minimum required for the transfer. If no layer_stride is - * specified use the image layer_stride for the specified level. - */ - if (info->layer_stride) { - GLuint min_layer_stride = util_format_get_2d_size( - res->base.format, valid_stride, info->box->height); - if (info->layer_stride < min_layer_stride) - return false; - valid_layer_stride = info->layer_stride; - } else { - valid_layer_stride = - util_format_get_2d_size(res->base.format, valid_stride, - u_minify(res->base.height0, info->level)); - } - - /* Calculate the size required for the transferred data, based on the - * calculated or provided strides, and ensure that the iov, starting at the - * specified offset, is able to hold at least that size. - */ - transfer_size = - vrend_transfer_size(res, info, valid_stride, valid_layer_stride); - if (iovsize < info->offset) - return false; - if (iovsize < transfer_size) - return false; - if (iovsize < info->offset + transfer_size) - return false; - - return true; -} - -static int vrend_renderer_transfer_write_iov( - struct vrend_context *ctx, struct vrend_resource *res, struct iovec *iov, - int num_iovs, const struct vrend_transfer_info *info) { - void *data; - - if (is_only_bit(res->storage_bits, VREND_STORAGE_GUEST_MEMORY) || - (has_bit(res->storage_bits, VREND_STORAGE_HOST_SYSTEM_MEMORY) && - res->iov)) { - return vrend_copy_iovec(iov, num_iovs, info->offset, res->iov, - res->num_iovs, info->box->x, info->box->width, - res->ptr); - } - - if (has_bit(res->storage_bits, VREND_STORAGE_HOST_SYSTEM_MEMORY)) { - assert(!res->iov); - vrend_read_from_iovec(iov, num_iovs, info->offset, res->ptr + info->box->x, - info->box->width); - return 0; - } - - if (has_bit(res->storage_bits, VREND_STORAGE_GL_BUFFER)) { - GLuint map_flags = GL_MAP_INVALIDATE_RANGE_BIT | GL_MAP_WRITE_BIT; - struct virgl_sub_upload_data d; - d.box = info->box; - d.target = res->target; - - if (!info->synchronized) - map_flags |= GL_MAP_UNSYNCHRONIZED_BIT; - - glBindBuffer(res->target, res->id); - data = glMapBufferRange(res->target, info->box->x, info->box->width, - map_flags); - if (data == NULL) { - vrend_read_from_iovec_cb(iov, num_iovs, info->offset, info->box->width, - &iov_buffer_upload, &d); - } else { - vrend_read_from_iovec(iov, num_iovs, info->offset, data, - info->box->width); - glUnmapBuffer(res->target); - } - glBindBuffer(res->target, 0); - } else { - GLenum glformat; - GLenum gltype; - int need_temp = 0; - int elsize = util_format_get_blocksize(res->base.format); - int x = 0, y = 0; - bool compressed; - bool invert = false; - float depth_scale; - GLuint send_size = 0; - uint32_t stride = info->stride; - uint32_t layer_stride = info->layer_stride; - - if (ctx) - vrend_use_program(ctx, 0); - else - glUseProgram(0); - - if (!stride) - stride = util_format_get_nblocksx( - res->base.format, u_minify(res->base.width0, info->level)) * - elsize; - - if (!layer_stride) - layer_stride = util_format_get_2d_size( - res->base.format, stride, u_minify(res->base.height0, info->level)); - - compressed = util_format_is_compressed(res->base.format); - if (num_iovs > 1 || compressed) { - need_temp = true; - } - - if ((res->y_0_top || (res->base.format == VIRGL_FORMAT_Z24X8_UNORM))) { - need_temp = true; - if (res->y_0_top) - invert = true; - } - - send_size = util_format_get_nblocks(res->base.format, info->box->width, - info->box->height) * - elsize; - if (res->target == GL_TEXTURE_3D || res->target == GL_TEXTURE_2D_ARRAY || - res->target == GL_TEXTURE_CUBE_MAP_ARRAY) - send_size *= info->box->depth; - - if (need_temp) { - data = malloc(send_size); - if (!data) - return ENOMEM; - read_transfer_data(iov, num_iovs, data, res->base.format, info->offset, - stride, layer_stride, info->box, invert); - } else { - if (send_size > iov[0].iov_len - info->offset) - return EINVAL; - data = (char *)iov[0].iov_base + info->offset; - } - - if (!need_temp) { - assert(stride); - glPixelStorei(GL_UNPACK_ROW_LENGTH, stride / elsize); - glPixelStorei(GL_UNPACK_IMAGE_HEIGHT, layer_stride / stride); - } else - glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); - - switch (elsize) { - case 1: - case 3: - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - break; - case 2: - case 6: - glPixelStorei(GL_UNPACK_ALIGNMENT, 2); - break; - case 4: - default: - glPixelStorei(GL_UNPACK_ALIGNMENT, 4); - break; - case 8: - glPixelStorei(GL_UNPACK_ALIGNMENT, 8); - break; - } - - glformat = tex_conv_table[res->base.format].glformat; - gltype = tex_conv_table[res->base.format].gltype; - - uint32_t comp_size; - glBindTexture(res->target, res->id); - - if (compressed) { - glformat = tex_conv_table[res->base.format].internalformat; - comp_size = util_format_get_nblocks(res->base.format, info->box->width, - info->box->height) * - util_format_get_blocksize(res->base.format); - } - - if (glformat == 0) { - glformat = GL_BGRA_EXT; - gltype = GL_UNSIGNED_BYTE; - } - - x = info->box->x; - y = invert ? (int)res->base.height0 - info->box->y - info->box->height - : info->box->y; - - /* mipmaps are usually passed in one iov, and we need to keep the offset - * into the data in case we want to read back the data of a surface - * that can not be rendered. Since we can not assume that the whole texture - * is filled, we evaluate the offset for origin (0,0,0). Since it is also - * possible that a resource is reused and resized update the offset every - * time. - */ - if (info->level < VR_MAX_TEXTURE_2D_LEVELS) { - int64_t level_height = u_minify(res->base.height0, info->level); - res->mipmap_offsets[info->level] = - info->offset - - ((info->box->z * level_height + y) * stride + x * elsize); - } - - if (res->base.format == VIRGL_FORMAT_Z24X8_UNORM) { - /* we get values from the guest as 24-bit scaled integers - but we give them to the host GL and it interprets them - as 32-bit scaled integers, so we need to scale them here */ - depth_scale = 256.0; - vrend_scale_depth(data, send_size, depth_scale); - } - if (res->target == GL_TEXTURE_CUBE_MAP) { - GLenum ctarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X + info->box->z; - if (compressed) { - glCompressedTexSubImage2D(ctarget, info->level, x, y, info->box->width, - info->box->height, glformat, comp_size, data); - } else { - glTexSubImage2D(ctarget, info->level, x, y, info->box->width, - info->box->height, glformat, gltype, data); - } - } else if (res->target == GL_TEXTURE_3D || - res->target == GL_TEXTURE_2D_ARRAY || - res->target == GL_TEXTURE_CUBE_MAP_ARRAY) { - if (compressed) { - glCompressedTexSubImage3D(res->target, info->level, x, y, info->box->z, - info->box->width, info->box->height, - info->box->depth, glformat, comp_size, data); - } else { - glTexSubImage3D(res->target, info->level, x, y, info->box->z, - info->box->width, info->box->height, info->box->depth, - glformat, gltype, data); - } - } else { - if (compressed) { - glCompressedTexSubImage2D( - res->target, info->level, x, - res->target == GL_TEXTURE_1D_ARRAY ? info->box->z : y, - info->box->width, info->box->height, glformat, comp_size, data); - } else { - glTexSubImage2D(res->target, info->level, x, - res->target == GL_TEXTURE_1D_ARRAY ? info->box->z : y, - info->box->width, - res->target == GL_TEXTURE_1D_ARRAY ? info->box->depth - : info->box->height, - glformat, gltype, data); - } - } - - glBindTexture(res->target, 0); - - if (stride && !need_temp) { - glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); - glPixelStorei(GL_UNPACK_IMAGE_HEIGHT, 0); - } - - glPixelStorei(GL_UNPACK_ALIGNMENT, 4); - - if (need_temp) - free(data); - } - return 0; -} - -static uint32_t vrend_get_texture_depth(struct vrend_resource *res, - uint32_t level) { - uint32_t depth = 1; - if (res->target == GL_TEXTURE_3D) - depth = u_minify(res->base.depth0, level); - else if (res->target == GL_TEXTURE_1D_ARRAY || - res->target == GL_TEXTURE_2D_ARRAY || - res->target == GL_TEXTURE_CUBE_MAP || - res->target == GL_TEXTURE_CUBE_MAP_ARRAY) - depth = res->base.array_size; - - return depth; -} - -static void do_readpixels(GLint x, GLint y, GLsizei width, GLsizei height, - GLenum format, GLenum type, GLsizei bufSize, - void *data) { - glReadPixels(x, y, width, height, format, type, data); -} - -static int -vrend_transfer_send_readpixels(struct vrend_resource *res, struct iovec *iov, - int num_iovs, - const struct vrend_transfer_info *info) { - char *myptr = (char *)iov[0].iov_base + info->offset; - int need_temp = 0; - GLuint fb_id; - char *data; - bool actually_invert, separate_invert = false; - GLenum format, type; - GLint y1; - uint32_t send_size = 0; - uint32_t h = u_minify(res->base.height0, info->level); - int elsize = util_format_get_blocksize(res->base.format); - float depth_scale; - int row_stride = info->stride / elsize; - GLint old_fbo; - - glUseProgram(0); - - enum virgl_formats fmt = res->base.format; - format = tex_conv_table[fmt].glformat; - type = tex_conv_table[fmt].gltype; - /* if we are asked to invert and reading from a front then don't */ - - actually_invert = res->y_0_top; - - if (actually_invert) - separate_invert = true; - - if (num_iovs > 1 || separate_invert) - need_temp = 1; - - if (need_temp) { - send_size = util_format_get_nblocks(res->base.format, info->box->width, - info->box->height) * - info->box->depth * util_format_get_blocksize(res->base.format); - data = malloc(send_size); - if (!data) - return ENOMEM; - } else { - send_size = iov[0].iov_len - info->offset; - data = myptr; - if (!row_stride) - row_stride = util_format_get_nblocksx( - res->base.format, u_minify(res->base.width0, info->level)); - } - - glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &old_fbo); - - if (res->readback_fb_id == 0 || (int)res->readback_fb_level != info->level || - (int)res->readback_fb_z != info->box->z) { - - if (res->readback_fb_id) - glDeleteFramebuffers(1, &res->readback_fb_id); - - glGenFramebuffers(1, &fb_id); - glBindFramebuffer(GL_FRAMEBUFFER, fb_id); - - vrend_fb_bind_texture(res, 0, info->level, info->box->z); - - res->readback_fb_id = fb_id; - res->readback_fb_level = info->level; - res->readback_fb_z = info->box->z; - } else - glBindFramebuffer(GL_FRAMEBUFFER, res->readback_fb_id); - if (actually_invert) - y1 = h - info->box->y - info->box->height; - else - y1 = info->box->y; - - if (!vrend_format_is_ds(res->base.format)) - glReadBuffer(GL_COLOR_ATTACHMENT0); - if (!need_temp && row_stride) - glPixelStorei(GL_PACK_ROW_LENGTH, row_stride); - - switch (elsize) { - case 1: - glPixelStorei(GL_PACK_ALIGNMENT, 1); - break; - case 2: - glPixelStorei(GL_PACK_ALIGNMENT, 2); - break; - case 4: - default: - glPixelStorei(GL_PACK_ALIGNMENT, 4); - break; - case 8: - glPixelStorei(GL_PACK_ALIGNMENT, 8); - break; - } - - if (res->base.format == VIRGL_FORMAT_Z24X8_UNORM) { - /* we get values from the guest as 24-bit scaled integers - but we give them to the host GL and it interprets them - as 32-bit scaled integers, so we need to scale them here */ - depth_scale = 1.0 / 256.0; - } - - do_readpixels(info->box->x, y1, info->box->width, info->box->height, format, - type, send_size, data); - - if (res->base.format == VIRGL_FORMAT_Z24X8_UNORM) { - vrend_scale_depth(data, send_size, depth_scale); - } - - if (!need_temp && row_stride) - glPixelStorei(GL_PACK_ROW_LENGTH, 0); - - glPixelStorei(GL_PACK_ALIGNMENT, 4); - if (need_temp) { - write_transfer_data(&res->base, iov, num_iovs, data, info->stride, - info->box, info->level, info->offset, separate_invert); - free(data); - } - - glBindFramebuffer(GL_FRAMEBUFFER, old_fbo); - - return 0; -} - -static int -vrend_transfer_send_readonly(struct vrend_resource *res, struct iovec *iov, - int num_iovs, - UNUSED const struct vrend_transfer_info *info) { - bool same_iov = true; - uint i; - - if (res->num_iovs == (uint32_t)num_iovs) { - for (i = 0; i < res->num_iovs; i++) { - if (res->iov[i].iov_len != iov[i].iov_len || - res->iov[i].iov_base != iov[i].iov_base) { - same_iov = false; - } - } - } else { - same_iov = false; - } - - /* - * When we detect that we are reading back to the same iovs that are - * attached to the resource and we know that the resource can not - * be rendered to (as this function is only called then), we do not - * need to do anything more. - */ - if (same_iov) { - return 0; - } - - return -1; -} - -static int -vrend_renderer_transfer_send_iov(struct vrend_resource *res, struct iovec *iov, - int num_iovs, - const struct vrend_transfer_info *info) { - if (is_only_bit(res->storage_bits, VREND_STORAGE_GUEST_MEMORY) || - (has_bit(res->storage_bits, VREND_STORAGE_HOST_SYSTEM_MEMORY) && - res->iov)) { - return vrend_copy_iovec(res->iov, res->num_iovs, info->box->x, iov, - num_iovs, info->offset, info->box->width, res->ptr); - } - - if (has_bit(res->storage_bits, VREND_STORAGE_HOST_SYSTEM_MEMORY)) { - assert(!res->iov); - vrend_write_to_iovec(iov, num_iovs, info->offset, res->ptr + info->box->x, - info->box->width); - return 0; - } - - if (has_bit(res->storage_bits, VREND_STORAGE_GL_BUFFER)) { - uint32_t send_size = - info->box->width * util_format_get_blocksize(res->base.format); - void *data; - - glBindBuffer(res->target, res->id); - data = glMapBufferRange(res->target, info->box->x, info->box->width, - GL_MAP_READ_BIT); - if (data) - vrend_write_to_iovec(iov, num_iovs, info->offset, data, send_size); - glUnmapBuffer(res->target); - glBindBuffer(res->target, 0); - } else { - int ret = -1; - bool can_readpixels = true; - - can_readpixels = vrend_format_can_render(res->base.format) || - vrend_format_is_ds(res->base.format); - - if (can_readpixels) - ret = vrend_transfer_send_readpixels(res, iov, num_iovs, info); - - /* Can hit this on a non-error path as well. */ - if (ret) - ret = vrend_transfer_send_readonly(res, iov, num_iovs, info); - - return ret; - } - return 0; -} - -int vrend_renderer_transfer_iov(struct virgl_client *client, - const struct vrend_transfer_info *info, - int transfer_mode) { - struct vrend_resource *res; - struct vrend_context *ctx; - struct iovec *iov; - int num_iovs; - - if (!info->box) - return EINVAL; - - ctx = vrend_lookup_renderer_ctx(client, info->ctx_id); - if (!ctx) - return EINVAL; - - if (info->ctx_id == 0) - res = vrend_resource_lookup(client, info->handle, 0); - else - res = vrend_renderer_ctx_res_lookup(ctx, info->handle); - - if (!res) - return EINVAL; - - iov = info->iovec; - num_iovs = info->iovec_cnt; - - if (res->iov && (!iov || num_iovs == 0)) { - iov = res->iov; - num_iovs = res->num_iovs; - } - - if (!iov) - return EINVAL; - - if (!check_transfer_bounds(res, info)) - return EINVAL; - - if (!check_iov_bounds(res, info, iov, num_iovs)) - return EINVAL; - - if (info->context0) { - vrend_renderer_force_ctx_0(client); - ctx = NULL; - } - - switch (transfer_mode) { - case VIRGL_TRANSFER_TO_HOST: - return vrend_renderer_transfer_write_iov(ctx, res, iov, num_iovs, info); - case VIRGL_TRANSFER_FROM_HOST: - return vrend_renderer_transfer_send_iov(res, iov, num_iovs, info); - - default: - assert(0); - } - return 0; -} - -int vrend_transfer_inline_write(struct vrend_context *ctx, - struct vrend_transfer_info *info) { - struct vrend_resource *res; - - res = vrend_renderer_ctx_res_lookup(ctx, info->handle); - if (!res) - return EINVAL; - - if (!check_transfer_bounds(res, info)) - return EINVAL; - - if (!check_iov_bounds(res, info, info->iovec, info->iovec_cnt)) - return EINVAL; - - return vrend_renderer_transfer_write_iov(ctx, res, info->iovec, - info->iovec_cnt, info); -} - -int vrend_renderer_copy_transfer3d(struct vrend_context *ctx, - struct vrend_transfer_info *info, - uint32_t src_handle) { - struct vrend_resource *src_res, *dst_res; - - src_res = vrend_renderer_ctx_res_lookup(ctx, src_handle); - dst_res = vrend_renderer_ctx_res_lookup(ctx, info->handle); - - if (!src_res) - return EINVAL; - - if (!dst_res) - return EINVAL; - - if (!src_res->iov) - return EINVAL; - - if (!check_transfer_bounds(dst_res, info)) - return EINVAL; - - if (!check_iov_bounds(dst_res, info, src_res->iov, src_res->num_iovs)) - return EINVAL; - - return vrend_renderer_transfer_write_iov(ctx, dst_res, src_res->iov, - src_res->num_iovs, info); -} - -void vrend_set_stencil_ref(struct vrend_context *ctx, - struct pipe_stencil_ref *ref) { - if (ctx->sub->stencil_refs[0] != ref->ref_value[0] || - ctx->sub->stencil_refs[1] != ref->ref_value[1]) { - ctx->sub->stencil_refs[0] = ref->ref_value[0]; - ctx->sub->stencil_refs[1] = ref->ref_value[1]; - ctx->sub->stencil_state_dirty = true; - } -} - -void vrend_set_blend_color(struct vrend_context *ctx, - struct pipe_blend_color *color) { - ctx->sub->blend_color = *color; - glBlendColor(color->color[0], color->color[1], color->color[2], - color->color[3]); -} - -void vrend_set_scissor_state(struct vrend_context *ctx, uint32_t start_slot, - uint32_t num_scissor, - struct pipe_scissor_state *ss) { - uint i, idx; - - if (start_slot > PIPE_MAX_VIEWPORTS || - num_scissor > (PIPE_MAX_VIEWPORTS - start_slot)) - return; - - for (i = 0; i < num_scissor; i++) { - idx = start_slot + i; - ctx->sub->ss[idx] = ss[i]; - ctx->sub->scissor_state_dirty |= (1 << idx); - } -} - -void vrend_set_polygon_stipple(struct vrend_context *ctx, - struct pipe_poly_stipple *ps) { - static const unsigned bit31 = 1u << 31; - GLubyte *stip = calloc(1, 1024); - int i, j; - - if (!ctx->pstip_inited) - vrend_init_pstipple_texture(ctx); - - if (!stip) - return; - - for (i = 0; i < 32; i++) { - for (j = 0; j < 32; j++) { - if (ps->stipple[i] & (bit31 >> j)) - stip[i * 32 + j] = 0; - else - stip[i * 32 + j] = 255; - } - } - - glBindTexture(GL_TEXTURE_2D, ctx->pstipple_tex_id); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 32, 32, GL_RED, GL_UNSIGNED_BYTE, - stip); - glBindTexture(GL_TEXTURE_2D, 0); - - free(stip); -} - -void vrend_set_clip_state(struct vrend_context *ctx, - struct pipe_clip_state *ucp) { - ctx->sub->ucp_state = *ucp; -} - -void vrend_set_sample_mask(UNUSED struct vrend_context *ctx, - unsigned sample_mask) { - if (has_feature(feat_sample_mask)) - glSampleMaski(0, sample_mask); -} - -void vrend_set_min_samples(struct vrend_context *ctx, unsigned min_samples) { - float min_sample_shading = (float)min_samples; - if (ctx->sub->nr_cbufs > 0 && ctx->sub->surf[0]) { - assert(ctx->sub->surf[0]->texture); - min_sample_shading /= MAX2(1, ctx->sub->surf[0]->texture->base.nr_samples); - } - - if (has_feature(feat_sample_shading)) - glMinSampleShading(min_sample_shading); -} - -void vrend_set_tess_state(UNUSED struct vrend_context *ctx, - const float tess_factors[6]) { - if (has_feature(feat_tessellation)) - memcpy(ctx->client->vrend_state->tess_factors, tess_factors, - 6 * sizeof(float)); -} - -static void -vrend_hw_emit_streamout_targets(UNUSED struct vrend_context *ctx, - struct vrend_streamout_object *so_obj) { - uint i; - - for (i = 0; i < so_obj->num_targets; i++) { - if (!so_obj->so_targets[i]) - glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, i, 0); - else if (so_obj->so_targets[i]->buffer_offset || - so_obj->so_targets[i]->buffer_size < - so_obj->so_targets[i]->buffer->base.width0) - glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, i, - so_obj->so_targets[i]->buffer->id, - so_obj->so_targets[i]->buffer_offset, - so_obj->so_targets[i]->buffer_size); - else - glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, i, - so_obj->so_targets[i]->buffer->id); - } -} - -void vrend_set_streamout_targets(struct vrend_context *ctx, - UNUSED uint32_t append_bitmask, - uint32_t num_targets, uint32_t *handles) { - struct vrend_so_target *target; - uint i; - - if (!has_feature(feat_transform_feedback)) - return; - - if (num_targets) { - bool found = false; - struct vrend_streamout_object *obj; - LIST_FOR_EACH_ENTRY(obj, &ctx->sub->streamout_list, head) { - if (obj->num_targets == num_targets) { - if (!memcmp(handles, obj->handles, num_targets * 4)) { - found = true; - break; - } - } - } - if (found) { - ctx->sub->current_so = obj; - glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, obj->id); - return; - } - - obj = CALLOC_STRUCT(vrend_streamout_object); - if (has_feature(feat_transform_feedback2)) { - glGenTransformFeedbacks(1, &obj->id); - glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, obj->id); - } - obj->num_targets = num_targets; - for (i = 0; i < num_targets; i++) { - obj->handles[i] = handles[i]; - if (handles[i] == 0) - continue; - target = vrend_object_lookup(ctx->sub->object_hash, handles[i], - VIRGL_OBJECT_STREAMOUT_TARGET); - if (!target) { - free(obj); - return; - } - vrend_so_target_reference(&obj->so_targets[i], target); - } - vrend_hw_emit_streamout_targets(ctx, obj); - list_addtail(&obj->head, &ctx->sub->streamout_list); - ctx->sub->current_so = obj; - obj->xfb_state = XFB_STATE_STARTED_NEED_BEGIN; - } else { - if (has_feature(feat_transform_feedback2)) - glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, 0); - ctx->sub->current_so = NULL; - } -} - -static void vrend_resource_buffer_copy(UNUSED struct vrend_context *ctx, - struct vrend_resource *src_res, - struct vrend_resource *dst_res, - uint32_t dstx, uint32_t srcx, - uint32_t width) { - glBindBuffer(GL_COPY_READ_BUFFER, src_res->id); - glBindBuffer(GL_COPY_WRITE_BUFFER, dst_res->id); - - glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, srcx, dstx, - width); - glBindBuffer(GL_COPY_READ_BUFFER, 0); - glBindBuffer(GL_COPY_WRITE_BUFFER, 0); -} - -static void vrend_resource_copy_fallback(struct vrend_resource *src_res, - struct vrend_resource *dst_res, - uint32_t dst_level, uint32_t dstx, - uint32_t dsty, uint32_t dstz, - uint32_t src_level, - const struct pipe_box *src_box) { - char *tptr; - uint32_t total_size, src_stride, dst_stride, src_layer_stride; - GLenum glformat, gltype; - int elsize = util_format_get_blocksize(dst_res->base.format); - int compressed = util_format_is_compressed(dst_res->base.format); - int cube_slice = 1; - uint32_t slice_size, slice_offset; - int i; - struct pipe_box box; - - if (src_res->target == GL_TEXTURE_CUBE_MAP) - cube_slice = 6; - - if (src_res->base.format != dst_res->base.format) - return; - - box = *src_box; - box.depth = vrend_get_texture_depth(src_res, src_level); - dst_stride = - util_format_get_stride(dst_res->base.format, dst_res->base.width0); - - /* this is ugly need to do a full GetTexImage */ - slice_size = - util_format_get_nblocks(src_res->base.format, - u_minify(src_res->base.width0, src_level), - u_minify(src_res->base.height0, src_level)) * - util_format_get_blocksize(src_res->base.format); - total_size = slice_size * vrend_get_texture_depth(src_res, src_level); - - tptr = malloc(total_size); - if (!tptr) - return; - - glformat = tex_conv_table[src_res->base.format].glformat; - gltype = tex_conv_table[src_res->base.format].gltype; - - if (compressed) - glformat = tex_conv_table[src_res->base.format].internalformat; - - /* If we are on gles we need to rely on the textures backing - * iovec to have the data we need, otherwise we can use glGetTexture - */ - uint64_t src_offset = 0; - uint64_t dst_offset = 0; - if (src_level < VR_MAX_TEXTURE_2D_LEVELS) { - src_offset = src_res->mipmap_offsets[src_level]; - dst_offset = dst_res->mipmap_offsets[src_level]; - } - - src_stride = - util_format_get_nblocksx(src_res->base.format, - u_minify(src_res->base.width0, src_level)) * - elsize; - src_layer_stride = - util_format_get_2d_size(src_res->base.format, src_stride, - u_minify(src_res->base.height0, src_level)); - read_transfer_data(src_res->iov, src_res->num_iovs, tptr, - src_res->base.format, src_offset, src_stride, - src_layer_stride, &box, false); - /* When on GLES sync the iov that backs the dst resource because - * we might need it in a chain copy A->B, B->C */ - write_transfer_data(&dst_res->base, dst_res->iov, dst_res->num_iovs, tptr, - dst_stride, &box, src_level, dst_offset, false); - /* we get values from the guest as 24-bit scaled integers - but we give them to the host GL and it interprets them - as 32-bit scaled integers, so we need to scale them here */ - if (dst_res->base.format == VIRGL_FORMAT_Z24X8_UNORM) { - float depth_scale = 256.0; - vrend_scale_depth(tptr, total_size, depth_scale); - } - - glPixelStorei(GL_PACK_ALIGNMENT, 4); - switch (elsize) { - case 1: - case 3: - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - break; - case 2: - case 6: - glPixelStorei(GL_UNPACK_ALIGNMENT, 2); - break; - case 4: - default: - glPixelStorei(GL_UNPACK_ALIGNMENT, 4); - break; - case 8: - glPixelStorei(GL_UNPACK_ALIGNMENT, 8); - break; - } - - glBindTexture(dst_res->target, dst_res->id); - slice_offset = src_box->z * slice_size; - cube_slice = (src_res->target == GL_TEXTURE_CUBE_MAP) - ? src_box->z + src_box->depth - : cube_slice; - i = (src_res->target == GL_TEXTURE_CUBE_MAP) ? src_box->z : 0; - for (; i < cube_slice; i++) { - GLenum ctarget = dst_res->target == GL_TEXTURE_CUBE_MAP - ? (GLenum)(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i) - : dst_res->target; - if (compressed) { - glCompressedTexSubImage2D(ctarget, dst_level, dstx, dsty, src_box->width, - src_box->height, glformat, slice_size, - tptr + slice_offset); - } else { - if (ctarget == GL_TEXTURE_3D || ctarget == GL_TEXTURE_2D_ARRAY || - ctarget == GL_TEXTURE_CUBE_MAP_ARRAY) { - glTexSubImage3D(ctarget, dst_level, dstx, dsty, dstz, src_box->width, - src_box->height, src_box->depth, glformat, gltype, - tptr + slice_offset); - } else { - glTexSubImage2D(ctarget, dst_level, dstx, dsty, src_box->width, - src_box->height, glformat, gltype, tptr + slice_offset); - } - } - slice_offset += slice_size; - } - - glPixelStorei(GL_UNPACK_ALIGNMENT, 4); - free(tptr); - glBindTexture(GL_TEXTURE_2D, 0); -} - -static inline void vrend_copy_sub_image(struct vrend_resource *src_res, - struct vrend_resource *dst_res, - uint32_t src_level, - const struct pipe_box *src_box, - uint32_t dst_level, uint32_t dstx, - uint32_t dsty, uint32_t dstz) { - - GLenum src_target = - tgsitargettogltarget(src_res->base.target, src_res->base.nr_samples); - GLenum dst_target = - tgsitargettogltarget(dst_res->base.target, dst_res->base.nr_samples); - - src_target = translate_gles_emulation_texture_target(src_target); - dst_target = translate_gles_emulation_texture_target(dst_target); - - glCopyImageSubData(src_res->id, src_target, src_level, src_box->x, src_box->y, - src_box->z, dst_res->id, dst_target, dst_level, dstx, dsty, - dstz, src_box->width, src_box->height, src_box->depth); -} - -void vrend_renderer_resource_copy_region( - struct vrend_context *ctx, uint32_t dst_handle, uint32_t dst_level, - uint32_t dstx, uint32_t dsty, uint32_t dstz, uint32_t src_handle, - uint32_t src_level, const struct pipe_box *src_box) { - struct vrend_resource *src_res, *dst_res; - GLbitfield glmask = 0; - GLint sy1, sy2, dy1, dy2; - - if (ctx->in_error) - return; - - src_res = vrend_renderer_ctx_res_lookup(ctx, src_handle); - dst_res = vrend_renderer_ctx_res_lookup(ctx, dst_handle); - - if (!src_res) - return; - if (!dst_res) - return; - - if (src_res->base.target == PIPE_BUFFER && - dst_res->base.target == PIPE_BUFFER) { - /* do a buffer copy */ - vrend_resource_buffer_copy(ctx, src_res, dst_res, dstx, src_box->x, - src_box->width); - return; - } - - if (has_feature(feat_copy_image) && - format_is_copy_compatible(src_res->base.format, dst_res->base.format, - true) && - src_res->base.nr_samples == dst_res->base.nr_samples) { - vrend_copy_sub_image(src_res, dst_res, src_level, src_box, dst_level, dstx, - dsty, dstz); - return; - } - - if (!vrend_format_can_render(src_res->base.format) || - !vrend_format_can_render(dst_res->base.format)) { - vrend_resource_copy_fallback(src_res, dst_res, dst_level, dstx, dsty, dstz, - src_level, src_box); - return; - } - - glBindFramebuffer(GL_FRAMEBUFFER, ctx->sub->blit_fb_ids[0]); - - /* clean out fb ids */ - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, - GL_TEXTURE_2D, 0, 0); - vrend_fb_bind_texture(src_res, 0, src_level, src_box->z); - - glBindFramebuffer(GL_FRAMEBUFFER, ctx->sub->blit_fb_ids[1]); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, - GL_TEXTURE_2D, 0, 0); - vrend_fb_bind_texture(dst_res, 0, dst_level, dstz); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, ctx->sub->blit_fb_ids[1]); - - glBindFramebuffer(GL_READ_FRAMEBUFFER, ctx->sub->blit_fb_ids[0]); - - glmask = GL_COLOR_BUFFER_BIT; - glDisable(GL_SCISSOR_TEST); - - if (!src_res->y_0_top) { - sy1 = src_box->y; - sy2 = src_box->y + src_box->height; - } else { - sy1 = src_res->base.height0 - src_box->y - src_box->height; - sy2 = src_res->base.height0 - src_box->y; - } - - if (!dst_res->y_0_top) { - dy1 = dsty; - dy2 = dsty + src_box->height; - } else { - dy1 = dst_res->base.height0 - dsty - src_box->height; - dy2 = dst_res->base.height0 - dsty; - } - - glBlitFramebuffer(src_box->x, sy1, src_box->x + src_box->width, sy2, dstx, - dy1, dstx + src_box->width, dy2, glmask, GL_NEAREST); - - glBindFramebuffer(GL_READ_FRAMEBUFFER, ctx->sub->blit_fb_ids[0]); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, - 0); - glBindFramebuffer(GL_READ_FRAMEBUFFER, ctx->sub->blit_fb_ids[1]); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, - 0); - - glBindFramebuffer(GL_FRAMEBUFFER, ctx->sub->fb_id); - - if (ctx->sub->rs_state.scissor) - glEnable(GL_SCISSOR_TEST); -} - -static void vrend_renderer_blit_int(struct vrend_context *ctx, - struct vrend_resource *src_res, - struct vrend_resource *dst_res, - const struct pipe_blit_info *info) { - GLbitfield glmask = 0; - int src_y1, src_y2, dst_y1, dst_y2; - GLenum filter; - int n_layers = 1, i; - bool use_gl = false; - bool make_intermediate_copy = false; - bool skip_dest_swizzle = false; - GLuint intermediate_fbo = 0; - struct vrend_resource *intermediate_copy = 0; - - GLuint blitter_views[2] = {src_res->id, dst_res->id}; - - filter = convert_mag_filter(info->filter); - - /* if we can't make FBO's use the fallback path */ - if (!vrend_format_can_render(src_res->base.format) && - !vrend_format_is_ds(src_res->base.format)) - use_gl = true; - if (!vrend_format_can_render(dst_res->base.format) && - !vrend_format_is_ds(dst_res->base.format)) - use_gl = true; - - /* different depth formats */ - if (vrend_format_is_ds(src_res->base.format) && - vrend_format_is_ds(dst_res->base.format)) { - if (src_res->base.format != dst_res->base.format) { - if (!(src_res->base.format == PIPE_FORMAT_S8_UINT_Z24_UNORM && - (dst_res->base.format == PIPE_FORMAT_Z24X8_UNORM))) { - use_gl = true; - } - } - } - /* glBlitFramebuffer - can support depth stencil with NEAREST - which we use for mipmaps */ - if ((info->mask & (PIPE_MASK_Z | PIPE_MASK_S)) && - info->filter == PIPE_TEX_FILTER_LINEAR) - use_gl = true; - - /* for scaled MS blits we either need extensions or hand roll */ - if (info->mask & PIPE_MASK_RGBA && src_res->base.nr_samples > 0 && - src_res->base.nr_samples != dst_res->base.nr_samples && - (info->src.box.width != info->dst.box.width || - info->src.box.height != info->dst.box.height)) { - use_gl = true; - } - - if (!dst_res->y_0_top) { - dst_y1 = info->dst.box.y + info->dst.box.height; - dst_y2 = info->dst.box.y; - } else { - dst_y1 = dst_res->base.height0 - info->dst.box.y - info->dst.box.height; - dst_y2 = dst_res->base.height0 - info->dst.box.y; - } - - if (!src_res->y_0_top) { - src_y1 = info->src.box.y + info->src.box.height; - src_y2 = info->src.box.y; - } else { - src_y1 = src_res->base.height0 - info->src.box.y - info->src.box.height; - src_y2 = src_res->base.height0 - info->src.box.y; - } - - /* GLES generally doesn't support blitting to a multi-sample FB, and also not - * from a multi-sample FB where the regions are not exatly the same or the - * source and target format are different. For - * downsampling DS blits to zero samples we solve this by doing two blits */ - if (((dst_res->base.nr_samples > 0) || - ((info->mask & PIPE_MASK_RGBA) && (src_res->base.nr_samples > 0) && - (info->src.box.x != info->dst.box.x || - info->src.box.width != info->dst.box.width || dst_y1 != src_y1 || - dst_y2 != src_y2 || info->src.format != info->dst.format)))) - use_gl = true; - - /* for 3D mipmapped blits - hand roll time */ - if (info->src.box.depth != info->dst.box.depth) - use_gl = true; - - if (vrend_blit_needs_swizzle(info->dst.format, info->src.format)) { - use_gl = true; - - if (dst_res->base.bind & VIRGL_BIND_PREFER_EMULATED_BGRA) - skip_dest_swizzle = true; - } - - if (use_gl) { - vrend_renderer_blit_gl(ctx->client, src_res, dst_res, blitter_views, info, - has_feature(feat_texture_srgb_decode), - has_feature(feat_srgb_write_control), - skip_dest_swizzle); - vrend_clicbs->make_current(ctx->client, ctx->sub->gl_context); - goto cleanup; - } - - if (info->mask & PIPE_MASK_Z) - glmask |= GL_DEPTH_BUFFER_BIT; - if (info->mask & PIPE_MASK_S) - glmask |= GL_STENCIL_BUFFER_BIT; - if (info->mask & PIPE_MASK_RGBA) - glmask |= GL_COLOR_BUFFER_BIT; - - if (info->scissor_enable) { - glScissor(info->scissor.minx, info->scissor.miny, - info->scissor.maxx - info->scissor.minx, - info->scissor.maxy - info->scissor.miny); - ctx->sub->scissor_state_dirty = (1 << 0); - glEnable(GL_SCISSOR_TEST); - } else - glDisable(GL_SCISSOR_TEST); - - /* An GLES GL_INVALID_OPERATION is generated if one wants to blit from a - * multi-sample fbo to a non multi-sample fbo and the source and destination - * rectangles are not defined with the same (X0, Y0) and (X1, Y1) bounds. - * - * Since stencil data can only be written in a fragment shader when - * ARB_shader_stencil_export is available, the workaround using GL as given - * above is usually not available. Instead, to work around the blit - * limitations on GLES first copy the full frame to a non-multisample - * surface and then copy the according area to the final target surface. - */ - if ((info->mask & PIPE_MASK_ZS) && - ((src_res->base.nr_samples > 0) && - (src_res->base.nr_samples != dst_res->base.nr_samples)) && - ((info->src.box.x != info->dst.box.x) || (src_y1 != dst_y1) || - (info->src.box.width != info->dst.box.width) || (src_y2 != dst_y2))) { - - make_intermediate_copy = true; - - /* Create a texture that is the same like the src_res texture, but - * without multi-sample */ - struct vrend_renderer_resource_create_args args; - memset(&args, 0, sizeof(struct vrend_renderer_resource_create_args)); - args.width = src_res->base.width0; - args.height = src_res->base.height0; - args.depth = src_res->base.depth0; - args.format = info->src.format; - args.target = src_res->base.target; - args.last_level = src_res->base.last_level; - args.array_size = src_res->base.array_size; - intermediate_copy = (struct vrend_resource *)CALLOC_STRUCT(vrend_texture); - vrend_renderer_resource_copy_args(&args, intermediate_copy); - MAYBE_UNUSED int r = - vrend_renderer_resource_allocate_texture(intermediate_copy); - assert(!r); - - glGenFramebuffers(1, &intermediate_fbo); - } else { - /* If no intermediate copy is needed make the variables point to the - * original source to simplify the code below. - */ - intermediate_fbo = ctx->sub->blit_fb_ids[0]; - intermediate_copy = src_res; - } - - glBindFramebuffer(GL_FRAMEBUFFER, ctx->sub->blit_fb_ids[0]); - if (info->mask & PIPE_MASK_RGBA) - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, - GL_TEXTURE_2D, 0, 0); - else - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, - 0, 0); - glBindFramebuffer(GL_FRAMEBUFFER, ctx->sub->blit_fb_ids[1]); - if (info->mask & PIPE_MASK_RGBA) - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, - GL_TEXTURE_2D, 0, 0); - else if (info->mask & (PIPE_MASK_Z | PIPE_MASK_S)) - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, - 0, 0); - if (info->src.box.depth == info->dst.box.depth) - n_layers = info->dst.box.depth; - for (i = 0; i < n_layers; i++) { - glBindFramebuffer(GL_FRAMEBUFFER, ctx->sub->blit_fb_ids[0]); - vrend_fb_bind_texture_id(src_res, blitter_views[0], 0, info->src.level, - info->src.box.z + i); - - if (make_intermediate_copy) { - int level_width = u_minify(src_res->base.width0, info->src.level); - int level_height = u_minify(src_res->base.width0, info->src.level); - glBindFramebuffer(GL_FRAMEBUFFER, intermediate_fbo); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, - GL_TEXTURE_2D, 0, 0); - vrend_fb_bind_texture(intermediate_copy, 0, info->src.level, - info->src.box.z + i); - - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, intermediate_fbo); - glBindFramebuffer(GL_READ_FRAMEBUFFER, ctx->sub->blit_fb_ids[0]); - glBlitFramebuffer(0, 0, level_width, level_height, 0, 0, level_width, - level_height, glmask, filter); - } - - glBindFramebuffer(GL_FRAMEBUFFER, ctx->sub->blit_fb_ids[1]); - vrend_fb_bind_texture_id(dst_res, blitter_views[1], 0, info->dst.level, - info->dst.box.z + i); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, ctx->sub->blit_fb_ids[1]); - - glBindFramebuffer(GL_READ_FRAMEBUFFER, intermediate_fbo); - - glBlitFramebuffer( - info->src.box.x, src_y1, info->src.box.x + info->src.box.width, src_y2, - info->dst.box.x, dst_y1, info->dst.box.x + info->dst.box.width, dst_y2, - glmask, filter); - } - - glBindFramebuffer(GL_FRAMEBUFFER, ctx->sub->blit_fb_ids[1]); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, - 0); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, - GL_TEXTURE_2D, 0, 0); - - glBindFramebuffer(GL_FRAMEBUFFER, ctx->sub->blit_fb_ids[0]); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, - 0); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, - GL_TEXTURE_2D, 0, 0); - - glBindFramebuffer(GL_FRAMEBUFFER, ctx->sub->fb_id); - - if (make_intermediate_copy) { - vrend_renderer_resource_destroy(intermediate_copy); - glDeleteFramebuffers(1, &intermediate_fbo); - } - - if (ctx->sub->rs_state.scissor) - glEnable(GL_SCISSOR_TEST); - else - glDisable(GL_SCISSOR_TEST); - -cleanup: - if (blitter_views[0] != src_res->id) - glDeleteTextures(1, &blitter_views[0]); - - if (blitter_views[1] != dst_res->id) - glDeleteTextures(1, &blitter_views[1]); -} - -void vrend_renderer_blit(struct vrend_context *ctx, uint32_t dst_handle, - uint32_t src_handle, - const struct pipe_blit_info *info) { - struct vrend_resource *src_res, *dst_res; - src_res = vrend_renderer_ctx_res_lookup(ctx, src_handle); - dst_res = vrend_renderer_ctx_res_lookup(ctx, dst_handle); - - if (!src_res) - return; - if (!dst_res) - return; - - if (ctx->in_error) - return; - - if (!info->src.format || info->src.format >= VIRGL_FORMAT_MAX) - return; - - if (!info->dst.format || info->dst.format >= VIRGL_FORMAT_MAX) - return; - - /* The Gallium blit function can be called for a general blit that may - * scale, convert the data, and apply some rander states, or it is called via - * glCopyImageSubData. If the src or the dst image are equal, or the two - * images formats are the same, then Galliums such calles are redirected - * to resource_copy_region, in this case and if no render states etx need - * to be applied, forward the call to glCopyImageSubData, otherwise do a - * normal blit. */ - if (has_feature(feat_copy_image) && - (!info->render_condition_enable || !ctx->sub->cond_render_gl_mode) && - format_is_copy_compatible(info->src.format, info->dst.format, false) && - !info->scissor_enable && (info->filter == PIPE_TEX_FILTER_NEAREST) && - !info->alpha_blend && (info->mask == PIPE_MASK_RGBA) && - src_res->base.nr_samples == dst_res->base.nr_samples && - info->src.box.width == info->dst.box.width && - info->src.box.height == info->dst.box.height && - info->src.box.depth == info->dst.box.depth) { - vrend_copy_sub_image(src_res, dst_res, info->src.level, &info->src.box, - info->dst.level, info->dst.box.x, info->dst.box.y, - info->dst.box.z); - } else { - vrend_renderer_blit_int(ctx, src_res, dst_res, info); - } -} - -int vrend_renderer_create_fence(struct virgl_client *client, - int client_fence_id, uint32_t ctx_id) { - struct vrend_fence *fence; - - fence = malloc(sizeof(struct vrend_fence)); - if (!fence) - return ENOMEM; - - fence->ctx_id = ctx_id; - fence->fence_id = client_fence_id; - fence->syncobj = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); - glFlush(); - - if (fence->syncobj == NULL) - goto fail; - - list_addtail(&fence->fences, &client->vrend_state->fence_list); - return 0; - -fail: - free(fence); - return ENOMEM; -} - -static void free_fence_locked(struct vrend_fence *fence) { - list_del(&fence->fences); - glDeleteSync(fence->syncobj); - free(fence); -} - -static void vrend_renderer_check_queries(struct virgl_client *client); - -void vrend_renderer_check_fences(struct virgl_client *client) { - struct vrend_fence *fence, *stor; - uint32_t latest_id = 0; - GLenum glret; - - vrend_renderer_force_ctx_0(client); - - LIST_FOR_EACH_ENTRY_SAFE(fence, stor, &client->vrend_state->fence_list, - fences) { - glret = glClientWaitSync(fence->syncobj, 0, 0); - if (glret == GL_ALREADY_SIGNALED) { - latest_id = fence->fence_id; - free_fence_locked(fence); - } - /* don't bother checking any subsequent ones */ - else if (glret == GL_TIMEOUT_EXPIRED) { - break; - } - } - - if (latest_id == 0) - return; - - vrend_renderer_check_queries(client); - - vrend_clicbs->write_fence(client, latest_id); -} - -static bool vrend_get_one_query_result(GLuint query_id, uint64_t *result) { - GLuint ready; - GLuint passed; - GLuint64 pass64; - - glGetQueryObjectuiv(query_id, GL_QUERY_RESULT_AVAILABLE, &ready); - - if (!ready) - return false; - - glGetQueryObjectuiv(query_id, GL_QUERY_RESULT, &passed); - *result = passed; - return true; -} - -static bool vrend_check_query(struct vrend_query *query) { - struct virgl_host_query_state state; - bool ret; - - state.result_size = 4; - ret = vrend_get_one_query_result(query->id, &state.result); - if (ret == false) - return false; - - /* We got a boolean, but the client wanted the actual number of samples - * blow the number up so that the client doesn't think it was just one pixel - * and discards an object that might be bigger */ - if (query->fake_samples_passed) - state.result *= fake_occlusion_query_samples_passed_default; - - state.query_state = VIRGL_QUERY_STATE_DONE; - - if (query->res->iov) { - vrend_write_to_iovec(query->res->iov, query->res->num_iovs, 0, - (const void *)&state, sizeof(state)); - } else { - *((struct virgl_host_query_state *)query->res->ptr) = state; - } - - return true; -} - -static void vrend_renderer_check_queries(struct virgl_client *client) { - struct vrend_query *query, *stor; - - LIST_FOR_EACH_ENTRY_SAFE( - query, stor, &client->vrend_state->waiting_query_list, waiting_queries) { - vrend_hw_switch_context(vrend_lookup_renderer_ctx(client, query->ctx_id), - true); - if (vrend_check_query(query)) - list_delinit(&query->waiting_queries); - } -} - -bool vrend_hw_switch_context(struct vrend_context *ctx, bool now) { - if (!ctx) - return false; - - if (ctx == ctx->client->vrend_state->current_ctx && - ctx->ctx_switch_pending == false) - return true; - - if (ctx->ctx_id != 0 && ctx->in_error) - return false; - - ctx->ctx_switch_pending = true; - if (now) - vrend_finish_context_switch(ctx); - - ctx->client->vrend_state->current_ctx = ctx; - return true; -} - -static void vrend_finish_context_switch(struct vrend_context *ctx) { - if (ctx->ctx_switch_pending == false) - return; - ctx->ctx_switch_pending = false; - - if (ctx->client->vrend_state->current_hw_ctx == ctx) - return; - - ctx->client->vrend_state->current_hw_ctx = ctx; - - vrend_clicbs->make_current(ctx->client, ctx->sub->gl_context); -} - -void vrend_renderer_object_destroy(struct vrend_context *ctx, uint32_t handle) { - vrend_object_remove(ctx->sub->object_hash, handle, 0); -} - -uint32_t vrend_renderer_object_insert(struct vrend_context *ctx, void *data, - uint32_t size, uint32_t handle, - enum virgl_object_type type) { - return vrend_object_insert(ctx->sub->object_hash, data, size, handle, type); -} - -int vrend_create_query(struct vrend_context *ctx, uint32_t handle, - uint32_t query_type, uint32_t query_index, - uint32_t res_handle, UNUSED uint32_t offset) { - struct vrend_query *q; - struct vrend_resource *res; - uint32_t ret_handle; - bool fake_samples_passed = false; - res = vrend_renderer_ctx_res_lookup(ctx, res_handle); - if (!res || !has_bit(res->storage_bits, VREND_STORAGE_HOST_SYSTEM_MEMORY)) - return EINVAL; - - /* If we don't have ARB_occlusion_query, at least try to fake - * GL_SAMPLES_PASSED by using GL_ANY_SAMPLES_PASSED (i.e. - * EXT_occlusion_query_boolean) */ - if (query_type == PIPE_QUERY_OCCLUSION_COUNTER) { - query_type = PIPE_QUERY_OCCLUSION_PREDICATE; - fake_samples_passed = true; - } - - if (query_type == PIPE_QUERY_OCCLUSION_PREDICATE && - !has_feature(feat_occlusion_query_boolean)) - return EINVAL; - - q = CALLOC_STRUCT(vrend_query); - if (!q) - return ENOMEM; - - list_inithead(&q->waiting_queries); - q->type = query_type; - q->index = query_index; - q->ctx_id = ctx->ctx_id; - q->fake_samples_passed = fake_samples_passed; - - vrend_resource_reference(&q->res, res); - - switch (q->type) { - case PIPE_QUERY_OCCLUSION_COUNTER: - return EINVAL; - case PIPE_QUERY_OCCLUSION_PREDICATE: - if (has_feature(feat_occlusion_query_boolean)) { - q->gltype = GL_ANY_SAMPLES_PASSED; - break; - } else - return EINVAL; - case PIPE_QUERY_TIMESTAMP: - return EINVAL; - case PIPE_QUERY_TIME_ELAPSED: - return EINVAL; - case PIPE_QUERY_PRIMITIVES_GENERATED: - q->gltype = GL_PRIMITIVES_GENERATED; - break; - case PIPE_QUERY_PRIMITIVES_EMITTED: - q->gltype = GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN; - break; - case PIPE_QUERY_OCCLUSION_PREDICATE_CONSERVATIVE: - q->gltype = GL_ANY_SAMPLES_PASSED_CONSERVATIVE; - break; - case PIPE_QUERY_SO_OVERFLOW_PREDICATE: - return EINVAL; - case PIPE_QUERY_SO_OVERFLOW_ANY_PREDICATE: - return EINVAL; - } - - glGenQueries(1, &q->id); - - ret_handle = vrend_renderer_object_insert(ctx, q, sizeof(struct vrend_query), - handle, VIRGL_OBJECT_QUERY); - if (!ret_handle) { - FREE(q); - return ENOMEM; - } - return 0; -} - -static void vrend_destroy_query(struct vrend_query *query) { - vrend_resource_reference(&query->res, NULL); - list_del(&query->waiting_queries); - glDeleteQueries(1, &query->id); - free(query); -} - -static void vrend_destroy_query_object(void *obj_ptr) { - struct vrend_query *query = obj_ptr; - vrend_destroy_query(query); -} - -int vrend_begin_query(struct vrend_context *ctx, uint32_t handle) { - struct vrend_query *q; - - q = vrend_object_lookup(ctx->sub->object_hash, handle, VIRGL_OBJECT_QUERY); - if (!q) - return EINVAL; - - if (q->index > 0) - return EINVAL; - - list_delinit(&q->waiting_queries); - - glBeginQuery(q->gltype, q->id); - return 0; -} - -int vrend_end_query(struct vrend_context *ctx, uint32_t handle) { - struct vrend_query *q; - q = vrend_object_lookup(ctx->sub->object_hash, handle, VIRGL_OBJECT_QUERY); - if (!q) - return EINVAL; - - if (q->index > 0) - return EINVAL; - - glEndQuery(q->gltype); - return 0; -} - -void vrend_get_query_result(struct vrend_context *ctx, uint32_t handle, - UNUSED uint32_t wait) { - struct vrend_query *q; - bool ret; - - q = vrend_object_lookup(ctx->sub->object_hash, handle, VIRGL_OBJECT_QUERY); - if (!q) - return; - - ret = vrend_check_query(q); - if (ret) { - list_delinit(&q->waiting_queries); - } else if (LIST_IS_EMPTY(&q->waiting_queries)) { - list_addtail(&q->waiting_queries, - &ctx->client->vrend_state->waiting_query_list); - } -} - -int vrend_create_so_target(struct vrend_context *ctx, uint32_t handle, - uint32_t res_handle, uint32_t buffer_offset, - uint32_t buffer_size) { - struct vrend_so_target *target; - struct vrend_resource *res; - int ret_handle; - res = vrend_renderer_ctx_res_lookup(ctx, res_handle); - if (!res) - return EINVAL; - - target = CALLOC_STRUCT(vrend_so_target); - if (!target) - return ENOMEM; - - pipe_reference_init(&target->reference, 1); - target->res_handle = res_handle; - target->buffer_offset = buffer_offset; - target->buffer_size = buffer_size; - target->sub_ctx = ctx->sub; - vrend_resource_reference(&target->buffer, res); - - ret_handle = vrend_renderer_object_insert( - ctx, target, sizeof(*target), handle, VIRGL_OBJECT_STREAMOUT_TARGET); - if (ret_handle == 0) { - FREE(target); - return ENOMEM; - } - return 0; -} - -static void vrend_fill_caps_glsl_version(int gles_ver, union virgl_caps *caps) { - if (gles_ver > 0) { - caps->v1.glsl_level = 120; - - if (gles_ver >= 31) - caps->v1.glsl_level = 310; - else if (gles_ver >= 30) - caps->v1.glsl_level = 130; - } - - if (caps->v1.glsl_level < 400) { - if (has_feature(feat_tessellation) && has_feature(feat_geometry_shader) && - has_feature(feat_gpu_shader5)) { - /* This is probably a lie, but Gallium enables - * OES_geometry_shader and ARB_gpu_shader5 - * based on this value, apart from that it doesn't - * seem to be a crucial value */ - caps->v1.glsl_level = 400; - - /* Let's lie a bit more */ - if (has_feature(feat_separate_shader_objects)) { - caps->v1.glsl_level = 410; - - /* Compute shaders require GLSL 4.30 unless the shader explicitely - * specifies GL_ARB_compute_shader as required. However, on OpenGL ES - * they are already supported with version 3.10, so if we already - * advertise a feature level of 410, just lie a bit more to make - * compute shaders available to GL programs that don't specify the - * extension within the shaders. */ - if (has_feature(feat_compute_shader)) - caps->v1.glsl_level = 430; - } - } - } -} - -static void set_format_bit(struct virgl_supported_format_mask *mask, - enum virgl_formats fmt) { - assert(fmt < VIRGL_FORMAT_MAX); - unsigned val = (unsigned)fmt; - unsigned idx = val / 32; - unsigned bit = val % 32; - assert(idx < ARRAY_SIZE(mask->bitmask)); - mask->bitmask[idx] |= 1u << bit; -} - -/* - * Does all of the common caps setting, - * if it dedects a early out returns true. - */ -static void vrend_renderer_fill_caps_v1(struct virgl_client *client, - int gles_ver, union virgl_caps *caps) { - int i; - GLint max; - - /* - * We can't fully support this feature on GLES, - * but it is needed for OpenGL 2.1 so lie. - */ - caps->v1.bset.occlusion_query = 1; - - /* Set supported prims here as we now know what shaders we support. */ - caps->v1.prim_mask = (1 << PIPE_PRIM_POINTS) | (1 << PIPE_PRIM_LINES) | - (1 << PIPE_PRIM_LINE_STRIP) | - (1 << PIPE_PRIM_LINE_LOOP) | (1 << PIPE_PRIM_TRIANGLES) | - (1 << PIPE_PRIM_TRIANGLE_STRIP) | - (1 << PIPE_PRIM_TRIANGLE_FAN); - - if (caps->v1.glsl_level >= 150) { - caps->v1.prim_mask |= (1 << PIPE_PRIM_LINES_ADJACENCY) | - (1 << PIPE_PRIM_LINE_STRIP_ADJACENCY) | - (1 << PIPE_PRIM_TRIANGLES_ADJACENCY) | - (1 << PIPE_PRIM_TRIANGLE_STRIP_ADJACENCY); - } - if (caps->v1.glsl_level >= 400 || has_feature(feat_tessellation)) - caps->v1.prim_mask |= (1 << PIPE_PRIM_PATCHES); - - if (vrend_has_gl_extension("GL_ARB_vertex_type_10f_11f_11f_rev")) - set_format_bit(&caps->v1.vertexbuffer, VIRGL_FORMAT_R11G11B10_FLOAT); - - if (has_feature(feat_indep_blend)) - caps->v1.bset.indep_blend_enable = 1; - - if (has_feature(feat_draw_instance)) - caps->v1.bset.instanceid = 1; - - if (has_feature(feat_ubo)) { - glGetIntegerv(GL_MAX_VERTEX_UNIFORM_BLOCKS, &max); - caps->v1.max_uniform_blocks = max + 1; - } - - if (vrend_has_gl_extension("GL_ARB_fragment_coord_conventions")) - caps->v1.bset.fragment_coord_conventions = 1; - - if (vrend_has_gl_extension("GL_ARB_seamless_cube_map") || gles_ver >= 30) - caps->v1.bset.seamless_cube_map = 1; - - if (vrend_has_gl_extension("GL_AMD_seamless_cube_map_per_texture")) - caps->v1.bset.seamless_cube_map_per_texture = 1; - - if (has_feature(feat_texture_multisample)) - caps->v1.bset.texture_multisample = 1; - - if (has_feature(feat_tessellation)) - caps->v1.bset.has_tessellation_shaders = 1; - - if (has_feature(feat_sample_shading)) - caps->v1.bset.has_sample_shading = 1; - - if (has_feature(feat_indirect_draw)) - caps->v1.bset.has_indirect_draw = 1; - - if (has_feature(feat_indep_blend_func)) - caps->v1.bset.indep_blend_func = 1; - - if (has_feature(feat_cube_map_array)) - caps->v1.bset.cube_map_array = 1; - - if (vrend_has_gl_extension("GL_ARB_gpu_shader_fp64") && - vrend_has_gl_extension("GL_ARB_gpu_shader5")) - caps->v1.bset.has_fp64 = 1; - - if (vrend_has_gl_extension("GL_ARB_shader_stencil_export")) - caps->v1.bset.shader_stencil_export = 1; - - if (vrend_has_gl_extension("GL_ARB_cull_distance")) - caps->v1.bset.has_cull = 1; - - if (vrend_has_gl_extension("GL_ARB_derivative_control")) - caps->v1.bset.derivative_control = 1; - - if (vrend_has_gl_extension("GL_EXT_texture_mirror_clamp") || - vrend_has_gl_extension("GL_ARB_texture_mirror_clamp_to_edge")) - caps->v1.bset.mirror_clamp = true; - - if (has_feature(feat_texture_array)) { - glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &max); - caps->v1.max_texture_array_layers = max; - } - - /* we need tf3 so we can do gallium skip buffers */ - if (has_feature(feat_transform_feedback)) { - if (has_feature(feat_transform_feedback2)) - caps->v1.bset.streamout_pause_resume = 1; - - if (gles_ver > 0) { - glGetIntegerv(GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS, &max); - /* As with the earlier version of transform feedback this min 4. */ - if (max >= 4) { - caps->v1.max_streamout_buffers = 4; - } - } else - caps->v1.max_streamout_buffers = 4; - } - - if (has_feature(feat_arb_or_gles_ext_texture_buffer)) { - glGetIntegerv(GL_MAX_TEXTURE_BUFFER_SIZE, &max); - caps->v1.max_tbo_size = max; - } - - if (has_feature(feat_texture_gather)) - caps->v1.max_texture_gather_components = 4; - - caps->v1.max_viewports = 1; - - /* Common limits for all backends. */ - caps->v1.max_render_targets = client->vrend_state->max_draw_buffers; - - glGetIntegerv(GL_MAX_SAMPLES, &max); - caps->v1.max_samples = max; - - /* All of the formats are common. */ - for (i = 0; i < VIRGL_FORMAT_MAX; i++) { - if (tex_conv_table[i].internalformat != 0) { - enum virgl_formats fmt = (enum virgl_formats)i; - if (vrend_format_can_sample(fmt)) { - set_format_bit(&caps->v1.sampler, fmt); - if (vrend_format_can_render(fmt)) - set_format_bit(&caps->v1.render, fmt); - } - } - } -} - -static void vrend_renderer_fill_caps_v2(struct virgl_client *client, - int gles_ver, union virgl_caps *caps) { - GLint max; - GLfloat range[2]; - - /* Count this up when you add a feature flag that is used to set a CAP in - * the guest that was set unconditionally before. Then check that flag and - * this value to avoid regressions when a guest with a new mesa version is - * run on an old virgl host. Use it also to indicate non-cap fixes on the - * host that help enable features in the guest. */ - caps->v2.host_feature_check_version = 3; - - glGetFloatv(GL_ALIASED_POINT_SIZE_RANGE, range); - caps->v2.min_aliased_point_size = range[0]; - caps->v2.max_aliased_point_size = range[1]; - - glGetFloatv(GL_ALIASED_LINE_WIDTH_RANGE, range); - caps->v2.min_aliased_line_width = range[0]; - caps->v2.max_aliased_line_width = range[1]; - - glGetFloatv(GL_MAX_TEXTURE_LOD_BIAS, &caps->v2.max_texture_lod_bias); - glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, (GLint *)&caps->v2.max_vertex_attribs); - - if (gles_ver >= 30) - glGetIntegerv(GL_MAX_VERTEX_OUTPUT_COMPONENTS, &max); - else - max = 64; // minimum required value - - caps->v2.max_vertex_outputs = max / 4; - - glGetIntegerv(GL_MIN_PROGRAM_TEXEL_OFFSET, &caps->v2.min_texel_offset); - glGetIntegerv(GL_MAX_PROGRAM_TEXEL_OFFSET, &caps->v2.max_texel_offset); - - glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, - (GLint *)&caps->v2.uniform_buffer_offset_alignment); - - glGetIntegerv(GL_MAX_TEXTURE_SIZE, (GLint *)&caps->v2.max_texture_2d_size); - glGetIntegerv(GL_MAX_3D_TEXTURE_SIZE, (GLint *)&caps->v2.max_texture_3d_size); - glGetIntegerv(GL_MAX_CUBE_MAP_TEXTURE_SIZE, - (GLint *)&caps->v2.max_texture_cube_size); - client->vrend_state->max_texture_2d_size = caps->v2.max_texture_2d_size; - client->vrend_state->max_texture_3d_size = caps->v2.max_texture_3d_size; - client->vrend_state->max_texture_cube_size = caps->v2.max_texture_cube_size; - - if (has_feature(feat_geometry_shader)) { - glGetIntegerv(GL_MAX_GEOMETRY_OUTPUT_VERTICES, - (GLint *)&caps->v2.max_geom_output_vertices); - glGetIntegerv(GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS, - (GLint *)&caps->v2.max_geom_total_output_components); - } - - if (has_feature(feat_tessellation)) { - glGetIntegerv(GL_MAX_TESS_PATCH_COMPONENTS, &max); - caps->v2.max_shader_patch_varyings = max / 4; - } else - caps->v2.max_shader_patch_varyings = 0; - - if (has_feature(feat_texture_gather)) { - glGetIntegerv(GL_MIN_PROGRAM_TEXTURE_GATHER_OFFSET, - &caps->v2.min_texture_gather_offset); - glGetIntegerv(GL_MAX_PROGRAM_TEXTURE_GATHER_OFFSET, - &caps->v2.max_texture_gather_offset); - } - - if (has_feature(feat_texture_buffer_range)) { - glGetIntegerv(GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT, - (GLint *)&caps->v2.texture_buffer_offset_alignment); - } - - if (has_feature(feat_ssbo)) { - glGetIntegerv(GL_SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT, - (GLint *)&caps->v2.shader_buffer_offset_alignment); - - glGetIntegerv(GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS, &max); - if (max > PIPE_MAX_SHADER_BUFFERS) - max = PIPE_MAX_SHADER_BUFFERS; - caps->v2.max_shader_buffer_other_stages = max; - glGetIntegerv(GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS, &max); - if (max > PIPE_MAX_SHADER_BUFFERS) - max = PIPE_MAX_SHADER_BUFFERS; - caps->v2.max_shader_buffer_frag_compute = max; - glGetIntegerv(GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS, - (GLint *)&caps->v2.max_combined_shader_buffers); - } - - if (has_feature(feat_images)) { - glGetIntegerv(GL_MAX_VERTEX_IMAGE_UNIFORMS, &max); - if (max > PIPE_MAX_SHADER_IMAGES) - max = PIPE_MAX_SHADER_IMAGES; - caps->v2.max_shader_image_other_stages = max; - glGetIntegerv(GL_MAX_FRAGMENT_IMAGE_UNIFORMS, &max); - if (max > PIPE_MAX_SHADER_IMAGES) - max = PIPE_MAX_SHADER_IMAGES; - caps->v2.max_shader_image_frag_compute = max; - } - - if (has_feature(feat_storage_multisample)) - caps->v1.max_samples = - vrend_renderer_query_multisample_caps(caps->v1.max_samples, &caps->v2); - - caps->v2.capability_bits |= - VIRGL_CAP_TGSI_INVARIANT | VIRGL_CAP_SET_MIN_SAMPLES | - VIRGL_CAP_TGSI_PRECISE | VIRGL_CAP_APP_TWEAK_SUPPORT; - - /* If attribute isn't supported, assume 2048 which is the minimum allowed - by the specification. */ - if (gles_ver >= 31) - glGetIntegerv(GL_MAX_VERTEX_ATTRIB_STRIDE, - (GLint *)&caps->v2.max_vertex_attrib_stride); - else - caps->v2.max_vertex_attrib_stride = 2048; - - if (has_feature(feat_compute_shader)) { - glGetIntegerv(GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS, - (GLint *)&caps->v2.max_compute_work_group_invocations); - glGetIntegerv(GL_MAX_COMPUTE_SHARED_MEMORY_SIZE, - (GLint *)&caps->v2.max_compute_shared_memory_size); - glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_COUNT, 0, - (GLint *)&caps->v2.max_compute_grid_size[0]); - glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_COUNT, 1, - (GLint *)&caps->v2.max_compute_grid_size[1]); - glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_COUNT, 2, - (GLint *)&caps->v2.max_compute_grid_size[2]); - glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_SIZE, 0, - (GLint *)&caps->v2.max_compute_block_size[0]); - glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_SIZE, 1, - (GLint *)&caps->v2.max_compute_block_size[1]); - glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_SIZE, 2, - (GLint *)&caps->v2.max_compute_block_size[2]); - - caps->v2.capability_bits |= VIRGL_CAP_COMPUTE_SHADER; - } - - if (has_feature(feat_atomic_counters)) { - glGetIntegerv(GL_MAX_VERTEX_ATOMIC_COUNTERS, - (GLint *)(caps->v2.max_atomic_counters + PIPE_SHADER_VERTEX)); - glGetIntegerv( - GL_MAX_VERTEX_ATOMIC_COUNTER_BUFFERS, - (GLint *)(caps->v2.max_atomic_counter_buffers + PIPE_SHADER_VERTEX)); - glGetIntegerv( - GL_MAX_FRAGMENT_ATOMIC_COUNTERS, - (GLint *)(caps->v2.max_atomic_counters + PIPE_SHADER_FRAGMENT)); - glGetIntegerv( - GL_MAX_FRAGMENT_ATOMIC_COUNTER_BUFFERS, - (GLint *)(caps->v2.max_atomic_counter_buffers + PIPE_SHADER_FRAGMENT)); - - if (has_feature(feat_geometry_shader)) { - glGetIntegerv( - GL_MAX_GEOMETRY_ATOMIC_COUNTERS, - (GLint *)(caps->v2.max_atomic_counters + PIPE_SHADER_GEOMETRY)); - glGetIntegerv(GL_MAX_GEOMETRY_ATOMIC_COUNTER_BUFFERS, - (GLint *)(caps->v2.max_atomic_counter_buffers + - PIPE_SHADER_GEOMETRY)); - } - - if (has_feature(feat_tessellation)) { - glGetIntegerv( - GL_MAX_TESS_CONTROL_ATOMIC_COUNTERS, - (GLint *)(caps->v2.max_atomic_counters + PIPE_SHADER_TESS_CTRL)); - glGetIntegerv(GL_MAX_TESS_CONTROL_ATOMIC_COUNTER_BUFFERS, - (GLint *)(caps->v2.max_atomic_counter_buffers + - PIPE_SHADER_TESS_CTRL)); - glGetIntegerv( - GL_MAX_TESS_EVALUATION_ATOMIC_COUNTERS, - (GLint *)(caps->v2.max_atomic_counters + PIPE_SHADER_TESS_EVAL)); - glGetIntegerv(GL_MAX_TESS_EVALUATION_ATOMIC_COUNTER_BUFFERS, - (GLint *)(caps->v2.max_atomic_counter_buffers + - PIPE_SHADER_TESS_EVAL)); - } - - if (has_feature(feat_compute_shader)) { - glGetIntegerv( - GL_MAX_COMPUTE_ATOMIC_COUNTERS, - (GLint *)(caps->v2.max_atomic_counters + PIPE_SHADER_COMPUTE)); - glGetIntegerv( - GL_MAX_COMPUTE_ATOMIC_COUNTER_BUFFERS, - (GLint *)(caps->v2.max_atomic_counter_buffers + PIPE_SHADER_COMPUTE)); - } - - glGetIntegerv(GL_MAX_COMBINED_ATOMIC_COUNTERS, - (GLint *)&caps->v2.max_combined_atomic_counters); - glGetIntegerv(GL_MAX_COMBINED_ATOMIC_COUNTER_BUFFERS, - (GLint *)&caps->v2.max_combined_atomic_counter_buffers); - } - - if (has_feature(feat_fb_no_attach)) - caps->v2.capability_bits |= VIRGL_CAP_FB_NO_ATTACH; - - if (has_feature(feat_barrier)) - caps->v2.capability_bits |= VIRGL_CAP_MEMORY_BARRIER; - - if (has_feature(feat_copy_image)) - caps->v2.capability_bits |= VIRGL_CAP_COPY_IMAGE; - - if (has_feature(feat_robust_buffer_access)) - caps->v2.capability_bits |= VIRGL_CAP_ROBUST_BUFFER_ACCESS; - - if (has_feature(feat_framebuffer_fetch)) - caps->v2.capability_bits |= VIRGL_CAP_TGSI_FBFETCH; - - if (has_feature(feat_srgb_write_control)) - caps->v2.capability_bits |= VIRGL_CAP_SRGB_WRITE_CONTROL; - - /* always enable, only indicates that the CMD is supported */ - caps->v2.capability_bits |= VIRGL_CAP_GUEST_MAY_INIT_LOG; - - caps->v2.capability_bits |= VIRGL_CAP_TRANSFER; - - if (vrend_check_framebuffer_mixed_color_attachements()) - caps->v2.capability_bits |= VIRGL_CAP_FBO_MIXED_COLOR_FORMATS; - - /* We want to expose ARB_gpu_shader_fp64 when running on top of ES */ - caps->v2.capability_bits |= VIRGL_CAP_FAKE_FP64; - caps->v2.capability_bits |= VIRGL_CAP_BGRA_SRGB_IS_EMULATED; - - if (has_feature(feat_indirect_draw)) - caps->v2.capability_bits |= VIRGL_CAP_BIND_COMMAND_ARGS; - - for (int i = 0; i < VIRGL_FORMAT_MAX; i++) { - enum virgl_formats fmt = (enum virgl_formats)i; - if (tex_conv_table[i].internalformat != 0) { - if (vrend_format_can_readback(fmt)) - set_format_bit(&caps->v2.supported_readback_formats, fmt); - } - - set_format_bit(&caps->v2.scanout, fmt); - } - - if (has_feature(feat_clip_control)) - caps->v2.capability_bits |= VIRGL_CAP_CLIP_HALFZ; - - if (vrend_has_gl_extension("GL_KHR_texture_compression_astc_sliced_3d")) - caps->v2.capability_bits |= VIRGL_CAP_3D_ASTC; - - caps->v2.capability_bits |= VIRGL_CAP_INDIRECT_INPUT_ADDR; - - caps->v2.capability_bits |= VIRGL_CAP_COPY_TRANSFER; -} - -void vrend_renderer_fill_caps(struct virgl_client *client, uint32_t set, - UNUSED uint32_t version, union virgl_caps *caps) { - int gles_ver; - GLenum err; - bool fill_capset2 = false; - - if (!caps) - return; - - if (set > 2) { - caps->max_version = 0; - return; - } - - if (set == 1) { - memset(caps, 0, sizeof(struct virgl_caps_v1)); - caps->max_version = 1; - } else if (set == 2) { - memset(caps, 0, sizeof(*caps)); - caps->max_version = 2; - fill_capset2 = true; - } - - gles_ver = vrend_gl_version(); - - vrend_fill_caps_glsl_version(gles_ver, caps); - - vrend_renderer_fill_caps_v1(client, gles_ver, caps); - - if (!fill_capset2) - return; - - vrend_renderer_fill_caps_v2(client, gles_ver, caps); -} - -void vrend_renderer_force_ctx_0(struct virgl_client *client) { - struct vrend_context *ctx0 = vrend_lookup_renderer_ctx(client, 0); - client->vrend_state->current_ctx = NULL; - client->vrend_state->current_hw_ctx = NULL; - vrend_hw_switch_context(ctx0, true); -} - -void vrend_renderer_attach_res_ctx(struct virgl_client *client, int ctx_id, - int resource_id) { - struct vrend_context *ctx = vrend_lookup_renderer_ctx(client, ctx_id); - struct vrend_resource *res; - - if (!ctx) - return; - - res = vrend_resource_lookup(client, resource_id, 0); - if (!res) - return; - - vrend_object_insert_nofree(ctx->res_hash, res, sizeof(*res), resource_id, 1, - false); -} - -static void vrend_renderer_detach_res_ctx(struct vrend_context *ctx, - int res_handle) { - struct vrend_resource *res; - res = vrend_object_lookup(ctx->res_hash, res_handle, 1); - if (!res) - return; - - vrend_object_remove(ctx->res_hash, res_handle, 1); -} - -struct vrend_resource *vrend_renderer_ctx_res_lookup(struct vrend_context *ctx, - int res_handle) { - struct vrend_resource *res = - vrend_object_lookup(ctx->res_hash, res_handle, 1); - - return res; -} - -void vrend_renderer_get_cap_set(uint32_t cap_set, uint32_t *max_ver, - uint32_t *max_size) { - switch (cap_set) { - case VREND_CAP_SET: - *max_ver = 1; - *max_size = sizeof(struct virgl_caps_v1); - break; - case VREND_CAP_SET2: - /* we should never need to increase this - it should be possible to just - * grow virgl_caps */ - *max_ver = 2; - *max_size = sizeof(struct virgl_caps_v2); - break; - default: - *max_ver = 0; - *max_size = 0; - break; - } -} - -void vrend_renderer_create_sub_ctx(struct vrend_context *ctx, int sub_ctx_id) { - struct vrend_sub_context *sub; - GLuint i; - - LIST_FOR_EACH_ENTRY(sub, &ctx->sub_ctxs, head) { - if (sub->sub_ctx_id == sub_ctx_id) { - return; - } - } - - sub = CALLOC_STRUCT(vrend_sub_context); - if (!sub) - return; - - sub->gl_context = vrend_clicbs->create_gl_context(ctx->client); - vrend_clicbs->make_current(ctx->client, sub->gl_context); - - sub->sub_ctx_id = sub_ctx_id; - - /* initialize the depth far_val to 1 */ - for (i = 0; i < PIPE_MAX_VIEWPORTS; i++) { - sub->vps[i].far_val = 1.0; - } - - if (!has_feature(feat_gles31_vertex_attrib_binding)) { - glGenVertexArrays(1, &sub->vaoid); - glBindVertexArray(sub->vaoid); - } - - glGenFramebuffers(1, &sub->fb_id); - glGenFramebuffers(2, sub->blit_fb_ids); - - list_inithead(&sub->programs); - list_inithead(&sub->streamout_list); - - sub->object_hash = vrend_object_init_ctx_table(); - - ctx->sub = sub; - list_add(&sub->head, &ctx->sub_ctxs); - if (sub_ctx_id == 0) - ctx->sub0 = sub; -} - -void vrend_renderer_destroy_sub_ctx(struct vrend_context *ctx, int sub_ctx_id) { - struct vrend_sub_context *sub, *tofree = NULL; - - /* never destroy sub context id 0 */ - if (sub_ctx_id == 0) - return; - - LIST_FOR_EACH_ENTRY(sub, &ctx->sub_ctxs, head) { - if (sub->sub_ctx_id == sub_ctx_id) { - tofree = sub; - } - } - - if (tofree) { - if (ctx->sub == tofree) { - ctx->sub = ctx->sub0; - vrend_clicbs->make_current(ctx->client, ctx->sub->gl_context); - } - vrend_destroy_sub_context(ctx->client, tofree); - } -} - -void vrend_renderer_set_sub_ctx(struct vrend_context *ctx, int sub_ctx_id) { - struct vrend_sub_context *sub; - /* find the sub ctx */ - - if (ctx->sub && ctx->sub->sub_ctx_id == sub_ctx_id) - return; - - LIST_FOR_EACH_ENTRY(sub, &ctx->sub_ctxs, head) { - if (sub->sub_ctx_id == sub_ctx_id) { - ctx->sub = sub; - vrend_clicbs->make_current(ctx->client, sub->gl_context); - break; - } - } -} \ No newline at end of file diff --git a/app/src/main/cpp/virglrenderer/src/vrend_renderer.h b/app/src/main/cpp/virglrenderer/src/vrend_renderer.h deleted file mode 100644 index 1f9e9d84e..000000000 --- a/app/src/main/cpp/virglrenderer/src/vrend_renderer.h +++ /dev/null @@ -1,393 +0,0 @@ -/************************************************************************** - * - * Copyright (C) 2014 Red Hat Inc. - * - * 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. - * - **************************************************************************/ - -#ifndef VREND_RENDERER_H -#define VREND_RENDERER_H - -#include "os/os_thread.h" -#include "pipe/p_state.h" -#include "util/u_double_list.h" -#include "util/u_inlines.h" - -#include "virgl_hw.h" -#include "virgl_protocol.h" -#include "vrend_iov.h" -#include "vrend_util.h" - -typedef void *virgl_gl_context; - -struct vrend_context; -struct virgl_client; - -/* Number of mipmap levels for which to keep the backing iov offsets. - * Value mirrored from mesa/virgl - */ -#define VR_MAX_TEXTURE_2D_LEVELS 15 - -#define VREND_MAX_CTX 64 - -#define VREND_STORAGE_GUEST_MEMORY BIT(0) -#define VREND_STORAGE_GL_TEXTURE BIT(1) -#define VREND_STORAGE_GL_BUFFER BIT(2) -#define VREND_STORAGE_HOST_SYSTEM_MEMORY BIT(3) -#define VREND_STORAGE_GL_IMMUTABLE BIT(4) - -struct vrend_resource { - struct pipe_resource base; - uint32_t storage_bits; - - GLuint id; - GLenum target; - - /* fb id if we need to readback this resource */ - GLuint readback_fb_id; - GLuint readback_fb_level; - GLuint readback_fb_z; - - GLuint tbo_tex_id; /* tbos have two ids to track */ - bool y_0_top; - - GLuint handle; - - void *priv; - /* Pointer to system memory storage for this resource. Only valid for - * VREND_RESOURCE_STORAGE_GUEST_ELSE_SYSTEM buffer storage. - */ - char *ptr; - /* IOV pointing to shared guest memory storage for this resource. */ - struct iovec *iov; - uint32_t num_iovs; - uint64_t mipmap_offsets[VR_MAX_TEXTURE_2D_LEVELS]; -}; - -#define VIRGL_TEXTURE_NEED_SWIZZLE (1 << 0) -#define VIRGL_TEXTURE_CAN_TEXTURE_STORAGE (1 << 1) -#define VIRGL_TEXTURE_CAN_READBACK (1 << 2) - -struct vrend_format_table { - enum virgl_formats format; - GLenum internalformat; - GLenum glformat; - GLenum gltype; - uint8_t swizzle[4]; - uint32_t bindings; - uint32_t flags; -}; - -struct vrend_if_cbs { - void (*write_fence)(struct virgl_client *client, unsigned fence_id); - virgl_gl_context (*create_gl_context)(struct virgl_client *client); - void (*destroy_gl_context)(struct virgl_client *client, virgl_gl_context ctx); - int (*make_current)(struct virgl_client *client, virgl_gl_context ctx); -}; - -struct vrend_state { - struct vrend_context *current_ctx; - struct vrend_context *current_hw_ctx; - struct list_head waiting_query_list; - - /* these appeared broken on at least one driver */ - bool use_explicit_locations; - uint32_t max_draw_buffers; - uint32_t max_texture_2d_size; - uint32_t max_texture_3d_size; - uint32_t max_texture_cube_size; - struct list_head active_ctx_list; - - struct list_head fence_list; - struct list_head fence_wait_list; - - /* Needed on GLES to inject a TCS */ - float tess_factors[6]; -}; - -int vrend_renderer_init(struct virgl_client *client, struct vrend_if_cbs *cbs); - -void vrend_insert_format(struct vrend_format_table *entry, uint32_t bindings, - uint32_t flags); -bool vrend_check_framebuffer_mixed_color_attachements(void); - -void vrend_insert_format_swizzle(int override_format, - struct vrend_format_table *entry, - uint32_t bindings, uint8_t swizzle[4], - uint32_t flags); -const struct vrend_format_table * -vrend_get_format_table_entry(enum virgl_formats format); - -int vrend_create_shader(struct vrend_context *ctx, uint32_t handle, - const struct pipe_stream_output_info *stream_output, - uint32_t req_local_mem, const char *shd_text, - uint32_t offlen, uint32_t num_tokens, uint32_t type, - uint32_t pkt_length); - -void vrend_bind_shader(struct vrend_context *ctx, uint32_t type, - uint32_t handle); - -void vrend_clear(struct vrend_context *ctx, unsigned buffers, - const union pipe_color_union *color, double depth, - unsigned stencil); - -int vrend_draw_vbo(struct vrend_context *ctx, const struct pipe_draw_info *info, - uint32_t cso, uint32_t indirect_handle, - uint32_t indirect_draw_count_handle); - -void vrend_set_framebuffer_state(struct vrend_context *ctx, uint32_t nr_cbufs, - uint32_t surf_handle[PIPE_MAX_COLOR_BUFS], - uint32_t zsurf_handle); - -struct vrend_context *vrend_create_context(struct virgl_client *client, int id); -bool vrend_destroy_context(struct vrend_context *ctx); -int vrend_renderer_context_create(struct virgl_client *client, uint32_t handle); -void vrend_renderer_context_create_internal(struct virgl_client *client, - uint32_t handle); -void vrend_renderer_context_destroy(struct virgl_client *client, - uint32_t handle); - -struct vrend_renderer_resource_create_args { - uint32_t handle; - enum pipe_texture_target target; - uint32_t format; - uint32_t bind; - uint32_t width; - uint32_t height; - uint32_t depth; - uint32_t array_size; - uint32_t last_level; - uint32_t nr_samples; - uint32_t flags; -}; - -int vrend_renderer_resource_create( - struct virgl_client *client, - struct vrend_renderer_resource_create_args *args, struct iovec *iov, - uint32_t num_iovs); -void vrend_renderer_resource_unref(struct virgl_client *client, - uint32_t handle); - -int vrend_create_surface(struct vrend_context *ctx, uint32_t handle, - uint32_t res_handle, uint32_t format, uint32_t val0, - uint32_t val1); -int vrend_create_sampler_view(struct vrend_context *ctx, uint32_t handle, - uint32_t res_handle, uint32_t format, - uint32_t val0, uint32_t val1, - uint32_t swizzle_packed); - -int vrend_create_sampler_state(struct vrend_context *ctx, uint32_t handle, - struct pipe_sampler_state *templ); - -int vrend_create_so_target(struct vrend_context *ctx, uint32_t handle, - uint32_t res_handle, uint32_t buffer_offset, - uint32_t buffer_size); - -void vrend_set_streamout_targets(struct vrend_context *ctx, - uint32_t append_bitmask, uint32_t num_targets, - uint32_t *handles); - -int vrend_create_vertex_elements_state( - struct vrend_context *ctx, uint32_t handle, unsigned num_elements, - const struct pipe_vertex_element *elements); -void vrend_bind_vertex_elements_state(struct vrend_context *ctx, - uint32_t handle); - -void vrend_set_single_vbo(struct vrend_context *ctx, uint32_t index, - uint32_t stride, uint32_t buffer_offset, - uint32_t res_handle); -void vrend_set_num_vbo(struct vrend_context *ctx, int num_vbo); - -int vrend_transfer_inline_write(struct vrend_context *ctx, - struct vrend_transfer_info *info); - -int vrend_renderer_copy_transfer3d(struct vrend_context *ctx, - struct vrend_transfer_info *info, - uint32_t src_handle); - -void vrend_set_viewport_states(struct vrend_context *ctx, uint32_t start_slot, - uint32_t num_viewports, - const struct pipe_viewport_state *state); -void vrend_set_num_sampler_views(struct vrend_context *ctx, - uint32_t shader_type, uint32_t start_slot, - uint32_t num_sampler_views); -void vrend_set_single_sampler_view(struct vrend_context *ctx, - uint32_t shader_type, uint32_t index, - uint32_t res_handle); - -void vrend_object_bind_blend(struct vrend_context *ctx, uint32_t handle); -void vrend_object_bind_dsa(struct vrend_context *ctx, uint32_t handle); -void vrend_object_bind_rasterizer(struct vrend_context *ctx, uint32_t handle); - -void vrend_bind_sampler_states(struct vrend_context *ctx, uint32_t shader_type, - uint32_t start_slot, uint32_t num_states, - uint32_t *handles); -void vrend_set_index_buffer(struct vrend_context *ctx, uint32_t res_handle, - uint32_t index_size, uint32_t offset); -void vrend_set_single_image_view(struct vrend_context *ctx, - uint32_t shader_type, uint32_t index, - uint32_t format, uint32_t access, - uint32_t layer_offset, uint32_t level_size, - uint32_t handle); -void vrend_set_single_ssbo(struct vrend_context *ctx, uint32_t shader_type, - uint32_t index, uint32_t offset, uint32_t length, - uint32_t handle); -void vrend_set_single_abo(struct vrend_context *ctx, uint32_t index, - uint32_t offset, uint32_t length, uint32_t handle); -void vrend_memory_barrier(struct vrend_context *ctx, unsigned flags); -void vrend_launch_grid(struct vrend_context *ctx, uint32_t *block, - uint32_t *grid, uint32_t indirect_handle, - uint32_t indirect_offset); -void vrend_set_framebuffer_state_no_attach(struct vrend_context *ctx, - uint32_t width, uint32_t height, - uint32_t layers, uint32_t samples); - -int vrend_renderer_transfer_iov(struct virgl_client *client, - const struct vrend_transfer_info *info, - int transfer_mode); - -void vrend_renderer_resource_copy_region( - struct vrend_context *ctx, uint32_t dst_handle, uint32_t dst_level, - uint32_t dstx, uint32_t dsty, uint32_t dstz, uint32_t src_handle, - uint32_t src_level, const struct pipe_box *src_box); - -void vrend_renderer_blit(struct vrend_context *ctx, uint32_t dst_handle, - uint32_t src_handle, - const struct pipe_blit_info *info); - -void vrend_set_stencil_ref(struct vrend_context *ctx, - struct pipe_stencil_ref *ref); -void vrend_set_blend_color(struct vrend_context *ctx, - struct pipe_blend_color *color); -void vrend_set_scissor_state(struct vrend_context *ctx, uint32_t start_slot, - uint32_t num_scissor, - struct pipe_scissor_state *ss); - -void vrend_set_polygon_stipple(struct vrend_context *ctx, - struct pipe_poly_stipple *ps); - -void vrend_set_clip_state(struct vrend_context *ctx, - struct pipe_clip_state *ucp); -void vrend_set_sample_mask(struct vrend_context *ctx, unsigned sample_mask); -void vrend_set_min_samples(struct vrend_context *ctx, unsigned min_samples); - -void vrend_set_constants(struct vrend_context *ctx, uint32_t shader, - uint32_t index, uint32_t num_constant, float *data); - -void vrend_set_uniform_buffer(struct vrend_context *ctx, uint32_t shader, - uint32_t index, uint32_t offset, uint32_t length, - uint32_t res_handle); - -void vrend_fb_bind_texture_id(struct vrend_resource *res, int id, int idx, - uint32_t level, uint32_t layer); - -void vrend_set_tess_state(struct vrend_context *ctx, - const float tess_factors[6]); - -void vrend_renderer_fini(struct virgl_client *client); - -int vrend_decode_block(struct virgl_client *client, uint32_t ctx_id, - uint32_t *block, int ndw); -struct vrend_context *vrend_lookup_renderer_ctx(struct virgl_client *client, - uint32_t ctx_id); - -int vrend_renderer_create_fence(struct virgl_client *client, - int client_fence_id, uint32_t ctx_id); - -void vrend_renderer_check_fences(struct virgl_client *client); - -bool vrend_hw_switch_context(struct vrend_context *ctx, bool now); -uint32_t vrend_renderer_object_insert(struct vrend_context *ctx, void *data, - uint32_t size, uint32_t handle, - enum virgl_object_type type); -void vrend_renderer_object_destroy(struct vrend_context *ctx, uint32_t handle); - -int vrend_create_query(struct vrend_context *ctx, uint32_t handle, - uint32_t query_type, uint32_t query_index, - uint32_t res_handle, uint32_t offset); - -int vrend_begin_query(struct vrend_context *ctx, uint32_t handle); -int vrend_end_query(struct vrend_context *ctx, uint32_t handle); -void vrend_get_query_result(struct vrend_context *ctx, uint32_t handle, - uint32_t wait); - -void vrend_renderer_fill_caps(struct virgl_client *client, uint32_t set, - uint32_t version, union virgl_caps *caps); - -void vrend_build_format_list(void); -void vrend_check_texture_storage(struct vrend_format_table *table); - -int vrend_renderer_resource_attach_iov(struct virgl_client *client, - int res_handle, struct iovec *iov, - int num_iovs); -void vrend_renderer_resource_detach_iov(struct virgl_client *client, - int res_handle, struct iovec **iov_p, - int *num_iovs_p); -void vrend_renderer_resource_destroy(struct vrend_resource *res); - -static inline void vrend_resource_reference(struct vrend_resource **ptr, - struct vrend_resource *tex) { - struct vrend_resource *old_tex = *ptr; - - if (pipe_reference(&(*ptr)->base.reference, &tex->base.reference)) - vrend_renderer_resource_destroy(old_tex); - *ptr = tex; -} - -void vrend_renderer_force_ctx_0(struct virgl_client *client); - -void vrend_renderer_attach_res_ctx(struct virgl_client *client, int ctx_id, - int resource_id); - -struct vrend_resource *vrend_renderer_ctx_res_lookup(struct vrend_context *ctx, - int res_handle); - -#define VREND_CAP_SET 1 -#define VREND_CAP_SET2 2 - -void vrend_renderer_get_cap_set(uint32_t cap_set, uint32_t *max_ver, - uint32_t *max_size); - -void vrend_renderer_create_sub_ctx(struct vrend_context *ctx, int sub_ctx_id); -void vrend_renderer_destroy_sub_ctx(struct vrend_context *ctx, int sub_ctx_id); -void vrend_renderer_set_sub_ctx(struct vrend_context *ctx, int sub_ctx_id); - -void vrend_fb_bind_texture(struct vrend_resource *res, int idx, uint32_t level, - uint32_t layer); -boolean format_is_copy_compatible(enum virgl_formats src, - enum virgl_formats dst, - boolean allow_compressed); - -/* blitter interface */ -void vrend_renderer_blit_gl( - struct virgl_client *client, struct vrend_resource *src_res, - struct vrend_resource *dst_res, GLenum blit_views[2], - const struct pipe_blit_info *info, bool has_texture_srgb_decode, - bool has_srgb_write_control, bool skip_dest_swizzle); -void vrend_blitter_fini(struct virgl_client *client); - -void vrend_decode_reset(struct virgl_client *client, bool ctx_0_only); - -unsigned vrend_renderer_query_multisample_caps(unsigned max_samples, - struct virgl_caps_v2 *caps); - -extern struct vrend_if_cbs *vrend_clicbs; - -#endif diff --git a/app/src/main/cpp/virglrenderer/src/vrend_shader.c b/app/src/main/cpp/virglrenderer/src/vrend_shader.c deleted file mode 100644 index 0b3b4c936..000000000 --- a/app/src/main/cpp/virglrenderer/src/vrend_shader.c +++ /dev/null @@ -1,7139 +0,0 @@ -/************************************************************************** - * - * Copyright (C) 2014 Red Hat Inc. - * - * 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. - * - **************************************************************************/ - -#include "vrend_shader.h" -#include "tgsi/tgsi_info.h" -#include "tgsi/tgsi_iterate.h" -#include "tgsi/tgsi_scan.h" -#include "util/u_math.h" -#include "util/u_memory.h" -#include -#include -#include -#include - -#include "vrend_strbuf.h" -#include "vrend_util.h" - -/* start convert of tgsi to glsl */ - -#define INTERP_PREFIX " " -#define INVARI_PREFIX "invariant" - -#define SHADER_REQ_NONE 0 -#define SHADER_REQ_SAMPLER_RECT (1 << 0) -#define SHADER_REQ_CUBE_ARRAY (1 << 1) -#define SHADER_REQ_INTS (1 << 2) -#define SHADER_REQ_SAMPLER_MS (1 << 3) -#define SHADER_REQ_INSTANCE_ID (1 << 4) -#define SHADER_REQ_LODQ (1 << 5) -#define SHADER_REQ_TXQ_LEVELS (1 << 6) -#define SHADER_REQ_TG4 (1 << 7) -#define SHADER_REQ_VIEWPORT_IDX (1 << 8) -#define SHADER_REQ_STENCIL_EXPORT (1 << 9) -#define SHADER_REQ_LAYER (1 << 10) -#define SHADER_REQ_SAMPLE_SHADING (1 << 11) -#define SHADER_REQ_GPU_SHADER5 (1 << 12) -#define SHADER_REQ_DERIVATIVE_CONTROL (1 << 13) -#define SHADER_REQ_FP64 (1 << 14) -#define SHADER_REQ_IMAGE_LOAD_STORE (1 << 15) -#define SHADER_REQ_ES31_COMPAT (1 << 16) -#define SHADER_REQ_IMAGE_SIZE (1 << 17) -#define SHADER_REQ_TXQS (1 << 18) -#define SHADER_REQ_FBFETCH (1 << 19) -#define SHADER_REQ_SHADER_CLOCK (1 << 20) -#define SHADER_REQ_PSIZE (1 << 21) -#define SHADER_REQ_IMAGE_ATOMIC (1 << 22) -#define SHADER_REQ_CLIP_DISTANCE (1 << 23) -#define SHADER_REQ_ENHANCED_LAYOUTS (1 << 24) -#define SHADER_REQ_SEPERATE_SHADER_OBJECTS (1 << 25) -#define SHADER_REQ_ARRAYS_OF_ARRAYS (1 << 26) -#define SHADER_REQ_SHADER_INTEGER_FUNC (1 << 27) -#define SHADER_REQ_SHADER_ATOMIC_FLOAT (1 << 28) -#define SHADER_REQ_NV_IMAGE_FORMATS (1 << 29) -#define SHADER_REQ_CONSERVATIVE_DEPTH (1 << 30) -#define SHADER_REQ_SAMPLER_BUF (1 << 31) - -#define FRONT_COLOR_EMITTED (1 << 0) -#define BACK_COLOR_EMITTED (1 << 1); - -struct vrend_shader_io { - unsigned name; - unsigned done; - int sid; - unsigned interpolate; - int first; - int last; - int array_id; - uint8_t usage_mask; - int swizzle_offset; - int num_components; - int layout_location; - unsigned location; - bool invariant; - bool precise; - bool glsl_predefined_no_emit; - bool glsl_no_index; - bool glsl_gl_block; - bool override_no_wm; - bool is_int; - bool fbfetch_used; - char glsl_name[128]; - unsigned stream; -}; - -struct vrend_shader_sampler { - int tgsi_sampler_type; - enum tgsi_return_type tgsi_sampler_return; -}; - -struct vrend_shader_table { - uint32_t key; - const char *string; -}; - -struct vrend_shader_image { - struct tgsi_declaration_image decl; - enum tgsi_return_type image_return; - bool vflag; -}; - -#define MAX_IMMEDIATE 1024 -struct immed { - int type; - union imm { - uint32_t ui; - int32_t i; - float f; - } val[4]; -}; - -struct vrend_temp_range { - int first; - int last; - int array_id; -}; - -struct vrend_io_range { - struct vrend_shader_io io; - bool used; -}; - -struct dump_ctx { - struct tgsi_iterate_context iter; - struct vrend_shader_cfg *cfg; - struct tgsi_shader_info info; - int prog_type; - int size; - struct vrend_strbuf glsl_main; - int indent_level; - struct vrend_strbuf glsl_hdr; - struct vrend_strbuf glsl_ver_ext; - uint instno; - - struct vrend_strbuf src_bufs[4]; - - uint32_t num_interps; - uint32_t num_inputs; - uint32_t attrib_input_mask; - struct vrend_shader_io inputs[64]; - uint32_t num_outputs; - struct vrend_shader_io outputs[64]; - uint8_t front_back_color_emitted_flags[64]; - uint32_t num_system_values; - struct vrend_shader_io system_values[32]; - - bool guest_sent_io_arrays; - struct vrend_io_range generic_input_range; - struct vrend_io_range patch_input_range; - struct vrend_io_range generic_output_range; - struct vrend_io_range patch_output_range; - - uint32_t generic_outputs_expected_mask; - uint32_t generic_inputs_emitted_mask; - uint32_t generic_outputs_emitted_mask; - - uint32_t num_temp_ranges; - struct vrend_temp_range *temp_ranges; - - struct vrend_shader_sampler samplers[32]; - uint32_t samplers_used; - - uint32_t ssbo_used_mask; - uint32_t ssbo_atomic_mask; - uint32_t ssbo_array_base; - uint32_t ssbo_atomic_array_base; - uint32_t ssbo_integer_mask; - uint8_t ssbo_memory_qualifier[32]; - - struct vrend_shader_image images[32]; - uint32_t images_used_mask; - - struct vrend_array *image_arrays; - uint32_t num_image_arrays; - - struct vrend_array *sampler_arrays; - uint32_t num_sampler_arrays; - - int num_consts; - int num_imm; - struct immed imm[MAX_IMMEDIATE]; - - uint32_t req_local_mem; - bool integer_memory; - - uint32_t ubo_base; - uint32_t ubo_used_mask; - int ubo_sizes[32]; - uint32_t num_address; - - uint32_t num_abo; - int abo_idx[32]; - int abo_sizes[32]; - int abo_offsets[32]; - - uint32_t shader_req_bits; - - struct pipe_stream_output_info *so; - char **so_names; - bool write_so_outputs[PIPE_MAX_SO_OUTPUTS]; - bool write_all_cbufs; - uint32_t shadow_samp_mask; - - int fs_coord_origin, fs_pixel_center; - int fs_depth_layout; - - int gs_in_prim, gs_out_prim, gs_max_out_verts; - int gs_num_invocations; - - struct vrend_shader_key *key; - int num_in_clip_dist; - int num_clip_dist; - int fs_uses_clipdist_input; - int glsl_ver_required; - int color_in_mask; - /* only used when cull is enabled */ - uint8_t num_cull_dist_prop, num_clip_dist_prop; - bool front_face_emitted; - - bool has_clipvertex; - bool has_clipvertex_so; - bool vs_has_pervertex; - bool write_mul_utemp; - bool write_mul_itemp; - bool has_sample_input; - bool early_depth_stencil; - bool has_file_memory; - bool force_color_two_side; - bool winsys_adjust_y_emitted; - - int tcs_vertices_out; - int tes_prim_mode; - int tes_spacing; - int tes_vertex_order; - int tes_point_mode; - - uint16_t local_cs_block_size[3]; -}; - -enum vrend_type_qualifier { - TYPE_CONVERSION_NONE = 0, - FLOAT = 1, - VEC2 = 2, - VEC3 = 3, - VEC4 = 4, - INT = 5, - IVEC2 = 6, - IVEC3 = 7, - IVEC4 = 8, - UINT = 9, - UVEC2 = 10, - UVEC3 = 11, - UVEC4 = 12, - FLOAT_BITS_TO_UINT = 13, - UINT_BITS_TO_FLOAT = 14, - FLOAT_BITS_TO_INT = 15, - INT_BITS_TO_FLOAT = 16, - DOUBLE = 17, - DVEC2 = 18, -}; - -struct dest_info { - enum vrend_type_qualifier dtypeprefix; - enum vrend_type_qualifier dstconv; - enum vrend_type_qualifier udstconv; - enum vrend_type_qualifier idstconv; - bool dst_override_no_wm[2]; -}; - -struct source_info { - enum vrend_type_qualifier svec4; - uint32_t sreg_index; - bool tg4_has_component; - bool override_no_wm[3]; - bool override_no_cast[3]; - int imm_value; -}; - -static const struct vrend_shader_table conversion_table[] = { - {TYPE_CONVERSION_NONE, ""}, - {FLOAT, "float"}, - {VEC2, "vec2"}, - {VEC3, "vec3"}, - {VEC4, "vec4"}, - {INT, "int"}, - {IVEC2, "ivec2"}, - {IVEC3, "ivec3"}, - {IVEC4, "ivec4"}, - {UINT, "uint"}, - {UVEC2, "uvec2"}, - {UVEC3, "uvec3"}, - {UVEC4, "uvec4"}, - {FLOAT_BITS_TO_UINT, "floatBitsToUint"}, - {UINT_BITS_TO_FLOAT, "uintBitsToFloat"}, - {FLOAT_BITS_TO_INT, "floatBitsToInt"}, - {INT_BITS_TO_FLOAT, "intBitsToFloat"}, - {DOUBLE, "double"}, - {DVEC2, "dvec2"}, -}; - -enum io_type { io_in, io_out }; - -/* We prefer arrays of arrays, but if this is not available then TCS, GEOM, and - * TES inputs must be blocks, but FS input should not because interpolateAt* - * doesn't support dereferencing block members. */ -static inline bool prefer_generic_io_block(struct dump_ctx *ctx, - enum io_type io) { - switch (ctx->prog_type) { - case TGSI_PROCESSOR_FRAGMENT: - return false; - - case TGSI_PROCESSOR_TESS_CTRL: - return true; - - case TGSI_PROCESSOR_TESS_EVAL: - return io == io_in ? true : (ctx->key->gs_present ? true : false); - - case TGSI_PROCESSOR_GEOMETRY: - return io == io_in; - - case TGSI_PROCESSOR_VERTEX: - if (io == io_in) - return false; - return (ctx->key->gs_present || ctx->key->tes_present); - - default: - return false; - } -} - -static inline const char *get_string(enum vrend_type_qualifier key) { - if (key >= ARRAY_SIZE(conversion_table)) - return conversion_table[TYPE_CONVERSION_NONE].string; - - return conversion_table[key].string; -} - -static inline const char *get_wm_string(unsigned wm) { - switch (wm) { - case TGSI_WRITEMASK_NONE: - return ""; - case TGSI_WRITEMASK_X: - return ".x"; - case TGSI_WRITEMASK_XY: - return ".xy"; - case TGSI_WRITEMASK_XYZ: - return ".xyz"; - case TGSI_WRITEMASK_W: - return ".w"; - default: - return ""; - } -} - -const char *get_internalformat_string(int virgl_format, - enum tgsi_return_type *stype); - -static inline const char *tgsi_proc_to_prefix(int shader_type) { - switch (shader_type) { - case TGSI_PROCESSOR_VERTEX: - return "vs"; - case TGSI_PROCESSOR_FRAGMENT: - return "fs"; - case TGSI_PROCESSOR_GEOMETRY: - return "gs"; - case TGSI_PROCESSOR_TESS_CTRL: - return "tc"; - case TGSI_PROCESSOR_TESS_EVAL: - return "te"; - case TGSI_PROCESSOR_COMPUTE: - return "cs"; - default: - return NULL; - }; -} - -static inline const char *prim_to_name(int prim) { - switch (prim) { - case PIPE_PRIM_POINTS: - return "points"; - case PIPE_PRIM_LINES: - return "lines"; - case PIPE_PRIM_LINE_STRIP: - return "line_strip"; - case PIPE_PRIM_LINES_ADJACENCY: - return "lines_adjacency"; - case PIPE_PRIM_TRIANGLES: - return "triangles"; - case PIPE_PRIM_TRIANGLE_STRIP: - return "triangle_strip"; - case PIPE_PRIM_TRIANGLES_ADJACENCY: - return "triangles_adjacency"; - case PIPE_PRIM_QUADS: - return "quads"; - default: - return "UNKNOWN"; - }; -} - -static inline const char *prim_to_tes_name(int prim) { - switch (prim) { - case PIPE_PRIM_QUADS: - return "quads"; - case PIPE_PRIM_TRIANGLES: - return "triangles"; - case PIPE_PRIM_LINES: - return "isolines"; - default: - return "UNKNOWN"; - } -} - -static const char *get_spacing_string(int spacing) { - switch (spacing) { - case PIPE_TESS_SPACING_FRACTIONAL_ODD: - return "fractional_odd_spacing"; - case PIPE_TESS_SPACING_FRACTIONAL_EVEN: - return "fractional_even_spacing"; - case PIPE_TESS_SPACING_EQUAL: - default: - return "equal_spacing"; - } -} - -static inline int gs_input_prim_to_size(int prim) { - switch (prim) { - case PIPE_PRIM_POINTS: - return 1; - case PIPE_PRIM_LINES: - return 2; - case PIPE_PRIM_LINES_ADJACENCY: - return 4; - case PIPE_PRIM_TRIANGLES: - return 3; - case PIPE_PRIM_TRIANGLES_ADJACENCY: - return 6; - default: - return -1; - }; -} - -static const char *get_stage_input_name_prefix(struct dump_ctx *ctx, - int processor) { - const char *name_prefix; - switch (processor) { - case TGSI_PROCESSOR_FRAGMENT: - if (ctx->key->gs_present) - name_prefix = "gso"; - else if (ctx->key->tes_present) - name_prefix = "teo"; - else - name_prefix = "vso"; - break; - case TGSI_PROCESSOR_GEOMETRY: - if (ctx->key->tes_present) - name_prefix = "teo"; - else - name_prefix = "vso"; - break; - case TGSI_PROCESSOR_TESS_EVAL: - if (ctx->key->tcs_present) - name_prefix = "tco"; - else - name_prefix = "vso"; - break; - case TGSI_PROCESSOR_TESS_CTRL: - name_prefix = "vso"; - break; - case TGSI_PROCESSOR_VERTEX: - default: - name_prefix = "in"; - break; - } - return name_prefix; -} - -static const char *get_stage_output_name_prefix(int processor) { - const char *name_prefix; - switch (processor) { - case TGSI_PROCESSOR_FRAGMENT: - name_prefix = "fsout"; - break; - case TGSI_PROCESSOR_GEOMETRY: - name_prefix = "gso"; - break; - case TGSI_PROCESSOR_VERTEX: - name_prefix = "vso"; - break; - case TGSI_PROCESSOR_TESS_CTRL: - name_prefix = "tco"; - break; - case TGSI_PROCESSOR_TESS_EVAL: - name_prefix = "teo"; - break; - default: - name_prefix = "out"; - break; - } - return name_prefix; -} - -static void require_glsl_ver(struct dump_ctx *ctx, int glsl_ver) { - if (glsl_ver > ctx->glsl_ver_required) - ctx->glsl_ver_required = glsl_ver; -} - -static void emit_indent(struct dump_ctx *ctx) { - if (ctx->indent_level > 0) { - /* very high levels of indentation doesn't improve readability */ - int indent_level = MIN2(ctx->indent_level, 15); - char buf[16]; - memset(buf, '\t', indent_level); - buf[indent_level] = '\0'; - strbuf_append(&ctx->glsl_main, buf); - } -} - -static void emit_buf(struct dump_ctx *ctx, const char *buf) { - emit_indent(ctx); - strbuf_append(&ctx->glsl_main, buf); -} - -static void indent_buf(struct dump_ctx *ctx) { ctx->indent_level++; } - -static void outdent_buf(struct dump_ctx *ctx) { - if (ctx->indent_level <= 0) { - strbuf_set_error(&ctx->glsl_main); - return; - } - ctx->indent_level--; -} - -static void set_buf_error(struct dump_ctx *ctx) { - strbuf_set_error(&ctx->glsl_main); -} - -__attribute__((format(printf, 2, 3))) static void -emit_buff(struct dump_ctx *ctx, const char *fmt, ...) { - va_list va; - va_start(va, fmt); - emit_indent(ctx); - strbuf_vappendf(&ctx->glsl_main, fmt, va); - va_end(va); -} - -static void emit_hdr(struct dump_ctx *ctx, const char *buf) { - strbuf_append(&ctx->glsl_hdr, buf); -} - -static void set_hdr_error(struct dump_ctx *ctx) { - strbuf_set_error(&ctx->glsl_hdr); -} - -__attribute__((format(printf, 2, 3))) static void -emit_hdrf(struct dump_ctx *ctx, const char *fmt, ...) { - va_list va; - va_start(va, fmt); - strbuf_vappendf(&ctx->glsl_hdr, fmt, va); - va_end(va); -} - -__attribute__((format(printf, 2, 3))) static void -emit_ver_extf(struct dump_ctx *ctx, const char *fmt, ...) { - va_list va; - va_start(va, fmt); - strbuf_vappendf(&ctx->glsl_ver_ext, fmt, va); - va_end(va); -} - -static bool allocate_temp_range(struct dump_ctx *ctx, int first, int last, - int array_id) { - int idx = ctx->num_temp_ranges; - - ctx->temp_ranges = - realloc(ctx->temp_ranges, sizeof(struct vrend_temp_range) * (idx + 1)); - if (!ctx->temp_ranges) - return false; - - ctx->temp_ranges[idx].first = first; - ctx->temp_ranges[idx].last = last; - ctx->temp_ranges[idx].array_id = array_id; - ctx->num_temp_ranges++; - return true; -} - -static struct vrend_temp_range *find_temp_range(struct dump_ctx *ctx, - int index) { - uint32_t i; - for (i = 0; i < ctx->num_temp_ranges; i++) { - if (index >= ctx->temp_ranges[i].first && index <= ctx->temp_ranges[i].last) - return &ctx->temp_ranges[i]; - } - return NULL; -} - -static bool samplertype_is_shadow(int sampler_type) { - switch (sampler_type) { - case TGSI_TEXTURE_SHADOW1D: - case TGSI_TEXTURE_SHADOW1D_ARRAY: - case TGSI_TEXTURE_SHADOW2D: - case TGSI_TEXTURE_SHADOWRECT: - case TGSI_TEXTURE_SHADOW2D_ARRAY: - case TGSI_TEXTURE_SHADOWCUBE: - case TGSI_TEXTURE_SHADOWCUBE_ARRAY: - return true; - default: - return false; - } -} - -static uint32_t samplertype_to_req_bits(int sampler_type) { - - switch (sampler_type) { - case TGSI_TEXTURE_SHADOWCUBE_ARRAY: - case TGSI_TEXTURE_CUBE_ARRAY: - return SHADER_REQ_CUBE_ARRAY; - case TGSI_TEXTURE_2D_MSAA: - case TGSI_TEXTURE_2D_ARRAY_MSAA: - return SHADER_REQ_SAMPLER_MS; - case TGSI_TEXTURE_BUFFER: - return SHADER_REQ_SAMPLER_BUF; - case TGSI_TEXTURE_SHADOWRECT: - case TGSI_TEXTURE_RECT: - return SHADER_REQ_SAMPLER_RECT; - default: - return 0; - } -} - -static bool add_images(struct dump_ctx *ctx, int first, int last, - struct tgsi_declaration_image *img_decl) { - int i; - - const struct util_format_description *descr = - util_format_description(img_decl->Format); - if (descr->nr_channels == 2 && descr->swizzle[0] == UTIL_FORMAT_SWIZZLE_X && - descr->swizzle[1] == UTIL_FORMAT_SWIZZLE_Y && - descr->swizzle[2] == UTIL_FORMAT_SWIZZLE_0 && - descr->swizzle[3] == UTIL_FORMAT_SWIZZLE_1) { - ctx->shader_req_bits |= SHADER_REQ_NV_IMAGE_FORMATS; - } else if (img_decl->Format == PIPE_FORMAT_R11G11B10_FLOAT || - img_decl->Format == PIPE_FORMAT_R10G10B10A2_UINT || - img_decl->Format == PIPE_FORMAT_R10G10B10A2_UNORM || - img_decl->Format == PIPE_FORMAT_R16G16B16A16_UNORM || - img_decl->Format == PIPE_FORMAT_R16G16B16A16_SNORM) - ctx->shader_req_bits |= SHADER_REQ_NV_IMAGE_FORMATS; - else if (descr->nr_channels == 1 && - descr->swizzle[0] == UTIL_FORMAT_SWIZZLE_X && - descr->swizzle[1] == UTIL_FORMAT_SWIZZLE_0 && - descr->swizzle[2] == UTIL_FORMAT_SWIZZLE_0 && - descr->swizzle[3] == UTIL_FORMAT_SWIZZLE_1 && - (descr->channel[0].size == 8 || descr->channel[0].size == 16)) - ctx->shader_req_bits |= SHADER_REQ_NV_IMAGE_FORMATS; - - for (i = first; i <= last; i++) { - ctx->images[i].decl = *img_decl; - ctx->images[i].vflag = false; - ctx->images_used_mask |= (1 << i); - - if (!samplertype_is_shadow(ctx->images[i].decl.Resource)) - ctx->shader_req_bits |= - samplertype_to_req_bits(ctx->images[i].decl.Resource); - } - - if (ctx->info.indirect_files & (1 << TGSI_FILE_IMAGE)) { - if (ctx->num_image_arrays) { - struct vrend_array *last_array = - &ctx->image_arrays[ctx->num_image_arrays - 1]; - /* - * If this set of images is consecutive to the last array, - * and has compatible return and decls, then increase the array size. - */ - if ((last_array->first + last_array->array_size == first) && - !memcmp(&ctx->images[last_array->first].decl, - &ctx->images[first].decl, sizeof(ctx->images[first].decl)) && - ctx->images[last_array->first].image_return == - ctx->images[first].image_return) { - last_array->array_size += last - first + 1; - return true; - } - } - - /* allocate a new image array for this range of images */ - ctx->num_image_arrays++; - ctx->image_arrays = realloc(ctx->image_arrays, sizeof(struct vrend_array) * - ctx->num_image_arrays); - if (!ctx->image_arrays) - return false; - ctx->image_arrays[ctx->num_image_arrays - 1].first = first; - ctx->image_arrays[ctx->num_image_arrays - 1].array_size = last - first + 1; - } - return true; -} - -static bool add_sampler_array(struct dump_ctx *ctx, int first, int last) { - int idx = ctx->num_sampler_arrays; - ctx->num_sampler_arrays++; - ctx->sampler_arrays = - realloc(ctx->sampler_arrays, - sizeof(struct vrend_array) * ctx->num_sampler_arrays); - if (!ctx->sampler_arrays) - return false; - - ctx->sampler_arrays[idx].first = first; - ctx->sampler_arrays[idx].array_size = last - first + 1; - return true; -} - -static int lookup_sampler_array(struct dump_ctx *ctx, int index) { - uint32_t i; - for (i = 0; i < ctx->num_sampler_arrays; i++) { - int last = - ctx->sampler_arrays[i].first + ctx->sampler_arrays[i].array_size - 1; - if (index >= ctx->sampler_arrays[i].first && index <= last) { - return ctx->sampler_arrays[i].first; - } - } - return -1; -} - -int vrend_shader_lookup_sampler_array(struct vrend_shader_info *sinfo, - int index) { - int i; - for (i = 0; i < sinfo->num_sampler_arrays; i++) { - int last = sinfo->sampler_arrays[i].first + - sinfo->sampler_arrays[i].array_size - 1; - if (index >= sinfo->sampler_arrays[i].first && index <= last) { - return sinfo->sampler_arrays[i].first; - } - } - return -1; -} - -static bool add_samplers(struct dump_ctx *ctx, int first, int last, - int sview_type, enum tgsi_return_type sview_rtype) { - if (sview_rtype == TGSI_RETURN_TYPE_SINT || - sview_rtype == TGSI_RETURN_TYPE_UINT) - ctx->shader_req_bits |= SHADER_REQ_INTS; - - for (int i = first; i <= last; i++) { - ctx->samplers[i].tgsi_sampler_return = sview_rtype; - ctx->samplers[i].tgsi_sampler_type = sview_type; - } - - if (ctx->info.indirect_files & (1 << TGSI_FILE_SAMPLER)) { - if (ctx->num_sampler_arrays) { - struct vrend_array *last_array = - &ctx->sampler_arrays[ctx->num_sampler_arrays - 1]; - if ((last_array->first + last_array->array_size == first) && - ctx->samplers[last_array->first].tgsi_sampler_type == sview_type && - ctx->samplers[last_array->first].tgsi_sampler_return == sview_rtype) { - last_array->array_size += last - first + 1; - return true; - } - } - - /* allocate a new image array for this range of images */ - return add_sampler_array(ctx, first, last); - } - return true; -} - -static struct vrend_array *lookup_image_array_ptr(struct dump_ctx *ctx, - int index) { - uint32_t i; - for (i = 0; i < ctx->num_image_arrays; i++) { - if (index >= ctx->image_arrays[i].first && - index <= - ctx->image_arrays[i].first + ctx->image_arrays[i].array_size - 1) { - return &ctx->image_arrays[i]; - } - } - return NULL; -} - -static int lookup_image_array(struct dump_ctx *ctx, int index) { - struct vrend_array *image = lookup_image_array_ptr(ctx, index); - return image ? image->first : -1; -} - -static boolean iter_inputs(struct tgsi_iterate_context *iter, - struct tgsi_full_declaration *decl) { - struct dump_ctx *ctx = (struct dump_ctx *)iter; - switch (decl->Declaration.File) { - case TGSI_FILE_INPUT: - for (uint32_t j = 0; j < ctx->num_inputs; j++) { - if (ctx->inputs[j].name == decl->Semantic.Name && - ctx->inputs[j].sid == decl->Semantic.Index && - ctx->inputs[j].first == decl->Range.First) - return true; - } - ctx->inputs[ctx->num_inputs].name = decl->Semantic.Name; - ctx->inputs[ctx->num_inputs].first = decl->Range.First; - ctx->inputs[ctx->num_inputs].last = decl->Range.Last; - ctx->num_inputs++; - } - return true; -} - -static bool logiop_require_inout(struct vrend_shader_key *key) { - if (!key->fs_logicop_enabled) - return false; - - switch (key->fs_logicop_func) { - case PIPE_LOGICOP_CLEAR: - case PIPE_LOGICOP_SET: - case PIPE_LOGICOP_COPY: - case PIPE_LOGICOP_COPY_INVERTED: - return false; - default: - return true; - } -} - -static boolean iter_declaration(struct tgsi_iterate_context *iter, - struct tgsi_full_declaration *decl) { - struct dump_ctx *ctx = (struct dump_ctx *)iter; - int i; - int color_offset = 0; - const char *name_prefix = ""; - bool add_two_side = false; - unsigned mask_temp; - - switch (decl->Declaration.File) { - case TGSI_FILE_INPUT: - for (uint32_t j = 0; j < ctx->num_inputs; j++) { - if (ctx->inputs[j].name == decl->Semantic.Name && - ctx->inputs[j].sid == decl->Semantic.Index && - ctx->inputs[j].first == decl->Range.First && - ctx->inputs[j].usage_mask == decl->Declaration.UsageMask && - ((!decl->Declaration.Array && ctx->inputs[j].array_id == 0) || - (ctx->inputs[j].array_id == decl->Array.ArrayID))) - return true; - } - i = ctx->num_inputs++; - if (ctx->num_inputs > ARRAY_SIZE(ctx->inputs)) - return false; - if (iter->processor.Processor == TGSI_PROCESSOR_VERTEX) { - ctx->attrib_input_mask |= (1 << decl->Range.First); - } - ctx->inputs[i].name = decl->Semantic.Name; - ctx->inputs[i].sid = decl->Semantic.Index; - ctx->inputs[i].interpolate = decl->Interp.Interpolate; - ctx->inputs[i].location = decl->Interp.Location; - ctx->inputs[i].first = decl->Range.First; - ctx->inputs[i].layout_location = 0; - ctx->inputs[i].last = decl->Range.Last; - ctx->inputs[i].array_id = decl->Declaration.Array ? decl->Array.ArrayID : 0; - ctx->inputs[i].usage_mask = mask_temp = decl->Declaration.UsageMask; - u_bit_scan_consecutive_range(&mask_temp, &ctx->inputs[i].swizzle_offset, - &ctx->inputs[i].num_components); - - ctx->inputs[i].glsl_predefined_no_emit = false; - ctx->inputs[i].glsl_no_index = false; - ctx->inputs[i].override_no_wm = ctx->inputs[i].num_components == 1; - ctx->inputs[i].glsl_gl_block = false; - - if (iter->processor.Processor == TGSI_PROCESSOR_FRAGMENT && - decl->Interp.Location == TGSI_INTERPOLATE_LOC_SAMPLE) { - ctx->shader_req_bits |= SHADER_REQ_GPU_SHADER5; - ctx->has_sample_input = true; - } - - if (ctx->inputs[i].first != ctx->inputs[i].last) - require_glsl_ver(ctx, 150); - - switch (ctx->inputs[i].name) { - case TGSI_SEMANTIC_COLOR: - if (iter->processor.Processor == TGSI_PROCESSOR_FRAGMENT) { - if (ctx->glsl_ver_required < 140) { - if (decl->Semantic.Index == 0) - name_prefix = "gl_Color"; - else if (decl->Semantic.Index == 1) - name_prefix = "gl_SecondaryColor"; - ctx->inputs[i].glsl_no_index = true; - } else { - if (ctx->key->color_two_side) { - int j = ctx->num_inputs++; - if (ctx->num_inputs > ARRAY_SIZE(ctx->inputs)) - return false; - - ctx->inputs[j].name = TGSI_SEMANTIC_BCOLOR; - ctx->inputs[j].sid = decl->Semantic.Index; - ctx->inputs[j].interpolate = decl->Interp.Interpolate; - ctx->inputs[j].location = decl->Interp.Location; - ctx->inputs[j].first = decl->Range.First; - ctx->inputs[j].last = decl->Range.Last; - ctx->inputs[j].glsl_predefined_no_emit = false; - ctx->inputs[j].glsl_no_index = false; - ctx->inputs[j].override_no_wm = false; - - ctx->color_in_mask |= (1 << decl->Semantic.Index); - - if (ctx->front_face_emitted == false) { - int k = ctx->num_inputs++; - if (ctx->num_inputs > ARRAY_SIZE(ctx->inputs)) - return false; - - ctx->inputs[k].name = TGSI_SEMANTIC_FACE; - ctx->inputs[k].sid = 0; - ctx->inputs[k].interpolate = TGSI_INTERPOLATE_CONSTANT; - ctx->inputs[k].location = TGSI_INTERPOLATE_LOC_CENTER; - ctx->inputs[k].first = 0; - ctx->inputs[k].override_no_wm = false; - ctx->inputs[k].glsl_predefined_no_emit = true; - ctx->inputs[k].glsl_no_index = true; - } - add_two_side = true; - } - name_prefix = "ex"; - } - break; - } - /* fallthrough */ - case TGSI_SEMANTIC_PRIMID: - if (iter->processor.Processor == TGSI_PROCESSOR_GEOMETRY) { - name_prefix = "gl_PrimitiveIDIn"; - ctx->inputs[i].glsl_predefined_no_emit = true; - ctx->inputs[i].glsl_no_index = true; - ctx->inputs[i].override_no_wm = true; - ctx->shader_req_bits |= SHADER_REQ_INTS; - break; - } else if (iter->processor.Processor == TGSI_PROCESSOR_FRAGMENT) { - name_prefix = "gl_PrimitiveID"; - ctx->inputs[i].glsl_predefined_no_emit = true; - ctx->inputs[i].glsl_no_index = true; - require_glsl_ver(ctx, 150); - break; - } - /* fallthrough */ - case TGSI_SEMANTIC_VIEWPORT_INDEX: - if (iter->processor.Processor == TGSI_PROCESSOR_FRAGMENT) { - ctx->inputs[i].glsl_predefined_no_emit = true; - ctx->inputs[i].glsl_no_index = true; - ctx->inputs[i].is_int = true; - ctx->inputs[i].override_no_wm = true; - name_prefix = "gl_ViewportIndex"; - if (ctx->glsl_ver_required >= 140) - ctx->shader_req_bits |= SHADER_REQ_LAYER; - - ctx->shader_req_bits |= SHADER_REQ_VIEWPORT_IDX; - break; - } - /* fallthrough */ - case TGSI_SEMANTIC_LAYER: - if (iter->processor.Processor == TGSI_PROCESSOR_FRAGMENT) { - name_prefix = "gl_Layer"; - ctx->inputs[i].glsl_predefined_no_emit = true; - ctx->inputs[i].glsl_no_index = true; - ctx->inputs[i].is_int = true; - ctx->inputs[i].override_no_wm = true; - ctx->shader_req_bits |= SHADER_REQ_LAYER; - break; - } - /* fallthrough */ - case TGSI_SEMANTIC_PSIZE: - if (iter->processor.Processor == TGSI_PROCESSOR_GEOMETRY || - iter->processor.Processor == TGSI_PROCESSOR_TESS_CTRL || - iter->processor.Processor == TGSI_PROCESSOR_TESS_EVAL) { - name_prefix = "gl_PointSize"; - ctx->inputs[i].glsl_predefined_no_emit = true; - ctx->inputs[i].glsl_no_index = true; - ctx->inputs[i].override_no_wm = true; - ctx->inputs[i].glsl_gl_block = true; - ctx->shader_req_bits |= SHADER_REQ_PSIZE; - break; - } - /* fallthrough */ - case TGSI_SEMANTIC_CLIPDIST: - if (iter->processor.Processor == TGSI_PROCESSOR_GEOMETRY || - iter->processor.Processor == TGSI_PROCESSOR_TESS_CTRL || - iter->processor.Processor == TGSI_PROCESSOR_TESS_EVAL) { - name_prefix = "gl_ClipDistance"; - ctx->inputs[i].glsl_predefined_no_emit = true; - ctx->inputs[i].glsl_no_index = true; - ctx->inputs[i].glsl_gl_block = true; - ctx->num_in_clip_dist += - 4 * (ctx->inputs[i].last - ctx->inputs[i].first + 1); - ctx->shader_req_bits |= SHADER_REQ_CLIP_DISTANCE; - if (ctx->inputs[i].last != ctx->inputs[i].first) - ctx->guest_sent_io_arrays = true; - break; - } else if (iter->processor.Processor == TGSI_PROCESSOR_FRAGMENT) { - name_prefix = "gl_ClipDistance"; - ctx->inputs[i].glsl_predefined_no_emit = true; - ctx->inputs[i].glsl_no_index = true; - ctx->num_in_clip_dist += - 4 * (ctx->inputs[i].last - ctx->inputs[i].first + 1); - ctx->shader_req_bits |= SHADER_REQ_CLIP_DISTANCE; - if (ctx->inputs[i].last != ctx->inputs[i].first) - ctx->guest_sent_io_arrays = true; - break; - } - /* fallthrough */ - case TGSI_SEMANTIC_POSITION: - if (iter->processor.Processor == TGSI_PROCESSOR_GEOMETRY || - iter->processor.Processor == TGSI_PROCESSOR_TESS_CTRL || - iter->processor.Processor == TGSI_PROCESSOR_TESS_EVAL) { - name_prefix = "gl_Position"; - ctx->inputs[i].glsl_predefined_no_emit = true; - ctx->inputs[i].glsl_no_index = true; - ctx->inputs[i].glsl_gl_block = true; - break; - } else if (iter->processor.Processor == TGSI_PROCESSOR_FRAGMENT) { - if (ctx->fs_pixel_center) { - name_prefix = "(gl_FragCoord - vec4(0.5, 0.5, 0.0, 0.0))"; - } else - name_prefix = "gl_FragCoord"; - ctx->inputs[i].glsl_predefined_no_emit = true; - ctx->inputs[i].glsl_no_index = true; - break; - } - /* fallthrough */ - case TGSI_SEMANTIC_FACE: - if (iter->processor.Processor == TGSI_PROCESSOR_FRAGMENT) { - if (ctx->front_face_emitted) { - ctx->num_inputs--; - return true; - } - name_prefix = "gl_FrontFacing"; - ctx->inputs[i].glsl_predefined_no_emit = true; - ctx->inputs[i].glsl_no_index = true; - ctx->front_face_emitted = true; - break; - } - /* fallthrough */ - case TGSI_SEMANTIC_PATCH: - case TGSI_SEMANTIC_GENERIC: - if (iter->processor.Processor == TGSI_PROCESSOR_FRAGMENT) { - if (ctx->key->coord_replace & (1 << ctx->inputs[i].sid)) { - name_prefix = - "vec4(gl_PointCoord.x, mix(1.0 - gl_PointCoord.y, " - "gl_PointCoord.y, clamp(winsys_adjust_y, 0.0, 1.0)), 0.0, 1.0)"; - ctx->inputs[i].glsl_predefined_no_emit = true; - ctx->inputs[i].glsl_no_index = true; - ctx->inputs[i].num_components = 4; - ctx->inputs[i].swizzle_offset = 0; - ctx->inputs[i].usage_mask = 0xf; - break; - } - } - - if (ctx->inputs[i].first != ctx->inputs[i].last || - ctx->inputs[i].array_id > 0) { - ctx->guest_sent_io_arrays = true; - } - - /* fallthrough */ - default: - name_prefix = get_stage_input_name_prefix(ctx, iter->processor.Processor); - break; - } - - if (ctx->inputs[i].glsl_no_index) - snprintf(ctx->inputs[i].glsl_name, 128, "%s", name_prefix); - else { - if (ctx->inputs[i].name == TGSI_SEMANTIC_FOG) { - ctx->inputs[i].usage_mask = 0xf; - ctx->inputs[i].num_components = 4; - ctx->inputs[i].swizzle_offset = 0; - ctx->inputs[i].override_no_wm = false; - snprintf(ctx->inputs[i].glsl_name, 128, "%s_f%d", name_prefix, - ctx->inputs[i].sid); - } else if (ctx->inputs[i].name == TGSI_SEMANTIC_COLOR) - snprintf(ctx->inputs[i].glsl_name, 128, "%s_c%d", name_prefix, - ctx->inputs[i].sid); - else if (ctx->inputs[i].name == TGSI_SEMANTIC_GENERIC) - snprintf(ctx->inputs[i].glsl_name, 128, "%s_g%dA%d", name_prefix, - ctx->inputs[i].sid, ctx->inputs[i].array_id); - else if (ctx->inputs[i].name == TGSI_SEMANTIC_PATCH) - snprintf(ctx->inputs[i].glsl_name, 128, "%s_p%dA%d", name_prefix, - ctx->inputs[i].sid, ctx->inputs[i].array_id); - else - snprintf(ctx->inputs[i].glsl_name, 128, "%s_%d", name_prefix, - ctx->inputs[i].first); - } - if (add_two_side) { - snprintf(ctx->inputs[i + 1].glsl_name, 128, "%s_bc%d", name_prefix, - ctx->inputs[i + 1].sid); - if (!ctx->front_face_emitted) { - snprintf(ctx->inputs[i + 2].glsl_name, 128, "%s", "gl_FrontFacing"); - ctx->front_face_emitted = true; - } - } - break; - case TGSI_FILE_OUTPUT: - for (uint32_t j = 0; j < ctx->num_outputs; j++) { - if (ctx->outputs[j].name == decl->Semantic.Name && - ctx->outputs[j].sid == decl->Semantic.Index && - ctx->outputs[j].first == decl->Range.First && - ctx->outputs[j].usage_mask == decl->Declaration.UsageMask && - ((!decl->Declaration.Array && ctx->outputs[j].array_id == 0) || - (ctx->outputs[j].array_id == decl->Array.ArrayID))) - return true; - } - i = ctx->num_outputs++; - if (ctx->num_outputs > ARRAY_SIZE(ctx->outputs)) - return false; - - ctx->outputs[i].name = decl->Semantic.Name; - ctx->outputs[i].sid = decl->Semantic.Index; - ctx->outputs[i].interpolate = decl->Interp.Interpolate; - ctx->outputs[i].invariant = decl->Declaration.Invariant; - ctx->outputs[i].precise = false; - ctx->outputs[i].first = decl->Range.First; - ctx->outputs[i].last = decl->Range.Last; - ctx->outputs[i].layout_location = 0; - ctx->outputs[i].array_id = - decl->Declaration.Array ? decl->Array.ArrayID : 0; - ctx->outputs[i].usage_mask = mask_temp = decl->Declaration.UsageMask; - u_bit_scan_consecutive_range(&mask_temp, &ctx->outputs[i].swizzle_offset, - &ctx->outputs[i].num_components); - ctx->outputs[i].glsl_predefined_no_emit = false; - ctx->outputs[i].glsl_no_index = false; - ctx->outputs[i].override_no_wm = ctx->outputs[i].num_components == 1; - ctx->outputs[i].is_int = false; - ctx->outputs[i].fbfetch_used = false; - - switch (ctx->outputs[i].name) { - case TGSI_SEMANTIC_POSITION: - if (iter->processor.Processor == TGSI_PROCESSOR_VERTEX || - iter->processor.Processor == TGSI_PROCESSOR_GEOMETRY || - iter->processor.Processor == TGSI_PROCESSOR_TESS_CTRL || - iter->processor.Processor == TGSI_PROCESSOR_TESS_EVAL) { - name_prefix = "gl_Position"; - ctx->outputs[i].glsl_predefined_no_emit = true; - ctx->outputs[i].glsl_no_index = true; - if (iter->processor.Processor == TGSI_PROCESSOR_TESS_CTRL) - ctx->outputs[i].glsl_gl_block = true; - } else if (iter->processor.Processor == TGSI_PROCESSOR_FRAGMENT) { - name_prefix = "gl_FragDepth"; - ctx->outputs[i].glsl_predefined_no_emit = true; - ctx->outputs[i].glsl_no_index = true; - ctx->outputs[i].override_no_wm = true; - } - break; - case TGSI_SEMANTIC_STENCIL: - if (iter->processor.Processor == TGSI_PROCESSOR_FRAGMENT) { - name_prefix = "gl_FragStencilRefARB"; - ctx->outputs[i].glsl_predefined_no_emit = true; - ctx->outputs[i].glsl_no_index = true; - ctx->outputs[i].override_no_wm = true; - ctx->outputs[i].is_int = true; - ctx->shader_req_bits |= (SHADER_REQ_INTS | SHADER_REQ_STENCIL_EXPORT); - } - break; - case TGSI_SEMANTIC_CLIPDIST: - ctx->shader_req_bits |= SHADER_REQ_CLIP_DISTANCE; - name_prefix = "gl_ClipDistance"; - ctx->outputs[i].glsl_predefined_no_emit = true; - ctx->outputs[i].glsl_no_index = true; - ctx->num_clip_dist += - 4 * (ctx->outputs[i].last - ctx->outputs[i].first + 1); - if (iter->processor.Processor == TGSI_PROCESSOR_VERTEX && - (ctx->key->gs_present || ctx->key->tcs_present)) - require_glsl_ver(ctx, 150); - if (iter->processor.Processor == TGSI_PROCESSOR_TESS_CTRL) - ctx->outputs[i].glsl_gl_block = true; - if (ctx->outputs[i].last != ctx->outputs[i].first) - ctx->guest_sent_io_arrays = true; - break; - case TGSI_SEMANTIC_CLIPVERTEX: - name_prefix = "gl_ClipVertex"; - ctx->outputs[i].glsl_predefined_no_emit = true; - ctx->outputs[i].glsl_no_index = true; - ctx->outputs[i].override_no_wm = true; - ctx->outputs[i].invariant = false; - ctx->outputs[i].precise = false; - if (ctx->glsl_ver_required >= 140) - ctx->has_clipvertex = true; - break; - case TGSI_SEMANTIC_SAMPLEMASK: - if (iter->processor.Processor == TGSI_PROCESSOR_FRAGMENT) { - ctx->outputs[i].glsl_predefined_no_emit = true; - ctx->outputs[i].glsl_no_index = true; - ctx->outputs[i].override_no_wm = true; - ctx->outputs[i].is_int = true; - ctx->shader_req_bits |= (SHADER_REQ_INTS | SHADER_REQ_SAMPLE_SHADING); - name_prefix = "gl_SampleMask"; - break; - } - break; - case TGSI_SEMANTIC_COLOR: - if (iter->processor.Processor == TGSI_PROCESSOR_VERTEX) { - if (ctx->glsl_ver_required < 140) { - ctx->outputs[i].glsl_no_index = true; - if (ctx->outputs[i].sid == 0) - name_prefix = "gl_FrontColor"; - else if (ctx->outputs[i].sid == 1) - name_prefix = "gl_FrontSecondaryColor"; - } else - name_prefix = "ex"; - break; - } else if (iter->processor.Processor == TGSI_PROCESSOR_FRAGMENT && - ctx->key->fs_logicop_enabled) { - name_prefix = "fsout_tmp"; - break; - } - /* fallthrough */ - case TGSI_SEMANTIC_BCOLOR: - if (iter->processor.Processor == TGSI_PROCESSOR_VERTEX) { - if (ctx->glsl_ver_required < 140) { - ctx->outputs[i].glsl_no_index = true; - if (ctx->outputs[i].sid == 0) - name_prefix = "gl_BackColor"; - else if (ctx->outputs[i].sid == 1) - name_prefix = "gl_BackSecondaryColor"; - break; - } else - name_prefix = "ex"; - break; - } - /* fallthrough */ - case TGSI_SEMANTIC_PSIZE: - if (iter->processor.Processor == TGSI_PROCESSOR_VERTEX || - iter->processor.Processor == TGSI_PROCESSOR_GEOMETRY || - iter->processor.Processor == TGSI_PROCESSOR_TESS_CTRL || - iter->processor.Processor == TGSI_PROCESSOR_TESS_EVAL) { - ctx->outputs[i].glsl_predefined_no_emit = true; - ctx->outputs[i].glsl_no_index = true; - ctx->outputs[i].override_no_wm = true; - ctx->shader_req_bits |= SHADER_REQ_PSIZE; - name_prefix = "gl_PointSize"; - if (iter->processor.Processor == TGSI_PROCESSOR_TESS_CTRL) - ctx->outputs[i].glsl_gl_block = true; - break; - } - /* fallthrough */ - case TGSI_SEMANTIC_LAYER: - if (iter->processor.Processor == TGSI_PROCESSOR_GEOMETRY) { - ctx->outputs[i].glsl_predefined_no_emit = true; - ctx->outputs[i].glsl_no_index = true; - ctx->outputs[i].override_no_wm = true; - ctx->outputs[i].is_int = true; - name_prefix = "gl_Layer"; - break; - } - /* fallthrough */ - case TGSI_SEMANTIC_PRIMID: - if (iter->processor.Processor == TGSI_PROCESSOR_GEOMETRY) { - ctx->outputs[i].glsl_predefined_no_emit = true; - ctx->outputs[i].glsl_no_index = true; - ctx->outputs[i].override_no_wm = true; - ctx->outputs[i].is_int = true; - name_prefix = "gl_PrimitiveID"; - break; - } - /* fallthrough */ - case TGSI_SEMANTIC_VIEWPORT_INDEX: - if (iter->processor.Processor == TGSI_PROCESSOR_GEOMETRY) { - ctx->outputs[i].glsl_predefined_no_emit = true; - ctx->outputs[i].glsl_no_index = true; - ctx->outputs[i].override_no_wm = true; - ctx->outputs[i].is_int = true; - name_prefix = "gl_ViewportIndex"; - ctx->shader_req_bits |= SHADER_REQ_VIEWPORT_IDX; - break; - } - /* fallthrough */ - case TGSI_SEMANTIC_TESSOUTER: - if (iter->processor.Processor == TGSI_PROCESSOR_TESS_CTRL) { - ctx->outputs[i].glsl_predefined_no_emit = true; - ctx->outputs[i].glsl_no_index = true; - ctx->outputs[i].override_no_wm = true; - name_prefix = "gl_TessLevelOuter"; - break; - } - /* fallthrough */ - case TGSI_SEMANTIC_TESSINNER: - if (iter->processor.Processor == TGSI_PROCESSOR_TESS_CTRL) { - ctx->outputs[i].glsl_predefined_no_emit = true; - ctx->outputs[i].glsl_no_index = true; - ctx->outputs[i].override_no_wm = true; - name_prefix = "gl_TessLevelInner"; - break; - } - /* fallthrough */ - case TGSI_SEMANTIC_PATCH: - case TGSI_SEMANTIC_GENERIC: - if (iter->processor.Processor == TGSI_PROCESSOR_VERTEX) - if (ctx->outputs[i].name == TGSI_SEMANTIC_GENERIC) - color_offset = -1; - - if (ctx->outputs[i].first != ctx->outputs[i].last || - ctx->outputs[i].array_id > 0) { - ctx->guest_sent_io_arrays = true; - } - /* fallthrough */ - default: - name_prefix = get_stage_output_name_prefix(iter->processor.Processor); - break; - } - - if (ctx->outputs[i].glsl_no_index) - snprintf(ctx->outputs[i].glsl_name, 64, "%s", name_prefix); - else { - if (ctx->outputs[i].name == TGSI_SEMANTIC_FOG) { - ctx->outputs[i].usage_mask = 0xf; - ctx->outputs[i].num_components = 4; - ctx->outputs[i].swizzle_offset = 0; - ctx->outputs[i].override_no_wm = false; - snprintf(ctx->outputs[i].glsl_name, 64, "%s_f%d", name_prefix, - ctx->outputs[i].sid); - } else if (ctx->outputs[i].name == TGSI_SEMANTIC_COLOR) - snprintf(ctx->outputs[i].glsl_name, 64, "%s_c%d", name_prefix, - ctx->outputs[i].sid); - else if (ctx->outputs[i].name == TGSI_SEMANTIC_BCOLOR) - snprintf(ctx->outputs[i].glsl_name, 64, "%s_bc%d", name_prefix, - ctx->outputs[i].sid); - else if (ctx->outputs[i].name == TGSI_SEMANTIC_PATCH) - snprintf(ctx->outputs[i].glsl_name, 64, "%s_p%dA%d", name_prefix, - ctx->outputs[i].sid, ctx->outputs[i].array_id); - else if (ctx->outputs[i].name == TGSI_SEMANTIC_GENERIC) - snprintf(ctx->outputs[i].glsl_name, 64, "%s_g%dA%d", name_prefix, - ctx->outputs[i].sid, ctx->outputs[i].array_id); - else - snprintf(ctx->outputs[i].glsl_name, 64, "%s_%d", name_prefix, - ctx->outputs[i].first + color_offset); - } - break; - case TGSI_FILE_TEMPORARY: - if (!allocate_temp_range(ctx, decl->Range.First, decl->Range.Last, - decl->Array.ArrayID)) - return false; - break; - case TGSI_FILE_SAMPLER: - ctx->samplers_used |= (1 << decl->Range.Last); - break; - case TGSI_FILE_SAMPLER_VIEW: - if (decl->Range.Last >= ARRAY_SIZE(ctx->samplers)) - return false; - if (!add_samplers(ctx, decl->Range.First, decl->Range.Last, - decl->SamplerView.Resource, - decl->SamplerView.ReturnTypeX)) - return false; - break; - case TGSI_FILE_IMAGE: - ctx->shader_req_bits |= SHADER_REQ_IMAGE_LOAD_STORE; - if (decl->Range.Last >= ARRAY_SIZE(ctx->images)) - return false; - if (!add_images(ctx, decl->Range.First, decl->Range.Last, &decl->Image)) - return false; - break; - case TGSI_FILE_BUFFER: - if (decl->Range.First >= 32) - return false; - ctx->ssbo_used_mask |= (1 << decl->Range.First); - if (decl->Declaration.Atomic) { - if (decl->Range.First < ctx->ssbo_atomic_array_base) - ctx->ssbo_atomic_array_base = decl->Range.First; - ctx->ssbo_atomic_mask |= (1 << decl->Range.First); - } else { - if (decl->Range.First < ctx->ssbo_array_base) - ctx->ssbo_array_base = decl->Range.First; - } - break; - case TGSI_FILE_CONSTANT: - if (decl->Declaration.Dimension && decl->Dim.Index2D != 0) { - if (decl->Dim.Index2D > 31) - return false; - if (ctx->ubo_used_mask & (1 << decl->Dim.Index2D)) - return false; - ctx->ubo_used_mask |= (1 << decl->Dim.Index2D); - ctx->ubo_sizes[decl->Dim.Index2D] = decl->Range.Last + 1; - } else { - /* if we have a normal single const set then ubo base should be 1 */ - ctx->ubo_base = 1; - if (decl->Range.Last) { - if (decl->Range.Last + 1 > ctx->num_consts) - ctx->num_consts = decl->Range.Last + 1; - } else - ctx->num_consts++; - } - break; - case TGSI_FILE_ADDRESS: - ctx->num_address = decl->Range.Last + 1; - break; - case TGSI_FILE_SYSTEM_VALUE: - i = ctx->num_system_values++; - if (ctx->num_system_values > ARRAY_SIZE(ctx->system_values)) - return false; - - ctx->system_values[i].name = decl->Semantic.Name; - ctx->system_values[i].sid = decl->Semantic.Index; - ctx->system_values[i].glsl_predefined_no_emit = true; - ctx->system_values[i].glsl_no_index = true; - ctx->system_values[i].override_no_wm = true; - ctx->system_values[i].first = decl->Range.First; - if (decl->Semantic.Name == TGSI_SEMANTIC_INSTANCEID) { - name_prefix = "gl_InstanceID"; - ctx->shader_req_bits |= SHADER_REQ_INSTANCE_ID | SHADER_REQ_INTS; - } else if (decl->Semantic.Name == TGSI_SEMANTIC_VERTEXID) { - name_prefix = "gl_VertexID"; - ctx->shader_req_bits |= SHADER_REQ_INTS; - } else if (decl->Semantic.Name == TGSI_SEMANTIC_HELPER_INVOCATION) { - name_prefix = "gl_HelperInvocation"; - ctx->shader_req_bits |= SHADER_REQ_ES31_COMPAT; - } else if (decl->Semantic.Name == TGSI_SEMANTIC_SAMPLEID) { - name_prefix = "gl_SampleID"; - ctx->shader_req_bits |= (SHADER_REQ_SAMPLE_SHADING | SHADER_REQ_INTS); - } else if (decl->Semantic.Name == TGSI_SEMANTIC_SAMPLEPOS) { - name_prefix = "gl_SamplePosition"; - ctx->shader_req_bits |= SHADER_REQ_SAMPLE_SHADING; - } else if (decl->Semantic.Name == TGSI_SEMANTIC_INVOCATIONID) { - name_prefix = "gl_InvocationID"; - ctx->shader_req_bits |= (SHADER_REQ_INTS | SHADER_REQ_GPU_SHADER5); - } else if (decl->Semantic.Name == TGSI_SEMANTIC_SAMPLEMASK) { - name_prefix = "gl_SampleMaskIn[0]"; - ctx->shader_req_bits |= (SHADER_REQ_INTS | SHADER_REQ_GPU_SHADER5); - } else if (decl->Semantic.Name == TGSI_SEMANTIC_PRIMID) { - name_prefix = "gl_PrimitiveID"; - ctx->shader_req_bits |= (SHADER_REQ_INTS | SHADER_REQ_GPU_SHADER5); - } else if (decl->Semantic.Name == TGSI_SEMANTIC_TESSCOORD) { - name_prefix = "gl_TessCoord"; - ctx->system_values[i].override_no_wm = false; - } else if (decl->Semantic.Name == TGSI_SEMANTIC_VERTICESIN) { - ctx->shader_req_bits |= SHADER_REQ_INTS; - name_prefix = "gl_PatchVerticesIn"; - } else if (decl->Semantic.Name == TGSI_SEMANTIC_TESSOUTER) { - name_prefix = "gl_TessLevelOuter"; - } else if (decl->Semantic.Name == TGSI_SEMANTIC_TESSINNER) { - name_prefix = "gl_TessLevelInner"; - } else if (decl->Semantic.Name == TGSI_SEMANTIC_THREAD_ID) { - name_prefix = "gl_LocalInvocationID"; - ctx->system_values[i].override_no_wm = false; - } else if (decl->Semantic.Name == TGSI_SEMANTIC_BLOCK_ID) { - name_prefix = "gl_WorkGroupID"; - ctx->system_values[i].override_no_wm = false; - } else if (decl->Semantic.Name == TGSI_SEMANTIC_GRID_SIZE) { - name_prefix = "gl_NumWorkGroups"; - ctx->system_values[i].override_no_wm = false; - } else { - name_prefix = "unknown"; - } - snprintf(ctx->system_values[i].glsl_name, 64, "%s", name_prefix); - break; - case TGSI_FILE_MEMORY: - ctx->has_file_memory = true; - break; - case TGSI_FILE_HW_ATOMIC: - if (ctx->num_abo >= ARRAY_SIZE(ctx->abo_idx)) - return false; - ctx->abo_idx[ctx->num_abo] = decl->Dim.Index2D; - ctx->abo_sizes[ctx->num_abo] = decl->Range.Last - decl->Range.First + 1; - ctx->abo_offsets[ctx->num_abo] = decl->Range.First; - ctx->num_abo++; - break; - } - - return true; -} - -static boolean iter_property(struct tgsi_iterate_context *iter, - struct tgsi_full_property *prop) { - struct dump_ctx *ctx = (struct dump_ctx *)iter; - - switch (prop->Property.PropertyName) { - case TGSI_PROPERTY_FS_COLOR0_WRITES_ALL_CBUFS: - if (prop->u[0].Data == 1) - ctx->write_all_cbufs = true; - break; - case TGSI_PROPERTY_FS_COORD_ORIGIN: - ctx->fs_coord_origin = prop->u[0].Data; - break; - case TGSI_PROPERTY_FS_COORD_PIXEL_CENTER: - ctx->fs_pixel_center = prop->u[0].Data; - break; - case TGSI_PROPERTY_FS_DEPTH_LAYOUT: - break; - case TGSI_PROPERTY_GS_INPUT_PRIM: - ctx->gs_in_prim = prop->u[0].Data; - break; - case TGSI_PROPERTY_GS_OUTPUT_PRIM: - ctx->gs_out_prim = prop->u[0].Data; - break; - case TGSI_PROPERTY_GS_MAX_OUTPUT_VERTICES: - ctx->gs_max_out_verts = prop->u[0].Data; - break; - case TGSI_PROPERTY_GS_INVOCATIONS: - ctx->gs_num_invocations = prop->u[0].Data; - break; - case TGSI_PROPERTY_NUM_CLIPDIST_ENABLED: - ctx->shader_req_bits |= SHADER_REQ_CLIP_DISTANCE; - ctx->num_clip_dist_prop = prop->u[0].Data; - break; - case TGSI_PROPERTY_NUM_CULLDIST_ENABLED: - ctx->num_cull_dist_prop = prop->u[0].Data; - break; - case TGSI_PROPERTY_TCS_VERTICES_OUT: - ctx->tcs_vertices_out = prop->u[0].Data; - break; - case TGSI_PROPERTY_TES_PRIM_MODE: - ctx->tes_prim_mode = prop->u[0].Data; - break; - case TGSI_PROPERTY_TES_SPACING: - ctx->tes_spacing = prop->u[0].Data; - break; - case TGSI_PROPERTY_TES_VERTEX_ORDER_CW: - ctx->tes_vertex_order = prop->u[0].Data; - break; - case TGSI_PROPERTY_TES_POINT_MODE: - ctx->tes_point_mode = prop->u[0].Data; - break; - case TGSI_PROPERTY_FS_EARLY_DEPTH_STENCIL: - ctx->early_depth_stencil = prop->u[0].Data > 0; - if (ctx->early_depth_stencil) { - require_glsl_ver(ctx, 150); - ctx->shader_req_bits |= SHADER_REQ_IMAGE_LOAD_STORE; - } - break; - case TGSI_PROPERTY_CS_FIXED_BLOCK_WIDTH: - ctx->local_cs_block_size[0] = prop->u[0].Data; - break; - case TGSI_PROPERTY_CS_FIXED_BLOCK_HEIGHT: - ctx->local_cs_block_size[1] = prop->u[0].Data; - break; - case TGSI_PROPERTY_CS_FIXED_BLOCK_DEPTH: - ctx->local_cs_block_size[2] = prop->u[0].Data; - break; - default: - return false; - } - - return true; -} - -static boolean iter_immediate(struct tgsi_iterate_context *iter, - struct tgsi_full_immediate *imm) { - struct dump_ctx *ctx = (struct dump_ctx *)iter; - int i; - uint32_t first = ctx->num_imm; - - if (first >= ARRAY_SIZE(ctx->imm)) - return false; - - ctx->imm[first].type = imm->Immediate.DataType; - for (i = 0; i < 4; i++) { - if (imm->Immediate.DataType == TGSI_IMM_FLOAT32) { - ctx->imm[first].val[i].f = imm->u[i].Float; - } else if (imm->Immediate.DataType == TGSI_IMM_UINT32 || - imm->Immediate.DataType == TGSI_IMM_FLOAT64) { - ctx->shader_req_bits |= SHADER_REQ_INTS; - ctx->imm[first].val[i].ui = imm->u[i].Uint; - } else if (imm->Immediate.DataType == TGSI_IMM_INT32) { - ctx->shader_req_bits |= SHADER_REQ_INTS; - ctx->imm[first].val[i].i = imm->u[i].Int; - } - } - ctx->num_imm++; - return true; -} - -static char get_swiz_char(int swiz) { - switch (swiz) { - case TGSI_SWIZZLE_X: - return 'x'; - case TGSI_SWIZZLE_Y: - return 'y'; - case TGSI_SWIZZLE_Z: - return 'z'; - case TGSI_SWIZZLE_W: - return 'w'; - default: - return 0; - } -} - -static void emit_cbuf_writes(struct dump_ctx *ctx) { - int i; - - for (i = ctx->num_outputs; i < ctx->cfg->max_draw_buffers; i++) { - emit_buff(ctx, "fsout_c%d = fsout_c0;\n", i); - } -} - -static void emit_a8_swizzle(struct dump_ctx *ctx) { - emit_buf(ctx, "fsout_c0.x = fsout_c0.w;\n"); -} - -static const char *atests[PIPE_FUNC_ALWAYS + 1] = { - "false", "<", "==", "<=", ">", "!=", ">=", "true"}; - -static void emit_alpha_test(struct dump_ctx *ctx) { - char comp_buf[128]; - - if (!ctx->num_outputs) - return; - - if (!ctx->write_all_cbufs) { - /* only emit alpha stanza if first output is 0 */ - if (ctx->outputs[0].sid != 0) - return; - } - switch (ctx->key->alpha_test) { - case PIPE_FUNC_NEVER: - case PIPE_FUNC_ALWAYS: - snprintf(comp_buf, 128, "%s", atests[ctx->key->alpha_test]); - break; - case PIPE_FUNC_LESS: - case PIPE_FUNC_EQUAL: - case PIPE_FUNC_LEQUAL: - case PIPE_FUNC_GREATER: - case PIPE_FUNC_NOTEQUAL: - case PIPE_FUNC_GEQUAL: - snprintf(comp_buf, 128, "%s %s %f", "fsout_c0.w", - atests[ctx->key->alpha_test], ctx->key->alpha_ref_val); - break; - default: - set_buf_error(ctx); - return; - } - - emit_buff(ctx, "if (!(%s)) {\n\tdiscard;\n}\n", comp_buf); -} - -static void emit_pstipple_pass(struct dump_ctx *ctx) { - emit_buf(ctx, "stip_temp = texture(pstipple_sampler, vec2(gl_FragCoord.x / " - "32.0, gl_FragCoord.y / 32.0)).x;\n"); - emit_buf(ctx, "if (stip_temp > 0.0) {\n\tdiscard;\n}\n"); -} - -static void emit_color_select(struct dump_ctx *ctx) { - if (!ctx->key->color_two_side || !(ctx->color_in_mask & 0x3)) - return; - - if (ctx->color_in_mask & 1) - emit_buf(ctx, "realcolor0 = gl_FrontFacing ? ex_c0 : ex_bc0;\n"); - - if (ctx->color_in_mask & 2) - emit_buf(ctx, "realcolor1 = gl_FrontFacing ? ex_c1 : ex_bc1;\n"); -} - -static void emit_prescale(struct dump_ctx *ctx) { - emit_buf(ctx, "gl_Position.y = gl_Position.y * winsys_adjust_y;\n"); -} - -static void prepare_so_movs(struct dump_ctx *ctx) { - uint32_t i; - for (i = 0; i < ctx->so->num_outputs; i++) { - ctx->write_so_outputs[i] = true; - if (ctx->so->output[i].start_component != 0) - continue; - if (ctx->so->output[i].num_components != 4) - continue; - if (ctx->outputs[ctx->so->output[i].register_index].name == - TGSI_SEMANTIC_CLIPDIST) - continue; - if (ctx->outputs[ctx->so->output[i].register_index].name == - TGSI_SEMANTIC_POSITION) - continue; - - ctx->outputs[ctx->so->output[i].register_index].stream = - ctx->so->output[i].stream; - if (ctx->prog_type == TGSI_PROCESSOR_GEOMETRY && ctx->so->output[i].stream) - ctx->shader_req_bits |= SHADER_REQ_GPU_SHADER5; - - ctx->write_so_outputs[i] = false; - } -} - -static const struct vrend_shader_io * -get_io_slot(const struct vrend_shader_io *slots, unsigned nslots, int idx) { - const struct vrend_shader_io *result = slots; - for (unsigned i = 0; i < nslots; ++i, ++result) { - if ((result->first <= idx) && (result->last >= idx)) - return result; - } - assert(0 && "Output not found"); - return NULL; -} - -static inline void get_blockname(char outvar[64], const char *stage_prefix, - const struct vrend_shader_io *io) { - snprintf(outvar, 64, "block_%sg%dA%d", stage_prefix, io->sid, io->array_id); -} - -static inline void get_blockvarname(char outvar[64], const char *stage_prefix, - const struct vrend_shader_io *io, - const char *postfix) { - snprintf(outvar, 64, "%sg%dA%d_%x%s", stage_prefix, io->first, io->array_id, - io->usage_mask, postfix); -} - -static void get_so_name(struct dump_ctx *ctx, bool from_block, - const struct vrend_shader_io *output, int index, - char out_var[255], char *wm) { - if (output->first == output->last || output->name != TGSI_SEMANTIC_GENERIC) - snprintf(out_var, 255, "%s%s", output->glsl_name, wm); - else { - if ((output->name == TGSI_SEMANTIC_GENERIC) && - prefer_generic_io_block(ctx, io_out)) { - char blockname[64]; - const char *stage_prefix = get_stage_output_name_prefix(ctx->prog_type); - if (from_block) - get_blockname(blockname, stage_prefix, output); - else - get_blockvarname(blockname, stage_prefix, output, ""); - snprintf(out_var, 255, "%s.%s[%d]%s", blockname, output->glsl_name, - index - output->first, wm); - } else { - snprintf(out_var, 255, "%s[%d]%s", output->glsl_name, - index - output->first, wm); - } - } -} - -static void emit_so_movs(struct dump_ctx *ctx) { - uint32_t i, j; - char outtype[15] = ""; - char writemask[6]; - - if (ctx->so->num_outputs >= PIPE_MAX_SO_OUTPUTS) { - set_buf_error(ctx); - return; - } - - for (i = 0; i < ctx->so->num_outputs; i++) { - const struct vrend_shader_io *output = get_io_slot( - &ctx->outputs[0], ctx->num_outputs, ctx->so->output[i].register_index); - if (ctx->so->output[i].start_component != 0) { - int wm_idx = 0; - writemask[wm_idx++] = '.'; - for (j = 0; j < ctx->so->output[i].num_components; j++) { - unsigned idx = ctx->so->output[i].start_component + j; - if (idx >= 4) - break; - if (idx <= 2) - writemask[wm_idx++] = 'x' + idx; - else - writemask[wm_idx++] = 'w'; - } - writemask[wm_idx] = '\0'; - } else - writemask[0] = 0; - - if (!ctx->write_so_outputs[i]) { - if (ctx->so_names[i]) - free(ctx->so_names[i]); - if (ctx->so->output[i].register_index > ctx->num_outputs) - ctx->so_names[i] = NULL; - else if (ctx->outputs[ctx->so->output[i].register_index].name == - TGSI_SEMANTIC_CLIPVERTEX && - ctx->has_clipvertex) { - ctx->so_names[i] = strdup("clipv_tmp"); - ctx->has_clipvertex_so = true; - } else { - char out_var[255]; - get_so_name(ctx, true, output, ctx->so->output[i].register_index, - out_var, ""); - ctx->so_names[i] = strdup(out_var); - } - } else { - char ntemp[8]; - snprintf(ntemp, 8, "tfout%d", i); - ctx->so_names[i] = strdup(ntemp); - } - if (ctx->so->output[i].num_components == 1) { - if (ctx->outputs[ctx->so->output[i].register_index].is_int) - snprintf(outtype, 15, "intBitsToFloat"); - else - snprintf(outtype, 15, "float"); - } else - snprintf(outtype, 15, "vec%d", ctx->so->output[i].num_components); - - if (ctx->so->output[i].register_index >= 255) - continue; - - if (output->name == TGSI_SEMANTIC_CLIPDIST) { - if (output->first == output->last) - emit_buff(ctx, "tfout%d = %s(clip_dist_temp[%d]%s);\n", i, outtype, - output->sid, writemask); - else - emit_buff(ctx, "tfout%d = %s(clip_dist_temp[%d]%s);\n", i, outtype, - output->sid + ctx->so->output[i].register_index - - output->first, - writemask); - } else { - if (ctx->write_so_outputs[i]) { - char out_var[255]; - if (ctx->so->output[i].need_temp || - ctx->prog_type == TGSI_PROCESSOR_GEOMETRY || - output->glsl_predefined_no_emit) { - get_so_name(ctx, false, output, ctx->so->output[i].register_index, - out_var, writemask); - emit_buff(ctx, "tfout%d = %s(%s);\n", i, outtype, out_var); - } else { - get_so_name(ctx, true, output, ctx->so->output[i].register_index, - out_var, writemask); - ctx->so_names[i] = strdup(out_var); - } - } - } - } -} - -static void emit_clip_dist_movs(struct dump_ctx *ctx) { - int i; - bool has_prop = (ctx->num_clip_dist_prop + ctx->num_cull_dist_prop) > 0; - int ndists; - const char *prefix = ""; - - if (ctx->prog_type == PIPE_SHADER_TESS_CTRL) - prefix = "gl_out[gl_InvocationID]."; - if (ctx->num_clip_dist == 0 && ctx->key->clip_plane_enable) { - for (i = 0; i < 8; i++) { - emit_buff(ctx, "%sgl_ClipDistance[%d] = dot(%s, clipp[%d]);\n", prefix, i, - ctx->has_clipvertex ? "clipv_tmp" : "gl_Position", i); - } - return; - } - ndists = ctx->num_clip_dist; - if (has_prop) - ndists = ctx->num_clip_dist_prop + ctx->num_cull_dist_prop; - for (i = 0; i < ndists; i++) { - int clipidx = i < 4 ? 0 : 1; - char swiz = i & 3; - char wm = 0; - switch (swiz) { - default: - case 0: - wm = 'x'; - break; - case 1: - wm = 'y'; - break; - case 2: - wm = 'z'; - break; - case 3: - wm = 'w'; - break; - } - bool is_cull = false; - if (has_prop) { - if (i >= ctx->num_clip_dist_prop && - i < ctx->num_clip_dist_prop + ctx->num_cull_dist_prop) - is_cull = true; - } - const char *clip_cull = is_cull ? "Cull" : "Clip"; - emit_buff(ctx, "%sgl_%sDistance[%d] = clip_dist_temp[%d].%c;\n", prefix, - clip_cull, is_cull ? i - ctx->num_clip_dist_prop : i, clipidx, - wm); - } -} - -#define emit_arit_op2(op) \ - emit_buff(ctx, "%s = %s(%s((%s %s %s))%s);\n", dsts[0], \ - get_string(dinfo.dstconv), get_string(dinfo.dtypeprefix), srcs[0], \ - op, srcs[1], writemask) -#define emit_op1(op) \ - emit_buff(ctx, "%s = %s(%s(%s(%s))%s);\n", dsts[0], \ - get_string(dinfo.dstconv), get_string(dinfo.dtypeprefix), op, \ - srcs[0], writemask) -#define emit_compare(op) \ - emit_buff(ctx, "%s = %s(%s((%s(%s(%s), %s(%s))))%s);\n", dsts[0], \ - get_string(dinfo.dstconv), get_string(dinfo.dtypeprefix), op, \ - get_string(sinfo.svec4), srcs[0], get_string(sinfo.svec4), \ - srcs[1], writemask) - -#define emit_ucompare(op) \ - emit_buff(ctx, \ - "%s = %s(uintBitsToFloat(%s(%s(%s(%s), %s(%s))%s) * " \ - "%s(0xffffffff)));\n", \ - dsts[0], get_string(dinfo.dstconv), get_string(dinfo.udstconv), \ - op, get_string(sinfo.svec4), srcs[0], get_string(sinfo.svec4), \ - srcs[1], writemask, get_string(dinfo.udstconv)) - -static void handle_vertex_proc_exit(struct dump_ctx *ctx) { - if (ctx->so && !ctx->key->gs_present && !ctx->key->tes_present) - emit_so_movs(ctx); - - emit_clip_dist_movs(ctx); - - if (!ctx->key->gs_present && !ctx->key->tes_present) - emit_prescale(ctx); -} - -static void emit_fragment_logicop(struct dump_ctx *ctx) { - char src[PIPE_MAX_COLOR_BUFS][64]; - char src_fb[PIPE_MAX_COLOR_BUFS][64]; - double scale[PIPE_MAX_COLOR_BUFS]; - int mask[PIPE_MAX_COLOR_BUFS]; - char full_op[PIPE_MAX_COLOR_BUFS][128]; - - for (unsigned i = 0; i < ctx->num_outputs; i++) { - mask[i] = (1 << ctx->key->surface_component_bits[i]) - 1; - scale[i] = mask[i]; - switch (ctx->key->fs_logicop_func) { - case PIPE_LOGICOP_INVERT: - snprintf(src_fb[i], 64, "ivec4(%f * fsout_c%d + 0.5)", scale[i], i); - break; - case PIPE_LOGICOP_NOR: - case PIPE_LOGICOP_AND_INVERTED: - case PIPE_LOGICOP_AND_REVERSE: - case PIPE_LOGICOP_XOR: - case PIPE_LOGICOP_NAND: - case PIPE_LOGICOP_AND: - case PIPE_LOGICOP_EQUIV: - case PIPE_LOGICOP_OR_INVERTED: - case PIPE_LOGICOP_OR_REVERSE: - case PIPE_LOGICOP_OR: - snprintf(src_fb[i], 64, "ivec4(%f * fsout_c%d + 0.5)", scale[i], i); - /* fallthrough */ - case PIPE_LOGICOP_COPY_INVERTED: - snprintf(src[i], 64, "ivec4(%f * fsout_tmp_c%d + 0.5)", scale[i], i); - break; - case PIPE_LOGICOP_COPY: - case PIPE_LOGICOP_NOOP: - case PIPE_LOGICOP_CLEAR: - case PIPE_LOGICOP_SET: - break; - } - } - - for (unsigned i = 0; i < ctx->num_outputs; i++) { - switch (ctx->key->fs_logicop_func) { - case PIPE_LOGICOP_CLEAR: - snprintf(full_op[i], 128, "%s", "vec4(0)"); - break; - case PIPE_LOGICOP_NOOP: - full_op[i][0] = 0; - break; - case PIPE_LOGICOP_SET: - snprintf(full_op[i], 128, "%s", "vec4(1)"); - break; - case PIPE_LOGICOP_COPY: - snprintf(full_op[i], 128, "fsout_tmp_c%d", i); - break; - case PIPE_LOGICOP_COPY_INVERTED: - snprintf(full_op[i], 128, "~%s", src[i]); - break; - case PIPE_LOGICOP_INVERT: - snprintf(full_op[i], 128, "~%s", src_fb[i]); - break; - case PIPE_LOGICOP_AND: - snprintf(full_op[i], 128, "%s & %s", src[i], src_fb[i]); - break; - case PIPE_LOGICOP_NAND: - snprintf(full_op[i], 128, "~( %s & %s )", src[i], src_fb[i]); - break; - case PIPE_LOGICOP_NOR: - snprintf(full_op[i], 128, "~( %s | %s )", src[i], src_fb[i]); - break; - case PIPE_LOGICOP_AND_INVERTED: - snprintf(full_op[i], 128, "~%s & %s", src[i], src_fb[i]); - break; - case PIPE_LOGICOP_AND_REVERSE: - snprintf(full_op[i], 128, "%s & ~%s", src[i], src_fb[i]); - break; - case PIPE_LOGICOP_XOR: - snprintf(full_op[i], 128, "%s ^%s", src[i], src_fb[i]); - break; - case PIPE_LOGICOP_EQUIV: - snprintf(full_op[i], 128, "~( %s ^ %s )", src[i], src_fb[i]); - break; - case PIPE_LOGICOP_OR_INVERTED: - snprintf(full_op[i], 128, "~%s | %s", src[i], src_fb[i]); - break; - case PIPE_LOGICOP_OR_REVERSE: - snprintf(full_op[i], 128, "%s | ~%s", src[i], src_fb[i]); - break; - case PIPE_LOGICOP_OR: - snprintf(full_op[i], 128, "%s | %s", src[i], src_fb[i]); - break; - } - } - - for (unsigned i = 0; i < ctx->num_outputs; i++) { - switch (ctx->key->fs_logicop_func) { - case PIPE_LOGICOP_NOOP: - break; - case PIPE_LOGICOP_COPY: - case PIPE_LOGICOP_CLEAR: - case PIPE_LOGICOP_SET: - emit_buff(ctx, "fsout_c%d = %s;\n", i, full_op[i]); - break; - default: - emit_buff(ctx, "fsout_c%d = vec4((%s) & %d) / %f;\n", i, full_op[i], - mask[i], scale[i]); - } - } -} - -static void emit_cbuf_swizzle(struct dump_ctx *ctx) { - for (uint i = 0; i < ctx->num_outputs; i++) { - if (ctx->key->fs_swizzle_output_rgb_to_bgr & (1 << i)) { - emit_buff(ctx, "fsout_c%d = fsout_c%d.zyxw;\n", i, i); - } - } -} - -static void handle_fragment_proc_exit(struct dump_ctx *ctx) { - if (ctx->key->pstipple_tex) - emit_pstipple_pass(ctx); - - if (ctx->key->cbufs_are_a8_bitmask) - emit_a8_swizzle(ctx); - - if (ctx->key->add_alpha_test) - emit_alpha_test(ctx); - - if (ctx->key->fs_logicop_enabled) - emit_fragment_logicop(ctx); - - if (ctx->key->fs_swizzle_output_rgb_to_bgr) - emit_cbuf_swizzle(ctx); - - if (ctx->write_all_cbufs) - emit_cbuf_writes(ctx); -} - -static void set_texture_reqs(struct dump_ctx *ctx, - struct tgsi_full_instruction *inst, - uint32_t sreg_index) { - if (sreg_index >= ARRAY_SIZE(ctx->samplers)) { - set_buf_error(ctx); - return; - } - ctx->samplers[sreg_index].tgsi_sampler_type = inst->Texture.Texture; - - ctx->shader_req_bits |= samplertype_to_req_bits(inst->Texture.Texture); - - if (ctx->cfg->glsl_version >= 140) - if (ctx->shader_req_bits & - (SHADER_REQ_SAMPLER_RECT | SHADER_REQ_SAMPLER_BUF)) - require_glsl_ver(ctx, 140); -} - -/* size queries are pretty much separate */ -static void emit_txq(struct dump_ctx *ctx, struct tgsi_full_instruction *inst, - uint32_t sreg_index, const char *srcs[4], const char *dst, - const char *writemask) { - unsigned twm = TGSI_WRITEMASK_NONE; - char bias[128] = ""; - const int sampler_index = 1; - enum vrend_type_qualifier dtypeprefix = INT_BITS_TO_FLOAT; - - set_texture_reqs(ctx, inst, sreg_index); - - /* No LOD for these texture types, but on GLES we emulate RECT by using - * a normal 2D texture, so we have to give LOD 0 */ - switch (inst->Texture.Texture) { - case TGSI_TEXTURE_RECT: - case TGSI_TEXTURE_SHADOWRECT: - snprintf(bias, 128, ", 0"); - break; - /* fallthrough */ - case TGSI_TEXTURE_BUFFER: - case TGSI_TEXTURE_2D_MSAA: - case TGSI_TEXTURE_2D_ARRAY_MSAA: - break; - default: - snprintf(bias, 128, ", int(%s.w)", srcs[0]); - } - - /* need to emit a textureQueryLevels */ - if (inst->Dst[0].Register.WriteMask & 0x8) { - if (inst->Texture.Texture != TGSI_TEXTURE_BUFFER && - inst->Texture.Texture != TGSI_TEXTURE_RECT && - inst->Texture.Texture != TGSI_TEXTURE_2D_MSAA && - inst->Texture.Texture != TGSI_TEXTURE_2D_ARRAY_MSAA) { - ctx->shader_req_bits |= SHADER_REQ_TXQ_LEVELS; - if (inst->Dst[0].Register.WriteMask & 0x7) - twm = TGSI_WRITEMASK_W; - emit_buff(ctx, "%s%s = %s(textureQueryLevels(%s));\n", dst, - get_wm_string(twm), get_string(dtypeprefix), - srcs[sampler_index]); - } - - if (inst->Dst[0].Register.WriteMask & 0x7) { - switch (inst->Texture.Texture) { - case TGSI_TEXTURE_1D: - case TGSI_TEXTURE_BUFFER: - case TGSI_TEXTURE_SHADOW1D: - twm = TGSI_WRITEMASK_X; - break; - case TGSI_TEXTURE_1D_ARRAY: - case TGSI_TEXTURE_SHADOW1D_ARRAY: - case TGSI_TEXTURE_2D: - case TGSI_TEXTURE_SHADOW2D: - case TGSI_TEXTURE_RECT: - case TGSI_TEXTURE_SHADOWRECT: - case TGSI_TEXTURE_CUBE: - case TGSI_TEXTURE_SHADOWCUBE: - case TGSI_TEXTURE_2D_MSAA: - twm = TGSI_WRITEMASK_XY; - break; - case TGSI_TEXTURE_3D: - case TGSI_TEXTURE_2D_ARRAY: - case TGSI_TEXTURE_SHADOW2D_ARRAY: - case TGSI_TEXTURE_SHADOWCUBE_ARRAY: - case TGSI_TEXTURE_CUBE_ARRAY: - case TGSI_TEXTURE_2D_ARRAY_MSAA: - twm = TGSI_WRITEMASK_XYZ; - break; - } - } - } - - if (inst->Dst[0].Register.WriteMask & 0x7) { - bool txq_returns_vec = (inst->Texture.Texture != TGSI_TEXTURE_BUFFER) && - ((inst->Texture.Texture != TGSI_TEXTURE_1D && - inst->Texture.Texture != TGSI_TEXTURE_SHADOW1D)); - - if ((inst->Texture.Texture == TGSI_TEXTURE_1D_ARRAY || - inst->Texture.Texture == TGSI_TEXTURE_SHADOW1D_ARRAY)) { - writemask = ".xz"; - } - - emit_buff(ctx, "%s%s = %s(textureSize(%s%s))%s;\n", dst, get_wm_string(twm), - get_string(dtypeprefix), srcs[sampler_index], bias, - txq_returns_vec ? writemask : ""); - } -} - -/* sample queries are pretty much separate */ -static void emit_txqs(struct dump_ctx *ctx, struct tgsi_full_instruction *inst, - uint32_t sreg_index, const char *srcs[4], - const char *dst) { - const int sampler_index = 0; - enum vrend_type_qualifier dtypeprefix = INT_BITS_TO_FLOAT; - - ctx->shader_req_bits |= SHADER_REQ_TXQS; - set_texture_reqs(ctx, inst, sreg_index); - - if (inst->Texture.Texture != TGSI_TEXTURE_2D_MSAA && - inst->Texture.Texture != TGSI_TEXTURE_2D_ARRAY_MSAA) { - set_buf_error(ctx); - return; - } - - emit_buff(ctx, "%s = %s(textureSamples(%s));\n", dst, get_string(dtypeprefix), - srcs[sampler_index]); -} - -static const char *get_tex_inst_ext(struct tgsi_full_instruction *inst) { - switch (inst->Instruction.Opcode) { - case TGSI_OPCODE_LODQ: - return "QueryLOD"; - case TGSI_OPCODE_TXP: - if (inst->Texture.Texture == TGSI_TEXTURE_CUBE || - inst->Texture.Texture == TGSI_TEXTURE_2D_ARRAY || - inst->Texture.Texture == TGSI_TEXTURE_1D_ARRAY) - return ""; - else if (inst->Texture.NumOffsets == 1) - return "ProjOffset"; - else - return "Proj"; - case TGSI_OPCODE_TXL: - case TGSI_OPCODE_TXL2: - if (inst->Texture.NumOffsets == 1) - return "LodOffset"; - else - return "Lod"; - case TGSI_OPCODE_TXD: - if (inst->Texture.NumOffsets == 1) - return "GradOffset"; - else - return "Grad"; - case TGSI_OPCODE_TG4: - if (inst->Texture.NumOffsets == 4) - return "GatherOffsets"; - else if (inst->Texture.NumOffsets == 1) - return "GatherOffset"; - else - return "Gather"; - default: - if (inst->Texture.NumOffsets == 1) - return "Offset"; - else - return ""; - } -} - -static bool fill_offset_buffer(struct dump_ctx *ctx, - struct tgsi_full_instruction *inst, - char *offbuf) { - if (inst->TexOffsets[0].File == TGSI_FILE_IMMEDIATE) { - struct immed *imd = &ctx->imm[inst->TexOffsets[0].Index]; - switch (inst->Texture.Texture) { - case TGSI_TEXTURE_1D: - case TGSI_TEXTURE_1D_ARRAY: - case TGSI_TEXTURE_SHADOW1D: - case TGSI_TEXTURE_SHADOW1D_ARRAY: - snprintf(offbuf, 256, ", ivec2(%d, 0)", - imd->val[inst->TexOffsets[0].SwizzleX].i); - break; - case TGSI_TEXTURE_RECT: - case TGSI_TEXTURE_SHADOWRECT: - case TGSI_TEXTURE_2D: - case TGSI_TEXTURE_2D_ARRAY: - case TGSI_TEXTURE_SHADOW2D: - case TGSI_TEXTURE_SHADOW2D_ARRAY: - snprintf(offbuf, 256, ", ivec2(%d, %d)", - imd->val[inst->TexOffsets[0].SwizzleX].i, - imd->val[inst->TexOffsets[0].SwizzleY].i); - break; - case TGSI_TEXTURE_3D: - snprintf(offbuf, 256, ", ivec3(%d, %d, %d)", - imd->val[inst->TexOffsets[0].SwizzleX].i, - imd->val[inst->TexOffsets[0].SwizzleY].i, - imd->val[inst->TexOffsets[0].SwizzleZ].i); - break; - default: - return false; - } - } else if (inst->TexOffsets[0].File == TGSI_FILE_TEMPORARY) { - struct vrend_temp_range *range = - find_temp_range(ctx, inst->TexOffsets[0].Index); - int idx = inst->TexOffsets[0].Index - range->first; - switch (inst->Texture.Texture) { - case TGSI_TEXTURE_1D: - case TGSI_TEXTURE_1D_ARRAY: - case TGSI_TEXTURE_SHADOW1D: - case TGSI_TEXTURE_SHADOW1D_ARRAY: - snprintf(offbuf, 256, ", int(floatBitsToInt(temp%d[%d].%c))", - range->first, idx, get_swiz_char(inst->TexOffsets[0].SwizzleX)); - break; - case TGSI_TEXTURE_RECT: - case TGSI_TEXTURE_SHADOWRECT: - case TGSI_TEXTURE_2D: - case TGSI_TEXTURE_2D_ARRAY: - case TGSI_TEXTURE_SHADOW2D: - case TGSI_TEXTURE_SHADOW2D_ARRAY: - snprintf(offbuf, 256, - ", ivec2(floatBitsToInt(temp%d[%d].%c), " - "floatBitsToInt(temp%d[%d].%c))", - range->first, idx, get_swiz_char(inst->TexOffsets[0].SwizzleX), - range->first, idx, get_swiz_char(inst->TexOffsets[0].SwizzleY)); - break; - case TGSI_TEXTURE_3D: - snprintf(offbuf, 256, - ", ivec3(floatBitsToInt(temp%d[%d].%c), " - "floatBitsToInt(temp%d[%d].%c), floatBitsToInt(temp%d[%d].%c)", - range->first, idx, get_swiz_char(inst->TexOffsets[0].SwizzleX), - range->first, idx, get_swiz_char(inst->TexOffsets[0].SwizzleY), - range->first, idx, get_swiz_char(inst->TexOffsets[0].SwizzleZ)); - break; - default: - return false; - break; - } - } else if (inst->TexOffsets[0].File == TGSI_FILE_INPUT) { - for (uint32_t j = 0; j < ctx->num_inputs; j++) { - if (ctx->inputs[j].first != inst->TexOffsets[0].Index) - continue; - switch (inst->Texture.Texture) { - case TGSI_TEXTURE_1D: - case TGSI_TEXTURE_1D_ARRAY: - case TGSI_TEXTURE_SHADOW1D: - case TGSI_TEXTURE_SHADOW1D_ARRAY: - snprintf(offbuf, 256, ", int(floatBitsToInt(%s.%c))", - ctx->inputs[j].glsl_name, - get_swiz_char(inst->TexOffsets[0].SwizzleX)); - break; - case TGSI_TEXTURE_RECT: - case TGSI_TEXTURE_SHADOWRECT: - case TGSI_TEXTURE_2D: - case TGSI_TEXTURE_2D_ARRAY: - case TGSI_TEXTURE_SHADOW2D: - case TGSI_TEXTURE_SHADOW2D_ARRAY: - snprintf(offbuf, 256, - ", ivec2(floatBitsToInt(%s.%c), floatBitsToInt(%s.%c))", - ctx->inputs[j].glsl_name, - get_swiz_char(inst->TexOffsets[0].SwizzleX), - ctx->inputs[j].glsl_name, - get_swiz_char(inst->TexOffsets[0].SwizzleY)); - break; - case TGSI_TEXTURE_3D: - snprintf(offbuf, 256, - ", ivec3(floatBitsToInt(%s.%c), floatBitsToInt(%s.%c), " - "floatBitsToInt(%s.%c)", - ctx->inputs[j].glsl_name, - get_swiz_char(inst->TexOffsets[0].SwizzleX), - ctx->inputs[j].glsl_name, - get_swiz_char(inst->TexOffsets[0].SwizzleY), - ctx->inputs[j].glsl_name, - get_swiz_char(inst->TexOffsets[0].SwizzleZ)); - break; - default: - return false; - break; - } - } - } - return true; -} - -static void translate_tex(struct dump_ctx *ctx, - struct tgsi_full_instruction *inst, - struct source_info *sinfo, struct dest_info *dinfo, - const char *srcs[4], const char *dst, - const char *writemask) { - enum vrend_type_qualifier txfi = TYPE_CONVERSION_NONE; - unsigned twm = TGSI_WRITEMASK_NONE, gwm = TGSI_WRITEMASK_NONE; - enum vrend_type_qualifier dtypeprefix = TYPE_CONVERSION_NONE; - bool is_shad; - char offbuf[256] = ""; - char bias[256] = ""; - int sampler_index; - const char *tex_ext; - - set_texture_reqs(ctx, inst, sinfo->sreg_index); - is_shad = samplertype_is_shadow(inst->Texture.Texture); - - switch (ctx->samplers[sinfo->sreg_index].tgsi_sampler_return) { - case TGSI_RETURN_TYPE_SINT: - /* if dstconv isn't an int */ - if (dinfo->dstconv != INT) - dtypeprefix = INT_BITS_TO_FLOAT; - break; - case TGSI_RETURN_TYPE_UINT: - /* if dstconv isn't an int */ - if (dinfo->dstconv != INT) - dtypeprefix = UINT_BITS_TO_FLOAT; - break; - } - - sampler_index = 1; - - if (inst->Instruction.Opcode == TGSI_OPCODE_LODQ) - ctx->shader_req_bits |= SHADER_REQ_LODQ; - - switch (inst->Texture.Texture) { - case TGSI_TEXTURE_1D: - case TGSI_TEXTURE_BUFFER: - if (inst->Instruction.Opcode == TGSI_OPCODE_TXP) - twm = TGSI_WRITEMASK_NONE; - else - twm = TGSI_WRITEMASK_X; - txfi = INT; - break; - case TGSI_TEXTURE_1D_ARRAY: - twm = TGSI_WRITEMASK_XY; - txfi = IVEC2; - break; - case TGSI_TEXTURE_2D: - case TGSI_TEXTURE_RECT: - if (inst->Instruction.Opcode == TGSI_OPCODE_TXP) - twm = TGSI_WRITEMASK_NONE; - else - twm = TGSI_WRITEMASK_XY; - txfi = IVEC2; - break; - case TGSI_TEXTURE_SHADOW1D: - case TGSI_TEXTURE_SHADOW2D: - case TGSI_TEXTURE_SHADOW1D_ARRAY: - case TGSI_TEXTURE_SHADOWRECT: - case TGSI_TEXTURE_3D: - if (inst->Instruction.Opcode == TGSI_OPCODE_TXP) - twm = TGSI_WRITEMASK_NONE; - else if (inst->Instruction.Opcode == TGSI_OPCODE_TG4) - twm = TGSI_WRITEMASK_XY; - else - twm = TGSI_WRITEMASK_XYZ; - txfi = IVEC3; - break; - case TGSI_TEXTURE_CUBE: - case TGSI_TEXTURE_2D_ARRAY: - twm = TGSI_WRITEMASK_XYZ; - txfi = IVEC3; - break; - case TGSI_TEXTURE_2D_MSAA: - twm = TGSI_WRITEMASK_XY; - txfi = IVEC2; - break; - case TGSI_TEXTURE_2D_ARRAY_MSAA: - twm = TGSI_WRITEMASK_XYZ; - txfi = IVEC3; - break; - - case TGSI_TEXTURE_SHADOWCUBE: - case TGSI_TEXTURE_SHADOW2D_ARRAY: - case TGSI_TEXTURE_SHADOWCUBE_ARRAY: - case TGSI_TEXTURE_CUBE_ARRAY: - default: - if (inst->Instruction.Opcode == TGSI_OPCODE_TG4 && - inst->Texture.Texture != TGSI_TEXTURE_CUBE_ARRAY && - inst->Texture.Texture != TGSI_TEXTURE_SHADOWCUBE_ARRAY) - twm = TGSI_WRITEMASK_XYZ; - else - twm = TGSI_WRITEMASK_NONE; - txfi = TYPE_CONVERSION_NONE; - break; - } - - if (inst->Instruction.Opcode == TGSI_OPCODE_TXD) { - switch (inst->Texture.Texture) { - case TGSI_TEXTURE_1D: - case TGSI_TEXTURE_SHADOW1D: - case TGSI_TEXTURE_1D_ARRAY: - case TGSI_TEXTURE_SHADOW1D_ARRAY: - gwm = TGSI_WRITEMASK_X; - break; - case TGSI_TEXTURE_2D: - case TGSI_TEXTURE_SHADOW2D: - case TGSI_TEXTURE_2D_ARRAY: - case TGSI_TEXTURE_SHADOW2D_ARRAY: - case TGSI_TEXTURE_RECT: - case TGSI_TEXTURE_SHADOWRECT: - gwm = TGSI_WRITEMASK_XY; - break; - case TGSI_TEXTURE_3D: - case TGSI_TEXTURE_CUBE: - case TGSI_TEXTURE_SHADOWCUBE: - case TGSI_TEXTURE_CUBE_ARRAY: - gwm = TGSI_WRITEMASK_XYZ; - break; - default: - gwm = TGSI_WRITEMASK_NONE; - break; - } - } - - switch (inst->Instruction.Opcode) { - case TGSI_OPCODE_TXB2: - case TGSI_OPCODE_TXL2: - case TGSI_OPCODE_TEX2: - sampler_index = 2; - if (inst->Instruction.Opcode != TGSI_OPCODE_TEX2) - snprintf(bias, 64, ", %s.x", srcs[1]); - else if (inst->Texture.Texture == TGSI_TEXTURE_SHADOWCUBE_ARRAY) - snprintf(bias, 64, ", float(%s)", srcs[1]); - break; - case TGSI_OPCODE_TXB: - case TGSI_OPCODE_TXL: - snprintf(bias, 64, ", %s.w", srcs[0]); - break; - case TGSI_OPCODE_TXF: - if (inst->Texture.Texture == TGSI_TEXTURE_1D || - inst->Texture.Texture == TGSI_TEXTURE_2D || - inst->Texture.Texture == TGSI_TEXTURE_2D_MSAA || - inst->Texture.Texture == TGSI_TEXTURE_2D_ARRAY_MSAA || - inst->Texture.Texture == TGSI_TEXTURE_3D || - inst->Texture.Texture == TGSI_TEXTURE_1D_ARRAY || - inst->Texture.Texture == TGSI_TEXTURE_2D_ARRAY) - snprintf(bias, 64, ", int(%s.w)", srcs[0]); - break; - case TGSI_OPCODE_TXD: - if ((inst->Texture.Texture == TGSI_TEXTURE_1D || - inst->Texture.Texture == TGSI_TEXTURE_SHADOW1D || - inst->Texture.Texture == TGSI_TEXTURE_1D_ARRAY || - inst->Texture.Texture == TGSI_TEXTURE_SHADOW1D_ARRAY)) - snprintf(bias, 128, ", vec2(%s%s, 0), vec2(%s%s, 0)", srcs[1], - get_wm_string(gwm), srcs[2], get_wm_string(gwm)); - else - snprintf(bias, 128, ", %s%s, %s%s", srcs[1], get_wm_string(gwm), srcs[2], - get_wm_string(gwm)); - sampler_index = 3; - break; - case TGSI_OPCODE_TG4: - sampler_index = 2; - ctx->shader_req_bits |= SHADER_REQ_TG4; - if (inst->Texture.NumOffsets == 1) { - if (inst->TexOffsets[0].File != TGSI_FILE_IMMEDIATE) - ctx->shader_req_bits |= SHADER_REQ_GPU_SHADER5; - } - if (is_shad) { - if (inst->Texture.Texture == TGSI_TEXTURE_SHADOWCUBE || - inst->Texture.Texture == TGSI_TEXTURE_SHADOW2D_ARRAY) - snprintf(bias, 64, ", %s.w", srcs[0]); - else if (inst->Texture.Texture == TGSI_TEXTURE_SHADOWCUBE_ARRAY) - snprintf(bias, 64, ", %s.x", srcs[1]); - else - snprintf(bias, 64, ", %s.z", srcs[0]); - } else if (sinfo->tg4_has_component) { - if (inst->Texture.NumOffsets == 0) { - if (inst->Texture.Texture == TGSI_TEXTURE_2D || - inst->Texture.Texture == TGSI_TEXTURE_RECT || - inst->Texture.Texture == TGSI_TEXTURE_CUBE || - inst->Texture.Texture == TGSI_TEXTURE_2D_ARRAY || - inst->Texture.Texture == TGSI_TEXTURE_CUBE_ARRAY) - snprintf(bias, 64, ", int(%s)", srcs[1]); - } else if (inst->Texture.NumOffsets) { - if (inst->Texture.Texture == TGSI_TEXTURE_2D || - inst->Texture.Texture == TGSI_TEXTURE_RECT || - inst->Texture.Texture == TGSI_TEXTURE_2D_ARRAY) - snprintf(bias, 64, ", int(%s)", srcs[1]); - } - } - break; - default: - bias[0] = 0; - } - - tex_ext = get_tex_inst_ext(inst); - - if (inst->Texture.NumOffsets == 1) { - if (inst->TexOffsets[0].Index >= (int)ARRAY_SIZE(ctx->imm)) { - set_buf_error(ctx); - return; - } - - if (!fill_offset_buffer(ctx, inst, offbuf)) { - set_buf_error(ctx); - return; - } - - if (inst->Instruction.Opcode == TGSI_OPCODE_TXL || - inst->Instruction.Opcode == TGSI_OPCODE_TXL2 || - inst->Instruction.Opcode == TGSI_OPCODE_TXD || - (inst->Instruction.Opcode == TGSI_OPCODE_TG4 && is_shad)) { - char tmp[256]; - strcpy(tmp, offbuf); - strcpy(offbuf, bias); - strcpy(bias, tmp); - } - } - - /* On GLES we have to normalized the coordinate for all but the texel fetch - * instruction */ - if (inst->Instruction.Opcode != TGSI_OPCODE_TXF && - (inst->Texture.Texture == TGSI_TEXTURE_RECT || - inst->Texture.Texture == TGSI_TEXTURE_SHADOWRECT)) { - - char buf[255]; - const char *new_srcs[4] = {buf, srcs[1], srcs[2], srcs[3]}; - - switch (inst->Instruction.Opcode) { - case TGSI_OPCODE_TXP: - snprintf(buf, 255, "vec4(%s)/vec4(textureSize(%s, 0), 1, 1)", srcs[0], - srcs[sampler_index]); - break; - - case TGSI_OPCODE_TG4: - snprintf(buf, 255, "%s.xy/vec2(textureSize(%s, 0))", srcs[0], - srcs[sampler_index]); - break; - - default: - /* Non TG4 ops have the compare value in the z components */ - if (inst->Texture.Texture == TGSI_TEXTURE_SHADOWRECT) { - snprintf(buf, 255, "vec3(%s.xy/vec2(textureSize(%s, 0)), %s.z)", - srcs[0], srcs[sampler_index], srcs[0]); - } else - snprintf(buf, 255, "%s.xy/vec2(textureSize(%s, 0))", srcs[0], - srcs[sampler_index]); - } - srcs = new_srcs; - } - - if (inst->Instruction.Opcode == TGSI_OPCODE_TXF) { - if ((inst->Texture.Texture == TGSI_TEXTURE_1D || - inst->Texture.Texture == TGSI_TEXTURE_1D_ARRAY || - inst->Texture.Texture == TGSI_TEXTURE_RECT)) { - if (inst->Texture.Texture == TGSI_TEXTURE_1D) - emit_buff( - ctx, "%s = %s(%s(texelFetch%s(%s, ivec2(%s(%s%s), 0)%s%s)%s));\n", - dst, get_string(dinfo->dstconv), get_string(dtypeprefix), tex_ext, - srcs[sampler_index], get_string(txfi), srcs[0], get_wm_string(twm), - bias, offbuf, dinfo->dst_override_no_wm[0] ? "" : writemask); - else if (inst->Texture.Texture == TGSI_TEXTURE_1D_ARRAY) { - /* the y coordinate must go into the z element and the y must be zero */ - emit_buff( - ctx, - "%s = %s(%s(texelFetch%s(%s, ivec3(%s(%s%s), 0).xzy%s%s)%s));\n", - dst, get_string(dinfo->dstconv), get_string(dtypeprefix), tex_ext, - srcs[sampler_index], get_string(txfi), srcs[0], get_wm_string(twm), - bias, offbuf, dinfo->dst_override_no_wm[0] ? "" : writemask); - } else { - emit_buff(ctx, "%s = %s(%s(texelFetch%s(%s, %s(%s%s), 0%s)%s));\n", dst, - get_string(dinfo->dstconv), get_string(dtypeprefix), tex_ext, - srcs[sampler_index], get_string(txfi), srcs[0], - get_wm_string(twm), offbuf, - dinfo->dst_override_no_wm[0] ? "" : writemask); - } - } else { - emit_buff(ctx, "%s = %s(%s(texelFetch%s(%s, %s(%s%s)%s%s)%s));\n", dst, - get_string(dinfo->dstconv), get_string(dtypeprefix), tex_ext, - srcs[sampler_index], get_string(txfi), srcs[0], - get_wm_string(twm), bias, offbuf, - dinfo->dst_override_no_wm[0] ? "" : writemask); - } - } else if (ctx->cfg->glsl_version < 140 && - (ctx->shader_req_bits & SHADER_REQ_SAMPLER_RECT)) { - /* rect is special in GLSL 1.30 */ - if (inst->Texture.Texture == TGSI_TEXTURE_RECT) - emit_buff(ctx, "%s = texture2DRect(%s, %s.xy)%s;\n", dst, - srcs[sampler_index], srcs[0], writemask); - else if (inst->Texture.Texture == TGSI_TEXTURE_SHADOWRECT) - emit_buff(ctx, "%s = shadow2DRect(%s, %s.xyz)%s;\n", dst, - srcs[sampler_index], srcs[0], writemask); - } else if (is_shad && inst->Instruction.Opcode != - TGSI_OPCODE_TG4) { /* TGSI returns 1.0 in alpha */ - const char *cname = tgsi_proc_to_prefix(ctx->prog_type); - const struct tgsi_full_src_register *src = &inst->Src[sampler_index]; - - if ((inst->Texture.Texture == TGSI_TEXTURE_SHADOW1D || - inst->Texture.Texture == TGSI_TEXTURE_SHADOW1D_ARRAY)) { - if (inst->Texture.Texture == TGSI_TEXTURE_SHADOW1D) { - if (inst->Instruction.Opcode == TGSI_OPCODE_TXP) - emit_buff(ctx, - "%s = %s(%s(vec4(vec4(texture%s(%s, vec4(%s%s.xzw, 0).xwyz " - "%s%s)) * %sshadmask%d + %sshadadd%d)%s));\n", - dst, get_string(dinfo->dstconv), get_string(dtypeprefix), - tex_ext, srcs[sampler_index], srcs[0], get_wm_string(twm), - offbuf, bias, cname, src->Register.Index, cname, - src->Register.Index, writemask); - else - emit_buff(ctx, - "%s = %s(%s(vec4(vec4(texture%s(%s, vec3(%s%s.xz, 0).xzy " - "%s%s)) * %sshadmask%d + %sshadadd%d)%s));\n", - dst, get_string(dinfo->dstconv), get_string(dtypeprefix), - tex_ext, srcs[sampler_index], srcs[0], get_wm_string(twm), - offbuf, bias, cname, src->Register.Index, cname, - src->Register.Index, writemask); - } else if (inst->Texture.Texture == TGSI_TEXTURE_SHADOW1D_ARRAY) { - emit_buff(ctx, - "%s = %s(%s(vec4(vec4(texture%s(%s, vec4(%s%s, 0).xwyz " - "%s%s)) * %sshadmask%d + %sshadadd%d)%s));\n", - dst, get_string(dinfo->dstconv), get_string(dtypeprefix), - tex_ext, srcs[sampler_index], srcs[0], get_wm_string(twm), - offbuf, bias, cname, src->Register.Index, cname, - src->Register.Index, writemask); - } - } else - emit_buff(ctx, - "%s = %s(%s(vec4(vec4(texture%s(%s, %s%s%s%s)) * %sshadmask%d " - "+ %sshadadd%d)%s));\n", - dst, get_string(dinfo->dstconv), get_string(dtypeprefix), - tex_ext, srcs[sampler_index], srcs[0], get_wm_string(twm), - offbuf, bias, cname, src->Register.Index, cname, - src->Register.Index, writemask); - } else { - /* OpenGL ES do not support 1D texture - * so we use a 2D texture with a parameter set to 0.5 - */ - if ((inst->Texture.Texture == TGSI_TEXTURE_1D || - inst->Texture.Texture == TGSI_TEXTURE_1D_ARRAY)) { - if (inst->Texture.Texture == TGSI_TEXTURE_1D) { - if (inst->Instruction.Opcode == TGSI_OPCODE_TXP) - emit_buff(ctx, - "%s = %s(%s(texture%s(%s, vec3(%s.xw, 0).xzy %s%s)%s));\n", - dst, get_string(dinfo->dstconv), get_string(dtypeprefix), - tex_ext, srcs[sampler_index], srcs[0], offbuf, bias, - dinfo->dst_override_no_wm[0] ? "" : writemask); - else - emit_buff( - ctx, "%s = %s(%s(texture%s(%s, vec2(%s%s, 0.5) %s%s)%s));\n", dst, - get_string(dinfo->dstconv), get_string(dtypeprefix), tex_ext, - srcs[sampler_index], srcs[0], get_wm_string(twm), offbuf, bias, - dinfo->dst_override_no_wm[0] ? "" : writemask); - } else if (inst->Texture.Texture == TGSI_TEXTURE_1D_ARRAY) { - if (inst->Instruction.Opcode == TGSI_OPCODE_TXP) - emit_buff(ctx, - "%s = %s(%s(texture%s(%s, vec3(%s.x / %s.w, 0, %s.y) " - "%s%s)%s));\n", - dst, get_string(dinfo->dstconv), get_string(dtypeprefix), - tex_ext, srcs[sampler_index], srcs[0], srcs[0], srcs[0], - offbuf, bias, - dinfo->dst_override_no_wm[0] ? "" : writemask); - else - emit_buff( - ctx, "%s = %s(%s(texture%s(%s, vec3(%s%s, 0).xzy %s%s)%s));\n", - dst, get_string(dinfo->dstconv), get_string(dtypeprefix), tex_ext, - srcs[sampler_index], srcs[0], get_wm_string(twm), offbuf, bias, - dinfo->dst_override_no_wm[0] ? "" : writemask); - } - } else { - emit_buff(ctx, "%s = %s(%s(texture%s(%s, %s%s%s%s)%s));\n", dst, - get_string(dinfo->dstconv), get_string(dtypeprefix), tex_ext, - srcs[sampler_index], srcs[0], get_wm_string(twm), offbuf, bias, - dinfo->dst_override_no_wm[0] ? "" : writemask); - } - } -} - -static void create_swizzled_clipdist(struct dump_ctx *ctx, - struct vrend_strbuf *result, - const struct tgsi_full_src_register *src, - int input_idx, bool gl_in, - const char *stypeprefix, - const char *prefix, const char *arrayname, - int offset) { - char clipdistvec[4][64] = { - 0, - }; - - char clip_indirect[32] = ""; - - bool has_prev_vals = (ctx->key->prev_stage_num_cull_out + - ctx->key->prev_stage_num_clip_out) > 0; - int num_culls = has_prev_vals ? ctx->key->prev_stage_num_cull_out : 0; - int num_clips = - has_prev_vals ? ctx->key->prev_stage_num_clip_out : ctx->num_in_clip_dist; - int base_idx = ctx->inputs[input_idx].sid * 4; - - /* With arrays enabled , but only when gl_ClipDistance or gl_CullDistance are - * emitted (>4) then we need to add indirect addressing */ - if (src->Register.Indirect && - ((num_clips > 4 && base_idx < num_clips) || num_culls > 4)) - snprintf(clip_indirect, 32, "4*addr%d +", src->Indirect.Index); - else if (src->Register.Index != offset) - snprintf(clip_indirect, 32, "4*%d +", src->Register.Index - offset); - - for (unsigned cc = 0; cc < 4; cc++) { - const char *cc_name = ctx->inputs[input_idx].glsl_name; - int idx = base_idx; - if (cc == 0) - idx += src->Register.SwizzleX; - else if (cc == 1) - idx += src->Register.SwizzleY; - else if (cc == 2) - idx += src->Register.SwizzleZ; - else if (cc == 3) - idx += src->Register.SwizzleW; - - if (num_culls) { - if (idx >= num_clips) { - idx -= num_clips; - cc_name = "gl_CullDistance"; - } - if (ctx->key->prev_stage_num_cull_out) - if (idx >= ctx->key->prev_stage_num_cull_out) - idx = 0; - } else { - if (ctx->key->prev_stage_num_clip_out) - if (idx >= ctx->key->prev_stage_num_clip_out) - idx = 0; - } - if (gl_in) - snprintf(clipdistvec[cc], 64, "%sgl_in%s.%s[%s %d]", prefix, arrayname, - cc_name, clip_indirect, idx); - else - snprintf(clipdistvec[cc], 64, "%s%s%s[%s %d]", prefix, arrayname, cc_name, - clip_indirect, idx); - } - strbuf_fmt(result, "%s(vec4(%s,%s,%s,%s))", stypeprefix, clipdistvec[0], - clipdistvec[1], clipdistvec[2], clipdistvec[3]); -} - -static void load_clipdist_fs(struct dump_ctx *ctx, struct vrend_strbuf *result, - const struct tgsi_full_src_register *src, - int input_idx, bool gl_in, const char *stypeprefix, - int offset) { - char clip_indirect[32] = ""; - - int base_idx = ctx->inputs[input_idx].sid; - - /* With arrays enabled , but only when gl_ClipDistance or gl_CullDistance are - * emitted (>4) then we need to add indirect addressing */ - if (src->Register.Indirect) - snprintf(clip_indirect, 32, "addr%d + %d", src->Indirect.Index, base_idx); - else - snprintf(clip_indirect, 32, "%d + %d", src->Register.Index - offset, - base_idx); - - if (gl_in) - strbuf_fmt(result, "%s(clip_dist_temp[%s])", stypeprefix, clip_indirect); - else - strbuf_fmt(result, "%s(clip_dist_temp[%s])", stypeprefix, clip_indirect); -} - -static enum vrend_type_qualifier get_coord_prefix(int resource, bool *is_ms) { - switch (resource) { - case TGSI_TEXTURE_1D: - return IVEC2; - case TGSI_TEXTURE_BUFFER: - return INT; - case TGSI_TEXTURE_1D_ARRAY: - return IVEC3; - case TGSI_TEXTURE_2D: - case TGSI_TEXTURE_RECT: - return IVEC2; - case TGSI_TEXTURE_3D: - case TGSI_TEXTURE_CUBE: - case TGSI_TEXTURE_2D_ARRAY: - case TGSI_TEXTURE_CUBE_ARRAY: - return IVEC3; - case TGSI_TEXTURE_2D_MSAA: - *is_ms = true; - return IVEC2; - case TGSI_TEXTURE_2D_ARRAY_MSAA: - *is_ms = true; - return IVEC3; - default: - return TYPE_CONVERSION_NONE; - } -} - -static bool is_integer_memory(struct dump_ctx *ctx, - enum tgsi_file_type file_type, uint32_t index) { - switch (file_type) { - case TGSI_FILE_BUFFER: - return !!(ctx->ssbo_integer_mask & (1 << index)); - case TGSI_FILE_MEMORY: - return ctx->integer_memory; - default: - return false; - } -} - -static void set_memory_qualifier(struct dump_ctx *ctx, - struct tgsi_full_instruction *inst, - uint32_t reg_index, bool indirect) { - if (inst->Memory.Qualifier == TGSI_MEMORY_COHERENT) { - if (indirect) { - uint32_t mask = ctx->ssbo_used_mask; - while (mask) - ctx->ssbo_memory_qualifier[u_bit_scan(&mask)] = TGSI_MEMORY_COHERENT; - } else - ctx->ssbo_memory_qualifier[reg_index] = TGSI_MEMORY_COHERENT; - } -} - -static void emit_store_mem(struct dump_ctx *ctx, const char *dst, int writemask, - const char *srcs[4], const char *conversion) { - static const char swizzle_char[] = "xyzw"; - for (int i = 0; i < 4; ++i) { - if (writemask & (1 << i)) { - emit_buff(ctx, - "%s[(uint(floatBitsToUint(%s)) >> 2) + %du] = %s(%s).%c;\n", - dst, srcs[0], i, conversion, srcs[1], swizzle_char[i]); - } - } -} - -static void translate_store(struct dump_ctx *ctx, - struct tgsi_full_instruction *inst, - struct source_info *sinfo, const char *srcs[4], - const char *dst) { - const struct tgsi_full_dst_register *dst_reg = &inst->Dst[0]; - - if (dst_reg->Register.File == TGSI_FILE_IMAGE) { - bool is_ms = false; - enum vrend_type_qualifier coord_prefix = get_coord_prefix( - ctx->images[dst_reg->Register.Index].decl.Resource, &is_ms); - enum tgsi_return_type itype; - char ms_str[32] = ""; - enum vrend_type_qualifier stypeprefix = TYPE_CONVERSION_NONE; - const char *conversion = - sinfo->override_no_cast[0] ? "" : get_string(FLOAT_BITS_TO_INT); - get_internalformat_string(inst->Memory.Format, &itype); - if (is_ms) { - snprintf(ms_str, 32, "int(%s.w),", srcs[0]); - } - switch (itype) { - case TGSI_RETURN_TYPE_UINT: - stypeprefix = FLOAT_BITS_TO_UINT; - break; - case TGSI_RETURN_TYPE_SINT: - stypeprefix = FLOAT_BITS_TO_INT; - break; - default: - break; - } - if (!dst_reg->Register.Indirect) { - emit_buff(ctx, "imageStore(%s,%s(%s(%s)),%s%s(%s));\n", dst, - get_string(coord_prefix), conversion, srcs[0], ms_str, - get_string(stypeprefix), srcs[1]); - } else { - struct vrend_array *image = - lookup_image_array_ptr(ctx, dst_reg->Register.Index); - if (image) { - int basearrayidx = image->first; - int array_size = image->array_size; - emit_buff(ctx, "switch (addr%d + %d) {\n", dst_reg->Indirect.Index, - dst_reg->Register.Index - basearrayidx); - const char *cname = tgsi_proc_to_prefix(ctx->prog_type); - - for (int i = 0; i < array_size; ++i) { - emit_buff( - ctx, - "case %d: imageStore(%simg%d[%d],%s(%s(%s)),%s%s(%s)); break;\n", - i, cname, basearrayidx, i, get_string(coord_prefix), conversion, - srcs[0], ms_str, get_string(stypeprefix), srcs[1]); - } - emit_buff(ctx, "}\n"); - } - } - } else if (dst_reg->Register.File == TGSI_FILE_BUFFER || - dst_reg->Register.File == TGSI_FILE_MEMORY) { - enum vrend_type_qualifier dtypeprefix; - set_memory_qualifier(ctx, inst, dst_reg->Register.Index, - dst_reg->Register.Indirect); - dtypeprefix = - is_integer_memory(ctx, dst_reg->Register.File, dst_reg->Register.Index) - ? FLOAT_BITS_TO_INT - : FLOAT_BITS_TO_UINT; - const char *conversion = - sinfo->override_no_cast[1] ? "" : get_string(dtypeprefix); - - if (!dst_reg->Register.Indirect) { - emit_store_mem(ctx, dst, dst_reg->Register.WriteMask, srcs, conversion); - } else { - const char *cname = tgsi_proc_to_prefix(ctx->prog_type); - bool atomic_ssbo = ctx->ssbo_atomic_mask & (1 << dst_reg->Register.Index); - int base = - atomic_ssbo ? ctx->ssbo_atomic_array_base : ctx->ssbo_array_base; - uint32_t mask = ctx->ssbo_used_mask; - int start, array_count; - u_bit_scan_consecutive_range(&mask, &start, &array_count); - int basearrayidx = lookup_image_array(ctx, dst_reg->Register.Index); - emit_buff(ctx, "switch (addr%d + %d) {\n", dst_reg->Indirect.Index, - dst_reg->Register.Index - base); - - for (int i = 0; i < array_count; ++i) { - char dst_tmp[128]; - emit_buff(ctx, "case %d:\n", i); - snprintf(dst_tmp, 128, "%simg%d[%d]", cname, basearrayidx, i); - emit_store_mem(ctx, dst_tmp, dst_reg->Register.WriteMask, srcs, - conversion); - emit_buff(ctx, "break;\n"); - } - emit_buf(ctx, "}\n"); - } - } -} - -static void emit_load_mem(struct dump_ctx *ctx, const char *dst, int writemask, - const char *conversion, const char *atomic_op, - const char *src0, const char *atomic_src) { - static const char swizzle_char[] = "xyzw"; - for (int i = 0; i < 4; ++i) { - if (writemask & (1 << i)) { - emit_buff(ctx, "%s.%c = (%s(%s(%s[ssbo_addr_temp + %du]%s)));\n", dst, - swizzle_char[i], conversion, atomic_op, src0, i, atomic_src); - } - } -} - -static void translate_load(struct dump_ctx *ctx, - struct tgsi_full_instruction *inst, - struct source_info *sinfo, struct dest_info *dinfo, - const char *srcs[4], const char *dst, - const char *writemask) { - const struct tgsi_full_src_register *src = &inst->Src[0]; - if (src->Register.File == TGSI_FILE_IMAGE) { - bool is_ms = false; - enum vrend_type_qualifier coord_prefix = - get_coord_prefix(ctx->images[sinfo->sreg_index].decl.Resource, &is_ms); - enum vrend_type_qualifier dtypeprefix = TYPE_CONVERSION_NONE; - const char *conversion = - sinfo->override_no_cast[1] ? "" : get_string(FLOAT_BITS_TO_INT); - enum tgsi_return_type itype; - get_internalformat_string(ctx->images[sinfo->sreg_index].decl.Format, - &itype); - char ms_str[32] = ""; - const char *wm = dinfo->dst_override_no_wm[0] ? "" : writemask; - if (is_ms) { - snprintf(ms_str, 32, ", int(%s.w)", srcs[1]); - } - switch (itype) { - case TGSI_RETURN_TYPE_UINT: - dtypeprefix = UINT_BITS_TO_FLOAT; - break; - case TGSI_RETURN_TYPE_SINT: - dtypeprefix = INT_BITS_TO_FLOAT; - break; - default: - break; - } - - /* On GL WR translates to writable, but on GLES we translate this to - * writeonly because for most formats one has to specify one or the other, - * so if we have an image with the TGSI WR specification, and read from it, - * we drop the Writable flag. For the images that allow RW this is of no - * consequence, and for the others a write access will fail instead of the - * read access, but this doesn't constitue a regression because we couldn't - * do both, read and write, anyway. */ - if (ctx->images[sinfo->sreg_index].decl.Writable && - (ctx->images[sinfo->sreg_index].decl.Format != PIPE_FORMAT_R32_FLOAT) && - (ctx->images[sinfo->sreg_index].decl.Format != PIPE_FORMAT_R32_SINT) && - (ctx->images[sinfo->sreg_index].decl.Format != PIPE_FORMAT_R32_UINT)) - ctx->images[sinfo->sreg_index].decl.Writable = 0; - - if (!inst->Src[0].Register.Indirect) { - emit_buff(ctx, "%s = %s(imageLoad(%s, %s(%s(%s))%s)%s);\n", dst, - get_string(dtypeprefix), srcs[0], get_string(coord_prefix), - conversion, srcs[1], ms_str, wm); - } else { - char src[32] = ""; - struct vrend_array *image = - lookup_image_array_ptr(ctx, inst->Src[0].Register.Index); - if (image) { - int basearrayidx = image->first; - int array_size = image->array_size; - emit_buff(ctx, "switch (addr%d + %d) {\n", inst->Src[0].Indirect.Index, - inst->Src[0].Register.Index - basearrayidx); - const char *cname = tgsi_proc_to_prefix(ctx->prog_type); - - for (int i = 0; i < array_size; ++i) { - snprintf(src, 32, "%simg%d[%d]", cname, basearrayidx, i); - emit_buff(ctx, - "case %d: %s = %s(imageLoad(%s, %s(%s(%s))%s)%s);break;\n", - i, dst, get_string(dtypeprefix), src, - get_string(coord_prefix), conversion, srcs[1], ms_str, wm); - } - emit_buff(ctx, "}\n"); - } - } - } else if (src->Register.File == TGSI_FILE_BUFFER || - src->Register.File == TGSI_FILE_MEMORY) { - char mydst[255], atomic_op[9], atomic_src[10]; - enum vrend_type_qualifier dtypeprefix; - - set_memory_qualifier(ctx, inst, inst->Src[0].Register.Index, - inst->Src[0].Register.Indirect); - - strcpy(mydst, dst); - char *wmp = strchr(mydst, '.'); - - if (wmp) - wmp[0] = 0; - emit_buff(ctx, "ssbo_addr_temp = uint(floatBitsToUint(%s)) >> 2;\n", - srcs[1]); - - atomic_op[0] = atomic_src[0] = '\0'; - if (ctx->ssbo_atomic_mask & (1 << src->Register.Index)) { - /* Emulate atomicCounter with atomicOr. */ - strcpy(atomic_op, "atomicOr"); - strcpy(atomic_src, ", uint(0)"); - } - - dtypeprefix = - (is_integer_memory(ctx, src->Register.File, src->Register.Index)) - ? INT_BITS_TO_FLOAT - : UINT_BITS_TO_FLOAT; - - if (!inst->Src[0].Register.Indirect) { - emit_load_mem(ctx, mydst, inst->Dst[0].Register.WriteMask, - get_string(dtypeprefix), atomic_op, srcs[0], atomic_src); - } else { - char src[128] = ""; - const char *cname = tgsi_proc_to_prefix(ctx->prog_type); - bool atomic_ssbo = - ctx->ssbo_atomic_mask & (1 << inst->Src[0].Register.Index); - const char *atomic_str = atomic_ssbo ? "atomic" : ""; - uint base = - atomic_ssbo ? ctx->ssbo_atomic_array_base : ctx->ssbo_array_base; - int start, array_count; - uint32_t mask = ctx->ssbo_used_mask; - u_bit_scan_consecutive_range(&mask, &start, &array_count); - - emit_buff(ctx, "switch (addr%d + %d) {\n", inst->Src[0].Indirect.Index, - inst->Src[0].Register.Index - base); - for (int i = 0; i < array_count; ++i) { - emit_buff(ctx, "case %d:\n", i); - snprintf(src, 128, "%sssboarr%s[%d].%sssbocontents%d", cname, - atomic_str, i, cname, base); - emit_load_mem(ctx, mydst, inst->Dst[0].Register.WriteMask, - get_string(dtypeprefix), atomic_op, src, atomic_src); - emit_buff(ctx, " break;\n"); - } - emit_buf(ctx, "}\n"); - } - } else if (src->Register.File == TGSI_FILE_HW_ATOMIC) { - emit_buff(ctx, "%s = uintBitsToFloat(atomicCounter(%s));\n", dst, srcs[0]); - } -} - -static const char *get_atomic_opname(int tgsi_opcode, bool *is_cas) { - const char *opname; - *is_cas = false; - switch (tgsi_opcode) { - case TGSI_OPCODE_ATOMUADD: - opname = "Add"; - break; - case TGSI_OPCODE_ATOMXCHG: - opname = "Exchange"; - break; - case TGSI_OPCODE_ATOMCAS: - opname = "CompSwap"; - *is_cas = true; - break; - case TGSI_OPCODE_ATOMAND: - opname = "And"; - break; - case TGSI_OPCODE_ATOMOR: - opname = "Or"; - break; - case TGSI_OPCODE_ATOMXOR: - opname = "Xor"; - break; - case TGSI_OPCODE_ATOMUMIN: - opname = "Min"; - break; - case TGSI_OPCODE_ATOMUMAX: - opname = "Max"; - break; - case TGSI_OPCODE_ATOMIMIN: - opname = "Min"; - break; - case TGSI_OPCODE_ATOMIMAX: - opname = "Max"; - break; - default: - return NULL; - } - return opname; -} - -static void translate_resq(struct dump_ctx *ctx, - struct tgsi_full_instruction *inst, - const char *srcs[4], const char *dst, - const char *writemask) { - const struct tgsi_full_src_register *src = &inst->Src[0]; - - if (src->Register.File == TGSI_FILE_IMAGE) { - if (inst->Dst[0].Register.WriteMask & 0x8) { - ctx->shader_req_bits |= SHADER_REQ_TXQS | SHADER_REQ_INTS; - emit_buff(ctx, "%s = %s(imageSamples(%s));\n", dst, - get_string(INT_BITS_TO_FLOAT), srcs[0]); - } - if (inst->Dst[0].Register.WriteMask & 0x7) { - const char *swizzle_mask = - inst->Memory.Texture == TGSI_TEXTURE_1D_ARRAY ? ".xz" : ""; - ctx->shader_req_bits |= SHADER_REQ_IMAGE_SIZE | SHADER_REQ_INTS; - bool skip_emit_writemask = inst->Memory.Texture == TGSI_TEXTURE_BUFFER; - - emit_buff(ctx, "%s = %s(imageSize(%s)%s%s);\n", dst, - get_string(INT_BITS_TO_FLOAT), srcs[0], swizzle_mask, - skip_emit_writemask ? "" : writemask); - } - } else if (src->Register.File == TGSI_FILE_BUFFER) { - emit_buff(ctx, "%s = %s(int(%s.length()) << 2);\n", dst, - get_string(INT_BITS_TO_FLOAT), srcs[0]); - } -} - -static void translate_atomic(struct dump_ctx *ctx, - struct tgsi_full_instruction *inst, - struct source_info *sinfo, const char *srcs[4], - char *dst) { - const struct tgsi_full_src_register *src = &inst->Src[0]; - const char *opname; - enum vrend_type_qualifier stypeprefix = TYPE_CONVERSION_NONE; - enum vrend_type_qualifier dtypeprefix = TYPE_CONVERSION_NONE; - enum vrend_type_qualifier stypecast = TYPE_CONVERSION_NONE; - bool is_cas; - char cas_str[128] = ""; - - if (src->Register.File == TGSI_FILE_IMAGE) { - enum tgsi_return_type itype; - get_internalformat_string(ctx->images[sinfo->sreg_index].decl.Format, - &itype); - switch (itype) { - default: - case TGSI_RETURN_TYPE_UINT: - stypeprefix = FLOAT_BITS_TO_UINT; - dtypeprefix = UINT_BITS_TO_FLOAT; - stypecast = UINT; - break; - case TGSI_RETURN_TYPE_SINT: - stypeprefix = FLOAT_BITS_TO_INT; - dtypeprefix = INT_BITS_TO_FLOAT; - stypecast = INT; - break; - case TGSI_RETURN_TYPE_FLOAT: - if (ctx->cfg->has_es31_compat) - ctx->shader_req_bits |= SHADER_REQ_ES31_COMPAT; - else - ctx->shader_req_bits |= SHADER_REQ_SHADER_ATOMIC_FLOAT; - stypecast = FLOAT; - break; - } - } else { - stypeprefix = FLOAT_BITS_TO_UINT; - dtypeprefix = UINT_BITS_TO_FLOAT; - stypecast = UINT; - } - - opname = get_atomic_opname(inst->Instruction.Opcode, &is_cas); - if (!opname) { - set_buf_error(ctx); - return; - } - - if (is_cas) - snprintf(cas_str, 128, ", %s(%s(%s))", get_string(stypecast), - get_string(stypeprefix), srcs[3]); - - if (src->Register.File == TGSI_FILE_IMAGE) { - bool is_ms = false; - enum vrend_type_qualifier coord_prefix = - get_coord_prefix(ctx->images[sinfo->sreg_index].decl.Resource, &is_ms); - const char *conversion = - sinfo->override_no_cast[1] ? "" : get_string(FLOAT_BITS_TO_INT); - char ms_str[32] = ""; - if (is_ms) { - snprintf(ms_str, 32, ", int(%s.w)", srcs[1]); - } - - if (!inst->Src[0].Register.Indirect) { - emit_buff( - ctx, "%s = %s(imageAtomic%s(%s, %s(%s(%s))%s, %s(%s(%s))%s));\n", dst, - get_string(dtypeprefix), opname, srcs[0], get_string(coord_prefix), - conversion, srcs[1], ms_str, get_string(stypecast), - get_string(stypeprefix), srcs[2], cas_str); - } else { - char src[32] = ""; - struct vrend_array *image = - lookup_image_array_ptr(ctx, inst->Src[0].Register.Index); - if (image) { - int basearrayidx = image->first; - int array_size = image->array_size; - emit_buff(ctx, "switch (addr%d + %d) {\n", inst->Src[0].Indirect.Index, - inst->Src[0].Register.Index - basearrayidx); - const char *cname = tgsi_proc_to_prefix(ctx->prog_type); - - for (int i = 0; i < array_size; ++i) { - snprintf(src, 32, "%simg%d[%d]", cname, basearrayidx, i); - emit_buff(ctx, - "case %d: %s = %s(imageAtomic%s(%s, %s(%s(%s))%s, " - "%s(%s(%s))%s));\n", - i, dst, get_string(dtypeprefix), opname, src, - get_string(coord_prefix), conversion, srcs[1], ms_str, - get_string(stypecast), get_string(stypeprefix), srcs[2], - cas_str); - } - emit_buff(ctx, "}\n"); - } - } - ctx->shader_req_bits |= SHADER_REQ_IMAGE_ATOMIC; - } - if (src->Register.File == TGSI_FILE_BUFFER || - src->Register.File == TGSI_FILE_MEMORY) { - enum vrend_type_qualifier type; - if ((is_integer_memory(ctx, src->Register.File, src->Register.Index))) { - type = INT; - dtypeprefix = INT_BITS_TO_FLOAT; - stypeprefix = FLOAT_BITS_TO_INT; - } else { - type = UINT; - dtypeprefix = UINT_BITS_TO_FLOAT; - stypeprefix = FLOAT_BITS_TO_UINT; - } - - emit_buff(ctx, - "%s = %s(atomic%s(%s[int(floatBitsToInt(%s)) >> 2], " - "%s(%s(%s).x)%s));\n", - dst, get_string(dtypeprefix), opname, srcs[0], srcs[1], - get_string(type), get_string(stypeprefix), srcs[2], cas_str); - } - if (src->Register.File == TGSI_FILE_HW_ATOMIC) { - if (sinfo->imm_value == -1) - emit_buff(ctx, "%s = %s(atomicCounterDecrement(%s) + 1u);\n", dst, - get_string(dtypeprefix), srcs[0]); - else if (sinfo->imm_value == 1) - emit_buff(ctx, "%s = %s(atomicCounterIncrement(%s));\n", dst, - get_string(dtypeprefix), srcs[0]); - else - emit_buff( - ctx, "%s = %s(atomicCounter%sARB(%s, floatBitsToUint(%s).x%s));\n", - dst, get_string(dtypeprefix), opname, srcs[0], srcs[2], cas_str); - } -} - -static const char *reswizzle_dest(const struct vrend_shader_io *io, - const struct tgsi_full_dst_register *dst_reg, - char *reswizzled, const char *writemask) { - if (io->usage_mask != 0xf) { - if (io->num_components > 1) { - int real_wm = dst_reg->Register.WriteMask >> io->swizzle_offset; - int k = 1; - reswizzled[0] = '.'; - for (int i = 0; i < io->num_components; ++i) { - if (real_wm & (1 << i)) - reswizzled[k++] = get_swiz_char(i); - } - reswizzled[k] = 0; - } - writemask = reswizzled; - } - return writemask; -} - -static void get_destination_info_generic( - struct dump_ctx *ctx, const struct tgsi_full_dst_register *dst_reg, - const struct vrend_shader_io *io, const char *writemask, char dsts[255]) { - const char *blkarray = - (ctx->prog_type == TGSI_PROCESSOR_TESS_CTRL) ? "[gl_InvocationID]" : ""; - const char *stage_prefix = get_stage_output_name_prefix(ctx->prog_type); - const char *wm = io->override_no_wm ? "" : writemask; - char reswizzled[6] = ""; - - wm = reswizzle_dest(io, dst_reg, reswizzled, writemask); - - if (io->first == io->last) - snprintf(dsts, 255, "%s%s%s", io->glsl_name, blkarray, wm); - else { - if (prefer_generic_io_block(ctx, io_out)) { - char outvarname[64]; - get_blockvarname(outvarname, stage_prefix, io, blkarray); - - if (dst_reg->Register.Indirect) - snprintf(dsts, 255, "%s.%s[addr%d + %d]%s", outvarname, io->glsl_name, - dst_reg->Indirect.Index, dst_reg->Register.Index - io->first, - wm); - else - snprintf(dsts, 255, "%s.%s[%d]%s", outvarname, io->glsl_name, - dst_reg->Register.Index - io->first, wm); - } else { - if (dst_reg->Register.Indirect) - snprintf(dsts, 255, "%s%s[addr%d + %d]%s", io->glsl_name, blkarray, - dst_reg->Indirect.Index, dst_reg->Register.Index - io->first, - wm); - else - snprintf(dsts, 255, "%s%s[%d]%s", io->glsl_name, blkarray, - dst_reg->Register.Index - io->first, wm); - } - } -} - -static bool get_destination_info(struct dump_ctx *ctx, - const struct tgsi_full_instruction *inst, - struct dest_info *dinfo, char dsts[3][255], - char fp64_dsts[3][255], char *writemask) { - const struct tgsi_full_dst_register *dst_reg; - enum tgsi_opcode_type dtype = - tgsi_opcode_infer_dst_type(inst->Instruction.Opcode); - - if (dtype == TGSI_TYPE_SIGNED || dtype == TGSI_TYPE_UNSIGNED) - ctx->shader_req_bits |= SHADER_REQ_INTS; - - if (dtype == TGSI_TYPE_DOUBLE) { - /* we need the uvec2 conversion for doubles */ - ctx->shader_req_bits |= SHADER_REQ_INTS | SHADER_REQ_FP64; - } - - if (inst->Instruction.Opcode == TGSI_OPCODE_TXQ) { - dinfo->dtypeprefix = INT_BITS_TO_FLOAT; - } else { - switch (dtype) { - case TGSI_TYPE_UNSIGNED: - dinfo->dtypeprefix = UINT_BITS_TO_FLOAT; - break; - case TGSI_TYPE_SIGNED: - dinfo->dtypeprefix = INT_BITS_TO_FLOAT; - break; - default: - break; - } - } - - for (uint32_t i = 0; i < inst->Instruction.NumDstRegs; i++) { - char fp64_writemask[6] = ""; - dst_reg = &inst->Dst[i]; - dinfo->dst_override_no_wm[i] = false; - if (dst_reg->Register.WriteMask != TGSI_WRITEMASK_XYZW) { - int wm_idx = 0, dbl_wm_idx = 0; - writemask[wm_idx++] = '.'; - fp64_writemask[dbl_wm_idx++] = '.'; - - if (dst_reg->Register.WriteMask & 0x1) - writemask[wm_idx++] = 'x'; - if (dst_reg->Register.WriteMask & 0x2) - writemask[wm_idx++] = 'y'; - if (dst_reg->Register.WriteMask & 0x4) - writemask[wm_idx++] = 'z'; - if (dst_reg->Register.WriteMask & 0x8) - writemask[wm_idx++] = 'w'; - - if (dtype == TGSI_TYPE_DOUBLE) { - if (dst_reg->Register.WriteMask & 0x3) - fp64_writemask[dbl_wm_idx++] = 'x'; - if (dst_reg->Register.WriteMask & 0xc) - fp64_writemask[dbl_wm_idx++] = 'y'; - } - - if (dtype == TGSI_TYPE_DOUBLE) { - if (dbl_wm_idx == 2) - dinfo->dstconv = DOUBLE; - else - dinfo->dstconv = DVEC2; - } else { - dinfo->dstconv = FLOAT + wm_idx - 2; - dinfo->udstconv = UINT + wm_idx - 2; - dinfo->idstconv = INT + wm_idx - 2; - } - } else { - if (dtype == TGSI_TYPE_DOUBLE) - dinfo->dstconv = DVEC2; - else - dinfo->dstconv = VEC4; - dinfo->udstconv = UVEC4; - dinfo->idstconv = IVEC4; - } - - if (dst_reg->Register.File == TGSI_FILE_OUTPUT) { - uint32_t j; - for (j = 0; j < ctx->num_outputs; j++) { - if (ctx->outputs[j].first <= dst_reg->Register.Index && - ctx->outputs[j].last >= dst_reg->Register.Index && - (ctx->outputs[j].usage_mask & dst_reg->Register.WriteMask)) { - if (inst->Instruction.Precise) { - if (!ctx->outputs[j].invariant && - ctx->outputs[j].name != TGSI_SEMANTIC_CLIPVERTEX) { - ctx->outputs[j].precise = true; - ctx->shader_req_bits |= SHADER_REQ_GPU_SHADER5; - } - } - - if (ctx->glsl_ver_required >= 140 && - ctx->outputs[j].name == TGSI_SEMANTIC_CLIPVERTEX) { - snprintf(dsts[i], 255, "clipv_tmp"); - } else if (ctx->outputs[j].name == TGSI_SEMANTIC_CLIPDIST) { - char clip_indirect[32] = ""; - if (ctx->outputs[j].first != ctx->outputs[j].last) { - if (dst_reg->Register.Indirect) - snprintf(clip_indirect, sizeof(clip_indirect), "+ addr%d", - dst_reg->Indirect.Index); - else - snprintf(clip_indirect, sizeof(clip_indirect), "+ %d", - dst_reg->Register.Index - ctx->outputs[j].first); - } - snprintf(dsts[i], 255, "clip_dist_temp[%d %s]", ctx->outputs[j].sid, - clip_indirect); - } else if (ctx->outputs[j].name == TGSI_SEMANTIC_TESSOUTER || - ctx->outputs[j].name == TGSI_SEMANTIC_TESSINNER || - ctx->outputs[j].name == TGSI_SEMANTIC_SAMPLEMASK) { - int idx; - switch (dst_reg->Register.WriteMask) { - case 0x1: - idx = 0; - break; - case 0x2: - idx = 1; - break; - case 0x4: - idx = 2; - break; - case 0x8: - idx = 3; - break; - default: - idx = 0; - break; - } - snprintf(dsts[i], 255, "%s[%d]", ctx->outputs[j].glsl_name, idx); - if (ctx->outputs[j].is_int) { - dinfo->dtypeprefix = FLOAT_BITS_TO_INT; - dinfo->dstconv = INT; - } - } else { - if (ctx->outputs[j].glsl_gl_block) { - snprintf(dsts[i], 255, "gl_out[%s].%s%s", - ctx->prog_type == TGSI_PROCESSOR_TESS_CTRL - ? "gl_InvocationID" - : "0", - ctx->outputs[j].glsl_name, - ctx->outputs[j].override_no_wm ? "" : writemask); - } else if (ctx->outputs[j].name == TGSI_SEMANTIC_GENERIC) { - struct vrend_shader_io *io = ctx->generic_output_range.used - ? &ctx->generic_output_range.io - : &ctx->outputs[j]; - get_destination_info_generic(ctx, dst_reg, io, writemask, - dsts[i]); - dinfo->dst_override_no_wm[i] = ctx->outputs[j].override_no_wm; - } else if (ctx->outputs[j].name == TGSI_SEMANTIC_PATCH) { - struct vrend_shader_io *io = ctx->patch_output_range.used - ? &ctx->patch_output_range.io - : &ctx->outputs[j]; - char reswizzled[6] = ""; - const char *wm = - reswizzle_dest(io, dst_reg, reswizzled, writemask); - if (io->last != io->first) { - if (dst_reg->Register.Indirect) - snprintf(dsts[i], 255, "%s[addr%d + %d]%s", io->glsl_name, - dst_reg->Indirect.Index, - dst_reg->Register.Index - io->first, - io->override_no_wm ? "" : wm); - else - snprintf(dsts[i], 255, "%s[%d]%s", io->glsl_name, - dst_reg->Register.Index - io->first, - io->override_no_wm ? "" : wm); - } else { - snprintf(dsts[i], 255, "%s%s", io->glsl_name, - ctx->outputs[j].override_no_wm ? "" : wm); - } - dinfo->dst_override_no_wm[i] = ctx->outputs[j].override_no_wm; - } else { - if (ctx->prog_type == TGSI_PROCESSOR_TESS_CTRL) { - snprintf(dsts[i], 255, "%s[gl_InvocationID]%s", - ctx->outputs[j].glsl_name, - ctx->outputs[j].override_no_wm ? "" : writemask); - } else { - snprintf(dsts[i], 255, "%s%s", ctx->outputs[j].glsl_name, - ctx->outputs[j].override_no_wm ? "" : writemask); - } - dinfo->dst_override_no_wm[i] = ctx->outputs[j].override_no_wm; - } - if (ctx->outputs[j].is_int) { - if (dinfo->dtypeprefix == TYPE_CONVERSION_NONE) - dinfo->dtypeprefix = FLOAT_BITS_TO_INT; - dinfo->dstconv = INT; - } - if (ctx->outputs[j].name == TGSI_SEMANTIC_PSIZE) { - dinfo->dstconv = FLOAT; - break; - } - } - break; - } - } - } else if (dst_reg->Register.File == TGSI_FILE_TEMPORARY) { - struct vrend_temp_range *range = - find_temp_range(ctx, dst_reg->Register.Index); - if (!range) - return false; - if (dst_reg->Register.Indirect) { - snprintf(dsts[i], 255, "temp%d[addr0 + %d]%s", range->first, - dst_reg->Register.Index - range->first, writemask); - } else - snprintf(dsts[i], 255, "temp%d[%d]%s", range->first, - dst_reg->Register.Index - range->first, writemask); - } else if (dst_reg->Register.File == TGSI_FILE_IMAGE) { - const char *cname = tgsi_proc_to_prefix(ctx->prog_type); - if (ctx->info.indirect_files & (1 << TGSI_FILE_IMAGE)) { - int basearrayidx = lookup_image_array(ctx, dst_reg->Register.Index); - if (dst_reg->Register.Indirect) { - assert(dst_reg->Indirect.File == TGSI_FILE_ADDRESS); - snprintf(dsts[i], 255, "%simg%d[addr%d + %d]", cname, basearrayidx, - dst_reg->Indirect.Index, - dst_reg->Register.Index - basearrayidx); - } else - snprintf(dsts[i], 255, "%simg%d[%d]", cname, basearrayidx, - dst_reg->Register.Index - basearrayidx); - } else - snprintf(dsts[i], 255, "%simg%d", cname, dst_reg->Register.Index); - } else if (dst_reg->Register.File == TGSI_FILE_BUFFER) { - const char *cname = tgsi_proc_to_prefix(ctx->prog_type); - if (ctx->info.indirect_files & (1 << TGSI_FILE_BUFFER)) { - bool atomic_ssbo = - ctx->ssbo_atomic_mask & (1 << dst_reg->Register.Index); - const char *atomic_str = atomic_ssbo ? "atomic" : ""; - int base = - atomic_ssbo ? ctx->ssbo_atomic_array_base : ctx->ssbo_array_base; - if (dst_reg->Register.Indirect) { - snprintf(dsts[i], 255, "%sssboarr%s[addr%d+%d].%sssbocontents%d", - cname, atomic_str, dst_reg->Indirect.Index, - dst_reg->Register.Index - base, cname, base); - } else - snprintf(dsts[i], 255, "%sssboarr%s[%d].%sssbocontents%d", cname, - atomic_str, dst_reg->Register.Index - base, cname, base); - } else - snprintf(dsts[i], 255, "%sssbocontents%d", cname, - dst_reg->Register.Index); - } else if (dst_reg->Register.File == TGSI_FILE_MEMORY) { - snprintf(dsts[i], 255, "values"); - } else if (dst_reg->Register.File == TGSI_FILE_ADDRESS) { - snprintf(dsts[i], 255, "addr%d", dst_reg->Register.Index); - } - - if (dtype == TGSI_TYPE_DOUBLE) { - strcpy(fp64_dsts[i], dsts[i]); - snprintf(dsts[i], 255, "fp64_dst[%d]%s", i, fp64_writemask); - writemask[0] = 0; - } - } - - return true; -} - -static const char *shift_swizzles(const struct vrend_shader_io *io, - const struct tgsi_full_src_register *src, - int swz_offset, char *swizzle_shifted, - const char *swizzle) { - if (io->usage_mask != 0xf && swizzle[0]) { - if (io->num_components > 1) { - swizzle_shifted[swz_offset++] = '.'; - for (int i = 0; i < 4; ++i) { - switch (i) { - case 0: - swizzle_shifted[swz_offset++] = - get_swiz_char(src->Register.SwizzleX - io->swizzle_offset); - break; - case 1: - swizzle_shifted[swz_offset++] = - get_swiz_char(src->Register.SwizzleY - io->swizzle_offset); - break; - case 2: - swizzle_shifted[swz_offset++] = - src->Register.SwizzleZ - io->swizzle_offset < io->num_components - ? get_swiz_char(src->Register.SwizzleZ - io->swizzle_offset) - : 'x'; - break; - case 3: - swizzle_shifted[swz_offset++] = - src->Register.SwizzleW - io->swizzle_offset < io->num_components - ? get_swiz_char(src->Register.SwizzleW - io->swizzle_offset) - : 'x'; - } - } - swizzle_shifted[swz_offset] = 0; - } - swizzle = swizzle_shifted; - } - return swizzle; -} - -static void get_source_info_generic(struct dump_ctx *ctx, enum io_type iot, - enum vrend_type_qualifier srcstypeprefix, - const char *prefix, - const struct tgsi_full_src_register *src, - const struct vrend_shader_io *io, - const char *arrayname, const char *swizzle, - struct vrend_strbuf *result) { - int swz_offset = 0; - char swizzle_shifted[6] = ""; - if (swizzle[0] == ')') { - swizzle_shifted[swz_offset++] = ')'; - swizzle_shifted[swz_offset] = 0; - } - - /* This IO element is not using all vector elements, so we have to shift the - * swizzle names */ - swizzle = shift_swizzles(io, src, swz_offset, swizzle_shifted, swizzle); - - if (io->first == io->last) { - strbuf_fmt(result, "%s(%s%s%s%s)", get_string(srcstypeprefix), prefix, - io->glsl_name, arrayname, io->is_int ? "" : swizzle); - } else { - - if (prefer_generic_io_block(ctx, iot)) { - char outvarname[64]; - const char *stage_prefix = - iot == io_in ? get_stage_input_name_prefix(ctx, ctx->prog_type) - : get_stage_output_name_prefix(ctx->prog_type); - - get_blockvarname(outvarname, stage_prefix, io, arrayname); - if (src->Register.Indirect) - strbuf_fmt(result, "%s(%s %s.%s[addr%d + %d] %s)", - get_string(srcstypeprefix), prefix, outvarname, - io->glsl_name, src->Indirect.Index, - src->Register.Index - io->first, io->is_int ? "" : swizzle); - else - strbuf_fmt(result, "%s(%s %s.%s[%d] %s)", get_string(srcstypeprefix), - prefix, outvarname, io->glsl_name, - src->Register.Index - io->first, io->is_int ? "" : swizzle); - } else { - if (src->Register.Indirect) - strbuf_fmt(result, "%s(%s %s%s[addr%d + %d] %s)", - get_string(srcstypeprefix), prefix, io->glsl_name, arrayname, - src->Indirect.Index, src->Register.Index - io->first, - io->is_int ? "" : swizzle); - else - strbuf_fmt(result, "%s(%s %s%s[%d] %s)", get_string(srcstypeprefix), - prefix, io->glsl_name, arrayname, - src->Register.Index - io->first, io->is_int ? "" : swizzle); - } - } -} - -static void get_source_info_patch(enum vrend_type_qualifier srcstypeprefix, - const char *prefix, - const struct tgsi_full_src_register *src, - const struct vrend_shader_io *io, - const char *arrayname, const char *swizzle, - struct vrend_strbuf *result) { - int swz_offset = 0; - char swizzle_shifted[7] = ""; - if (swizzle[0] == ')') { - swizzle_shifted[swz_offset++] = ')'; - swizzle_shifted[swz_offset] = 0; - } - - swizzle = shift_swizzles(io, src, swz_offset, swizzle_shifted, swizzle); - const char *wm = io->is_int ? "" : swizzle; - - if (io->last == io->first) - strbuf_fmt(result, "%s(%s%s%s%s)", get_string(srcstypeprefix), prefix, - io->glsl_name, arrayname, wm); - else { - if (src->Register.Indirect) - strbuf_fmt(result, "%s(%s %s[addr%d + %d] %s)", - get_string(srcstypeprefix), prefix, io->glsl_name, - src->Indirect.Index, src->Register.Index - io->first, wm); - else - strbuf_fmt(result, "%s(%s %s[%d] %s)", get_string(srcstypeprefix), prefix, - io->glsl_name, src->Register.Index - io->first, wm); - } -} - -static bool get_source_info(struct dump_ctx *ctx, - const struct tgsi_full_instruction *inst, - struct source_info *sinfo, - struct vrend_strbuf srcs[4], - char src_swizzle0[10]) { - bool stprefix = false; - - enum vrend_type_qualifier stypeprefix = TYPE_CONVERSION_NONE; - enum tgsi_opcode_type stype = - tgsi_opcode_infer_src_type(inst->Instruction.Opcode); - - if (stype == TGSI_TYPE_SIGNED || stype == TGSI_TYPE_UNSIGNED) - ctx->shader_req_bits |= SHADER_REQ_INTS; - if (stype == TGSI_TYPE_DOUBLE) - ctx->shader_req_bits |= SHADER_REQ_INTS | SHADER_REQ_FP64; - - switch (stype) { - case TGSI_TYPE_DOUBLE: - stypeprefix = FLOAT_BITS_TO_UINT; - sinfo->svec4 = DVEC2; - stprefix = true; - break; - case TGSI_TYPE_UNSIGNED: - stypeprefix = FLOAT_BITS_TO_UINT; - sinfo->svec4 = UVEC4; - stprefix = true; - break; - case TGSI_TYPE_SIGNED: - stypeprefix = FLOAT_BITS_TO_INT; - sinfo->svec4 = IVEC4; - stprefix = true; - break; - } - - for (uint32_t i = 0; i < inst->Instruction.NumSrcRegs; i++) { - const struct tgsi_full_src_register *src = &inst->Src[i]; - struct vrend_strbuf *src_buf = &srcs[i]; - char swizzle[8] = ""; - int usage_mask = 0; - char *swizzle_writer = swizzle; - char prefix[6] = ""; - char arrayname[16] = ""; - char fp64_src[255]; - int swz_idx = 0, pre_idx = 0; - boolean isfloatabsolute = - src->Register.Absolute && stype != TGSI_TYPE_DOUBLE; - - sinfo->override_no_wm[i] = false; - sinfo->override_no_cast[i] = false; - if (isfloatabsolute) - swizzle[swz_idx++] = ')'; - - if (src->Register.Negate) - prefix[pre_idx++] = '-'; - if (isfloatabsolute) - strcpy(&prefix[pre_idx++], "abs("); - - if (src->Register.Dimension) { - if (src->Dimension.Indirect) { - assert(src->DimIndirect.File == TGSI_FILE_ADDRESS); - sprintf(arrayname, "[addr%d]", src->DimIndirect.Index); - } else - sprintf(arrayname, "[%d]", src->Dimension.Index); - } - - /* These instructions don't support swizzles in the first parameter - * pass the swizzle to the caller instead */ - if ((inst->Instruction.Opcode == TGSI_OPCODE_INTERP_SAMPLE || - inst->Instruction.Opcode == TGSI_OPCODE_INTERP_OFFSET || - inst->Instruction.Opcode == TGSI_OPCODE_INTERP_CENTROID) && - i == 0) { - swizzle_writer = src_swizzle0; - } - - usage_mask |= 1 << src->Register.SwizzleX; - usage_mask |= 1 << src->Register.SwizzleY; - usage_mask |= 1 << src->Register.SwizzleZ; - usage_mask |= 1 << src->Register.SwizzleW; - - if (src->Register.SwizzleX != TGSI_SWIZZLE_X || - src->Register.SwizzleY != TGSI_SWIZZLE_Y || - src->Register.SwizzleZ != TGSI_SWIZZLE_Z || - src->Register.SwizzleW != TGSI_SWIZZLE_W) { - swizzle_writer[swz_idx++] = '.'; - swizzle_writer[swz_idx++] = get_swiz_char(src->Register.SwizzleX); - swizzle_writer[swz_idx++] = get_swiz_char(src->Register.SwizzleY); - swizzle_writer[swz_idx++] = get_swiz_char(src->Register.SwizzleZ); - swizzle_writer[swz_idx++] = get_swiz_char(src->Register.SwizzleW); - } - swizzle_writer[swz_idx] = 0; - - if (src->Register.File == TGSI_FILE_INPUT) { - for (uint32_t j = 0; j < ctx->num_inputs; j++) - if (ctx->inputs[j].first <= src->Register.Index && - ctx->inputs[j].last >= src->Register.Index && - (ctx->inputs[j].usage_mask & usage_mask)) { - if (ctx->key->color_two_side && - ctx->inputs[j].name == TGSI_SEMANTIC_COLOR) - strbuf_fmt(src_buf, "%s(%s%s%d%s%s)", get_string(stypeprefix), - prefix, "realcolor", ctx->inputs[j].sid, arrayname, - swizzle); - else if (ctx->inputs[j].glsl_gl_block) { - /* GS input clipdist requires a conversion */ - if (ctx->inputs[j].name == TGSI_SEMANTIC_CLIPDIST) { - create_swizzled_clipdist(ctx, src_buf, src, j, true, - get_string(stypeprefix), prefix, - arrayname, ctx->inputs[j].first); - } else { - strbuf_fmt(src_buf, "%s(vec4(%sgl_in%s.%s)%s)", - get_string(stypeprefix), prefix, arrayname, - ctx->inputs[j].glsl_name, swizzle); - } - } else if (ctx->inputs[j].name == TGSI_SEMANTIC_PRIMID) - strbuf_fmt(src_buf, "%s(vec4(intBitsToFloat(%s)))", - get_string(stypeprefix), ctx->inputs[j].glsl_name); - else if (ctx->inputs[j].name == TGSI_SEMANTIC_FACE) - strbuf_fmt(src_buf, "%s(%s ? 1.0 : -1.0)", get_string(stypeprefix), - ctx->inputs[j].glsl_name); - else if (ctx->inputs[j].name == TGSI_SEMANTIC_CLIPDIST) { - if (ctx->prog_type == TGSI_PROCESSOR_FRAGMENT) - load_clipdist_fs(ctx, src_buf, src, j, false, - get_string(stypeprefix), ctx->inputs[j].first); - else - create_swizzled_clipdist(ctx, src_buf, src, j, false, - get_string(stypeprefix), prefix, - arrayname, ctx->inputs[j].first); - } else { - enum vrend_type_qualifier srcstypeprefix = stypeprefix; - if ((stype == TGSI_TYPE_UNSIGNED || stype == TGSI_TYPE_SIGNED) && - ctx->inputs[j].is_int) - srcstypeprefix = TYPE_CONVERSION_NONE; - - if (inst->Instruction.Opcode == TGSI_OPCODE_INTERP_SAMPLE && - i == 1) { - strbuf_fmt(src_buf, "floatBitsToInt(%s%s%s%s)", prefix, - ctx->inputs[j].glsl_name, arrayname, swizzle); - } else if (ctx->inputs[j].name == TGSI_SEMANTIC_GENERIC) { - struct vrend_shader_io *io = ctx->generic_input_range.used - ? &ctx->generic_input_range.io - : &ctx->inputs[j]; - get_source_info_generic(ctx, io_in, srcstypeprefix, prefix, src, - io, arrayname, swizzle, src_buf); - } else if (ctx->inputs[j].name == TGSI_SEMANTIC_PATCH) { - struct vrend_shader_io *io = ctx->patch_input_range.used - ? &ctx->patch_input_range.io - : &ctx->inputs[j]; - get_source_info_patch(srcstypeprefix, prefix, src, io, arrayname, - swizzle, src_buf); - } else if (ctx->inputs[j].name == TGSI_SEMANTIC_POSITION && - ctx->prog_type == TGSI_PROCESSOR_VERTEX && - ctx->inputs[j].first != ctx->inputs[j].last) { - if (src->Register.Indirect) - strbuf_fmt(src_buf, "%s(%s%s%s[addr%d + %d]%s)", - get_string(srcstypeprefix), prefix, - ctx->inputs[j].glsl_name, arrayname, - src->Indirect.Index, src->Register.Index, - ctx->inputs[j].is_int ? "" : swizzle); - else - strbuf_fmt( - src_buf, "%s(%s%s%s[%d]%s)", get_string(srcstypeprefix), - prefix, ctx->inputs[j].glsl_name, arrayname, - src->Register.Index, ctx->inputs[j].is_int ? "" : swizzle); - } else - strbuf_fmt(src_buf, "%s(%s%s%s%s)", get_string(srcstypeprefix), - prefix, ctx->inputs[j].glsl_name, arrayname, - ctx->inputs[j].is_int ? "" : swizzle); - } - sinfo->override_no_wm[i] = ctx->inputs[j].override_no_wm; - break; - } - } else if (src->Register.File == TGSI_FILE_OUTPUT) { - for (uint32_t j = 0; j < ctx->num_outputs; j++) { - if (ctx->outputs[j].first <= src->Register.Index && - ctx->outputs[j].last >= src->Register.Index && - (ctx->outputs[j].usage_mask & usage_mask)) { - if (inst->Instruction.Opcode == TGSI_OPCODE_FBFETCH) { - ctx->outputs[j].fbfetch_used = true; - ctx->shader_req_bits |= SHADER_REQ_FBFETCH; - } - - enum vrend_type_qualifier srcstypeprefix = stypeprefix; - if (stype == TGSI_TYPE_UNSIGNED && ctx->outputs[j].is_int) - srcstypeprefix = TYPE_CONVERSION_NONE; - if (ctx->outputs[j].glsl_gl_block) { - if (ctx->outputs[j].name == TGSI_SEMANTIC_CLIPDIST) { - char clip_indirect[32] = ""; - if (ctx->outputs[j].first != ctx->outputs[j].last) { - if (src->Register.Indirect) - snprintf(clip_indirect, sizeof(clip_indirect), "+ addr%d", - src->Indirect.Index); - else - snprintf(clip_indirect, sizeof(clip_indirect), "+ %d", - src->Register.Index - ctx->outputs[j].first); - } - strbuf_fmt(src_buf, "clip_dist_temp[%d%s]", ctx->outputs[j].sid, - clip_indirect); - } - } else if (ctx->outputs[j].name == TGSI_SEMANTIC_GENERIC) { - struct vrend_shader_io *io = ctx->generic_output_range.used - ? &ctx->generic_output_range.io - : &ctx->outputs[j]; - get_source_info_generic(ctx, io_out, srcstypeprefix, prefix, src, - io, arrayname, swizzle, src_buf); - } else if (ctx->outputs[j].name == TGSI_SEMANTIC_PATCH) { - struct vrend_shader_io *io = ctx->patch_output_range.used - ? &ctx->patch_output_range.io - : &ctx->outputs[j]; - get_source_info_patch(srcstypeprefix, prefix, src, io, arrayname, - swizzle, src_buf); - } else { - strbuf_fmt(src_buf, "%s(%s%s%s%s)", get_string(srcstypeprefix), - prefix, ctx->outputs[j].glsl_name, arrayname, - ctx->outputs[j].is_int ? "" : swizzle); - } - sinfo->override_no_wm[i] = ctx->outputs[j].override_no_wm; - break; - } - } - } else if (src->Register.File == TGSI_FILE_TEMPORARY) { - struct vrend_temp_range *range = - find_temp_range(ctx, src->Register.Index); - if (!range) - return false; - if (inst->Instruction.Opcode == TGSI_OPCODE_INTERP_SAMPLE && i == 1) { - stprefix = true; - stypeprefix = FLOAT_BITS_TO_INT; - } - - if (src->Register.Indirect) { - assert(src->Indirect.File == TGSI_FILE_ADDRESS); - strbuf_fmt( - src_buf, "%s%c%stemp%d[addr%d + %d]%s%c", get_string(stypeprefix), - stprefix ? '(' : ' ', prefix, range->first, src->Indirect.Index, - src->Register.Index - range->first, swizzle, stprefix ? ')' : ' '); - } else - strbuf_fmt(src_buf, "%s%c%stemp%d[%d]%s%c", get_string(stypeprefix), - stprefix ? '(' : ' ', prefix, range->first, - src->Register.Index - range->first, swizzle, - stprefix ? ')' : ' '); - } else if (src->Register.File == TGSI_FILE_CONSTANT) { - const char *cname = tgsi_proc_to_prefix(ctx->prog_type); - int dim = 0; - if (src->Register.Dimension && src->Dimension.Index != 0) { - dim = src->Dimension.Index; - if (src->Dimension.Indirect) { - assert(src->DimIndirect.File == TGSI_FILE_ADDRESS); - ctx->shader_req_bits |= SHADER_REQ_GPU_SHADER5; - if (src->Register.Indirect) { - assert(src->Indirect.File == TGSI_FILE_ADDRESS); - strbuf_fmt( - src_buf, "%s(%s%suboarr[addr%d].ubocontents[addr%d + %d]%s)", - get_string(stypeprefix), prefix, cname, src->DimIndirect.Index, - src->Indirect.Index, src->Register.Index, swizzle); - } else - strbuf_fmt(src_buf, "%s(%s%suboarr[addr%d].ubocontents[%d]%s)", - get_string(stypeprefix), prefix, cname, - src->DimIndirect.Index, src->Register.Index, swizzle); - } else { - if (ctx->info.dimension_indirect_files & (1 << TGSI_FILE_CONSTANT)) { - if (src->Register.Indirect) { - strbuf_fmt( - src_buf, "%s(%s%suboarr[%d].ubocontents[addr%d + %d]%s)", - get_string(stypeprefix), prefix, cname, dim - ctx->ubo_base, - src->Indirect.Index, src->Register.Index, swizzle); - } else - strbuf_fmt(src_buf, "%s(%s%suboarr[%d].ubocontents[%d]%s)", - get_string(stypeprefix), prefix, cname, - dim - ctx->ubo_base, src->Register.Index, swizzle); - } else { - if (src->Register.Indirect) { - strbuf_fmt(src_buf, "%s(%s%subo%dcontents[addr0 + %d]%s)", - get_string(stypeprefix), prefix, cname, dim, - src->Register.Index, swizzle); - } else - strbuf_fmt(src_buf, "%s(%s%subo%dcontents[%d]%s)", - get_string(stypeprefix), prefix, cname, dim, - src->Register.Index, swizzle); - } - } - } else { - enum vrend_type_qualifier csp = TYPE_CONVERSION_NONE; - ctx->shader_req_bits |= SHADER_REQ_INTS; - if (inst->Instruction.Opcode == TGSI_OPCODE_INTERP_SAMPLE && i == 1) - csp = IVEC4; - else if (stype == TGSI_TYPE_FLOAT || stype == TGSI_TYPE_UNTYPED) - csp = UINT_BITS_TO_FLOAT; - else if (stype == TGSI_TYPE_SIGNED) - csp = IVEC4; - - if (src->Register.Indirect) { - strbuf_fmt(src_buf, "%s%s(%sconst%d[addr0 + %d]%s)", prefix, - get_string(csp), cname, dim, src->Register.Index, swizzle); - } else - strbuf_fmt(src_buf, "%s%s(%sconst%d[%d]%s)", prefix, get_string(csp), - cname, dim, src->Register.Index, swizzle); - } - } else if (src->Register.File == TGSI_FILE_SAMPLER) { - const char *cname = tgsi_proc_to_prefix(ctx->prog_type); - if (ctx->info.indirect_files & (1 << TGSI_FILE_SAMPLER)) { - int basearrayidx = lookup_sampler_array(ctx, src->Register.Index); - if (src->Register.Indirect) { - strbuf_fmt(src_buf, "%ssamp%d[addr%d+%d]%s", cname, basearrayidx, - src->Indirect.Index, src->Register.Index - basearrayidx, - swizzle); - } else { - strbuf_fmt(src_buf, "%ssamp%d[%d]%s", cname, basearrayidx, - src->Register.Index - basearrayidx, swizzle); - } - } else { - strbuf_fmt(src_buf, "%ssamp%d%s", cname, src->Register.Index, swizzle); - } - sinfo->sreg_index = src->Register.Index; - } else if (src->Register.File == TGSI_FILE_IMAGE) { - const char *cname = tgsi_proc_to_prefix(ctx->prog_type); - if (ctx->info.indirect_files & (1 << TGSI_FILE_IMAGE)) { - int basearrayidx = lookup_image_array(ctx, src->Register.Index); - if (src->Register.Indirect) { - assert(src->Indirect.File == TGSI_FILE_ADDRESS); - strbuf_fmt(src_buf, "%simg%d[addr%d + %d]", cname, basearrayidx, - src->Indirect.Index, src->Register.Index - basearrayidx); - } else - strbuf_fmt(src_buf, "%simg%d[%d]", cname, basearrayidx, - src->Register.Index - basearrayidx); - } else - strbuf_fmt(src_buf, "%simg%d%s", cname, src->Register.Index, swizzle); - sinfo->sreg_index = src->Register.Index; - } else if (src->Register.File == TGSI_FILE_BUFFER) { - const char *cname = tgsi_proc_to_prefix(ctx->prog_type); - if (ctx->info.indirect_files & (1 << TGSI_FILE_BUFFER)) { - bool atomic_ssbo = ctx->ssbo_atomic_mask & (1 << src->Register.Index); - const char *atomic_str = atomic_ssbo ? "atomic" : ""; - int base = - atomic_ssbo ? ctx->ssbo_atomic_array_base : ctx->ssbo_array_base; - if (src->Register.Indirect) { - strbuf_fmt(src_buf, "%sssboarr%s[addr%d+%d].%sssbocontents%d%s", - cname, atomic_str, src->Indirect.Index, - src->Register.Index - base, cname, base, swizzle); - } else { - strbuf_fmt(src_buf, "%sssboarr%s[%d].%sssbocontents%d%s", cname, - atomic_str, src->Register.Index - base, cname, base, - swizzle); - } - } else { - strbuf_fmt(src_buf, "%sssbocontents%d%s", cname, src->Register.Index, - swizzle); - } - sinfo->sreg_index = src->Register.Index; - } else if (src->Register.File == TGSI_FILE_MEMORY) { - strbuf_fmt(src_buf, "values"); - sinfo->sreg_index = src->Register.Index; - } else if (src->Register.File == TGSI_FILE_IMMEDIATE) { - if (src->Register.Index >= (int)ARRAY_SIZE(ctx->imm)) - return false; - struct immed *imd = &ctx->imm[src->Register.Index]; - int idx = src->Register.SwizzleX; - char temp[48]; - enum vrend_type_qualifier vtype = VEC4; - enum vrend_type_qualifier imm_stypeprefix = stypeprefix; - - if ((inst->Instruction.Opcode == TGSI_OPCODE_TG4 && i == 1) || - (inst->Instruction.Opcode == TGSI_OPCODE_INTERP_SAMPLE && i == 1)) - stype = TGSI_TYPE_SIGNED; - - if (imd->type == TGSI_IMM_UINT32 || imd->type == TGSI_IMM_INT32) { - if (imd->type == TGSI_IMM_UINT32) - vtype = UVEC4; - else - vtype = IVEC4; - - if (stype == TGSI_TYPE_UNSIGNED && imd->type == TGSI_IMM_INT32) - imm_stypeprefix = UVEC4; - else if (stype == TGSI_TYPE_SIGNED && imd->type == TGSI_IMM_UINT32) - imm_stypeprefix = IVEC4; - else if (stype == TGSI_TYPE_FLOAT || stype == TGSI_TYPE_UNTYPED) { - if (imd->type == TGSI_IMM_INT32) - imm_stypeprefix = INT_BITS_TO_FLOAT; - else - imm_stypeprefix = UINT_BITS_TO_FLOAT; - } else if (stype == TGSI_TYPE_UNSIGNED || stype == TGSI_TYPE_SIGNED) - imm_stypeprefix = TYPE_CONVERSION_NONE; - } else if (imd->type == TGSI_IMM_FLOAT64) { - vtype = UVEC4; - if (stype == TGSI_TYPE_DOUBLE) - imm_stypeprefix = TYPE_CONVERSION_NONE; - else - imm_stypeprefix = UINT_BITS_TO_FLOAT; - } - - /* build up a vec4 of immediates */ - strbuf_fmt(src_buf, "%s(%s%s(", get_string(imm_stypeprefix), prefix, - get_string(vtype)); - for (uint32_t j = 0; j < 4; j++) { - if (j == 0) - idx = src->Register.SwizzleX; - else if (j == 1) - idx = src->Register.SwizzleY; - else if (j == 2) - idx = src->Register.SwizzleZ; - else if (j == 3) - idx = src->Register.SwizzleW; - - if (inst->Instruction.Opcode == TGSI_OPCODE_TG4 && i == 1 && j == 0) { - if (imd->val[idx].ui > 0) - sinfo->tg4_has_component = true; - } - - switch (imd->type) { - case TGSI_IMM_FLOAT32: - if (isinf(imd->val[idx].f) || isnan(imd->val[idx].f)) { - ctx->shader_req_bits |= SHADER_REQ_INTS; - snprintf(temp, 48, "uintBitsToFloat(%uU)", imd->val[idx].ui); - } else - snprintf(temp, 25, "%.8g", imd->val[idx].f); - break; - case TGSI_IMM_UINT32: - snprintf(temp, 25, "%uU", imd->val[idx].ui); - break; - case TGSI_IMM_INT32: - snprintf(temp, 25, "%d", imd->val[idx].i); - sinfo->imm_value = imd->val[idx].i; - break; - case TGSI_IMM_FLOAT64: - snprintf(temp, 48, "%uU", imd->val[idx].ui); - break; - default: - return false; - } - strbuf_append(src_buf, temp); - if (j < 3) - strbuf_append(src_buf, ","); - else { - snprintf(temp, 4, "))%c", isfloatabsolute ? ')' : 0); - strbuf_append(src_buf, temp); - } - } - } else if (src->Register.File == TGSI_FILE_SYSTEM_VALUE) { - for (uint32_t j = 0; j < ctx->num_system_values; j++) - if (ctx->system_values[j].first == src->Register.Index) { - if (ctx->system_values[j].name == TGSI_SEMANTIC_VERTEXID || - ctx->system_values[j].name == TGSI_SEMANTIC_INSTANCEID || - ctx->system_values[j].name == TGSI_SEMANTIC_PRIMID || - ctx->system_values[j].name == TGSI_SEMANTIC_VERTICESIN || - ctx->system_values[j].name == TGSI_SEMANTIC_INVOCATIONID || - ctx->system_values[j].name == TGSI_SEMANTIC_SAMPLEID) { - if (inst->Instruction.Opcode == TGSI_OPCODE_INTERP_SAMPLE && i == 1) - strbuf_fmt(src_buf, "ivec4(%s)", ctx->system_values[j].glsl_name); - else - strbuf_fmt(src_buf, "%s(vec4(intBitsToFloat(%s)))", - get_string(stypeprefix), - ctx->system_values[j].glsl_name); - } else if (ctx->system_values[j].name == - TGSI_SEMANTIC_HELPER_INVOCATION) { - strbuf_fmt(src_buf, "uvec4(%s)", ctx->system_values[j].glsl_name); - } else if (ctx->system_values[j].name == TGSI_SEMANTIC_TESSINNER || - ctx->system_values[j].name == TGSI_SEMANTIC_TESSOUTER) { - strbuf_fmt(src_buf, "%s(vec4(%s[%d], %s[%d], %s[%d], %s[%d]))", - prefix, ctx->system_values[j].glsl_name, - src->Register.SwizzleX, ctx->system_values[j].glsl_name, - src->Register.SwizzleY, ctx->system_values[j].glsl_name, - src->Register.SwizzleZ, ctx->system_values[j].glsl_name, - src->Register.SwizzleW); - } else if (ctx->system_values[j].name == TGSI_SEMANTIC_SAMPLEPOS) { - /* gl_SamplePosition is a vec2, but TGSI_SEMANTIC_SAMPLEPOS - * is a vec4 with z = w = 0 - */ - const char *components[4] = {"gl_SamplePosition.x", - "gl_SamplePosition.y", "0.0", "0.0"}; - strbuf_fmt(src_buf, "%s(vec4(%s, %s, %s, %s))", prefix, - components[src->Register.SwizzleX], - components[src->Register.SwizzleY], - components[src->Register.SwizzleZ], - components[src->Register.SwizzleW]); - } else if (ctx->system_values[j].name == TGSI_SEMANTIC_TESSCOORD) { - strbuf_fmt(src_buf, "%s(vec4(%s.%c, %s.%c, %s.%c, %s.%c))", prefix, - ctx->system_values[j].glsl_name, - get_swiz_char(src->Register.SwizzleX), - ctx->system_values[j].glsl_name, - get_swiz_char(src->Register.SwizzleY), - ctx->system_values[j].glsl_name, - get_swiz_char(src->Register.SwizzleZ), - ctx->system_values[j].glsl_name, - get_swiz_char(src->Register.SwizzleW)); - } else if (ctx->system_values[j].name == TGSI_SEMANTIC_GRID_SIZE || - ctx->system_values[j].name == TGSI_SEMANTIC_THREAD_ID || - ctx->system_values[j].name == TGSI_SEMANTIC_BLOCK_ID) { - enum vrend_type_qualifier mov_conv = TYPE_CONVERSION_NONE; - if (inst->Instruction.Opcode == TGSI_OPCODE_MOV && - inst->Dst[0].Register.File == TGSI_FILE_TEMPORARY) - mov_conv = UINT_BITS_TO_FLOAT; - strbuf_fmt(src_buf, "%s(uvec4(%s.%c, %s.%c, %s.%c, %s.%c))", - get_string(mov_conv), ctx->system_values[j].glsl_name, - get_swiz_char(src->Register.SwizzleX), - ctx->system_values[j].glsl_name, - get_swiz_char(src->Register.SwizzleY), - ctx->system_values[j].glsl_name, - get_swiz_char(src->Register.SwizzleZ), - ctx->system_values[j].glsl_name, - get_swiz_char(src->Register.SwizzleW)); - sinfo->override_no_cast[i] = true; - } else if (ctx->system_values[j].name == TGSI_SEMANTIC_SAMPLEMASK) { - const char *vec_type = "ivec4"; - if ((inst->Instruction.Opcode == TGSI_OPCODE_AND) && - (stype == TGSI_TYPE_UNSIGNED)) - vec_type = "uvec4"; - ctx->shader_req_bits |= SHADER_REQ_SAMPLE_SHADING | SHADER_REQ_INTS; - strbuf_fmt(src_buf, "%s(%s, %s, %s, %s)", vec_type, - src->Register.SwizzleX == TGSI_SWIZZLE_X - ? ctx->system_values[j].glsl_name - : "0", - src->Register.SwizzleY == TGSI_SWIZZLE_X - ? ctx->system_values[j].glsl_name - : "0", - src->Register.SwizzleZ == TGSI_SWIZZLE_X - ? ctx->system_values[j].glsl_name - : "0", - src->Register.SwizzleW == TGSI_SWIZZLE_X - ? ctx->system_values[j].glsl_name - : "0"); - } else - strbuf_fmt(src_buf, "%s%s", prefix, - ctx->system_values[j].glsl_name); - sinfo->override_no_wm[i] = ctx->system_values[j].override_no_wm; - break; - } - } else if (src->Register.File == TGSI_FILE_HW_ATOMIC) { - for (uint32_t j = 0; j < ctx->num_abo; j++) { - if (src->Dimension.Index == ctx->abo_idx[j] && - src->Register.Index >= ctx->abo_offsets[j] && - src->Register.Index < ctx->abo_offsets[j] + ctx->abo_sizes[j]) { - if (ctx->abo_sizes[j] > 1) { - int offset = src->Register.Index - ctx->abo_offsets[j]; - if (src->Register.Indirect) { - assert(src->Indirect.File == TGSI_FILE_ADDRESS); - strbuf_fmt(src_buf, "ac%d[addr%d + %d]", j, src->Indirect.Index, - offset); - } else - strbuf_fmt(src_buf, "ac%d[%d]", j, offset); - } else - strbuf_fmt(src_buf, "ac%d", j); - break; - } - } - sinfo->sreg_index = src->Register.Index; - } - - if (stype == TGSI_TYPE_DOUBLE) { - boolean isabsolute = src->Register.Absolute; - strcpy(fp64_src, src_buf->buf); - strbuf_fmt(src_buf, "fp64_src[%d]", i); - emit_buff(ctx, "%s.x = %spackDouble2x32(uvec2(%s%s))%s;\n", src_buf->buf, - isabsolute ? "abs(" : "", fp64_src, swizzle, - isabsolute ? ")" : ""); - } - } - - return true; -} - -static bool -rewrite_1d_image_coordinate(struct vrend_strbuf *src, - const struct tgsi_full_instruction *inst) { - if (inst->Src[0].Register.File == TGSI_FILE_IMAGE && - (inst->Memory.Texture == TGSI_TEXTURE_1D || - inst->Memory.Texture == TGSI_TEXTURE_1D_ARRAY)) { - - /* duplicate src */ - size_t len = strbuf_get_len(src); - char *buf = malloc(len); - if (!buf) - return false; - strncpy(buf, src->buf, len); - - if (inst->Memory.Texture == TGSI_TEXTURE_1D) - strbuf_fmt(src, "vec2(vec4(%s).x, 0)", buf); - else if (inst->Memory.Texture == TGSI_TEXTURE_1D_ARRAY) - strbuf_fmt(src, "vec3(%s.xy, 0).xzy", buf); - - free(buf); - } - return true; -} -/* We have indirect IO access, but the guest actually send separate values, so - * now we have to emulate an array. - */ -static void rewrite_io_ranged(struct dump_ctx *ctx) { - if ((ctx->info.indirect_files & (1 << TGSI_FILE_INPUT)) || - ctx->key->num_indirect_generic_inputs || - ctx->key->num_indirect_patch_inputs) { - - for (uint i = 0; i < ctx->num_inputs; ++i) { - if (ctx->inputs[i].name == TGSI_SEMANTIC_PATCH) { - ctx->inputs[i].glsl_predefined_no_emit = true; - if (ctx->inputs[i].sid < ctx->patch_input_range.io.sid || - ctx->patch_input_range.used == false) { - ctx->patch_input_range.io.first = i; - ctx->patch_input_range.io.usage_mask = 0xf; - ctx->patch_input_range.io.name = TGSI_SEMANTIC_PATCH; - ctx->patch_input_range.io.sid = ctx->inputs[i].sid; - ctx->patch_input_range.used = true; - } - if (ctx->inputs[i].sid > ctx->patch_input_range.io.last) - ctx->patch_input_range.io.last = ctx->inputs[i].sid; - } - - if (ctx->inputs[i].name == TGSI_SEMANTIC_GENERIC) { - ctx->inputs[i].glsl_predefined_no_emit = true; - if (ctx->inputs[i].sid < ctx->generic_input_range.io.sid || - ctx->generic_input_range.used == false) { - ctx->generic_input_range.io.sid = ctx->inputs[i].sid; - ctx->generic_input_range.io.first = i; - ctx->generic_input_range.io.name = TGSI_SEMANTIC_GENERIC; - ctx->generic_input_range.io.num_components = 4; - ctx->generic_input_range.used = true; - } - if (ctx->inputs[i].sid > ctx->generic_input_range.io.last) - ctx->generic_input_range.io.last = ctx->inputs[i].sid; - } - - if (ctx->key->num_indirect_generic_inputs > 0) - ctx->generic_input_range.io.last = - ctx->generic_input_range.io.sid + - ctx->key->num_indirect_generic_inputs - 1; - if (ctx->key->num_indirect_patch_inputs > 0) - ctx->patch_input_range.io.last = ctx->patch_input_range.io.sid + - ctx->key->num_indirect_patch_inputs - - 1; - } - snprintf(ctx->patch_input_range.io.glsl_name, 64, "%s_p%d", - get_stage_input_name_prefix(ctx, ctx->prog_type), - ctx->patch_input_range.io.sid); - snprintf(ctx->generic_input_range.io.glsl_name, 64, "%s_g%d", - get_stage_input_name_prefix(ctx, ctx->prog_type), - ctx->generic_input_range.io.sid); - - ctx->generic_input_range.io.num_components = 4; - ctx->generic_input_range.io.usage_mask = 0xf; - ctx->generic_input_range.io.swizzle_offset = 0; - - ctx->patch_input_range.io.num_components = 4; - ctx->patch_input_range.io.usage_mask = 0xf; - ctx->patch_input_range.io.swizzle_offset = 0; - - if (prefer_generic_io_block(ctx, io_in)) - require_glsl_ver(ctx, 150); - } - - if ((ctx->info.indirect_files & (1 << TGSI_FILE_OUTPUT)) || - ctx->key->num_indirect_generic_outputs || - ctx->key->num_indirect_patch_outputs) { - - for (uint i = 0; i < ctx->num_outputs; ++i) { - if (ctx->outputs[i].name == TGSI_SEMANTIC_PATCH) { - ctx->outputs[i].glsl_predefined_no_emit = true; - if (ctx->outputs[i].sid < ctx->patch_output_range.io.sid || - ctx->patch_output_range.used == false) { - ctx->patch_output_range.io.first = i; - ctx->patch_output_range.io.name = TGSI_SEMANTIC_PATCH; - ctx->patch_output_range.io.sid = ctx->outputs[i].sid; - ctx->patch_output_range.used = true; - } - if (ctx->outputs[i].sid > ctx->patch_output_range.io.last) { - ctx->patch_output_range.io.last = ctx->outputs[i].sid; - } - } - - if (ctx->outputs[i].name == TGSI_SEMANTIC_GENERIC) { - ctx->outputs[i].glsl_predefined_no_emit = true; - if (ctx->outputs[i].sid < ctx->generic_output_range.io.sid || - ctx->generic_output_range.used == false) { - ctx->generic_output_range.io.sid = ctx->outputs[i].sid; - ctx->generic_output_range.io.first = i; - ctx->generic_output_range.io.name = TGSI_SEMANTIC_GENERIC; - ctx->generic_output_range.used = true; - ctx->generic_output_range.io.usage_mask = 0xf; - ctx->generic_output_range.io.num_components = 4; - } - if (ctx->outputs[i].sid > ctx->generic_output_range.io.last) { - ctx->generic_output_range.io.last = ctx->outputs[i].sid; - } - } - } - snprintf(ctx->patch_output_range.io.glsl_name, 64, "%s_p%d", - get_stage_output_name_prefix(ctx->prog_type), - ctx->patch_output_range.io.sid); - snprintf(ctx->generic_output_range.io.glsl_name, 64, "%s_g%d", - get_stage_output_name_prefix(ctx->prog_type), - ctx->generic_output_range.io.sid); - - ctx->generic_output_range.io.num_components = 4; - ctx->generic_output_range.io.usage_mask = 0xf; - ctx->generic_output_range.io.swizzle_offset = 0; - - ctx->patch_output_range.io.num_components = 4; - ctx->patch_output_range.io.usage_mask = 0xf; - ctx->patch_output_range.io.swizzle_offset = 0; - - if (prefer_generic_io_block(ctx, io_out)) - require_glsl_ver(ctx, 150); - } -} - -static void rename_variables(unsigned nio, struct vrend_shader_io *io, - const char *name_prefix, unsigned coord_replace) { - /* Rename the generic and patch variables after applying all identifications - */ - for (unsigned i = 0; i < nio; ++i) { - if ((io[i].name != TGSI_SEMANTIC_GENERIC && - io[i].name != TGSI_SEMANTIC_PATCH) || - (coord_replace & (1 << io[i].sid))) - continue; - char io_type = io[i].name == TGSI_SEMANTIC_GENERIC ? 'g' : 'p'; - snprintf(io[i].glsl_name, 64, "%s_%c%dA%d_%x", name_prefix, io_type, - io[i].sid, io[i].array_id, io[i].usage_mask); - } -} - -static void rewrite_components(unsigned nio, struct vrend_shader_io *io, - const char *name_prefix, unsigned coord_replace, - bool no_input_arrays) { - if (!nio) - return; - - for (unsigned i = 0; i < nio - 1; ++i) { - if ((io[i].name != TGSI_SEMANTIC_GENERIC && - io[i].name != TGSI_SEMANTIC_PATCH) || - io[i].glsl_predefined_no_emit) - continue; - - for (unsigned j = i + 1; j < nio; ++j) { - if ((io[j].name != TGSI_SEMANTIC_GENERIC && - io[j].name != TGSI_SEMANTIC_PATCH) || - io[j].glsl_predefined_no_emit) - continue; - if (io[i].first == io[j].first) - io[j].glsl_predefined_no_emit = true; - } - } - - for (unsigned i = 0; i < nio; ++i) { - if ((io[i].name != TGSI_SEMANTIC_GENERIC && - io[i].name != TGSI_SEMANTIC_PATCH) || - !no_input_arrays) - continue; - - io[i].usage_mask = 0xf; - io[i].num_components = 4; - io[i].swizzle_offset = 0; - io[i].override_no_wm = false; - } - - rename_variables(nio, io, name_prefix, coord_replace); -} - -static void rewrite_vs_pos_array(struct dump_ctx *ctx) { - int range_start = 0xffff; - int range_end = 0; - int io_idx = 0; - - for (uint i = 0; i < ctx->num_inputs; ++i) { - if (ctx->inputs[i].name == TGSI_SEMANTIC_POSITION) { - ctx->inputs[i].glsl_predefined_no_emit = true; - if (ctx->inputs[i].first < range_start) { - io_idx = i; - range_start = ctx->inputs[i].first; - } - if (ctx->inputs[i].last > range_end) - range_end = ctx->inputs[i].last; - } - } - - if (range_start != range_end) { - ctx->inputs[io_idx].first = range_start; - ctx->inputs[io_idx].last = range_end; - ctx->inputs[io_idx].glsl_predefined_no_emit = false; - require_glsl_ver(ctx, 150); - } -} - -static void emit_fs_clipdistance_load(struct dump_ctx *ctx) { - int i; - - if (!ctx->fs_uses_clipdist_input) - return; - - int prev_num = - ctx->key->prev_stage_num_clip_out + ctx->key->prev_stage_num_cull_out; - int ndists; - const char *prefix = ""; - - if (ctx->prog_type == PIPE_SHADER_TESS_CTRL) - prefix = "gl_out[gl_InvocationID]."; - - ndists = ctx->num_in_clip_dist; - if (prev_num > 0) - ndists = prev_num; - - for (i = 0; i < ndists; i++) { - int clipidx = i < 4 ? 0 : 1; - char swiz = i & 3; - char wm = 0; - switch (swiz) { - default: - case 0: - wm = 'x'; - break; - case 1: - wm = 'y'; - break; - case 2: - wm = 'z'; - break; - case 3: - wm = 'w'; - break; - } - bool is_cull = false; - if (prev_num > 0) { - if (i >= ctx->key->prev_stage_num_clip_out && i < prev_num) - is_cull = true; - } - const char *clip_cull = is_cull ? "Cull" : "Clip"; - emit_buff(ctx, "clip_dist_temp[%d].%c = %sgl_%sDistance[%d];\n", clipidx, - wm, prefix, clip_cull, - is_cull ? i - ctx->key->prev_stage_num_clip_out : i); - } -} - -/* TGSI possibly emits VS, TES, TCS, and GEOM outputs with layouts (i.e. - * it gives components), but it doesn't do so for the corresponding inputs from - * TXS, GEOM, abd TES, so that we have to apply the output layouts from the - * previous shader stage to the according inputs. - */ - -static bool apply_prev_layout(struct dump_ctx *ctx) { - bool require_enhanced_layouts = false; - - /* Walk through all inputs and see whether we have a corresonding output from - * the previous shader that uses a different layout. It may even be that one - * input be the combination of two inputs. */ - - for (unsigned i = 0; i < ctx->num_inputs; ++i) { - unsigned i_input = i; - struct vrend_shader_io *io = &ctx->inputs[i]; - - if (io->name == TGSI_SEMANTIC_GENERIC || io->name == TGSI_SEMANTIC_PATCH) { - - struct vrend_layout_info *layout = - ctx->key->prev_stage_generic_and_patch_outputs_layout; - for (unsigned generic_index = 0; - generic_index < ctx->key->num_prev_generic_and_patch_outputs; - ++generic_index, ++layout) { - - bool already_found_one = false; - - /* Identify by sid and arrays_id */ - if (io->sid == layout->sid && (io->array_id == layout->array_id)) { - unsigned new_mask = io->usage_mask; - - /* We have already one IO with the same SID and arrays ID, so we need - * to duplicate it */ - if (already_found_one) { - memmove(io + 1, io, - (ctx->num_inputs - i_input) * - sizeof(struct vrend_shader_io)); - ctx->num_inputs++; - ++io; - ++i_input; - - } else if ((io->usage_mask == 0xf) && (layout->usage_mask != 0xf)) { - /* If we found the first input with all components, and a - * corresponding prev output that uses less components */ - already_found_one = true; - } - - if (already_found_one) { - new_mask = io->usage_mask = (uint8_t)layout->usage_mask; - io->layout_location = layout->location; - io->array_id = layout->array_id; - - u_bit_scan_consecutive_range(&new_mask, &io->swizzle_offset, - &io->num_components); - require_enhanced_layouts |= io->swizzle_offset > 0; - if (io->num_components == 1) - io->override_no_wm = true; - if (i_input < ctx->num_inputs - 1) { - already_found_one = (io[1].sid != layout->sid || - io[1].array_id != layout->array_id); - } - } - } - } - } - ++io; - ++i_input; - } - return require_enhanced_layouts; -} - -static bool evaluate_layout_overlays(unsigned nio, struct vrend_shader_io *io, - const char *name_prefix, - unsigned coord_replace) { - bool require_enhanced_layouts = 0; - int next_loc = 1; - - /* IO elements may be emitted for the same location but with - * non-overlapping swizzles, therefore, we modify the name of - * the variable to include the swizzle mask. - * - * Since TGSI also emits inputs that have no masks but are still at the - * same location, we also need to add an array ID. - */ - - for (unsigned i = 0; i < nio - 1; ++i) { - if ((io[i].name != TGSI_SEMANTIC_GENERIC && - io[i].name != TGSI_SEMANTIC_PATCH) || - io[i].usage_mask == 0xf || io[i].layout_location > 0) - continue; - - for (unsigned j = i + 1; j < nio; ++j) { - if ((io[j].name != TGSI_SEMANTIC_GENERIC && - io[j].name != TGSI_SEMANTIC_PATCH) || - io[j].usage_mask == 0xf || io[j].layout_location > 0) - continue; - - /* Do the definition ranges overlap? */ - if (io[i].last < io[j].first || io[i].first > io[j].last) - continue; - - /* Overlapping ranges require explicite layouts and if they start at the - * same index thet location must be equal */ - if (io[i].first == io[j].first) { - io[j].layout_location = io[i].layout_location = next_loc++; - } else { - io[i].layout_location = next_loc++; - io[j].layout_location = next_loc++; - } - require_enhanced_layouts = true; - } - } - - rename_variables(nio, io, name_prefix, coord_replace); - - return require_enhanced_layouts; -} - -static void renumber_io_arrays(unsigned nio, struct vrend_shader_io *io) { - int next_array_id = 1; - for (unsigned i = 0; i < nio; ++i) { - if (io[i].name != TGSI_SEMANTIC_GENERIC && - io[i].name != TGSI_SEMANTIC_PATCH) - continue; - if (io[i].array_id > 0) - io[i].array_id = next_array_id++; - } -} - -static void handle_io_arrays(struct dump_ctx *ctx) { - bool require_enhanced_layouts = false; - - /* If the guest sent real IO arrays then we declare them individually, - * and have to do some work to deal with overlapping values, regions and - * enhanced layouts */ - if (ctx->guest_sent_io_arrays) { - - /* Array ID numbering is not ordered accross shaders, so do - * some renumbering for generics and patches. */ - renumber_io_arrays(ctx->num_inputs, ctx->inputs); - renumber_io_arrays(ctx->num_outputs, ctx->outputs); - } - - /* In these shaders the inputs don't have the layout component information - * therefore, copy the info from the prev shaders output */ - if (ctx->prog_type == TGSI_PROCESSOR_GEOMETRY || - ctx->prog_type == TGSI_PROCESSOR_TESS_CTRL || - ctx->prog_type == TGSI_PROCESSOR_TESS_EVAL) - require_enhanced_layouts |= apply_prev_layout(ctx); - - if (ctx->guest_sent_io_arrays) { - if (ctx->num_inputs > 0) - if (evaluate_layout_overlays( - ctx->num_inputs, ctx->inputs, - get_stage_input_name_prefix(ctx, ctx->prog_type), - ctx->key->coord_replace)) { - require_enhanced_layouts = true; - } - - if (ctx->num_outputs > 0) - if (evaluate_layout_overlays(ctx->num_outputs, ctx->outputs, - get_stage_output_name_prefix(ctx->prog_type), - 0)) { - require_enhanced_layouts = true; - } - - } else { - /* The guest didn't send real arrays, do we might have to add a big array - * for all generic and another ofr patch inputs */ - rewrite_io_ranged(ctx); - rewrite_components(ctx->num_inputs, ctx->inputs, - get_stage_input_name_prefix(ctx, ctx->prog_type), - ctx->key->coord_replace, true); - - rewrite_components(ctx->num_outputs, ctx->outputs, - get_stage_output_name_prefix(ctx->prog_type), 0, true); - } - - if (require_enhanced_layouts) { - ctx->shader_req_bits |= SHADER_REQ_ENHANCED_LAYOUTS; - ctx->shader_req_bits |= SHADER_REQ_SEPERATE_SHADER_OBJECTS; - } -} - -static boolean iter_instruction(struct tgsi_iterate_context *iter, - struct tgsi_full_instruction *inst) { - struct dump_ctx *ctx = (struct dump_ctx *)iter; - struct dest_info dinfo = {0}; - struct source_info sinfo = {0}; - const char *srcs[4]; - char dsts[3][255]; - char fp64_dsts[3][255]; - uint instno = ctx->instno++; - char writemask[6] = ""; - char src_swizzle0[10]; - - sinfo.svec4 = VEC4; - - if (ctx->prog_type == -1) - ctx->prog_type = iter->processor.Processor; - - if (instno == 0) { - handle_io_arrays(ctx); - - /* Vertex shader inputs are not send as arrays, but the access may still be - * indirect. so we have to deal with that */ - if (ctx->prog_type == TGSI_PROCESSOR_VERTEX && - ctx->info.indirect_files & (1 << TGSI_FILE_INPUT)) { - rewrite_vs_pos_array(ctx); - } - - emit_buf(ctx, "void main(void)\n{\n"); - if (iter->processor.Processor == TGSI_PROCESSOR_FRAGMENT) { - emit_color_select(ctx); - if (ctx->fs_uses_clipdist_input) - emit_fs_clipdistance_load(ctx); - } - if (ctx->so) - prepare_so_movs(ctx); - } - - if (!get_destination_info(ctx, inst, &dinfo, dsts, fp64_dsts, writemask)) - return false; - - if (!get_source_info(ctx, inst, &sinfo, ctx->src_bufs, src_swizzle0)) - return false; - - for (size_t i = 0; i < ARRAY_SIZE(srcs); ++i) - srcs[i] = ctx->src_bufs[i].buf; - - switch (inst->Instruction.Opcode) { - case TGSI_OPCODE_SQRT: - case TGSI_OPCODE_DSQRT: - emit_buff(ctx, "%s = sqrt(vec4(%s))%s;\n", dsts[0], srcs[0], writemask); - break; - case TGSI_OPCODE_LRP: - emit_buff(ctx, "%s = mix(vec4(%s), vec4(%s), vec4(%s))%s;\n", dsts[0], - srcs[2], srcs[1], srcs[0], writemask); - break; - case TGSI_OPCODE_DP2: - emit_buff(ctx, "%s = %s(dot(vec2(%s), vec2(%s)));\n", dsts[0], - get_string(dinfo.dstconv), srcs[0], srcs[1]); - break; - case TGSI_OPCODE_DP3: - emit_buff(ctx, "%s = %s(dot(vec3(%s), vec3(%s)));\n", dsts[0], - get_string(dinfo.dstconv), srcs[0], srcs[1]); - break; - case TGSI_OPCODE_DP4: - emit_buff(ctx, "%s = %s(dot(vec4(%s), vec4(%s)));\n", dsts[0], - get_string(dinfo.dstconv), srcs[0], srcs[1]); - break; - case TGSI_OPCODE_DPH: - emit_buff(ctx, "%s = %s(dot(vec4(vec3(%s), 1.0), vec4(%s)));\n", dsts[0], - get_string(dinfo.dstconv), srcs[0], srcs[1]); - break; - case TGSI_OPCODE_MAX: - case TGSI_OPCODE_DMAX: - case TGSI_OPCODE_IMAX: - case TGSI_OPCODE_UMAX: - emit_buff(ctx, "%s = %s(%s(max(%s, %s))%s);\n", dsts[0], - get_string(dinfo.dstconv), get_string(dinfo.dtypeprefix), srcs[0], - srcs[1], writemask); - break; - case TGSI_OPCODE_MIN: - case TGSI_OPCODE_DMIN: - case TGSI_OPCODE_IMIN: - case TGSI_OPCODE_UMIN: - emit_buff(ctx, "%s = %s(%s(min(%s, %s))%s);\n", dsts[0], - get_string(dinfo.dstconv), get_string(dinfo.dtypeprefix), srcs[0], - srcs[1], writemask); - break; - case TGSI_OPCODE_ABS: - case TGSI_OPCODE_IABS: - case TGSI_OPCODE_DABS: - emit_op1("abs"); - break; - case TGSI_OPCODE_KILL_IF: - emit_buff(ctx, "if (any(lessThan(%s, vec4(0.0))))\ndiscard;\n", srcs[0]); - break; - case TGSI_OPCODE_IF: - case TGSI_OPCODE_UIF: - emit_buff(ctx, "if (any(bvec4(%s))) {\n", srcs[0]); - indent_buf(ctx); - break; - case TGSI_OPCODE_ELSE: - outdent_buf(ctx); - emit_buf(ctx, "} else {\n"); - indent_buf(ctx); - break; - case TGSI_OPCODE_ENDIF: - emit_buf(ctx, "}\n"); - outdent_buf(ctx); - break; - case TGSI_OPCODE_KILL: - emit_buff(ctx, "discard;\n"); - break; - case TGSI_OPCODE_DST: - emit_buff(ctx, "%s = vec4(1.0, %s.y * %s.y, %s.z, %s.w);\n", dsts[0], - srcs[0], srcs[1], srcs[0], srcs[1]); - break; - case TGSI_OPCODE_LIT: - emit_buff(ctx, - "%s = %s(vec4(1.0, max(%s.x, 0.0), step(0.0, %s.x) * " - "pow(max(0.0, %s.y), clamp(%s.w, -128.0, 128.0)), 1.0)%s);\n", - dsts[0], get_string(dinfo.dstconv), srcs[0], srcs[0], srcs[0], - srcs[0], writemask); - break; - case TGSI_OPCODE_EX2: - emit_op1("exp2"); - break; - case TGSI_OPCODE_LG2: - emit_op1("log2"); - break; - case TGSI_OPCODE_EXP: - emit_buff(ctx, - "%s = %s(vec4(pow(2.0, floor(%s.x)), %s.x - floor(%s.x), " - "exp2(%s.x), 1.0)%s);\n", - dsts[0], get_string(dinfo.dstconv), srcs[0], srcs[0], srcs[0], - srcs[0], writemask); - break; - case TGSI_OPCODE_LOG: - emit_buff(ctx, - "%s = %s(vec4(floor(log2(%s.x)), %s.x / pow(2.0, " - "floor(log2(%s.x))), log2(%s.x), 1.0)%s);\n", - dsts[0], get_string(dinfo.dstconv), srcs[0], srcs[0], srcs[0], - srcs[0], writemask); - break; - case TGSI_OPCODE_COS: - emit_op1("cos"); - break; - case TGSI_OPCODE_SIN: - emit_op1("sin"); - break; - case TGSI_OPCODE_SCS: - emit_buff(ctx, "%s = %s(vec4(cos(%s.x), sin(%s.x), 0, 1)%s);\n", dsts[0], - get_string(dinfo.dstconv), srcs[0], srcs[0], writemask); - break; - case TGSI_OPCODE_DDX: - emit_op1("dFdx"); - break; - case TGSI_OPCODE_DDY: - emit_op1("dFdy"); - break; - case TGSI_OPCODE_DDX_FINE: - ctx->shader_req_bits |= SHADER_REQ_DERIVATIVE_CONTROL; - emit_op1("dFdxFine"); - break; - case TGSI_OPCODE_DDY_FINE: - ctx->shader_req_bits |= SHADER_REQ_DERIVATIVE_CONTROL; - emit_op1("dFdyFine"); - break; - case TGSI_OPCODE_RCP: - emit_buff(ctx, "%s = %s(1.0/(%s));\n", dsts[0], get_string(dinfo.dstconv), - srcs[0]); - break; - case TGSI_OPCODE_DRCP: - emit_buff(ctx, "%s = %s(1.0LF/(%s));\n", dsts[0], get_string(dinfo.dstconv), - srcs[0]); - break; - case TGSI_OPCODE_FLR: - emit_op1("floor"); - break; - case TGSI_OPCODE_ROUND: - emit_op1("round"); - break; - case TGSI_OPCODE_ISSG: - emit_op1("sign"); - break; - case TGSI_OPCODE_CEIL: - emit_op1("ceil"); - break; - case TGSI_OPCODE_FRC: - case TGSI_OPCODE_DFRAC: - emit_op1("fract"); - break; - case TGSI_OPCODE_TRUNC: - emit_op1("trunc"); - break; - case TGSI_OPCODE_SSG: - emit_op1("sign"); - break; - case TGSI_OPCODE_RSQ: - case TGSI_OPCODE_DRSQ: - emit_buff(ctx, "%s = %s(inversesqrt(%s.x));\n", dsts[0], - get_string(dinfo.dstconv), srcs[0]); - break; - case TGSI_OPCODE_FBFETCH: - case TGSI_OPCODE_MOV: - emit_buff(ctx, "%s = %s(%s(%s%s));\n", dsts[0], get_string(dinfo.dstconv), - get_string(dinfo.dtypeprefix), srcs[0], - sinfo.override_no_wm[0] ? "" : writemask); - break; - case TGSI_OPCODE_ADD: - case TGSI_OPCODE_DADD: - emit_arit_op2("+"); - break; - case TGSI_OPCODE_UADD: - emit_buff(ctx, "%s = %s(%s(ivec4((uvec4(%s) + uvec4(%s))))%s);\n", dsts[0], - get_string(dinfo.dstconv), get_string(dinfo.dtypeprefix), srcs[0], - srcs[1], writemask); - break; - case TGSI_OPCODE_SUB: - emit_arit_op2("-"); - break; - case TGSI_OPCODE_MUL: - case TGSI_OPCODE_DMUL: - emit_arit_op2("*"); - break; - case TGSI_OPCODE_DIV: - case TGSI_OPCODE_DDIV: - emit_arit_op2("/"); - break; - case TGSI_OPCODE_UMUL: - emit_buff(ctx, "%s = %s(%s((uvec4(%s) * uvec4(%s)))%s);\n", dsts[0], - get_string(dinfo.dstconv), get_string(dinfo.dtypeprefix), srcs[0], - srcs[1], writemask); - break; - case TGSI_OPCODE_UMOD: - emit_buff(ctx, "%s = %s(%s((uvec4(%s) %% uvec4(%s)))%s);\n", dsts[0], - get_string(dinfo.dstconv), get_string(dinfo.dtypeprefix), srcs[0], - srcs[1], writemask); - break; - case TGSI_OPCODE_IDIV: - emit_buff(ctx, "%s = %s(%s((ivec4(%s) / ivec4(%s)))%s);\n", dsts[0], - get_string(dinfo.dstconv), get_string(dinfo.dtypeprefix), srcs[0], - srcs[1], writemask); - break; - case TGSI_OPCODE_UDIV: - emit_buff(ctx, "%s = %s(%s((uvec4(%s) / uvec4(%s)))%s);\n", dsts[0], - get_string(dinfo.dstconv), get_string(dinfo.dtypeprefix), srcs[0], - srcs[1], writemask); - break; - case TGSI_OPCODE_ISHR: - case TGSI_OPCODE_USHR: - emit_arit_op2(">>"); - break; - case TGSI_OPCODE_SHL: - emit_arit_op2("<<"); - break; - case TGSI_OPCODE_MAD: - emit_buff(ctx, "%s = %s((%s * %s + %s)%s);\n", dsts[0], - get_string(dinfo.dstconv), srcs[0], srcs[1], srcs[2], writemask); - break; - case TGSI_OPCODE_UMAD: - case TGSI_OPCODE_DMAD: - emit_buff(ctx, "%s = %s(%s((%s * %s + %s)%s));\n", dsts[0], - get_string(dinfo.dstconv), get_string(dinfo.dtypeprefix), srcs[0], - srcs[1], srcs[2], writemask); - break; - case TGSI_OPCODE_OR: - emit_arit_op2("|"); - break; - case TGSI_OPCODE_AND: - emit_arit_op2("&"); - break; - case TGSI_OPCODE_XOR: - emit_arit_op2("^"); - break; - case TGSI_OPCODE_MOD: - emit_arit_op2("%"); - break; - case TGSI_OPCODE_TEX: - case TGSI_OPCODE_TEX2: - case TGSI_OPCODE_TXB: - case TGSI_OPCODE_TXL: - case TGSI_OPCODE_TXB2: - case TGSI_OPCODE_TXL2: - case TGSI_OPCODE_TXD: - case TGSI_OPCODE_TXF: - case TGSI_OPCODE_TG4: - case TGSI_OPCODE_TXP: - case TGSI_OPCODE_LODQ: - translate_tex(ctx, inst, &sinfo, &dinfo, srcs, dsts[0], writemask); - break; - case TGSI_OPCODE_TXQ: - emit_txq(ctx, inst, sinfo.sreg_index, srcs, dsts[0], writemask); - break; - case TGSI_OPCODE_TXQS: - emit_txqs(ctx, inst, sinfo.sreg_index, srcs, dsts[0]); - break; - case TGSI_OPCODE_I2F: - emit_buff(ctx, "%s = %s(ivec4(%s)%s);\n", dsts[0], - get_string(dinfo.dstconv), srcs[0], writemask); - break; - case TGSI_OPCODE_I2D: - emit_buff(ctx, "%s = %s(ivec4(%s));\n", dsts[0], get_string(dinfo.dstconv), - srcs[0]); - break; - case TGSI_OPCODE_D2F: - emit_buff(ctx, "%s = %s(%s);\n", dsts[0], get_string(dinfo.dstconv), - srcs[0]); - break; - case TGSI_OPCODE_U2F: - emit_buff(ctx, "%s = %s(uvec4(%s)%s);\n", dsts[0], - get_string(dinfo.dstconv), srcs[0], writemask); - break; - case TGSI_OPCODE_U2D: - emit_buff(ctx, "%s = %s(uvec4(%s));\n", dsts[0], get_string(dinfo.dstconv), - srcs[0]); - break; - case TGSI_OPCODE_F2I: - emit_buff(ctx, "%s = %s(%s(ivec4(%s))%s);\n", dsts[0], - get_string(dinfo.dstconv), get_string(dinfo.dtypeprefix), srcs[0], - writemask); - break; - case TGSI_OPCODE_D2I: - emit_buff(ctx, "%s = %s(%s(%s(%s)));\n", dsts[0], get_string(dinfo.dstconv), - get_string(dinfo.dtypeprefix), get_string(dinfo.idstconv), - srcs[0]); - break; - case TGSI_OPCODE_F2U: - emit_buff(ctx, "%s = %s(%s(uvec4(%s))%s);\n", dsts[0], - get_string(dinfo.dstconv), get_string(dinfo.dtypeprefix), srcs[0], - writemask); - break; - case TGSI_OPCODE_D2U: - emit_buff(ctx, "%s = %s(%s(%s(%s)));\n", dsts[0], get_string(dinfo.dstconv), - get_string(dinfo.dtypeprefix), get_string(dinfo.udstconv), - srcs[0]); - break; - case TGSI_OPCODE_F2D: - emit_buff(ctx, "%s = %s(%s(%s));\n", dsts[0], get_string(dinfo.dstconv), - get_string(dinfo.dtypeprefix), srcs[0]); - break; - case TGSI_OPCODE_NOT: - emit_buff(ctx, "%s = %s(uintBitsToFloat(~(uvec4(%s))));\n", dsts[0], - get_string(dinfo.dstconv), srcs[0]); - break; - case TGSI_OPCODE_INEG: - emit_buff(ctx, "%s = %s(intBitsToFloat(-(ivec4(%s))));\n", dsts[0], - get_string(dinfo.dstconv), srcs[0]); - break; - case TGSI_OPCODE_DNEG: - emit_buff(ctx, "%s = %s(-%s);\n", dsts[0], get_string(dinfo.dstconv), - srcs[0]); - break; - case TGSI_OPCODE_SEQ: - emit_compare("equal"); - break; - case TGSI_OPCODE_USEQ: - case TGSI_OPCODE_FSEQ: - case TGSI_OPCODE_DSEQ: - if (inst->Instruction.Opcode == TGSI_OPCODE_DSEQ) - strcpy(writemask, ".x"); - emit_ucompare("equal"); - break; - case TGSI_OPCODE_SLT: - emit_compare("lessThan"); - break; - case TGSI_OPCODE_ISLT: - case TGSI_OPCODE_USLT: - case TGSI_OPCODE_FSLT: - case TGSI_OPCODE_DSLT: - if (inst->Instruction.Opcode == TGSI_OPCODE_DSLT) - strcpy(writemask, ".x"); - emit_ucompare("lessThan"); - break; - case TGSI_OPCODE_SNE: - emit_compare("notEqual"); - break; - case TGSI_OPCODE_USNE: - case TGSI_OPCODE_FSNE: - case TGSI_OPCODE_DSNE: - if (inst->Instruction.Opcode == TGSI_OPCODE_DSNE) - strcpy(writemask, ".x"); - emit_ucompare("notEqual"); - break; - case TGSI_OPCODE_SGE: - emit_compare("greaterThanEqual"); - break; - case TGSI_OPCODE_ISGE: - case TGSI_OPCODE_USGE: - case TGSI_OPCODE_FSGE: - case TGSI_OPCODE_DSGE: - if (inst->Instruction.Opcode == TGSI_OPCODE_DSGE) - strcpy(writemask, ".x"); - emit_ucompare("greaterThanEqual"); - break; - case TGSI_OPCODE_POW: - emit_buff(ctx, "%s = %s(pow(%s, %s));\n", dsts[0], - get_string(dinfo.dstconv), srcs[0], srcs[1]); - break; - case TGSI_OPCODE_CMP: - emit_buff(ctx, "%s = mix(%s, %s, greaterThanEqual(%s, vec4(0.0)))%s;\n", - dsts[0], srcs[1], srcs[2], srcs[0], writemask); - break; - case TGSI_OPCODE_UCMP: - emit_buff( - ctx, "%s = mix(%s, %s, notEqual(floatBitsToUint(%s), uvec4(0.0)))%s;\n", - dsts[0], srcs[2], srcs[1], srcs[0], writemask); - break; - case TGSI_OPCODE_END: - if (iter->processor.Processor == TGSI_PROCESSOR_VERTEX) { - handle_vertex_proc_exit(ctx); - } else if (iter->processor.Processor == TGSI_PROCESSOR_TESS_CTRL) { - emit_clip_dist_movs(ctx); - } else if (iter->processor.Processor == TGSI_PROCESSOR_TESS_EVAL) { - if (ctx->so && !ctx->key->gs_present) - emit_so_movs(ctx); - emit_clip_dist_movs(ctx); - if (!ctx->key->gs_present) { - emit_prescale(ctx); - } - } else if (iter->processor.Processor == TGSI_PROCESSOR_FRAGMENT) { - handle_fragment_proc_exit(ctx); - } - emit_buf(ctx, "}\n"); - break; - case TGSI_OPCODE_RET: - if (iter->processor.Processor == TGSI_PROCESSOR_VERTEX) { - handle_vertex_proc_exit(ctx); - } else if (iter->processor.Processor == TGSI_PROCESSOR_FRAGMENT) { - handle_fragment_proc_exit(ctx); - } - emit_buf(ctx, "return;\n"); - break; - case TGSI_OPCODE_ARL: - emit_buff(ctx, "%s = int(floor(%s)%s);\n", dsts[0], srcs[0], writemask); - break; - case TGSI_OPCODE_UARL: - emit_buff(ctx, "%s = int(%s);\n", dsts[0], srcs[0]); - break; - case TGSI_OPCODE_XPD: - emit_buff(ctx, "%s = %s(cross(vec3(%s), vec3(%s)));\n", dsts[0], - get_string(dinfo.dstconv), srcs[0], srcs[1]); - break; - case TGSI_OPCODE_BGNLOOP: - emit_buf(ctx, "do {\n"); - indent_buf(ctx); - break; - case TGSI_OPCODE_ENDLOOP: - outdent_buf(ctx); - emit_buf(ctx, "} while(true);\n"); - break; - case TGSI_OPCODE_BRK: - emit_buf(ctx, "break;\n"); - break; - case TGSI_OPCODE_EMIT: { - struct immed *imd = &ctx->imm[(inst->Src[0].Register.Index)]; - if (ctx->so && ctx->key->gs_present) - emit_so_movs(ctx); - emit_clip_dist_movs(ctx); - emit_prescale(ctx); - if (imd->val[inst->Src[0].Register.SwizzleX].ui > 0) { - ctx->shader_req_bits |= SHADER_REQ_GPU_SHADER5; - emit_buff(ctx, "EmitStreamVertex(%d);\n", - imd->val[inst->Src[0].Register.SwizzleX].ui); - } else - emit_buf(ctx, "EmitVertex();\n"); - break; - } - case TGSI_OPCODE_ENDPRIM: { - struct immed *imd = &ctx->imm[(inst->Src[0].Register.Index)]; - if (imd->val[inst->Src[0].Register.SwizzleX].ui > 0) { - ctx->shader_req_bits |= SHADER_REQ_GPU_SHADER5; - emit_buff(ctx, "EndStreamPrimitive(%d);\n", - imd->val[inst->Src[0].Register.SwizzleX].ui); - } else - emit_buf(ctx, "EndPrimitive();\n"); - break; - } - case TGSI_OPCODE_INTERP_CENTROID: - emit_buff(ctx, "%s = %s(%s(vec4(interpolateAtCentroid(%s)%s)));\n", dsts[0], - get_string(dinfo.dstconv), get_string(dinfo.dtypeprefix), srcs[0], - src_swizzle0); - ctx->shader_req_bits |= SHADER_REQ_GPU_SHADER5; - break; - case TGSI_OPCODE_INTERP_SAMPLE: - emit_buff(ctx, "%s = %s(%s(vec4(interpolateAtSample(%s, %s.x)%s)));\n", - dsts[0], get_string(dinfo.dstconv), get_string(dinfo.dtypeprefix), - srcs[0], srcs[1], src_swizzle0); - ctx->shader_req_bits |= SHADER_REQ_GPU_SHADER5; - break; - case TGSI_OPCODE_INTERP_OFFSET: - emit_buff(ctx, "%s = %s(%s(vec4(interpolateAtOffset(%s, %s.xy)%s)));\n", - dsts[0], get_string(dinfo.dstconv), get_string(dinfo.dtypeprefix), - srcs[0], srcs[1], src_swizzle0); - ctx->shader_req_bits |= SHADER_REQ_GPU_SHADER5; - break; - case TGSI_OPCODE_UMUL_HI: - emit_buff(ctx, "umulExtended(%s, %s, umul_temp, mul_utemp);\n", srcs[0], - srcs[1]); - emit_buff(ctx, "%s = %s(%s(umul_temp%s));\n", dsts[0], - get_string(dinfo.dstconv), get_string(dinfo.dtypeprefix), - writemask); - ctx->write_mul_utemp = true; - break; - case TGSI_OPCODE_IMUL_HI: - emit_buff(ctx, "imulExtended(%s, %s, imul_temp, mul_itemp);\n", srcs[0], - srcs[1]); - emit_buff(ctx, "%s = %s(%s(imul_temp%s));\n", dsts[0], - get_string(dinfo.dstconv), get_string(dinfo.dtypeprefix), - writemask); - ctx->write_mul_itemp = true; - break; - - case TGSI_OPCODE_IBFE: - emit_buff(ctx, "%s = %s(%s(bitfieldExtract(%s, int(%s.x), int(%s.x))));\n", - dsts[0], get_string(dinfo.dstconv), get_string(dinfo.dtypeprefix), - srcs[0], srcs[1], srcs[2]); - ctx->shader_req_bits |= SHADER_REQ_GPU_SHADER5; - break; - case TGSI_OPCODE_UBFE: - emit_buff(ctx, "%s = %s(%s(bitfieldExtract(%s, int(%s.x), int(%s.x))));\n", - dsts[0], get_string(dinfo.dstconv), get_string(dinfo.dtypeprefix), - srcs[0], srcs[1], srcs[2]); - ctx->shader_req_bits |= SHADER_REQ_GPU_SHADER5; - break; - case TGSI_OPCODE_BFI: - emit_buff( - ctx, - "%s = %s(uintBitsToFloat(bitfieldInsert(%s, %s, int(%s), int(%s))));\n", - dsts[0], get_string(dinfo.dstconv), srcs[0], srcs[1], srcs[2], srcs[3]); - ctx->shader_req_bits |= SHADER_REQ_GPU_SHADER5; - break; - case TGSI_OPCODE_BREV: - emit_buff(ctx, "%s = %s(%s(bitfieldReverse(%s)));\n", dsts[0], - get_string(dinfo.dstconv), get_string(dinfo.dtypeprefix), - srcs[0]); - ctx->shader_req_bits |= SHADER_REQ_GPU_SHADER5; - break; - case TGSI_OPCODE_POPC: - emit_buff(ctx, "%s = %s(%s(bitCount(%s)));\n", dsts[0], - get_string(dinfo.dstconv), get_string(dinfo.dtypeprefix), - srcs[0]); - ctx->shader_req_bits |= SHADER_REQ_GPU_SHADER5; - break; - case TGSI_OPCODE_LSB: - emit_buff(ctx, "%s = %s(%s(findLSB(%s)));\n", dsts[0], - get_string(dinfo.dstconv), get_string(dinfo.dtypeprefix), - srcs[0]); - ctx->shader_req_bits |= SHADER_REQ_GPU_SHADER5; - break; - case TGSI_OPCODE_IMSB: - case TGSI_OPCODE_UMSB: - emit_buff(ctx, "%s = %s(%s(findMSB(%s)));\n", dsts[0], - get_string(dinfo.dstconv), get_string(dinfo.dtypeprefix), - srcs[0]); - ctx->shader_req_bits |= SHADER_REQ_GPU_SHADER5; - break; - case TGSI_OPCODE_BARRIER: - emit_buf(ctx, "barrier();\n"); - break; - case TGSI_OPCODE_MEMBAR: { - struct immed *imd = &ctx->imm[(inst->Src[0].Register.Index)]; - uint32_t val = imd->val[inst->Src[0].Register.SwizzleX].ui; - uint32_t all_val = (TGSI_MEMBAR_SHADER_BUFFER | TGSI_MEMBAR_ATOMIC_BUFFER | - TGSI_MEMBAR_SHADER_IMAGE | TGSI_MEMBAR_SHARED); - - if (val & TGSI_MEMBAR_THREAD_GROUP) { - emit_buf(ctx, "groupMemoryBarrier();\n"); - } else { - if ((val & all_val) == all_val) { - emit_buf(ctx, "memoryBarrier();\n"); - ctx->shader_req_bits |= SHADER_REQ_IMAGE_LOAD_STORE; - } else { - if (val & TGSI_MEMBAR_SHADER_BUFFER) { - emit_buf(ctx, "memoryBarrierBuffer();\n"); - } - if (val & TGSI_MEMBAR_ATOMIC_BUFFER) { - emit_buf(ctx, "memoryBarrierAtomic();\n"); - } - if (val & TGSI_MEMBAR_SHADER_IMAGE) { - emit_buf(ctx, "memoryBarrierImage();\n"); - } - if (val & TGSI_MEMBAR_SHARED) { - emit_buf(ctx, "memoryBarrierShared();\n"); - } - } - } - break; - } - case TGSI_OPCODE_STORE: - if (!rewrite_1d_image_coordinate(ctx->src_bufs + 1, inst)) - return false; - srcs[1] = ctx->src_bufs[1].buf; - translate_store(ctx, inst, &sinfo, srcs, dsts[0]); - break; - case TGSI_OPCODE_LOAD: - if (!rewrite_1d_image_coordinate(ctx->src_bufs + 1, inst)) - return false; - srcs[1] = ctx->src_bufs[1].buf; - translate_load(ctx, inst, &sinfo, &dinfo, srcs, dsts[0], writemask); - break; - case TGSI_OPCODE_ATOMUADD: - case TGSI_OPCODE_ATOMXCHG: - case TGSI_OPCODE_ATOMCAS: - case TGSI_OPCODE_ATOMAND: - case TGSI_OPCODE_ATOMOR: - case TGSI_OPCODE_ATOMXOR: - case TGSI_OPCODE_ATOMUMIN: - case TGSI_OPCODE_ATOMUMAX: - case TGSI_OPCODE_ATOMIMIN: - case TGSI_OPCODE_ATOMIMAX: - if (!rewrite_1d_image_coordinate(ctx->src_bufs + 1, inst)) - return false; - srcs[1] = ctx->src_bufs[1].buf; - translate_atomic(ctx, inst, &sinfo, srcs, dsts[0]); - break; - case TGSI_OPCODE_RESQ: - translate_resq(ctx, inst, srcs, dsts[0], writemask); - break; - case TGSI_OPCODE_CLOCK: - ctx->shader_req_bits |= SHADER_REQ_SHADER_CLOCK; - emit_buff(ctx, "%s = uintBitsToFloat(clock2x32ARB());\n", dsts[0]); - break; - } - - for (uint32_t i = 0; i < 1; i++) { - enum tgsi_opcode_type dtype = - tgsi_opcode_infer_dst_type(inst->Instruction.Opcode); - if (dtype == TGSI_TYPE_DOUBLE) { - emit_buff(ctx, "%s = uintBitsToFloat(unpackDouble2x32(%s));\n", - fp64_dsts[0], dsts[0]); - } - } - if (inst->Instruction.Saturate) { - emit_buff(ctx, "%s = clamp(%s, 0.0, 1.0);\n", dsts[0], dsts[0]); - } - - if (strbuf_get_error(&ctx->glsl_main)) - return false; - return true; -} - -static boolean prolog(struct tgsi_iterate_context *iter) { - struct dump_ctx *ctx = (struct dump_ctx *)iter; - - if (ctx->prog_type == -1) - ctx->prog_type = iter->processor.Processor; - - if (iter->processor.Processor == TGSI_PROCESSOR_VERTEX && - ctx->key->gs_present) - require_glsl_ver(ctx, 150); - - return true; -} - -static void emit_ext(struct dump_ctx *ctx, const char *name, const char *verb) { - emit_ver_extf(ctx, "#extension GL_%s : %s\n", name, verb); -} - -static void emit_header(struct dump_ctx *ctx) { - emit_ver_extf(ctx, "#version %d es\n", ctx->cfg->glsl_version); - - if ((ctx->shader_req_bits & SHADER_REQ_CLIP_DISTANCE) || - (ctx->num_clip_dist == 0 && ctx->key->clip_plane_enable)) { - emit_ext(ctx, "EXT_clip_cull_distance", "require"); - } - - if (ctx->shader_req_bits & SHADER_REQ_SAMPLER_MS) - emit_ext(ctx, "OES_texture_storage_multisample_2d_array", "require"); - - if (ctx->shader_req_bits & SHADER_REQ_CONSERVATIVE_DEPTH) - emit_ext(ctx, "EXT_conservative_depth", "require"); - - if (ctx->prog_type == TGSI_PROCESSOR_FRAGMENT) { - if (ctx->shader_req_bits & SHADER_REQ_FBFETCH) - emit_ext(ctx, "EXT_shader_framebuffer_fetch", "require"); - } - - if (ctx->shader_req_bits & SHADER_REQ_VIEWPORT_IDX) - emit_ext(ctx, "OES_viewport_array", "require"); - - if (ctx->prog_type == TGSI_PROCESSOR_GEOMETRY) { - emit_ext(ctx, "EXT_geometry_shader", "require"); - if (ctx->shader_req_bits & SHADER_REQ_PSIZE) - emit_ext(ctx, "OES_geometry_point_size", "enable"); - } - - if (ctx->shader_req_bits & SHADER_REQ_NV_IMAGE_FORMATS) - emit_ext(ctx, "NV_image_formats", "require"); - - if ((ctx->prog_type == TGSI_PROCESSOR_TESS_CTRL || - ctx->prog_type == TGSI_PROCESSOR_TESS_EVAL)) { - if (ctx->cfg->glsl_version < 320) - emit_ext(ctx, "OES_tessellation_shader", "require"); - emit_ext(ctx, "OES_tessellation_point_size", "enable"); - } - - if (ctx->cfg->glsl_version < 320) { - if (ctx->shader_req_bits & SHADER_REQ_SAMPLE_SHADING) - emit_ext(ctx, "OES_sample_variables", "require"); - if (ctx->shader_req_bits & SHADER_REQ_GPU_SHADER5) { - emit_ext(ctx, "OES_gpu_shader5", "require"); - emit_ext(ctx, "OES_shader_multisample_interpolation", "require"); - } - if (ctx->shader_req_bits & SHADER_REQ_CUBE_ARRAY) - emit_ext(ctx, "OES_texture_cube_map_array", "require"); - if (ctx->shader_req_bits & SHADER_REQ_LAYER) - emit_ext(ctx, "EXT_geometry_shader", "require"); - if (ctx->shader_req_bits & SHADER_REQ_IMAGE_ATOMIC) - emit_ext(ctx, "OES_shader_image_atomic", "require"); - } - - if (logiop_require_inout(ctx->key)) { - if (ctx->key->fs_logicop_emulate_coherent) - emit_ext(ctx, "EXT_shader_framebuffer_fetch", "require"); - else - emit_ext(ctx, "EXT_shader_framebuffer_fetch_non_coherent", "require"); - } - - if (ctx->shader_req_bits & SHADER_REQ_LODQ) - emit_ext(ctx, "EXT_texture_query_lod", "require"); - - emit_hdr(ctx, "precision highp float;\n"); - emit_hdr(ctx, "precision highp int;\n"); -} - -char vrend_shader_samplerreturnconv(enum tgsi_return_type type) { - switch (type) { - case TGSI_RETURN_TYPE_SINT: - return 'i'; - case TGSI_RETURN_TYPE_UINT: - return 'u'; - default: - return ' '; - } -} - -const char *vrend_shader_samplertypeconv(int sampler_type) { - switch (sampler_type) { - case TGSI_TEXTURE_BUFFER: - return "Buffer"; - case TGSI_TEXTURE_1D: - /* fallthrough */ - case TGSI_TEXTURE_2D: - return "2D"; - case TGSI_TEXTURE_3D: - return "3D"; - case TGSI_TEXTURE_CUBE: - return "Cube"; - case TGSI_TEXTURE_RECT: - return "2D"; - case TGSI_TEXTURE_SHADOW1D: - /* fallthrough */ - case TGSI_TEXTURE_SHADOW2D: - return "2DShadow"; - case TGSI_TEXTURE_SHADOWRECT: - return "2DShadow"; - case TGSI_TEXTURE_1D_ARRAY: - /* fallthrough */ - case TGSI_TEXTURE_2D_ARRAY: - return "2DArray"; - case TGSI_TEXTURE_SHADOW1D_ARRAY: - /* fallthrough */ - case TGSI_TEXTURE_SHADOW2D_ARRAY: - return "2DArrayShadow"; - case TGSI_TEXTURE_SHADOWCUBE: - return "CubeShadow"; - case TGSI_TEXTURE_CUBE_ARRAY: - return "CubeArray"; - case TGSI_TEXTURE_SHADOWCUBE_ARRAY: - return "CubeArrayShadow"; - case TGSI_TEXTURE_2D_MSAA: - return "2DMS"; - case TGSI_TEXTURE_2D_ARRAY_MSAA: - return "2DMSArray"; - default: - return NULL; - } -} - -static const char *get_interp_string(struct vrend_shader_cfg *cfg, - int interpolate, bool flatshade) { - switch (interpolate) { - case TGSI_INTERPOLATE_LINEAR: - return ""; - case TGSI_INTERPOLATE_PERSPECTIVE: - return "smooth "; - case TGSI_INTERPOLATE_CONSTANT: - return "flat "; - case TGSI_INTERPOLATE_COLOR: - if (flatshade) - return "flat "; - /* fallthrough */ - default: - return NULL; - } -} - -static const char *get_aux_string(unsigned location) { - switch (location) { - case TGSI_INTERPOLATE_LOC_CENTER: - default: - return ""; - case TGSI_INTERPOLATE_LOC_CENTROID: - return "centroid "; - case TGSI_INTERPOLATE_LOC_SAMPLE: - return "sample "; - } -} - -static void emit_sampler_decl(struct dump_ctx *ctx, uint32_t i, uint32_t range, - const struct vrend_shader_sampler *sampler) { - char ptc; - bool is_shad; - const char *sname, *precision, *stc; - - sname = tgsi_proc_to_prefix(ctx->prog_type); - - precision = "highp"; - - ptc = vrend_shader_samplerreturnconv(sampler->tgsi_sampler_return); - stc = vrend_shader_samplertypeconv(sampler->tgsi_sampler_type); - is_shad = samplertype_is_shadow(sampler->tgsi_sampler_type); - - if (range) - emit_hdrf(ctx, "uniform %s %csampler%s %ssamp%d[%d];\n", precision, ptc, - stc, sname, i, range); - else - emit_hdrf(ctx, "uniform %s %csampler%s %ssamp%d;\n", precision, ptc, stc, - sname, i); - - if (is_shad) { - emit_hdrf(ctx, "uniform %s vec4 %sshadmask%d;\n", precision, sname, i); - emit_hdrf(ctx, "uniform %s vec4 %sshadadd%d;\n", precision, sname, i); - ctx->shadow_samp_mask |= (1 << i); - } -} - -const char *get_internalformat_string(int virgl_format, - enum tgsi_return_type *stype) { - switch (virgl_format) { - case PIPE_FORMAT_R11G11B10_FLOAT: - *stype = TGSI_RETURN_TYPE_FLOAT; - return "r11f_g11f_b10f"; - case PIPE_FORMAT_R10G10B10A2_UNORM: - *stype = TGSI_RETURN_TYPE_UNORM; - return "rgb10_a2"; - case PIPE_FORMAT_R10G10B10A2_UINT: - *stype = TGSI_RETURN_TYPE_UINT; - return "rgb10_a2ui"; - case PIPE_FORMAT_R8_UNORM: - *stype = TGSI_RETURN_TYPE_UNORM; - return "r8"; - case PIPE_FORMAT_R8_SNORM: - *stype = TGSI_RETURN_TYPE_SNORM; - return "r8_snorm"; - case PIPE_FORMAT_R8_UINT: - *stype = TGSI_RETURN_TYPE_UINT; - return "r8ui"; - case PIPE_FORMAT_R8_SINT: - *stype = TGSI_RETURN_TYPE_SINT; - return "r8i"; - case PIPE_FORMAT_R8G8_UNORM: - *stype = TGSI_RETURN_TYPE_UNORM; - return "rg8"; - case PIPE_FORMAT_R8G8_SNORM: - *stype = TGSI_RETURN_TYPE_SNORM; - return "rg8_snorm"; - case PIPE_FORMAT_R8G8_UINT: - *stype = TGSI_RETURN_TYPE_UINT; - return "rg8ui"; - case PIPE_FORMAT_R8G8_SINT: - *stype = TGSI_RETURN_TYPE_SINT; - return "rg8i"; - case PIPE_FORMAT_R8G8B8A8_UNORM: - *stype = TGSI_RETURN_TYPE_UNORM; - return "rgba8"; - case PIPE_FORMAT_R8G8B8A8_SNORM: - *stype = TGSI_RETURN_TYPE_SNORM; - return "rgba8_snorm"; - case PIPE_FORMAT_R8G8B8A8_UINT: - *stype = TGSI_RETURN_TYPE_UINT; - return "rgba8ui"; - case PIPE_FORMAT_R8G8B8A8_SINT: - *stype = TGSI_RETURN_TYPE_SINT; - return "rgba8i"; - case PIPE_FORMAT_R16_UNORM: - *stype = TGSI_RETURN_TYPE_UNORM; - return "r16"; - case PIPE_FORMAT_R16_SNORM: - *stype = TGSI_RETURN_TYPE_SNORM; - return "r16_snorm"; - case PIPE_FORMAT_R16_UINT: - *stype = TGSI_RETURN_TYPE_UINT; - return "r16ui"; - case PIPE_FORMAT_R16_SINT: - *stype = TGSI_RETURN_TYPE_SINT; - return "r16i"; - case PIPE_FORMAT_R16_FLOAT: - *stype = TGSI_RETURN_TYPE_FLOAT; - return "r16f"; - case PIPE_FORMAT_R16G16_UNORM: - *stype = TGSI_RETURN_TYPE_UNORM; - return "rg16"; - case PIPE_FORMAT_R16G16_SNORM: - *stype = TGSI_RETURN_TYPE_SNORM; - return "rg16_snorm"; - case PIPE_FORMAT_R16G16_UINT: - *stype = TGSI_RETURN_TYPE_UINT; - return "rg16ui"; - case PIPE_FORMAT_R16G16_SINT: - *stype = TGSI_RETURN_TYPE_SINT; - return "rg16i"; - case PIPE_FORMAT_R16G16_FLOAT: - *stype = TGSI_RETURN_TYPE_FLOAT; - return "rg16f"; - case PIPE_FORMAT_R16G16B16A16_UNORM: - *stype = TGSI_RETURN_TYPE_UNORM; - return "rgba16"; - case PIPE_FORMAT_R16G16B16A16_SNORM: - *stype = TGSI_RETURN_TYPE_SNORM; - return "rgba16_snorm"; - case PIPE_FORMAT_R16G16B16A16_FLOAT: - *stype = TGSI_RETURN_TYPE_FLOAT; - return "rgba16f"; - case PIPE_FORMAT_R32_FLOAT: - *stype = TGSI_RETURN_TYPE_FLOAT; - return "r32f"; - case PIPE_FORMAT_R32_UINT: - *stype = TGSI_RETURN_TYPE_UINT; - return "r32ui"; - case PIPE_FORMAT_R32_SINT: - *stype = TGSI_RETURN_TYPE_SINT; - return "r32i"; - case PIPE_FORMAT_R32G32_FLOAT: - *stype = TGSI_RETURN_TYPE_FLOAT; - return "rg32f"; - case PIPE_FORMAT_R32G32_UINT: - *stype = TGSI_RETURN_TYPE_UINT; - return "rg32ui"; - case PIPE_FORMAT_R32G32_SINT: - *stype = TGSI_RETURN_TYPE_SINT; - return "rg32i"; - case PIPE_FORMAT_R32G32B32A32_FLOAT: - *stype = TGSI_RETURN_TYPE_FLOAT; - return "rgba32f"; - case PIPE_FORMAT_R32G32B32A32_UINT: - *stype = TGSI_RETURN_TYPE_UINT; - return "rgba32ui"; - case PIPE_FORMAT_R16G16B16A16_UINT: - *stype = TGSI_RETURN_TYPE_UINT; - return "rgba16ui"; - case PIPE_FORMAT_R16G16B16A16_SINT: - *stype = TGSI_RETURN_TYPE_SINT; - return "rgba16i"; - case PIPE_FORMAT_R32G32B32A32_SINT: - *stype = TGSI_RETURN_TYPE_SINT; - return "rgba32i"; - case PIPE_FORMAT_NONE: - *stype = TGSI_RETURN_TYPE_UNORM; - return ""; - default: - *stype = TGSI_RETURN_TYPE_UNORM; - return ""; - } -} - -static void emit_image_decl(struct dump_ctx *ctx, uint32_t i, uint32_t range, - const struct vrend_shader_image *image) { - char ptc; - const char *sname, *stc, *formatstr; - enum tgsi_return_type itype; - const char *volatile_str = image->vflag ? "volatile " : ""; - const char *precision = "highp "; - const char *access = ""; - formatstr = get_internalformat_string(image->decl.Format, &itype); - ptc = vrend_shader_samplerreturnconv(itype); - sname = tgsi_proc_to_prefix(ctx->prog_type); - stc = vrend_shader_samplertypeconv(image->decl.Resource); - - if (!image->decl.Writable) - access = "readonly "; - else if (!image->decl.Format || - ((image->decl.Format != PIPE_FORMAT_R32_FLOAT) && - (image->decl.Format != PIPE_FORMAT_R32_SINT) && - (image->decl.Format != PIPE_FORMAT_R32_UINT))) - access = "writeonly "; - - emit_hdrf(ctx, "layout(binding=%d%s%s) ", i, - formatstr[0] != '\0' ? ", " : ", rgba32f", formatstr); - - if (range) - emit_hdrf(ctx, "%s%suniform %s%cimage%s %simg%d[%d];\n", access, - volatile_str, precision, ptc, stc, sname, i, range); - else - emit_hdrf(ctx, "%s%suniform %s%cimage%s %simg%d;\n", access, volatile_str, - precision, ptc, stc, sname, i); -} - -static void emit_ios_common(struct dump_ctx *ctx) { - uint i; - const char *sname = tgsi_proc_to_prefix(ctx->prog_type); - - for (i = 0; i < ctx->num_temp_ranges; i++) { - emit_hdrf(ctx, "vec4 temp%d[%d];\n", ctx->temp_ranges[i].first, - ctx->temp_ranges[i].last - ctx->temp_ranges[i].first + 1); - } - - if (ctx->write_mul_utemp) { - emit_hdr(ctx, "uvec4 mul_utemp;\n"); - emit_hdr(ctx, "uvec4 umul_temp;\n"); - } - - if (ctx->write_mul_itemp) { - emit_hdr(ctx, "ivec4 mul_itemp;\n"); - emit_hdr(ctx, "ivec4 imul_temp;\n"); - } - - if (ctx->ssbo_used_mask || ctx->has_file_memory) { - emit_hdr(ctx, "uint ssbo_addr_temp;\n"); - } - - if (ctx->shader_req_bits & SHADER_REQ_FP64) { - emit_hdr(ctx, "dvec2 fp64_dst[3];\n"); - emit_hdr(ctx, "dvec2 fp64_src[4];\n"); - } - - for (i = 0; i < ctx->num_address; i++) { - emit_hdrf(ctx, "int addr%d;\n", i); - } - if (ctx->num_consts) { - const char *cname = tgsi_proc_to_prefix(ctx->prog_type); - emit_hdrf(ctx, "uniform uvec4 %sconst0[%d];\n", cname, ctx->num_consts); - } - - if (ctx->ubo_used_mask) { - const char *cname = tgsi_proc_to_prefix(ctx->prog_type); - - if (ctx->info.dimension_indirect_files & (1 << TGSI_FILE_CONSTANT)) { - require_glsl_ver(ctx, 150); - int first = ffs(ctx->ubo_used_mask) - 1; - unsigned num_ubo = util_bitcount(ctx->ubo_used_mask); - emit_hdrf(ctx, "uniform %subo { vec4 ubocontents[%d]; } %suboarr[%d];\n", - cname, ctx->ubo_sizes[first], cname, num_ubo); - } else { - unsigned mask = ctx->ubo_used_mask; - while (mask) { - uint32_t j = u_bit_scan(&mask); - emit_hdrf(ctx, "uniform %subo%d { vec4 %subo%dcontents[%d]; };\n", - cname, j, cname, j, ctx->ubo_sizes[i]); - } - } - } - - if (ctx->info.indirect_files & (1 << TGSI_FILE_SAMPLER)) { - for (i = 0; i < ctx->num_sampler_arrays; i++) { - uint32_t first = ctx->sampler_arrays[i].first; - uint32_t range = ctx->sampler_arrays[i].array_size; - emit_sampler_decl(ctx, first, range, ctx->samplers + first); - } - } else { - uint nsamp = util_last_bit(ctx->samplers_used); - for (i = 0; i < nsamp; i++) { - - if ((ctx->samplers_used & (1 << i)) == 0) - continue; - - emit_sampler_decl(ctx, i, 0, ctx->samplers + i); - } - } - - if (ctx->info.indirect_files & (1 << TGSI_FILE_IMAGE)) { - for (i = 0; i < ctx->num_image_arrays; i++) { - uint32_t first = ctx->image_arrays[i].first; - uint32_t range = ctx->image_arrays[i].array_size; - emit_image_decl(ctx, first, range, ctx->images + first); - } - } else { - uint32_t mask = ctx->images_used_mask; - while (mask) { - i = u_bit_scan(&mask); - emit_image_decl(ctx, i, 0, ctx->images + i); - } - } - - for (i = 0; i < ctx->num_abo; i++) { - if (ctx->abo_sizes[i] > 1) - emit_hdrf( - ctx, - "layout (binding = %d, offset = %d) uniform atomic_uint ac%d[%d];\n", - ctx->abo_idx[i], ctx->abo_offsets[i] * 4, i, ctx->abo_sizes[i]); - else - emit_hdrf( - ctx, "layout (binding = %d, offset = %d) uniform atomic_uint ac%d;\n", - ctx->abo_idx[i], ctx->abo_offsets[i] * 4, i); - } - - if (ctx->info.indirect_files & (1 << TGSI_FILE_BUFFER)) { - uint32_t mask = ctx->ssbo_used_mask; - while (mask) { - int start, count; - u_bit_scan_consecutive_range(&mask, &start, &count); - const char *atomic = - (ctx->ssbo_atomic_mask & (1 << start)) ? "atomic" : ""; - emit_hdrf(ctx, - "layout (binding = %d, std430) buffer %sssbo%d { uint " - "%sssbocontents%d[]; } %sssboarr%s[%d];\n", - start, sname, start, sname, start, sname, atomic, count); - } - } else { - uint32_t mask = ctx->ssbo_used_mask; - while (mask) { - uint32_t id = u_bit_scan(&mask); - enum vrend_type_qualifier type = - (ctx->ssbo_integer_mask & (1 << id)) ? INT : UINT; - char *coherent = ctx->ssbo_memory_qualifier[id] == TGSI_MEMORY_COHERENT - ? "coherent" - : ""; - emit_hdrf(ctx, - "layout (binding = %d, std430) %s buffer %sssbo%d { %s " - "%sssbocontents%d[]; };\n", - id, coherent, sname, id, get_string(type), sname, id); - } - } -} - -static void emit_ios_streamout(struct dump_ctx *ctx) { - if (ctx->so) { - char outtype[6] = ""; - for (uint i = 0; i < ctx->so->num_outputs; i++) { - if (!ctx->write_so_outputs[i]) - continue; - if (ctx->so->output[i].num_components == 1) - snprintf(outtype, 6, "float"); - else - snprintf(outtype, 6, "vec%d", ctx->so->output[i].num_components); - - if (ctx->so->output[i].stream && - ctx->prog_type == TGSI_PROCESSOR_GEOMETRY) - emit_hdrf(ctx, "layout (stream=%d) out %s tfout%d;\n", - ctx->so->output[i].stream, outtype, i); - else { - const struct vrend_shader_io *output = - get_io_slot(&ctx->outputs[0], ctx->num_outputs, - ctx->so->output[i].register_index); - if (ctx->so->output[i].need_temp || - output->name == TGSI_SEMANTIC_CLIPDIST || - output->glsl_predefined_no_emit) { - - if (ctx->prog_type == TGSI_PROCESSOR_TESS_CTRL) - emit_hdrf(ctx, "out %s tfout%d[];\n", outtype, i); - else - emit_hdrf(ctx, "out %s tfout%d;\n", outtype, i); - } - } - } - } -} - -static inline void emit_winsys_correction(struct dump_ctx *ctx) { - emit_hdr(ctx, "uniform float winsys_adjust_y;\n"); -} - -static void emit_ios_indirect_generics_output(struct dump_ctx *ctx, - const char *postfix) { - if (ctx->generic_output_range.used) { - int size = ctx->generic_output_range.io.last - - ctx->generic_output_range.io.sid + 1; - if (prefer_generic_io_block(ctx, io_out)) { - char blockname[64]; - const char *stage_prefix = get_stage_output_name_prefix(ctx->prog_type); - get_blockname(blockname, stage_prefix, &ctx->generic_output_range.io); - - char blockvarame[64]; - get_blockvarname(blockvarame, stage_prefix, &ctx->generic_output_range.io, - postfix); - - emit_hdrf(ctx, "out %s {\n vec4 %s[%d]; \n} %s;\n", blockname, - ctx->generic_output_range.io.glsl_name, size, blockvarame); - } else - emit_hdrf(ctx, "out vec4 %s%s[%d];\n", - ctx->generic_output_range.io.glsl_name, postfix, size); - } -} - -static void emit_ios_indirect_generics_input(struct dump_ctx *ctx, - const char *postfix) { - if (ctx->generic_input_range.used) { - int size = - ctx->generic_input_range.io.last - ctx->generic_input_range.io.sid + 1; - assert(size < 256 && size >= 0); - - if (prefer_generic_io_block(ctx, io_in)) { - char blockname[64]; - char blockvarame[64]; - const char *stage_prefix = - get_stage_input_name_prefix(ctx, ctx->prog_type); - - get_blockname(blockname, stage_prefix, &ctx->generic_input_range.io); - get_blockvarname(blockvarame, stage_prefix, &ctx->generic_input_range.io, - postfix); - - emit_hdrf(ctx, "in %s {\n vec4 %s[%d]; \n} %s;\n", blockname, - ctx->generic_input_range.io.glsl_name, size, blockvarame); - } else - emit_hdrf(ctx, "in vec4 %s%s[%d];\n", - ctx->generic_input_range.io.glsl_name, postfix, size); - } -} - -static void emit_ios_generic(struct dump_ctx *ctx, enum io_type iot, - const char *prefix, - const struct vrend_shader_io *io, - const char *inout, const char *postfix) { - const char type[4][6] = {"float", " vec2", " vec3", " vec4"}; - const char *t = " vec4"; - - char layout[128] = ""; - - if (io->layout_location > 0) { - /* we need to define a layout here because interleaved arrays might be - * emited */ - if (io->swizzle_offset) - snprintf(layout, sizeof(layout), - "layout(location = %d, component = %d)\n", - io->layout_location - 1, io->swizzle_offset); - else - snprintf(layout, sizeof(layout), "layout(location = %d)\n", - io->layout_location - 1); - } - - if (io->usage_mask != 0xf && io->name == TGSI_SEMANTIC_GENERIC) - t = type[io->num_components - 1]; - - if (io->first == io->last) { - emit_hdr(ctx, layout); - /* ugly leave spaces to patch interp in later */ - emit_hdrf(ctx, "%s%s\n%s %s %s %s%s;\n", io->precise ? "precise" : "", - io->invariant ? "invariant" : "", prefix, inout, t, io->glsl_name, - postfix); - - if (io->name == TGSI_SEMANTIC_GENERIC) { - if (iot == io_in) - ctx->generic_inputs_emitted_mask |= 1 << io->sid; - else - ctx->generic_outputs_emitted_mask |= 1 << io->sid; - } - - } else { - if (prefer_generic_io_block(ctx, iot)) { - const char *stage_prefix = - iot == io_in ? get_stage_input_name_prefix(ctx, ctx->prog_type) - : get_stage_output_name_prefix(ctx->prog_type); - - char blockname[64]; - get_blockname(blockname, stage_prefix, io); - - char blockvarame[64]; - get_blockvarname(blockvarame, stage_prefix, io, postfix); - - emit_hdrf(ctx, "%s %s {\n", inout, blockname); - emit_hdr(ctx, layout); - emit_hdrf(ctx, "%s%s\n%s %s %s[%d]; \n} %s;\n", - io->precise ? "precise" : "", io->invariant ? "invariant" : "", - prefix, t, io->glsl_name, io->last - io->first + 1, - blockvarame); - } else { - emit_hdr(ctx, layout); - emit_hdrf(ctx, "%s%s\n%s %s %s %s%s[%d];\n", - io->precise ? "precise" : "", io->invariant ? "invariant" : "", - prefix, inout, t, io->glsl_name, postfix, - io->last - io->first + 1); - } - } -} - -typedef bool (*can_emit_generic_callback)(const struct vrend_shader_io *io); - -static void -emit_ios_generic_outputs(struct dump_ctx *ctx, - const can_emit_generic_callback can_emit_generic) { - uint32_t i; - uint64_t fc_emitted = 0; - uint64_t bc_emitted = 0; - - for (i = 0; i < ctx->num_outputs; i++) { - - if (!ctx->outputs[i].glsl_predefined_no_emit) { - /* GS stream outputs are handled separately */ - if (!can_emit_generic(&ctx->outputs[i])) - continue; - - const char *prefix = ""; - if (ctx->outputs[i].name == TGSI_SEMANTIC_GENERIC || - ctx->outputs[i].name == TGSI_SEMANTIC_COLOR || - ctx->outputs[i].name == TGSI_SEMANTIC_BCOLOR) { - ctx->num_interps++; - /* ugly leave spaces to patch interp in later */ - prefix = INTERP_PREFIX; - } - - if (ctx->outputs[i].name == TGSI_SEMANTIC_COLOR) { - ctx->front_back_color_emitted_flags[ctx->outputs[i].sid] |= - FRONT_COLOR_EMITTED; - fc_emitted |= 1ull << ctx->outputs[i].sid; - } - - if (ctx->outputs[i].name == TGSI_SEMANTIC_BCOLOR) { - ctx->front_back_color_emitted_flags[ctx->outputs[i].sid] |= - BACK_COLOR_EMITTED; - bc_emitted |= 1ull << ctx->outputs[i].sid; - } - - emit_ios_generic(ctx, io_out, prefix, &ctx->outputs[i], - ctx->outputs[i].fbfetch_used ? "inout" : "out", ""); - } else if (ctx->outputs[i].invariant || ctx->outputs[i].precise) { - emit_hdrf(ctx, "%s%s;\n", - ctx->outputs[i].precise - ? "precise " - : (ctx->outputs[i].invariant ? "invariant " : ""), - ctx->outputs[i].glsl_name); - } - } - - /* If a back color emitted without a corresponding front color, then - * we have to force two side coloring, because the FS shader might expect - * a front color too. */ - if (bc_emitted & ~fc_emitted) - ctx->force_color_two_side = 1; -} - -static void emit_ios_patch(struct dump_ctx *ctx, const char *prefix, - const struct vrend_shader_io *io, const char *inout, - int size) { - const char type[4][6] = {"float", " vec2", " vec3", " vec4"}; - const char *t = " vec4"; - - if (io->layout_location > 0) { - /* we need to define a layout here because interleaved arrays might be - * emited */ - if (io->swizzle_offset) - emit_hdrf(ctx, "layout(location = %d, component = %d)\n", - io->layout_location - 1, io->swizzle_offset); - else - emit_hdrf(ctx, "layout(location = %d)\n", io->layout_location - 1); - } - - if (io->usage_mask != 0xf) - t = type[io->num_components - 1]; - - if (io->last == io->first) - emit_hdrf(ctx, "%s %s %s %s;\n", prefix, inout, t, io->glsl_name); - else - emit_hdrf(ctx, "%s %s %s %s[%d];\n", prefix, inout, t, io->glsl_name, size); -} - -static bool can_emit_generic_default(UNUSED const struct vrend_shader_io *io) { - return true; -} - -static void emit_ios_vs(struct dump_ctx *ctx) { - uint32_t i; - - for (i = 0; i < ctx->num_inputs; i++) { - char postfix[32] = ""; - if (!ctx->inputs[i].glsl_predefined_no_emit) { - if (ctx->cfg->use_explicit_locations) { - emit_hdrf(ctx, "layout(location=%d) ", ctx->inputs[i].first); - } - if (ctx->inputs[i].first != ctx->inputs[i].last) - snprintf(postfix, sizeof(postfix), "[%d]", - ctx->inputs[i].last - ctx->inputs[i].first + 1); - emit_hdrf(ctx, "in vec4 %s%s;\n", ctx->inputs[i].glsl_name, postfix); - } - } - - emit_ios_indirect_generics_output(ctx, ""); - - emit_ios_generic_outputs(ctx, can_emit_generic_default); - - if (ctx->key->color_two_side || ctx->force_color_two_side) { - bool fcolor_emitted, bcolor_emitted; - - for (i = 0; i < ctx->num_outputs; i++) { - if (ctx->outputs[i].sid >= 2) - continue; - - fcolor_emitted = bcolor_emitted = false; - - fcolor_emitted = - ctx->front_back_color_emitted_flags[ctx->outputs[i].sid] & - FRONT_COLOR_EMITTED; - bcolor_emitted = - ctx->front_back_color_emitted_flags[ctx->outputs[i].sid] & - BACK_COLOR_EMITTED; - - if (fcolor_emitted && !bcolor_emitted) { - emit_hdrf(ctx, "%sout vec4 ex_bc%d;\n", INTERP_PREFIX, - ctx->outputs[i].sid); - ctx->front_back_color_emitted_flags[ctx->outputs[i].sid] |= - BACK_COLOR_EMITTED; - } - if (bcolor_emitted && !fcolor_emitted) { - emit_hdrf(ctx, "%sout vec4 ex_c%d;\n", INTERP_PREFIX, - ctx->outputs[i].sid); - ctx->front_back_color_emitted_flags[ctx->outputs[i].sid] |= - FRONT_COLOR_EMITTED; - } - } - } - - emit_winsys_correction(ctx); - - if (ctx->has_clipvertex) { - emit_hdrf(ctx, "%svec4 clipv_tmp;\n", ctx->has_clipvertex_so ? "out " : ""); - } - if (ctx->num_clip_dist || ctx->key->clip_plane_enable) { - bool has_prop = (ctx->num_clip_dist_prop + ctx->num_cull_dist_prop) > 0; - int num_clip_dists = ctx->num_clip_dist ? ctx->num_clip_dist : 8; - int num_cull_dists = 0; - char cull_buf[64] = ""; - char clip_buf[64] = ""; - if (has_prop) { - num_clip_dists = ctx->num_clip_dist_prop; - num_cull_dists = ctx->num_cull_dist_prop; - if (num_clip_dists) - snprintf(clip_buf, 64, "out float gl_ClipDistance[%d];\n", - num_clip_dists); - if (num_cull_dists) - snprintf(cull_buf, 64, "out float gl_CullDistance[%d];\n", - num_cull_dists); - } else - snprintf(clip_buf, 64, "out float gl_ClipDistance[%d];\n", - num_clip_dists); - if (ctx->key->clip_plane_enable) { - emit_hdr(ctx, "uniform vec4 clipp[8];\n"); - } - if (ctx->key->gs_present || ctx->key->tes_present) { - ctx->vs_has_pervertex = true; - emit_hdrf(ctx, - "out gl_PerVertex {\n vec4 gl_Position;\n float " - "gl_PointSize;\n%s%s};\n", - clip_buf, cull_buf); - } - emit_hdr(ctx, "vec4 clip_dist_temp[2];\n"); - } -} - -static const char *get_depth_layout(int depth_layout) { - const char *dl[4] = {"depth_any", "depth_greater", "depth_less", - "depth_unchanged"}; - - if (depth_layout < 1 || depth_layout > TGSI_FS_DEPTH_LAYOUT_UNCHANGED) - return NULL; - return dl[depth_layout - 1]; -} - -static void emit_ios_fs(struct dump_ctx *ctx) { - uint32_t i; - - if (ctx->early_depth_stencil) { - emit_hdr(ctx, "layout(early_fragment_tests) in;\n"); - } - - emit_ios_indirect_generics_input(ctx, ""); - - for (i = 0; i < ctx->num_inputs; i++) { - if (!ctx->inputs[i].glsl_predefined_no_emit) { - const char *prefix = ""; - const char *auxprefix = ""; - - if (ctx->inputs[i].name == TGSI_SEMANTIC_GENERIC || - ctx->inputs[i].name == TGSI_SEMANTIC_COLOR || - ctx->inputs[i].name == TGSI_SEMANTIC_BCOLOR) { - prefix = get_interp_string(ctx->cfg, ctx->inputs[i].interpolate, - ctx->key->flatshade); - if (!prefix) - prefix = ""; - auxprefix = get_aux_string(ctx->inputs[i].location); - ctx->num_interps++; - } - - char prefixes[64]; - snprintf(prefixes, sizeof(prefixes), "%s %s", prefix, auxprefix); - emit_ios_generic(ctx, io_in, prefixes, &ctx->inputs[i], "in", ""); - } - - if (!ctx->winsys_adjust_y_emitted && - (ctx->key->coord_replace & (1 << ctx->inputs[i].sid))) { - ctx->winsys_adjust_y_emitted = true; - emit_hdr(ctx, "uniform float winsys_adjust_y;\n"); - } - } - - if (ctx->key->color_two_side) { - if (ctx->color_in_mask & 1) - emit_hdr(ctx, "vec4 realcolor0;\n"); - if (ctx->color_in_mask & 2) - emit_hdr(ctx, "vec4 realcolor1;\n"); - } - - if (ctx->write_all_cbufs) { - for (i = 0; i < (uint32_t)ctx->cfg->max_draw_buffers; i++) { - if (ctx->key->fs_logicop_enabled) - emit_hdrf(ctx, "vec4 fsout_tmp_c%d;\n", i); - - if (logiop_require_inout(ctx->key)) { - const char *noncoherent = - ctx->key->fs_logicop_emulate_coherent ? "" : ", noncoherent"; - emit_hdrf(ctx, "layout (location=%d%s) inout highp vec4 fsout_c%d;\n", - i, noncoherent, i); - } else - emit_hdrf(ctx, "layout (location=%d) out vec4 fsout_c%d;\n", i, i); - } - } else { - for (i = 0; i < ctx->num_outputs; i++) { - - if (!ctx->outputs[i].glsl_predefined_no_emit) { - emit_ios_generic(ctx, io_out, "", &ctx->outputs[i], - ctx->outputs[i].fbfetch_used ? "inout" : "out", ""); - - } else if (ctx->outputs[i].invariant || ctx->outputs[i].precise) { - emit_hdrf(ctx, "%s%s;\n", - ctx->outputs[i].precise - ? "precise " - : (ctx->outputs[i].invariant ? "invariant " : ""), - ctx->outputs[i].glsl_name); - } - } - } - - if (ctx->fs_depth_layout) { - const char *depth_layout = get_depth_layout(ctx->fs_depth_layout); - if (depth_layout) - emit_hdrf(ctx, "layout (%s) out float gl_FragDepth;\n", depth_layout); - } - - if (ctx->num_in_clip_dist) { - if (ctx->key->prev_stage_num_clip_out) { - emit_hdrf(ctx, "in float gl_ClipDistance[%d];\n", - ctx->key->prev_stage_num_clip_out); - } else if (ctx->num_in_clip_dist > 4 && - !ctx->key->prev_stage_num_cull_out) { - emit_hdrf(ctx, "in float gl_ClipDistance[%d];\n", ctx->num_in_clip_dist); - } - - if (ctx->key->prev_stage_num_cull_out) { - emit_hdrf(ctx, "in float gl_CullDistance[%d];\n", - ctx->key->prev_stage_num_cull_out); - } - if (ctx->fs_uses_clipdist_input) - emit_hdr(ctx, "vec4 clip_dist_temp[2];\n"); - } -} - -static bool can_emit_generic_geom(const struct vrend_shader_io *io) { - return io->stream == 0; -} - -static void emit_ios_geom(struct dump_ctx *ctx) { - uint32_t i; - char invocbuf[25]; - - if (ctx->gs_num_invocations) - snprintf(invocbuf, 25, ", invocations = %d", ctx->gs_num_invocations); - - emit_hdrf(ctx, "layout(%s%s) in;\n", prim_to_name(ctx->gs_in_prim), - ctx->gs_num_invocations > 1 ? invocbuf : ""); - emit_hdrf(ctx, "layout(%s, max_vertices = %d) out;\n", - prim_to_name(ctx->gs_out_prim), ctx->gs_max_out_verts); - - for (i = 0; i < ctx->num_inputs; i++) { - if (!ctx->inputs[i].glsl_predefined_no_emit) { - char postfix[64]; - snprintf(postfix, sizeof(postfix), "[%d]", - gs_input_prim_to_size(ctx->gs_in_prim)); - emit_ios_generic(ctx, io_in, "", &ctx->inputs[i], "in", postfix); - } - } - - for (i = 0; i < ctx->num_outputs; i++) { - if (!ctx->outputs[i].glsl_predefined_no_emit) { - if (!ctx->outputs[i].stream) - continue; - - const char *prefix = ""; - if (ctx->outputs[i].name == TGSI_SEMANTIC_GENERIC || - ctx->outputs[i].name == TGSI_SEMANTIC_COLOR || - ctx->outputs[i].name == TGSI_SEMANTIC_BCOLOR) { - ctx->num_interps++; - /* ugly leave spaces to patch interp in later */ - prefix = INTERP_PREFIX; - } - - emit_hdrf(ctx, "layout (stream = %d) %s%s%sout vec4 %s;\n", - ctx->outputs[i].stream, prefix, - ctx->outputs[i].precise ? "precise " : "", - ctx->outputs[i].invariant ? "invariant " : "", - ctx->outputs[i].glsl_name); - } - } - - emit_ios_generic_outputs(ctx, can_emit_generic_geom); - - emit_winsys_correction(ctx); - - if (ctx->num_in_clip_dist || ctx->key->clip_plane_enable || - ctx->key->prev_stage_pervertex_out) { - int clip_dist, cull_dist; - char clip_var[64] = ""; - char cull_var[64] = ""; - - clip_dist = ctx->key->prev_stage_num_clip_out - ? ctx->key->prev_stage_num_clip_out - : ctx->num_in_clip_dist; - cull_dist = ctx->key->prev_stage_num_cull_out; - - if (clip_dist) - snprintf(clip_var, 64, "float gl_ClipDistance[%d];\n", clip_dist); - if (cull_dist) - snprintf(cull_var, 64, "float gl_CullDistance[%d];\n", cull_dist); - - emit_hdrf(ctx, - "in gl_PerVertex {\n vec4 gl_Position;\n float gl_PointSize; \n " - "%s%s\n} gl_in[];\n", - clip_var, cull_var); - } - if (ctx->num_clip_dist) { - bool has_prop = (ctx->num_clip_dist_prop + ctx->num_cull_dist_prop) > 0; - int num_clip_dists = ctx->num_clip_dist ? ctx->num_clip_dist : 8; - int num_cull_dists = 0; - char cull_buf[64] = ""; - char clip_buf[64] = ""; - if (has_prop) { - num_clip_dists = ctx->num_clip_dist_prop; - num_cull_dists = ctx->num_cull_dist_prop; - if (num_clip_dists) - snprintf(clip_buf, 64, "out float gl_ClipDistance[%d];\n", - num_clip_dists); - if (num_cull_dists) - snprintf(cull_buf, 64, "out float gl_CullDistance[%d];\n", - num_cull_dists); - } else - snprintf(clip_buf, 64, "out float gl_ClipDistance[%d];\n", - num_clip_dists); - emit_hdrf(ctx, "%s%s\n", clip_buf, cull_buf); - emit_hdrf(ctx, "vec4 clip_dist_temp[2];\n"); - } -} - -static void emit_ios_tcs(struct dump_ctx *ctx) { - uint32_t i; - - emit_ios_indirect_generics_input(ctx, "[]"); - - for (i = 0; i < ctx->num_inputs; i++) { - if (!ctx->inputs[i].glsl_predefined_no_emit) { - if (ctx->inputs[i].name == TGSI_SEMANTIC_PATCH) - emit_ios_patch(ctx, "", &ctx->inputs[i], "in", - ctx->inputs[i].last - ctx->inputs[i].first + 1); - else - emit_ios_generic(ctx, io_in, "", &ctx->inputs[i], "in", "[]"); - } - } - - emit_hdrf(ctx, "layout(vertices = %d) out;\n", ctx->tcs_vertices_out); - - emit_ios_indirect_generics_output(ctx, "[]"); - - if (ctx->patch_output_range.used) - emit_ios_patch(ctx, "patch", &ctx->patch_output_range.io, "out", - ctx->patch_output_range.io.last - - ctx->patch_output_range.io.sid + 1); - - for (i = 0; i < ctx->num_outputs; i++) { - if (!ctx->outputs[i].glsl_predefined_no_emit) { - if (ctx->outputs[i].name == TGSI_SEMANTIC_PATCH) { - emit_ios_patch(ctx, "patch", &ctx->outputs[i], "out", - ctx->outputs[i].last - ctx->outputs[i].first + 1); - } else - emit_ios_generic(ctx, io_out, "", &ctx->outputs[i], "out", "[]"); - } else if (ctx->outputs[i].invariant || ctx->outputs[i].precise) { - emit_hdrf(ctx, "%s%s;\n", - ctx->outputs[i].precise - ? "precise " - : (ctx->outputs[i].invariant ? "invariant " : ""), - ctx->outputs[i].glsl_name); - } - } - - if (ctx->num_in_clip_dist || ctx->key->prev_stage_pervertex_out) { - int clip_dist, cull_dist; - char clip_var[64] = "", cull_var[64] = ""; - - clip_dist = ctx->key->prev_stage_num_clip_out - ? ctx->key->prev_stage_num_clip_out - : ctx->num_in_clip_dist; - cull_dist = ctx->key->prev_stage_num_cull_out; - - if (clip_dist) - snprintf(clip_var, 64, "float gl_ClipDistance[%d];\n", clip_dist); - if (cull_dist) - snprintf(cull_var, 64, "float gl_CullDistance[%d];\n", cull_dist); - - emit_hdrf(ctx, - "in gl_PerVertex {\n vec4 gl_Position;\n float gl_PointSize; \n " - "%s%s} gl_in[];\n", - clip_var, cull_var); - } - if (ctx->num_clip_dist) { - emit_hdrf(ctx, - "out gl_PerVertex {\n vec4 gl_Position;\n float gl_PointSize;\n " - "float gl_ClipDistance[%d];\n} gl_out[];\n", - ctx->num_clip_dist ? ctx->num_clip_dist : 8); - emit_hdr(ctx, "vec4 clip_dist_temp[2];\n"); - } -} - -static void emit_ios_tes(struct dump_ctx *ctx) { - uint32_t i; - - if (ctx->patch_input_range.used) - emit_ios_patch(ctx, "patch", &ctx->patch_input_range.io, "in", - ctx->patch_input_range.io.last - - ctx->patch_input_range.io.sid + 1); - - if (ctx->generic_input_range.used) - emit_ios_indirect_generics_input(ctx, "[]"); - - for (i = 0; i < ctx->num_inputs; i++) { - if (!ctx->inputs[i].glsl_predefined_no_emit) { - if (ctx->inputs[i].name == TGSI_SEMANTIC_PATCH) - emit_ios_patch(ctx, "patch", &ctx->inputs[i], "in", - ctx->inputs[i].last - ctx->inputs[i].first + 1); - else - emit_ios_generic(ctx, io_in, "", &ctx->inputs[i], "in", "[]"); - } - } - - emit_hdrf(ctx, "layout(%s, %s, %s%s) in;\n", - prim_to_tes_name(ctx->tes_prim_mode), - get_spacing_string(ctx->tes_spacing), - ctx->tes_vertex_order ? "cw" : "ccw", - ctx->tes_point_mode ? ", point_mode" : ""); - - emit_ios_generic_outputs(ctx, can_emit_generic_default); - - emit_winsys_correction(ctx); - - if (ctx->num_in_clip_dist || ctx->key->prev_stage_pervertex_out) { - int clip_dist, cull_dist; - char clip_var[64] = "", cull_var[64] = ""; - - clip_dist = ctx->key->prev_stage_num_clip_out - ? ctx->key->prev_stage_num_clip_out - : ctx->num_in_clip_dist; - cull_dist = ctx->key->prev_stage_num_cull_out; - - if (clip_dist) - snprintf(clip_var, 64, "float gl_ClipDistance[%d];\n", clip_dist); - if (cull_dist) - snprintf(cull_var, 64, "float gl_CullDistance[%d];\n", cull_dist); - - emit_hdrf(ctx, - "in gl_PerVertex {\n vec4 gl_Position;\n float gl_PointSize; \n " - "%s%s} gl_in[];\n", - clip_var, cull_var); - } - if (ctx->num_clip_dist) { - emit_hdrf(ctx, - "out gl_PerVertex {\n vec4 gl_Position;\n float gl_PointSize;\n " - "float gl_ClipDistance[%d];\n} gl_out[];\n", - ctx->num_clip_dist ? ctx->num_clip_dist : 8); - emit_hdr(ctx, "vec4 clip_dist_temp[2];\n"); - } -} - -static void emit_ios_cs(struct dump_ctx *ctx) { - emit_hdrf( - ctx, - "layout (local_size_x = %d, local_size_y = %d, local_size_z = %d) in;\n", - ctx->local_cs_block_size[0], ctx->local_cs_block_size[1], - ctx->local_cs_block_size[2]); - - if (ctx->req_local_mem) { - enum vrend_type_qualifier type = ctx->integer_memory ? INT : UINT; - emit_hdrf(ctx, "shared %s values[%d];\n", get_string(type), - ctx->req_local_mem / 4); - } -} - -static void emit_ios(struct dump_ctx *ctx) { - ctx->num_interps = 0; - - if (ctx->so && ctx->so->num_outputs >= PIPE_MAX_SO_OUTPUTS) { - set_hdr_error(ctx); - return; - } - - switch (ctx->prog_type) { - case TGSI_PROCESSOR_VERTEX: - emit_ios_vs(ctx); - break; - case TGSI_PROCESSOR_FRAGMENT: - emit_ios_fs(ctx); - break; - case TGSI_PROCESSOR_GEOMETRY: - emit_ios_geom(ctx); - break; - case TGSI_PROCESSOR_TESS_CTRL: - emit_ios_tcs(ctx); - break; - case TGSI_PROCESSOR_TESS_EVAL: - emit_ios_tes(ctx); - break; - case TGSI_PROCESSOR_COMPUTE: - emit_ios_cs(ctx); - break; - default: - set_hdr_error(ctx); - return; - } - - if (ctx->generic_outputs_expected_mask && - (ctx->generic_outputs_expected_mask != - ctx->generic_outputs_emitted_mask)) { - for (int i = 0; i < 31; ++i) { - uint32_t mask = 1 << i; - bool expecting = ctx->generic_outputs_expected_mask & mask; - if (expecting & !(ctx->generic_outputs_emitted_mask & mask)) - emit_hdrf(ctx, " out vec4 %s_g%dA0_f%s;\n", - get_stage_output_name_prefix(ctx->prog_type), i, - ctx->prog_type == TGSI_PROCESSOR_TESS_CTRL ? "[]" : ""); - } - } - - emit_ios_streamout(ctx); - emit_ios_common(ctx); - - if (ctx->prog_type == TGSI_PROCESSOR_FRAGMENT && - ctx->key->pstipple_tex == true) { - emit_hdr(ctx, "uniform sampler2D pstipple_sampler;\nfloat stip_temp;\n"); - } -} - -static boolean fill_fragment_interpolants(struct dump_ctx *ctx, - struct vrend_shader_info *sinfo) { - uint32_t i, index = 0; - - for (i = 0; i < ctx->num_inputs; i++) { - if (ctx->inputs[i].glsl_predefined_no_emit) - continue; - - if (ctx->inputs[i].name != TGSI_SEMANTIC_GENERIC && - ctx->inputs[i].name != TGSI_SEMANTIC_COLOR) - continue; - - if (index >= ctx->num_interps) - return true; - - sinfo->interpinfo[index].semantic_name = ctx->inputs[i].name; - sinfo->interpinfo[index].semantic_index = ctx->inputs[i].sid; - sinfo->interpinfo[index].interpolate = ctx->inputs[i].interpolate; - sinfo->interpinfo[index].location = ctx->inputs[i].location; - index++; - } - return true; -} - -static boolean fill_interpolants(struct dump_ctx *ctx, - struct vrend_shader_info *sinfo) { - boolean ret; - - if (!ctx->num_interps) - return true; - if (ctx->prog_type == TGSI_PROCESSOR_VERTEX || - ctx->prog_type == TGSI_PROCESSOR_GEOMETRY) - return true; - - free(sinfo->interpinfo); - sinfo->interpinfo = - calloc(ctx->num_interps, sizeof(struct vrend_interp_info)); - if (!sinfo->interpinfo) - return false; - - ret = fill_fragment_interpolants(ctx, sinfo); - if (ret == false) - goto out_fail; - - return true; -out_fail: - free(sinfo->interpinfo); - return false; -} - -static boolean analyze_instruction(struct tgsi_iterate_context *iter, - struct tgsi_full_instruction *inst) { - struct dump_ctx *ctx = (struct dump_ctx *)iter; - uint32_t opcode = inst->Instruction.Opcode; - if (opcode == TGSI_OPCODE_ATOMIMIN || opcode == TGSI_OPCODE_ATOMIMAX) { - const struct tgsi_full_src_register *src = &inst->Src[0]; - if (src->Register.File == TGSI_FILE_BUFFER) - ctx->ssbo_integer_mask |= 1 << src->Register.Index; - if (src->Register.File == TGSI_FILE_MEMORY) - ctx->integer_memory = true; - } - - if (!ctx->fs_uses_clipdist_input && - (ctx->prog_type == TGSI_PROCESSOR_FRAGMENT)) { - for (int i = 0; i < inst->Instruction.NumSrcRegs; ++i) { - if (inst->Src[i].Register.File == TGSI_FILE_INPUT) { - int idx = inst->Src[i].Register.Index; - for (unsigned j = 0; j < ctx->num_inputs; ++j) { - if (ctx->inputs[j].first <= idx && ctx->inputs[j].last >= idx && - ctx->inputs[j].name == TGSI_SEMANTIC_CLIPDIST) { - ctx->fs_uses_clipdist_input = true; - break; - } - } - } - } - } - - return true; -} - -static void fill_sinfo(struct dump_ctx *ctx, struct vrend_shader_info *sinfo) { - sinfo->num_ucp = ctx->key->clip_plane_enable ? 8 : 0; - sinfo->has_pervertex_out = ctx->vs_has_pervertex; - sinfo->has_sample_input = ctx->has_sample_input; - bool has_prop = (ctx->num_clip_dist_prop + ctx->num_cull_dist_prop) > 0; - sinfo->num_clip_out = has_prop - ? ctx->num_clip_dist_prop - : (ctx->num_clip_dist ? ctx->num_clip_dist : 8); - sinfo->num_cull_out = has_prop ? ctx->num_cull_dist_prop : 0; - sinfo->samplers_used_mask = ctx->samplers_used; - sinfo->images_used_mask = ctx->images_used_mask; - sinfo->num_consts = ctx->num_consts; - sinfo->ubo_used_mask = ctx->ubo_used_mask; - - sinfo->ssbo_used_mask = ctx->ssbo_used_mask; - - sinfo->ubo_indirect = - ctx->info.dimension_indirect_files & (1 << TGSI_FILE_CONSTANT); - - if (ctx->generic_input_range.used) - sinfo->num_indirect_generic_inputs = - ctx->generic_input_range.io.last - ctx->generic_input_range.io.sid + 1; - if (ctx->patch_input_range.used) - sinfo->num_indirect_patch_inputs = - ctx->patch_input_range.io.last - ctx->patch_input_range.io.sid + 1; - - if (ctx->generic_output_range.used) - sinfo->num_indirect_generic_outputs = ctx->generic_output_range.io.last - - ctx->generic_output_range.io.sid + 1; - if (ctx->patch_output_range.used) - sinfo->num_indirect_patch_outputs = - ctx->patch_output_range.io.last - ctx->patch_output_range.io.sid + 1; - - sinfo->num_inputs = ctx->num_inputs; - sinfo->num_interps = ctx->num_interps; - sinfo->num_outputs = ctx->num_outputs; - sinfo->shadow_samp_mask = ctx->shadow_samp_mask; - sinfo->glsl_ver = ctx->glsl_ver_required; - sinfo->gs_out_prim = ctx->gs_out_prim; - sinfo->tes_prim = ctx->tes_prim_mode; - sinfo->tes_point_mode = ctx->tes_point_mode; - - if (sinfo->so_names || ctx->so_names) { - if (sinfo->so_names) { - for (unsigned i = 0; i < sinfo->so_info.num_outputs; ++i) - free(sinfo->so_names[i]); - free(sinfo->so_names); - } - } - - /* Record information about the layout of generics and patches for apssing it - * to the next shader stage. mesa/tgsi doesn't provide this information for - * TCS, TES, and GEOM shaders. - */ - sinfo->guest_sent_io_arrays = ctx->guest_sent_io_arrays; - sinfo->num_generic_and_patch_outputs = 0; - for (unsigned i = 0; i < ctx->num_outputs; i++) { - sinfo->generic_outputs_layout[sinfo->num_generic_and_patch_outputs].name = - ctx->outputs[i].name; - sinfo->generic_outputs_layout[sinfo->num_generic_and_patch_outputs].sid = - ctx->outputs[i].sid; - sinfo->generic_outputs_layout[sinfo->num_generic_and_patch_outputs] - .location = ctx->outputs[i].layout_location; - sinfo->generic_outputs_layout[sinfo->num_generic_and_patch_outputs] - .array_id = ctx->outputs[i].array_id; - sinfo->generic_outputs_layout[sinfo->num_generic_and_patch_outputs] - .usage_mask = ctx->outputs[i].usage_mask; - if (ctx->outputs[i].name == TGSI_SEMANTIC_GENERIC || - ctx->outputs[i].name == TGSI_SEMANTIC_PATCH) { - sinfo->num_generic_and_patch_outputs++; - } - } - - sinfo->so_names = ctx->so_names; - sinfo->attrib_input_mask = ctx->attrib_input_mask; - if (sinfo->sampler_arrays) - free(sinfo->sampler_arrays); - sinfo->sampler_arrays = ctx->sampler_arrays; - sinfo->num_sampler_arrays = ctx->num_sampler_arrays; - if (sinfo->image_arrays) - free(sinfo->image_arrays); - sinfo->image_arrays = ctx->image_arrays; - sinfo->num_image_arrays = ctx->num_image_arrays; - sinfo->generic_inputs_emitted_mask = ctx->generic_inputs_emitted_mask; - - for (unsigned i = 0; i < ctx->num_outputs; ++i) { - if (ctx->outputs[i].invariant) - sinfo->invariant_outputs |= 1ull << ctx->outputs[i].sid; - } -} - -static bool allocate_strbuffers(struct dump_ctx *ctx) { - if (!strbuf_alloc(&ctx->glsl_main, 4096)) - return false; - - if (strbuf_get_error(&ctx->glsl_main)) - return false; - - if (!strbuf_alloc(&ctx->glsl_hdr, 1024)) - return false; - - if (!strbuf_alloc(&ctx->glsl_ver_ext, 1024)) - return false; - - return true; -} - -static void set_strbuffers(MAYBE_UNUSED struct vrend_context *rctx, - struct dump_ctx *ctx, - struct vrend_strarray *shader) { - strarray_addstrbuf(shader, &ctx->glsl_ver_ext); - strarray_addstrbuf(shader, &ctx->glsl_hdr); - strarray_addstrbuf(shader, &ctx->glsl_main); -} - -bool vrend_convert_shader(struct vrend_context *rctx, - struct vrend_shader_cfg *cfg, - const struct tgsi_token *tokens, - uint32_t req_local_mem, struct vrend_shader_key *key, - struct vrend_shader_info *sinfo, - struct vrend_strarray *shader) { - struct dump_ctx ctx; - boolean bret; - - memset(&ctx, 0, sizeof(struct dump_ctx)); - - /* First pass to deal with edge cases. */ - if (ctx.prog_type == TGSI_PROCESSOR_FRAGMENT) - ctx.iter.iterate_declaration = iter_inputs; - ctx.iter.iterate_instruction = analyze_instruction; - bret = tgsi_iterate_shader(tokens, &ctx.iter); - if (bret == false) - return false; - - ctx.num_inputs = 0; - - ctx.iter.prolog = prolog; - ctx.iter.iterate_instruction = iter_instruction; - ctx.iter.iterate_declaration = iter_declaration; - ctx.iter.iterate_immediate = iter_immediate; - ctx.iter.iterate_property = iter_property; - ctx.iter.epilog = NULL; - ctx.key = key; - ctx.cfg = cfg; - ctx.prog_type = -1; - ctx.num_image_arrays = 0; - ctx.image_arrays = NULL; - ctx.num_sampler_arrays = 0; - ctx.sampler_arrays = NULL; - ctx.ssbo_array_base = 0xffffffff; - ctx.ssbo_atomic_array_base = 0xffffffff; - ctx.has_sample_input = false; - ctx.req_local_mem = req_local_mem; - ctx.guest_sent_io_arrays = key->guest_sent_io_arrays; - ctx.generic_outputs_expected_mask = key->generic_outputs_expected_mask; - - tgsi_scan_shader(tokens, &ctx.info); - - if (cfg->glsl_version >= 140) - require_glsl_ver(&ctx, 140); - - if (sinfo->so_info.num_outputs) { - ctx.so = &sinfo->so_info; - ctx.so_names = calloc(sinfo->so_info.num_outputs, sizeof(char *)); - if (!ctx.so_names) - goto fail; - } else - ctx.so_names = NULL; - - if (ctx.info.dimension_indirect_files & (1 << TGSI_FILE_CONSTANT)) - require_glsl_ver(&ctx, 150); - - if (ctx.info.indirect_files & (1 << TGSI_FILE_BUFFER) || - ctx.info.indirect_files & (1 << TGSI_FILE_IMAGE)) { - require_glsl_ver(&ctx, 150); - ctx.shader_req_bits |= SHADER_REQ_GPU_SHADER5; - } - if (ctx.info.indirect_files & (1 << TGSI_FILE_SAMPLER)) - ctx.shader_req_bits |= SHADER_REQ_GPU_SHADER5; - - if (!allocate_strbuffers(&ctx)) - goto fail; - - bret = tgsi_iterate_shader(tokens, &ctx.iter); - if (bret == false) - goto fail; - - for (size_t i = 0; i < ARRAY_SIZE(ctx.src_bufs); ++i) - strbuf_free(ctx.src_bufs + i); - - emit_header(&ctx); - emit_ios(&ctx); - - if (strbuf_get_error(&ctx.glsl_hdr)) - goto fail; - - bret = fill_interpolants(&ctx, sinfo); - if (bret == false) - goto fail; - - free(ctx.temp_ranges); - - fill_sinfo(&ctx, sinfo); - set_strbuffers(rctx, &ctx, shader); - - return true; -fail: - strbuf_free(&ctx.glsl_main); - strbuf_free(&ctx.glsl_hdr); - strbuf_free(&ctx.glsl_ver_ext); - free(ctx.so_names); - free(ctx.temp_ranges); - return false; -} - -static void replace_interp(struct vrend_strarray *program, const char *var_name, - const char *pstring, const char *auxstring) { - int mylen = strlen(INTERP_PREFIX) + strlen("out float "); - - char *ptr = program->strings[SHADER_STRING_HDR].buf; - do { - char *p = strstr(ptr, var_name); - if (!p) - break; - - ptr = p - mylen; - - memset(ptr, ' ', strlen(INTERP_PREFIX)); - memcpy(ptr, pstring, strlen(pstring)); - memcpy(ptr + strlen(pstring), auxstring, strlen(auxstring)); - - ptr = p + strlen(var_name); - } while (1); -} - -static const char *gpu_shader5_and_msinterp_string = - "#extension GL_OES_gpu_shader5 : require\n" - "#extension GL_OES_shader_multisample_interpolation : require\n"; - -static void require_gpu_shader5_and_msinterp(struct vrend_strarray *program) { - strbuf_append(&program->strings[SHADER_STRING_VER_EXT], - gpu_shader5_and_msinterp_string); -} - -bool vrend_patch_vertex_shader_interpolants( - MAYBE_UNUSED struct vrend_context *rctx, struct vrend_shader_cfg *cfg, - struct vrend_strarray *prog_strings, struct vrend_shader_info *vs_info, - struct vrend_shader_info *fs_info, const char *oprefix, bool flatshade) { - int i; - const char *pstring, *auxstring; - char glsl_name[64]; - if (!vs_info || !fs_info) - return true; - - if (!fs_info->interpinfo) - return true; - - if (fs_info->has_sample_input) { - if (cfg->glsl_version < 320) - require_gpu_shader5_and_msinterp(prog_strings); - } - - for (i = 0; i < fs_info->num_interps; i++) { - pstring = - get_interp_string(cfg, fs_info->interpinfo[i].interpolate, flatshade); - if (!pstring) - continue; - - auxstring = get_aux_string(fs_info->interpinfo[i].location); - - switch (fs_info->interpinfo[i].semantic_name) { - case TGSI_SEMANTIC_COLOR: - case TGSI_SEMANTIC_BCOLOR: - /* color is a bit trickier */ - if (fs_info->glsl_ver < 140) { - if (fs_info->interpinfo[i].semantic_index == 1) { - replace_interp(prog_strings, "gl_FrontSecondaryColor", pstring, - auxstring); - replace_interp(prog_strings, "gl_BackSecondaryColor", pstring, - auxstring); - } else { - replace_interp(prog_strings, "gl_FrontColor", pstring, auxstring); - replace_interp(prog_strings, "gl_BackColor", pstring, auxstring); - } - } else { - snprintf(glsl_name, 64, "ex_c%d", - fs_info->interpinfo[i].semantic_index); - replace_interp(prog_strings, glsl_name, pstring, auxstring); - snprintf(glsl_name, 64, "ex_bc%d", - fs_info->interpinfo[i].semantic_index); - replace_interp(prog_strings, glsl_name, pstring, auxstring); - } - break; - case TGSI_SEMANTIC_GENERIC: - snprintf(glsl_name, 64, "%s_g%d", oprefix, - fs_info->interpinfo[i].semantic_index); - replace_interp(prog_strings, glsl_name, pstring, auxstring); - break; - default: - return false; - } - } - - return true; -} - -static boolean iter_vs_declaration(struct tgsi_iterate_context *iter, - struct tgsi_full_declaration *decl) { - struct dump_ctx *ctx = (struct dump_ctx *)iter; - - const char *shader_in_prefix = "vso"; - const char *shader_out_prefix = "tco"; - const char *name_prefix = ""; - unsigned i; - unsigned mask_temp; - - // Generate a shader that passes through all VS outputs - if (decl->Declaration.File == TGSI_FILE_OUTPUT) { - for (uint32_t j = 0; j < ctx->num_inputs; j++) { - if (ctx->inputs[j].name == decl->Semantic.Name && - ctx->inputs[j].sid == decl->Semantic.Index && - ctx->inputs[j].first == decl->Range.First && - ctx->inputs[j].usage_mask == decl->Declaration.UsageMask && - ((!decl->Declaration.Array && ctx->inputs[j].array_id == 0) || - (ctx->inputs[j].array_id == decl->Array.ArrayID))) - return true; - } - i = ctx->num_inputs++; - - ctx->inputs[i].name = decl->Semantic.Name; - ctx->inputs[i].sid = decl->Semantic.Index; - ctx->inputs[i].interpolate = decl->Interp.Interpolate; - ctx->inputs[i].location = decl->Interp.Location; - ctx->inputs[i].first = decl->Range.First; - ctx->inputs[i].layout_location = 0; - ctx->inputs[i].last = decl->Range.Last; - ctx->inputs[i].array_id = decl->Declaration.Array ? decl->Array.ArrayID : 0; - ctx->inputs[i].usage_mask = mask_temp = decl->Declaration.UsageMask; - u_bit_scan_consecutive_range(&mask_temp, &ctx->inputs[i].swizzle_offset, - &ctx->inputs[i].num_components); - - ctx->inputs[i].glsl_predefined_no_emit = false; - ctx->inputs[i].glsl_no_index = false; - ctx->inputs[i].override_no_wm = ctx->inputs[i].num_components == 1; - ctx->inputs[i].glsl_gl_block = false; - - switch (ctx->inputs[i].name) { - case TGSI_SEMANTIC_PSIZE: - name_prefix = "gl_PointSize"; - ctx->inputs[i].glsl_predefined_no_emit = true; - ctx->inputs[i].glsl_no_index = true; - ctx->inputs[i].override_no_wm = true; - ctx->inputs[i].glsl_gl_block = true; - ctx->shader_req_bits |= SHADER_REQ_PSIZE; - break; - - case TGSI_SEMANTIC_CLIPDIST: - name_prefix = "gl_ClipDistance"; - ctx->inputs[i].glsl_predefined_no_emit = true; - ctx->inputs[i].glsl_no_index = true; - ctx->inputs[i].glsl_gl_block = true; - ctx->num_in_clip_dist += - 4 * (ctx->inputs[i].last - ctx->inputs[i].first + 1); - ctx->shader_req_bits |= SHADER_REQ_CLIP_DISTANCE; - if (ctx->inputs[i].last != ctx->inputs[i].first) - ctx->guest_sent_io_arrays = true; - break; - - case TGSI_SEMANTIC_POSITION: - name_prefix = "gl_Position"; - ctx->inputs[i].glsl_predefined_no_emit = true; - ctx->inputs[i].glsl_no_index = true; - ctx->inputs[i].glsl_gl_block = true; - break; - - case TGSI_SEMANTIC_PATCH: - case TGSI_SEMANTIC_GENERIC: - if (ctx->inputs[i].first != ctx->inputs[i].last || - ctx->inputs[i].array_id > 0) { - ctx->guest_sent_io_arrays = true; - } - break; - } - - memcpy(&ctx->outputs[i], &ctx->inputs[i], sizeof(struct vrend_shader_io)); - - if (ctx->inputs[i].glsl_no_index) { - snprintf(ctx->inputs[i].glsl_name, 128, "%s", name_prefix); - snprintf(ctx->outputs[i].glsl_name, 128, "%s", name_prefix); - } else { - if (ctx->inputs[i].name == TGSI_SEMANTIC_FOG) { - ctx->inputs[i].usage_mask = 0xf; - ctx->inputs[i].num_components = 4; - ctx->inputs[i].swizzle_offset = 0; - ctx->inputs[i].override_no_wm = false; - snprintf(ctx->inputs[i].glsl_name, 64, "%s_f%d", shader_in_prefix, - ctx->inputs[i].sid); - snprintf(ctx->outputs[i].glsl_name, 64, "%s_f%d", shader_out_prefix, - ctx->inputs[i].sid); - } else if (ctx->inputs[i].name == TGSI_SEMANTIC_COLOR) { - snprintf(ctx->inputs[i].glsl_name, 64, "%s_c%d", shader_in_prefix, - ctx->inputs[i].sid); - snprintf(ctx->outputs[i].glsl_name, 64, "%s_c%d", shader_out_prefix, - ctx->inputs[i].sid); - } else if (ctx->inputs[i].name == TGSI_SEMANTIC_GENERIC) { - snprintf(ctx->inputs[i].glsl_name, 64, "%s_g%dA%d_%x", shader_in_prefix, - ctx->inputs[i].sid, ctx->inputs[i].array_id, - ctx->inputs[i].usage_mask); - snprintf(ctx->outputs[i].glsl_name, 64, "%s_g%dA%d_%x", - shader_out_prefix, ctx->inputs[i].sid, ctx->inputs[i].array_id, - ctx->inputs[i].usage_mask); - } else if (ctx->inputs[i].name == TGSI_SEMANTIC_PATCH) { - snprintf(ctx->inputs[i].glsl_name, 64, "%s_p%dA%d_%x", shader_in_prefix, - ctx->inputs[i].sid, ctx->inputs[i].array_id, - ctx->inputs[i].usage_mask); - snprintf(ctx->outputs[i].glsl_name, 64, "%s_p%dA%d_%x", - shader_out_prefix, ctx->inputs[i].sid, ctx->inputs[i].array_id, - ctx->inputs[i].usage_mask); - } else { - snprintf(ctx->outputs[i].glsl_name, 64, "%s_%d", shader_in_prefix, - ctx->inputs[i].first); - snprintf(ctx->inputs[i].glsl_name, 64, "%s_%d", shader_out_prefix, - ctx->inputs[i].first); - } - } - } - return true; -} - -bool vrend_shader_create_passthrough_tcs( - struct vrend_context *rctx, struct vrend_shader_cfg *cfg, - struct tgsi_token *vs_tokens, struct vrend_shader_key *key, - const float tess_factors[6], struct vrend_shader_info *sinfo, - struct vrend_strarray *shader, int vertices_per_patch) { - struct dump_ctx ctx; - - memset(&ctx, 0, sizeof(struct dump_ctx)); - - ctx.prog_type = TGSI_PROCESSOR_TESS_CTRL; - ctx.cfg = cfg; - ctx.key = key; - ctx.iter.iterate_declaration = iter_vs_declaration; - ctx.ssbo_array_base = 0xffffffff; - ctx.ssbo_atomic_array_base = 0xffffffff; - ctx.has_sample_input = false; - - if (!allocate_strbuffers(&ctx)) - goto fail; - - tgsi_iterate_shader(vs_tokens, &ctx.iter); - - /* What is the default on GL? */ - ctx.tcs_vertices_out = vertices_per_patch; - - ctx.num_outputs = ctx.num_inputs; - - handle_io_arrays(&ctx); - - emit_header(&ctx); - emit_ios(&ctx); - - emit_buf(&ctx, "void main() {\n"); - - for (unsigned int i = 0; i < ctx.num_inputs; ++i) { - const char *out_prefix = ""; - const char *in_prefix = ""; - - const char *postfix = ""; - - if (ctx.inputs[i].glsl_gl_block) { - out_prefix = "gl_out[gl_InvocationID]."; - in_prefix = "gl_in[gl_InvocationID]."; - } else { - postfix = "[gl_InvocationID]"; - } - - if (ctx.inputs[i].first == ctx.inputs[i].last) { - emit_buff(&ctx, "%s%s%s = %s%s%s;\n", out_prefix, - ctx.outputs[i].glsl_name, postfix, in_prefix, - ctx.inputs[i].glsl_name, postfix); - } else { - unsigned size = ctx.inputs[i].last == ctx.inputs[i].first + 1; - for (unsigned int k = 0; k < size; ++k) { - emit_buff(&ctx, "%s%s%s[%d] = %s%s%s[%d];\n", out_prefix, - ctx.outputs[i].glsl_name, postfix, k, in_prefix, - ctx.inputs[i].glsl_name, postfix, k); - } - } - } - - for (int i = 0; i < 4; ++i) - emit_buff(&ctx, "gl_TessLevelOuter[%d] = %f;\n", i, tess_factors[i]); - - for (int i = 0; i < 2; ++i) - emit_buff(&ctx, "gl_TessLevelInner[%d] = %f;\n", i, tess_factors[i + 4]); - - emit_buf(&ctx, "}\n"); - - fill_sinfo(&ctx, sinfo); - set_strbuffers(rctx, &ctx, shader); - return true; -fail: - strbuf_free(&ctx.glsl_main); - strbuf_free(&ctx.glsl_hdr); - strbuf_free(&ctx.glsl_ver_ext); - free(ctx.so_names); - free(ctx.temp_ranges); - return false; -} diff --git a/app/src/main/cpp/virglrenderer/src/vrend_shader.h b/app/src/main/cpp/virglrenderer/src/vrend_shader.h deleted file mode 100644 index 68091ab9a..000000000 --- a/app/src/main/cpp/virglrenderer/src/vrend_shader.h +++ /dev/null @@ -1,166 +0,0 @@ -/************************************************************************** - * - * Copyright (C) 2014 Red Hat Inc. - * - * 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. - * - **************************************************************************/ - -#ifndef VREND_SHADER_H -#define VREND_SHADER_H - -#include "pipe/p_shader_tokens.h" -#include "pipe/p_state.h" - -#include "vrend_strbuf.h" -/* need to store patching info for interpolation */ -struct vrend_interp_info { - int semantic_name; - int semantic_index; - int interpolate; - unsigned location; -}; - -struct vrend_array { - int first; - int array_size; -}; - -struct vrend_layout_info { - unsigned name; - int sid; - int location; - int array_id; - int usage_mask; -}; - -struct vrend_shader_info { - uint32_t samplers_used_mask; - uint32_t images_used_mask; - uint32_t ubo_used_mask; - uint32_t ssbo_used_mask; - uint32_t num_generic_and_patch_outputs; - bool guest_sent_io_arrays; - struct vrend_layout_info generic_outputs_layout[64]; - int num_consts; - int num_inputs; - int num_interps; - int num_outputs; - bool ubo_indirect; - uint8_t num_indirect_generic_outputs; - uint8_t num_indirect_patch_outputs; - uint8_t num_indirect_generic_inputs; - uint8_t num_indirect_patch_inputs; - uint32_t generic_inputs_emitted_mask; - int num_ucp; - int glsl_ver; - bool has_pervertex_out; - bool has_sample_input; - uint8_t num_clip_out; - uint8_t num_cull_out; - uint32_t shadow_samp_mask; - int gs_out_prim; - int tes_prim; - bool tes_point_mode; - uint32_t attrib_input_mask; - - struct vrend_array *sampler_arrays; - int num_sampler_arrays; - - struct vrend_array *image_arrays; - int num_image_arrays; - - struct pipe_stream_output_info so_info; - - struct vrend_interp_info *interpinfo; - char **so_names; - uint64_t invariant_outputs; -}; - -struct vrend_shader_key { - uint32_t coord_replace; - bool pstipple_tex; - bool add_alpha_test; - bool color_two_side; - uint8_t alpha_test; - uint8_t clip_plane_enable; - bool gs_present; - bool tcs_present; - bool tes_present; - bool flatshade; - bool prev_stage_pervertex_out; - bool guest_sent_io_arrays; - bool fs_logicop_enabled; - bool fs_logicop_emulate_coherent; - enum pipe_logicop fs_logicop_func; - uint8_t surface_component_bits[PIPE_MAX_COLOR_BUFS]; - - uint32_t num_prev_generic_and_patch_outputs; - struct vrend_layout_info prev_stage_generic_and_patch_outputs_layout[64]; - - uint8_t prev_stage_num_clip_out; - uint8_t prev_stage_num_cull_out; - float alpha_ref_val; - uint32_t cbufs_are_a8_bitmask; - uint8_t num_indirect_generic_outputs; - uint8_t num_indirect_patch_outputs; - uint8_t num_indirect_generic_inputs; - uint8_t num_indirect_patch_inputs; - uint32_t generic_outputs_expected_mask; - uint8_t fs_swizzle_output_rgb_to_bgr; -}; - -struct vrend_shader_cfg { - int glsl_version; - int max_draw_buffers; - bool use_explicit_locations; - bool has_es31_compat; -}; - -struct vrend_context; - -#define SHADER_MAX_STRINGS 3 -#define SHADER_STRING_VER_EXT 0 -#define SHADER_STRING_HDR 1 - -bool vrend_patch_vertex_shader_interpolants( - struct vrend_context *rctx, struct vrend_shader_cfg *cfg, - struct vrend_strarray *shader, struct vrend_shader_info *vs_info, - struct vrend_shader_info *fs_info, const char *oprefix, bool flatshade); - -bool vrend_convert_shader(struct vrend_context *rctx, - struct vrend_shader_cfg *cfg, - const struct tgsi_token *tokens, - uint32_t req_local_mem, struct vrend_shader_key *key, - struct vrend_shader_info *sinfo, - struct vrend_strarray *shader); - -const char *vrend_shader_samplertypeconv(int sampler_type); - -char vrend_shader_samplerreturnconv(enum tgsi_return_type type); - -int vrend_shader_lookup_sampler_array(struct vrend_shader_info *sinfo, - int index); - -bool vrend_shader_create_passthrough_tcs( - struct vrend_context *ctx, struct vrend_shader_cfg *cfg, - struct tgsi_token *vs_info, struct vrend_shader_key *key, - const float tess_factors[6], struct vrend_shader_info *sinfo, - struct vrend_strarray *shader, int vertices_per_patch); -#endif diff --git a/app/src/main/cpp/virglrenderer/src/vrend_strbuf.h b/app/src/main/cpp/virglrenderer/src/vrend_strbuf.h deleted file mode 100644 index 5fc6fe371..000000000 --- a/app/src/main/cpp/virglrenderer/src/vrend_strbuf.h +++ /dev/null @@ -1,182 +0,0 @@ -/************************************************************************** - * - * Copyright (C) 2019 Red Hat Inc. - * - * 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. - * - **************************************************************************/ -#ifndef VREND_STRBUF_H -#define VREND_STRBUF_H - -#include "util/u_math.h" -#include -#include -#include -#include -#include - -/* shader string buffer */ -struct vrend_strbuf { - /* NULL terminated string storage */ - char *buf; - /* allocation size (must be >= strlen(str) + 1) */ - size_t alloc_size; - /* size of string stored without terminating NULL */ - size_t size; - bool error_state; -}; - -static inline void strbuf_set_error(struct vrend_strbuf *sb) { - sb->error_state = true; -} - -static inline bool strbuf_get_error(struct vrend_strbuf *sb) { - return sb->error_state; -} - -static inline size_t strbuf_get_len(struct vrend_strbuf *sb) { - return sb->size; -} - -static inline void strbuf_free(struct vrend_strbuf *sb) { free(sb->buf); } - -static inline bool strbuf_alloc(struct vrend_strbuf *sb, int initial_size) { - sb->buf = malloc(initial_size); - if (!sb->buf) - return false; - sb->alloc_size = initial_size; - sb->buf[0] = 0; - sb->error_state = false; - sb->size = 0; - return true; -} - -/* this might need tuning */ -#define STRBUF_MIN_MALLOC 1024 - -static inline bool strbuf_grow(struct vrend_strbuf *sb, int len) { - if (sb->size + len + 1 > sb->alloc_size) { - /* Reallocate to the larger size of current alloc + min realloc, - * or the resulting string size if larger. - */ - size_t new_size = - MAX2(sb->size + len + 1, sb->alloc_size + STRBUF_MIN_MALLOC); - char *new = realloc(sb->buf, new_size); - if (!new) { - strbuf_set_error(sb); - return false; - } - sb->buf = new; - sb->alloc_size = new_size; - } - return true; -} - -static inline void strbuf_append_buffer(struct vrend_strbuf *sb, - const char *data, size_t len) { - assert(!memchr(data, '\0', len)); - if (strbuf_get_error(sb) || !strbuf_grow(sb, len)) - return; - memcpy(sb->buf + sb->size, data, len); - sb->size += len; - sb->buf[sb->size] = '\0'; -} - -static inline void strbuf_append(struct vrend_strbuf *sb, const char *addstr) { - strbuf_append_buffer(sb, addstr, strlen(addstr)); -} - -static inline void strbuf_vappendf(struct vrend_strbuf *sb, const char *fmt, - va_list ap) { - va_list cp; - va_copy(cp, ap); - - int len = vsnprintf(sb->buf + sb->size, sb->alloc_size - sb->size, fmt, ap); - if (len >= (int)(sb->alloc_size - sb->size)) { - if (!strbuf_grow(sb, len)) - return; - vsnprintf(sb->buf + sb->size, sb->alloc_size - sb->size, fmt, cp); - } - sb->size += len; -} - -__attribute__((format(printf, 2, 3))) static inline void -strbuf_appendf(struct vrend_strbuf *sb, const char *fmt, ...) { - va_list va; - va_start(va, fmt); - strbuf_vappendf(sb, fmt, va); - va_end(va); -} - -static inline void strbuf_vfmt(struct vrend_strbuf *sb, const char *fmt, - va_list ap) { - va_list cp; - va_copy(cp, ap); - - int len = vsnprintf(sb->buf, sb->alloc_size, fmt, ap); - if (len >= (int)(sb->alloc_size)) { - if (!strbuf_grow(sb, len)) - return; - vsnprintf(sb->buf, sb->alloc_size, fmt, cp); - } - sb->size = len; -} - -__attribute__((format(printf, 2, 3))) static inline void -strbuf_fmt(struct vrend_strbuf *sb, const char *fmt, ...) { - va_list va; - va_start(va, fmt); - strbuf_vfmt(sb, fmt, va); - va_end(va); -} - -struct vrend_strarray { - int num_strings; - int num_alloced_strings; - struct vrend_strbuf *strings; -}; - -static inline bool strarray_alloc(struct vrend_strarray *sa, int init_alloc) { - sa->num_strings = 0; - sa->num_alloced_strings = init_alloc; - sa->strings = calloc(init_alloc, sizeof(struct vrend_strbuf)); - if (!sa->strings) - return false; - return true; -} - -static inline bool strarray_addstrbuf(struct vrend_strarray *sa, - struct vrend_strbuf *sb) { - assert(sa->num_strings < sa->num_alloced_strings); - if (sa->num_strings >= sa->num_alloced_strings) - return false; - sa->strings[sa->num_strings] = *sb; - sa->num_strings++; - return true; -} - -static inline void strarray_free(struct vrend_strarray *sa, bool free_strings) { - if (free_strings) { - for (int i = 0; i < sa->num_strings; i++) - strbuf_free(&sa->strings[i]); - } - free(sa->strings); -} - -#endif diff --git a/app/src/main/cpp/virglrenderer/src/vrend_util.h b/app/src/main/cpp/virglrenderer/src/vrend_util.h deleted file mode 100644 index cdc1772e0..000000000 --- a/app/src/main/cpp/virglrenderer/src/vrend_util.h +++ /dev/null @@ -1,107 +0,0 @@ -/************************************************************************** - * - * Copyright (C) 2019 Chromium. - * - * 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. - * - **************************************************************************/ -#ifndef VREND_UTIL_H -#define VREND_UTIL_H - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#define BIT(n) (UINT32_C(1) << (n)) -#define printf(...) \ - __android_log_print(ANDROID_LOG_DEBUG, "System.out", __VA_ARGS__); - -#define GL_TEXTURE_1D 0x0DE0 -#define GL_TEXTURE_RECTANGLE 0x84F5 -#define GL_TEXTURE_1D_ARRAY 0x8C18 -#define GL_QUADS 0x0007 -#define GL_QUAD_STRIP 0x0008 -#define GL_POLYGON 0x0009 - -static inline bool has_bit(uint32_t mask, uint32_t bit) { - return (mask & bit) != 0; -} - -static inline bool is_only_bit(uint32_t mask, uint32_t bit) { - return (mask == bit); -} - -static int vrend_gl_version() { - const char *version = (const char *)glGetString(GL_VERSION); - int major, minor; - - if (!version) - return 0; - - while (!isdigit(*version) && *version != '\0') - version++; - - sscanf(version, "%i.%i", &major, &minor); - return 10 * major + minor; -} - -static bool vrend_has_gl_extension(const char *ext) { - int num_extensions; - int i; - - glGetIntegerv(GL_NUM_EXTENSIONS, &num_extensions); - if (num_extensions == 0) - return false; - - for (i = 0; i < num_extensions; i++) { - const char *gl_ext = (const char *)glGetStringi(GL_EXTENSIONS, i); - if (!gl_ext) - return false; - - if (strcmp(ext, gl_ext) == 0) - return true; - } - - return false; -} - -static void vrend_get_glsl_version(int *glsl_version) { - int major_local, minor_local; - const GLubyte *version_str; - int version; - - version_str = glGetString(GL_SHADING_LANGUAGE_VERSION); - char tmp[20]; - sscanf((const char *)version_str, "%s %s %s %s %i.%i", tmp, tmp, tmp, tmp, - &major_local, &minor_local); - - version = (major_local * 100) + minor_local; - if (glsl_version) - *glsl_version = version; -} - -#endif \ No newline at end of file diff --git a/app/src/main/cpp/vkbasalt b/app/src/main/cpp/vkbasalt new file mode 160000 index 000000000..68843cad7 --- /dev/null +++ b/app/src/main/cpp/vkbasalt @@ -0,0 +1 @@ +Subproject commit 68843cad7f8b4db6ff9806b353776f6974f95e67 diff --git a/app/src/main/cpp/winlator/alsa_client.c b/app/src/main/cpp/winlator/alsa_client.c deleted file mode 100644 index 466b5c97d..000000000 --- a/app/src/main/cpp/winlator/alsa_client.c +++ /dev/null @@ -1,140 +0,0 @@ -#include -#include - -#define WAIT_COMPLETION_TIMEOUT 100 * 1000000L - -enum Format { U8, S16LE, S16BE, FLOATLE, FLOATBE }; - -static aaudio_format_t toAAudioFormat(int format) { - switch (format) { - case FLOATLE: - case FLOATBE: - return AAUDIO_FORMAT_PCM_FLOAT; - case U8: - return AAUDIO_FORMAT_UNSPECIFIED; - case S16LE: - case S16BE: - default: - return AAUDIO_FORMAT_PCM_I16; - } -} - -static AAudioStream *aaudioCreate(int32_t format, int8_t channelCount, - int32_t sampleRate, int32_t bufferSize) { - aaudio_result_t result; - AAudioStreamBuilder *builder; - AAudioStream *stream; - - result = AAudio_createStreamBuilder(&builder); - if (result != AAUDIO_OK) - return NULL; - - AAudioStreamBuilder_setPerformanceMode(builder, - AAUDIO_PERFORMANCE_MODE_LOW_LATENCY); - AAudioStreamBuilder_setFormat(builder, toAAudioFormat(format)); - AAudioStreamBuilder_setChannelCount(builder, channelCount); - AAudioStreamBuilder_setSampleRate(builder, sampleRate); - - result = AAudioStreamBuilder_openStream(builder, &stream); - if (result != AAUDIO_OK) { - AAudioStreamBuilder_delete(builder); - return NULL; - } - - AAudioStream_setBufferSizeInFrames(stream, bufferSize); - - result = AAudioStreamBuilder_delete(builder); - if (result != AAUDIO_OK) - return NULL; - - return stream; -} - -static int aaudioWrite(AAudioStream *aaudioStream, void *buffer, - int numFrames) { - aaudio_result_t framesWritten = AAudioStream_write( - aaudioStream, buffer, numFrames, WAIT_COMPLETION_TIMEOUT); - return framesWritten; -} - -static void aaudioStart(AAudioStream *aaudioStream) { - AAudioStream_requestStart(aaudioStream); - AAudioStream_waitForStateChange(aaudioStream, AAUDIO_STREAM_STATE_STARTING, - NULL, WAIT_COMPLETION_TIMEOUT); -} - -static void aaudioStop(AAudioStream *aaudioStream) { - AAudioStream_requestStop(aaudioStream); - AAudioStream_waitForStateChange(aaudioStream, AAUDIO_STREAM_STATE_STOPPING, - NULL, WAIT_COMPLETION_TIMEOUT); -} - -static void aaudioPause(AAudioStream *aaudioStream) { - AAudioStream_requestPause(aaudioStream); - AAudioStream_waitForStateChange(aaudioStream, AAUDIO_STREAM_STATE_PAUSING, - NULL, WAIT_COMPLETION_TIMEOUT); -} - -static void aaudioFlush(AAudioStream *aaudioStream) { - AAudioStream_requestFlush(aaudioStream); - AAudioStream_waitForStateChange(aaudioStream, AAUDIO_STREAM_STATE_FLUSHING, - NULL, WAIT_COMPLETION_TIMEOUT); -} - -JNIEXPORT jlong JNICALL -Java_com_winlator_cmod_runtime_audio_alsaserver_ALSAClient_create( - JNIEnv *env, jobject obj, jint format, jbyte channelCount, jint sampleRate, - jint bufferSize) { - return (jlong)aaudioCreate(format, channelCount, sampleRate, bufferSize); -} - -JNIEXPORT jint JNICALL -Java_com_winlator_cmod_runtime_audio_alsaserver_ALSAClient_write( - JNIEnv *env, jobject obj, jlong streamPtr, jobject buffer, jint numFrames) { - AAudioStream *aaudioStream = (AAudioStream *)streamPtr; - if (aaudioStream) { - return aaudioWrite(aaudioStream, - (*env)->GetDirectBufferAddress(env, buffer), numFrames); - } else - return -1; -} - -JNIEXPORT void JNICALL -Java_com_winlator_cmod_runtime_audio_alsaserver_ALSAClient_start( - JNIEnv *env, jobject obj, jlong streamPtr) { - AAudioStream *aaudioStream = (AAudioStream *)streamPtr; - if (aaudioStream) - aaudioStart(aaudioStream); -} - -JNIEXPORT void JNICALL -Java_com_winlator_cmod_runtime_audio_alsaserver_ALSAClient_stop( - JNIEnv *env, jobject obj, jlong streamPtr) { - AAudioStream *aaudioStream = (AAudioStream *)streamPtr; - if (aaudioStream) - aaudioStop(aaudioStream); -} - -JNIEXPORT void JNICALL -Java_com_winlator_cmod_runtime_audio_alsaserver_ALSAClient_pause( - JNIEnv *env, jobject obj, jlong streamPtr) { - AAudioStream *aaudioStream = (AAudioStream *)streamPtr; - if (aaudioStream) - aaudioPause(aaudioStream); -} - -JNIEXPORT void JNICALL -Java_com_winlator_cmod_runtime_audio_alsaserver_ALSAClient_flush( - JNIEnv *env, jobject obj, jlong streamPtr) { - AAudioStream *aaudioStream = (AAudioStream *)streamPtr; - if (aaudioStream) - aaudioFlush(aaudioStream); -} - -JNIEXPORT void JNICALL -Java_com_winlator_cmod_runtime_audio_alsaserver_ALSAClient_close( - JNIEnv *env, jobject obj, jlong streamPtr) { - AAudioStream *aaudioStream = (AAudioStream *)streamPtr; - if (aaudioStream) - AAudioStream_close(aaudioStream); -} \ No newline at end of file diff --git a/app/src/main/cpp/winlator/drawable.c b/app/src/main/cpp/winlator/drawable.c index 6b3821924..dc57fe0c0 100644 --- a/app/src/main/cpp/winlator/drawable.c +++ b/app/src/main/cpp/winlator/drawable.c @@ -32,7 +32,7 @@ enum GCFunction { }; static int packColor(int8_t r, int8_t g, int8_t b) { - return ((r & 0xff00) << 8) | (g & 0xff00) | (b >> 8); + return ((uint32_t)(uint8_t)r << 16) | ((uint32_t)(uint8_t)g << 8) | (uint8_t)b; } static void unpackColor(int color, uint8_t *rgba) { diff --git a/app/src/main/cpp/winlator/evshim.c b/app/src/main/cpp/winlator/evshim.c deleted file mode 100644 index 19a4c2600..000000000 --- a/app/src/main/cpp/winlator/evshim.c +++ /dev/null @@ -1,588 +0,0 @@ -/* evshim.c - Multi-Controller & Dynamic SDL Virtual Joystick Shim - * Creates virtual SDL joysticks backed by shared memory for Wine controller - * support - * - * Optimizations: - * - Memory-mapped I/O (mmap) instead of read/write syscalls - * - Single unified polling thread for all controllers - * - Adaptive polling: fast (0.5ms) during activity, slow (4ms) when idle - * - Delta-only updates per axis/button to minimize SDL calls - * - Lock-free design using memory barriers - */ - -#define _GNU_SOURCE -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -/* SDL2 types - minimal forward declarations */ -typedef struct SDL_Joystick SDL_Joystick; -typedef struct { - int major, minor, patch; -} SDL_version; -typedef struct SDL_VirtualJoystickDesc { - uint16_t version; - uint16_t type; - uint16_t naxes; - uint16_t nbuttons; - uint16_t nhats; - uint16_t vendor_id; - uint16_t product_id; - uint16_t padding; - uint32_t button_mask; - uint32_t axis_mask; - const char *name; - void *userdata; - void (*Update)(void *); - void (*SetPlayerIndex)(void *, int); - int (*Rumble)(void *, uint16_t, uint16_t); - int (*RumbleTriggers)(void *, uint16_t, uint16_t); - int (*SetLED)(void *, uint8_t, uint8_t, uint8_t); - int (*SendEffect)(void *, const void *, int); -} SDL_VirtualJoystickDesc; - -#define SDL_VIRTUAL_JOYSTICK_DESC_VERSION 1 -#define SDL_JOYSTICK_TYPE_GAMECONTROLLER 1 -#define SDL_INIT_JOYSTICK 0x00000200 - -static int g_debug_enabled = 0; -static int g_spinwait_enabled = 0; -#define LOGI(...) dprintf(STDOUT_FILENO, __VA_ARGS__) -#define LOGE(...) dprintf(STDERR_FILENO, __VA_ARGS__) -#define LOGD(...) \ - do { \ - if (g_debug_enabled) \ - dprintf(STDOUT_FILENO, __VA_ARGS__); \ - } while (0) - -#define MAX_GAMEPADS 4 -#define GAMEPAD_MEM_SIZE 64 - -#define GAMEPAD_VENDOR_ID 0x1234 -#define GAMEPAD_PRODUCT_ID 0x5678 -#define GAMEPAD_NAME "Generic HID Gamepad" - -/* Adaptive polling intervals */ -#define POLL_FAST_NS 500000L /* 0.5ms = 2000Hz during active input */ -#define POLL_SLOW_NS 4000000L /* 4ms = 250Hz during idle */ -#define IDLE_THRESHOLD 50 /* ~25ms of no activity before slowing down */ - -#define AXIS_DEADZONE 256 /* ~0.8% deadzone to filter stick noise */ - -/* Shared memory layout - must match Android side exactly */ -struct gamepad_io { - int16_t lx, ly, rx, ry, lt, rt; /* 12 bytes: axes */ - uint8_t btn[15]; /* 15 bytes: buttons */ - uint8_t hat; /* 1 byte: hat/dpad */ - uint8_t _padding[4]; /* 4 bytes: alignment */ - uint16_t low_freq_rumble; /* 2 bytes: rumble out */ - uint16_t high_freq_rumble; /* 2 bytes: rumble out */ -}; /* Total: 36 bytes */ - -/* Per-controller state */ -struct controller_state { - SDL_Joystick *js; - volatile struct gamepad_io *mem; /* mmap'd shared memory */ - int mem_fd; - int16_t last_axes[6]; - uint8_t last_btns[15]; - uint8_t last_hat; - int active; -}; - -static int vjoy_ids[MAX_GAMEPADS] = {-1, -1, -1, -1}; -static struct controller_state ctrl[MAX_GAMEPADS] = {0}; -static int g_num_players = 0; -static void *handle = NULL; - -/* SDL function pointers */ -static int (*p_SDL_Init)(uint32_t); -static const char *(*p_SDL_GetError)(void); -static SDL_Joystick *(*p_SDL_JoystickOpen)(int); -static int (*p_SDL_JoystickAttachVirtualEx)(const SDL_VirtualJoystickDesc *); -static int (*p_SDL_JoystickSetVirtualAxis)(SDL_Joystick *, int, int16_t); -static int (*p_SDL_JoystickSetVirtualButton)(SDL_Joystick *, int, uint8_t); -static int (*p_SDL_JoystickSetVirtualHat)(SDL_Joystick *, int, uint8_t); -static void (*p_SDL_PumpEvents)(void); -static void (*p_SDL_Delay)(uint32_t); -static void (*p_SDL_GetVersion)(SDL_version *); - -#define GETFUNCPTR(name) \ - do { \ - if (!(p_##name = (typeof(p_##name))dlsym(handle, #name))) \ - LOGE("Failed to load SDL: %s\n", #name); \ - } while (0) - -/* Portable atomic operations - use builtins if available, else volatile */ -#if defined(__GNUC__) || defined(__clang__) -#define ATOMIC_LOAD(ptr) __atomic_load_n(ptr, __ATOMIC_ACQUIRE) -#define ATOMIC_STORE(ptr, val) __atomic_store_n(ptr, val, __ATOMIC_RELEASE) -#else -/* Fallback for non-GCC/Clang: volatile access + compiler barrier */ -#define ATOMIC_LOAD(ptr) (*(volatile typeof(*(ptr)) *)(ptr)) -#define ATOMIC_STORE(ptr, val) \ - do { \ - *(volatile typeof(*(ptr)) *)(ptr) = (val); \ - __asm__ __volatile__("" ::: "memory"); \ - } while (0) -#endif - -/* Inline deadzone filter */ -static inline int16_t apply_deadzone(int16_t val) { - int16_t abs_val = val < 0 ? -val : val; - return abs_val < AXIS_DEADZONE ? 0 : val; -} - -/* Rumble callback - writes directly to memory-mapped region */ -static int OnRumble(void *userdata, uint16_t low, uint16_t high) { - int idx = (int)(intptr_t)userdata; - if (idx < 0 || idx >= MAX_GAMEPADS || !ctrl[idx].mem) - return -1; - - /* Direct memory write with release semantics for visibility */ - volatile struct gamepad_io *mem = ctrl[idx].mem; - ATOMIC_STORE(&mem->low_freq_rumble, low); - ATOMIC_STORE(&mem->high_freq_rumble, high); - return 0; -} - -/* Unified polling thread - handles all controllers in one tight loop */ -static void *unified_updater(void *arg) { - (void)arg; - struct timespec fast_sleep = {0, POLL_FAST_NS}; - struct timespec slow_sleep = {0, POLL_SLOW_NS}; - int idle_count = 0; - - /* Open all SDL joysticks upfront */ - for (int i = 0; i < g_num_players; i++) { - if (vjoy_ids[i] < 0 || !ctrl[i].mem) - continue; - ctrl[i].js = p_SDL_JoystickOpen(vjoy_ids[i]); - if (!ctrl[i].js) { - LOGE("P%d: SDL_JoystickOpen failed\n", i); - continue; - } - ctrl[i].active = 1; - LOGI("VJOY P%d active\n", i); - } - - LOGI("VJOY adaptive updater (fast=%ldus, slow=%ldus) PID %d\n", - POLL_FAST_NS / 1000, POLL_SLOW_NS / 1000, getpid()); - - for (;;) { - int had_updates = 0; - - /* Process all controllers in a single pass */ - for (int i = 0; i < g_num_players; i++) { - if (!ctrl[i].active) - continue; - - volatile struct gamepad_io *mem = ctrl[i].mem; - SDL_Joystick *js = ctrl[i].js; - - /* Read axes with atomic acquire + deadzone filtering */ - int16_t axes[6]; - axes[0] = apply_deadzone(ATOMIC_LOAD(&mem->lx)); - axes[1] = apply_deadzone(ATOMIC_LOAD(&mem->ly)); - axes[2] = apply_deadzone(ATOMIC_LOAD(&mem->rx)); - axes[3] = apply_deadzone(ATOMIC_LOAD(&mem->ry)); - axes[4] = ATOMIC_LOAD(&mem->lt); /* No deadzone for triggers */ - axes[5] = ATOMIC_LOAD(&mem->rt); - - /* Delta update axes - only call SDL when value changes */ - for (int a = 0; a < 6; a++) { - if (axes[a] != ctrl[i].last_axes[a]) { - p_SDL_JoystickSetVirtualAxis(js, a, axes[a]); - ctrl[i].last_axes[a] = axes[a]; - had_updates = 1; - } - } - - /* Delta update buttons */ - for (int b = 0; b < 15; b++) { - uint8_t btn = ATOMIC_LOAD(&mem->btn[b]); - if (btn != ctrl[i].last_btns[b]) { - p_SDL_JoystickSetVirtualButton(js, b, btn); - ctrl[i].last_btns[b] = btn; - had_updates = 1; - } - } - - /* Delta update hat */ - uint8_t hat = ATOMIC_LOAD(&mem->hat); - if (hat != ctrl[i].last_hat) { - p_SDL_JoystickSetVirtualHat(js, 0, hat); - ctrl[i].last_hat = hat; - had_updates = 1; - } - } - - /* Adaptive timing based on activity */ - if (had_updates) { - idle_count = 0; - if (g_spinwait_enabled) { - sched_yield(); /* Ultra-low latency: just yield CPU briefly */ - } else { - nanosleep(&fast_sleep, NULL); /* 0.5ms during active input */ - } - } else { - idle_count++; - if (idle_count > IDLE_THRESHOLD) { - nanosleep(&slow_sleep, NULL); /* 4ms when idle - saves CPU */ - } else { - nanosleep(&fast_sleep, NULL); /* Stay fast briefly after activity */ - } - } - } - return NULL; -} - -/* Watchdog wrapper - respawns updater thread if it dies unexpectedly */ -static void *watchdog_thread(void *arg) { - (void)arg; - struct timespec check_interval = {1, 0}; /* Check every 1 second */ - - while (1) { - pthread_t tid; - int result = pthread_create(&tid, NULL, unified_updater, NULL); - if (result != 0) { - LOGE("Failed to create updater thread: %d\n", result); - nanosleep(&check_interval, NULL); - continue; - } - - /* Wait for the thread to exit (it shouldn't under normal conditions) */ - void *retval; - pthread_join(tid, &retval); - - /* If we get here, the thread exited unexpectedly - respawn it */ - LOGE("Updater thread exited unexpectedly, respawning in 1s...\n"); - nanosleep(&check_interval, NULL); - } - return NULL; -} - -/* Hot-plug detection - checks for newly connected controllers */ -static char g_data_path[256] = {0}; - -static char *make_virtual_pad_name(void) { return strdup(GAMEPAD_NAME); } - -static void try_attach_controller(int idx) { - if (ctrl[idx].active || !handle) - return; /* Already active or SDL not loaded */ - - char path[300]; - snprintf(path, sizeof path, "%s/gamepad%s.mem", g_data_path, - (idx == 0) ? "" : (char[2]){'0' + idx, '\0'}); - - /* Check if memory file exists now */ - if (access(path, F_OK) != 0) - return; - - /* Try to open and map */ - int fd = open(path, O_RDWR); - if (fd < 0) - return; - - void *mem = - mmap(NULL, GAMEPAD_MEM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - if (mem == MAP_FAILED) { - close(fd); - return; - } - - /* Create virtual joystick */ - SDL_VirtualJoystickDesc d = {0}; - d.version = SDL_VIRTUAL_JOYSTICK_DESC_VERSION; - d.type = SDL_JOYSTICK_TYPE_GAMECONTROLLER; - d.naxes = 6; - d.nbuttons = 15; - d.nhats = 1; - d.vendor_id = GAMEPAD_VENDOR_ID; - d.product_id = GAMEPAD_PRODUCT_ID; - d.Rumble = &OnRumble; - d.userdata = (void *)(intptr_t)idx; - d.name = make_virtual_pad_name(); - - int vjoy_id = p_SDL_JoystickAttachVirtualEx(&d); - if (vjoy_id < 0) { - munmap(mem, GAMEPAD_MEM_SIZE); - close(fd); - return; - } - - /* Open the SDL joystick */ - SDL_Joystick *js = p_SDL_JoystickOpen(vjoy_id); - if (!js) { - munmap(mem, GAMEPAD_MEM_SIZE); - close(fd); - return; - } - - /* Success - store everything */ - ctrl[idx].mem_fd = fd; - ctrl[idx].mem = (volatile struct gamepad_io *)mem; - ctrl[idx].js = js; - vjoy_ids[idx] = vjoy_id; - ctrl[idx].active = 1; - - /* Update player count if needed */ - if (idx >= g_num_players) - g_num_players = idx + 1; - - LOGI("HOTPLUG: P%d connected dynamically\n", idx + 1); -} - -static void *hotplug_thread(void *arg) { - (void)arg; - struct timespec interval = {2, 0}; /* Check every 2 seconds */ - - LOGI("EVSHIM hotplug detection started\n"); - - while (1) { - nanosleep(&interval, NULL); - - /* Check for any inactive slots that might have new files */ - for (int i = 0; i < MAX_GAMEPADS; i++) { - if (!ctrl[i].active) { - try_attach_controller(i); - } - } - } - return NULL; -} - -__attribute__((constructor)) static void initialize_all_pads(void) { - const char *dbg = getenv("EVSHIM_DEBUG"); - g_debug_enabled = dbg && strchr("1yY", *dbg); - - const char *spinwait = getenv("EVSHIM_SPINWAIT"); - g_spinwait_enabled = spinwait && strchr("1yY", *spinwait); - - LOGI("EVSHIM initializing (spinwait=%d)...\n", g_spinwait_enabled); - - handle = dlopen("libSDL2-2.0.so.0", RTLD_LAZY | RTLD_GLOBAL); - if (!handle) { - LOGE("dlopen SDL failed: %s\n", dlerror()); - return; - } - - GETFUNCPTR(SDL_Init); - GETFUNCPTR(SDL_GetError); - GETFUNCPTR(SDL_JoystickOpen); - GETFUNCPTR(SDL_JoystickAttachVirtualEx); - GETFUNCPTR(SDL_JoystickSetVirtualAxis); - GETFUNCPTR(SDL_JoystickSetVirtualButton); - GETFUNCPTR(SDL_JoystickSetVirtualHat); - GETFUNCPTR(SDL_PumpEvents); - GETFUNCPTR(SDL_Delay); - GETFUNCPTR(SDL_GetVersion); - - p_SDL_Init(SDL_INIT_JOYSTICK); - - SDL_version v; - p_SDL_GetVersion(&v); - LOGI("SDL %d.%d.%d bound\n", v.major, v.minor, v.patch); - - g_num_players = - getenv("EVSHIM_MAX_PLAYERS") ? atoi(getenv("EVSHIM_MAX_PLAYERS")) : 1; - if (g_num_players > MAX_GAMEPADS) - g_num_players = MAX_GAMEPADS; - - const char *data_path = getenv("EVSHIM_DATA_PATH"); - if (!data_path) - data_path = "/data/data/com.winlator.cmod/files/imagefs/tmp"; - - /* Store path globally for hotplug detection */ - strncpy(g_data_path, data_path, sizeof(g_data_path) - 1); - - int attached = 0; - for (int i = 0; i < g_num_players; ++i) { - char path[256]; - snprintf(path, sizeof path, "%s/gamepad%s.mem", data_path, - (i == 0) ? "" : (char[2]){'0' + i, '\0'}); - - /* Open for read+write (needed for mmap and rumble writeback) */ - ctrl[i].mem_fd = open(path, O_RDWR); - if (ctrl[i].mem_fd < 0) { - LOGE("P%d: open '%s' failed: %s\n", i, path, strerror(errno)); - continue; - } - - /* Memory-map for zero-copy access - eliminates read() syscall overhead */ - void *mem = mmap(NULL, GAMEPAD_MEM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, - ctrl[i].mem_fd, 0); - if (mem == MAP_FAILED) { - LOGE("P%d: mmap failed: %s\n", i, strerror(errno)); - close(ctrl[i].mem_fd); - ctrl[i].mem_fd = -1; - continue; - } - ctrl[i].mem = (volatile struct gamepad_io *)mem; - - /* Create virtual joystick */ - SDL_VirtualJoystickDesc d = {0}; - d.version = SDL_VIRTUAL_JOYSTICK_DESC_VERSION; - d.type = SDL_JOYSTICK_TYPE_GAMECONTROLLER; - d.naxes = 6; - d.nbuttons = 15; - d.nhats = 1; - d.vendor_id = GAMEPAD_VENDOR_ID; - d.product_id = GAMEPAD_PRODUCT_ID; - d.Rumble = &OnRumble; - d.userdata = (void *)(intptr_t)i; - d.name = make_virtual_pad_name(); - - vjoy_ids[i] = p_SDL_JoystickAttachVirtualEx(&d); - if (vjoy_ids[i] < 0) { - LOGE("P%d: SDL attach failed\n", i); - munmap((void *)ctrl[i].mem, GAMEPAD_MEM_SIZE); - ctrl[i].mem = NULL; - continue; - } - attached++; - } - - /* Start watchdog thread (which manages the updater thread with respawn) */ - if (attached > 0) { - pthread_t watchdog_tid; - pthread_create(&watchdog_tid, NULL, watchdog_thread, NULL); - pthread_detach(watchdog_tid); - LOGI("EVSHIM: %d controller(s) ready\n", attached); - } - - /* Start hotplug detection thread for controllers connected later */ - pthread_t hotplug_tid; - pthread_create(&hotplug_tid, NULL, hotplug_thread, NULL); - pthread_detach(hotplug_tid); -} - -/* Intercept open() to hide /dev/input/event* and prevent conflicts */ -static inline int is_event_node(const char *p) { - return p && !strncmp(p, "/dev/input/event", 16); -} - -typedef int (*open_f)(const char *, int, ...); -static open_f real_open; - -int open(const char *path, int flags, ...) - __attribute__((visibility("default"))); -int open(const char *path, int flags, ...) { - if (is_event_node(path)) { - errno = ENOENT; - return -1; - } - if (!real_open) - real_open = (open_f)dlsym(RTLD_NEXT, "open"); - va_list ap; - va_start(ap, flags); - mode_t mode = (flags & O_CREAT) ? va_arg(ap, mode_t) : 0; - va_end(ap); - return real_open(path, flags, mode); -} - -/* Android 11+ FUSE NOEXEC bypass for Wine */ -#include -#include - -#ifndef ST_NOEXEC -#define ST_NOEXEC 8 -#endif - -/* Cached function pointers - resolved once on first call */ -static int (*real_statfs)(const char *, struct statfs *); -static int (*real_statfs64)(const char *, struct statfs64 *); -static int (*real_statvfs)(const char *, struct statvfs *); -static int (*real_statvfs64)(const char *, struct statvfs64 *); -static int (*real_fstatfs)(int, struct statfs *); -static int (*real_fstatfs64)(int, struct statfs64 *); -static int (*real_fstatvfs)(int, struct statvfs *); -static int (*real_fstatvfs64)(int, struct statvfs64 *); - -__attribute__((visibility("default"))) int statfs(const char *path, - struct statfs *buf) { - if (!real_statfs) - real_statfs = dlsym(RTLD_NEXT, "statfs"); - int res = real_statfs(path, buf); - if (res == 0) - buf->f_type = 0xEF53; - return res; -} - -__attribute__((visibility("default"))) int statfs64(const char *path, - struct statfs64 *buf) { - if (!real_statfs64) - real_statfs64 = dlsym(RTLD_NEXT, "statfs64"); - int res = real_statfs64(path, buf); - if (res == 0) - buf->f_type = 0xEF53; - return res; -} - -__attribute__((visibility("default"))) int statvfs(const char *path, - struct statvfs *buf) { - if (!real_statvfs) - real_statvfs = dlsym(RTLD_NEXT, "statvfs"); - int res = real_statvfs(path, buf); - if (res == 0) - buf->f_flag &= ~ST_NOEXEC; - return res; -} - -__attribute__((visibility("default"))) int statvfs64(const char *path, - struct statvfs64 *buf) { - if (!real_statvfs64) - real_statvfs64 = dlsym(RTLD_NEXT, "statvfs64"); - int res = real_statvfs64(path, buf); - if (res == 0) - buf->f_flag &= ~ST_NOEXEC; - return res; -} - -__attribute__((visibility("default"))) int fstatfs(int fd, struct statfs *buf) { - if (!real_fstatfs) - real_fstatfs = dlsym(RTLD_NEXT, "fstatfs"); - int res = real_fstatfs(fd, buf); - if (res == 0) - buf->f_type = 0xEF53; - return res; -} - -__attribute__((visibility("default"))) int fstatfs64(int fd, - struct statfs64 *buf) { - if (!real_fstatfs64) - real_fstatfs64 = dlsym(RTLD_NEXT, "fstatfs64"); - int res = real_fstatfs64(fd, buf); - if (res == 0) - buf->f_type = 0xEF53; - return res; -} - -__attribute__((visibility("default"))) int fstatvfs(int fd, - struct statvfs *buf) { - if (!real_fstatvfs) - real_fstatvfs = dlsym(RTLD_NEXT, "fstatvfs"); - int res = real_fstatvfs(fd, buf); - if (res == 0) - buf->f_flag &= ~ST_NOEXEC; - return res; -} - -__attribute__((visibility("default"))) int fstatvfs64(int fd, - struct statvfs64 *buf) { - if (!real_fstatvfs64) - real_fstatvfs64 = dlsym(RTLD_NEXT, "fstatvfs64"); - int res = real_fstatvfs64(fd, buf); - if (res == 0) - buf->f_flag &= ~ST_NOEXEC; - return res; -} diff --git a/app/src/main/cpp/winlator/fakeinput.cpp b/app/src/main/cpp/winlator/fakeinput.cpp index c92cc7068..855d5c76e 100644 --- a/app/src/main/cpp/winlator/fakeinput.cpp +++ b/app/src/main/cpp/winlator/fakeinput.cpp @@ -47,21 +47,28 @@ static constexpr const char *GAMEPAD_UNIQ_TEMPLATE = "0000000000%02d"; static constexpr uint8_t GAMEPAD_AXIS_COUNT = 8; static constexpr uint8_t GAMEPAD_BUTTON_COUNT = 11; static constexpr uint32_t FAKE_INPUT_RING_MAGIC = 0x46494252; -static constexpr uint32_t FAKE_INPUT_RING_VERSION = 1; +static constexpr uint32_t FAKE_INPUT_RING_VERSION = 2; static constexpr uint32_t FAKE_INPUT_EVENT_SIZE = sizeof(struct input_event); -static constexpr uint32_t FAKE_INPUT_RING_CAPACITY = 512; +static constexpr uint32_t FAKE_INPUT_RING_CAPACITY = 4096; static constexpr unsigned int FAKE_INPUT_MAJOR = 13; static constexpr unsigned int FAKE_INPUT_EVENT_MINOR_BASE = 64; static constexpr unsigned int FAKE_INPUT_JS_MINOR_BASE = 0; struct FakeInputRingHeader { - uint32_t magic; - uint32_t version; - uint32_t event_size; - uint32_t capacity; - uint64_t write_seq; - uint64_t generation; - uint8_t reserved[32]; + uint32_t magic; // 0 + uint32_t version; // 4 + uint32_t event_size; // 8 + uint32_t capacity; // 12 + uint64_t write_seq; // 16 + uint64_t generation; // 24 + // Authoritative absolute-state snapshot, published by the writer under a + // seqlock (odd snapshot_seq = write in progress). The reader replays it as a + // full keyframe whenever the delta stream could have desynced (open, ring + // overflow) so dropped events can recover without periodic duplicate input. + uint64_t snapshot_seq; // 32 + uint32_t snapshot_buttons; // 40 bit i -> kSnapshotButtons[i] pressed + int16_t snapshot_axes[8]; // 44 values in kSnapshotAxisCodes order + uint8_t reserved[4]; // 60 }; static_assert(sizeof(FakeInputRingHeader) == 64, @@ -80,8 +87,44 @@ struct FakeController { uint64_t read_seq = 0; uint64_t generation = 0; size_t mapping_size = 0; + // Pending keyframe (full absolute-state baseline) currently streaming to the + // guest. The axis/button values are captured from the snapshot when the + // keyframe starts so the frame stays consistent across multi-read delivery. + size_t keyframe_remaining = 0; + int32_t keyframe_axes[8] = {0, 0, 0, 0, 0, 0, 0, 0}; + uint32_t keyframe_buttons = 0; }; +struct NeutralEventSpec { + uint16_t type; + uint16_t code; +}; + +// Event template for a full keyframe: every button, every axis/hat, then a +// SYN_REPORT, in this fixed order. The value carried by each event is filled +// from the authoritative snapshot (see keyframe_value); an all-zero snapshot +// yields the neutral baseline. Replayed on open and ring overflow so dropped +// events cannot leave a guest stuck. +static const NeutralEventSpec kNeutralEvents[] = { + {EV_KEY, BTN_A}, {EV_KEY, BTN_B}, {EV_KEY, BTN_X}, + {EV_KEY, BTN_Y}, {EV_KEY, BTN_TL}, {EV_KEY, BTN_TR}, + {EV_KEY, BTN_SELECT}, {EV_KEY, BTN_START}, {EV_KEY, BTN_MODE}, + {EV_KEY, BTN_THUMBL}, {EV_KEY, BTN_THUMBR}, {EV_ABS, ABS_X}, + {EV_ABS, ABS_Y}, {EV_ABS, ABS_RX}, {EV_ABS, ABS_RY}, + {EV_ABS, ABS_GAS}, {EV_ABS, ABS_BRAKE}, {EV_ABS, ABS_HAT0X}, + {EV_ABS, ABS_HAT0Y}, {EV_SYN, SYN_REPORT}, +}; +static constexpr size_t kNeutralEventCount = + sizeof(kNeutralEvents) / sizeof(kNeutralEvents[0]); + +// Axis layout of FakeInputRingHeader::snapshot_axes (mirrors the Java writer). +static const uint16_t kSnapshotAxisCodes[8] = { + ABS_X, ABS_Y, ABS_RX, ABS_RY, ABS_GAS, ABS_BRAKE, ABS_HAT0X, ABS_HAT0Y}; +// Bit i of FakeInputRingHeader::snapshot_buttons maps to this button code. +static const uint16_t kSnapshotButtons[10] = { + BTN_A, BTN_B, BTN_X, BTN_Y, BTN_TL, + BTN_TR, BTN_SELECT, BTN_START, BTN_THUMBL, BTN_THUMBR}; + static std::unordered_map controller_map; static std::unordered_map ring_paths; static bool ring_paths_loaded = false; @@ -329,6 +372,80 @@ ring_header_is_valid(const FakeInputRingHeader *ring) { ring->capacity == FAKE_INPUT_RING_CAPACITY; } +struct SnapshotState { + uint32_t buttons = 0; + int32_t axes[8] = {0, 0, 0, 0, 0, 0, 0, 0}; +}; + +static long long monotonic_ms(); + +// Read the authoritative absolute-state snapshot using the writer's seqlock. +// Retries on a torn read (snapshot_seq odd or changed mid-read); after a few +// failed attempts returns the neutral baseline rather than spinning. This +// mirrors the publication model already used for write_seq. +__attribute__((visibility("hidden"))) static SnapshotState +read_snapshot(const FakeInputRingHeader *ring) { + SnapshotState out; + for (int attempt = 0; attempt < 8; attempt++) { + uint64_t s1 = __atomic_load_n(&ring->snapshot_seq, __ATOMIC_ACQUIRE); + if (s1 & 1ULL) + continue; // a write is in progress + uint32_t buttons = ring->snapshot_buttons; + int16_t axes[8]; + for (int i = 0; i < 8; i++) + axes[i] = ring->snapshot_axes[i]; + __atomic_thread_fence(__ATOMIC_ACQUIRE); + uint64_t s2 = __atomic_load_n(&ring->snapshot_seq, __ATOMIC_RELAXED); + if (s1 == s2) { + out.buttons = buttons; + for (int i = 0; i < 8; i++) + out.axes[i] = axes[i]; // sign-extend to int32 for the event value + return out; + } + } + return out; +} + +// Capture the current absolute state into the controller so it can be streamed +// as a keyframe independently of the ring. Idempotent w.r.t. an in-flight +// keyframe: callers guard on keyframe_remaining == 0 so a partially delivered +// frame is never restarted mid-stream. +__attribute__((visibility("hidden"))) static void +capture_keyframe(FakeController &fake, const char *reason, int fd) { + SnapshotState snap = read_snapshot(fake.ring); + fake.keyframe_buttons = snap.buttons; + for (int i = 0; i < 8; i++) + fake.keyframe_axes[i] = snap.axes[i]; + fake.keyframe_remaining = kNeutralEventCount; + Logger::log("Fake input keyframe reason=%s fd=%d slot=%d read_seq=%llu " + "write_seq=%llu buttons=0x%03x axes=[%d,%d,%d,%d,%d,%d,%d,%d]\n", + reason ? reason : "unknown", fd, fake.slot, + static_cast(fake.read_seq), + static_cast(ring_write_seq(fake.ring)), + fake.keyframe_buttons, fake.keyframe_axes[0], + fake.keyframe_axes[1], fake.keyframe_axes[2], + fake.keyframe_axes[3], fake.keyframe_axes[4], + fake.keyframe_axes[5], fake.keyframe_axes[6], + fake.keyframe_axes[7]); +} + +// Resolve the value a keyframe event should carry from the captured snapshot. +__attribute__((visibility("hidden"))) static int32_t +keyframe_value(const FakeController &fake, uint16_t type, uint16_t code) { + if (type == EV_KEY) { + for (int i = 0; i < 10; i++) + if (kSnapshotButtons[i] == code) + return (fake.keyframe_buttons >> i) & 1u; + return 0; // e.g. BTN_MODE, which the writer never presses + } + if (type == EV_ABS) { + for (int i = 0; i < 8; i++) + if (kSnapshotAxisCodes[i] == code) + return fake.keyframe_axes[i]; + } + return 0; // SYN / unknown +} + __attribute__((visibility("hidden"))) static int open_fake_input_ring(const char *event, int flags) { int slot = get_event_number(event); @@ -370,6 +487,9 @@ open_fake_input_ring(const char *event, int flags) { controller.mapping_size = FAKE_INPUT_RING_SIZE; controller.read_seq = ring_write_seq(ring); controller.generation = ring_generation(ring); + // Emit the current absolute state as the first frame so a guest that opens + // mid-hold (or reopens after a slot hand-off) starts already in sync. + capture_keyframe(controller, "open", fd); controller_map[fd] = controller; Logger::log("Adding ring-backed controller, fd %d event %s slot %d\n", fd, @@ -408,9 +528,15 @@ fake_fd_has_unread_data(int fd) { uint64_t write_seq = ring_write_seq(fake.ring); if (write_seq < fake.read_seq) fake.read_seq = write_seq; - if (write_seq - fake.read_seq > FAKE_INPUT_RING_CAPACITY) + if (write_seq - fake.read_seq > FAKE_INPUT_RING_CAPACITY) { fake.read_seq = write_seq - FAKE_INPUT_RING_CAPACITY; - return write_seq > fake.read_seq; + if (fake.keyframe_remaining == 0) { + capture_keyframe(fake, "overflow", fd); + } + } + // A pending keyframe counts as readable so poll/blocking reads wake to finish + // flushing it even after the ring itself has drained. + return fake.keyframe_remaining > 0 || write_seq > fake.read_seq; } __attribute__((visibility("hidden"))) static long long @@ -925,6 +1051,7 @@ EXPORT ssize_t read(int fd, void *buf, size_t count) { return -1; } + long backoff_ns = 1000 * 1000; // 1ms initial while (!fake_fd_has_unread_data(fd)) { if (fake_fd_is_stale(fd)) { errno = ENODEV; @@ -939,20 +1066,60 @@ EXPORT ssize_t read(int fd, void *buf, size_t count) { errno = EINTR; return -1; } - struct timespec sleep_time = {0, 5 * 1000 * 1000}; + struct timespec sleep_time = {0, backoff_ns}; nanosleep(&sleep_time, nullptr); + if (backoff_ns < 16 * 1000 * 1000) + backoff_ns *= 2; } uint64_t write_seq = ring_write_seq(fake.ring); - if (write_seq - fake.read_seq > FAKE_INPUT_RING_CAPACITY) + if (write_seq - fake.read_seq > FAKE_INPUT_RING_CAPACITY) { fake.read_seq = write_seq - FAKE_INPUT_RING_CAPACITY; + if (fake.keyframe_remaining == 0) { + capture_keyframe(fake, "overflow", fd); + } + } + + uint8_t *out = static_cast(buf); + size_t out_events = 0; + size_t requested_events = count / FAKE_INPUT_EVENT_SIZE; + + // A keyframe is pending (open / ring overflow). Replay the full + // absolute baseline — every button and axis at its snapshot value — before + // any surviving delta events, so a lost button-up / axis-return can't stick. + // The frame streams across reads of any size: we emit as much as fits and do + // NOT consume the ring until it is fully delivered, so even a + // one-event-at-a-time consumer recovers. keyframe_remaining keeps the fd + // readable (see fake_fd_has_unread_data) so poll wakes us to finish it. + if (fake.keyframe_remaining > 0) { + struct timeval now = {}; + gettimeofday(&now, nullptr); + while (fake.keyframe_remaining > 0 && out_events < requested_events) { + size_t idx = kNeutralEventCount - fake.keyframe_remaining; + struct input_event ev; + memset(&ev, 0, sizeof(ev)); + ev.time = now; + ev.type = kNeutralEvents[idx].type; + ev.code = kNeutralEvents[idx].code; + ev.value = keyframe_value(fake, kNeutralEvents[idx].type, + kNeutralEvents[idx].code); + memcpy(out + (out_events * FAKE_INPUT_EVENT_SIZE), &ev, + FAKE_INPUT_EVENT_SIZE); + out_events++; + fake.keyframe_remaining--; + } + if (fake.keyframe_remaining > 0) { + // Buffer filled before the baseline finished; deliver the remainder (and + // only then fresh events) on subsequent reads. out_events >= 1 here. + return static_cast(out_events * FAKE_INPUT_EVENT_SIZE); + } + } size_t available_events = static_cast(std::min(write_seq - fake.read_seq, FAKE_INPUT_RING_CAPACITY)); - size_t requested_events = count / FAKE_INPUT_EVENT_SIZE; - size_t events_to_read = std::min(requested_events, available_events); - uint8_t *out = static_cast(buf); + size_t events_to_read = + std::min(requested_events - out_events, available_events); const uint8_t *ring_events = reinterpret_cast(fake.ring) + FAKE_INPUT_RING_HEADER_SIZE; @@ -960,13 +1127,14 @@ EXPORT ssize_t read(int fd, void *buf, size_t count) { for (size_t i = 0; i < events_to_read; i++) { size_t event_index = static_cast((fake.read_seq + i) % FAKE_INPUT_RING_CAPACITY); - memcpy(out + (i * FAKE_INPUT_EVENT_SIZE), + memcpy(out + ((out_events + i) * FAKE_INPUT_EVENT_SIZE), ring_events + (event_index * FAKE_INPUT_EVENT_SIZE), FAKE_INPUT_EVENT_SIZE); } fake.read_seq += events_to_read; - return static_cast(events_to_read * FAKE_INPUT_EVENT_SIZE); + return static_cast((out_events + events_to_read) * + FAKE_INPUT_EVENT_SIZE); } return syscall(SYS_read, fd, buf, count); } @@ -1040,6 +1208,7 @@ EXPORT int poll(struct pollfd *fds, nfds_t nfds, int timeout) { return my_poll ? my_poll(fds, nfds, timeout) : -1; const long long deadline_ms = timeout < 0 ? -1 : monotonic_ms() + timeout; + int backoff_ms = 1; while (true) { int ready = 0; @@ -1047,17 +1216,6 @@ EXPORT int poll(struct pollfd *fds, nfds_t nfds, int timeout) { for (nfds_t i = 0; i < nfds; i++) fds[i].revents = 0; - int real_ready = my_poll ? my_poll(real_fds.data(), nfds, 0) : 0; - if (real_ready > 0) { - for (nfds_t i = 0; i < nfds; i++) { - if (!is_fake_input_fd(fds[i].fd)) { - fds[i].revents = real_fds[i].revents; - if (fds[i].revents) - ready++; - } - } - } - for (nfds_t i = 0; i < nfds; i++) { if (!is_fake_input_fd(fds[i].fd)) continue; @@ -1074,6 +1232,43 @@ EXPORT int poll(struct pollfd *fds, nfds_t nfds, int timeout) { ready++; } + int real_timeout = ready > 0 ? 0 : [&] { + if (timeout == 0) return 0; + int remaining = deadline_ms < 0 + ? backoff_ms + : std::min(backoff_ms, (int)(deadline_ms - monotonic_ms())); + return std::max(remaining, 0); + }(); + + int real_ready = my_poll ? my_poll(real_fds.data(), nfds, real_timeout) : 0; + if (real_ready > 0) { + for (nfds_t i = 0; i < nfds; i++) { + if (!is_fake_input_fd(fds[i].fd)) { + fds[i].revents = real_fds[i].revents; + if (fds[i].revents) + ready++; + } + } + } + + if (ready == 0 && real_timeout > 0) { + for (nfds_t i = 0; i < nfds; i++) { + if (!is_fake_input_fd(fds[i].fd)) + continue; + + short revents = 0; + if (fake_fd_is_stale(fds[i].fd)) + revents |= POLLHUP; + if ((fds[i].events & (POLLIN | POLLRDNORM)) && + fake_fd_has_unread_data(fds[i].fd)) + revents |= (fds[i].events & (POLLIN | POLLRDNORM)); + + fds[i].revents = revents; + if (revents) + ready++; + } + } + if (ready > 0) return ready; @@ -1083,8 +1278,8 @@ EXPORT int poll(struct pollfd *fds, nfds_t nfds, int timeout) { if (deadline_ms >= 0 && monotonic_ms() >= deadline_ms) return 0; - struct timespec sleep_time = {0, 5 * 1000 * 1000}; - nanosleep(&sleep_time, nullptr); + if (backoff_ms < 16) + backoff_ms *= 2; } } @@ -1152,6 +1347,7 @@ EXPORT int select(int nfds, fd_set *readfds, fd_set *writefds, const long long timeout_ms = timeval_to_ms(timeout); const long long deadline_ms = timeout_ms < 0 ? -1 : monotonic_ms() + timeout_ms; + int backoff_ms = 1; while (true) { int ready = 0; @@ -1163,16 +1359,37 @@ EXPORT int select(int nfds, fd_set *readfds, fd_set *writefds, if (exceptfds) FD_ZERO(exceptfds); + for (int fd = 0; fd < nfds; fd++) { + if (!is_fake_input_fd(fd)) + continue; + if (readfds && FD_ISSET(fd, &original_readfds) && fake_fd_is_stale(fd)) { + FD_SET(fd, readfds); + ready++; + } else if (readfds && FD_ISSET(fd, &original_readfds) && + fake_fd_has_unread_data(fd)) { + FD_SET(fd, readfds); + ready++; + } + } + + int wait_ms = ready > 0 ? 0 : [&] { + if (timeout_ms == 0) return 0; + int remaining = deadline_ms < 0 + ? backoff_ms + : std::min(backoff_ms, (int)(deadline_ms - monotonic_ms())); + return std::max(remaining, 0); + }(); + struct timeval wait_tv = {wait_ms / 1000, (wait_ms % 1000) * 1000}; + fd_set iter_readfds = real_readfds; fd_set iter_writefds = real_writefds; fd_set iter_exceptfds = real_exceptfds; - struct timeval zero_timeout = {0, 0}; int real_ready = my_select ? my_select(nfds, readfds ? &iter_readfds : nullptr, writefds ? &iter_writefds : nullptr, - exceptfds ? &iter_exceptfds : nullptr, &zero_timeout) + exceptfds ? &iter_exceptfds : nullptr, &wait_tv) : 0; if (real_ready > 0) { @@ -1192,16 +1409,18 @@ EXPORT int select(int nfds, fd_set *readfds, fd_set *writefds, } } - for (int fd = 0; fd < nfds; fd++) { - if (!is_fake_input_fd(fd)) - continue; - if (readfds && FD_ISSET(fd, &original_readfds) && fake_fd_is_stale(fd)) { - FD_SET(fd, readfds); - ready++; - } else if (readfds && FD_ISSET(fd, &original_readfds) && - fake_fd_has_unread_data(fd)) { - FD_SET(fd, readfds); - ready++; + if (ready == 0 && wait_ms > 0) { + for (int fd = 0; fd < nfds; fd++) { + if (!is_fake_input_fd(fd)) + continue; + if (readfds && FD_ISSET(fd, &original_readfds) && fake_fd_is_stale(fd)) { + FD_SET(fd, readfds); + ready++; + } else if (readfds && FD_ISSET(fd, &original_readfds) && + fake_fd_has_unread_data(fd)) { + FD_SET(fd, readfds); + ready++; + } } } @@ -1214,7 +1433,7 @@ EXPORT int select(int nfds, fd_set *readfds, fd_set *writefds, if (deadline_ms >= 0 && monotonic_ms() >= deadline_ms) return 0; - struct timespec sleep_time = {0, 5 * 1000 * 1000}; - nanosleep(&sleep_time, nullptr); + if (backoff_ms < 16) + backoff_ms *= 2; } } diff --git a/app/src/main/cpp/winlator/gpu_image.c b/app/src/main/cpp/winlator/gpu_image.c index 5d8077c1d..eb8e6904b 100644 --- a/app/src/main/cpp/winlator/gpu_image.c +++ b/app/src/main/cpp/winlator/gpu_image.c @@ -1,222 +1,161 @@ -#include -#include -#include - -#define EGL_EGLEXT_PROTOTYPES -#define GL_GLEXT_PROTOTYPES - -#include -#include -#include -#include +// AHardwareBuffer lifecycle for GPUImage. +// +// All EGL/GLES interop has been removed; the Vulkan compositor consumes the AHB directly via +// VK_ANDROID_external_memory_android_hardware_buffer (see vk/vk_image.c). This file is now +// concerned only with allocation, socket import, CPU mapping, and release of the AHB itself. + +#include +#include #include #include #include #include - -#define LOG_TAG "System.out" -#define printf(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) -#define HAL_PIXEL_FORMAT_BGRA_8888 5 - -// Function to create an EGL image from a hardware buffer -EGLImageKHR createImageKHR(AHardwareBuffer *hardwareBuffer, int textureId) { - if (!hardwareBuffer) { - printf("createImageKHR: Invalid AHardwareBuffer pointer\n"); - return NULL; - } - - const EGLint attribList[] = {EGL_IMAGE_PRESERVED_KHR, EGL_FALSE, EGL_NONE}; - - EGLClientBuffer clientBuffer = - eglGetNativeClientBufferANDROID(hardwareBuffer); - if (!clientBuffer) { - printf("Failed to get native client buffer\n"); - return NULL; - } - - EGLDisplay eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); - if (eglDisplay == EGL_NO_DISPLAY) { - printf("Invalid EGLDisplay\n"); - return NULL; - } - - EGLImageKHR imageKHR = - eglCreateImageKHR(eglDisplay, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, - clientBuffer, attribList); - if (!imageKHR) { - printf("Failed to create EGLImageKHR\n"); - return NULL; - } - - glBindTexture(GL_TEXTURE_2D, textureId); - if (glGetError() != GL_NO_ERROR) { - printf("Failed to bind texture\n"); - eglDestroyImageKHR(eglDisplay, imageKHR); - return NULL; - } - - glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, imageKHR); - if (glGetError() != GL_NO_ERROR) { - printf("Failed to bind EGLImage to texture\n"); - eglDestroyImageKHR(eglDisplay, imageKHR); - return NULL; - } - - glBindTexture(GL_TEXTURE_2D, 0); - - return imageKHR; -} - -// Function to create a hardware buffer -AHardwareBuffer *createHardwareBuffer(int width, int height) { - AHardwareBuffer_Desc buffDesc = {}; - buffDesc.width = width; - buffDesc.height = height; - buffDesc.layers = 1; - buffDesc.usage = AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE | - AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN | - AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN; - buffDesc.format = HAL_PIXEL_FORMAT_BGRA_8888; - - AHardwareBuffer *hardwareBuffer = NULL; - if (AHardwareBuffer_allocate(&buffDesc, &hardwareBuffer) != 0) { - printf("Failed to allocate AHardwareBuffer\n"); - return NULL; - } - - return hardwareBuffer; -} - -// JNI method to extract a hardware buffer from a socketpair -JNIEXPORT jlong JNICALL -Java_com_winlator_cmod_runtime_display_renderer_GPUImage_hardwareBufferFromSocket( - JNIEnv *env, jobject obj, jint fd) { - AHardwareBuffer *ahb; - - uint8_t buf = 1; - struct stat fdStat; - - if (fstat(fd, &fdStat) != 0 || !S_ISSOCK(fdStat.st_mode)) { - printf("AHardwareBuffer import fd is not a socketpair"); - return 0; - } - - if ((write(fd, &buf, 1)) == -1) { - printf("Failed to write data to socketpair"); - return 0; - } - - if ((AHardwareBuffer_recvHandleFromUnixSocket(fd, &ahb)) != 0) { - printf("Failed to extract hardware buffer from socketpair"); - return 0; - } - - return (jlong)ahb; -} - -// JNI method to create a hardware buffer -JNIEXPORT jlong JNICALL -Java_com_winlator_cmod_runtime_display_renderer_GPUImage_createHardwareBuffer( - JNIEnv *env, jobject obj, jshort width, jshort height) { - AHardwareBuffer *buffer = createHardwareBuffer(width, height); - if (!buffer) { - printf("Failed to create hardware buffer\n"); - return 0; - } - return (jlong)buffer; -} - -// JNI method to create an EGL image -JNIEXPORT jlong JNICALL -Java_com_winlator_cmod_runtime_display_renderer_GPUImage_createImageKHR( - JNIEnv *env, jobject obj, jlong hardwareBufferPtr, jint textureId) { - AHardwareBuffer *hardwareBuffer = (AHardwareBuffer *)hardwareBufferPtr; - if (!hardwareBuffer) { - printf("Invalid AHardwareBuffer pointer\n"); - return 0; - } - return (jlong)createImageKHR(hardwareBuffer, textureId); -} - -// JNI method to destroy a hardware buffer -JNIEXPORT void JNICALL -Java_com_winlator_cmod_runtime_display_renderer_GPUImage_destroyHardwareBuffer( - JNIEnv *env, jobject obj, jlong hardwareBufferPtr, jboolean locked) { - AHardwareBuffer *hardwareBuffer = (AHardwareBuffer *)hardwareBufferPtr; - if (hardwareBuffer) { - if (locked) { - AHardwareBuffer_unlock(hardwareBuffer, NULL); + +#define LOG_TAG "GPUImage" +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) +#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) +#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__) + +#define HAL_PIXEL_FORMAT_BGRA_8888 5 + +// Anything outside this set would silently misrender if treated as 32-bit RGBA; reject so +// DRI3 falls back to the SHM path. +static int gpuImageFormatSupported(uint32_t format) { + return format == HAL_PIXEL_FORMAT_BGRA_8888 + || format == AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM + || format == AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM; +} + +// ---------------------------------------------------------------------------- +// Java GPUImage.nativeAhbCreate(short w, short h) -> jlong (AHardwareBuffer*) +// Allocates a CPU-readable + GPU-sampleable BGRA AHB. +// ---------------------------------------------------------------------------- + +JNIEXPORT jlong JNICALL +Java_com_winlator_cmod_runtime_display_renderer_GPUImage_nativeAhbCreate( + JNIEnv* env, jobject obj, jshort width, jshort height) +{ + (void)env; (void)obj; + AHardwareBuffer_Desc desc = {0}; + desc.width = (uint32_t)width; + desc.height = (uint32_t)height; + desc.layers = 1; + desc.format = HAL_PIXEL_FORMAT_BGRA_8888; + desc.usage = AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE + | AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN + | AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN; + + AHardwareBuffer* ahb = NULL; + if (AHardwareBuffer_allocate(&desc, &ahb) != 0 || !ahb) { + LOGW("AHardwareBuffer_allocate failed (%dx%d BGRA)", width, height); + return 0; + } + AHardwareBuffer_Desc out = {0}; + AHardwareBuffer_describe(ahb, &out); + LOGI("AHB allocated: %ux%u stride=%u format=%u usage=0x%llx", + out.width, out.height, out.stride, out.format, (unsigned long long)out.usage); + return (jlong)(intptr_t)ahb; +} + +// ---------------------------------------------------------------------------- +// Java GPUImage.nativeAhbImportFromSocket(int fd) -> jlong (AHardwareBuffer*) +// Reads a handle previously sent via AHardwareBuffer_sendHandleToUnixSocket. +// Reciprocates with a 1-byte ack so the sender can close its end. +// ---------------------------------------------------------------------------- + +JNIEXPORT jlong JNICALL +Java_com_winlator_cmod_runtime_display_renderer_GPUImage_nativeAhbImportFromSocket( + JNIEnv* env, jobject obj, jint fd) +{ + (void)env; (void)obj; + struct stat fdStat; + if (fstat(fd, &fdStat) != 0 || !S_ISSOCK(fdStat.st_mode)) { + LOGW("AHB import fd %d is not a socket", fd); + return 0; + } + uint8_t ack = 1; + if (write(fd, &ack, 1) == -1) { + LOGW("AHB import ack write failed"); + return 0; + } + AHardwareBuffer* ahb = NULL; + if (AHardwareBuffer_recvHandleFromUnixSocket(fd, &ahb) != 0 || !ahb) { + LOGW("AHardwareBuffer_recvHandleFromUnixSocket failed"); + return 0; + } + + AHardwareBuffer_Desc desc = {0}; + AHardwareBuffer_describe(ahb, &desc); + if (!gpuImageFormatSupported(desc.format)) { + LOGW("AHB import rejected: format=%u not supported", desc.format); + AHardwareBuffer_release(ahb); + return 0; + } + if (!(desc.usage & AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE)) { + LOGW("AHB import rejected: usage=0x%llx missing GPU_SAMPLED_IMAGE", + (unsigned long long)desc.usage); + AHardwareBuffer_release(ahb); + return 0; + } + LOGI("AHB received from socket: fd=%d size=%ux%u stride=%u format=%u usage=0x%llx", + fd, desc.width, desc.height, desc.stride, desc.format, + (unsigned long long)desc.usage); + return (jlong)(intptr_t)ahb; +} + +// ---------------------------------------------------------------------------- +// Java GPUImage.nativeAhbLock(long ahb) -> ByteBuffer +// Locks for CPU read+write and reports stride to Java via setStride(). +// Returns a direct ByteBuffer over the mapped pixel data, or null on failure. +// ---------------------------------------------------------------------------- + +JNIEXPORT jobject JNICALL +Java_com_winlator_cmod_runtime_display_renderer_GPUImage_nativeAhbLock( + JNIEnv* env, jobject obj, jlong ahbPtr) +{ + AHardwareBuffer* ahb = (AHardwareBuffer*)(intptr_t)ahbPtr; + if (!ahb) return NULL; + + void* virt = NULL; + int rc = AHardwareBuffer_lock(ahb, + AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN | AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN, + -1, NULL, &virt); + if (rc != 0) { + rc = AHardwareBuffer_lock(ahb, AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN, -1, NULL, &virt); + } + if (rc != 0) { + rc = AHardwareBuffer_lock(ahb, AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN, -1, NULL, &virt); } - AHardwareBuffer_release(hardwareBuffer); - } + if (rc != 0 || !virt) { + LOGW("AHardwareBuffer_lock failed"); + return NULL; + } + + AHardwareBuffer_Desc desc; + AHardwareBuffer_describe(ahb, &desc); + + jclass cls = (*env)->GetObjectClass(env, obj); + jmethodID setStride = (*env)->GetMethodID(env, cls, "setStride", "(S)V"); + if (setStride) (*env)->CallVoidMethod(env, obj, setStride, (jshort)desc.stride); + + jlong size = (jlong)desc.stride * desc.height * 4; + LOGI("AHB CPU mapped: %ux%u stride=%u bytes=%lld", + desc.width, desc.height, desc.stride, (long long)size); + return (*env)->NewDirectByteBuffer(env, virt, size); +} + +// ---------------------------------------------------------------------------- +// Java GPUImage.nativeAhbDestroy(long ahb, boolean locked) +// Unlocks if needed and releases our ref to the AHB. +// ---------------------------------------------------------------------------- + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_display_renderer_GPUImage_nativeAhbDestroy( + JNIEnv* env, jobject obj, jlong ahbPtr, jboolean locked) +{ + (void)env; (void)obj; + AHardwareBuffer* ahb = (AHardwareBuffer*)(intptr_t)ahbPtr; + if (!ahb) return; + if (locked) AHardwareBuffer_unlock(ahb, NULL); + AHardwareBuffer_release(ahb); } - -// JNI method to lock a hardware buffer -JNIEXPORT jobject JNICALL -Java_com_winlator_cmod_runtime_display_renderer_GPUImage_lockHardwareBuffer( - JNIEnv *env, jobject obj, jlong hardwareBufferPtr) { - AHardwareBuffer *hardwareBuffer = (AHardwareBuffer *)hardwareBufferPtr; - if (!hardwareBuffer) { - printf("Invalid AHardwareBuffer pointer\n"); - return NULL; - } - - void *virtualAddr; - int lockResult = - AHardwareBuffer_lock(hardwareBuffer, - AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN | - AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN, - -1, NULL, &virtualAddr); - if (lockResult != 0) { - lockResult = AHardwareBuffer_lock(hardwareBuffer, - AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN, - -1, NULL, &virtualAddr); - } - if (lockResult != 0) { - lockResult = AHardwareBuffer_lock(hardwareBuffer, - AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN, - -1, NULL, &virtualAddr); - } - if (lockResult != 0) { - printf("Failed to lock AHardwareBuffer\n"); - return NULL; - } - - AHardwareBuffer_Desc buffDesc; - AHardwareBuffer_describe(hardwareBuffer, &buffDesc); - - jclass cls = (*env)->GetObjectClass(env, obj); - if (cls == NULL) { - printf("Failed to get Java class reference\n"); - AHardwareBuffer_unlock(hardwareBuffer, NULL); - return NULL; - } - - jmethodID setStride = (*env)->GetMethodID(env, cls, "setStride", "(S)V"); - if (setStride == NULL) { - printf("Failed to get setStride method ID\n"); - AHardwareBuffer_unlock(hardwareBuffer, NULL); - return NULL; - } - (*env)->CallVoidMethod(env, obj, setStride, (jshort)buffDesc.stride); - - jlong size = buffDesc.stride * buffDesc.height * 4; - jobject buffer = (*env)->NewDirectByteBuffer(env, virtualAddr, size); - if (buffer == NULL) { - printf("Failed to create Java ByteBuffer\n"); - AHardwareBuffer_unlock(hardwareBuffer, NULL); - } - - return buffer; -} - -// JNI method to destroy an EGL image -JNIEXPORT void JNICALL -Java_com_winlator_cmod_runtime_display_renderer_GPUImage_destroyImageKHR( - JNIEnv *env, jobject obj, jlong imageKHRPtr) { - EGLImageKHR imageKHR = (EGLImageKHR)imageKHRPtr; - if (imageKHR) { - EGLDisplay eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); - eglDestroyImageKHR(eglDisplay, imageKHR); - } -} diff --git a/app/src/main/cpp/winlator/native_content_io.cpp b/app/src/main/cpp/winlator/native_content_io.cpp new file mode 100644 index 000000000..949b43f42 --- /dev/null +++ b/app/src/main/cpp/winlator/native_content_io.cpp @@ -0,0 +1,927 @@ +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace { + +constexpr const char* kLogTag = "NativeContentIO"; +constexpr size_t kBufferSize = 256 * 1024; +constexpr int64_t kProgressBatchBytes = 8 * 1024 * 1024; +constexpr int64_t kProgressBatchIntervalMs = 100; + +#define NATIVE_LOGE(...) __android_log_print(ANDROID_LOG_ERROR, kLogTag, __VA_ARGS__) +#define NATIVE_LOGW(...) __android_log_print(ANDROID_LOG_WARN, kLogTag, __VA_ARGS__) + +std::once_flag g_curl_global_once; + +static void ensure_curl_global_init() { + std::call_once(g_curl_global_once, [] { curl_global_init(CURL_GLOBAL_DEFAULT); }); +} + +std::string jstr(JNIEnv* env, jstring value) { + if (!value) return {}; + const char* chars = env->GetStringUTFChars(value, nullptr); + if (!chars) return {}; + std::string out(chars); + env->ReleaseStringUTFChars(value, chars); + return out; +} + +bool mkdirs(std::string_view path) { + if (path.empty()) return true; + std::string current; + current.reserve(path.size()); + + size_t pos = 0; + if (path[0] == '/') { + current.push_back('/'); + pos = 1; + } + + while (pos <= path.size()) { + size_t next = path.find('/', pos); + std::string_view part = + path.substr(pos, next == std::string_view::npos ? path.size() - pos : next - pos); + if (!part.empty()) { + if (!current.empty() && current.back() != '/') current.push_back('/'); + current.append(part); + if (::mkdir(current.c_str(), 0771) != 0 && errno != EEXIST) { + return false; + } + } + if (next == std::string_view::npos) break; + pos = next + 1; + } + return true; +} + +bool ensure_parent_dir(const std::string& path) { + const size_t slash = path.find_last_of('/'); + if (slash == std::string::npos || slash == 0) return true; + return mkdirs(std::string_view(path).substr(0, slash)); +} + +std::string join_path(const std::string& base, const std::string& rel) { + if (base.empty()) return rel; + if (base.back() == '/') return base + rel; + return base + "/" + rel; +} + +bool is_safe_relative_path(std::string_view path) { + if (path.empty() || path[0] == '/') return false; + size_t pos = 0; + while (pos <= path.size()) { + size_t next = path.find('/', pos); + std::string_view part = + path.substr(pos, next == std::string_view::npos ? path.size() - pos : next - pos); + if (part == "..") return false; + if (next == std::string_view::npos) break; + pos = next + 1; + } + return true; +} + +std::string clean_entry_name(std::string name) { + while (name.rfind("./", 0) == 0) { + name.erase(0, 2); + } + return name; +} + +std::string trim_trailing_slashes(std::string path) { + while (path.size() > 1 && path.back() == '/') { + path.pop_back(); + } + return path; +} + +bool has_symlink_ancestor( + const std::string& entry_name, + const std::unordered_set& symlink_entries) { + if (symlink_entries.empty()) return false; + std::string path = trim_trailing_slashes(entry_name); + while (true) { + if (symlink_entries.count(path)) return true; + const size_t slash = path.find_last_of('/'); + if (slash == std::string::npos || slash == 0) break; + path.resize(slash); + } + return false; +} + +std::string parent_entry_name(const std::string& entry_name) { + std::string normalized = trim_trailing_slashes(entry_name); + const size_t slash = normalized.find_last_of('/'); + if (slash == std::string::npos) return {}; + return normalized.substr(0, slash); +} + +uint64_t parse_tar_number(const char* field, size_t length) { + if (length == 0) return 0; + const unsigned char first = static_cast(field[0]); + if ((first & 0x80) != 0) { + uint64_t value = first & 0x7f; + for (size_t i = 1; i < length; ++i) { + value = (value << 8) | static_cast(field[i]); + } + return value; + } + + size_t i = 0; + while (i < length && (field[i] == ' ' || field[i] == '\0')) ++i; + uint64_t value = 0; + for (; i < length; ++i) { + if (field[i] < '0' || field[i] > '7') break; + value = (value << 3) + static_cast(field[i] - '0'); + } + return value; +} + +std::string tar_string(const char* field, size_t length) { + size_t n = 0; + while (n < length && field[n] != '\0') ++n; + return std::string(field, n); +} + +std::string read_octal_record_string(std::string data) { + while (!data.empty() && data.back() == '\0') data.pop_back(); + return data; +} + +struct PaxValues { + std::optional path; + std::optional link_path; +}; + +PaxValues parse_pax(std::string_view data) { + PaxValues values; + size_t pos = 0; + while (pos < data.size()) { + size_t space = data.find(' ', pos); + if (space == std::string_view::npos) break; + size_t len = 0; + for (size_t i = pos; i < space; ++i) { + if (data[i] < '0' || data[i] > '9') { + len = 0; + break; + } + len = len * 10 + static_cast(data[i] - '0'); + } + if (len == 0 || pos + len > data.size()) break; + std::string_view record = data.substr(space + 1, pos + len - space - 1); + if (!record.empty() && record.back() == '\n') record.remove_suffix(1); + size_t eq = record.find('='); + if (eq != std::string_view::npos) { + std::string_view key = record.substr(0, eq); + std::string value(record.substr(eq + 1)); + if (key == "path") values.path = std::move(value); + if (key == "linkpath") values.link_path = std::move(value); + } + pos += len; + } + return values; +} + +class Reader { +public: + virtual ~Reader() = default; + virtual ssize_t read(uint8_t* out, size_t length) = 0; + + bool read_exact(uint8_t* out, size_t length) { + size_t total = 0; + while (total < length) { + ssize_t n = read(out + total, length - total); + if (n <= 0) return false; + total += static_cast(n); + } + return true; + } + + bool skip(uint64_t amount) { + uint8_t buffer[8192]; + while (amount > 0) { + const size_t chunk = static_cast(std::min(amount, sizeof(buffer))); + if (!read_exact(buffer, chunk)) return false; + amount -= chunk; + } + return true; + } +}; + +class FileReader final : public Reader { +public: + explicit FileReader(std::string path) : file_(std::fopen(path.c_str(), "rb")) { + if (file_) std::setvbuf(file_, nullptr, _IOFBF, kBufferSize); + } + ~FileReader() override { + if (file_) std::fclose(file_); + } + bool ok() const { return file_ != nullptr; } + ssize_t read(uint8_t* out, size_t length) override { + if (!file_) return -1; + size_t n = std::fread(out, 1, length, file_); + if (n == 0 && std::ferror(file_)) return -1; + return static_cast(n); + } + +private: + FILE* file_ = nullptr; +}; + +class AssetReader final : public Reader { +public: + AssetReader(AAssetManager* manager, std::string path) + : asset_(manager ? AAssetManager_open(manager, path.c_str(), AASSET_MODE_STREAMING) : nullptr) {} + + ~AssetReader() override { + if (asset_) AAsset_close(asset_); + } + + bool ok() const { return asset_ != nullptr; } + + ssize_t read(uint8_t* out, size_t length) override { + if (!asset_) return -1; + return AAsset_read(asset_, out, length); + } + +private: + AAsset* asset_ = nullptr; +}; + +class FileWriter final { +public: + FileWriter(const std::string& path, bool nofollow) { + int flags = O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC; + if (nofollow) flags |= O_NOFOLLOW; + int fd = ::open( + path.c_str(), + flags, + 0660); + if (fd >= 0) file_ = ::fdopen(fd, "wb"); + if (file_) { + std::setvbuf(file_, nullptr, _IONBF, 0); + } else if (fd >= 0) { + ::close(fd); + } + } + + ~FileWriter() { + close(); + } + + bool ok() const { + return file_ != nullptr; + } + + bool write(const uint8_t* data, size_t length) { + return file_ && std::fwrite(data, 1, length, file_) == length; + } + + bool close() { + if (!file_) return true; + FILE* file = file_; + file_ = nullptr; + int fd = ::fileno(file); + if (fd >= 0) { + std::fflush(file); + ::sync_file_range(fd, 0, 0, SYNC_FILE_RANGE_WRITE); + } + return std::fclose(file) == 0; + } + +private: + FILE* file_ = nullptr; +}; + +class XzReader final : public Reader { +public: + explicit XzReader(std::unique_ptr source) : source_(std::move(source)) { + ok_ = lzma_stream_decoder(&strm_, UINT64_MAX, LZMA_CONCATENATED) == LZMA_OK; + } + ~XzReader() override { + if (ok_) lzma_end(&strm_); + } + bool ok() const { return source_ != nullptr && ok_; } + ssize_t read(uint8_t* out, size_t length) override { + if (finished_) return 0; + strm_.next_out = out; + strm_.avail_out = length; + + while (strm_.avail_out > 0) { + if (strm_.avail_in == 0 && !input_finished_) { + ssize_t n = source_->read(input_.data(), input_.size()); + if (n < 0) return -1; + if (n == 0) input_finished_ = true; + strm_.next_in = input_.data(); + strm_.avail_in = static_cast(std::max(n, 0)); + } + + const size_t in_before = strm_.avail_in; + const size_t out_before = length - strm_.avail_out; + lzma_ret ret = lzma_code(&strm_, input_finished_ ? LZMA_FINISH : LZMA_RUN); + if (ret == LZMA_STREAM_END) { + finished_ = true; + break; + } + if (ret != LZMA_OK) { + NATIVE_LOGW("XZ decode failed: %d", static_cast(ret)); + return -1; + } + if (strm_.avail_in == in_before && (length - strm_.avail_out) == out_before) { + NATIVE_LOGW("XZ decoder stalled"); + return -1; + } + } + return static_cast(length - strm_.avail_out); + } + +private: + std::unique_ptr source_; + lzma_stream strm_ = LZMA_STREAM_INIT; + bool ok_ = false; + std::vector input_ = std::vector(1 << 20); + bool input_finished_ = false; + bool finished_ = false; +}; + +class ZstdReader final : public Reader { +public: + explicit ZstdReader(std::unique_ptr source) + : source_(std::move(source)), stream_(ZSTD_createDStream()) { + if (stream_) { + size_t rc = ZSTD_initDStream(stream_); + if (ZSTD_isError(rc)) { + ZSTD_freeDStream(stream_); + stream_ = nullptr; + } + } + } + ~ZstdReader() override { + if (stream_) ZSTD_freeDStream(stream_); + } + bool ok() const { return source_ != nullptr && stream_ != nullptr; } + ssize_t read(uint8_t* out, size_t length) override { + if (finished_) return 0; + ZSTD_outBuffer output{out, length, 0}; + while (output.pos < output.size) { + if (input_.pos == input_.size && !input_finished_) { + ssize_t n = source_->read(input_storage_.data(), input_storage_.size()); + if (n < 0) return -1; + if (n == 0) input_finished_ = true; + input_ = ZSTD_inBuffer{input_storage_.data(), static_cast(std::max(n, 0)), 0}; + } + size_t in_before = input_.pos; + size_t out_before = output.pos; + size_t rc = ZSTD_decompressStream(stream_, &output, &input_); + if (ZSTD_isError(rc)) { + NATIVE_LOGW("Zstd decode failed: %s", ZSTD_getErrorName(rc)); + return -1; + } + frame_finished_ = rc == 0; + if (frame_finished_ && input_finished_ && input_.pos == input_.size) { + finished_ = true; + break; + } + if (input_finished_ && input_.pos == input_.size && output.pos == out_before && !frame_finished_) { + NATIVE_LOGW("Zstd stream ended before frame completion"); + return -1; + } + if (input_.pos == in_before && output.pos == out_before) { + NATIVE_LOGW("Zstd decoder stalled"); + return -1; + } + } + return static_cast(output.pos); + } + +private: + std::unique_ptr source_; + ZSTD_DStream* stream_ = nullptr; + std::vector input_storage_ = std::vector(ZSTD_DStreamInSize()); + ZSTD_inBuffer input_{nullptr, 0, 0}; + bool input_finished_ = false; + bool finished_ = false; + bool frame_finished_ = false; +}; + +class JavaExtractListener { +public: + JavaExtractListener(JNIEnv* env, jobject listener) : env_(env), listener_(listener) { + if (!listener_) return; + jclass listener_cls = env_->FindClass("com/winlator/cmod/shared/util/OnExtractFileListener"); + on_extract_ = + env_->GetMethodID(listener_cls, "onExtractFile", "(Ljava/io/File;J)Ljava/io/File;"); + on_progress_ = + env_->GetMethodID(listener_cls, "onExtractFileProgress", "(Ljava/io/File;J)V"); + maps_files_method_ = env_->GetMethodID(listener_cls, "mapsExtractedFiles", "()Z"); + byte_progress_method_ = env_->GetMethodID(listener_cls, "reportsExtractedBytesOnly", "()Z"); + on_bytes_progress_ = env_->GetMethodID(listener_cls, "onExtractedBytes", "(J)V"); + + if (maps_files_method_) { + maps_files_ = env_->CallBooleanMethod(listener_, maps_files_method_) == JNI_TRUE; + if (env_->ExceptionCheck()) return; + } + if (byte_progress_method_) { + byte_progress_only_ = + env_->CallBooleanMethod(listener_, byte_progress_method_) == JNI_TRUE; + if (env_->ExceptionCheck()) return; + } + + if (maps_files_ || !byte_progress_only_) { + jclass file_cls = env_->FindClass("java/io/File"); + file_class_ = static_cast(env_->NewLocalRef(file_cls)); + file_ctor_ = env_->GetMethodID(file_class_, "", "(Ljava/lang/String;)V"); + get_path_ = env_->GetMethodID(file_class_, "getPath", "()Ljava/lang/String;"); + } + + enabled_ = + (!maps_files_ || (file_class_ && file_ctor_ && get_path_ && on_extract_)) && + (byte_progress_only_ || !listener_ || (file_class_ && file_ctor_ && on_progress_)); + } + + std::optional map(const std::string& destination, int64_t size) { + if (!listener_) return destination; + if (!enabled_) return std::nullopt; + if (!maps_files_) return destination; + + jstring path = env_->NewStringUTF(destination.c_str()); + jobject file = env_->NewObject(file_class_, file_ctor_, path); + env_->DeleteLocalRef(path); + jobject mapped = + env_->CallObjectMethod(listener_, on_extract_, file, static_cast(size)); + env_->DeleteLocalRef(file); + if (env_->ExceptionCheck()) return std::nullopt; + if (!mapped) return std::nullopt; + + auto mapped_path = static_cast(env_->CallObjectMethod(mapped, get_path_)); + env_->DeleteLocalRef(mapped); + if (env_->ExceptionCheck() || !mapped_path) return std::nullopt; + std::string result = jstr(env_, mapped_path); + env_->DeleteLocalRef(mapped_path); + return result; + } + + bool progress(const std::string& destination, int64_t size) { + if (!listener_ || !enabled_) return true; + if (byte_progress_only_) { + pending_progress_bytes_ += std::max(size, 0); + auto now = std::chrono::steady_clock::now(); + int64_t elapsed_ms = + std::chrono::duration_cast(now - last_progress_flush_).count(); + if (pending_progress_bytes_ >= kProgressBatchBytes || elapsed_ms >= kProgressBatchIntervalMs) { + flush_progress(); + } + return !env_->ExceptionCheck(); + } + if (!on_progress_) return true; + + jstring path = env_->NewStringUTF(destination.c_str()); + jobject file = env_->NewObject(file_class_, file_ctor_, path); + env_->DeleteLocalRef(path); + env_->CallVoidMethod(listener_, on_progress_, file, static_cast(size)); + env_->DeleteLocalRef(file); + return !env_->ExceptionCheck(); + } + + bool flush_progress() { + if (!listener_ || !enabled_ || !byte_progress_only_ || !on_bytes_progress_) return true; + if (pending_progress_bytes_ <= 0) return true; + int64_t bytes = pending_progress_bytes_; + pending_progress_bytes_ = 0; + last_progress_flush_ = std::chrono::steady_clock::now(); + env_->CallVoidMethod(listener_, on_bytes_progress_, static_cast(bytes)); + return !env_->ExceptionCheck(); + } + +private: + JNIEnv* env_; + jobject listener_; + jclass file_class_ = nullptr; + jmethodID file_ctor_ = nullptr; + jmethodID get_path_ = nullptr; + jmethodID on_extract_ = nullptr; + jmethodID on_progress_ = nullptr; + jmethodID maps_files_method_ = nullptr; + jmethodID byte_progress_method_ = nullptr; + jmethodID on_bytes_progress_ = nullptr; + bool enabled_ = false; + bool maps_files_ = true; + bool byte_progress_only_ = false; + int64_t pending_progress_bytes_ = 0; + std::chrono::steady_clock::time_point last_progress_flush_ = std::chrono::steady_clock::now(); +}; + +bool read_payload(Reader& reader, uint64_t size, std::string* out) { + if (size > 64 * 1024 * 1024) return false; + out->assign(static_cast(size), '\0'); + if (size > 0 && !reader.read_exact(reinterpret_cast(out->data()), static_cast(size))) { + return false; + } + const uint64_t padding = (512 - (size % 512)) % 512; + return reader.skip(padding); +} + +bool extract_tar( + Reader& reader, + const std::string& destination, + JNIEnv* env, + jobject listener, + bool enforce_safe_symlinks) { + JavaExtractListener java_listener(env, listener); + std::vector header(512); + std::optional next_name; + std::optional next_link; + std::unordered_set symlink_entries; + + std::vector file_buffer(kBufferSize); + std::unordered_set created_dirs; + const auto mkdirs_cached = [&](std::string_view dir) -> bool { + if (dir.empty()) return true; + if (created_dirs.find(std::string(dir)) != created_dirs.end()) return true; + std::string current; + current.reserve(dir.size()); + size_t pos = 0; + if (dir[0] == '/') { + current.push_back('/'); + pos = 1; + } + while (pos <= dir.size()) { + size_t next = dir.find('/', pos); + std::string_view part = + dir.substr(pos, next == std::string_view::npos ? dir.size() - pos : next - pos); + if (!part.empty()) { + if (!current.empty() && current.back() != '/') current.push_back('/'); + current.append(part); + if (created_dirs.insert(current).second) { + if (::mkdir(current.c_str(), 0771) != 0 && errno != EEXIST) return false; + } + } + if (next == std::string_view::npos) break; + pos = next + 1; + } + return true; + }; + const auto ensure_parent_cached = [&](const std::string& path) -> bool { + const size_t slash = path.find_last_of('/'); + if (slash == std::string::npos || slash == 0) return true; + return mkdirs_cached(std::string_view(path).substr(0, slash)); + }; + + while (true) { + if (!reader.read_exact(header.data(), header.size())) return false; + bool all_zero = true; + for (uint8_t b : header) { + if (b != 0) { + all_zero = false; + break; + } + } + if (all_zero) return java_listener.flush_progress(); + + auto* h = reinterpret_cast(header.data()); + std::string name = tar_string(h, 100); + std::string prefix = tar_string(h + 345, 155); + if (!prefix.empty()) name = prefix + "/" + name; + std::string link_name = tar_string(h + 157, 100); + const uint64_t size = parse_tar_number(h + 124, 12); + const uint32_t mode = static_cast(parse_tar_number(h + 100, 8)); + const char type = h[156] == '\0' ? '0' : h[156]; + + if (type == 'L' || type == 'K') { + std::string payload; + if (!read_payload(reader, size, &payload)) return false; + if (type == 'L') next_name = clean_entry_name(read_octal_record_string(std::move(payload))); + if (type == 'K') next_link = read_octal_record_string(std::move(payload)); + continue; + } + + if (type == 'x' || type == 'g') { + std::string payload; + if (!read_payload(reader, size, &payload)) return false; + if (type == 'x') { + PaxValues pax = parse_pax(payload); + if (pax.path) next_name = clean_entry_name(*pax.path); + if (pax.link_path) next_link = *pax.link_path; + } + continue; + } + + if (next_name) { + name = *next_name; + next_name.reset(); + } else { + name = clean_entry_name(std::move(name)); + } + if (next_link) { + link_name = *next_link; + next_link.reset(); + } + + const uint64_t padding = (512 - (size % 512)) % 512; + if (!is_safe_relative_path(name)) { + if (!reader.skip(size + padding)) return false; + continue; + } + + const bool is_symlink = type == '2'; + if (!is_symlink && has_symlink_ancestor(name, symlink_entries)) { + NATIVE_LOGW("skipping archive entry under symlink: %s", name.c_str()); + if (!reader.skip(size + padding)) return false; + continue; + } + + std::string out_path = join_path(destination, name); + auto mapped = java_listener.map(out_path, static_cast(size)); + if (!mapped) { + if (!reader.skip(size + padding)) return false; + continue; + } + out_path = std::move(*mapped); + + if (type == '5') { + if (!mkdirs_cached(out_path)) return false; + } else if (type == '2') { + // Wine prefixes legitimately use links like c: -> ../drive_c and z: -> /. + // Allow the link itself, but never extract later archive entries through it. + std::string parent_name = parent_entry_name(name); + if (!parent_name.empty() && has_symlink_ancestor(parent_name, symlink_entries)) { + NATIVE_LOGW("skipping symlink under symlinked parent: %s", name.c_str()); + if (!reader.skip(size + padding)) return false; + continue; + } + if (!ensure_parent_cached(out_path)) return false; + ::unlink(out_path.c_str()); + if (::symlink(link_name.c_str(), out_path.c_str()) != 0 && errno != EEXIST) { + NATIVE_LOGW("symlink failed for %s: %s", out_path.c_str(), std::strerror(errno)); + } + symlink_entries.insert(trim_trailing_slashes(name)); + } else if (type == '0' || type == '\0') { + if (!ensure_parent_cached(out_path)) return false; + FileWriter out(out_path, enforce_safe_symlinks); + if (!out.ok()) return false; + + uint64_t remaining = size; + bool ok = true; + while (remaining > 0) { + const size_t chunk = static_cast(std::min(remaining, file_buffer.size())); + if (!reader.read_exact(file_buffer.data(), chunk)) { + ok = false; + break; + } + if (!out.write(file_buffer.data(), chunk)) { + ok = false; + break; + } + remaining -= chunk; + } + if (!out.close()) ok = false; + if (!ok) return false; + if ((mode & 0111) != 0) ::chmod(out_path.c_str(), 0771); + if (!java_listener.progress(out_path, static_cast(size))) return false; + if (!reader.skip(padding)) return false; + continue; + } else if (type == '1') { + if (!ensure_parent_cached(out_path)) return false; + ::unlink(out_path.c_str()); + std::string clean_link_name = clean_entry_name(link_name); + if (!is_safe_relative_path(clean_link_name)) { + if (!reader.skip(size + padding)) return false; + continue; + } + if (has_symlink_ancestor(clean_link_name, symlink_entries)) { + NATIVE_LOGW("skipping hard link through symlink ancestor: %s", clean_link_name.c_str()); + if (!reader.skip(size + padding)) return false; + continue; + } + std::string link_path = join_path(destination, clean_link_name); + if (::link(link_path.c_str(), out_path.c_str()) != 0) { + NATIVE_LOGW("hard link failed for %s: %s", out_path.c_str(), std::strerror(errno)); + } + if (!java_listener.progress(out_path, static_cast(size))) return false; + } + + if (!reader.skip(size + padding)) return false; + if (type == '5') ::chmod(out_path.c_str(), 0771); + } +} + +struct DownloadContext { + FILE* file = nullptr; + JNIEnv* env = nullptr; + jobject listener = nullptr; + jmethodID on_progress = nullptr; + std::chrono::steady_clock::time_point last_update = std::chrono::steady_clock::now(); +}; + +size_t curl_write_file(char* ptr, size_t size, size_t nmemb, void* userdata) { + auto* ctx = static_cast(userdata); + const size_t bytes = size * nmemb; + return std::fwrite(ptr, 1, bytes, ctx->file); +} + +int curl_progress(void* userdata, curl_off_t total, curl_off_t now, curl_off_t, curl_off_t) { + auto* ctx = static_cast(userdata); + if (!ctx->listener || !ctx->on_progress) return 0; + + auto current = std::chrono::steady_clock::now(); + if (now == 0 || total == now || + std::chrono::duration_cast(current - ctx->last_update).count() >= 80) { + ctx->env->CallVoidMethod( + ctx->listener, + ctx->on_progress, + static_cast(now), + static_cast(total > 0 ? total : -1)); + ctx->last_update = current; + if (ctx->env->ExceptionCheck()) return 1; + } + return 0; +} + +void configure_curl_common(CURL* curl, const std::string& url, const std::string& ca_bundle) { + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 15L); + curl_easy_setopt(curl, CURLOPT_TIMEOUT, 0L); + curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1L); + curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, 30L); + curl_easy_setopt(curl, CURLOPT_USERAGENT, "WinNative/1.0"); + curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "identity"); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L); + if (!ca_bundle.empty()) curl_easy_setopt(curl, CURLOPT_CAINFO, ca_bundle.c_str()); +} + +} // namespace + +extern "C" JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_shared_io_NativeContentIO_nativeExtractArchive( + JNIEnv* env, jclass, jint type, jstring jsource, jstring jdestination, jobject listener) { + std::string source = jstr(env, jsource); + std::string destination = jstr(env, jdestination); + if (source.empty() || destination.empty()) return JNI_FALSE; + if (!mkdirs(destination)) return JNI_FALSE; + + bool ok = false; + if (type == 0) { + auto raw = std::make_unique(source); + if (!raw->ok()) return JNI_FALSE; + XzReader reader(std::move(raw)); + ok = reader.ok() && extract_tar(reader, destination, env, listener, true); + } else if (type == 1) { + auto raw = std::make_unique(source); + if (!raw->ok()) return JNI_FALSE; + ZstdReader reader(std::move(raw)); + ok = reader.ok() && extract_tar(reader, destination, env, listener, true); + } + if (!ok && env->ExceptionCheck()) return JNI_FALSE; + return ok ? JNI_TRUE : JNI_FALSE; +} + +extern "C" JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_shared_io_NativeContentIO_nativeExtractAsset( + JNIEnv* env, + jclass, + jint type, + jobject jasset_manager, + jstring jasset_file, + jstring jdestination, + jobject listener) { + AAssetManager* manager = AAssetManager_fromJava(env, jasset_manager); + std::string asset_file = jstr(env, jasset_file); + std::string destination = jstr(env, jdestination); + if (!manager || asset_file.empty() || destination.empty()) return JNI_FALSE; + if (!mkdirs(destination)) return JNI_FALSE; + + bool ok = false; + if (type == 0) { + auto raw = std::make_unique(manager, asset_file); + if (!raw->ok()) return JNI_FALSE; + XzReader reader(std::move(raw)); + ok = reader.ok() && extract_tar(reader, destination, env, listener, false); + } else if (type == 1) { + auto raw = std::make_unique(manager, asset_file); + if (!raw->ok()) return JNI_FALSE; + ZstdReader reader(std::move(raw)); + ok = reader.ok() && extract_tar(reader, destination, env, listener, false); + } + if (!ok && env->ExceptionCheck()) return JNI_FALSE; + return ok ? JNI_TRUE : JNI_FALSE; +} + +extern "C" JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_shared_io_NativeContentIO_nativeDownloadFile( + JNIEnv* env, jclass, jstring jaddress, jstring jdestination, jstring jca_bundle, jobject listener) { + std::string address = jstr(env, jaddress); + std::string destination = jstr(env, jdestination); + std::string ca_bundle = jstr(env, jca_bundle); + if (address.empty() || destination.empty() || !ensure_parent_dir(destination)) return JNI_FALSE; + + ensure_curl_global_init(); + std::string partial = destination + ".part"; + FILE* file = std::fopen(partial.c_str(), "wb"); + if (!file) return JNI_FALSE; + std::setvbuf(file, nullptr, _IOFBF, kBufferSize); + + DownloadContext ctx; + ctx.file = file; + ctx.env = env; + ctx.listener = listener; + if (listener) { + jclass cls = env->GetObjectClass(listener); + ctx.on_progress = env->GetMethodID(cls, "onProgress", "(JJ)V"); + if (ctx.on_progress) { + env->CallVoidMethod(listener, ctx.on_progress, static_cast(0), static_cast(-1)); + } + } + + CURL* curl = curl_easy_init(); + if (!curl) { + std::fclose(file); + ::unlink(partial.c_str()); + return JNI_FALSE; + } + configure_curl_common(curl, address, ca_bundle); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_file); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ctx); + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); + curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, curl_progress); + curl_easy_setopt(curl, CURLOPT_XFERINFODATA, &ctx); + + CURLcode rc = curl_easy_perform(curl); + long status = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status); + curl_off_t downloaded = 0; + curl_off_t expected = -1; + curl_easy_getinfo(curl, CURLINFO_SIZE_DOWNLOAD_T, &downloaded); + curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &expected); + curl_easy_cleanup(curl); + + bool ok = (rc == CURLE_OK && status >= 200 && status < 300); + if (expected >= 0 && downloaded != expected) ok = false; + if (std::fclose(file) != 0) ok = false; + + if (ok && listener && ctx.on_progress && !env->ExceptionCheck()) { + env->CallVoidMethod(listener, ctx.on_progress, static_cast(downloaded), static_cast(downloaded)); + } + + if (!ok || env->ExceptionCheck()) { + ::unlink(partial.c_str()); + NATIVE_LOGW("download failed for %s: curl=%d http=%ld", address.c_str(), static_cast(rc), status); + return JNI_FALSE; + } + + ::unlink(destination.c_str()); + if (::rename(partial.c_str(), destination.c_str()) != 0) { + ::unlink(partial.c_str()); + return JNI_FALSE; + } + return JNI_TRUE; +} + +extern "C" JNIEXPORT jlong JNICALL +Java_com_winlator_cmod_shared_io_NativeContentIO_nativeFetchContentLength( + JNIEnv* env, jclass, jstring jaddress, jstring jca_bundle) { + std::string address = jstr(env, jaddress); + std::string ca_bundle = jstr(env, jca_bundle); + if (address.empty()) return -1; + + ensure_curl_global_init(); + CURL* curl = curl_easy_init(); + if (!curl) return -1; + configure_curl_common(curl, address, ca_bundle); + curl_easy_setopt(curl, CURLOPT_NOBODY, 1L); + curl_easy_setopt(curl, CURLOPT_HEADER, 0L); + + CURLcode rc = curl_easy_perform(curl); + long status = 0; + curl_off_t length = -1; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status); + curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &length); + curl_easy_cleanup(curl); + if (rc != CURLE_OK || status < 200 || status >= 300 || length < 0) return -1; + return static_cast(length); +} diff --git a/app/src/main/cpp/winlator/ring_fence.c b/app/src/main/cpp/winlator/ring_fence.c new file mode 100644 index 000000000..634f3a0a7 --- /dev/null +++ b/app/src/main/cpp/winlator/ring_fence.c @@ -0,0 +1,13 @@ +#include +#include + +// Store-store barrier (dmb ish on arm64) so ring event/snapshot payload stores +// are visible to the guest-side reader before the sequence word that publishes +// them. Pairs with the __ATOMIC_ACQUIRE loads in fakeinput.cpp. +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_input_controls_FakeInputWriter_nativeStoreFence( + JNIEnv *env, jclass clazz) { + (void)env; + (void)clazz; + atomic_thread_fence(memory_order_release); +} diff --git a/app/src/main/cpp/winlator/surface_compositor.c b/app/src/main/cpp/winlator/surface_compositor.c index 910303897..f6ed4ba40 100644 --- a/app/src/main/cpp/winlator/surface_compositor.c +++ b/app/src/main/cpp/winlator/surface_compositor.c @@ -1,28 +1,20 @@ -// JNI wrapper around Android's ASurfaceControl / ASurfaceTransaction NDK API -// (libandroid.so, API 29+). Phase-by-phase scope: -// * Phase 1 — `nativeIsAvailable` probe; nothing else. -// * Phase 2.1 — lifecycle: create a child ASurfaceControl bound to the -// XServerView's SurfaceView, hide it, parent it to the SurfaceView's -// layer, expose attach/detach/setColor/release. -// * Phase 2.2+ — buffer push, sync fence, real game frames. +// JNI wrapper around ASurfaceControl / ASurfaceTransaction (libandroid.so, +// API 29+). Hands a DRI3 game frame's AHardwareBuffer to a child +// ASurfaceControl layer so HWC can scan it out from a DPU overlay plane +// instead of the renderer blitting it. // -// Symbols are resolved via dlopen/dlsym so the shared library still loads on -// minSdk-26 devices that lack the API-29 entry points. Calling any resolved -// pointer on a pre-API-29 device is gated by the Java side checking -// `isAvailable()` first. +// Symbols are dlopen/dlsym'd so the library still loads on minSdk-26 devices; +// the Java side gates every call on SurfaceCompositor.isAvailable(). // -// Quoting the NDK documentation referenced while writing this: -// * `ASurfaceControl_createFromWindow` (surface_control.h:50-65) — caller -// owns the returned ASurfaceControl and must release it. -// * `ASurfaceTransaction_reparent` (surface_control.h:298-307) — passing -// a null new_parent removes the surface from the display. -// * `ASurfaceTransaction_setVisibility` (surface_control.h:323) — HIDE/SHOW. -// * `ASurfaceTransaction_setZOrder` (surface_control.h:329-339) — relative -// to siblings; default is 0; behaviour with same z is undefined. -// * `ASurfaceTransaction_setColor` (surface_control.h:359-370) — sets the -// background color for a layer that has no buffer; useful as a Phase 2.2 -// proof-of-life and to avoid the "blank initial frame" race when a fresh -// SurfaceControl is shown before its first real buffer arrives. +// Device-safety invariants (regressing any of these has caused soft reboots): +// - Never allocate a CPU_WRITE + COMPOSER_OVERLAY buffer. That combination +// panics some gralloc implementations (Adreno 6xx qdgralloc, MediaTek, +// older Exynos). This is why there is no proof-of-life smoke-test buffer. +// - Reject negative destination coordinates; some OEM ROMs crash SF on them. +// - Never release an ASurfaceControl while SF is still processing a +// transaction on it — crashes SF on Xiaomi/HyperOS. +// +// Ported from https://github.com/WinNative-Emu/WinNative/pull/380 #include #include #include @@ -30,12 +22,14 @@ #include #include #include +#include #include #include #include #include #include #include +#include #include #define LOG_TAG "SurfaceCompositor" @@ -48,22 +42,19 @@ struct ASurfaceControl; struct ASurfaceTransaction; // Mirror of `enum ASurfaceTransactionVisibility` (surface_control.h:312-315). -// Hard-coded so we don't need to include (which -// would fail to compile on minSdk-26 toolchains for direct symbol references). #define DC_VISIBILITY_HIDE ((int8_t)0) #define DC_VISIBILITY_SHOW ((int8_t)1) // Mirror of `enum ASurfaceTransactionTransparency` (surface_control.h:447-451). -// OPAQUE tells HWC the buffer is fully opaque so it can skip per-pixel -// alpha blending — important on Snapdragon DPUs where the alpha-blend stage -// engages the HDR-aware composition pipeline (mixed SDR/HDR routing) which -// boosts SDR-layer brightness vs the legacy GL composition path. +// OPAQUE tells HWC the buffer is fully opaque so it can skip per-pixel alpha +// blending — important on Snapdragon DPUs where the alpha-blend stage engages +// the HDR-aware composition pipeline (mixed SDR/HDR routing) which boosts SDR +// layer brightness vs the legacy GL composition path. #define DC_TRANSPARENCY_TRANSPARENT ((int8_t)0) #define DC_TRANSPARENCY_TRANSLUCENT ((int8_t)1) #define DC_TRANSPARENCY_OPAQUE ((int8_t)2) -// Function-pointer typedefs for every libandroid.so symbol we use. Kept in -// the order they're documented in surface_control.h for easy cross-reference. +// Function-pointer typedefs for every libandroid.so symbol we use. typedef struct ASurfaceControl* (*pfn_ASurfaceControl_createFromWindow)( ANativeWindow* parent, const char* debug_name); typedef void (*pfn_ASurfaceControl_release)(struct ASurfaceControl* sc); @@ -82,7 +73,7 @@ typedef void (*pfn_ASurfaceTransaction_setZOrder)(struct ASurfaceTransaction* t, typedef void (*pfn_ASurfaceTransaction_setColor)(struct ASurfaceTransaction* t, struct ASurfaceControl* sc, float r, float g, float b, float alpha, - int dataspace /* ADataSpace */); + int dataspace); typedef void (*pfn_ASurfaceTransaction_setBuffer)(struct ASurfaceTransaction* t, struct ASurfaceControl* sc, AHardwareBuffer* buffer, @@ -93,9 +84,7 @@ typedef void (*pfn_ASurfaceTransaction_setGeometry)(struct ASurfaceTransaction* const ARect* source, const ARect* destination, int32_t transform); -// API-31+ preferred geometry. When all four are present we prefer this path -// per surface_control.h:387-391 ("setGeometry deprecated; use setCrop, -// setPosition, setBufferTransform, setScale instead"). +// API-31+ preferred geometry. typedef void (*pfn_ASurfaceTransaction_setPosition)(struct ASurfaceTransaction* t, struct ASurfaceControl* sc, int32_t x, int32_t y); @@ -108,28 +97,10 @@ typedef void (*pfn_ASurfaceTransaction_setCrop)(struct ASurfaceTransaction* t, typedef void (*pfn_ASurfaceTransaction_setBufferTransform)(struct ASurfaceTransaction* t, struct ASurfaceControl* sc, int32_t transform); - -// Phase 4 — colour / brightness control to neutralise the Snapdragon DPU's -// HDR-aware composition pipeline that boosts SDR layer brightness vs the -// legacy GL composition path. -// -// `setBufferDataSpace` (API 29) — explicit ADATASPACE_SRGB so HWC can't pick -// ADATASPACE_UNKNOWN from gralloc metadata and route through a path that -// speculatively decodes-then-re-encodes. -// `setBufferTransparency` (API 29) — OPAQUE skips per-pixel alpha blend, -// bypassing the mixed-SDR/HDR routing stage on layers known to be -// fully-opaque (game swap-chain frames are RGBA8888 with alpha=1.0). -// `setExtendedRangeBrightness` (API 34) — pin layer's extended-range ratio -// to (1.0, 1.0) so SurfaceFlinger's SDR-on-HDR-panel path doesn't apply -// a midtone boost. Default is (1.0, 1.0) but the AOSP pipeline only -// skips the boost when the call is explicit. -// -// All three are optional: if dlsym returns null we degrade to the prior -// (visibly brighter) behaviour and log it once at startup so the missing -// symbol is diagnosable from logcat without a re-build. +// Colour / brightness control (optional — null on older Android). typedef void (*pfn_ASurfaceTransaction_setBufferDataSpace)(struct ASurfaceTransaction* t, struct ASurfaceControl* sc, - int data_space /* ADataSpace */); + int data_space); typedef void (*pfn_ASurfaceTransaction_setBufferTransparency)(struct ASurfaceTransaction* t, struct ASurfaceControl* sc, int8_t transparency); @@ -165,9 +136,113 @@ static pfn_ASurfaceTransaction_setBufferDataSpace g_tx_set_buffer_dataspace = NU static pfn_ASurfaceTransaction_setBufferTransparency g_tx_set_buffer_transparency = NULL; static pfn_ASurfaceTransaction_setExtendedRangeBrightness g_tx_set_extended_range_brightness = NULL; -// `__typeof__` is the documented-extension spelling that doesn't trip -// `-Wgnu-typeof-extension` under pedantic Clang flags. Equivalent to GCC/C23 -// `typeof` in every case we use it. +// Hardware fence sync: setOnComplete callback fires on SF's binder thread when the buffer is on display. +typedef struct ASurfaceTransactionStats ASurfaceTransactionStats; +typedef void (*ASurfaceTransaction_OnComplete)(void* context, ASurfaceTransactionStats* stats); +typedef void (*pfn_ASurfaceTransaction_setOnComplete)(struct ASurfaceTransaction* t, void* context, ASurfaceTransaction_OnComplete func); +static pfn_ASurfaceTransaction_setOnComplete g_tx_set_on_complete = NULL; +static bool g_has_on_complete = false; + +// === ATOMIC SUBMISSION GATE === +static pthread_mutex_t g_inflight_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t g_inflight_cv = PTHREAD_COND_INITIALIZER; +static int g_inflight_count = 0; +// Atomic flag: true while a transaction is pending in SF's pipeline. +static volatile bool g_transaction_pending = false; + +static void inflight_increment(void) { + pthread_mutex_lock(&g_inflight_mutex); + g_inflight_count++; + pthread_mutex_unlock(&g_inflight_mutex); +} + +static void inflight_decrement(void) { + pthread_mutex_lock(&g_inflight_mutex); + if (g_inflight_count > 0) g_inflight_count--; + if (g_inflight_count == 0) { + g_transaction_pending = false; + pthread_cond_broadcast(&g_inflight_cv); + } + pthread_mutex_unlock(&g_inflight_mutex); +} + +// SF binder thread callback: flips the atomic gate back to false. +static void on_transaction_complete(void* context, ASurfaceTransactionStats* stats) { + (void)context; (void)stats; + inflight_decrement(); +} + +// Block until any pending transaction completes (condvar wait, not busy-wait). +static void wait_for_transaction_gate(long timeout_ms) { + struct timespec deadline; + clock_gettime(CLOCK_REALTIME, &deadline); + int64_t add_ns = (int64_t)timeout_ms * 1000000L; + deadline.tv_sec += (time_t)(add_ns / 1000000000L); + deadline.tv_nsec += (long)(add_ns % 1000000000L); + if (deadline.tv_nsec >= 1000000000L) { deadline.tv_nsec -= 1000000000L; deadline.tv_sec += 1; } + pthread_mutex_lock(&g_inflight_mutex); + while (g_transaction_pending) { + if (pthread_cond_timedwait(&g_inflight_cv, &g_inflight_mutex, &deadline) == ETIMEDOUT) { + g_transaction_pending = false; // force-clear on timeout to prevent deadlock + break; + } + } + pthread_mutex_unlock(&g_inflight_mutex); +} + +// JNI: nativeWaitForPreviousFrame — blocks render thread until SF finishes (hardware signal, no CPU polling). +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_runtime_display_composition_DirectCompositionLayer_nativeWaitForPreviousFrame( + JNIEnv* env, jobject thiz, jlong timeout_ms) { + (void)env; (void)thiz; + // g_inflight_count is only safe to read under the mutex; the loop below + // already short-circuits when nothing is in flight. + if (!g_has_on_complete) return JNI_TRUE; + struct timespec deadline; + clock_gettime(CLOCK_REALTIME, &deadline); + int64_t add_ns = (int64_t)timeout_ms * 1000000L; + deadline.tv_sec += (time_t)(add_ns / 1000000000L); + deadline.tv_nsec += (long)(add_ns % 1000000000L); + if (deadline.tv_nsec >= 1000000000L) { deadline.tv_nsec -= 1000000000L; deadline.tv_sec += 1; } + pthread_mutex_lock(&g_inflight_mutex); + bool ok = true; + while (g_inflight_count > 0) { + if (pthread_cond_timedwait(&g_inflight_cv, &g_inflight_mutex, &deadline) == ETIMEDOUT) { + ok = false; break; + } + } + pthread_mutex_unlock(&g_inflight_mutex); + return ok ? JNI_TRUE : JNI_FALSE; +} + +// Wait up to 500ms for all in-flight transactions to complete. Returns true +// if all cleared, false on timeout (in which case release proceeds anyway — +// holding the SC longer risks a worse deadlock). +static bool inflight_wait_all(void) { + struct timespec deadline; + clock_gettime(CLOCK_REALTIME, &deadline); + deadline.tv_nsec += 500 * 1000000L; + if (deadline.tv_nsec >= 1000000000L) { + deadline.tv_nsec -= 1000000000L; + deadline.tv_sec += 1; + } + pthread_mutex_lock(&g_inflight_mutex); + bool ok = true; + while (g_inflight_count > 0) { + if (pthread_cond_timedwait(&g_inflight_cv, &g_inflight_mutex, &deadline) == ETIMEDOUT) { + LOGW("inflight_wait_all: timed out with %d in-flight; proceeding with release", + g_inflight_count); + g_inflight_count = 0; + g_transaction_pending = false; + pthread_cond_broadcast(&g_inflight_cv); + ok = false; + break; + } + } + pthread_mutex_unlock(&g_inflight_mutex); + return ok; +} + #define RESOLVE(target, name) do { \ void* sym = dlsym(g_libandroid, (name)); \ (target) = (__typeof__(target))sym; \ @@ -194,60 +269,56 @@ static void init_once_locked(void) { RESOLVE(g_tx_set_color, "ASurfaceTransaction_setColor"); RESOLVE(g_tx_set_buffer, "ASurfaceTransaction_setBuffer"); RESOLVE(g_tx_set_geometry, "ASurfaceTransaction_setGeometry"); - // Optional API-31+ symbols — null on API 29/30, in which case we fall back - // to setGeometry. Not part of the availability gate. + // Optional API-31+ symbols — null on API 29/30, fall back to setGeometry. RESOLVE(g_tx_set_position, "ASurfaceTransaction_setPosition"); RESOLVE(g_tx_set_scale, "ASurfaceTransaction_setScale"); RESOLVE(g_tx_set_crop, "ASurfaceTransaction_setCrop"); RESOLVE(g_tx_set_buffer_transform, "ASurfaceTransaction_setBufferTransform"); - // Phase 4 colour / brightness symbols. Optional — failure to resolve - // means we'll see the visibly-brighter behaviour and log the miss. + // Optional Phase-4 colour / brightness symbols. RESOLVE(g_tx_set_buffer_dataspace, "ASurfaceTransaction_setBufferDataSpace"); RESOLVE(g_tx_set_buffer_transparency, "ASurfaceTransaction_setBufferTransparency"); RESOLVE(g_tx_set_extended_range_brightness, "ASurfaceTransaction_setExtendedRangeBrightness"); + RESOLVE(g_tx_set_on_complete, "ASurfaceTransaction_setOnComplete"); + g_has_on_complete = (g_tx_set_on_complete != NULL); + + // Availability gate: the Phase-1 lifecycle symbols + setBuffer + at least + // one COMPLETE geometry API (either the deprecated setGeometry, or all + // three of setPosition + setScale + setCrop) must be present. + bool has_complete_geometry_31 = + g_tx_set_position && g_tx_set_scale && g_tx_set_crop; + bool has_geometry = g_tx_set_geometry || has_complete_geometry_31; + + g_available = g_create_from_window && g_sc_release + && g_tx_create && g_tx_delete && g_tx_apply + && g_tx_reparent && g_tx_set_visibility && g_tx_set_zorder + && g_tx_set_buffer && has_geometry; - // Phase-1 lifecycle symbols + setBuffer + at least one COMPLETE geometry - // path are mandatory. The modern path requires all three of - // setPosition+setScale+setCrop together — accepting setPosition alone - // would leave us with no scaling primitive and silently render at the - // wrong size on a hypothetical device that ships only the position - // symbol. Fall back to setGeometry whenever any of the trio is missing. - bool modern_geom_complete = (g_tx_set_position != NULL) && - (g_tx_set_scale != NULL) && - (g_tx_set_crop != NULL); - bool legacy_geom = (g_tx_set_geometry != NULL); - g_available = (g_create_from_window != NULL) && (g_sc_release != NULL) && - (g_tx_create != NULL) && (g_tx_delete != NULL) && - (g_tx_apply != NULL) && (g_tx_reparent != NULL) && - (g_tx_set_visibility != NULL) && (g_tx_set_zorder != NULL) && - (g_tx_set_color != NULL) && (g_tx_set_buffer != NULL) && - (modern_geom_complete || legacy_geom); if (g_available) { - LOGI("Direct Composition NDK symbols resolved (geom=%s)", - modern_geom_complete ? "API31+" : "API29 setGeometry"); - // Per-symbol diagnostic for the Phase 4 colour fix surface — printed - // once on first probe so we can distinguish "fix didn't apply" from - // "fix applied, vendor pipeline still boosting" in post-deploy - // logcats without rebuilding. - LOGI("Direct Composition colour symbols: setBufferDataSpace=%s setBufferTransparency=%s setExtendedRangeBrightness=%s", + LOGI("Direct Composition available. Geometry path: %s, colour symbols: " + "setBufferDataSpace=%s setBufferTransparency=%s setExtendedRangeBrightness=%s", + has_complete_geometry_31 ? "API-31+ (setPosition/setScale/setCrop)" + : "API-29 (setGeometry)", g_tx_set_buffer_dataspace ? "yes" : "MISSING", g_tx_set_buffer_transparency ? "yes" : "MISSING", - g_tx_set_extended_range_brightness ? "yes" : "MISSING (API < 34)"); + g_tx_set_extended_range_brightness ? "yes (API 34+)" : "MISSING (API < 34)"); + if (!g_has_on_complete) { + LOGW("ASurfaceTransaction_setOnComplete missing — no completion " + "tracking; release falls back to a timed wait"); + } } else { - LOGW("Direct Composition NDK symbols missing (API < 29 or stripped libandroid)"); + LOGW("Direct Composition NOT available — missing required symbols"); } } static bool ensure_initialised(void) { pthread_mutex_lock(&g_init_mutex); init_once_locked(); - bool available = g_available; pthread_mutex_unlock(&g_init_mutex); - return available; + return g_available; } // --------------------------------------------------------------------------- -// JNI: nativeIsAvailable() — Phase 1 probe, unchanged in Phase 2. +// JNI: nativeIsAvailable() -> jboolean // --------------------------------------------------------------------------- JNIEXPORT jboolean JNICALL Java_com_winlator_cmod_runtime_display_composition_SurfaceCompositor_nativeIsAvailable( @@ -257,182 +328,133 @@ Java_com_winlator_cmod_runtime_display_composition_SurfaceCompositor_nativeIsAva return ensure_initialised() ? JNI_TRUE : JNI_FALSE; } -// --------------------------------------------------------------------------- -// JNI: nativeAttachToSurface(Surface) -> jlong (ASurfaceControl*) -// -// Creates a child SurfaceControl bound to the SurfaceView's ANativeWindow. -// Initial state is HIDDEN with z-order 1 (above the SurfaceView's primary -// BufferQueue, which sits at the default z=0). Subsequent transactions -// (Phase 2.2+) flip visibility on and push buffers. -// -// On any failure returns 0 and the Java caller falls back to the GLRenderer -// path. The ANativeWindow is acquired and released within this call — the -// returned ASurfaceControl holds its own reference to the underlying -// SurfaceFlinger layer via the parent layer relationship. -// --------------------------------------------------------------------------- +// Creates a child ASurfaceControl at z=1, hidden until the first pushBuffer. JNIEXPORT jlong JNICALL -Java_com_winlator_cmod_runtime_display_composition_DirectCompositionLayer_nativeAttachToSurface( - JNIEnv* env, jclass clazz, jobject surface) { - (void)clazz; - if (!ensure_initialised()) { - LOGW("attachToSurface called but NDK is unavailable"); - return 0; - } +Java_com_winlator_cmod_runtime_display_composition_DirectCompositionLayer_nativeCreateFromWindow( + JNIEnv* env, jobject thiz, jobject surface, jstring debug_name) { + (void)thiz; + if (!ensure_initialised()) return 0; if (surface == NULL) { - LOGE("attachToSurface called with null Surface"); + LOGW("nativeCreateFromWindow: null Surface"); return 0; } - - ANativeWindow* window = ANativeWindow_fromSurface(env, surface); - if (window == NULL) { - LOGE("ANativeWindow_fromSurface returned null"); + ANativeWindow* win = ANativeWindow_fromSurface(env, surface); + if (win == NULL) { + LOGE("nativeCreateFromWindow: ANativeWindow_fromSurface returned null"); return 0; } - struct ASurfaceControl* sc = g_create_from_window(window, "winnative-direct-composition"); - // ANativeWindow_fromSurface incremented the window's refcount; release our - // ref now — the SurfaceControl holds its own internal reference to the - // SurfaceFlinger layer that the window referenced. - ANativeWindow_release(window); + // Track the JNI-owned copy separately: if GetStringUTFChars fails we fall + // back to the literal, and releasing a literal is undefined behaviour. + const char* jni_name = NULL; + if (debug_name != NULL) { + jni_name = (*env)->GetStringUTFChars(env, debug_name, NULL); + } + const char* name_str = jni_name ? jni_name : "winnative-direct-composition"; + struct ASurfaceControl* sc = g_create_from_window(win, name_str); + ANativeWindow_release(win); // release the ref fromSurface acquired + if (jni_name != NULL) { + (*env)->ReleaseStringUTFChars(env, debug_name, jni_name); + } if (sc == NULL) { - LOGE("ASurfaceControl_createFromWindow returned null"); + LOGE("nativeCreateFromWindow: ASurfaceControl_createFromWindow failed"); return 0; } - // Initial transaction: hidden and z=1 (above the SurfaceView's primary BQ - // which is z=0). Per surface_control.h:323-326 a fresh SurfaceControl - // starts hidden by default, but applying the explicit setVisibility(HIDE) - // here makes the contract observable on the SurfaceFlinger side and - // guarantees we don't get a one-frame flash of an uninitialised layer. + // Hide the layer initially + set z=1. We show it on the first successful + // pushBuffer (atomic show + setBuffer avoids the blank-frame race). struct ASurfaceTransaction* tx = g_tx_create(); if (tx == NULL) { - LOGE("ASurfaceTransaction_create returned null; releasing SC"); + LOGE("nativeCreateFromWindow: tx_create failed"); g_sc_release(sc); return 0; } g_tx_set_visibility(tx, sc, DC_VISIBILITY_HIDE); g_tx_set_zorder(tx, sc, 1); + inflight_increment(); g_tx_apply(tx); + inflight_decrement(); g_tx_delete(tx); - LOGI("Direct Composition layer attached (sc=%p)", (void*)sc); + LOGI("Direct Composition layer created: sc=%p", (void*)sc); return (jlong)(uintptr_t)sc; } -// --------------------------------------------------------------------------- -// JNI: nativeDetachAndRelease(jlong sc) -> void -// -// Reparents the SurfaceControl to null in a transaction, applies, then -// releases. Per the agent research and Chromium's -// android_surface_control_compat.cc convention, reparent-to-null *must* -// happen before release, otherwise SurfaceFlinger may keep the orphaned -// layer alive briefly past the parent's destruction and produce ghost -// frames on re-attach. -// --------------------------------------------------------------------------- +// Reparents to null, drains in-flight transactions, then releases. JNIEXPORT void JNICALL Java_com_winlator_cmod_runtime_display_composition_DirectCompositionLayer_nativeDetachAndRelease( - JNIEnv* env, jclass clazz, jlong sc_ptr) { + JNIEnv* env, jobject thiz, jlong sc_ptr) { (void)env; - (void)clazz; + (void)thiz; if (sc_ptr == 0) return; - if (!ensure_initialised()) { - // Should be impossible — the layer wouldn't exist if init had failed — - // but be defensive and don't dereference unresolved symbols. - LOGE("detachAndRelease called but NDK is unavailable; leaking SC=%p", - (void*)(uintptr_t)sc_ptr); - return; - } + if (!ensure_initialised()) return; + struct ASurfaceControl* sc = (struct ASurfaceControl*)(uintptr_t)sc_ptr; + // Reparent to null — removes the layer from the display atomically. struct ASurfaceTransaction* tx = g_tx_create(); if (tx != NULL) { g_tx_reparent(tx, sc, NULL); + inflight_increment(); g_tx_apply(tx); + inflight_decrement(); g_tx_delete(tx); - } else { - LOGW("detachAndRelease: tx_create failed; releasing without reparent"); } - g_sc_release(sc); - LOGI("Direct Composition layer released (sc=%p)", (void*)sc); -} - -// --------------------------------------------------------------------------- -// JNI: nativeSetColor(jlong sc, float r, float g, float b, float a) -> void -// -// Phase 2.1 proof-of-life. Paints a solid color on the layer and unhides it -// with the same transaction (atomic — avoids the documented "blank initial -// frame" race). Useful as a smoke test that the lifecycle is wired correctly -// before Phase 2.2 plumbs real AHardwareBuffer content. -// --------------------------------------------------------------------------- -JNIEXPORT void JNICALL -Java_com_winlator_cmod_runtime_display_composition_DirectCompositionLayer_nativeSetColor( - JNIEnv* env, jclass clazz, jlong sc_ptr, - jfloat r, jfloat g, jfloat b, jfloat a) { - (void)env; - (void)clazz; - if (sc_ptr == 0 || !ensure_initialised()) return; - struct ASurfaceControl* sc = (struct ASurfaceControl*)(uintptr_t)sc_ptr; - struct ASurfaceTransaction* tx = g_tx_create(); - if (tx == NULL) { - LOGE("setColor: tx_create failed"); - return; + // Wait for all in-flight transactions (including the one we just applied) + // to be processed by SF before releasing. This is the critical soft-boot + // fix: releasing a SC while SF is still processing a transaction on it + // crashes SF on Xiaomi/HyperOS 2.0+. + inflight_wait_all(); + + // Without setOnComplete there is nothing to count, so inflight_wait_all + // returns immediately and the release above would race SF exactly as it + // does on unhardened builds. Fall back to a bounded sleep (~2 frames at + // 60Hz) so SF has had time to process the reparent. Teardown-only, and + // only on devices whose libandroid.so lacks the symbol. + if (!g_has_on_complete) { + struct timespec ts = { .tv_sec = 0, .tv_nsec = 32 * 1000000L }; + nanosleep(&ts, NULL); } - g_tx_set_color(tx, sc, r, g, b, a, ADATASPACE_SRGB); - g_tx_set_visibility(tx, sc, DC_VISIBILITY_SHOW); - g_tx_apply(tx); - g_tx_delete(tx); + + g_sc_release(sc); + LOGI("Direct Composition layer released: sc=%p", (void*)sc); } -// --------------------------------------------------------------------------- -// JNI: nativeHide(jlong sc) -> void -// -// Hides the layer (used when falling back to the GLRenderer path on a frame -// where direct-scanout doesn't qualify, see Phase 2.5). -// --------------------------------------------------------------------------- JNIEXPORT void JNICALL Java_com_winlator_cmod_runtime_display_composition_DirectCompositionLayer_nativeHide( - JNIEnv* env, jclass clazz, jlong sc_ptr) { + JNIEnv* env, jobject thiz, jlong sc_ptr) { (void)env; - (void)clazz; - if (sc_ptr == 0 || !ensure_initialised()) return; + (void)thiz; + if (sc_ptr == 0) return; + if (!ensure_initialised()) return; struct ASurfaceControl* sc = (struct ASurfaceControl*)(uintptr_t)sc_ptr; - struct ASurfaceTransaction* tx = g_tx_create(); if (tx == NULL) return; g_tx_set_visibility(tx, sc, DC_VISIBILITY_HIDE); + inflight_increment(); g_tx_apply(tx); + inflight_decrement(); g_tx_delete(tx); } -// --------------------------------------------------------------------------- -// JNI: nativePushBuffer(sc, ahb, x, y, w, h, fence_fd) -> jboolean -// -// Phase 2.2: hand an AHardwareBuffer-backed image to the SurfaceControl in -// one transaction. The transaction also positions/sizes the layer in the -// SurfaceView's coordinate space and unhides it (atomic — same transaction -// avoids the documented "blank initial frame" race when transitioning from -// hidden to first-buffer). +// Per-frame hot path: setBuffer + geometry + SHOW in one atomic transaction, +// which avoids a blank-frame race. The caller only invokes this when the +// buffer or geometry actually changed. // -// Geometry path: -// * Prefer setPosition + setScale + setCrop + setBufferTransform (API 31+) -// * Fall back to deprecated setGeometry (API 29-30) -// -// `acquire_fence_fd` semantics per surface_control.h:343-348: framework -// takes ownership and closes it. Pass -1 when no GPU writes are pending -// (e.g. the test buffer that was filled on the CPU before we got here). -// --------------------------------------------------------------------------- +// acquire_fence_fd must be closed on every error path — the framework only +// takes ownership once setBuffer succeeds. JNIEXPORT jboolean JNICALL Java_com_winlator_cmod_runtime_display_composition_DirectCompositionLayer_nativePushBuffer( JNIEnv* env, jclass clazz, jlong sc_ptr, jlong ahb_ptr, jint dst_x, jint dst_y, jint dst_w, jint dst_h, jint acquire_fence_fd, - jboolean opaque) { + jboolean opaque, jboolean pace) { (void)env; (void)clazz; + + // --- Validation --- if (sc_ptr == 0 || ahb_ptr == 0) { - // We promised the framework that we'd close any fence FD we received, - // even on the failure path — otherwise we leak FDs. if (acquire_fence_fd >= 0) close(acquire_fence_fd); return JNI_FALSE; } @@ -440,8 +462,9 @@ Java_com_winlator_cmod_runtime_display_composition_DirectCompositionLayer_native if (acquire_fence_fd >= 0) close(acquire_fence_fd); return JNI_FALSE; } - if (dst_w <= 0 || dst_h <= 0) { - LOGW("pushBuffer: invalid dst rect %dx%d", dst_w, dst_h); + // Negative destination coordinates crash SF on some OEM ROMs. + if (dst_x < 0 || dst_y < 0 || dst_w <= 0 || dst_h <= 0) { + LOGW("pushBuffer: invalid dst rect %dx%d at (%d,%d)", dst_w, dst_h, dst_x, dst_y); if (acquire_fence_fd >= 0) close(acquire_fence_fd); return JNI_FALSE; } @@ -449,7 +472,7 @@ Java_com_winlator_cmod_runtime_display_composition_DirectCompositionLayer_native struct ASurfaceControl* sc = (struct ASurfaceControl*)(uintptr_t)sc_ptr; AHardwareBuffer* ahb = (AHardwareBuffer*)(uintptr_t)ahb_ptr; - // Source rect = the entire buffer extents — query AHB for its native dims. + // Source rect = the entire buffer extents. AHardwareBuffer_Desc desc; memset(&desc, 0, sizeof(desc)); AHardwareBuffer_describe(ahb, &desc); @@ -458,6 +481,12 @@ Java_com_winlator_cmod_runtime_display_composition_DirectCompositionLayer_native if (acquire_fence_fd >= 0) close(acquire_fence_fd); return JNI_FALSE; } + if (!(desc.usage & AHARDWAREBUFFER_USAGE_COMPOSER_OVERLAY)) { + LOGW("pushBuffer: AHB usage 0x%llx lacks COMPOSER_OVERLAY, rejecting", + (unsigned long long)desc.usage); + if (acquire_fence_fd >= 0) close(acquire_fence_fd); + return JNI_FALSE; + } struct ASurfaceTransaction* tx = g_tx_create(); if (tx == NULL) { @@ -466,168 +495,64 @@ Java_com_winlator_cmod_runtime_display_composition_DirectCompositionLayer_native return JNI_FALSE; } - // setBuffer takes ownership of acquire_fence_fd. After this call, the - // framework will close the fd; we MUST NOT touch it again. + // setBuffer takes ownership of acquire_fence_fd on success. After this + // call the framework will close the fd; we MUST NOT touch it again. g_tx_set_buffer(tx, sc, ahb, acquire_fence_fd); - // Phase 4 colour / brightness control. Each call is best-effort — if the - // symbol wasn't resolved (older Android, stripped libandroid) we skip and - // the layer falls back to whatever default the platform applies. The - // missing-symbol case was logged once at init. - // - // Order within the transaction is irrelevant per surface_control.h: - // properties are committed atomically on apply(). + // Colour / brightness control; each call is best-effort. if (g_tx_set_buffer_dataspace != NULL) { // Explicit ADATASPACE_SRGB so HWC can't pick UNKNOWN-via-gralloc and // route through a speculative re-encoding path. g_tx_set_buffer_dataspace(tx, sc, ADATASPACE_SRGB); } if (g_tx_set_buffer_transparency != NULL) { - // Caller-declared opacity — game frames are typically RGBA8888 with - // alpha=1.0 throughout, declaring OPAQUE skips alpha blending and - // bypasses the mixed-SDR/HDR routing stage that brightens layers. - // Untrusted/translucent surfaces pass opaque=false to keep - // PREMULTIPLIED behaviour. + // OPAQUE skips alpha blending, bypassing the mixed-SDR/HDR routing + // stage that brightens layers on Snapdragon DPUs. g_tx_set_buffer_transparency(tx, sc, opaque ? DC_TRANSPARENCY_OPAQUE : DC_TRANSPARENCY_TRANSLUCENT); } if (g_tx_set_extended_range_brightness != NULL) { - // Pin extended-range to (1.0, 1.0) — explicit "no HDR headroom - // requested." Default value but only assertively skips the - // SDR-on-HDR-panel midtone boost when stated. + // Pin extended-range to (1.0, 1.0) — explicit "no HDR headroom". g_tx_set_extended_range_brightness(tx, sc, 1.0f, 1.0f); } - // Geometry. The modern path lets us crop and scale independently; if - // unavailable on the device's libandroid, fall back to setGeometry. - if (g_tx_set_position != NULL && g_tx_set_scale != NULL && g_tx_set_crop != NULL) { + // Geometry: prefer API-31+ setPosition + setScale + setCrop; fall back to + // deprecated setGeometry on API 29-30. + if (g_tx_set_position && g_tx_set_scale && g_tx_set_crop) { + g_tx_set_position(tx, sc, dst_x, dst_y); + g_tx_set_scale(tx, sc, + (float)dst_w / (float)desc.width, + (float)dst_h / (float)desc.height); ARect crop = { 0, 0, (int32_t)desc.width, (int32_t)desc.height }; g_tx_set_crop(tx, sc, &crop); - g_tx_set_position(tx, sc, dst_x, dst_y); - float xs = (float)dst_w / (float)desc.width; - float ys = (float)dst_h / (float)desc.height; - g_tx_set_scale(tx, sc, xs, ys); - if (g_tx_set_buffer_transform != NULL) { - g_tx_set_buffer_transform(tx, sc, 0); // no transform - } - } else if (g_tx_set_geometry != NULL) { + } else if (g_tx_set_geometry) { ARect src = { 0, 0, (int32_t)desc.width, (int32_t)desc.height }; ARect dst = { dst_x, dst_y, dst_x + dst_w, dst_y + dst_h }; g_tx_set_geometry(tx, sc, &src, &dst, 0); } else { - LOGE("pushBuffer: no geometry function available — should be impossible past availability gate"); + LOGE("pushBuffer: no geometry API available"); + // setBuffer already took ownership of acquire_fence_fd — but since we're + // deleting the tx without apply(), SF never processes it. The fd is leaked. + // Fix: we can't close it (setBuffer may have already consumed it), but + // g_tx_delete should handle cleanup. Log the error and proceed with apply + // so the framework closes the fd properly. } - // Unhide in the same transaction so the very first frame is the buffer - // we just supplied, not a blank/uninit layer. + // Atomic with setBuffer, so the layer never shows an empty frame. g_tx_set_visibility(tx, sc, DC_VISIBILITY_SHOW); - g_tx_apply(tx); - g_tx_delete(tx); - return JNI_TRUE; -} - -// --------------------------------------------------------------------------- -// JNI: nativeAllocateTestBuffer(width, height, argb_color) -> jlong -// -// Phase 2.2 smoke-test helper: allocates an AHardwareBuffer and CPU-fills it -// with a single colour. Used so we can prove the SurfaceControl path is alive -// (a small magenta swatch on top of the X server) before plumbing real Wine -// frames in Phase 2.3. -// -// Format / usage: RGBA_8888 + GPU_SAMPLED_IMAGE + CPU_WRITE_RARELY + -// COMPOSER_OVERLAY. Per surface_control.h:343-345 setBuffer requires -// GPU_SAMPLED_IMAGE; COMPOSER_OVERLAY is a hint to gralloc that the buffer -// may be scanned out by the display controller. -// --------------------------------------------------------------------------- -JNIEXPORT jlong JNICALL -Java_com_winlator_cmod_runtime_display_composition_DirectCompositionLayer_nativeAllocateTestBuffer( - JNIEnv* env, jclass clazz, jint width, jint height, jint argb_color) { - (void)env; - (void)clazz; - if (width <= 0 || height <= 0) return 0; - - // Try the ideal flag set first: GPU sampling, CPU write (so we can fill - // the buffer in software), and COMPOSER_OVERLAY (hint to gralloc that - // this buffer should be eligible for HWC overlay-plane scanout). Some - // gralloc implementations on recent Adreno devices reject the - // CPU_WRITE + COMPOSER_OVERLAY combo — in that case fall back to a - // CPU-only buffer. We lose the overlay hint, but the smoke test still - // proves the SurfaceControl path is alive. - AHardwareBuffer_Desc desc; - memset(&desc, 0, sizeof(desc)); - desc.width = (uint32_t)width; - desc.height = (uint32_t)height; - desc.layers = 1; - desc.format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM; - desc.usage = AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE - | AHARDWAREBUFFER_USAGE_CPU_WRITE_RARELY - | AHARDWAREBUFFER_USAGE_COMPOSER_OVERLAY; - - AHardwareBuffer* ahb = NULL; - int rc = AHardwareBuffer_allocate(&desc, &ahb); - if (rc != 0 || ahb == NULL) { - LOGW("allocateTestBuffer: GPU+CPU+OVERLAY failed (rc=%d), retrying without OVERLAY", rc); - desc.usage = AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE - | AHARDWAREBUFFER_USAGE_CPU_WRITE_RARELY; - rc = AHardwareBuffer_allocate(&desc, &ahb); - if (rc != 0 || ahb == NULL) { - LOGW("allocateTestBuffer: both flag combos failed (rc=%d) for %dx%d", - rc, width, height); - return 0; - } - } - void* mapped = NULL; - if (AHardwareBuffer_lock( - ahb, AHARDWAREBUFFER_USAGE_CPU_WRITE_RARELY, - -1, NULL, &mapped) != 0 || mapped == NULL) { - LOGW("allocateTestBuffer: AHardwareBuffer_lock failed"); - AHardwareBuffer_release(ahb); - return 0; - } - - // After lock we need the actual stride from gralloc — re-describe. - AHardwareBuffer_Desc realDesc; - memset(&realDesc, 0, sizeof(realDesc)); - AHardwareBuffer_describe(ahb, &realDesc); - - // Convert ARGB jint to little-endian RGBA u32. AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM - // is byte-packed: R, G, B, A. Java jint = 0xAARRGGBB. Layout the bytes as R,G,B,A. - uint8_t a = (uint8_t)((argb_color >> 24) & 0xFF); - uint8_t r = (uint8_t)((argb_color >> 16) & 0xFF); - uint8_t g = (uint8_t)((argb_color >> 8) & 0xFF); - uint8_t b = (uint8_t)((argb_color ) & 0xFF); - - uint8_t* base = (uint8_t*)mapped; - for (uint32_t y = 0; y < realDesc.height; ++y) { - uint8_t* row = base + (size_t)y * (size_t)realDesc.stride * 4u; - for (uint32_t x = 0; x < realDesc.width; ++x) { - row[x*4 + 0] = r; - row[x*4 + 1] = g; - row[x*4 + 2] = b; - row[x*4 + 3] = a; - } - } - - if (AHardwareBuffer_unlock(ahb, NULL) != 0) { - LOGW("allocateTestBuffer: AHardwareBuffer_unlock failed"); - // Keep the buffer anyway; SurfaceFlinger doesn't care about lock state. + // Block until SF has retired the previous transaction, so we pace to the + // display rather than queueing ahead of it. + if (g_has_on_complete) { + if (pace) wait_for_transaction_gate(17); // ~60Hz budget; condvar wait, not busy-spin + g_tx_set_on_complete(tx, NULL, on_transaction_complete); + g_transaction_pending = true; + inflight_increment(); + g_tx_apply(tx); + } else { + g_tx_apply(tx); } - return (jlong)(uintptr_t)ahb; -} + g_tx_delete(tx); -// --------------------------------------------------------------------------- -// JNI: nativeReleaseBuffer(ahbPtr) -> void -// -// Drops our reference to a test AHardwareBuffer. SurfaceFlinger may still -// hold a ref if the buffer is the layer's current setBuffer — that's OK, -// AHardwareBuffer is reference-counted and the layer's ref is independent. -// --------------------------------------------------------------------------- -JNIEXPORT void JNICALL -Java_com_winlator_cmod_runtime_display_composition_DirectCompositionLayer_nativeReleaseBuffer( - JNIEnv* env, jclass clazz, jlong ahb_ptr) { - (void)env; - (void)clazz; - if (ahb_ptr == 0) return; - AHardwareBuffer_release((AHardwareBuffer*)(uintptr_t)ahb_ptr); + return JNI_TRUE; } diff --git a/app/src/main/cpp/winlator/sync_fence.c b/app/src/main/cpp/winlator/sync_fence.c new file mode 100644 index 000000000..46c6f3a0c --- /dev/null +++ b/app/src/main/cpp/winlator/sync_fence.c @@ -0,0 +1,127 @@ +// Native sync_file / eventfd helpers backing SyncExtension's fence FDs. + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LOG_TAG "SyncFenceFd" +#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__) + +JNIEXPORT jintArray JNICALL +Java_com_winlator_cmod_runtime_display_connector_SyncFenceFd_pollFds( + JNIEnv* env, jclass cls, jintArray fdsArray, jint timeoutMs) +{ + (void)cls; + if (fdsArray == NULL) return NULL; + jsize n = (*env)->GetArrayLength(env, fdsArray); + if (n == 0) return (*env)->NewIntArray(env, 0); + + jint* fdsRaw = (*env)->GetIntArrayElements(env, fdsArray, NULL); + if (!fdsRaw) return NULL; + + struct pollfd* pfds = calloc((size_t)n, sizeof(struct pollfd)); + if (!pfds) { + (*env)->ReleaseIntArrayElements(env, fdsArray, fdsRaw, JNI_ABORT); + return NULL; + } + for (jsize i = 0; i < n; i++) { + pfds[i].fd = fdsRaw[i]; + pfds[i].events = POLLIN; + } + (*env)->ReleaseIntArrayElements(env, fdsArray, fdsRaw, JNI_ABORT); + + int rc; + do { + rc = poll(pfds, (nfds_t)n, timeoutMs); + } while (rc < 0 && errno == EINTR); + + if (rc < 0) { + LOGW("poll() failed: %d", errno); + free(pfds); + return NULL; + } + + jintArray result = (*env)->NewIntArray(env, n); + if (!result) { + free(pfds); + return NULL; + } + + jint* revents = calloc((size_t)n, sizeof(jint)); + if (!revents) { + free(pfds); + return NULL; + } + if (rc > 0) { + for (jsize i = 0; i < n; i++) revents[i] = (jint)pfds[i].revents; + } + (*env)->SetIntArrayRegion(env, result, 0, n, revents); + free(revents); + free(pfds); + return result; +} + +JNIEXPORT jint JNICALL +Java_com_winlator_cmod_runtime_display_connector_SyncFenceFd_createSignalEventFd( + JNIEnv* env, jclass cls) +{ + (void)env; (void)cls; + int fd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC); + if (fd < 0) LOGW("eventfd() failed: %d", errno); + return fd; +} + +JNIEXPORT jint JNICALL +Java_com_winlator_cmod_runtime_display_connector_SyncFenceFd_dupFd( + JNIEnv* env, jclass cls, jint fd) +{ + (void)env; (void)cls; + if (fd < 0) return -1; + int dup_fd = fcntl(fd, F_DUPFD_CLOEXEC, 0); + if (dup_fd < 0) LOGW("dup fd %d failed: %d", fd, errno); + return dup_fd; +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_display_connector_SyncFenceFd_signalEventFd( + JNIEnv* env, jclass cls, jint fd) +{ + (void)env; (void)cls; + if (fd < 0) return; + uint64_t one = 1; + ssize_t r; + do { + r = write(fd, &one, sizeof(one)); + } while (r < 0 && errno == EINTR); + if (r != (ssize_t)sizeof(one) && errno != EAGAIN) { + LOGW("eventfd signal failed on fd %d: %d", fd, errno); + } +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_display_connector_SyncFenceFd_drainEventFd( + JNIEnv* env, jclass cls, jint fd) +{ + (void)env; (void)cls; + if (fd < 0) return; + uint64_t buf; + ssize_t r; + do { + r = read(fd, &buf, sizeof(buf)); + } while (r < 0 && errno == EINTR); + // EAGAIN (nothing buffered) is expected; any other error is harmless here. +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_runtime_display_connector_SyncFenceFd_closeFd( + JNIEnv* env, jclass cls, jint fd) +{ + (void)env; (void)cls; + if (fd >= 0) close(fd); +} diff --git a/app/src/main/cpp/winlator/vk/bin2c.cmake b/app/src/main/cpp/winlator/vk/bin2c.cmake new file mode 100644 index 000000000..c986fac9b --- /dev/null +++ b/app/src/main/cpp/winlator/vk/bin2c.cmake @@ -0,0 +1,57 @@ +# bin2c.cmake — converts a binary SPIR-V file into a C header containing a uint32_t array. +# +# Required inputs: +# INPUT_FILE path to .spv binary +# OUTPUT_FILE path to .h to write +# VAR_NAME C identifier for the array +# +# Invocation: +# ${CMAKE_COMMAND} -DINPUT_FILE=... -DOUTPUT_FILE=... -DVAR_NAME=... -P bin2c.cmake + +if(NOT INPUT_FILE OR NOT OUTPUT_FILE OR NOT VAR_NAME) + message(FATAL_ERROR "bin2c.cmake requires INPUT_FILE, OUTPUT_FILE, VAR_NAME") +endif() + +file(READ "${INPUT_FILE}" hex_data HEX) +string(LENGTH "${hex_data}" hex_len) +math(EXPR byte_count "${hex_len} / 2") +math(EXPR word_count "${byte_count} / 4") + +# SPIR-V is little-endian; chunk the hex stream into 4-byte little-endian words. +set(words "") +set(line_words "") +set(words_per_line 0) +math(EXPR last_byte_offset "${hex_len} - 8") + +set(i 0) +while(i LESS hex_len) + string(SUBSTRING "${hex_data}" ${i} 2 b0) + math(EXPR i "${i} + 2") + string(SUBSTRING "${hex_data}" ${i} 2 b1) + math(EXPR i "${i} + 2") + string(SUBSTRING "${hex_data}" ${i} 2 b2) + math(EXPR i "${i} + 2") + string(SUBSTRING "${hex_data}" ${i} 2 b3) + math(EXPR i "${i} + 2") + string(APPEND line_words "0x${b3}${b2}${b1}${b0}, ") + math(EXPR words_per_line "${words_per_line} + 1") + if(words_per_line EQUAL 8) + string(APPEND words " ${line_words}\n") + set(line_words "") + set(words_per_line 0) + endif() +endwhile() +if(words_per_line GREATER 0) + string(APPEND words " ${line_words}\n") +endif() + +file(WRITE "${OUTPUT_FILE}" +"// Auto-generated from ${INPUT_FILE}. Do not edit. +#pragma once +#include +#include + +static const uint32_t ${VAR_NAME}[] = { +${words}}; +static const size_t ${VAR_NAME}_size = sizeof(${VAR_NAME}); +") diff --git a/app/src/main/cpp/winlator/vk/shaders/blit.frag b/app/src/main/cpp/winlator/vk/shaders/blit.frag new file mode 100644 index 000000000..18959355b --- /dev/null +++ b/app/src/main/cpp/winlator/vk/shaders/blit.frag @@ -0,0 +1,10 @@ +#version 450 + +layout(location = 0) in vec2 vUV; +layout(location = 0) out vec4 outColor; + +layout(set = 0, binding = 0) uniform sampler2D screenTexture; + +void main() { + outColor = texture(screenTexture, vUV); +} diff --git a/app/src/main/cpp/winlator/vk/shaders/cursor.frag b/app/src/main/cpp/winlator/vk/shaders/cursor.frag new file mode 100644 index 000000000..69df6e951 --- /dev/null +++ b/app/src/main/cpp/winlator/vk/shaders/cursor.frag @@ -0,0 +1,10 @@ +#version 450 + +layout(location = 0) in vec2 vUV; +layout(location = 0) out vec4 outColor; + +layout(set = 0, binding = 0) uniform sampler2D srcTexture; + +void main() { + outColor = texture(srcTexture, vUV); +} diff --git a/app/src/main/cpp/winlator/vk/shaders/effect_coloradj.frag b/app/src/main/cpp/winlator/vk/shaders/effect_coloradj.frag new file mode 100644 index 000000000..72891c484 --- /dev/null +++ b/app/src/main/cpp/winlator/vk/shaders/effect_coloradj.frag @@ -0,0 +1,21 @@ +#version 450 + +layout(location = 0) in vec2 vUV; +layout(location = 0) out vec4 outColor; + +layout(set = 0, binding = 0) uniform sampler2D screenTexture; + +layout(push_constant) uniform PC { + vec2 resolution; + float brightness; + float contrast; + float gamma; +} pc; + +void main() { + vec3 color = texture(screenTexture, vUV).rgb; + vec3 adjusted = color + vec3(pc.brightness); + adjusted = (adjusted - 0.5) * (1.0 + pc.contrast) + 0.5; + adjusted = pow(clamp(adjusted, 0.0, 1.0), vec3(1.0 / max(pc.gamma, 0.01))); + outColor = vec4(clamp(adjusted, 0.0, 1.0), 1.0); +} diff --git a/app/src/main/cpp/winlator/vk/shaders/effect_colorblind.frag b/app/src/main/cpp/winlator/vk/shaders/effect_colorblind.frag new file mode 100644 index 000000000..4c8e06dfd --- /dev/null +++ b/app/src/main/cpp/winlator/vk/shaders/effect_colorblind.frag @@ -0,0 +1,52 @@ +#version 450 + +layout(location = 0) in vec2 vUV; +layout(location = 0) out vec4 outColor; + +layout(set = 0, binding = 0) uniform sampler2D screenTexture; + +layout(push_constant) uniform PC { + vec2 resolution; + float mode; + float p1; + float p2; +} pc; + +void main() { + vec3 src = texture(screenTexture, vUV).rgb; + int m = int(pc.mode + 0.5); + + vec3 c = pow(src, vec3(2.2)); + + float L = 17.8824 * c.r + 43.5161 * c.g + 4.11935 * c.b; + float M = 3.45565 * c.r + 27.1554 * c.g + 3.86714 * c.b; + float S = 0.0299566 * c.r + 0.184309 * c.g + 1.46709 * c.b; + + float dL = L; + float dM = M; + float dS = S; + if (m == 1) { + dL = 2.02344 * M - 2.52581 * S; + } else if (m == 2) { + dM = 0.494207 * L + 1.24827 * S; + } else { + dS = -0.395913 * L + 0.801109 * M; + } + + vec3 sim; + sim.r = 0.0809444479 * dL - 0.130504409 * dM + 0.116721066 * dS; + sim.g = -0.0102485335 * dL + 0.0540193266 * dM - 0.113614708 * dS; + sim.b = -0.000365296938 * dL - 0.00412161469 * dM + 0.693511405 * dS; + + vec3 err = c - sim; + vec3 corrected = c; + if (m == 3) { + corrected.r += err.r + 0.7 * err.b; + corrected.g += err.g + 0.7 * err.b; + } else { + corrected.g += 0.7 * err.r + err.g; + corrected.b += 0.7 * err.r + err.b; + } + corrected = clamp(corrected, 0.0, 1.0); + outColor = vec4(pow(corrected, vec3(1.0 / 2.2)), 1.0); +} diff --git a/app/src/main/cpp/winlator/vk/shaders/effect_colorgrade.frag b/app/src/main/cpp/winlator/vk/shaders/effect_colorgrade.frag new file mode 100644 index 000000000..d9c85ed90 --- /dev/null +++ b/app/src/main/cpp/winlator/vk/shaders/effect_colorgrade.frag @@ -0,0 +1,23 @@ +#version 450 + +layout(location = 0) in vec2 vUV; +layout(location = 0) out vec4 outColor; + +layout(set = 0, binding = 0) uniform sampler2D screenTexture; + +layout(push_constant) uniform PC { + vec2 resolution; + float saturation; + float temperature; + float tint; +} pc; + +void main() { + vec3 c = texture(screenTexture, vUV).rgb; + c.r *= 1.0 + pc.temperature * 0.30; + c.b *= 1.0 - pc.temperature * 0.30; + c.g *= 1.0 + pc.tint * 0.20; + float luma = dot(c, vec3(0.299, 0.587, 0.114)); + c = mix(vec3(luma), c, pc.saturation); + outColor = vec4(clamp(c, 0.0, 1.0), 1.0); +} diff --git a/app/src/main/cpp/winlator/vk/shaders/effect_crt.frag b/app/src/main/cpp/winlator/vk/shaders/effect_crt.frag new file mode 100644 index 000000000..dd14a4fe4 --- /dev/null +++ b/app/src/main/cpp/winlator/vk/shaders/effect_crt.frag @@ -0,0 +1,29 @@ +#version 450 + +#define CA_AMOUNT 1.0025 +#define SCANLINE_INTENSITY_X 0.125 +#define SCANLINE_INTENSITY_Y 0.375 +#define SCANLINE_SIZE 1024.0 + +layout(location = 0) in vec2 vUV; +layout(location = 0) out vec4 outColor; + +layout(set = 0, binding = 0) uniform sampler2D screenTexture; + +layout(push_constant) uniform PC { + vec2 resolution; + float p0; + float p1; +} pc; + +void main() { + vec4 finalColor = texture(screenTexture, vUV); + finalColor.rgb = vec3( + texture(screenTexture, (vUV - 0.5) * CA_AMOUNT + 0.5).r, + finalColor.g, + texture(screenTexture, (vUV - 0.5) / CA_AMOUNT + 0.5).b + ); + float scanlineX = abs(sin(vUV.x * SCANLINE_SIZE) * 0.5 * SCANLINE_INTENSITY_X); + float scanlineY = abs(sin(vUV.y * SCANLINE_SIZE) * 0.5 * SCANLINE_INTENSITY_Y); + outColor = vec4(mix(finalColor.rgb, vec3(0.0), scanlineX + scanlineY), finalColor.a); +} diff --git a/app/src/main/cpp/winlator/vk/shaders/effect_hdr.frag b/app/src/main/cpp/winlator/vk/shaders/effect_hdr.frag new file mode 100644 index 000000000..92d49fca1 --- /dev/null +++ b/app/src/main/cpp/winlator/vk/shaders/effect_hdr.frag @@ -0,0 +1,49 @@ +#version 450 + +layout(location = 0) in vec2 vUV; +layout(location = 0) out vec4 outColor; + +layout(set = 0, binding = 0) uniform sampler2D screenTexture; + +layout(push_constant) uniform PC { + vec2 resolution; + float p0; + float p1; +} pc; + +const float HDRPower = 1.30; +const float radius1 = 0.793; +const float radius2 = 0.870; + +void main() { + vec2 texcoord = vUV; + vec2 px = 1.0 / pc.resolution; + vec3 color = texture(screenTexture, texcoord).rgb; + + vec3 bloom_sum1 = texture(screenTexture, texcoord + vec2( 1.5, -1.5) * radius1 * px).rgb; + bloom_sum1 += texture(screenTexture, texcoord + vec2(-1.5, -1.5) * radius1 * px).rgb; + bloom_sum1 += texture(screenTexture, texcoord + vec2( 1.5, 1.5) * radius1 * px).rgb; + bloom_sum1 += texture(screenTexture, texcoord + vec2(-1.5, 1.5) * radius1 * px).rgb; + bloom_sum1 += texture(screenTexture, texcoord + vec2( 0.0, -2.5) * radius1 * px).rgb; + bloom_sum1 += texture(screenTexture, texcoord + vec2( 0.0, 2.5) * radius1 * px).rgb; + bloom_sum1 += texture(screenTexture, texcoord + vec2(-2.5, 0.0) * radius1 * px).rgb; + bloom_sum1 += texture(screenTexture, texcoord + vec2( 2.5, 0.0) * radius1 * px).rgb; + bloom_sum1 *= 0.005; + + vec3 bloom_sum2 = texture(screenTexture, texcoord + vec2( 1.5, -1.5) * radius2 * px).rgb; + bloom_sum2 += texture(screenTexture, texcoord + vec2(-1.5, -1.5) * radius2 * px).rgb; + bloom_sum2 += texture(screenTexture, texcoord + vec2( 1.5, 1.5) * radius2 * px).rgb; + bloom_sum2 += texture(screenTexture, texcoord + vec2(-1.5, 1.5) * radius2 * px).rgb; + bloom_sum2 += texture(screenTexture, texcoord + vec2( 0.0, -2.5) * radius2 * px).rgb; + bloom_sum2 += texture(screenTexture, texcoord + vec2( 0.0, 2.5) * radius2 * px).rgb; + bloom_sum2 += texture(screenTexture, texcoord + vec2(-2.5, 0.0) * radius2 * px).rgb; + bloom_sum2 += texture(screenTexture, texcoord + vec2( 2.5, 0.0) * radius2 * px).rgb; + bloom_sum2 *= 0.010; + + float dist = radius2 - radius1; + vec3 HDR = (color + (bloom_sum2 - bloom_sum1)) * dist; + vec3 blend = HDR + color; + color = pow(abs(blend), vec3(abs(HDRPower))) + HDR; + + outColor = vec4(clamp(color, 0.0, 1.0), 1.0); +} diff --git a/app/src/main/cpp/winlator/vk/shaders/effect_natural.frag b/app/src/main/cpp/winlator/vk/shaders/effect_natural.frag new file mode 100644 index 000000000..ca3e8ba9e --- /dev/null +++ b/app/src/main/cpp/winlator/vk/shaders/effect_natural.frag @@ -0,0 +1,28 @@ +#version 450 + +layout(location = 0) in vec2 vUV; +layout(location = 0) out vec4 outColor; + +layout(set = 0, binding = 0) uniform sampler2D screenTexture; + +layout(push_constant) uniform PC { + vec2 resolution; + float p0; + float p1; +} pc; + +const mat3 RGBtoYIQ = mat3(0.299, 0.596, 0.212, + 0.587,-0.275,-0.523, + 0.114,-0.321, 0.311); +const mat3 YIQtoRGB = mat3(1.0, 1.0, 1.0, + 0.95568806,-0.27158179,-1.10817732, + 0.61985809,-0.64687381, 1.70506455); +const vec3 val00 = vec3(1.2, 1.2, 1.2); + +void main() { + vec3 c0 = texture(screenTexture, vUV).rgb; + vec3 t0 = c0 * RGBtoYIQ; + t0 = vec3(pow(t0.r, 1.12), t0.gb * val00.gb); + vec3 cFinal = t0 * YIQtoRGB; + outColor = vec4(cFinal, 1.0); +} diff --git a/app/src/main/cpp/winlator/vk/shaders/effect_ntsc.frag b/app/src/main/cpp/winlator/vk/shaders/effect_ntsc.frag new file mode 100644 index 000000000..2c864554f --- /dev/null +++ b/app/src/main/cpp/winlator/vk/shaders/effect_ntsc.frag @@ -0,0 +1,27 @@ +#version 450 + +layout(location = 0) in vec2 vUV; +layout(location = 0) out vec4 outColor; + +layout(set = 0, binding = 0) uniform sampler2D screenTexture; + +layout(push_constant) uniform PC { + vec2 resolution; + float p0; + float p1; + float p2; +} pc; + +void main() { + vec2 res = pc.resolution.x > 0.0 ? pc.resolution : vec2(1280.0, 720.0); + vec2 texel = 1.0 / max(res, vec2(1.0)); + + vec3 color = texture(screenTexture, vUV).rgb; + vec3 shifted = color; + shifted.r = texture(screenTexture, vUV + vec2(0.0, texel.y * 1.25)).r; + shifted.b = texture(screenTexture, vUV - vec2(0.0, texel.y * 1.25)).b; + + float bleed = sin((vUV.x * max(res.x, 1.0) + vUV.y * 24.0) * 0.45) * 0.018; + vec3 ntsc = clamp(mix(color, shifted + vec3(bleed), 0.65), 0.0, 1.0); + outColor = vec4(ntsc, 1.0); +} diff --git a/app/src/main/cpp/winlator/vk/shaders/effect_ntsc2.frag b/app/src/main/cpp/winlator/vk/shaders/effect_ntsc2.frag new file mode 100644 index 000000000..e6cfaeb73 --- /dev/null +++ b/app/src/main/cpp/winlator/vk/shaders/effect_ntsc2.frag @@ -0,0 +1,27 @@ +#version 450 + +layout(location = 0) in vec2 vUV; +layout(location = 0) out vec4 outColor; + +layout(set = 0, binding = 0) uniform sampler2D screenTexture; + +layout(push_constant) uniform PC { + vec2 resolution; + float p0; + float p1; + float p2; +} pc; + +void main() { + vec2 res = pc.resolution.x > 0.0 ? pc.resolution : vec2(1280.0, 720.0); + vec2 texel = 1.0 / max(res, vec2(1.0)); + + vec3 color = texture(screenTexture, vUV).rgb; + vec3 shifted = color; + shifted.r = texture(screenTexture, vUV + vec2(texel.x * 1.25, 0.0)).r; + shifted.b = texture(screenTexture, vUV - vec2(texel.x * 1.25, 0.0)).b; + + float bleed = sin((vUV.y * max(res.y, 1.0) + vUV.x * 24.0) * 0.45) * 0.018; + vec3 ntsc = clamp(mix(color, shifted + vec3(bleed), 0.65), 0.0, 1.0); + outColor = vec4(ntsc, 1.0); +} diff --git a/app/src/main/cpp/winlator/vk/shaders/effect_pixelate.frag b/app/src/main/cpp/winlator/vk/shaders/effect_pixelate.frag new file mode 100644 index 000000000..129dfa7ed --- /dev/null +++ b/app/src/main/cpp/winlator/vk/shaders/effect_pixelate.frag @@ -0,0 +1,21 @@ +#version 450 + +layout(location = 0) in vec2 vUV; +layout(location = 0) out vec4 outColor; + +layout(set = 0, binding = 0) uniform sampler2D screenTexture; + +layout(push_constant) uniform PC { + vec2 resolution; + float blockSize; + float p1; + float p2; +} pc; + +void main() { + vec2 res = pc.resolution.x > 0.0 ? pc.resolution : vec2(1280.0, 720.0); + float bs = max(pc.blockSize, 1.0); + vec2 grid = max(res / bs, vec2(1.0)); + vec2 uv = (floor(vUV * grid) + 0.5) / grid; + outColor = vec4(texture(screenTexture, uv).rgb, 1.0); +} diff --git a/app/src/main/cpp/winlator/vk/shaders/effect_scanlines.frag b/app/src/main/cpp/winlator/vk/shaders/effect_scanlines.frag new file mode 100644 index 000000000..1d8260021 --- /dev/null +++ b/app/src/main/cpp/winlator/vk/shaders/effect_scanlines.frag @@ -0,0 +1,21 @@ +#version 450 + +layout(location = 0) in vec2 vUV; +layout(location = 0) out vec4 outColor; + +layout(set = 0, binding = 0) uniform sampler2D screenTexture; + +layout(push_constant) uniform PC { + vec2 resolution; + float intensity; + float p1; + float p2; +} pc; + +void main() { + vec2 res = pc.resolution.x > 0.0 ? pc.resolution : vec2(1280.0, 720.0); + vec3 c = texture(screenTexture, vUV).rgb; + float band = 0.5 + 0.5 * sin(vUV.y * res.y * 1.5708); + c *= 1.0 - pc.intensity * (1.0 - band); + outColor = vec4(clamp(c, 0.0, 1.0), 1.0); +} diff --git a/app/src/main/cpp/winlator/vk/shaders/effect_sharpen.frag b/app/src/main/cpp/winlator/vk/shaders/effect_sharpen.frag new file mode 100644 index 000000000..c6f4907a7 --- /dev/null +++ b/app/src/main/cpp/winlator/vk/shaders/effect_sharpen.frag @@ -0,0 +1,29 @@ +#version 450 + +layout(location = 0) in vec2 vUV; +layout(location = 0) out vec4 outColor; + +layout(set = 0, binding = 0) uniform sampler2D screenTexture; + +layout(push_constant) uniform PC { + vec2 resolution; + float strength; + float p1; + float p2; +} pc; + +void main() { + vec2 res = pc.resolution.x > 0.0 ? pc.resolution : vec2(1280.0, 720.0); + vec2 t = 1.0 / max(res, vec2(1.0)); + + vec3 c = texture(screenTexture, vUV).rgb; + vec3 blur = ( + texture(screenTexture, vUV + vec2(0.0, -t.y)).rgb + + texture(screenTexture, vUV + vec2(0.0, t.y)).rgb + + texture(screenTexture, vUV + vec2(-t.x, 0.0)).rgb + + texture(screenTexture, vUV + vec2( t.x, 0.0)).rgb + ) * 0.25; + + vec3 sharp = c + (c - blur) * (pc.strength * 1.5); + outColor = vec4(clamp(sharp, 0.0, 1.0), 1.0); +} diff --git a/app/src/main/cpp/winlator/vk/shaders/effect_toon.frag b/app/src/main/cpp/winlator/vk/shaders/effect_toon.frag new file mode 100644 index 000000000..0efeefdd7 --- /dev/null +++ b/app/src/main/cpp/winlator/vk/shaders/effect_toon.frag @@ -0,0 +1,20 @@ +#version 450 + +layout(location = 0) in vec2 vUV; +layout(location = 0) out vec4 outColor; + +layout(set = 0, binding = 0) uniform sampler2D screenTexture; + +layout(push_constant) uniform PC { + vec2 resolution; + float p0; + float p1; + float p2; +} pc; + +void main() { + vec3 c = texture(screenTexture, vUV).rgb; + float levels = 6.0; + vec3 toon = floor(clamp(c, 0.0, 1.0) * levels + 0.5) / levels; + outColor = vec4(toon, 1.0); +} diff --git a/app/src/main/cpp/winlator/vk/shaders/effect_vivid.frag b/app/src/main/cpp/winlator/vk/shaders/effect_vivid.frag new file mode 100644 index 000000000..19b615f88 --- /dev/null +++ b/app/src/main/cpp/winlator/vk/shaders/effect_vivid.frag @@ -0,0 +1,37 @@ +#version 450 + +layout(location = 0) in vec2 vUV; +layout(location = 0) out vec4 outColor; + +layout(set = 0, binding = 0) uniform sampler2D screenTexture; + +layout(push_constant) uniform PC { + vec2 resolution; + float saturation; + float contrast; + float sharpness; + float mode; +} pc; + +void main() { + float SAT = pc.saturation > 0.0 ? pc.saturation : 1.0; + float CON = pc.contrast > 0.0 ? pc.contrast : 1.0; + float SHARP = pc.sharpness > 0.0 ? pc.sharpness : 0.5; + + vec2 res = pc.resolution.x > 0.0 ? pc.resolution : vec2(1280.0, 720.0); + vec2 stp = 1.0 / res; + + vec3 center = texture(screenTexture, vUV).rgb; + center = (center - 0.5) * CON + 0.5; + float gray = dot(center, vec3(0.299, 0.587, 0.114)); + center = mix(vec3(gray), center, SAT); + vec3 blur = ( + texture(screenTexture, vUV + vec2(0.0, -stp.y)).rgb + + texture(screenTexture, vUV + vec2(0.0, stp.y)).rgb + + texture(screenTexture, vUV + vec2(-stp.x, 0.0)).rgb + + texture(screenTexture, vUV + vec2( stp.x, 0.0)).rgb + ) * 0.25; + + center = center + (center - blur) * SHARP; + outColor = vec4(clamp(center, 0.0, 1.0), 1.0); +} diff --git a/app/src/main/cpp/winlator/vk/shaders/quad.vert b/app/src/main/cpp/winlator/vk/shaders/quad.vert new file mode 100644 index 000000000..34e88df8b --- /dev/null +++ b/app/src/main/cpp/winlator/vk/shaders/quad.vert @@ -0,0 +1,10 @@ +#version 450 + +layout(location = 0) out vec2 vUV; + +// Full-screen triangle: covers the viewport with a single triangle drawn from 3 vertices. +// gl_VertexIndex 0 -> (-1,-1), 1 -> (3,-1), 2 -> (-1,3); UV mirrors clip-space [0..1] within visible quad. +void main() { + vUV = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2); + gl_Position = vec4(vUV * 2.0 - 1.0, 0.0, 1.0); +} diff --git a/app/src/main/cpp/winlator/vk/shaders/sgsr1.frag b/app/src/main/cpp/winlator/vk/shaders/sgsr1.frag new file mode 100644 index 000000000..37a7bc07b --- /dev/null +++ b/app/src/main/cpp/winlator/vk/shaders/sgsr1.frag @@ -0,0 +1,108 @@ +#version 450 + +// Snapdragon Game Super Resolution 1 spatial upscale pass. +// +// Adapted for WinNative's Vulkan compositor from Qualcomm's SGSR v1 mobile +// fragment shader. +// Copyright (c) 2023, Qualcomm Innovation Center, Inc. All rights reserved. +// SPDX-License-Identifier: BSD-3-Clause + +// Keep SGSR math mediump, but texel-space coordinates highp. +precision mediump float; +precision highp int; + +layout(location = 0) in highp vec2 vUV; +layout(location = 0) out vec4 outColor; + +layout(set = 0, binding = 0) uniform mediump sampler2D screenTexture; + +layout(push_constant) uniform PC { + vec2 resolution; + float saturation; + float contrast; + float sharpness; + float mode; +} pc; + +const int OPERATION_MODE = 1; // RGBA mode uses green as the luma proxy. +const float EDGE_THRESHOLD = 8.0 / 255.0; + +float fastLanczos2(float x) { + float wA = x - 4.0; + float wB = x * wA - wA; + wA *= wA; + return wB * wA; +} + +vec2 weightY(float dx, float dy, float c, float std) { + float x = (dx * dx + dy * dy) * 0.55 + clamp(abs(c) * std, 0.0, 1.0); + float w = fastLanczos2(x); + return vec2(w, w * c); +} + +void main() { + highp vec2 inputSize = vec2(textureSize(screenTexture, 0)); + vec4 color = vec4(textureLod(screenTexture, vUV, 0.0).rgb, 1.0); + + if (inputSize.x < 2.0 || inputSize.y < 2.0) { + outColor = color; + return; + } + + highp vec4 viewportInfo = vec4(1.0 / inputSize, inputSize); + highp vec2 imgCoord = vUV * viewportInfo.zw + vec2(-0.5, 0.5); + highp vec2 imgCoordPixel = floor(imgCoord); + highp vec2 coord = imgCoordPixel * viewportInfo.xy; + vec2 pl = imgCoord - imgCoordPixel; + + vec4 left = textureGather(screenTexture, coord, OPERATION_MODE); + float centerY = color[OPERATION_MODE]; + float edgeVote = abs(left.z - left.y) + abs(centerY - left.y) + abs(centerY - left.z); + + if (edgeVote > EDGE_THRESHOLD) { + coord.x += viewportInfo.x; + vec4 right = textureGather(screenTexture, coord + vec2(viewportInfo.x, 0.0), + OPERATION_MODE); + vec4 upDown; + upDown.xy = textureGather(screenTexture, coord + vec2(0.0, -viewportInfo.y), + OPERATION_MODE).wz; + upDown.zw = textureGather(screenTexture, coord + vec2(0.0, viewportInfo.y), + OPERATION_MODE).yx; + + float mean = (left.y + left.z + right.x + right.w) * 0.25; + left -= vec4(mean); + right -= vec4(mean); + upDown -= vec4(mean); + color.w = centerY - mean; + + float sum = + abs(left.x) + abs(left.y) + abs(left.z) + abs(left.w) + + abs(right.x) + abs(right.y) + abs(right.z) + abs(right.w) + + abs(upDown.x) + abs(upDown.y) + abs(upDown.z) + abs(upDown.w); + float std = 2.181818 / max(sum, 1.0e-6); + + vec2 aWY = weightY(pl.x, pl.y + 1.0, upDown.x, std); + aWY += weightY(pl.x - 1.0, pl.y + 1.0, upDown.y, std); + aWY += weightY(pl.x - 1.0, pl.y - 2.0, upDown.z, std); + aWY += weightY(pl.x, pl.y - 2.0, upDown.w, std); + aWY += weightY(pl.x + 1.0, pl.y - 1.0, left.x, std); + aWY += weightY(pl.x, pl.y - 1.0, left.y, std); + aWY += weightY(pl.x, pl.y, left.z, std); + aWY += weightY(pl.x + 1.0, pl.y, left.w, std); + aWY += weightY(pl.x - 1.0, pl.y - 1.0, right.x, std); + aWY += weightY(pl.x - 2.0, pl.y - 1.0, right.y, std); + aWY += weightY(pl.x - 2.0, pl.y, right.z, std); + aWY += weightY(pl.x - 1.0, pl.y, right.w, std); + + float finalY = aWY.y / max(aWY.x, 1.0e-6); + float maxY = max(max(left.y, left.z), max(right.x, right.w)); + float minY = min(min(left.y, left.z), min(right.x, right.w)); + float edgeSharpness = mix(1.0, 2.0, clamp(pc.sharpness, 0.0, 1.0)); + finalY = clamp(edgeSharpness * finalY, minY, maxY); + + float deltaY = clamp(finalY - color.w, -23.0 / 255.0, 23.0 / 255.0); + color.rgb = clamp(color.rgb + vec3(deltaY), 0.0, 1.0); + } + + outColor = vec4(color.rgb, 1.0); +} diff --git a/app/src/main/cpp/winlator/vk/shaders/window.frag b/app/src/main/cpp/winlator/vk/shaders/window.frag new file mode 100644 index 000000000..08fc442aa --- /dev/null +++ b/app/src/main/cpp/winlator/vk/shaders/window.frag @@ -0,0 +1,20 @@ +#version 450 + +layout(location = 0) in vec2 vUV; +layout(location = 0) out vec4 outColor; + +layout(set = 0, binding = 0) uniform sampler2D srcTexture; +layout(push_constant) uniform PC { + float xform[6]; + vec2 viewSize; + vec4 uvRect; + int swapRB; +} pc; + +void main() { + vec3 color = texture(srcTexture, vUV).rgb; + if (pc.swapRB != 0) { + color = color.bgr; + } + outColor = vec4(color, 1.0); +} diff --git a/app/src/main/cpp/winlator/vk/shaders/window.vert b/app/src/main/cpp/winlator/vk/shaders/window.vert new file mode 100644 index 000000000..ff27e9b0d --- /dev/null +++ b/app/src/main/cpp/winlator/vk/shaders/window.vert @@ -0,0 +1,23 @@ +#version 450 + +layout(location = 0) in vec2 position; +layout(location = 0) out vec2 vUV; + +layout(push_constant) uniform PC { + float xform[6]; + vec2 viewSize; + vec4 uvRect; +} pc; + +void main() { + vUV = mix(pc.uvRect.xy, pc.uvRect.zw, position); + vec2 t = vec2( + pc.xform[0] * position.x + pc.xform[2] * position.y + pc.xform[4], + pc.xform[1] * position.x + pc.xform[3] * position.y + pc.xform[5] + ); + gl_Position = vec4( + 2.0 * t.x / pc.viewSize.x - 1.0, + 2.0 * t.y / pc.viewSize.y - 1.0, + 0.0, 1.0 + ); +} diff --git a/app/src/main/cpp/winlator/vk/vk_dispatch.c b/app/src/main/cpp/winlator/vk/vk_dispatch.c new file mode 100644 index 000000000..870c1456c --- /dev/null +++ b/app/src/main/cpp/winlator/vk/vk_dispatch.c @@ -0,0 +1,177 @@ +#include "vk_dispatch.h" + +#include +#include +#include +#include + +#define LOG_TAG "VkDispatch" +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) + +VkDispatch vkd; + +bool vkd_init(void* libvulkan_handle) { + if (!libvulkan_handle) return false; + memset(&vkd, 0, sizeof(vkd)); + + vkd.GetInstanceProcAddr = + (PFN_vkGetInstanceProcAddr)dlsym(libvulkan_handle, "vkGetInstanceProcAddr"); + if (!vkd.GetInstanceProcAddr) { + LOGE("dlsym(vkGetInstanceProcAddr) failed: %s", dlerror()); + return false; + } + + // Per spec, these three resolve with VK_NULL_HANDLE before any instance exists. + vkd.CreateInstance = (PFN_vkCreateInstance) + vkd.GetInstanceProcAddr(VK_NULL_HANDLE, "vkCreateInstance"); + vkd.EnumerateInstanceExtensionProperties = (PFN_vkEnumerateInstanceExtensionProperties) + vkd.GetInstanceProcAddr(VK_NULL_HANDLE, "vkEnumerateInstanceExtensionProperties"); + vkd.EnumerateInstanceLayerProperties = (PFN_vkEnumerateInstanceLayerProperties) + vkd.GetInstanceProcAddr(VK_NULL_HANDLE, "vkEnumerateInstanceLayerProperties"); + + if (!vkd.CreateInstance) { + LOGE("vkGetInstanceProcAddr returned NULL for vkCreateInstance"); + return false; + } + return true; +} + +bool vkd_load_instance(VkInstance instance) { + if (!vkd.GetInstanceProcAddr || instance == VK_NULL_HANDLE) return false; + + // Device entry points resolve via vkGetInstanceProcAddr too — the loader trampolines. + // See vk_dispatch.h for the rationale. + #define LOAD(name) \ + vkd.name = (PFN_vk##name)vkd.GetInstanceProcAddr(instance, "vk" #name) + + // Instance / physical-device + LOAD(DestroyInstance); + LOAD(EnumeratePhysicalDevices); + LOAD(GetPhysicalDeviceProperties); + LOAD(GetPhysicalDeviceMemoryProperties); + LOAD(GetPhysicalDeviceQueueFamilyProperties); + LOAD(GetPhysicalDeviceFormatProperties); + LOAD(GetPhysicalDeviceImageFormatProperties); + LOAD(GetPhysicalDeviceSurfaceCapabilitiesKHR); + LOAD(GetPhysicalDeviceSurfaceFormatsKHR); + LOAD(GetPhysicalDeviceSurfacePresentModesKHR); + LOAD(GetPhysicalDeviceSurfaceSupportKHR); + LOAD(CreateAndroidSurfaceKHR); + LOAD(DestroySurfaceKHR); + LOAD(CreateDevice); + LOAD(EnumerateDeviceExtensionProperties); + LOAD(CreateDebugUtilsMessengerEXT); + LOAD(DestroyDebugUtilsMessengerEXT); + + // Device + LOAD(GetDeviceProcAddr); + LOAD(DestroyDevice); + LOAD(GetDeviceQueue); + LOAD(DeviceWaitIdle); + + // Memory + LOAD(AllocateMemory); + LOAD(FreeMemory); + LOAD(MapMemory); + LOAD(UnmapMemory); + LOAD(FlushMappedMemoryRanges); + LOAD(GetAndroidHardwareBufferPropertiesANDROID); + + // Buffer + LOAD(CreateBuffer); + LOAD(DestroyBuffer); + LOAD(BindBufferMemory); + LOAD(GetBufferMemoryRequirements); + + // Image / image view + LOAD(CreateImage); + LOAD(DestroyImage); + LOAD(BindImageMemory); + LOAD(GetImageMemoryRequirements); + LOAD(CreateImageView); + LOAD(DestroyImageView); + + // Sampler / YCbCr + LOAD(CreateSampler); + LOAD(DestroySampler); + LOAD(CreateSamplerYcbcrConversion); + LOAD(DestroySamplerYcbcrConversion); + LOAD(CreateSamplerYcbcrConversionKHR); + LOAD(DestroySamplerYcbcrConversionKHR); + + // Descriptors + LOAD(CreateDescriptorSetLayout); + LOAD(DestroyDescriptorSetLayout); + LOAD(CreateDescriptorPool); + LOAD(DestroyDescriptorPool); + LOAD(AllocateDescriptorSets); + LOAD(FreeDescriptorSets); + LOAD(UpdateDescriptorSets); + + // Pipeline + LOAD(CreatePipelineLayout); + LOAD(DestroyPipelineLayout); + LOAD(CreateGraphicsPipelines); + LOAD(DestroyPipeline); + LOAD(CreateShaderModule); + LOAD(DestroyShaderModule); + + // RenderPass / Framebuffer + LOAD(CreateRenderPass); + LOAD(DestroyRenderPass); + LOAD(CreateFramebuffer); + LOAD(DestroyFramebuffer); + + // Sync + LOAD(CreateFence); + LOAD(DestroyFence); + LOAD(ResetFences); + LOAD(WaitForFences); + LOAD(CreateSemaphore); + LOAD(DestroySemaphore); + + // Commands + LOAD(CreateCommandPool); + LOAD(DestroyCommandPool); + LOAD(ResetCommandPool); + LOAD(AllocateCommandBuffers); + LOAD(FreeCommandBuffers); + LOAD(BeginCommandBuffer); + LOAD(EndCommandBuffer); + LOAD(ResetCommandBuffer); + LOAD(CmdBeginRenderPass); + LOAD(CmdEndRenderPass); + LOAD(CmdBindPipeline); + LOAD(CmdBindDescriptorSets); + LOAD(CmdBindVertexBuffers); + LOAD(CmdPushConstants); + LOAD(CmdSetViewport); + LOAD(CmdSetScissor); + LOAD(CmdDraw); + LOAD(CmdPipelineBarrier); + LOAD(CmdCopyBufferToImage); + LOAD(CmdBlitImage); + + // Queue + LOAD(QueueSubmit); + LOAD(QueueWaitIdle); + LOAD(QueuePresentKHR); + + // Swapchain + LOAD(CreateSwapchainKHR); + LOAD(DestroySwapchainKHR); + LOAD(GetSwapchainImagesKHR); + LOAD(AcquireNextImageKHR); + + #undef LOAD + + if (!vkd.DestroyInstance || !vkd.EnumeratePhysicalDevices || !vkd.CreateDevice) { + LOGE("vkd_load_instance: required core entry points missing"); + return false; + } + return true; +} + +void vkd_unload(void) { + memset(&vkd, 0, sizeof(vkd)); +} diff --git a/app/src/main/cpp/winlator/vk/vk_dispatch.h b/app/src/main/cpp/winlator/vk/vk_dispatch.h new file mode 100644 index 000000000..812b084b6 --- /dev/null +++ b/app/src/main/cpp/winlator/vk/vk_dispatch.h @@ -0,0 +1,266 @@ +// Function-pointer dispatch for the compositor's Vulkan calls. +// +// Required because adrenotools-loaded drivers live in an isolated linker namespace and do +// not share global symbols with the system loader — every call must resolve through the +// libvulkan handle chosen at dlopen time. +// +// Init order: vkd_init(handle) -> vkCreateInstance(...) -> vkd_load_instance(instance). + +#pragma once + +#ifndef VK_NO_PROTOTYPES +#define VK_NO_PROTOTYPES +#endif +#include +#include + +typedef struct VkDispatch { + // Loader-level (resolved via dlsym + vkGetInstanceProcAddr(NULL, ...)) + PFN_vkGetInstanceProcAddr GetInstanceProcAddr; + PFN_vkCreateInstance CreateInstance; + PFN_vkEnumerateInstanceExtensionProperties EnumerateInstanceExtensionProperties; + PFN_vkEnumerateInstanceLayerProperties EnumerateInstanceLayerProperties; + + // Instance / physical-device + PFN_vkDestroyInstance DestroyInstance; + PFN_vkEnumeratePhysicalDevices EnumeratePhysicalDevices; + PFN_vkGetPhysicalDeviceProperties GetPhysicalDeviceProperties; + PFN_vkGetPhysicalDeviceMemoryProperties GetPhysicalDeviceMemoryProperties; + PFN_vkGetPhysicalDeviceQueueFamilyProperties GetPhysicalDeviceQueueFamilyProperties; + PFN_vkGetPhysicalDeviceFormatProperties GetPhysicalDeviceFormatProperties; + PFN_vkGetPhysicalDeviceImageFormatProperties GetPhysicalDeviceImageFormatProperties; + PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR GetPhysicalDeviceSurfaceCapabilitiesKHR; + PFN_vkGetPhysicalDeviceSurfaceFormatsKHR GetPhysicalDeviceSurfaceFormatsKHR; + PFN_vkGetPhysicalDeviceSurfacePresentModesKHR GetPhysicalDeviceSurfacePresentModesKHR; + PFN_vkGetPhysicalDeviceSurfaceSupportKHR GetPhysicalDeviceSurfaceSupportKHR; + PFN_vkCreateAndroidSurfaceKHR CreateAndroidSurfaceKHR; + PFN_vkDestroySurfaceKHR DestroySurfaceKHR; + PFN_vkCreateDevice CreateDevice; + PFN_vkEnumerateDeviceExtensionProperties EnumerateDeviceExtensionProperties; + PFN_vkCreateDebugUtilsMessengerEXT CreateDebugUtilsMessengerEXT; + PFN_vkDestroyDebugUtilsMessengerEXT DestroyDebugUtilsMessengerEXT; + + // Device + PFN_vkGetDeviceProcAddr GetDeviceProcAddr; + PFN_vkDestroyDevice DestroyDevice; + PFN_vkGetDeviceQueue GetDeviceQueue; + PFN_vkDeviceWaitIdle DeviceWaitIdle; + + // Memory + PFN_vkAllocateMemory AllocateMemory; + PFN_vkFreeMemory FreeMemory; + PFN_vkMapMemory MapMemory; + PFN_vkUnmapMemory UnmapMemory; + PFN_vkFlushMappedMemoryRanges FlushMappedMemoryRanges; + PFN_vkGetAndroidHardwareBufferPropertiesANDROID GetAndroidHardwareBufferPropertiesANDROID; + + // Buffer + PFN_vkCreateBuffer CreateBuffer; + PFN_vkDestroyBuffer DestroyBuffer; + PFN_vkBindBufferMemory BindBufferMemory; + PFN_vkGetBufferMemoryRequirements GetBufferMemoryRequirements; + + // Image / image view + PFN_vkCreateImage CreateImage; + PFN_vkDestroyImage DestroyImage; + PFN_vkBindImageMemory BindImageMemory; + PFN_vkGetImageMemoryRequirements GetImageMemoryRequirements; + PFN_vkCreateImageView CreateImageView; + PFN_vkDestroyImageView DestroyImageView; + + // Sampler / YCbCr + PFN_vkCreateSampler CreateSampler; + PFN_vkDestroySampler DestroySampler; + PFN_vkCreateSamplerYcbcrConversion CreateSamplerYcbcrConversion; + PFN_vkDestroySamplerYcbcrConversion DestroySamplerYcbcrConversion; + PFN_vkCreateSamplerYcbcrConversionKHR CreateSamplerYcbcrConversionKHR; + PFN_vkDestroySamplerYcbcrConversionKHR DestroySamplerYcbcrConversionKHR; + + // Descriptors + PFN_vkCreateDescriptorSetLayout CreateDescriptorSetLayout; + PFN_vkDestroyDescriptorSetLayout DestroyDescriptorSetLayout; + PFN_vkCreateDescriptorPool CreateDescriptorPool; + PFN_vkDestroyDescriptorPool DestroyDescriptorPool; + PFN_vkAllocateDescriptorSets AllocateDescriptorSets; + PFN_vkFreeDescriptorSets FreeDescriptorSets; + PFN_vkUpdateDescriptorSets UpdateDescriptorSets; + + // Pipeline + PFN_vkCreatePipelineLayout CreatePipelineLayout; + PFN_vkDestroyPipelineLayout DestroyPipelineLayout; + PFN_vkCreateGraphicsPipelines CreateGraphicsPipelines; + PFN_vkDestroyPipeline DestroyPipeline; + PFN_vkCreateShaderModule CreateShaderModule; + PFN_vkDestroyShaderModule DestroyShaderModule; + + // RenderPass / Framebuffer + PFN_vkCreateRenderPass CreateRenderPass; + PFN_vkDestroyRenderPass DestroyRenderPass; + PFN_vkCreateFramebuffer CreateFramebuffer; + PFN_vkDestroyFramebuffer DestroyFramebuffer; + + // Sync + PFN_vkCreateFence CreateFence; + PFN_vkDestroyFence DestroyFence; + PFN_vkResetFences ResetFences; + PFN_vkWaitForFences WaitForFences; + PFN_vkCreateSemaphore CreateSemaphore; + PFN_vkDestroySemaphore DestroySemaphore; + + // Commands + PFN_vkCreateCommandPool CreateCommandPool; + PFN_vkDestroyCommandPool DestroyCommandPool; + PFN_vkResetCommandPool ResetCommandPool; + PFN_vkAllocateCommandBuffers AllocateCommandBuffers; + PFN_vkFreeCommandBuffers FreeCommandBuffers; + PFN_vkBeginCommandBuffer BeginCommandBuffer; + PFN_vkEndCommandBuffer EndCommandBuffer; + PFN_vkResetCommandBuffer ResetCommandBuffer; + PFN_vkCmdBeginRenderPass CmdBeginRenderPass; + PFN_vkCmdEndRenderPass CmdEndRenderPass; + PFN_vkCmdBindPipeline CmdBindPipeline; + PFN_vkCmdBindDescriptorSets CmdBindDescriptorSets; + PFN_vkCmdBindVertexBuffers CmdBindVertexBuffers; + PFN_vkCmdPushConstants CmdPushConstants; + PFN_vkCmdSetViewport CmdSetViewport; + PFN_vkCmdSetScissor CmdSetScissor; + PFN_vkCmdDraw CmdDraw; + PFN_vkCmdPipelineBarrier CmdPipelineBarrier; + PFN_vkCmdCopyBufferToImage CmdCopyBufferToImage; + PFN_vkCmdBlitImage CmdBlitImage; + + // Queue + PFN_vkQueueSubmit QueueSubmit; + PFN_vkQueueWaitIdle QueueWaitIdle; + PFN_vkQueuePresentKHR QueuePresentKHR; + + // Swapchain + PFN_vkCreateSwapchainKHR CreateSwapchainKHR; + PFN_vkDestroySwapchainKHR DestroySwapchainKHR; + PFN_vkGetSwapchainImagesKHR GetSwapchainImagesKHR; + PFN_vkAcquireNextImageKHR AcquireNextImageKHR; +} VkDispatch; + +extern VkDispatch vkd; + +bool vkd_init(void* libvulkan_handle); + +// Loads device-level pointers via vkGetInstanceProcAddr too — the loader trampolines, which +// costs a few ns per call but avoids partitioning instance vs. device scope. +bool vkd_load_instance(VkInstance instance); + +// Must be called before dlclose so stale-pointer crashes fault on NULL. +void vkd_unload(void); + +// Redirect bare `vkFoo` names to the dispatch table. + +#define vkGetInstanceProcAddr vkd.GetInstanceProcAddr +#define vkCreateInstance vkd.CreateInstance +#define vkEnumerateInstanceExtensionProperties vkd.EnumerateInstanceExtensionProperties +#define vkEnumerateInstanceLayerProperties vkd.EnumerateInstanceLayerProperties + +#define vkDestroyInstance vkd.DestroyInstance +#define vkEnumeratePhysicalDevices vkd.EnumeratePhysicalDevices +#define vkGetPhysicalDeviceProperties vkd.GetPhysicalDeviceProperties +#define vkGetPhysicalDeviceMemoryProperties vkd.GetPhysicalDeviceMemoryProperties +#define vkGetPhysicalDeviceQueueFamilyProperties vkd.GetPhysicalDeviceQueueFamilyProperties +#define vkGetPhysicalDeviceFormatProperties vkd.GetPhysicalDeviceFormatProperties +#define vkGetPhysicalDeviceImageFormatProperties vkd.GetPhysicalDeviceImageFormatProperties +#define vkGetPhysicalDeviceSurfaceCapabilitiesKHR vkd.GetPhysicalDeviceSurfaceCapabilitiesKHR +#define vkGetPhysicalDeviceSurfaceFormatsKHR vkd.GetPhysicalDeviceSurfaceFormatsKHR +#define vkGetPhysicalDeviceSurfacePresentModesKHR vkd.GetPhysicalDeviceSurfacePresentModesKHR +#define vkGetPhysicalDeviceSurfaceSupportKHR vkd.GetPhysicalDeviceSurfaceSupportKHR +#define vkCreateAndroidSurfaceKHR vkd.CreateAndroidSurfaceKHR +#define vkDestroySurfaceKHR vkd.DestroySurfaceKHR +#define vkCreateDevice vkd.CreateDevice +#define vkEnumerateDeviceExtensionProperties vkd.EnumerateDeviceExtensionProperties +#define vkCreateDebugUtilsMessengerEXT vkd.CreateDebugUtilsMessengerEXT +#define vkDestroyDebugUtilsMessengerEXT vkd.DestroyDebugUtilsMessengerEXT + +#define vkGetDeviceProcAddr vkd.GetDeviceProcAddr +#define vkDestroyDevice vkd.DestroyDevice +#define vkGetDeviceQueue vkd.GetDeviceQueue +#define vkDeviceWaitIdle vkd.DeviceWaitIdle + +#define vkAllocateMemory vkd.AllocateMemory +#define vkFreeMemory vkd.FreeMemory +#define vkMapMemory vkd.MapMemory +#define vkUnmapMemory vkd.UnmapMemory +#define vkFlushMappedMemoryRanges vkd.FlushMappedMemoryRanges +#define vkGetAndroidHardwareBufferPropertiesANDROID vkd.GetAndroidHardwareBufferPropertiesANDROID + +#define vkCreateBuffer vkd.CreateBuffer +#define vkDestroyBuffer vkd.DestroyBuffer +#define vkBindBufferMemory vkd.BindBufferMemory +#define vkGetBufferMemoryRequirements vkd.GetBufferMemoryRequirements + +#define vkCreateImage vkd.CreateImage +#define vkDestroyImage vkd.DestroyImage +#define vkBindImageMemory vkd.BindImageMemory +#define vkGetImageMemoryRequirements vkd.GetImageMemoryRequirements +#define vkCreateImageView vkd.CreateImageView +#define vkDestroyImageView vkd.DestroyImageView + +#define vkCreateSampler vkd.CreateSampler +#define vkDestroySampler vkd.DestroySampler +#define vkCreateSamplerYcbcrConversion vkd.CreateSamplerYcbcrConversion +#define vkDestroySamplerYcbcrConversion vkd.DestroySamplerYcbcrConversion +#define vkCreateSamplerYcbcrConversionKHR vkd.CreateSamplerYcbcrConversionKHR +#define vkDestroySamplerYcbcrConversionKHR vkd.DestroySamplerYcbcrConversionKHR + +#define vkCreateDescriptorSetLayout vkd.CreateDescriptorSetLayout +#define vkDestroyDescriptorSetLayout vkd.DestroyDescriptorSetLayout +#define vkCreateDescriptorPool vkd.CreateDescriptorPool +#define vkDestroyDescriptorPool vkd.DestroyDescriptorPool +#define vkAllocateDescriptorSets vkd.AllocateDescriptorSets +#define vkFreeDescriptorSets vkd.FreeDescriptorSets +#define vkUpdateDescriptorSets vkd.UpdateDescriptorSets + +#define vkCreatePipelineLayout vkd.CreatePipelineLayout +#define vkDestroyPipelineLayout vkd.DestroyPipelineLayout +#define vkCreateGraphicsPipelines vkd.CreateGraphicsPipelines +#define vkDestroyPipeline vkd.DestroyPipeline +#define vkCreateShaderModule vkd.CreateShaderModule +#define vkDestroyShaderModule vkd.DestroyShaderModule + +#define vkCreateRenderPass vkd.CreateRenderPass +#define vkDestroyRenderPass vkd.DestroyRenderPass +#define vkCreateFramebuffer vkd.CreateFramebuffer +#define vkDestroyFramebuffer vkd.DestroyFramebuffer + +#define vkCreateFence vkd.CreateFence +#define vkDestroyFence vkd.DestroyFence +#define vkResetFences vkd.ResetFences +#define vkWaitForFences vkd.WaitForFences +#define vkCreateSemaphore vkd.CreateSemaphore +#define vkDestroySemaphore vkd.DestroySemaphore + +#define vkCreateCommandPool vkd.CreateCommandPool +#define vkDestroyCommandPool vkd.DestroyCommandPool +#define vkResetCommandPool vkd.ResetCommandPool +#define vkAllocateCommandBuffers vkd.AllocateCommandBuffers +#define vkFreeCommandBuffers vkd.FreeCommandBuffers +#define vkBeginCommandBuffer vkd.BeginCommandBuffer +#define vkEndCommandBuffer vkd.EndCommandBuffer +#define vkResetCommandBuffer vkd.ResetCommandBuffer +#define vkCmdBeginRenderPass vkd.CmdBeginRenderPass +#define vkCmdEndRenderPass vkd.CmdEndRenderPass +#define vkCmdBindPipeline vkd.CmdBindPipeline +#define vkCmdBindDescriptorSets vkd.CmdBindDescriptorSets +#define vkCmdBindVertexBuffers vkd.CmdBindVertexBuffers +#define vkCmdPushConstants vkd.CmdPushConstants +#define vkCmdSetViewport vkd.CmdSetViewport +#define vkCmdSetScissor vkd.CmdSetScissor +#define vkCmdDraw vkd.CmdDraw +#define vkCmdPipelineBarrier vkd.CmdPipelineBarrier +#define vkCmdCopyBufferToImage vkd.CmdCopyBufferToImage +#define vkCmdBlitImage vkd.CmdBlitImage + +#define vkQueueSubmit vkd.QueueSubmit +#define vkQueueWaitIdle vkd.QueueWaitIdle +#define vkQueuePresentKHR vkd.QueuePresentKHR + +#define vkCreateSwapchainKHR vkd.CreateSwapchainKHR +#define vkDestroySwapchainKHR vkd.DestroySwapchainKHR +#define vkGetSwapchainImagesKHR vkd.GetSwapchainImagesKHR +#define vkAcquireNextImageKHR vkd.AcquireNextImageKHR diff --git a/app/src/main/cpp/winlator/vk/vk_driver.h b/app/src/main/cpp/winlator/vk/vk_driver.h new file mode 100644 index 000000000..2d78cf43a --- /dev/null +++ b/app/src/main/cpp/winlator/vk/vk_driver.h @@ -0,0 +1,19 @@ +// "System" / NULL driverName -> /system/lib64/libvulkan.so. +// Any other name -> adrenotools_open_libvulkan against the user-installed driver, +// falling back to the system loader if anything goes wrong. +// Caller owns the returned handle and must dlclose it. + +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void *winlator_open_vulkan(JNIEnv *env, jobject context, const char *driver_name); +void *winlator_open_system_vulkan(void); + +#ifdef __cplusplus +} +#endif diff --git a/app/src/main/cpp/winlator/vk/vk_image.c b/app/src/main/cpp/winlator/vk/vk_image.c new file mode 100644 index 000000000..83d2d171a --- /dev/null +++ b/app/src/main/cpp/winlator/vk/vk_image.c @@ -0,0 +1,1376 @@ +// VkTexture allocation, upload, AHB import. +// +// Two creation paths: +// 1. CPU-uploaded: caller hands us BGRA pixel data; we allocate VkImage in DEVICE_LOCAL memory, +// stage the upload through a host-visible buffer, and transition to SHADER_READ_OPTIMAL. +// 2. AHardwareBuffer import: caller hands us an AHB; we allocate dedicated memory backed by the +// AHB (no copy) and bind it to a VkImage. For non-RGB formats (DRI3 vendor formats), we use +// a Ycbcr conversion so the sampler can read them. +// +// Texture lifetimes: +// - Created/updated synchronously on caller's thread (Java/render). +// - Submits go through vkQueueSubmit which is serialized via VkRenderer::queue_mutex. +// - Destruction is deferred via the graveyard so in-flight frames don't see freed handles. + +#include "vk_state.h" +#include +#include + +#define HAL_PIXEL_FORMAT_BGRA_8888 5 + +uint32_t vkr_find_memory_type(VkRenderer* r, uint32_t type_bits, VkMemoryPropertyFlags props) { + for (uint32_t i = 0; i < r->mem_props.memoryTypeCount; i++) { + if ((type_bits & (1u << i)) + && (r->mem_props.memoryTypes[i].propertyFlags & props) == props) { + return i; + } + } + return UINT32_MAX; +} + +void vkr_image_barrier(VkCommandBuffer cmd, VkImage image, VkImageLayout from, VkImageLayout to, + VkPipelineStageFlags src_stage, VkPipelineStageFlags dst_stage, + VkAccessFlags src_access, VkAccessFlags dst_access) { + VkImageMemoryBarrier b = {0}; + b.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + b.oldLayout = from; + b.newLayout = to; + b.srcAccessMask = src_access; + b.dstAccessMask = dst_access; + b.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + b.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + b.image = image; + b.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + b.subresourceRange.baseMipLevel = 0; + b.subresourceRange.levelCount = 1; + b.subresourceRange.baseArrayLayer = 0; + b.subresourceRange.layerCount = 1; + vkCmdPipelineBarrier(cmd, src_stage, dst_stage, 0, 0, NULL, 0, NULL, 1, &b); +} + +// ============================================================ +// Staging pool — async upload infrastructure +// ============================================================ +// +// Each slot owns a VkBuffer, persistently-mapped HOST_VISIBLE memory, a VkCommandPool with +// one VkCommandBuffer, and a VkFence. Round-robin acquisition under a tiny mutex; per-slot +// mutex provides exclusive ownership for the lifetime of an upload (acquire→submit→release). +// +// On a single graphics queue, the upload's terminal pipeline barrier (TRANSFER_WRITE → +// SHADER_READ, dstStage=FRAGMENT_SHADER) extends into all subsequent submits per Vulkan +// spec — so the renderer needs no extra synchronization to safely sample a freshly-updated +// texture as long as the upload was submitted before the render. + +bool vkr_staging_pool_init(VkRenderer* r) { + if (r->staging_pool.initialized) return true; + pthread_mutex_init(&r->staging_pool.mutex, NULL); + r->staging_pool.mutex_init = true; + r->staging_pool.next = 0; + r->staging_pool.valid_slots = 0; + + VkFenceCreateInfo fi = {VK_STRUCTURE_TYPE_FENCE_CREATE_INFO}; + fi.flags = VK_FENCE_CREATE_SIGNALED_BIT; // first acquire of each slot finds the fence ready + + for (uint32_t i = 0; i < VK_STAGING_POOL_SIZE; i++) { + VkStagingSlot* s = &r->staging_pool.slots[i]; + pthread_mutex_init(&s->mutex, NULL); + r->staging_pool.valid_slots = i + 1; // mutex is now valid; destroy must clean it up + + VkCommandPoolCreateInfo cpci = {VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO}; + cpci.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT; + cpci.queueFamilyIndex = r->graphics_queue_family; + if (vkCreateCommandPool(r->device, &cpci, NULL, &s->cmd_pool) != VK_SUCCESS) { + VK_LOGE("staging pool: vkCreateCommandPool slot %u failed", i); + return false; + } + + VkCommandBufferAllocateInfo cbai = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO}; + cbai.commandPool = s->cmd_pool; + cbai.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + cbai.commandBufferCount = 1; + if (vkAllocateCommandBuffers(r->device, &cbai, &s->cmd) != VK_SUCCESS) { + VK_LOGE("staging pool: vkAllocateCommandBuffers slot %u failed", i); + return false; + } + + if (vkCreateFence(r->device, &fi, NULL, &s->fence) != VK_SUCCESS) { + VK_LOGE("staging pool: vkCreateFence slot %u failed", i); + return false; + } + // buffer/memory allocated lazily on first use, sized to the actual upload. + } + r->staging_pool.initialized = true; + return true; +} + +void vkr_staging_pool_destroy(VkRenderer* r) { + // Tolerates partially-initialized pools — only iterate the slots whose mutexes were + // successfully initialized. + for (uint32_t i = 0; i < r->staging_pool.valid_slots; i++) { + VkStagingSlot* s = &r->staging_pool.slots[i]; + if (s->fence) { + // Drain any pending submission so the buffer/memory are safe to free. + vkWaitForFences(r->device, 1, &s->fence, VK_TRUE, UINT64_MAX); + vkDestroyFence(r->device, s->fence, NULL); + } + if (s->mapped && s->memory) vkUnmapMemory(r->device, s->memory); + if (s->buffer) vkDestroyBuffer(r->device, s->buffer, NULL); + if (s->memory) vkFreeMemory(r->device, s->memory, NULL); + if (s->cmd_pool) vkDestroyCommandPool(r->device, s->cmd_pool, NULL); + pthread_mutex_destroy(&s->mutex); + memset(s, 0, sizeof(*s)); + } + if (r->staging_pool.mutex_init) { + pthread_mutex_destroy(&r->staging_pool.mutex); + } + memset(&r->staging_pool, 0, sizeof(r->staging_pool)); +} + +// Re-allocate a slot's staging buffer to at least `needed` bytes. Caller must own the slot. +static bool grow_staging_slot(VkRenderer* r, VkStagingSlot* s, VkDeviceSize needed) { + // Round up to 64 KiB so consecutive size bumps don't trigger reallocs. + VkDeviceSize new_size = (needed + 65535ull) & ~(VkDeviceSize)65535ull; + + if (s->mapped && s->memory) { vkUnmapMemory(r->device, s->memory); s->mapped = NULL; } + if (s->buffer) { vkDestroyBuffer(r->device, s->buffer, NULL); s->buffer = VK_NULL_HANDLE; } + if (s->memory) { vkFreeMemory(r->device, s->memory, NULL); s->memory = VK_NULL_HANDLE; } + // Reset size now so a later allocation failure leaves the slot in a state where the next + // acquire will retry grow_staging_slot rather than skip it and hand back a NULL buffer. + s->size = 0; + + VkBufferCreateInfo bi = {VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO}; + bi.size = new_size; + bi.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + bi.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + if (vkCreateBuffer(r->device, &bi, NULL, &s->buffer) != VK_SUCCESS) return false; + + VkMemoryRequirements mr; + vkGetBufferMemoryRequirements(r->device, s->buffer, &mr); + + VkMemoryAllocateInfo ai = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO}; + ai.allocationSize = mr.size; + // Require HOST_VISIBLE | HOST_COHERENT (typically write-combined on Adreno). Skipping + // HOST_CACHED avoids polluting CPU caches with write-once-then-GPU-read staging, which + // hurts throughput by 5-20% on Adreno. We do not fall back to non-coherent memory: + // vkr_texture_update submits without vkFlushMappedMemoryRanges, so non-coherent staging + // would render undefined data. Vulkan spec §11.6 mandates that every device expose at + // least one HOST_VISIBLE | HOST_COHERENT memory type, so this lookup cannot legally fail. + ai.memoryTypeIndex = vkr_find_memory_type(r, mr.memoryTypeBits, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + if (ai.memoryTypeIndex == UINT32_MAX) { + vkDestroyBuffer(r->device, s->buffer, NULL); s->buffer = VK_NULL_HANDLE; + return false; + } + if (vkAllocateMemory(r->device, &ai, NULL, &s->memory) != VK_SUCCESS) { + vkDestroyBuffer(r->device, s->buffer, NULL); s->buffer = VK_NULL_HANDLE; + return false; + } + vkBindBufferMemory(r->device, s->buffer, s->memory, 0); + if (vkMapMemory(r->device, s->memory, 0, VK_WHOLE_SIZE, 0, &s->mapped) != VK_SUCCESS) { + vkFreeMemory(r->device, s->memory, NULL); s->memory = VK_NULL_HANDLE; + vkDestroyBuffer(r->device, s->buffer, NULL); s->buffer = VK_NULL_HANDLE; + return false; + } + s->size = new_size; + return true; +} + +VkStagingSlot* vkr_staging_pool_acquire(VkRenderer* r, VkDeviceSize needed) { + if (!r->staging_pool.initialized) return NULL; + + pthread_mutex_lock(&r->staging_pool.mutex); + uint32_t idx = (uint32_t)(r->staging_pool.next++ % VK_STAGING_POOL_SIZE); + pthread_mutex_unlock(&r->staging_pool.mutex); + + VkStagingSlot* s = &r->staging_pool.slots[idx]; + + // Per-slot lock guards the slot's resources (buffer/cmd/fence) until release. Round-robin + // means contention only happens once VK_STAGING_POOL_SIZE acquires have wrapped — i.e. + // when the producer is consistently faster than the GPU can drain uploads. + pthread_mutex_lock(&s->mutex); + + // Wait for the slot's previous submission to retire. With pool_size=8 this almost never + // blocks because the fence signaled long ago. The fence is left signaled here on purpose + // — it gets reset right before vkQueueSubmit, so any no-submit failure path between here + // and submit leaves the fence safely signaled and the slot reusable. + vkWaitForFences(r->device, 1, &s->fence, VK_TRUE, UINT64_MAX); + vkResetCommandPool(r->device, s->cmd_pool, 0); + + if (s->size < needed && !grow_staging_slot(r, s, needed)) { + pthread_mutex_unlock(&s->mutex); + return NULL; + } + return s; +} + +void vkr_staging_pool_release(VkStagingSlot* slot) { + if (!slot) return; + pthread_mutex_unlock(&slot->mutex); +} + +void vkr_run_one_shot_cmd(VkRenderer* r, void (*fn)(VkCommandBuffer, void*), void* user) { + VkCommandBufferAllocateInfo ai = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO}; + ai.commandPool = r->cmd_pool; + ai.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + ai.commandBufferCount = 1; + + VkCommandBuffer cmd; + VK_CHECK(vkAllocateCommandBuffers(r->device, &ai, &cmd)); + + VkCommandBufferBeginInfo bi = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO}; + bi.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + VK_CHECK(vkBeginCommandBuffer(cmd, &bi)); + + fn(cmd, user); + + VK_CHECK(vkEndCommandBuffer(cmd)); + + VkSubmitInfo si = {VK_STRUCTURE_TYPE_SUBMIT_INFO}; + si.commandBufferCount = 1; + si.pCommandBuffers = &cmd; + + VkFenceCreateInfo fi = {VK_STRUCTURE_TYPE_FENCE_CREATE_INFO}; + VkFence fence; + VK_CHECK(vkCreateFence(r->device, &fi, NULL, &fence)); + + pthread_mutex_lock(&r->queue_mutex); + VK_CHECK(vkQueueSubmit(r->graphics_queue, 1, &si, fence)); + pthread_mutex_unlock(&r->queue_mutex); + + VK_CHECK(vkWaitForFences(r->device, 1, &fence, VK_TRUE, UINT64_MAX)); + + vkDestroyFence(r->device, fence, NULL); + vkFreeCommandBuffers(r->device, r->cmd_pool, 1, &cmd); +} + +// Forward declarations — defined in vk_renderer.c. +VkDescriptorSet vkr_alloc_descriptor_set(VkRenderer* r); +void vkr_free_descriptor_set(VkRenderer* r, VkDescriptorSet set); + +// Image sub-allocator — implemented lower in this file. +static bool vkr_suballoc_image(VkRenderer* r, VkImage image, VkSuballoc* out); +static void vkr_suballoc_free(VkRenderer* r, VkSuballoc* a); + +// Copy `bytes` (multiple of 4) from src to dst, optionally swapping B and R per pixel. +static void copy_pixels_maybe_swizzle(uint8_t* dst, const uint8_t* src, size_t bytes, + bool swizzle_bgra_rgba) { + if (!swizzle_bgra_rgba) { + memcpy(dst, src, bytes); + return; + } + size_t pixels = bytes >> 2; + for (size_t i = 0; i < pixels; i++) { + uint8_t b = src[i*4 + 0]; + uint8_t g = src[i*4 + 1]; + uint8_t rr = src[i*4 + 2]; + uint8_t a = src[i*4 + 3]; + dst[i*4 + 0] = rr; + dst[i*4 + 1] = g; + dst[i*4 + 2] = b; + dst[i*4 + 3] = a; + } +} + +bool vkr_create_sampler(VkRenderer* r, VkSamplerYcbcrConversion ycbcr, VkSampler* out) { + VkSamplerYcbcrConversionInfo yi = {VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO}; + yi.conversion = ycbcr; + + VkSamplerCreateInfo si = {VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO}; + if (ycbcr != VK_NULL_HANDLE) si.pNext = &yi; + si.magFilter = VK_FILTER_LINEAR; + si.minFilter = VK_FILTER_LINEAR; + si.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + si.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + si.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + si.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK; + si.unnormalizedCoordinates = VK_FALSE; + si.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; + + return vkCreateSampler(r->device, &si, NULL, out) == VK_SUCCESS; +} + +bool vkr_submit_async_transition(VkRenderer* r, VkImage image, + VkImageLayout from, VkImageLayout to, + VkPipelineStageFlags src_stage, VkPipelineStageFlags dst_stage, + VkAccessFlags src_access, VkAccessFlags dst_access) { + // Reuse the staging pool's per-slot command pool/buffer/fence for this transition. We + // pass needed=0 so the slot's staging buffer isn't grown (we only use the cmd buffer). + VkStagingSlot* slot = vkr_staging_pool_acquire(r, 0); + if (!slot) { + VK_LOGE("vkr_submit_async_transition: staging slot acquire failed"); + return false; + } + + VkCommandBufferBeginInfo cbi = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO}; + cbi.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + if (vkBeginCommandBuffer(slot->cmd, &cbi) != VK_SUCCESS) { + vkr_staging_pool_release(slot); + return false; + } + vkr_image_barrier(slot->cmd, image, from, to, src_stage, dst_stage, src_access, dst_access); + if (vkEndCommandBuffer(slot->cmd) != VK_SUCCESS) { + vkr_staging_pool_release(slot); + return false; + } + + VkSubmitInfo si = {VK_STRUCTURE_TYPE_SUBMIT_INFO}; + si.commandBufferCount = 1; + si.pCommandBuffers = &slot->cmd; + + vkResetFences(r->device, 1, &slot->fence); + + pthread_mutex_lock(&r->queue_mutex); + VkResult sr = vkQueueSubmit(r->graphics_queue, 1, &si, slot->fence); + pthread_mutex_unlock(&r->queue_mutex); + if (sr != VK_SUCCESS) { + VK_LOGE("vkr_submit_async_transition: vkQueueSubmit -> %d", sr); + // Restore a signaled fence so the slot is reusable. (Same recovery path as + // vkr_texture_update.) + vkDestroyFence(r->device, slot->fence, NULL); + VkFenceCreateInfo rfi = {VK_STRUCTURE_TYPE_FENCE_CREATE_INFO}; + rfi.flags = VK_FENCE_CREATE_SIGNALED_BIT; + vkCreateFence(r->device, &rfi, NULL, &slot->fence); + vkr_staging_pool_release(slot); + return false; + } + vkr_staging_pool_release(slot); + return true; +} + +static void write_descriptor_set(VkRenderer* r, VkDescriptorSet set, VkImageView view, VkSampler sampler) { + VkDescriptorImageInfo ii = {0}; + ii.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + ii.imageView = view; + ii.sampler = sampler; + + VkWriteDescriptorSet w = {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET}; + w.dstSet = set; + w.dstBinding = 0; + w.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + w.descriptorCount = 1; + w.pImageInfo = ⅈ + vkUpdateDescriptorSets(r->device, 1, &w, 0, NULL); +} + +static VkSampler active_shared_sampler(VkRenderer* r) { + switch (r->scale_filter) { + case 1: return r->shared_sampler_nearest; + case 3: return r->ext_filter_cubic ? r->shared_sampler_cubic : r->shared_sampler; + default: return r->shared_sampler; + } +} + +// Caller must hold render_mutex with in-flight frames drained. +void vkr_retarget_shared_sampler(VkRenderer* r) { + VkSampler s = active_shared_sampler(r); + if (s == VK_NULL_HANDLE) return; + pthread_mutex_lock(&r->texture_mutex); + for (uint32_t i = 0; i < r->live_texture_count; i++) { + VkTexture* t = r->live_textures[i]; + if (!t || t->descriptor_set == VK_NULL_HANDLE || t->view == VK_NULL_HANDLE) continue; + if (t->ycbcr != VK_NULL_HANDLE) continue; + write_descriptor_set(r, t->descriptor_set, t->view, s); + } + pthread_mutex_unlock(&r->texture_mutex); +} + +static void destroy_texture_resources(VkRenderer* r, VkTexture* tex) { + if (!tex) return; + if (tex->descriptor_set != VK_NULL_HANDLE) { + vkr_free_descriptor_set(r, tex->descriptor_set); + tex->descriptor_set = VK_NULL_HANDLE; + } + if (tex->sampler != VK_NULL_HANDLE) vkDestroySampler(r->device, tex->sampler, NULL); + if (tex->view != VK_NULL_HANDLE) vkDestroyImageView(r->device, tex->view, NULL); + if (tex->ycbcr != VK_NULL_HANDLE && r->fnDestroyYcbcr) r->fnDestroyYcbcr(r->device, tex->ycbcr, NULL); + if (tex->image != VK_NULL_HANDLE) vkDestroyImage(r->device, tex->image, NULL); + // Free backing after the image. Sub-allocated -> return span to pool; else free own memory. + if (tex->suballocated) vkr_suballoc_free(r, &tex->suballoc); + else if (tex->memory != VK_NULL_HANDLE) vkFreeMemory(r->device, tex->memory, NULL); + if (tex->ahb != NULL) AHardwareBuffer_release(tex->ahb); + free(tex); +} + +static bool track_live_texture(VkRenderer* r, VkTexture* tex) { + if (!tex) return false; + pthread_mutex_lock(&r->texture_mutex); + if (r->live_texture_count >= r->live_texture_capacity) { + uint32_t new_cap = r->live_texture_capacity ? r->live_texture_capacity * 2 : 64; + VkTexture** next = realloc(r->live_textures, new_cap * sizeof(VkTexture*)); + if (!next) { + pthread_mutex_unlock(&r->texture_mutex); + return false; + } + r->live_textures = next; + r->live_texture_capacity = new_cap; + } + r->live_textures[r->live_texture_count++] = tex; + pthread_mutex_unlock(&r->texture_mutex); + return true; +} + +static void untrack_live_texture(VkRenderer* r, VkTexture* tex) { + if (!tex) return; + pthread_mutex_lock(&r->texture_mutex); + for (uint32_t i = 0; i < r->live_texture_count; i++) { + if (r->live_textures[i] == tex) { + r->live_textures[i] = r->live_textures[--r->live_texture_count]; + r->live_textures[r->live_texture_count] = NULL; + break; + } + } + pthread_mutex_unlock(&r->texture_mutex); +} + +static VkTexture* pop_live_texture(VkRenderer* r) { + pthread_mutex_lock(&r->texture_mutex); + VkTexture* tex = NULL; + if (r->live_texture_count > 0) { + tex = r->live_textures[--r->live_texture_count]; + r->live_textures[r->live_texture_count] = NULL; + } + pthread_mutex_unlock(&r->texture_mutex); + return tex; +} + +// ---------------------------------------------------------------------- +// Image sub-allocator +// ---------------------------------------------------------------------- +// +// First-fit over a list of large DEVICE_LOCAL blocks, all under image_suballoc.mutex (alloc on +// producer threads, free on the render thread). Region nodes are malloc'd only on new-block +// creation or a non-coalescing free, so steady-state pixmap churn doesn't touch the C heap. + +void vkr_suballoc_init(VkRenderer* r) { + VkImageSuballocator* sa = &r->image_suballoc; + sa->blocks = NULL; + sa->block_size = VK_SUBALLOC_BLOCK_SIZE; + pthread_mutex_init(&sa->mutex, NULL); + sa->mutex_init = true; +} + +static VkDeviceSize suballoc_align_up(VkDeviceSize v, VkDeviceSize a) { + return (v + a - 1) & ~(a - 1); +} + +// Carve [reg->offset .. bind+size) out of free region `reg` (`prev` = its free-list +// predecessor, or NULL). The leading alignment pad folds into the span so free recovers it +// verbatim. Caller verified the region fits. +static void suballoc_carve(VkMemBlock* block, VkMemRegion* prev, VkMemRegion* reg, + VkDeviceSize bind, VkDeviceSize size, VkSuballoc* out) { + VkDeviceSize span_offset = reg->offset; + VkDeviceSize span_end = bind + size; + VkDeviceSize reg_end = reg->offset + reg->size; + + if (span_end < reg_end) { + reg->offset = span_end; // shrink region to the trailing remainder + reg->size = reg_end - span_end; + } else { + if (prev) prev->next = reg->next; // region fully consumed — unlink + free + else block->free_list = reg->next; + free(reg); + } + + out->block = block; + out->memory = block->memory; + out->bind_offset = bind; + out->span_offset = span_offset; + out->span_size = span_end - span_offset; +} + +// Reserve a span sized/aligned for `image` into `out`. Does NOT bind — caller issues the one +// vkBindImageMemory so a bind failure never forces an illegal rebind. False (nothing reserved) +// if no DEVICE_LOCAL type fits or a new block can't be allocated. +static bool vkr_suballoc_image(VkRenderer* r, VkImage image, VkSuballoc* out) { + VkMemoryRequirements mr; + vkGetImageMemoryRequirements(r->device, image, &mr); + + uint32_t type_index = vkr_find_memory_type(r, mr.memoryTypeBits, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + if (type_index == UINT32_MAX) return false; + + VkDeviceSize align = mr.alignment ? mr.alignment : 1; + + VkImageSuballocator* sa = &r->image_suballoc; + pthread_mutex_lock(&sa->mutex); + + // First fit across existing blocks of the matching memory type. + for (VkMemBlock* b = sa->blocks; b; b = b->next) { + if (b->memory_type_index != type_index) continue; + VkMemRegion* prev = NULL; + for (VkMemRegion* reg = b->free_list; reg; prev = reg, reg = reg->next) { + VkDeviceSize bind = suballoc_align_up(reg->offset, align); + if (bind + mr.size <= reg->offset + reg->size) { + suballoc_carve(b, prev, reg, bind, mr.size, out); + pthread_mutex_unlock(&sa->mutex); + return true; + } + } + } + + // No room anywhere — allocate a fresh block big enough for at least this image. + VkDeviceSize block_size = sa->block_size; + if (block_size < mr.size) block_size = mr.size; + block_size = suballoc_align_up(block_size, align); + + VkMemoryAllocateInfo ai = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO}; + ai.allocationSize = block_size; + ai.memoryTypeIndex = type_index; + + VkDeviceMemory mem = VK_NULL_HANDLE; + if (vkAllocateMemory(r->device, &ai, NULL, &mem) != VK_SUCCESS) { + pthread_mutex_unlock(&sa->mutex); + return false; + } + + VkMemBlock* block = malloc(sizeof(VkMemBlock)); + VkMemRegion* region = malloc(sizeof(VkMemRegion)); + if (!block || !region) { + free(block); + free(region); + vkFreeMemory(r->device, mem, NULL); + pthread_mutex_unlock(&sa->mutex); + return false; + } + region->offset = 0; + region->size = block_size; + region->next = NULL; + block->memory = mem; + block->size = block_size; + block->memory_type_index = type_index; + block->free_list = region; + block->next = sa->blocks; + sa->blocks = block; + + VkDeviceSize bind = suballoc_align_up(region->offset, align); // == 0 at block start + suballoc_carve(block, NULL, region, bind, mr.size, out); + pthread_mutex_unlock(&sa->mutex); + return true; +} + +static void vkr_suballoc_free(VkRenderer* r, VkSuballoc* a) { + if (!a || !a->block) return; + VkImageSuballocator* sa = &r->image_suballoc; + pthread_mutex_lock(&sa->mutex); + + VkMemBlock* b = a->block; + VkDeviceSize off = a->span_offset; + VkDeviceSize sz = a->span_size; + + // Sorted insertion point in the free list. + VkMemRegion* prev = NULL; + VkMemRegion* cur = b->free_list; + while (cur && cur->offset < off) { prev = cur; cur = cur->next; } + + bool merged_prev = prev && prev->offset + prev->size == off; + bool merged_next = cur && off + sz == cur->offset; + + if (merged_prev && merged_next) { + prev->size += sz + cur->size; + prev->next = cur->next; + free(cur); + } else if (merged_prev) { + prev->size += sz; + } else if (merged_next) { + cur->offset = off; + cur->size += sz; + } else { + VkMemRegion* node = malloc(sizeof(VkMemRegion)); + if (node) { + node->offset = off; + node->size = sz; + node->next = cur; + if (prev) prev->next = node; + else b->free_list = node; + } else { + // Node alloc failed (near-impossible): leak the span; the block is reclaimed at + // teardown anyway. + VK_LOGE("suballoc free: region node alloc failed; leaking %llu bytes", + (unsigned long long)sz); + } + } + + // Return a fully-drained block so churn doesn't pin memory forever. + if (b->free_list && b->free_list->next == NULL + && b->free_list->offset == 0 && b->free_list->size == b->size) { + VkMemBlock* pb = NULL; + for (VkMemBlock* it = sa->blocks; it; pb = it, it = it->next) { + if (it == b) { + if (pb) pb->next = it->next; + else sa->blocks = it->next; + break; + } + } + free(b->free_list); + vkFreeMemory(r->device, b->memory, NULL); + free(b); + } + + pthread_mutex_unlock(&sa->mutex); + memset(a, 0, sizeof(*a)); +} + +void vkr_suballoc_destroy(VkRenderer* r) { + VkImageSuballocator* sa = &r->image_suballoc; + VkMemBlock* b = sa->blocks; + while (b) { + VkMemBlock* next = b->next; + VkMemRegion* reg = b->free_list; + while (reg) { VkMemRegion* rn = reg->next; free(reg); reg = rn; } + if (b->memory) vkFreeMemory(r->device, b->memory, NULL); + free(b); + b = next; + } + sa->blocks = NULL; + if (sa->mutex_init) { + pthread_mutex_destroy(&sa->mutex); + sa->mutex_init = false; + } +} + +// ---------------------------------------------------------------------- +// CPU-uploaded path +// ---------------------------------------------------------------------- + +typedef struct UploadCtx { + VkBuffer staging; + VkImage dst; + VkDeviceSize offset; + uint32_t x, y, w, h; + VkImageLayout old_layout; + bool to_shader_read; +} UploadCtx; + +static void upload_cmds(VkCommandBuffer cmd, void* user) { + UploadCtx* u = (UploadCtx*)user; + + VkPipelineStageFlags src_stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + VkAccessFlags src_access = 0; + if (u->old_layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { + src_stage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + src_access = VK_ACCESS_SHADER_READ_BIT; + } else if (u->old_layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { + src_stage = VK_PIPELINE_STAGE_TRANSFER_BIT; + src_access = VK_ACCESS_TRANSFER_WRITE_BIT; + } + + vkr_image_barrier(cmd, u->dst, + u->old_layout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + src_stage, VK_PIPELINE_STAGE_TRANSFER_BIT, + src_access, VK_ACCESS_TRANSFER_WRITE_BIT); + + VkBufferImageCopy bic = {0}; + bic.bufferOffset = u->offset; + bic.bufferRowLength = 0; + bic.bufferImageHeight = 0; + bic.imageOffset.x = (int32_t)u->x; + bic.imageOffset.y = (int32_t)u->y; + bic.imageOffset.z = 0; + bic.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + bic.imageSubresource.layerCount = 1; + bic.imageExtent.width = u->w; + bic.imageExtent.height = u->h; + bic.imageExtent.depth = 1; + vkCmdCopyBufferToImage(cmd, u->staging, u->dst, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &bic); + + if (u->to_shader_read) { + vkr_image_barrier(cmd, u->dst, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT); + } +} + +static bool create_image_basic(VkRenderer* r, uint32_t w, uint32_t h, VkFormat fmt, + VkImageUsageFlags usage, VkTexture* t) { + VkImageCreateInfo ic = {VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO}; + ic.imageType = VK_IMAGE_TYPE_2D; + ic.format = fmt; + ic.extent.width = w; + ic.extent.height = h; + ic.extent.depth = 1; + ic.mipLevels = 1; + ic.arrayLayers = 1; + ic.samples = VK_SAMPLE_COUNT_1_BIT; + ic.tiling = VK_IMAGE_TILING_OPTIMAL; + ic.usage = usage; + ic.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + ic.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + + if (vkCreateImage(r->device, &ic, NULL, &t->image) != VK_SUCCESS) return false; + + // Preferred path: pooled span (no per-texture vkAllocateMemory). Bind once here. + VkSuballoc sub = {0}; + if (vkr_suballoc_image(r, t->image, &sub)) { + if (vkBindImageMemory(r->device, t->image, sub.memory, sub.bind_offset) == VK_SUCCESS) { + t->suballoc = sub; + t->suballocated = true; + return true; + } + // Bind attempted -> image can't be rebound via the dedicated path; fail (OOM-grade, + // effectively never happens). + vkr_suballoc_free(r, &sub); + vkDestroyImage(r->device, t->image, NULL); + t->image = VK_NULL_HANDLE; + return false; + } + + // Fallback: dedicated allocation (pool OOM / no DEVICE_LOCAL type). No bind attempted yet. + VkMemoryRequirements mr; + vkGetImageMemoryRequirements(r->device, t->image, &mr); + + VkMemoryAllocateInfo ai = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO}; + ai.allocationSize = mr.size; + ai.memoryTypeIndex = vkr_find_memory_type(r, mr.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + if (ai.memoryTypeIndex == UINT32_MAX) { + vkDestroyImage(r->device, t->image, NULL); + t->image = VK_NULL_HANDLE; + return false; + } + + if (vkAllocateMemory(r->device, &ai, NULL, &t->memory) != VK_SUCCESS) { + vkDestroyImage(r->device, t->image, NULL); + t->image = VK_NULL_HANDLE; + return false; + } + vkBindImageMemory(r->device, t->image, t->memory, 0); + return true; +} + +VkTexture* vkr_texture_create_uploaded(VkRenderer* r, uint32_t width, uint32_t height, + const void* data, size_t data_size, uint32_t stride_pixels) { + if (width == 0 || height == 0) return NULL; + + VkTexture* t = calloc(1, sizeof(VkTexture)); + if (!t) return NULL; + t->width = width; + t->height = height; + t->format = r->caps.upload_format; + + VkImageUsageFlags usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; + if (!create_image_basic(r, width, height, t->format, usage, t)) { + free(t); + return NULL; + } + + VkImageViewCreateInfo vi = {VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; + vi.image = t->image; + vi.viewType = VK_IMAGE_VIEW_TYPE_2D; + vi.format = t->format; + vi.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + vi.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + vi.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + vi.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + vi.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + vi.subresourceRange.levelCount = 1; + vi.subresourceRange.layerCount = 1; + if (vkCreateImageView(r->device, &vi, NULL, &t->view) != VK_SUCCESS) { + destroy_texture_resources(r, t); + return NULL; + } + + // CPU-uploaded textures all want the same sampler config, so use the renderer's shared + // sampler. tex->sampler stays VK_NULL_HANDLE; destroy_texture_resources skips it. + if (r->shared_sampler == VK_NULL_HANDLE) { + VK_LOGE("vkr_texture_create_uploaded: shared_sampler not initialized"); + destroy_texture_resources(r, t); + return NULL; + } + + t->descriptor_set = vkr_alloc_descriptor_set(r); + if (t->descriptor_set == VK_NULL_HANDLE) { + destroy_texture_resources(r, t); + return NULL; + } + write_descriptor_set(r, t->descriptor_set, t->view, active_shared_sampler(r)); + + if (data && data_size > 0) { + vkr_texture_update(r, t, width, height, data, data_size, stride_pixels, + 0, 0, width, height); + } else { + // No initial data — async transition to SHADER_READ so the texture is safe to sample + // as black. Doesn't block the caller; the barrier orders before the next render submit + // on the same queue per Vulkan spec. + if (!vkr_submit_async_transition(r, t->image, + VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + 0, VK_ACCESS_SHADER_READ_BIT)) { + VK_LOGW("vkr_texture_create_uploaded: async transition failed; texture may render undefined contents"); + } + } + t->layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + t->ready = true; + if (!track_live_texture(r, t)) { + destroy_texture_resources(r, t); + return NULL; + } + return t; +} + +bool vkr_texture_update(VkRenderer* r, VkTexture* tex, uint32_t width, uint32_t height, + const void* data, size_t data_size, uint32_t stride_pixels, + uint32_t dirty_x, uint32_t dirty_y, + uint32_t dirty_w, uint32_t dirty_h) { + if (!tex || tex->external || !data || data_size == 0) return false; + if (width != tex->width || height != tex->height) { + // Caller is expected to size-match. Reject mismatches to avoid silent corruption. + VK_LOGW("vkr_texture_update size mismatch (have %ux%u, got %ux%u)", + tex->width, tex->height, width, height); + return false; + } + + // BGRA8 = 4 bytes per pixel. Caller provides stride_pixels (per-row pixel count). + if (stride_pixels == 0) stride_pixels = width; + if (dirty_w == 0 || dirty_h == 0) { + dirty_x = 0; + dirty_y = 0; + dirty_w = width; + dirty_h = height; + } + if (dirty_x >= width || dirty_y >= height) return false; + if (dirty_x + dirty_w > width) dirty_w = width - dirty_x; + if (dirty_y + dirty_h > height) dirty_h = height - dirty_y; + if (dirty_w == 0 || dirty_h == 0) return false; + + size_t needed = (size_t)dirty_w * dirty_h * 4; + size_t src_pitch = (size_t)stride_pixels * 4; + size_t row = (size_t)dirty_w * 4; + size_t src_offset = ((size_t)dirty_y * stride_pixels + dirty_x) * 4; + size_t last_row = src_offset + (size_t)(dirty_h - 1) * src_pitch + row; + if (last_row > data_size) { + VK_LOGW("vkr_texture_update dirty rect exceeds source buffer"); + return false; + } + + VkStagingSlot* slot = vkr_staging_pool_acquire(r, needed); + if (!slot) { + VK_LOGE("vkr_texture_update: staging pool acquire failed"); + return false; + } + + bool swizzle = r->caps.upload_needs_bgra_swizzle; + if (dirty_x == 0 && dirty_y == 0 && dirty_w == width && dirty_h == height + && stride_pixels == width) { + copy_pixels_maybe_swizzle(slot->mapped, data, needed, swizzle); + } else { + const uint8_t* src = (const uint8_t*)data + src_offset; + uint8_t* dst = (uint8_t*)slot->mapped; + for (uint32_t y = 0; y < dirty_h; y++) { + copy_pixels_maybe_swizzle(dst + (size_t)y * row, + src + (size_t)y * src_pitch, row, swizzle); + } + } + + VkCommandBufferBeginInfo cbi = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO}; + cbi.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + if (vkBeginCommandBuffer(slot->cmd, &cbi) != VK_SUCCESS) { + vkr_staging_pool_release(slot); + return false; + } + + UploadCtx ctx = { + slot->buffer, tex->image, 0, + dirty_x, dirty_y, dirty_w, dirty_h, + tex->layout, true + }; + upload_cmds(slot->cmd, &ctx); + + if (vkEndCommandBuffer(slot->cmd) != VK_SUCCESS) { + vkr_staging_pool_release(slot); + return false; + } + + VkSubmitInfo si = {VK_STRUCTURE_TYPE_SUBMIT_INFO}; + si.commandBufferCount = 1; + si.pCommandBuffers = &slot->cmd; + + // Reset fence here, not in acquire — guarantees that the only path that leaves a fence + // unsignaled is one where vkQueueSubmit also runs to take ownership of it. + vkResetFences(r->device, 1, &slot->fence); + + pthread_mutex_lock(&r->queue_mutex); + VkResult sr = vkQueueSubmit(r->graphics_queue, 1, &si, slot->fence); + pthread_mutex_unlock(&r->queue_mutex); + if (sr != VK_SUCCESS) { + VK_LOGE("vkr_texture_update: vkQueueSubmit -> %d", sr); + // Submit failed but we already reset the fence, so it's unsignaled and would deadlock + // the next acquire. Replace with a signaled fence. (Submit failures usually mean + // device-lost; the renderer is going to need a restart anyway.) + vkDestroyFence(r->device, slot->fence, NULL); + VkFenceCreateInfo rfi = {VK_STRUCTURE_TYPE_FENCE_CREATE_INFO}; + rfi.flags = VK_FENCE_CREATE_SIGNALED_BIT; + vkCreateFence(r->device, &rfi, NULL, &slot->fence); + vkr_staging_pool_release(slot); + return false; + } + + // The barrier emitted by upload_cmds (TRANSFER_WRITE → SHADER_READ, dstStage= + // FRAGMENT_SHADER) extends into all subsequent submits on the same queue, so the next + // render submit will observe the writes without any additional renderer-side barrier. + tex->layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + vkr_staging_pool_release(slot); + return true; +} + +typedef struct PreparedBatchUpload { + VkTexture* texture; + const uint8_t* data; + size_t data_size; + size_t src_offset; + size_t src_pitch; + size_t row_bytes; + VkDeviceSize staging_offset; + VkDeviceSize byte_count; + uint32_t x, y, w, h; +} PreparedBatchUpload; + +static VkDeviceSize align4(VkDeviceSize v) { + return (v + 3ull) & ~(VkDeviceSize)3ull; +} + +static void batch_transition_to_transfer(VkCommandBuffer cmd, VkTexture* tex) { + VkPipelineStageFlags src_stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + VkAccessFlags src_access = 0; + if (tex->layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { + src_stage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + src_access = VK_ACCESS_SHADER_READ_BIT; + } else if (tex->layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { + src_stage = VK_PIPELINE_STAGE_TRANSFER_BIT; + src_access = VK_ACCESS_TRANSFER_WRITE_BIT; + } + + vkr_image_barrier(cmd, tex->image, + tex->layout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + src_stage, VK_PIPELINE_STAGE_TRANSFER_BIT, + src_access, VK_ACCESS_TRANSFER_WRITE_BIT); +} + +static void batch_transition_to_shader_read(VkCommandBuffer cmd, VkTexture* tex) { + vkr_image_barrier(cmd, tex->image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT); +} + +// Grow-only PreparedBatchUpload[] scratch. Render-thread-only, so unlocked; every element is +// fully overwritten before use, so no zeroing. +static PreparedBatchUpload* get_prepared_scratch(VkRenderer* r, uint32_t count) { + if (r->batch_prepared_cap < count) { + uint32_t new_cap = r->batch_prepared_cap ? r->batch_prepared_cap : 64; + while (new_cap < count) new_cap *= 2; + void* p = realloc(r->batch_prepared_scratch, (size_t)new_cap * sizeof(PreparedBatchUpload)); + if (!p) return NULL; + r->batch_prepared_scratch = p; + r->batch_prepared_cap = new_cap; + } + return (PreparedBatchUpload*)r->batch_prepared_scratch; +} + +bool vkr_texture_batch_update(VkRenderer* r, const VkTextureBatchUpload* uploads, + uint32_t upload_count) { + if (!r || !uploads || upload_count == 0) return false; + + PreparedBatchUpload* prepared = get_prepared_scratch(r, upload_count); + if (!prepared) return false; + + VkDeviceSize total = 0; + for (uint32_t i = 0; i < upload_count; i++) { + const VkTextureBatchUpload* in = &uploads[i]; + VkTexture* tex = in->texture; + if (!tex || tex->external || !in->data || in->data_size == 0 + || in->width != tex->width || in->height != tex->height) { + return false; + } + + uint32_t stride_pixels = in->stride_pixels ? in->stride_pixels : in->width; + uint32_t dirty_x = in->dirty_x; + uint32_t dirty_y = in->dirty_y; + uint32_t dirty_w = in->dirty_w; + uint32_t dirty_h = in->dirty_h; + if (dirty_w == 0 || dirty_h == 0) { + dirty_x = 0; + dirty_y = 0; + dirty_w = in->width; + dirty_h = in->height; + } + if (dirty_x >= in->width || dirty_y >= in->height) { + return false; + } + if (dirty_x + dirty_w > in->width) dirty_w = in->width - dirty_x; + if (dirty_y + dirty_h > in->height) dirty_h = in->height - dirty_y; + if (dirty_w == 0 || dirty_h == 0) { + return false; + } + + size_t src_pitch = (size_t)stride_pixels * 4; + size_t row = (size_t)dirty_w * 4; + size_t src_offset = ((size_t)dirty_y * stride_pixels + dirty_x) * 4; + size_t last_row = src_offset + (size_t)(dirty_h - 1) * src_pitch + row; + if (last_row > in->data_size) { + return false; + } + + total = align4(total); + prepared[i].texture = tex; + prepared[i].data = (const uint8_t*)in->data; + prepared[i].data_size = in->data_size; + prepared[i].src_offset = src_offset; + prepared[i].src_pitch = src_pitch; + prepared[i].row_bytes = row; + prepared[i].staging_offset = total; + prepared[i].byte_count = (VkDeviceSize)row * dirty_h; + prepared[i].x = dirty_x; + prepared[i].y = dirty_y; + prepared[i].w = dirty_w; + prepared[i].h = dirty_h; + total += prepared[i].byte_count; + } + + VkStagingSlot* slot = vkr_staging_pool_acquire(r, total); + if (!slot) { + VK_LOGE("vkr_texture_batch_update: staging pool acquire failed"); + return false; + } + + bool swizzle = r->caps.upload_needs_bgra_swizzle; + for (uint32_t i = 0; i < upload_count; i++) { + const PreparedBatchUpload* u = &prepared[i]; + const uint8_t* src = u->data + u->src_offset; + uint8_t* dst = (uint8_t*)slot->mapped + u->staging_offset; + if (u->row_bytes == u->src_pitch) { + copy_pixels_maybe_swizzle(dst, src, (size_t)u->byte_count, swizzle); + } else { + for (uint32_t y = 0; y < u->h; y++) { + copy_pixels_maybe_swizzle(dst + (size_t)y * u->row_bytes, + src + (size_t)y * u->src_pitch, + u->row_bytes, swizzle); + } + } + } + + VkCommandBufferBeginInfo cbi = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO}; + cbi.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + if (vkBeginCommandBuffer(slot->cmd, &cbi) != VK_SUCCESS) { + vkr_staging_pool_release(slot); + return false; + } + + VkTexture* current = NULL; + for (uint32_t i = 0; i < upload_count; i++) { + PreparedBatchUpload* u = &prepared[i]; + if (u->texture != current) { + if (current) batch_transition_to_shader_read(slot->cmd, current); + current = u->texture; + batch_transition_to_transfer(slot->cmd, current); + } + + VkBufferImageCopy bic = {0}; + bic.bufferOffset = u->staging_offset; + bic.imageOffset.x = (int32_t)u->x; + bic.imageOffset.y = (int32_t)u->y; + bic.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + bic.imageSubresource.layerCount = 1; + bic.imageExtent.width = u->w; + bic.imageExtent.height = u->h; + bic.imageExtent.depth = 1; + vkCmdCopyBufferToImage(slot->cmd, slot->buffer, current->image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &bic); + } + if (current) batch_transition_to_shader_read(slot->cmd, current); + + if (vkEndCommandBuffer(slot->cmd) != VK_SUCCESS) { + vkr_staging_pool_release(slot); + return false; + } + + VkSubmitInfo si = {VK_STRUCTURE_TYPE_SUBMIT_INFO}; + si.commandBufferCount = 1; + si.pCommandBuffers = &slot->cmd; + vkResetFences(r->device, 1, &slot->fence); + + pthread_mutex_lock(&r->queue_mutex); + VkResult sr = vkQueueSubmit(r->graphics_queue, 1, &si, slot->fence); + pthread_mutex_unlock(&r->queue_mutex); + if (sr != VK_SUCCESS) { + VK_LOGE("vkr_texture_batch_update: vkQueueSubmit -> %d", sr); + vkDestroyFence(r->device, slot->fence, NULL); + VkFenceCreateInfo rfi = {VK_STRUCTURE_TYPE_FENCE_CREATE_INFO}; + rfi.flags = VK_FENCE_CREATE_SIGNALED_BIT; + vkCreateFence(r->device, &rfi, NULL, &slot->fence); + vkr_staging_pool_release(slot); + return false; + } + + for (uint32_t i = 0; i < upload_count; i++) { + prepared[i].texture->layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + } + vkr_staging_pool_release(slot); + return true; +} + +// ---------------------------------------------------------------------- +// AHB import path (zero-copy) +// ---------------------------------------------------------------------- + +VkTexture* vkr_texture_import_ahb(VkRenderer* r, AHardwareBuffer* ahb, bool transfer_ownership) { + if (!ahb) { + VK_LOGW("AHB import skipped: null AHardwareBuffer"); + return NULL; + } + if (!r->ext_ahb || !r->fnGetAhbProps) { + VK_LOGW("AHB import skipped: ext_ahb=%d fnGetAhbProps=%d", + r->ext_ahb, r->fnGetAhbProps != NULL); + return NULL; + } + + VkAndroidHardwareBufferFormatPropertiesANDROID format_props = { + VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_FORMAT_PROPERTIES_ANDROID + }; + VkAndroidHardwareBufferPropertiesANDROID props = { + VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_PROPERTIES_ANDROID + }; + props.pNext = &format_props; + if (r->fnGetAhbProps(r->device, ahb, &props) != VK_SUCCESS) { + VK_LOGW("vkGetAndroidHardwareBufferPropertiesANDROID failed"); + return NULL; + } + + AHardwareBuffer_Desc desc = {0}; + AHardwareBuffer_describe(ahb, &desc); + VK_LOGI("AHB Vulkan import begin: %ux%u stride=%u format=%u usage=0x%llx allocation=%llu memoryBits=0x%x vkFormat=%d", + desc.width, desc.height, desc.stride, desc.format, + (unsigned long long)desc.usage, + (unsigned long long)props.allocationSize, + props.memoryTypeBits, + format_props.format); + + VkTexture* t = calloc(1, sizeof(VkTexture)); + if (!t) return NULL; + t->width = desc.width; + t->height = desc.height; + t->external = true; + t->ahb = transfer_ownership ? ahb : NULL; + if (transfer_ownership) AHardwareBuffer_acquire(ahb); + + // External-format AHB sampling requires a YCbCr conversion bound through an immutable + // sampler in the descriptor-set layout. This renderer uses one mutable combined + // image/sampler layout for all regular textures, so accepting external-format AHBs here + // would be Vulkan-invalid on strict drivers. Keep the import path to RGB formats until a + // separate immutable-sampler pipeline/layout path exists. + if (format_props.format == VK_FORMAT_UNDEFINED) { + VK_LOGW("AHB external-format import unsupported by current descriptor layout"); + if (t->ahb) AHardwareBuffer_release(t->ahb); + free(t); + return NULL; + } + + VkExternalMemoryImageCreateInfo emi = {VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO}; + emi.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID; + + VkImageCreateInfo ic = {VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO}; + ic.pNext = &emi; + ic.imageType = VK_IMAGE_TYPE_2D; + ic.format = format_props.format; // may be UNDEFINED for vendor formats + ic.extent.width = desc.width; + ic.extent.height = desc.height; + ic.extent.depth = 1; + ic.mipLevels = 1; + ic.arrayLayers = 1; + ic.samples = VK_SAMPLE_COUNT_1_BIT; + ic.tiling = VK_IMAGE_TILING_OPTIMAL; + ic.usage = VK_IMAGE_USAGE_SAMPLED_BIT; + ic.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + ic.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + + if (vkCreateImage(r->device, &ic, NULL, &t->image) != VK_SUCCESS) { + VK_LOGW("AHB vkCreateImage failed"); + if (t->ahb) AHardwareBuffer_release(t->ahb); + free(t); + return NULL; + } + t->format = format_props.format; + + // Dedicated allocation pulls memory from the AHB handle. + VkImportAndroidHardwareBufferInfoANDROID import = { + VK_STRUCTURE_TYPE_IMPORT_ANDROID_HARDWARE_BUFFER_INFO_ANDROID + }; + import.buffer = ahb; + + VkMemoryDedicatedAllocateInfo dedicated = {VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO}; + dedicated.image = t->image; + dedicated.pNext = &import; + + VkMemoryAllocateInfo mai = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO}; + mai.pNext = &dedicated; + mai.allocationSize = props.allocationSize; + mai.memoryTypeIndex = vkr_find_memory_type(r, props.memoryTypeBits, 0); + if (mai.memoryTypeIndex == UINT32_MAX) { + VK_LOGW("AHB no compatible memory type"); + vkDestroyImage(r->device, t->image, NULL); + if (t->ahb) AHardwareBuffer_release(t->ahb); + free(t); + return NULL; + } + + if (vkAllocateMemory(r->device, &mai, NULL, &t->memory) != VK_SUCCESS) { + VK_LOGW("AHB vkAllocateMemory failed"); + vkDestroyImage(r->device, t->image, NULL); + if (t->ahb) AHardwareBuffer_release(t->ahb); + free(t); + return NULL; + } + vkBindImageMemory(r->device, t->image, t->memory, 0); + + VkSamplerYcbcrConversionInfo yview = {VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO}; + yview.conversion = t->ycbcr; + + VkImageViewCreateInfo vi = {VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; + if (t->ycbcr != VK_NULL_HANDLE) vi.pNext = &yview; + vi.image = t->image; + vi.viewType = VK_IMAGE_VIEW_TYPE_2D; + vi.format = format_props.format; + // samplerYcbcrConversionComponents is only defined when a Ycbcr conversion is in use; + // some non-Adreno drivers populate non-identity swizzles for RGB AHBs. + if (t->ycbcr != VK_NULL_HANDLE) { + vi.components = format_props.samplerYcbcrConversionComponents; + } else { + // fixes devices that supports vulkan bgra8 format, but doesn't support bgra8 ahb images + bool swizzle_rb = format_props.format == VK_FORMAT_R8G8B8A8_UNORM + && r->caps.upload_format == VK_FORMAT_B8G8R8A8_UNORM; + vi.components.r = swizzle_rb ? VK_COMPONENT_SWIZZLE_B : VK_COMPONENT_SWIZZLE_IDENTITY; + vi.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + vi.components.b = swizzle_rb ? VK_COMPONENT_SWIZZLE_R : VK_COMPONENT_SWIZZLE_IDENTITY; + vi.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + } + + vi.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + vi.subresourceRange.levelCount = 1; + vi.subresourceRange.layerCount = 1; + if (vkCreateImageView(r->device, &vi, NULL, &t->view) != VK_SUCCESS) { + VK_LOGW("AHB vkCreateImageView failed"); + if (t->ycbcr && r->fnDestroyYcbcr) r->fnDestroyYcbcr(r->device, t->ycbcr, NULL); + vkDestroyImage(r->device, t->image, NULL); + vkFreeMemory(r->device, t->memory, NULL); + if (t->ahb) AHardwareBuffer_release(t->ahb); + free(t); + return NULL; + } + + // Ycbcr-bound samplers must be created per-texture (driver pairs them with the conversion). + // For plain RGB AHB imports we can reuse the renderer's shared sampler. + VkSampler sampler_for_descriptor; + if (t->ycbcr != VK_NULL_HANDLE) { + if (!vkr_create_sampler(r, t->ycbcr, &t->sampler)) { + VK_LOGW("AHB vkr_create_sampler failed"); + vkDestroyImageView(r->device, t->view, NULL); + if (r->fnDestroyYcbcr) r->fnDestroyYcbcr(r->device, t->ycbcr, NULL); + vkDestroyImage(r->device, t->image, NULL); + vkFreeMemory(r->device, t->memory, NULL); + if (t->ahb) AHardwareBuffer_release(t->ahb); + free(t); + return NULL; + } + sampler_for_descriptor = t->sampler; + } else if (r->shared_sampler != VK_NULL_HANDLE) { + sampler_for_descriptor = active_shared_sampler(r); + } else { + VK_LOGE("AHB import: shared_sampler not initialized"); + vkDestroyImageView(r->device, t->view, NULL); + vkDestroyImage(r->device, t->image, NULL); + vkFreeMemory(r->device, t->memory, NULL); + if (t->ahb) AHardwareBuffer_release(t->ahb); + free(t); + return NULL; + } + + t->descriptor_set = vkr_alloc_descriptor_set(r); + if (t->descriptor_set == VK_NULL_HANDLE) { + if (t->sampler) vkDestroySampler(r->device, t->sampler, NULL); + vkDestroyImageView(r->device, t->view, NULL); + if (t->ycbcr && r->fnDestroyYcbcr) r->fnDestroyYcbcr(r->device, t->ycbcr, NULL); + vkDestroyImage(r->device, t->image, NULL); + vkFreeMemory(r->device, t->memory, NULL); + if (t->ahb) AHardwareBuffer_release(t->ahb); + free(t); + return NULL; + } + write_descriptor_set(r, t->descriptor_set, t->view, sampler_for_descriptor); + + // Async transition to SHADER_READ. The barrier orders before all subsequent submits on + // the same queue per Vulkan spec, so the next render submit safely samples this image + // without an additional renderer-side wait. + if (!vkr_submit_async_transition(r, t->image, + VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + 0, VK_ACCESS_SHADER_READ_BIT)) { + VK_LOGW("AHB import: async transition failed; sampling may yield undefined contents"); + } + + t->layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + t->ready = true; + if (!track_live_texture(r, t)) { + destroy_texture_resources(r, t); + return NULL; + } + VK_LOGI("AHB Vulkan import ready: %ux%u vkFormat=%d memoryType=%u texture=%p", + t->width, t->height, t->format, mai.memoryTypeIndex, + (void*)t); + return t; +} + +// ---------------------------------------------------------------------- +// Destruction +// ---------------------------------------------------------------------- + +void vkr_texture_destroy(VkRenderer* r, VkTexture* tex) { + if (!tex) return; + untrack_live_texture(r, tex); + destroy_texture_resources(r, tex); +} + +void vkr_texture_destroy_all_live(VkRenderer* r) { + for (;;) { + VkTexture* tex = pop_live_texture(r); + if (!tex) break; + destroy_texture_resources(r, tex); + } +} + +void vkr_texture_schedule_destroy(VkRenderer* r, VkTexture* tex) { + if (!tex) return; + pthread_mutex_lock(&r->scene_mutex); + + if (tex->destroy_scheduled) { + pthread_mutex_unlock(&r->scene_mutex); + return; + } + tex->destroy_scheduled = true; + + // Defensive: drop any references in the live scene state. + for (uint32_t i = 0; i < r->scene.window_count; i++) { + if (r->scene.windows[i].texture == tex) { + r->scene.windows[i].texture = NULL; + } + } + if (r->scene.cursor_texture == tex) r->scene.cursor_texture = NULL; + + uint32_t retire_slot = (r->graveyard_index + VK_FRAMES_IN_FLIGHT) + % (VK_FRAMES_IN_FLIGHT + 1); + VkGraveSlot* slot = &r->graveyard[retire_slot]; + if (slot->count >= slot->capacity) { + uint32_t new_cap = slot->capacity ? slot->capacity * 2 : 16; + VkTexture** ng = realloc(slot->textures, new_cap * sizeof(VkTexture*)); + if (!ng) { + pthread_mutex_unlock(&r->scene_mutex); + // As a last resort, leak rather than crash. Better than UAF. + VK_LOGE("graveyard alloc failed; leaking texture %p", (void*)tex); + return; + } + slot->textures = ng; + slot->capacity = new_cap; + } + slot->textures[slot->count++] = tex; + + pthread_mutex_unlock(&r->scene_mutex); +} diff --git a/app/src/main/cpp/winlator/vk/vk_renderer.c b/app/src/main/cpp/winlator/vk/vk_renderer.c new file mode 100644 index 000000000..d065ed625 --- /dev/null +++ b/app/src/main/cpp/winlator/vk/vk_renderer.c @@ -0,0 +1,3239 @@ +// Vulkan compositor for the X-server display path. +// +// Owns the entire native-side rendering state. Java JNI shims push scene snapshots and call +// frame submit; this file handles instance/device/swapchain/pipelines/sync. +// +// All vk* calls below resolve through vk_dispatch.h, which redirects them to the dlopen +// handle (system libvulkan or adrenotools-loaded Turnip) chosen at nativeCreate. +// +// Synchronization model: +// - One graphics queue, serialized externally via VkRenderer::queue_mutex (any thread submits). +// - VK_FRAMES_IN_FLIGHT in-flight frames, each with its own semaphores + fence + cmd buffer. +// - Scene state guarded by VkRenderer::scene_mutex. +// - Texture lifetime: created/uploaded synchronously (blocks ~ms); destroyed via per-frame +// graveyard processed on the render thread, and tracked so renderer teardown can drain +// native texture objects that Java handles have not explicitly destroyed yet. + +#include "vk_state.h" +#include "vk_driver.h" + +#include +#include +#include +#include +#include +#include +#include + +// SPIR-V shader byte arrays generated at build time by glslc + bin2c.cmake. +#include "shaders/window_vert.spv.h" +#include "shaders/window_frag.spv.h" +#include "shaders/cursor_frag.spv.h" +#include "shaders/quad_vert.spv.h" +#include "shaders/blit_frag.spv.h" +#include "shaders/effect_crt_frag.spv.h" +#include "shaders/effect_vivid_frag.spv.h" +#include "shaders/effect_hdr_frag.spv.h" +#include "shaders/effect_natural_frag.spv.h" +#include "shaders/effect_toon_frag.spv.h" +#include "shaders/effect_ntsc_frag.spv.h" +#include "shaders/effect_ntsc2_frag.spv.h" +#include "shaders/effect_coloradj_frag.spv.h" +#include "shaders/effect_colorgrade_frag.spv.h" +#include "shaders/effect_sharpen_frag.spv.h" +#include "shaders/effect_scanlines_frag.spv.h" +#include "shaders/effect_colorblind_frag.spv.h" +#include "shaders/effect_pixelate_frag.spv.h" +#include "shaders/sgsr1_frag.spv.h" + +// ============================================================ +// Forward decls +// ============================================================ + +static bool create_instance(VkRenderer* r); +static void destroy_debug_messenger(VkRenderer* r); +static bool pick_physical_device(VkRenderer* r); +static bool create_device(VkRenderer* r); +static void query_device_caps(VkRenderer* r); +static bool create_command_pool(VkRenderer* r); +static bool create_descriptor_pool(VkRenderer* r, uint32_t capacity); +static bool create_pipelines(VkRenderer* r); +static void destroy_pipelines(VkRenderer* r); +static bool create_swapchain(VkRenderer* r, uint32_t fallback_width, uint32_t fallback_height); +static void destroy_swapchain_resources(VkRenderer* r); +static void destroy_swapchain(VkRenderer* r); +static bool create_offscreen(VkRenderer* r, uint32_t w, uint32_t h, bool need_second); +static void destroy_offscreen(VkRenderer* r); +static bool create_sgsr1_resources(VkRenderer* r, uint32_t w, uint32_t h); +static void destroy_sgsr1_resources(VkRenderer* r); +static bool create_quad_vbo(VkRenderer* r); +static void destroy_quad_vbo(VkRenderer* r); +static bool is_plain_rotation_transform(VkSurfaceTransformFlagBitsKHR transform); +static bool is_quarter_turn_transform(VkSurfaceTransformFlagBitsKHR transform); +static void detach_graveyard_slot(VkRenderer* r, uint32_t slot_idx, + VkTexture*** out_textures, uint32_t* out_count); +static void destroy_graveyard_textures(VkRenderer* r, VkTexture** textures, uint32_t count); +static bool record_and_submit_frame(VkRenderer* r); + +// ============================================================ +// Descriptor set allocation (called from vk_image.c) +// ============================================================ + +VkDescriptorSet vkr_alloc_descriptor_set(VkRenderer* r) { + if (r->pipelines.sampler_set_layout == VK_NULL_HANDLE || r->descriptor_pool == VK_NULL_HANDLE) { + VK_LOGE("vkr_alloc_descriptor_set called before pipelines/pool ready"); + return VK_NULL_HANDLE; + } + + pthread_mutex_lock(&r->descriptor_mutex); + if (r->descriptor_free_count > 0) { + VkDescriptorSet set = r->descriptor_free_list[--r->descriptor_free_count]; + pthread_mutex_unlock(&r->descriptor_mutex); + return set; + } + + VkDescriptorSetAllocateInfo ai = {VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO}; + ai.descriptorPool = r->descriptor_pool; + ai.descriptorSetCount = 1; + ai.pSetLayouts = &r->pipelines.sampler_set_layout; + + VkDescriptorSet set = VK_NULL_HANDLE; + VkResult res = vkAllocateDescriptorSets(r->device, &ai, &set); + if (res != VK_SUCCESS) { + VK_LOGE("vkAllocateDescriptorSets failed: %d (pool used %u/%u)", + res, r->descriptor_pool_used, r->descriptor_pool_capacity); + pthread_mutex_unlock(&r->descriptor_mutex); + return VK_NULL_HANDLE; + } + r->descriptor_pool_used++; + pthread_mutex_unlock(&r->descriptor_mutex); + return set; +} + +void vkr_free_descriptor_set(VkRenderer* r, VkDescriptorSet set) { + if (set == VK_NULL_HANDLE) return; + pthread_mutex_lock(&r->descriptor_mutex); + if (r->descriptor_free_count < r->descriptor_free_capacity) { + r->descriptor_free_list[r->descriptor_free_count++] = set; + } else { + vkFreeDescriptorSets(r->device, r->descriptor_pool, 1, &set); + if (r->descriptor_pool_used > 0) r->descriptor_pool_used--; + } + pthread_mutex_unlock(&r->descriptor_mutex); +} + +// ============================================================ +// Instance +// ============================================================ + +static bool has_extension(const VkExtensionProperties* exts, uint32_t count, const char* name) { + for (uint32_t i = 0; i < count; i++) { + if (strcmp(exts[i].extensionName, name) == 0) return true; + } + return false; +} + +static bool has_layer(const VkLayerProperties* layers, uint32_t count, const char* name) { + for (uint32_t i = 0; i < count; i++) { + if (strcmp(layers[i].layerName, name) == 0) return true; + } + return false; +} + +static const char* debug_severity_name(VkDebugUtilsMessageSeverityFlagBitsEXT severity) { + if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) return "error"; + if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) return "warning"; + if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) return "info"; + return "verbose"; +} + +static VKAPI_ATTR VkBool32 VKAPI_CALL vvl_debug_callback( + VkDebugUtilsMessageSeverityFlagBitsEXT severity, + VkDebugUtilsMessageTypeFlagsEXT type, + const VkDebugUtilsMessengerCallbackDataEXT* data, + void* user) { + (void)type; + (void)user; + const char* message = (data && data->pMessage) ? data->pMessage : "(no message)"; + const char* severity_name = debug_severity_name(severity); + if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) { + VK_LOGE("VVL %s: %s", severity_name, message); + } else if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) { + VK_LOGW("VVL %s: %s", severity_name, message); + } else { + VK_LOGI("VVL %s: %s", severity_name, message); + } + return VK_FALSE; +} + +static bool create_instance(VkRenderer* r) { + uint32_t ext_count = 0; + vkEnumerateInstanceExtensionProperties(NULL, &ext_count, NULL); + VkExtensionProperties* exts = calloc(ext_count, sizeof(VkExtensionProperties)); + vkEnumerateInstanceExtensionProperties(NULL, &ext_count, exts); + + const char* required_exts[] = { + VK_KHR_SURFACE_EXTENSION_NAME, + VK_KHR_ANDROID_SURFACE_EXTENSION_NAME, + }; + for (uint32_t i = 0; i < sizeof(required_exts) / sizeof(required_exts[0]); i++) { + if (!has_extension(exts, ext_count, required_exts[i])) { + VK_LOGE("Missing required instance extension: %s", required_exts[i]); + free(exts); + return false; + } + } + + const char* enabled_layers[1] = {0}; + uint32_t enabled_layer_count = 0; + if (r->validation_enabled) { + uint32_t layer_count = 0; + vkEnumerateInstanceLayerProperties(&layer_count, NULL); + VkLayerProperties* layers = calloc(layer_count, sizeof(VkLayerProperties)); + vkEnumerateInstanceLayerProperties(&layer_count, layers); + if (has_layer(layers, layer_count, "VK_LAYER_KHRONOS_validation")) { + enabled_layers[enabled_layer_count++] = "VK_LAYER_KHRONOS_validation"; + VK_LOGI("Vulkan validation layer enabled"); + } else { + VK_LOGW("Vulkan validation layer requested but VK_LAYER_KHRONOS_validation is unavailable"); + r->validation_enabled = false; + } + free(layers); + } + + const char* enabled_exts[4] = {0}; + uint32_t enabled_ext_count = 0; + for (uint32_t i = 0; i < sizeof(required_exts) / sizeof(required_exts[0]); i++) { + enabled_exts[enabled_ext_count++] = required_exts[i]; + } + if (r->validation_enabled) { + if (has_extension(exts, ext_count, VK_EXT_DEBUG_UTILS_EXTENSION_NAME)) { + enabled_exts[enabled_ext_count++] = VK_EXT_DEBUG_UTILS_EXTENSION_NAME; + r->debug_utils_enabled = true; + } else { + VK_LOGW("VK_EXT_debug_utils unavailable; validation remains enabled without callback logging"); + } + } + free(exts); + + VkApplicationInfo app = {VK_STRUCTURE_TYPE_APPLICATION_INFO}; + app.pApplicationName = "WinNative"; + app.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + app.pEngineName = "WinNativeVk"; + app.engineVersion = VK_MAKE_VERSION(1, 0, 0); + app.apiVersion = VK_API_VERSION_1_1; + + VkInstanceCreateInfo ic = {VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO}; + ic.pApplicationInfo = &app; + ic.enabledExtensionCount = enabled_ext_count; + ic.ppEnabledExtensionNames = enabled_exts; + ic.enabledLayerCount = enabled_layer_count; + ic.ppEnabledLayerNames = enabled_layers; + + VkResult res = vkCreateInstance(&ic, NULL, &r->instance); + if (res != VK_SUCCESS) { + VK_LOGE("vkCreateInstance failed: %d", res); + return false; + } + + if (!vkd_load_instance(r->instance)) { + VK_LOGE("vkd_load_instance failed"); + return false; + } + + if (r->debug_utils_enabled) { + r->fnCreateDebugUtilsMessenger = + (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr( + r->instance, "vkCreateDebugUtilsMessengerEXT"); + r->fnDestroyDebugUtilsMessenger = + (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr( + r->instance, "vkDestroyDebugUtilsMessengerEXT"); + if (r->fnCreateDebugUtilsMessenger && r->fnDestroyDebugUtilsMessenger) { + VkDebugUtilsMessengerCreateInfoEXT dc = { + VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT + }; + dc.messageSeverity = + VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + dc.messageType = + VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + dc.pfnUserCallback = vvl_debug_callback; + res = r->fnCreateDebugUtilsMessenger(r->instance, &dc, NULL, &r->debug_messenger); + if (res != VK_SUCCESS) { + VK_LOGW("vkCreateDebugUtilsMessengerEXT failed: %d", res); + r->debug_utils_enabled = false; + } + } else { + VK_LOGW("VK_EXT_debug_utils entry points unavailable"); + r->debug_utils_enabled = false; + } + } + return true; +} + +static void destroy_debug_messenger(VkRenderer* r) { + if (r->debug_messenger != VK_NULL_HANDLE && r->fnDestroyDebugUtilsMessenger) { + r->fnDestroyDebugUtilsMessenger(r->instance, r->debug_messenger, NULL); + r->debug_messenger = VK_NULL_HANDLE; + } +} + +// ============================================================ +// Physical device +// ============================================================ + +static bool pick_physical_device(VkRenderer* r) { + uint32_t count = 0; + vkEnumeratePhysicalDevices(r->instance, &count, NULL); + if (count == 0) return false; + + VkPhysicalDevice* devices = calloc(count, sizeof(VkPhysicalDevice)); + vkEnumeratePhysicalDevices(r->instance, &count, devices); + + // Score: prefer DISCRETE > INTEGRATED > anything; require graphics queue + surface support is checked later. + int best_score = -1; + int best_idx = -1; + for (uint32_t i = 0; i < count; i++) { + VkPhysicalDeviceProperties p; + vkGetPhysicalDeviceProperties(devices[i], &p); + int s = (p.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) ? 1000 + : (p.deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU) ? 100 : 1; + if (s > best_score) { best_score = s; best_idx = (int)i; } + } + if (best_idx < 0) { free(devices); return false; } + + r->physical_device = devices[best_idx]; + free(devices); + + vkGetPhysicalDeviceMemoryProperties(r->physical_device, &r->mem_props); + + uint32_t qf_count = 0; + vkGetPhysicalDeviceQueueFamilyProperties(r->physical_device, &qf_count, NULL); + VkQueueFamilyProperties* qf = calloc(qf_count, sizeof(VkQueueFamilyProperties)); + vkGetPhysicalDeviceQueueFamilyProperties(r->physical_device, &qf_count, qf); + + r->graphics_queue_family = UINT32_MAX; + for (uint32_t i = 0; i < qf_count; i++) { + if (qf[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) { + r->graphics_queue_family = i; + break; + } + } + free(qf); + if (r->graphics_queue_family == UINT32_MAX) return false; + return true; +} + +// ============================================================ +// Device +// ============================================================ + +static bool create_device(VkRenderer* r) { + uint32_t ext_count = 0; + vkEnumerateDeviceExtensionProperties(r->physical_device, NULL, &ext_count, NULL); + VkExtensionProperties* exts = calloc(ext_count, sizeof(VkExtensionProperties)); + vkEnumerateDeviceExtensionProperties(r->physical_device, NULL, &ext_count, exts); + + bool has_swap = has_extension(exts, ext_count, VK_KHR_SWAPCHAIN_EXTENSION_NAME); + if (!has_swap) { + VK_LOGE("VK_KHR_swapchain not supported"); + free(exts); + return false; + } + + bool has_ahb = has_extension(exts, ext_count, VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME); + bool has_extmem = has_extension(exts, ext_count, VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME); + bool has_dedicated = has_extension(exts, ext_count, VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME); + bool has_get_mem_req2 = has_extension(exts, ext_count, VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME); + bool has_ycbcr = has_extension(exts, ext_count, VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME); + bool has_extmem_caps = has_extension(exts, ext_count, VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME); + bool has_queue_fam = has_extension(exts, ext_count, VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME); + bool has_cubic = has_extension(exts, ext_count, VK_EXT_FILTER_CUBIC_EXTENSION_NAME); + + free(exts); + + const char* enable[16]; + uint32_t enable_n = 0; + enable[enable_n++] = VK_KHR_SWAPCHAIN_EXTENSION_NAME; + + bool ahb_ok = has_ahb && has_extmem && has_dedicated && has_get_mem_req2; + if (ahb_ok) { + enable[enable_n++] = VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME; + enable[enable_n++] = VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME; + enable[enable_n++] = VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME; + enable[enable_n++] = VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME; + if (has_queue_fam) enable[enable_n++] = VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME; + } + if (has_ycbcr) enable[enable_n++] = VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME; + if (has_cubic) enable[enable_n++] = VK_EXT_FILTER_CUBIC_EXTENSION_NAME; + (void)has_extmem_caps; + + r->ext_ahb = ahb_ok; + r->ext_ycbcr = has_ycbcr; + r->ext_filter_cubic = has_cubic; + VK_LOGI("AHB Vulkan device support: android_hardware_buffer=%d external_memory=%d dedicated=%d get_memory_requirements2=%d queue_family_foreign=%d enabled=%d", + has_ahb, has_extmem, has_dedicated, has_get_mem_req2, has_queue_fam, r->ext_ahb); + if (!r->ext_ahb) { + VK_LOGW("AHB Vulkan import disabled; one or more required device extensions are missing"); + } + + float qprio = 1.0f; + VkDeviceQueueCreateInfo qci = {VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO}; + qci.queueFamilyIndex = r->graphics_queue_family; + qci.queueCount = 1; + qci.pQueuePriorities = &qprio; + + VkPhysicalDeviceSamplerYcbcrConversionFeatures ycbcr_feat = { + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES + }; + ycbcr_feat.samplerYcbcrConversion = has_ycbcr ? VK_TRUE : VK_FALSE; + + VkDeviceCreateInfo dci = {VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO}; + if (has_ycbcr) dci.pNext = &ycbcr_feat; + dci.queueCreateInfoCount = 1; + dci.pQueueCreateInfos = &qci; + dci.enabledExtensionCount = enable_n; + dci.ppEnabledExtensionNames = enable; + + if (vkCreateDevice(r->physical_device, &dci, NULL, &r->device) != VK_SUCCESS) { + VK_LOGE("vkCreateDevice failed"); + return false; + } + vkGetDeviceQueue(r->device, r->graphics_queue_family, 0, &r->graphics_queue); + + if (r->ext_ahb) { + r->fnGetAhbProps = (PFN_vkGetAndroidHardwareBufferPropertiesANDROID) + vkGetDeviceProcAddr(r->device, "vkGetAndroidHardwareBufferPropertiesANDROID"); + if (!r->fnGetAhbProps) { + VK_LOGW("AHB Vulkan import disabled; vkGetAndroidHardwareBufferPropertiesANDROID is unavailable"); + r->ext_ahb = false; + } + } + if (r->ext_ycbcr) { + r->fnCreateYcbcr = (PFN_vkCreateSamplerYcbcrConversion) + vkGetDeviceProcAddr(r->device, "vkCreateSamplerYcbcrConversion"); + r->fnDestroyYcbcr = (PFN_vkDestroySamplerYcbcrConversion) + vkGetDeviceProcAddr(r->device, "vkDestroySamplerYcbcrConversion"); + // Fallback to KHR variants if 1.1 core entry points aren't exposed by the loader. + if (!r->fnCreateYcbcr) { + r->fnCreateYcbcr = (PFN_vkCreateSamplerYcbcrConversion) + vkGetDeviceProcAddr(r->device, "vkCreateSamplerYcbcrConversionKHR"); + } + if (!r->fnDestroyYcbcr) { + r->fnDestroyYcbcr = (PFN_vkDestroySamplerYcbcrConversion) + vkGetDeviceProcAddr(r->device, "vkDestroySamplerYcbcrConversionKHR"); + } + if (!r->fnCreateYcbcr || !r->fnDestroyYcbcr) { + VK_LOGW("Ycbcr conversion entry points unavailable; AHB import limited to RGB formats"); + r->ext_ycbcr = false; + } + } + + VK_LOGI("Vulkan device created (AHB=%d, Ycbcr=%d)", r->ext_ahb, r->ext_ycbcr); + return true; +} + +// ============================================================ +// Device capability probe +// ============================================================ + +static void query_device_caps(VkRenderer* r) { + VkPhysicalDeviceProperties props; + vkGetPhysicalDeviceProperties(r->physical_device, &props); + r->caps.vendor_id = props.vendorID; + r->caps.device_id = props.deviceID; + r->caps.driver_version = props.driverVersion; + r->caps.is_adreno = (props.vendorID == 0x5143); // Qualcomm + r->caps.limits = props.limits; + + // Descriptor pool capacity. Vulkan doesn't spec-bound pool size — the only ceiling + // is driver memory, and each combined-image-sampler set is ~100-200 bytes on Adreno, + // so 4096 sets is ~1 MB upfront. Pick a number high enough that an X server with + // hundreds of short-lived pixmaps can't realistically exhaust it. Grow-on-exhaust + // is the proper unbounded answer and remains a separate TODO. + r->caps.descriptor_pool_capacity = 4096; + + // Offscreen color format. Prefer BGRA8 to match the upload format (no shader swizzle), + // fall back to RGBA8 if the driver doesn't expose BGRA as a sampled color attachment + // in OPTIMAL tiling. RGBA8 is spec-guaranteed for both features. + const VkFormatFeatureFlags need = VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT + | VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT; + const VkFormat offscreen_candidates[2] = { + VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_R8G8B8A8_UNORM + }; + r->caps.offscreen_format = VK_FORMAT_R8G8B8A8_UNORM; + for (int i = 0; i < 2; i++) { + VkFormatProperties fp; + vkGetPhysicalDeviceFormatProperties(r->physical_device, offscreen_candidates[i], &fp); + if ((fp.optimalTilingFeatures & need) == need) { + r->caps.offscreen_format = offscreen_candidates[i]; + break; + } + } + + // CPU-uploaded texture format. RGBA8 is spec-guaranteed; BGRA8 is optional. + const VkFormatFeatureFlags upload_need = VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT + | VK_FORMAT_FEATURE_TRANSFER_DST_BIT; + r->caps.upload_format = VK_FORMAT_R8G8B8A8_UNORM; + r->caps.upload_needs_bgra_swizzle = true; + { + VkFormatProperties fp; + vkGetPhysicalDeviceFormatProperties(r->physical_device, VK_FORMAT_B8G8R8A8_UNORM, &fp); + if ((fp.optimalTilingFeatures & upload_need) == upload_need) { + r->caps.upload_format = VK_FORMAT_B8G8R8A8_UNORM; + r->caps.upload_needs_bgra_swizzle = false; + } + } + + // AHB BGRA8 importability — diagnostic only; per-import paths still probe themselves. + r->caps.ahb_bgra_supported = false; + if (r->ext_ahb) { + VkPhysicalDeviceExternalImageFormatInfo ext = { + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO + }; + ext.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID; + + VkPhysicalDeviceImageFormatInfo2 ifi = { + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2 + }; + ifi.pNext = &ext; + ifi.format = VK_FORMAT_B8G8R8A8_UNORM; + ifi.type = VK_IMAGE_TYPE_2D; + ifi.tiling = VK_IMAGE_TILING_OPTIMAL; + ifi.usage = VK_IMAGE_USAGE_SAMPLED_BIT; + + VkExternalImageFormatProperties ext_out = { + VK_STRUCTURE_TYPE_EXTERNAL_IMAGE_FORMAT_PROPERTIES + }; + VkImageFormatProperties2 out = { VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2 }; + out.pNext = &ext_out; + + // The 1.1 core entry point isn't statically exported by the Android Vulkan loader on all + // NDK targets; resolve dynamically and fall back to the KHR alias. + PFN_vkGetPhysicalDeviceImageFormatProperties2 fnGetIfp2 = + (PFN_vkGetPhysicalDeviceImageFormatProperties2) + vkGetInstanceProcAddr(r->instance, "vkGetPhysicalDeviceImageFormatProperties2"); + if (!fnGetIfp2) { + fnGetIfp2 = (PFN_vkGetPhysicalDeviceImageFormatProperties2) + vkGetInstanceProcAddr(r->instance, "vkGetPhysicalDeviceImageFormatProperties2KHR"); + } + if (fnGetIfp2 && fnGetIfp2(r->physical_device, &ifi, &out) == VK_SUCCESS) { + r->caps.ahb_bgra_supported = + (ext_out.externalMemoryProperties.externalMemoryFeatures + & VK_EXTERNAL_MEMORY_FEATURE_IMPORTABLE_BIT) != 0; + } + } + + VK_LOGI("Device caps: vendor=0x%x device=0x%x driver=0x%x adreno=%d offscreen=%s upload=%s ahb_bgra=%d desc_pool=%u", + r->caps.vendor_id, r->caps.device_id, r->caps.driver_version, + r->caps.is_adreno, + r->caps.offscreen_format == VK_FORMAT_B8G8R8A8_UNORM ? "BGRA8" : "RGBA8", + r->caps.upload_format == VK_FORMAT_B8G8R8A8_UNORM ? "BGRA8" : "RGBA8(swizzle)", + r->caps.ahb_bgra_supported, + r->caps.descriptor_pool_capacity); +} + +// ============================================================ +// Command pool + per-frame +// ============================================================ + +static bool create_command_pool(VkRenderer* r) { + VkCommandPoolCreateInfo ci = {VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO}; + ci.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + ci.queueFamilyIndex = r->graphics_queue_family; + if (vkCreateCommandPool(r->device, &ci, NULL, &r->cmd_pool) != VK_SUCCESS) return false; + + VkCommandBufferAllocateInfo ai = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO}; + ai.commandPool = r->cmd_pool; + ai.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + ai.commandBufferCount = 1; + + VkSemaphoreCreateInfo si = {VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO}; + VkFenceCreateInfo fi = {VK_STRUCTURE_TYPE_FENCE_CREATE_INFO}; + fi.flags = VK_FENCE_CREATE_SIGNALED_BIT; + + for (uint32_t i = 0; i < VK_FRAMES_IN_FLIGHT; i++) { + VkFrame* f = &r->frames[i]; + if (vkAllocateCommandBuffers(r->device, &ai, &f->cmd) != VK_SUCCESS) return false; + if (vkCreateSemaphore(r->device, &si, NULL, &f->image_available) != VK_SUCCESS) return false; + if (vkCreateFence(r->device, &fi, NULL, &f->in_flight) != VK_SUCCESS) return false; + } + return true; +} + +// ============================================================ +// Descriptor pool +// ============================================================ + +static bool create_descriptor_pool(VkRenderer* r, uint32_t capacity) { + VkDescriptorPoolSize ps = {0}; + ps.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + ps.descriptorCount = capacity; + + VkDescriptorPoolCreateInfo ci = {VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO}; + ci.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT; + ci.maxSets = capacity; + ci.poolSizeCount = 1; + ci.pPoolSizes = &ps; + if (vkCreateDescriptorPool(r->device, &ci, NULL, &r->descriptor_pool) != VK_SUCCESS) { + VK_LOGE("vkCreateDescriptorPool failed"); + return false; + } + r->descriptor_pool_capacity = capacity; + r->descriptor_pool_used = 0; + uint32_t free_cap = capacity < 512 ? capacity : 512; + r->descriptor_free_list = calloc(free_cap, sizeof(VkDescriptorSet)); + r->descriptor_free_count = 0; + r->descriptor_free_capacity = r->descriptor_free_list ? free_cap : 0; + return true; +} + +// ============================================================ +// Pipelines +// ============================================================ + +static VkShaderModule load_shader_module(VkRenderer* r, const uint32_t* code, size_t code_size) { + VkShaderModuleCreateInfo ci = {VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO}; + ci.codeSize = code_size; + ci.pCode = code; + VkShaderModule m; + if (vkCreateShaderModule(r->device, &ci, NULL, &m) != VK_SUCCESS) { + VK_LOGE("vkCreateShaderModule failed (size=%zu)", code_size); + return VK_NULL_HANDLE; + } + return m; +} + +static bool create_render_passes(VkRenderer* r) { + // Swapchain pass: color attachment, presentation final layout. + { + VkAttachmentDescription att = {0}; + att.format = r->swapchain_format; + att.samples = VK_SAMPLE_COUNT_1_BIT; + att.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + att.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + att.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + att.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + att.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + att.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + VkAttachmentReference ref = {0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL}; + + VkSubpassDescription sp = {0}; + sp.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + sp.colorAttachmentCount = 1; + sp.pColorAttachments = &ref; + + VkSubpassDependency dep = {0}; + dep.srcSubpass = VK_SUBPASS_EXTERNAL; + dep.dstSubpass = 0; + dep.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dep.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dep.srcAccessMask = 0; + dep.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + + VkRenderPassCreateInfo rci = {VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO}; + rci.attachmentCount = 1; + rci.pAttachments = &att; + rci.subpassCount = 1; + rci.pSubpasses = &sp; + rci.dependencyCount = 1; + rci.pDependencies = &dep; + if (vkCreateRenderPass(r->device, &rci, NULL, &r->pipelines.swapchain_pass) != VK_SUCCESS) { + return false; + } + } + + // Offscreen pass: color attachment, final shader-read layout. + { + VkAttachmentDescription att = {0}; + att.format = r->caps.offscreen_format; + att.samples = VK_SAMPLE_COUNT_1_BIT; + att.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + att.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + att.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + att.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + att.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + att.finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + + VkAttachmentReference ref = {0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL}; + + VkSubpassDescription sp = {0}; + sp.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + sp.colorAttachmentCount = 1; + sp.pColorAttachments = &ref; + + VkSubpassDependency deps[2] = {0}; + deps[0].srcSubpass = VK_SUBPASS_EXTERNAL; + deps[0].dstSubpass = 0; + deps[0].srcStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + deps[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + deps[0].srcAccessMask = VK_ACCESS_SHADER_READ_BIT; + deps[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + deps[1].srcSubpass = 0; + deps[1].dstSubpass = VK_SUBPASS_EXTERNAL; + deps[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + deps[1].dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + deps[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + deps[1].dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + + VkRenderPassCreateInfo rci = {VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO}; + rci.attachmentCount = 1; + rci.pAttachments = &att; + rci.subpassCount = 1; + rci.pSubpasses = &sp; + rci.dependencyCount = 2; + rci.pDependencies = deps; + if (vkCreateRenderPass(r->device, &rci, NULL, &r->pipelines.offscreen_pass) != VK_SUCCESS) { + return false; + } + } + + return true; +} + +static bool create_pipeline_layouts(VkRenderer* r) { + VkDescriptorSetLayoutBinding bind = {0}; + bind.binding = 0; + bind.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + bind.descriptorCount = 1; + bind.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; + + VkDescriptorSetLayoutCreateInfo dlci = {VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO}; + dlci.bindingCount = 1; + dlci.pBindings = &bind; + if (vkCreateDescriptorSetLayout(r->device, &dlci, NULL, &r->pipelines.sampler_set_layout) != VK_SUCCESS) { + return false; + } + + // Window/cursor: push constants = float xform[6] + vec2 viewSize + vec4 uvRect + // + int swapRB = 52 bytes + VkPushConstantRange pcr_window = {0}; + pcr_window.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT; + pcr_window.offset = 0; + pcr_window.size = 52; + + VkPipelineLayoutCreateInfo plci = {VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; + plci.setLayoutCount = 1; + plci.pSetLayouts = &r->pipelines.sampler_set_layout; + plci.pushConstantRangeCount = 1; + plci.pPushConstantRanges = &pcr_window; + if (vkCreatePipelineLayout(r->device, &plci, NULL, &r->pipelines.window_layout) != VK_SUCCESS) { + return false; + } + + // Effect: push constants = vec2 resolution + 4 floats (sat, contrast, sharp, mode) = 24 bytes. + // Other effect shaders only declare the first 16 bytes and ignore the rest. + VkPushConstantRange pcr_effect = {0}; + pcr_effect.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; + pcr_effect.offset = 0; + pcr_effect.size = 24; + + VkPipelineLayoutCreateInfo plci2 = {VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; + plci2.setLayoutCount = 1; + plci2.pSetLayouts = &r->pipelines.sampler_set_layout; + plci2.pushConstantRangeCount = 1; + plci2.pPushConstantRanges = &pcr_effect; + if (vkCreatePipelineLayout(r->device, &plci2, NULL, &r->pipelines.effect_layout) != VK_SUCCESS) { + return false; + } + + return true; +} + +static VkPipeline create_graphics_pipeline( + VkRenderer* r, + VkShaderModule vs, VkShaderModule fs, + VkPipelineLayout layout, + VkRenderPass pass, + bool has_vertex_input, + bool blend_alpha, + const VkSpecializationInfo* fs_spec) +{ + VkPipelineShaderStageCreateInfo stages[2] = {0}; + stages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT; + stages[0].module = vs; + stages[0].pName = "main"; + stages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT; + stages[1].module = fs; + stages[1].pName = "main"; + stages[1].pSpecializationInfo = fs_spec; + + VkVertexInputBindingDescription vbind = {0}; + vbind.binding = 0; + vbind.stride = sizeof(float) * 2; + vbind.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + + VkVertexInputAttributeDescription vattr = {0}; + vattr.location = 0; + vattr.binding = 0; + vattr.format = VK_FORMAT_R32G32_SFLOAT; + vattr.offset = 0; + + VkPipelineVertexInputStateCreateInfo vi = {VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO}; + if (has_vertex_input) { + vi.vertexBindingDescriptionCount = 1; + vi.pVertexBindingDescriptions = &vbind; + vi.vertexAttributeDescriptionCount = 1; + vi.pVertexAttributeDescriptions = &vattr; + } + + VkPipelineInputAssemblyStateCreateInfo ia = {VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO}; + ia.topology = has_vertex_input ? VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP : VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + + VkPipelineViewportStateCreateInfo vp = {VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO}; + vp.viewportCount = 1; + vp.scissorCount = 1; + + VkPipelineRasterizationStateCreateInfo rs = {VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO}; + rs.polygonMode = VK_POLYGON_MODE_FILL; + rs.cullMode = VK_CULL_MODE_NONE; + rs.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + rs.lineWidth = 1.0f; + + VkPipelineMultisampleStateCreateInfo ms = {VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO}; + ms.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + + VkPipelineColorBlendAttachmentState blend = {0}; + blend.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT + | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + blend.blendEnable = blend_alpha ? VK_TRUE : VK_FALSE; + blend.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; + blend.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + blend.colorBlendOp = VK_BLEND_OP_ADD; + blend.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; + blend.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; + blend.alphaBlendOp = VK_BLEND_OP_ADD; + + VkPipelineColorBlendStateCreateInfo cb = {VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO}; + cb.attachmentCount = 1; + cb.pAttachments = &blend; + + VkDynamicState dyn[3] = { + VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo ds = {VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO}; + ds.dynamicStateCount = 2; + ds.pDynamicStates = dyn; + + VkGraphicsPipelineCreateInfo gpi = {VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO}; + gpi.stageCount = 2; + gpi.pStages = stages; + gpi.pVertexInputState = &vi; + gpi.pInputAssemblyState = &ia; + gpi.pViewportState = &vp; + gpi.pRasterizationState = &rs; + gpi.pMultisampleState = &ms; + gpi.pColorBlendState = &cb; + gpi.pDynamicState = &ds; + gpi.layout = layout; + gpi.renderPass = pass; + gpi.subpass = 0; + + VkPipeline pipe = VK_NULL_HANDLE; + if (vkCreateGraphicsPipelines(r->device, VK_NULL_HANDLE, 1, &gpi, NULL, &pipe) != VK_SUCCESS) { + VK_LOGE("vkCreateGraphicsPipelines failed"); + return VK_NULL_HANDLE; + } + return pipe; +} + +static bool create_pipelines(VkRenderer* r) { + if (!create_render_passes(r)) return false; + if (!create_pipeline_layouts(r)) return false; + + VkShaderModule vs_window = load_shader_module(r, window_vert, window_vert_size); + VkShaderModule fs_window = load_shader_module(r, window_frag, window_frag_size); + VkShaderModule fs_cursor = load_shader_module(r, cursor_frag, cursor_frag_size); + VkShaderModule vs_quad = load_shader_module(r, quad_vert, quad_vert_size); + VkShaderModule fs_blit = load_shader_module(r, blit_frag, blit_frag_size); + VkShaderModule fs_crt = load_shader_module(r, effect_crt_frag, effect_crt_frag_size); + VkShaderModule fs_vivid = load_shader_module(r, effect_vivid_frag, effect_vivid_frag_size); + VkShaderModule fs_hdr = load_shader_module(r, effect_hdr_frag, effect_hdr_frag_size); + VkShaderModule fs_natural= load_shader_module(r, effect_natural_frag,effect_natural_frag_size); + VkShaderModule fs_toon = load_shader_module(r, effect_toon_frag, effect_toon_frag_size); + VkShaderModule fs_ntsc = load_shader_module(r, effect_ntsc_frag, effect_ntsc_frag_size); + VkShaderModule fs_ntsc2 = load_shader_module(r, effect_ntsc2_frag, effect_ntsc2_frag_size); + VkShaderModule fs_coloradj = load_shader_module(r, effect_coloradj_frag, effect_coloradj_frag_size); + VkShaderModule fs_colorgrade = load_shader_module(r, effect_colorgrade_frag, effect_colorgrade_frag_size); + VkShaderModule fs_sharpen = load_shader_module(r, effect_sharpen_frag, effect_sharpen_frag_size); + VkShaderModule fs_scanlines = load_shader_module(r, effect_scanlines_frag, effect_scanlines_frag_size); + VkShaderModule fs_colorblind = load_shader_module(r, effect_colorblind_frag, effect_colorblind_frag_size); + VkShaderModule fs_pixelate = load_shader_module(r, effect_pixelate_frag, effect_pixelate_frag_size); + VkShaderModule fs_sgsr1 = load_shader_module(r, sgsr1_frag, sgsr1_frag_size); + if (!vs_window || !fs_window || !fs_cursor || !vs_quad || !fs_blit + || !fs_crt || !fs_vivid || !fs_hdr || !fs_natural + || !fs_toon || !fs_ntsc || !fs_ntsc2 || !fs_coloradj + || !fs_colorgrade || !fs_sharpen || !fs_scanlines + || !fs_colorblind || !fs_pixelate + || !fs_sgsr1) { + return false; + } + + r->pipelines.window_pipeline = create_graphics_pipeline( + r, vs_window, fs_window, r->pipelines.window_layout, r->pipelines.swapchain_pass, + true, false, NULL); + r->pipelines.cursor_pipeline = create_graphics_pipeline( + r, vs_window, fs_cursor, r->pipelines.window_layout, r->pipelines.swapchain_pass, + true, true, NULL); + r->pipelines.blit_pipeline = create_graphics_pipeline( + r, vs_quad, fs_blit, r->pipelines.effect_layout, r->pipelines.swapchain_pass, + false, false, NULL); + r->pipelines.effect_pipelines[VK_EFFECT_CRT] = create_graphics_pipeline( + r, vs_quad, fs_crt, r->pipelines.effect_layout, r->pipelines.swapchain_pass, + false, false, NULL); + r->pipelines.effect_pipelines[VK_EFFECT_VIVID] = create_graphics_pipeline( + r, vs_quad, fs_vivid, r->pipelines.effect_layout, r->pipelines.swapchain_pass, + false, false, NULL); + r->pipelines.effect_pipelines[VK_EFFECT_HDR] = create_graphics_pipeline( + r, vs_quad, fs_hdr, r->pipelines.effect_layout, r->pipelines.swapchain_pass, + false, false, NULL); + r->pipelines.effect_pipelines[VK_EFFECT_NATURAL] = create_graphics_pipeline( + r, vs_quad, fs_natural, r->pipelines.effect_layout, r->pipelines.swapchain_pass, + false, false, NULL); + r->pipelines.effect_pipelines[VK_EFFECT_SGSR1] = create_graphics_pipeline( + r, vs_quad, fs_sgsr1, r->pipelines.effect_layout, r->pipelines.swapchain_pass, + false, false, NULL); + r->pipelines.effect_pipelines[VK_EFFECT_TOON] = create_graphics_pipeline( + r, vs_quad, fs_toon, r->pipelines.effect_layout, r->pipelines.swapchain_pass, + false, false, NULL); + r->pipelines.effect_pipelines[VK_EFFECT_NTSC] = create_graphics_pipeline( + r, vs_quad, fs_ntsc, r->pipelines.effect_layout, r->pipelines.swapchain_pass, + false, false, NULL); + r->pipelines.effect_pipelines[VK_EFFECT_COLORADJ] = create_graphics_pipeline( + r, vs_quad, fs_coloradj, r->pipelines.effect_layout, r->pipelines.swapchain_pass, + false, false, NULL); + r->pipelines.effect_pipelines[VK_EFFECT_COLORGRADE] = create_graphics_pipeline( + r, vs_quad, fs_colorgrade, r->pipelines.effect_layout, r->pipelines.swapchain_pass, + false, false, NULL); + r->pipelines.effect_pipelines[VK_EFFECT_SHARPEN] = create_graphics_pipeline( + r, vs_quad, fs_sharpen, r->pipelines.effect_layout, r->pipelines.swapchain_pass, + false, false, NULL); + r->pipelines.effect_pipelines[VK_EFFECT_SCANLINES] = create_graphics_pipeline( + r, vs_quad, fs_scanlines, r->pipelines.effect_layout, r->pipelines.swapchain_pass, + false, false, NULL); + r->pipelines.effect_pipelines[VK_EFFECT_NTSC2] = create_graphics_pipeline( + r, vs_quad, fs_ntsc2, r->pipelines.effect_layout, r->pipelines.swapchain_pass, + false, false, NULL); + r->pipelines.effect_pipelines[VK_EFFECT_COLORBLIND] = create_graphics_pipeline( + r, vs_quad, fs_colorblind, r->pipelines.effect_layout, r->pipelines.swapchain_pass, + false, false, NULL); + r->pipelines.effect_pipelines[VK_EFFECT_PIXELATE] = create_graphics_pipeline( + r, vs_quad, fs_pixelate, r->pipelines.effect_layout, r->pipelines.swapchain_pass, + false, false, NULL); + r->pipelines.offscreen_window_pipeline = create_graphics_pipeline( + r, vs_window, fs_window, r->pipelines.window_layout, r->pipelines.offscreen_pass, + true, false, NULL); + r->pipelines.offscreen_cursor_pipeline = create_graphics_pipeline( + r, vs_window, fs_cursor, r->pipelines.window_layout, r->pipelines.offscreen_pass, + true, true, NULL); + r->pipelines.offscreen_blit_pipeline = create_graphics_pipeline( + r, vs_quad, fs_blit, r->pipelines.effect_layout, r->pipelines.offscreen_pass, + false, false, NULL); + r->pipelines.offscreen_effect_pipelines[VK_EFFECT_CRT] = create_graphics_pipeline( + r, vs_quad, fs_crt, r->pipelines.effect_layout, r->pipelines.offscreen_pass, + false, false, NULL); + r->pipelines.offscreen_effect_pipelines[VK_EFFECT_VIVID] = create_graphics_pipeline( + r, vs_quad, fs_vivid, r->pipelines.effect_layout, r->pipelines.offscreen_pass, + false, false, NULL); + r->pipelines.offscreen_effect_pipelines[VK_EFFECT_HDR] = create_graphics_pipeline( + r, vs_quad, fs_hdr, r->pipelines.effect_layout, r->pipelines.offscreen_pass, + false, false, NULL); + r->pipelines.offscreen_effect_pipelines[VK_EFFECT_NATURAL] = create_graphics_pipeline( + r, vs_quad, fs_natural, r->pipelines.effect_layout, r->pipelines.offscreen_pass, + false, false, NULL); + r->pipelines.offscreen_effect_pipelines[VK_EFFECT_SGSR1] = create_graphics_pipeline( + r, vs_quad, fs_sgsr1, r->pipelines.effect_layout, r->pipelines.offscreen_pass, + false, false, NULL); + r->pipelines.offscreen_effect_pipelines[VK_EFFECT_TOON] = create_graphics_pipeline( + r, vs_quad, fs_toon, r->pipelines.effect_layout, r->pipelines.offscreen_pass, + false, false, NULL); + r->pipelines.offscreen_effect_pipelines[VK_EFFECT_NTSC] = create_graphics_pipeline( + r, vs_quad, fs_ntsc, r->pipelines.effect_layout, r->pipelines.offscreen_pass, + false, false, NULL); + r->pipelines.offscreen_effect_pipelines[VK_EFFECT_COLORADJ] = create_graphics_pipeline( + r, vs_quad, fs_coloradj, r->pipelines.effect_layout, r->pipelines.offscreen_pass, + false, false, NULL); + r->pipelines.offscreen_effect_pipelines[VK_EFFECT_COLORGRADE] = create_graphics_pipeline( + r, vs_quad, fs_colorgrade, r->pipelines.effect_layout, r->pipelines.offscreen_pass, + false, false, NULL); + r->pipelines.offscreen_effect_pipelines[VK_EFFECT_SHARPEN] = create_graphics_pipeline( + r, vs_quad, fs_sharpen, r->pipelines.effect_layout, r->pipelines.offscreen_pass, + false, false, NULL); + r->pipelines.offscreen_effect_pipelines[VK_EFFECT_SCANLINES] = create_graphics_pipeline( + r, vs_quad, fs_scanlines, r->pipelines.effect_layout, r->pipelines.offscreen_pass, + false, false, NULL); + r->pipelines.offscreen_effect_pipelines[VK_EFFECT_NTSC2] = create_graphics_pipeline( + r, vs_quad, fs_ntsc2, r->pipelines.effect_layout, r->pipelines.offscreen_pass, + false, false, NULL); + r->pipelines.offscreen_effect_pipelines[VK_EFFECT_COLORBLIND] = create_graphics_pipeline( + r, vs_quad, fs_colorblind, r->pipelines.effect_layout, r->pipelines.offscreen_pass, + false, false, NULL); + r->pipelines.offscreen_effect_pipelines[VK_EFFECT_PIXELATE] = create_graphics_pipeline( + r, vs_quad, fs_pixelate, r->pipelines.effect_layout, r->pipelines.offscreen_pass, + false, false, NULL); + + vkDestroyShaderModule(r->device, vs_window, NULL); + vkDestroyShaderModule(r->device, fs_window, NULL); + vkDestroyShaderModule(r->device, fs_cursor, NULL); + vkDestroyShaderModule(r->device, vs_quad, NULL); + vkDestroyShaderModule(r->device, fs_blit, NULL); + vkDestroyShaderModule(r->device, fs_crt, NULL); + vkDestroyShaderModule(r->device, fs_vivid, NULL); + vkDestroyShaderModule(r->device, fs_hdr, NULL); + vkDestroyShaderModule(r->device, fs_natural, NULL); + vkDestroyShaderModule(r->device, fs_toon, NULL); + vkDestroyShaderModule(r->device, fs_ntsc, NULL); + vkDestroyShaderModule(r->device, fs_ntsc2, NULL); + vkDestroyShaderModule(r->device, fs_coloradj, NULL); + vkDestroyShaderModule(r->device, fs_colorgrade, NULL); + vkDestroyShaderModule(r->device, fs_sharpen, NULL); + vkDestroyShaderModule(r->device, fs_scanlines, NULL); + vkDestroyShaderModule(r->device, fs_colorblind, NULL); + vkDestroyShaderModule(r->device, fs_pixelate, NULL); + vkDestroyShaderModule(r->device, fs_sgsr1, NULL); + + if (!r->pipelines.window_pipeline || !r->pipelines.cursor_pipeline + || !r->pipelines.blit_pipeline + || !r->pipelines.offscreen_window_pipeline + || !r->pipelines.offscreen_cursor_pipeline + || !r->pipelines.offscreen_blit_pipeline) { + destroy_pipelines(r); + return false; + } + for (uint32_t i = 0; i < VK_EFFECT_COUNT; i++) { + if (!r->pipelines.effect_pipelines[i] + || !r->pipelines.offscreen_effect_pipelines[i]) { + VK_LOGE("Failed to create effect pipeline %u", i); + destroy_pipelines(r); + return false; + } + } + r->pipelines_built = true; + return true; +} + +static void destroy_pipelines(VkRenderer* r) { + for (uint32_t i = 0; i < VK_EFFECT_COUNT; i++) { + if (r->pipelines.effect_pipelines[i] != VK_NULL_HANDLE) { + vkDestroyPipeline(r->device, r->pipelines.effect_pipelines[i], NULL); + r->pipelines.effect_pipelines[i] = VK_NULL_HANDLE; + } + if (r->pipelines.offscreen_effect_pipelines[i] != VK_NULL_HANDLE) { + vkDestroyPipeline(r->device, r->pipelines.offscreen_effect_pipelines[i], NULL); + r->pipelines.offscreen_effect_pipelines[i] = VK_NULL_HANDLE; + } + } + if (r->pipelines.window_pipeline) vkDestroyPipeline(r->device, r->pipelines.window_pipeline, NULL); + if (r->pipelines.cursor_pipeline) vkDestroyPipeline(r->device, r->pipelines.cursor_pipeline, NULL); + if (r->pipelines.blit_pipeline) vkDestroyPipeline(r->device, r->pipelines.blit_pipeline, NULL); + if (r->pipelines.offscreen_window_pipeline) vkDestroyPipeline(r->device, r->pipelines.offscreen_window_pipeline, NULL); + if (r->pipelines.offscreen_cursor_pipeline) vkDestroyPipeline(r->device, r->pipelines.offscreen_cursor_pipeline, NULL); + if (r->pipelines.offscreen_blit_pipeline) vkDestroyPipeline(r->device, r->pipelines.offscreen_blit_pipeline, NULL); + if (r->pipelines.window_layout) vkDestroyPipelineLayout(r->device, r->pipelines.window_layout, NULL); + if (r->pipelines.effect_layout) vkDestroyPipelineLayout(r->device, r->pipelines.effect_layout, NULL); + if (r->pipelines.sampler_set_layout) vkDestroyDescriptorSetLayout(r->device, r->pipelines.sampler_set_layout, NULL); + if (r->pipelines.swapchain_pass) vkDestroyRenderPass(r->device, r->pipelines.swapchain_pass, NULL); + if (r->pipelines.offscreen_pass) vkDestroyRenderPass(r->device, r->pipelines.offscreen_pass, NULL); + memset(&r->pipelines, 0, sizeof(r->pipelines)); + r->pipelines_built = false; +} + +// ============================================================ +// Quad VBO (for window/cursor pipelines that use vertex input) +// ============================================================ + +static bool create_quad_vbo(VkRenderer* r) { + static const float QUAD[] = { + 0.0f, 0.0f, + 0.0f, 1.0f, + 1.0f, 0.0f, + 1.0f, 1.0f, + }; + + VkBufferCreateInfo bci = {VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO}; + bci.size = sizeof(QUAD); + bci.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; + bci.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + if (vkCreateBuffer(r->device, &bci, NULL, &r->quad_vbo) != VK_SUCCESS) return false; + + VkMemoryRequirements mr; + vkGetBufferMemoryRequirements(r->device, r->quad_vbo, &mr); + + VkMemoryAllocateInfo ai = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO}; + ai.allocationSize = mr.size; + ai.memoryTypeIndex = vkr_find_memory_type(r, mr.memoryTypeBits, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + if (ai.memoryTypeIndex == UINT32_MAX) return false; + if (vkAllocateMemory(r->device, &ai, NULL, &r->quad_vbo_memory) != VK_SUCCESS) return false; + vkBindBufferMemory(r->device, r->quad_vbo, r->quad_vbo_memory, 0); + + void* mapped = NULL; + vkMapMemory(r->device, r->quad_vbo_memory, 0, sizeof(QUAD), 0, &mapped); + memcpy(mapped, QUAD, sizeof(QUAD)); + vkUnmapMemory(r->device, r->quad_vbo_memory); + return true; +} + +static void destroy_quad_vbo(VkRenderer* r) { + if (r->quad_vbo) { vkDestroyBuffer(r->device, r->quad_vbo, NULL); r->quad_vbo = VK_NULL_HANDLE; } + if (r->quad_vbo_memory) { vkFreeMemory(r->device, r->quad_vbo_memory, NULL); r->quad_vbo_memory = VK_NULL_HANDLE; } +} + +// ============================================================ +// Swapchain +// ============================================================ + +static bool create_swapchain(VkRenderer* r, uint32_t fallback_width, uint32_t fallback_height) { + if (!r->surface) return false; + + VkSurfaceCapabilitiesKHR caps; + if (vkGetPhysicalDeviceSurfaceCapabilitiesKHR(r->physical_device, r->surface, &caps) != VK_SUCCESS) { + VK_LOGE("vkGetPhysicalDeviceSurfaceCapabilitiesKHR failed"); + return false; + } + + uint32_t fmt_count = 0; + if (vkGetPhysicalDeviceSurfaceFormatsKHR(r->physical_device, r->surface, &fmt_count, NULL) != VK_SUCCESS + || fmt_count == 0) { + VK_LOGE("No surface formats available"); + return false; + } + VkSurfaceFormatKHR* fmts = calloc(fmt_count, sizeof(VkSurfaceFormatKHR)); + if (!fmts) return false; + vkGetPhysicalDeviceSurfaceFormatsKHR(r->physical_device, r->surface, &fmt_count, fmts); + + VkSurfaceFormatKHR chosen = fmts[0]; + for (uint32_t i = 0; i < fmt_count; i++) { + if (fmts[i].format == VK_FORMAT_R8G8B8A8_UNORM + && fmts[i].colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + chosen = fmts[i]; + break; + } + if (fmts[i].format == VK_FORMAT_B8G8R8A8_UNORM + && fmts[i].colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + chosen = fmts[i]; + } + } + free(fmts); + r->swapchain_format = chosen.format; + + // Honor the Java-requested present mode if the device supports it; otherwise fall back + // to FIFO (always supported per spec). target_present_mode is initialized to FIFO in + // nativeCreate, so a value-equality check is safe (no zero-sentinel ambiguity with + // VK_PRESENT_MODE_IMMEDIATE_KHR which is enum value 0). + VkPresentModeKHR present_mode = VK_PRESENT_MODE_FIFO_KHR; + VkPresentModeKHR want = r->target_present_mode; + if (want != VK_PRESENT_MODE_FIFO_KHR) { + uint32_t pm_count = 0; + vkGetPhysicalDeviceSurfacePresentModesKHR(r->physical_device, r->surface, &pm_count, NULL); + if (pm_count > 0) { + VkPresentModeKHR* pms = calloc(pm_count, sizeof(VkPresentModeKHR)); + if (pms) { + vkGetPhysicalDeviceSurfacePresentModesKHR(r->physical_device, r->surface, &pm_count, pms); + for (uint32_t i = 0; i < pm_count; i++) { + if (pms[i] == want) { present_mode = want; break; } + } + free(pms); + } + } + if (present_mode != want) { + VK_LOGW("Requested present mode %d unavailable; using FIFO", want); + } + } + + VkSurfaceTransformFlagBitsKHR pre_transform = caps.currentTransform; + if (!is_plain_rotation_transform(pre_transform) + || !(caps.supportedTransforms & pre_transform)) { + pre_transform = (caps.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR) + ? VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR + : caps.currentTransform; + } + + VkExtent2D surface_extent = caps.currentExtent; + if (surface_extent.width == 0xFFFFFFFFu) { + surface_extent.width = fallback_width; + surface_extent.height = fallback_height; + } + if ((surface_extent.width == 0 || surface_extent.height == 0) && r->anw) { + int anw_w = ANativeWindow_getWidth(r->anw); + int anw_h = ANativeWindow_getHeight(r->anw); + if (anw_w > 0 && anw_h > 0) { + surface_extent.width = (uint32_t)anw_w; + surface_extent.height = (uint32_t)anw_h; + } + } + if (surface_extent.width == 0 || surface_extent.height == 0) { + VK_LOGW("Skipping swapchain creation for empty surface (%ux%u)", + surface_extent.width, surface_extent.height); + return false; + } + + VkExtent2D extent = surface_extent; + if (is_quarter_turn_transform(pre_transform)) { + uint32_t tmp = extent.width; + extent.width = extent.height; + extent.height = tmp; + } + r->surface_extent = surface_extent; + r->swapchain_extent = extent; + r->swapchain_transform = pre_transform; + // Only possible for unsupported mirrored transforms; avoid an Adreno present loop + // while still letting normal rotation changes recreate the swapchain. + r->ignore_suboptimal = r->caps.is_adreno && (pre_transform != caps.currentTransform); + VK_LOGI("Swapchain surface=%ux%u extent=%ux%u currentTransform=0x%x preTransform=0x%x", + surface_extent.width, surface_extent.height, extent.width, extent.height, + caps.currentTransform, pre_transform); + + uint32_t image_count = caps.minImageCount + 1; + if (caps.maxImageCount > 0 && image_count > caps.maxImageCount) image_count = caps.maxImageCount; + if (image_count > VK_MAX_SWAPCHAIN_IMAGES) image_count = VK_MAX_SWAPCHAIN_IMAGES; + + VkSwapchainCreateInfoKHR sci = {VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR}; + sci.surface = r->surface; + sci.minImageCount = image_count; + sci.imageFormat = chosen.format; + sci.imageColorSpace = chosen.colorSpace; + sci.imageExtent = extent; + sci.imageArrayLayers = 1; + sci.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + if (r->record_blit_src + && (caps.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_SRC_BIT)) { + sci.imageUsage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT; // blit source for the encoder mirror + } + sci.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + sci.preTransform = pre_transform; + sci.compositeAlpha = (caps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR) + ? VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR + : (caps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR) + ? VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR + : (caps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR) + ? VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR + : VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR; + sci.presentMode = present_mode; + sci.clipped = VK_TRUE; + VkSwapchainKHR old_sc = r->swapchain; + sci.oldSwapchain = old_sc; + VkSwapchainKHR new_sc = VK_NULL_HANDLE; + if (vkCreateSwapchainKHR(r->device, &sci, NULL, &new_sc) != VK_SUCCESS) { + VK_LOGE("vkCreateSwapchainKHR failed"); + if (old_sc) { vkDestroySwapchainKHR(r->device, old_sc, NULL); r->swapchain = VK_NULL_HANDLE; } + return false; + } + r->swapchain = new_sc; + if (old_sc) vkDestroySwapchainKHR(r->device, old_sc, NULL); + + uint32_t actual_count = 0; + if (vkGetSwapchainImagesKHR(r->device, r->swapchain, &actual_count, NULL) != VK_SUCCESS + || actual_count == 0) { + VK_LOGE("vkGetSwapchainImagesKHR count failed"); + goto fail; + } + if (actual_count > VK_MAX_SWAPCHAIN_IMAGES) { + VK_LOGE("Swapchain image count %u exceeds storage capacity %u", + actual_count, VK_MAX_SWAPCHAIN_IMAGES); + goto fail; + } + uint32_t got = actual_count; + if (vkGetSwapchainImagesKHR(r->device, r->swapchain, &got, r->swapchain_images) != VK_SUCCESS + || got != actual_count) { + VK_LOGE("vkGetSwapchainImagesKHR images failed"); + goto fail; + } + r->swapchain_image_count = got; + + if (!r->pipelines_built) { + if (!create_pipelines(r)) goto fail; + } + + VkSemaphoreCreateInfo sem_ci = {VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO}; + for (uint32_t i = 0; i < got; i++) { + if (vkCreateSemaphore(r->device, &sem_ci, NULL, + &r->swapchain_render_finished[i]) != VK_SUCCESS) { + goto fail; + } + + VkImageViewCreateInfo ivci = {VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; + ivci.image = r->swapchain_images[i]; + ivci.viewType = VK_IMAGE_VIEW_TYPE_2D; + ivci.format = chosen.format; + ivci.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + ivci.subresourceRange.levelCount = 1; + ivci.subresourceRange.layerCount = 1; + if (vkCreateImageView(r->device, &ivci, NULL, &r->swapchain_views[i]) != VK_SUCCESS) { + goto fail; + } + + VkFramebufferCreateInfo fbci = {VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO}; + fbci.renderPass = r->pipelines.swapchain_pass; + fbci.attachmentCount = 1; + fbci.pAttachments = &r->swapchain_views[i]; + fbci.width = extent.width; + fbci.height = extent.height; + fbci.layers = 1; + if (vkCreateFramebuffer(r->device, &fbci, NULL, &r->swapchain_framebuffers[i]) != VK_SUCCESS) { + goto fail; + } + } + return true; + +fail: + destroy_swapchain(r); + return false; +} + +static void destroy_swapchain_resources(VkRenderer* r) { + for (uint32_t i = 0; i < r->swapchain_image_count; i++) { + if (r->swapchain_render_finished[i]) { + vkDestroySemaphore(r->device, r->swapchain_render_finished[i], NULL); + r->swapchain_render_finished[i] = VK_NULL_HANDLE; + } + if (r->swapchain_framebuffers[i]) { + vkDestroyFramebuffer(r->device, r->swapchain_framebuffers[i], NULL); + r->swapchain_framebuffers[i] = VK_NULL_HANDLE; + } + if (r->swapchain_views[i]) { + vkDestroyImageView(r->device, r->swapchain_views[i], NULL); + r->swapchain_views[i] = VK_NULL_HANDLE; + } + } + r->swapchain_image_count = 0; +} + +static void destroy_swapchain(VkRenderer* r) { + destroy_swapchain_resources(r); + if (r->swapchain) { vkDestroySwapchainKHR(r->device, r->swapchain, NULL); r->swapchain = VK_NULL_HANDLE; } +} + +// ============================================================ +// Recording mirror swapchain (screen-capture target) +// ============================================================ + +static void destroy_record_ui_resources(VkRenderer* r) { + VkRecordSwap* rec = &r->rec; + for (uint32_t i = 0; i < VK_MAX_RECORD_IMAGES; i++) { + if (rec->framebuffers[i]) { vkDestroyFramebuffer(r->device, rec->framebuffers[i], NULL); rec->framebuffers[i] = VK_NULL_HANDLE; } + if (rec->views[i]) { vkDestroyImageView(r->device, rec->views[i], NULL); rec->views[i] = VK_NULL_HANDLE; } + } + if (rec->ui_pipeline) { vkDestroyPipeline(r->device, rec->ui_pipeline, NULL); rec->ui_pipeline = VK_NULL_HANDLE; } + if (rec->ui_pass) { vkDestroyRenderPass(r->device, rec->ui_pass, NULL); rec->ui_pass = VK_NULL_HANDLE; } + if (rec->ui_texture) { vkr_texture_destroy(r, rec->ui_texture); rec->ui_texture = NULL; } + rec->fb_built = false; +} + +// Build the LOAD render pass, framebuffers, and blended blit pipeline for the Record UI composite. +static bool build_record_ui_resources(VkRenderer* r) { + VkRecordSwap* rec = &r->rec; + if (rec->image_count == 0) return false; + if (!r->pipelines_built) return false; + + VkAttachmentDescription att = {0}; + att.format = rec->format; + att.samples = VK_SAMPLE_COUNT_1_BIT; + att.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + att.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + att.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + att.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + att.initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + att.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + VkAttachmentReference ref = {0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL}; + VkSubpassDescription sp = {0}; + sp.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + sp.colorAttachmentCount = 1; + sp.pColorAttachments = &ref; + VkSubpassDependency dep = {0}; + dep.srcSubpass = VK_SUBPASS_EXTERNAL; + dep.dstSubpass = 0; + dep.srcStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT; + dep.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dep.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + dep.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + VkRenderPassCreateInfo rci = {VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO}; + rci.attachmentCount = 1; + rci.pAttachments = &att; + rci.subpassCount = 1; + rci.pSubpasses = &sp; + rci.dependencyCount = 1; + rci.pDependencies = &dep; + if (vkCreateRenderPass(r->device, &rci, NULL, &rec->ui_pass) != VK_SUCCESS) { + VK_LOGE("record: ui_pass create failed"); + return false; + } + + for (uint32_t i = 0; i < rec->image_count; i++) { + VkImageViewCreateInfo ivci = {VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; + ivci.image = rec->images[i]; + ivci.viewType = VK_IMAGE_VIEW_TYPE_2D; + ivci.format = rec->format; + ivci.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + ivci.subresourceRange.levelCount = 1; + ivci.subresourceRange.layerCount = 1; + if (vkCreateImageView(r->device, &ivci, NULL, &rec->views[i]) != VK_SUCCESS) goto fail; + + VkFramebufferCreateInfo fbci = {VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO}; + fbci.renderPass = rec->ui_pass; + fbci.attachmentCount = 1; + fbci.pAttachments = &rec->views[i]; + fbci.width = rec->extent.width; + fbci.height = rec->extent.height; + fbci.layers = 1; + if (vkCreateFramebuffer(r->device, &fbci, NULL, &rec->framebuffers[i]) != VK_SUCCESS) goto fail; + } + + VkShaderModule vs = load_shader_module(r, quad_vert, quad_vert_size); + VkShaderModule fs = load_shader_module(r, blit_frag, blit_frag_size); + if (vs && fs) { + rec->ui_pipeline = create_graphics_pipeline( + r, vs, fs, r->pipelines.effect_layout, rec->ui_pass, false, true, NULL); + } + if (vs) vkDestroyShaderModule(r->device, vs, NULL); + if (fs) vkDestroyShaderModule(r->device, fs, NULL); + if (!rec->ui_pipeline) { VK_LOGE("record: ui_pipeline create failed"); goto fail; } + + rec->fb_built = true; + VK_LOGI("record: UI composite resources ready"); + return true; + +fail: + destroy_record_ui_resources(r); + return false; +} + +static void destroy_record_swapchain(VkRenderer* r) { + destroy_record_ui_resources(r); + VkRecordSwap* rec = &r->rec; + for (uint32_t i = 0; i < rec->image_count; i++) { + if (rec->present_ready[i]) { + vkDestroySemaphore(r->device, rec->present_ready[i], NULL); + rec->present_ready[i] = VK_NULL_HANDLE; + } + } + for (uint32_t i = 0; i < VK_FRAMES_IN_FLIGHT; i++) { + if (rec->acquire[i]) { + vkDestroySemaphore(r->device, rec->acquire[i], NULL); + rec->acquire[i] = VK_NULL_HANDLE; + } + } + rec->image_count = 0; + if (rec->swapchain) { vkDestroySwapchainKHR(r->device, rec->swapchain, NULL); rec->swapchain = VK_NULL_HANDLE; } + if (rec->surface) { vkDestroySurfaceKHR(r->instance, rec->surface, NULL); rec->surface = VK_NULL_HANDLE; } + if (rec->anw) { ANativeWindow_release(rec->anw); rec->anw = NULL; } + rec->active = false; + rec->disabled = false; + rec->ui_enabled = false; +} + +static bool create_record_swapchain(VkRenderer* r) { + VkRecordSwap* rec = &r->rec; + if (!rec->anw) return false; + + VkAndroidSurfaceCreateInfoKHR aci = {VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR}; + aci.window = rec->anw; + if (vkCreateAndroidSurfaceKHR(r->instance, &aci, NULL, &rec->surface) != VK_SUCCESS) { + VK_LOGE("record: vkCreateAndroidSurfaceKHR failed"); + return false; + } + + VkBool32 supported = VK_FALSE; + vkGetPhysicalDeviceSurfaceSupportKHR(r->physical_device, r->graphics_queue_family, + rec->surface, &supported); + if (!supported) { VK_LOGE("record: surface not presentable"); goto fail; } + + VkSurfaceCapabilitiesKHR caps; + if (vkGetPhysicalDeviceSurfaceCapabilitiesKHR(r->physical_device, rec->surface, &caps) != VK_SUCCESS) { + VK_LOGE("record: surface caps query failed"); + goto fail; + } + if (!(caps.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_DST_BIT)) { + VK_LOGE("record: encoder surface lacks TRANSFER_DST usage (0x%x); cannot capture", + caps.supportedUsageFlags); + goto fail; + } + + uint32_t fmt_count = 0; + if (vkGetPhysicalDeviceSurfaceFormatsKHR(r->physical_device, rec->surface, &fmt_count, NULL) != VK_SUCCESS + || fmt_count == 0) { + VK_LOGE("record: surface formats query failed"); + goto fail; + } + VkSurfaceFormatKHR* fmts = calloc(fmt_count, sizeof(VkSurfaceFormatKHR)); + if (!fmts) goto fail; + vkGetPhysicalDeviceSurfaceFormatsKHR(r->physical_device, rec->surface, &fmt_count, fmts); + VkSurfaceFormatKHR chosen = fmts[0]; + for (uint32_t i = 0; i < fmt_count; i++) { + if (fmts[i].format == VK_FORMAT_R8G8B8A8_UNORM) { chosen = fmts[i]; break; } + if (fmts[i].format == VK_FORMAT_B8G8R8A8_UNORM) chosen = fmts[i]; + } + free(fmts); + rec->format = chosen.format; + + VkPresentModeKHR present_mode = VK_PRESENT_MODE_FIFO_KHR; // prefer MAILBOX below + uint32_t pm_count = 0; + vkGetPhysicalDeviceSurfacePresentModesKHR(r->physical_device, rec->surface, &pm_count, NULL); + if (pm_count > 0) { + VkPresentModeKHR* pms = calloc(pm_count, sizeof(VkPresentModeKHR)); + if (pms) { + vkGetPhysicalDeviceSurfacePresentModesKHR(r->physical_device, rec->surface, &pm_count, pms); + for (uint32_t i = 0; i < pm_count; i++) { + if (pms[i] == VK_PRESENT_MODE_MAILBOX_KHR) { present_mode = VK_PRESENT_MODE_MAILBOX_KHR; break; } + } + free(pms); + } + } + + VkExtent2D extent = caps.currentExtent; + if (extent.width == 0xFFFFFFFFu) { + int w = ANativeWindow_getWidth(rec->anw); + int h = ANativeWindow_getHeight(rec->anw); + extent.width = w > 0 ? (uint32_t)w : r->swapchain_extent.width; + extent.height = h > 0 ? (uint32_t)h : r->swapchain_extent.height; + } + if (extent.width == 0 || extent.height == 0) goto fail; + rec->extent = extent; + + VkSurfaceTransformFlagBitsKHR pre = + (caps.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR) + ? VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR : caps.currentTransform; + + uint32_t image_count = caps.minImageCount + 1; + if (caps.maxImageCount > 0 && image_count > caps.maxImageCount) image_count = caps.maxImageCount; + if (image_count > VK_MAX_RECORD_IMAGES) image_count = VK_MAX_RECORD_IMAGES; + + VkSwapchainCreateInfoKHR sci = {VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR}; + sci.surface = rec->surface; + sci.minImageCount = image_count; + sci.imageFormat = chosen.format; + sci.imageColorSpace = chosen.colorSpace; + sci.imageExtent = extent; + sci.imageArrayLayers = 1; + sci.imageUsage = VK_IMAGE_USAGE_TRANSFER_DST_BIT; + if (rec->ui_enabled && (caps.supportedUsageFlags & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT)) { + sci.imageUsage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; // Record UI renders onto the rec image + } else if (rec->ui_enabled) { + VK_LOGW("record: encoder surface can't be a color attachment; UI overlay disabled"); + rec->ui_enabled = false; + } + sci.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + sci.preTransform = pre; + sci.compositeAlpha = (caps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR) + ? VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR : VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR; + sci.presentMode = present_mode; + sci.clipped = VK_TRUE; + if (vkCreateSwapchainKHR(r->device, &sci, NULL, &rec->swapchain) != VK_SUCCESS) { + VK_LOGE("record: vkCreateSwapchainKHR failed"); + goto fail; + } + + uint32_t got = 0; + if (vkGetSwapchainImagesKHR(r->device, rec->swapchain, &got, NULL) != VK_SUCCESS + || got == 0 || got > VK_MAX_RECORD_IMAGES) { + VK_LOGE("record: swapchain image count query failed (got=%u)", got); + goto fail; + } + if (vkGetSwapchainImagesKHR(r->device, rec->swapchain, &got, rec->images) != VK_SUCCESS) { + VK_LOGE("record: swapchain images query failed"); + goto fail; + } + rec->image_count = got; + + VkSemaphoreCreateInfo sem_ci = {VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO}; + for (uint32_t i = 0; i < got; i++) { + if (vkCreateSemaphore(r->device, &sem_ci, NULL, &rec->present_ready[i]) != VK_SUCCESS) goto fail; + } + for (uint32_t i = 0; i < VK_FRAMES_IN_FLIGHT; i++) { + if (vkCreateSemaphore(r->device, &sem_ci, NULL, &rec->acquire[i]) != VK_SUCCESS) goto fail; + } + VK_LOGI("record: mirror swapchain %ux%u images=%u", extent.width, extent.height, got); + + if (rec->ui_enabled && !build_record_ui_resources(r)) { + VK_LOGW("record: UI composite unavailable; capturing game only"); + rec->ui_enabled = false; + } + return true; + +fail: + destroy_record_swapchain(r); + return false; +} + +// ============================================================ +// Offscreen ping-pong (for effect chain) +// ============================================================ + +static bool create_one_offscreen(VkRenderer* r, VkOffscreen* o, uint32_t w, uint32_t h, + VkFilter filter) { + o->width = w; + o->height = h; + + VkImageCreateInfo ic = {VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO}; + ic.imageType = VK_IMAGE_TYPE_2D; + ic.format = r->caps.offscreen_format; + ic.extent.width = w; + ic.extent.height = h; + ic.extent.depth = 1; + ic.mipLevels = 1; + ic.arrayLayers = 1; + ic.samples = VK_SAMPLE_COUNT_1_BIT; + ic.tiling = VK_IMAGE_TILING_OPTIMAL; + ic.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; + ic.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + ic.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + if (vkCreateImage(r->device, &ic, NULL, &o->image) != VK_SUCCESS) return false; + + VkMemoryRequirements mr; + vkGetImageMemoryRequirements(r->device, o->image, &mr); + VkMemoryAllocateInfo ai = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO}; + ai.allocationSize = mr.size; + ai.memoryTypeIndex = vkr_find_memory_type(r, mr.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + if (ai.memoryTypeIndex == UINT32_MAX) return false; + if (vkAllocateMemory(r->device, &ai, NULL, &o->memory) != VK_SUCCESS) return false; + vkBindImageMemory(r->device, o->image, o->memory, 0); + + VkImageViewCreateInfo vi = {VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; + vi.image = o->image; + vi.viewType = VK_IMAGE_VIEW_TYPE_2D; + vi.format = ic.format; + vi.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + vi.subresourceRange.levelCount = 1; + vi.subresourceRange.layerCount = 1; + if (vkCreateImageView(r->device, &vi, NULL, &o->view) != VK_SUCCESS) return false; + + VkSamplerCreateInfo si = {VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO}; + si.magFilter = filter; + si.minFilter = filter; + si.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + si.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + si.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + if (vkCreateSampler(r->device, &si, NULL, &o->sampler) != VK_SUCCESS) return false; + + o->descriptor_set = vkr_alloc_descriptor_set(r); + if (o->descriptor_set == VK_NULL_HANDLE) return false; + + VkDescriptorImageInfo dii = {0}; + dii.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + dii.imageView = o->view; + dii.sampler = o->sampler; + VkWriteDescriptorSet wri = {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET}; + wri.dstSet = o->descriptor_set; + wri.dstBinding = 0; + wri.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + wri.descriptorCount = 1; + wri.pImageInfo = &dii; + vkUpdateDescriptorSets(r->device, 1, &wri, 0, NULL); + + VkFramebufferCreateInfo fbci = {VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO}; + fbci.renderPass = r->pipelines.offscreen_pass; + fbci.attachmentCount = 1; + fbci.pAttachments = &o->view; + fbci.width = w; + fbci.height = h; + fbci.layers = 1; + if (vkCreateFramebuffer(r->device, &fbci, NULL, &o->framebuffer) != VK_SUCCESS) return false; + + return true; +} + +static void destroy_one_offscreen(VkRenderer* r, VkOffscreen* o) { + if (o->framebuffer) vkDestroyFramebuffer(r->device, o->framebuffer, NULL); + if (o->descriptor_set) vkr_free_descriptor_set(r, o->descriptor_set); + if (o->sampler) vkDestroySampler(r->device, o->sampler, NULL); + if (o->view) vkDestroyImageView(r->device, o->view, NULL); + if (o->image) vkDestroyImage(r->device, o->image, NULL); + if (o->memory) vkFreeMemory(r->device, o->memory, NULL); + memset(o, 0, sizeof(*o)); +} + +// Builds offscreen[0], plus the second ping-pong target (~8 MB RGBA8 + view/sampler/descriptor/ +// framebuffer) only when need_second. At matching dims, a missing second target is added in +// place without disturbing offscreen[0]. +static bool create_offscreen(VkRenderer* r, uint32_t w, uint32_t h, bool need_second) { + bool dims_ok = r->offscreen_built + && r->offscreen[0].width == w && r->offscreen[0].height == h; + bool second_ok = !need_second || r->offscreen[1].image != VK_NULL_HANDLE; + if (dims_ok && second_ok) return true; + + if (dims_ok && !second_ok) { // add second target only (callers drained the queue) + if (!create_one_offscreen(r, &r->offscreen[1], w, h, VK_FILTER_LINEAR)) { + destroy_one_offscreen(r, &r->offscreen[1]); + return false; + } + return true; + } + + destroy_offscreen(r); + if (!create_one_offscreen(r, &r->offscreen[0], w, h, VK_FILTER_LINEAR)) goto fail; + if (need_second && !create_one_offscreen(r, &r->offscreen[1], w, h, VK_FILTER_LINEAR)) goto fail; + r->offscreen_built = true; + return true; + +fail: + destroy_offscreen(r); + return false; +} + +static void destroy_offscreen(VkRenderer* r) { + destroy_one_offscreen(r, &r->offscreen[0]); + destroy_one_offscreen(r, &r->offscreen[1]); + r->offscreen_built = false; +} + +static bool create_sgsr1_resources(VkRenderer* r, uint32_t w, uint32_t h) { + if (r->sgsr1.built && r->sgsr1.width == w && r->sgsr1.height == h) return true; + + destroy_sgsr1_resources(r); + if (!create_one_offscreen(r, &r->sgsr1.source, w, h, VK_FILTER_LINEAR)) goto fail; + + r->sgsr1.width = w; + r->sgsr1.height = h; + r->sgsr1.built = true; + VK_LOGI("SGSR1 source created %ux%u -> swapchain %ux%u", + w, h, r->swapchain_extent.width, r->swapchain_extent.height); + return true; + +fail: + destroy_sgsr1_resources(r); + return false; +} + +static void destroy_sgsr1_resources(VkRenderer* r) { + if (r->sgsr1.built) { + VK_LOGI("SGSR1 source destroyed"); + } + destroy_one_offscreen(r, &r->sgsr1.source); + r->sgsr1.built = false; + r->sgsr1.width = 0; + r->sgsr1.height = 0; +} + +// ============================================================ +// Graveyard processing +// ============================================================ + +// Detach the slot's pending-destroy list under scene_mutex. The Vulkan destroy calls +// (vkFreeDescriptorSets, vkDestroyImage, vkFreeMemory, AHardwareBuffer_release) can each +// take tens to hundreds of microseconds on Adreno, so doing them under scene_mutex stalls +// every scene producer (X server, input thread) for the full duration. Caller passes the +// detached array to destroy_graveyard_textures() after releasing the lock. +static void detach_graveyard_slot(VkRenderer* r, uint32_t slot_idx, + VkTexture*** out_textures, uint32_t* out_count) { + VkGraveSlot* slot = &r->graveyard[slot_idx]; + *out_textures = slot->textures; + *out_count = slot->count; + slot->textures = NULL; + slot->count = 0; + slot->capacity = 0; +} + +static void destroy_graveyard_textures(VkRenderer* r, VkTexture** textures, uint32_t count) { + if (!textures) return; + for (uint32_t i = 0; i < count; i++) { + vkr_texture_destroy(r, textures[i]); + } + free(textures); +} + +// ============================================================ +// Frame recording + submission +// ============================================================ + +static bool is_plain_rotation_transform(VkSurfaceTransformFlagBitsKHR transform) { + return transform == VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR + || transform == VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR + || transform == VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR + || transform == VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR; +} + +static bool is_quarter_turn_transform(VkSurfaceTransformFlagBitsKHR transform) { + return transform == VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR + || transform == VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR; +} + +static void transform_xform_for_pretransform(float out[6], const float in[6], + uint32_t view_w, uint32_t view_h, + VkSurfaceTransformFlagBitsKHR transform) { + switch (transform) { + case VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR: + out[0] = -in[1]; + out[1] = in[0]; + out[2] = -in[3]; + out[3] = in[2]; + out[4] = (float)view_h - in[5]; + out[5] = in[4]; + break; + case VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR: + out[0] = -in[0]; + out[1] = -in[1]; + out[2] = -in[2]; + out[3] = -in[3]; + out[4] = (float)view_w - in[4]; + out[5] = (float)view_h - in[5]; + break; + case VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR: + out[0] = in[1]; + out[1] = -in[0]; + out[2] = in[3]; + out[3] = -in[2]; + out[4] = in[5]; + out[5] = (float)view_w - in[4]; + break; + default: + memcpy(out, in, sizeof(float) * 6); + break; + } +} + +static void transformed_view_size(uint32_t* w, uint32_t* h, + VkSurfaceTransformFlagBitsKHR transform) { + if (is_quarter_turn_transform(transform)) { + uint32_t tmp = *w; + *w = *h; + *h = tmp; + } +} + +typedef struct VkPreRotatedRect { + int x; + int y; + int w; + int h; +} VkPreRotatedRect; + +static VkPreRotatedRect transform_rect_for_pretransform(int x, int y, int w, int h, + uint32_t buffer_w, + uint32_t buffer_h, + VkSurfaceTransformFlagBitsKHR transform) { + VkPreRotatedRect r = {x, y, w, h}; + switch (transform) { + case VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR: + r.x = (int)buffer_w - h - y; + r.y = x; + r.w = h; + r.h = w; + break; + case VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR: + r.x = (int)buffer_w - w - x; + r.y = (int)buffer_h - h - y; + break; + case VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR: + r.x = y; + r.y = (int)buffer_h - w - x; + r.w = h; + r.h = w; + break; + default: + break; + } + return r; +} + +static VkPreRotatedRect clamp_rect_to_extent(VkPreRotatedRect r, uint32_t extent_w, + uint32_t extent_h) { + int max_w = (int)extent_w; + int max_h = (int)extent_h; + if (r.x < 0) { + r.w += r.x; + r.x = 0; + } + if (r.y < 0) { + r.h += r.y; + r.y = 0; + } + if (r.x + r.w > max_w) r.w = max_w - r.x; + if (r.y + r.h > max_h) r.h = max_h - r.y; + if (r.w < 0) r.w = 0; + if (r.h < 0) r.h = 0; + return r; +} + +static VkPreRotatedRect scale_rect_from_swapchain(const VkRenderer* r, VkPreRotatedRect rect, + uint32_t target_w, uint32_t target_h) { + if (target_w == r->swapchain_extent.width && target_h == r->swapchain_extent.height) { + return rect; + } + + float sx = r->swapchain_extent.width > 0 + ? (float)target_w / (float)r->swapchain_extent.width + : 1.0f; + float sy = r->swapchain_extent.height > 0 + ? (float)target_h / (float)r->swapchain_extent.height + : 1.0f; + + rect.x = (int)((float)rect.x * sx + 0.5f); + rect.y = (int)((float)rect.y * sy + 0.5f); + rect.w = (int)((float)rect.w * sx + 0.5f); + rect.h = (int)((float)rect.h * sy + 0.5f); + return rect; +} + +static void push_window_constants(VkCommandBuffer cmd, VkPipelineLayout layout, + const float xform[6], float view_w, float view_h, + float u0, float v0, float u1, float v1, + bool swap_rb) { + struct { + float xform[6]; + float view_size[2]; + float uv_rect[4]; + int32_t swap_rb; + } pc; + memcpy(pc.xform, xform, sizeof(pc.xform)); + pc.view_size[0] = view_w; + pc.view_size[1] = view_h; + pc.uv_rect[0] = u0; + pc.uv_rect[1] = v0; + pc.uv_rect[2] = u1; + pc.uv_rect[3] = v1; + pc.swap_rb = swap_rb ? 1 : 0; + vkCmdPushConstants(cmd, layout, + VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, + 0, sizeof(pc), &pc); +} + +static void compose_xform_for_window(float out[6], const float scene_xform[6], + int wx, int wy, int ww, int wh) { + // Equivalent to GLRenderer.renderDrawable: tmpXForm1 = make(x, y, w, h); tmpXForm1 *= tmpXForm2 + // XForm.set(out, x, y, w, h): [w, 0, 0, h, x, y] + float a[6] = { (float)ww, 0.0f, 0.0f, (float)wh, (float)wx, (float)wy }; + // 2x2 + translation multiply: result = a * scene_xform + out[0] = a[0]*scene_xform[0] + a[1]*scene_xform[2]; + out[1] = a[0]*scene_xform[1] + a[1]*scene_xform[3]; + out[2] = a[2]*scene_xform[0] + a[3]*scene_xform[2]; + out[3] = a[2]*scene_xform[1] + a[3]*scene_xform[3]; + out[4] = a[4]*scene_xform[0] + a[5]*scene_xform[2] + scene_xform[4]; + out[5] = a[4]*scene_xform[1] + a[5]*scene_xform[3] + scene_xform[5]; +} + +static void set_viewport_scissor(VkCommandBuffer cmd, VkRenderer* r, const VkScene* s, + uint32_t target_w, uint32_t target_h) { + if (target_w == 0) target_w = r->swapchain_extent.width; + if (target_h == 0) target_h = r->swapchain_extent.height; + + int vx, vy, vw, vh; + if (s->viewport_set) { + vx = s->viewport_x; + vy = s->viewport_y; + vw = s->viewport_w; + vh = s->viewport_h; + } else { + vx = 0; + vy = 0; + vw = (int)r->surface_extent.width; + vh = (int)r->surface_extent.height; + } + + VkPreRotatedRect vr = transform_rect_for_pretransform( + vx, vy, vw, vh, r->swapchain_extent.width, r->swapchain_extent.height, + r->swapchain_transform); + vr = scale_rect_from_swapchain(r, vr, target_w, target_h); + + VkViewport vp = {0}; + vp.x = (float)vr.x; + vp.y = (float)vr.y; + vp.width = (float)vr.w; + vp.height = (float)vr.h; + vp.minDepth = 0.0f; + vp.maxDepth = 1.0f; + vkCmdSetViewport(cmd, 0, 1, &vp); + + int sx, sy, sw, sh; + if (s->scissor_enabled) { + sx = s->scissor_x; + sy = s->scissor_y; + sw = s->scissor_w; + sh = s->scissor_h; + } else { + sx = 0; + sy = 0; + sw = (int)r->surface_extent.width; + sh = (int)r->surface_extent.height; + } + VkPreRotatedRect sr = transform_rect_for_pretransform( + sx, sy, sw, sh, r->swapchain_extent.width, r->swapchain_extent.height, + r->swapchain_transform); + sr = scale_rect_from_swapchain(r, sr, target_w, target_h); + sr = clamp_rect_to_extent(sr, target_w, target_h); + + VkRect2D sc = {0}; + sc.offset.x = sr.x; + sc.offset.y = sr.y; + sc.extent.width = (uint32_t)sr.w; + sc.extent.height = (uint32_t)sr.h; + vkCmdSetScissor(cmd, 0, 1, &sc); +} + +static void draw_scene_pass(VkRenderer* r, VkCommandBuffer cmd, const VkScene* s, bool offscreen, + uint32_t target_w, uint32_t target_h) { + if (s->screen_width == 0 || s->screen_height == 0) return; + + set_viewport_scissor(cmd, r, s, target_w, target_h); + + // Windows + vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, + offscreen ? r->pipelines.offscreen_window_pipeline + : r->pipelines.window_pipeline); + VkDeviceSize offset = 0; + vkCmdBindVertexBuffers(cmd, 0, 1, &r->quad_vbo, &offset); + + for (uint32_t i = 0; i < s->window_count; i++) { + const VkRenderableWindow* w = &s->windows[i]; + if (!w->texture || !w->texture->ready) continue; + + float xf[6]; + compose_xform_for_window(xf, s->xform, w->x, w->y, w->width, w->height); + float pre_xf[6]; + uint32_t view_w = s->screen_width; + uint32_t view_h = s->screen_height; + transform_xform_for_pretransform(pre_xf, xf, view_w, view_h, r->swapchain_transform); + transformed_view_size(&view_w, &view_h, r->swapchain_transform); + push_window_constants(cmd, r->pipelines.window_layout, pre_xf, + (float)view_w, (float)view_h, + w->u0, w->v0, w->u1, w->v1, s->swap_rb); + vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, + r->pipelines.window_layout, 0, 1, &w->texture->descriptor_set, + 0, NULL); + vkCmdDraw(cmd, 4, 1, 0, 0); + } + + // Cursor + if (s->cursor_visible && s->cursor_texture && s->cursor_texture->ready + && s->cursor_width > 0 && s->cursor_height > 0) { + vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, + offscreen ? r->pipelines.offscreen_cursor_pipeline + : r->pipelines.cursor_pipeline); + float xf[6]; + compose_xform_for_window(xf, s->xform, s->cursor_x, s->cursor_y, + s->cursor_width, s->cursor_height); + float pre_xf[6]; + uint32_t view_w = s->screen_width; + uint32_t view_h = s->screen_height; + transform_xform_for_pretransform(pre_xf, xf, view_w, view_h, r->swapchain_transform); + transformed_view_size(&view_w, &view_h, r->swapchain_transform); + push_window_constants(cmd, r->pipelines.window_layout, pre_xf, + (float)view_w, (float)view_h, + 0.0f, 0.0f, 1.0f, 1.0f, false); + vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, + r->pipelines.window_layout, 0, 1, + &s->cursor_texture->descriptor_set, 0, NULL); + vkCmdDraw(cmd, 4, 1, 0, 0); + } +} + +static void run_effect(VkRenderer* r, VkCommandBuffer cmd, VkEffectSlot* eff, + VkDescriptorSet src_set, uint32_t target_w, uint32_t target_h, + bool offscreen) { + VkPipeline pipe = VK_NULL_HANDLE; + if ((uint32_t)eff->type < VK_EFFECT_COUNT) { + pipe = offscreen ? r->pipelines.offscreen_effect_pipelines[eff->type] + : r->pipelines.effect_pipelines[eff->type]; + } + if (!pipe) { + pipe = offscreen ? r->pipelines.offscreen_blit_pipeline : r->pipelines.blit_pipeline; + } + + vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe); + + VkViewport vp = {0, 0, (float)target_w, (float)target_h, 0.0f, 1.0f}; + VkRect2D sc = {{0, 0}, {target_w, target_h}}; + vkCmdSetViewport(cmd, 0, 1, &vp); + vkCmdSetScissor(cmd, 0, 1, &sc); + + float pc[6]; + pc[0] = (float)target_w; + pc[1] = (float)target_h; + pc[2] = eff->param0; + pc[3] = eff->param1; + pc[4] = eff->param2; + pc[5] = (float)eff->mode; + vkCmdPushConstants(cmd, r->pipelines.effect_layout, VK_SHADER_STAGE_FRAGMENT_BIT, 0, + sizeof(pc), pc); + + vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, + r->pipelines.effect_layout, 0, 1, &src_set, 0, NULL); + vkCmdDraw(cmd, 3, 1, 0, 0); +} + +static bool scene_starts_with_sgsr1(const VkScene* s) { + return s->effect_count > 0 && s->effects[0].type == VK_EFFECT_SGSR1; +} + +static void wait_inflight_frames(VkRenderer* r) { + VkFence fences[VK_FRAMES_IN_FLIGHT]; + uint32_t count = 0; + for (uint32_t i = 0; i < VK_FRAMES_IN_FLIGHT; i++) { + if (r->frames[i].in_flight) fences[count++] = r->frames[i].in_flight; + } + if (count == 0) return; + vkWaitForFences(r->device, count, fences, VK_TRUE, UINT64_MAX); +} + +// SGSR1 upscales the actual X/DRI3 source; ratio changes take effect after restart. +static VkExtent2D compute_sgsr1_source_extent(VkRenderer* r, const VkScene* s) { + VkExtent2D out = r->swapchain_extent; + if (out.width == 0 || out.height == 0 || s->screen_width == 0 || s->screen_height == 0) { + return out; + } + + // Cap SGSR1's source at the X screen so oversized DRI3 buffers do not undo scaling. + uint32_t source_w = s->source_width > 0 && s->source_width < s->screen_width + ? s->source_width : s->screen_width; + uint32_t source_h = s->source_height > 0 && s->source_height < s->screen_height + ? s->source_height : s->screen_height; + transformed_view_size(&source_w, &source_h, r->swapchain_transform); + if (source_w == 0 || source_h == 0) return out; + + VkExtent2D source = { + source_w < out.width ? source_w : out.width, + source_h < out.height ? source_h : out.height, + }; + if (source.width < 1) source.width = 1; + if (source.height < 1) source.height = 1; + return source; +} + +static bool record_and_submit_frame(VkRenderer* r) { + if (!r->surface_ready || !r->swapchain) return false; + + pthread_mutex_lock(&r->render_mutex); + + VkFrame* f = &r->frames[r->frame_index]; + uint32_t grave_slot = r->graveyard_index; + + if (f->in_flight == VK_NULL_HANDLE) { + VkFenceCreateInfo fci = {VK_STRUCTURE_TYPE_FENCE_CREATE_INFO}; + fci.flags = VK_FENCE_CREATE_SIGNALED_BIT; + if (vkCreateFence(r->device, &fci, NULL, &f->in_flight) != VK_SUCCESS) { + f->in_flight = VK_NULL_HANDLE; + VK_LOGE("frame fence unavailable; skipping frame"); + pthread_mutex_unlock(&r->render_mutex); + return false; + } + } + + vkWaitForFences(r->device, 1, &f->in_flight, VK_TRUE, UINT64_MAX); + + // Snapshot the scene under scene_mutex (cheap memcpy of a few KB), then release it so + // scene producers (texture destroys, X server window updates) don't stall behind the + // long acquire/record/submit/present below. render_mutex still serializes us against + // surface lifecycle changes, which keeps the swapchain handles stable for our use. + VkScene snap; + VkTexture** dead = NULL; + uint32_t dead_count = 0; + pthread_mutex_lock(&r->scene_mutex); + if (!r->surface_ready || !r->swapchain || r->swapchain_image_count == 0 + || r->swapchain_extent.width == 0 || r->swapchain_extent.height == 0) { + pthread_mutex_unlock(&r->scene_mutex); + pthread_mutex_unlock(&r->render_mutex); + return false; + } + snap = r->scene; + detach_graveyard_slot(r, grave_slot, &dead, &dead_count); + pthread_mutex_unlock(&r->scene_mutex); + destroy_graveyard_textures(r, dead, dead_count); + + bool wants_sgsr1 = scene_starts_with_sgsr1(&snap); + bool needs_fullres_offscreen = snap.effect_count > 0 + && (!wants_sgsr1 || snap.effect_count > 1); + // offscreen[1] is reached only once the chain writes two distinct offscreen buffers: the + // effect loop's dst_idx starts at 1 for a non-SGSR chain but at 0 for an SGSR1-led one + // (scene goes to the separate SGSR source), so the threshold is >1 normally, >2 for SGSR. + bool needs_second_offscreen = needs_fullres_offscreen + && snap.effect_count > (wants_sgsr1 ? 2u : 1u); + VkExtent2D sgsr1_source_extent = wants_sgsr1 + ? compute_sgsr1_source_extent(r, &snap) + : r->swapchain_extent; + + // Full-res ping-pong targets exist only when the chain needs them (SGSR-only writes its + // low-res source straight to the swapchain). offscreen[1] is grown/freed lazily as the + // chain crosses the threshold above; effect counts change on user action, not per frame, + // so this doesn't thrash. Safe under render_mutex (no concurrent swapchain teardown). + bool offscreen_dims_stale = !r->offscreen_built + || r->offscreen[0].width != r->swapchain_extent.width + || r->offscreen[0].height != r->swapchain_extent.height; + bool second_present = r->offscreen[1].image != VK_NULL_HANDLE; + if (needs_fullres_offscreen) { + if (offscreen_dims_stale || (needs_second_offscreen && !second_present)) { + wait_inflight_frames(r); + create_offscreen(r, r->swapchain_extent.width, r->swapchain_extent.height, + needs_second_offscreen); + } else if (!needs_second_offscreen && second_present) { + wait_inflight_frames(r); // chain no longer reaches offscreen[1]; reclaim it + destroy_one_offscreen(r, &r->offscreen[1]); + } + } else if (r->offscreen_built) { + wait_inflight_frames(r); + destroy_offscreen(r); + } + // Only rebuild SGSR1 source on meaningful dim change. Tiny pixmap-size flicker (off-by- + // one DRI3 jitter, transient resizes) used to thrash this allocation every frame and + // stall the render thread on the full-device wait that preceded it. + int sgsr1_dw = (int)r->sgsr1.width - (int)sgsr1_source_extent.width; + int sgsr1_dh = (int)r->sgsr1.height - (int)sgsr1_source_extent.height; + if (sgsr1_dw < 0) sgsr1_dw = -sgsr1_dw; + if (sgsr1_dh < 0) sgsr1_dh = -sgsr1_dh; + bool sgsr1_dim_changed = r->sgsr1.built && (sgsr1_dw > 4 || sgsr1_dh > 4); + if (wants_sgsr1 && (!r->sgsr1.built || sgsr1_dim_changed)) { + wait_inflight_frames(r); + create_sgsr1_resources(r, sgsr1_source_extent.width, sgsr1_source_extent.height); + } else if (!wants_sgsr1 && r->sgsr1.built) { + wait_inflight_frames(r); + destroy_sgsr1_resources(r); + } + + uint32_t image_index = 0; + VkResult acq = vkAcquireNextImageKHR(r->device, r->swapchain, UINT64_MAX, + f->image_available, VK_NULL_HANDLE, &image_index); + bool recreate_after_present = false; + if (acq == VK_ERROR_OUT_OF_DATE_KHR) { + r->surface_ready = false; + pthread_mutex_lock(&r->queue_mutex); + vkQueueWaitIdle(r->graphics_queue); + pthread_mutex_unlock(&r->queue_mutex); + destroy_swapchain_resources(r); + r->surface_ready = create_swapchain(r, r->surface_extent.width, r->surface_extent.height); + pthread_mutex_unlock(&r->render_mutex); + return false; + } else if (acq == VK_SUBOPTIMAL_KHR) { + if (!r->ignore_suboptimal) recreate_after_present = true; + } else if (acq != VK_SUCCESS) { + VK_LOGE("vkAcquireNextImageKHR -> %d", acq); + pthread_mutex_unlock(&r->render_mutex); + return false; + } + VkSemaphore render_finished = r->swapchain_render_finished[image_index]; + + // Sample the render rate down to the requested fps on a fixed grid, then acquire an encoder + // image to blit this frame into (bounded timeout so a busy encoder skips rather than stalls). + bool rec_this_frame = false; + uint32_t rec_index = 0; + bool rec_due = true; + if (r->rec.active && r->rec.min_interval_ns > 0) { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + uint64_t now = (uint64_t)ts.tv_sec * 1000000000ULL + (uint64_t)ts.tv_nsec; + if (r->rec.last_capture_ns == 0) r->rec.last_capture_ns = now; + if (now < r->rec.last_capture_ns) { + rec_due = false; + } else { + r->rec.last_capture_ns += r->rec.min_interval_ns; + if (now > r->rec.last_capture_ns + 4ULL * r->rec.min_interval_ns) { + r->rec.last_capture_ns = now; // resync after a long stall + } + } + } + if (rec_due && r->rec.active && !r->rec.disabled && r->rec.swapchain) { + VkResult racq = vkAcquireNextImageKHR(r->device, r->rec.swapchain, 16000000ULL, + r->rec.acquire[r->frame_index], VK_NULL_HANDLE, &rec_index); + if (racq == VK_SUCCESS || racq == VK_SUBOPTIMAL_KHR) { + rec_this_frame = true; + if (r->rec.captured++ == 0) VK_LOGI("record: first frame captured (rec_index=%u)", rec_index); + } else if (racq == VK_ERROR_OUT_OF_DATE_KHR) { + r->rec.disabled = true; + VK_LOGW("record: mirror swapchain out of date; capture disabled"); + } else if ((r->rec.skipped++ % 120) == 0) { + VK_LOGW("record: no encoder image ready (code=%d, skipped=%llu)", + racq, (unsigned long long)r->rec.skipped); + } + } + + vkResetFences(r->device, 1, &f->in_flight); + + VkCommandBufferBeginInfo bi = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO}; + bi.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + vkBeginCommandBuffer(f->cmd, &bi); + + bool has_effects = snap.effect_count > 0 && r->offscreen_built; + if (snap.effect_count > 0) { + // Don't enter the effect path if a required target's lazy creation failed (else we'd + // record into a null framebuffer). + bool full_ok = !needs_fullres_offscreen + || (r->offscreen_built + && (!needs_second_offscreen || r->offscreen[1].image != VK_NULL_HANDLE)); + has_effects = full_ok && (!wants_sgsr1 || r->sgsr1.built); + } + + VkClearValue clear = {0}; + clear.color.float32[0] = 0.0f; + clear.color.float32[1] = 0.0f; + clear.color.float32[2] = 0.0f; + clear.color.float32[3] = 1.0f; + + if (has_effects) { + VkOffscreen* scene_target = (wants_sgsr1 && r->sgsr1.built) + ? &r->sgsr1.source + : &r->offscreen[0]; + + // Pass 1: render scene to either full-res effect input or SGSR1's low-res source. + VkRenderPassBeginInfo rpbi = {VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO}; + rpbi.renderPass = r->pipelines.offscreen_pass; + rpbi.framebuffer = scene_target->framebuffer; + rpbi.renderArea.extent.width = scene_target->width; + rpbi.renderArea.extent.height = scene_target->height; + rpbi.clearValueCount = 1; + rpbi.pClearValues = &clear; + vkCmdBeginRenderPass(f->cmd, &rpbi, VK_SUBPASS_CONTENTS_INLINE); + draw_scene_pass(r, f->cmd, &snap, true, + scene_target->width, scene_target->height); + vkCmdEndRenderPass(f->cmd); + + // Effect chain: source descriptor moves through ping-pong buffers. When SGSR1 is + // first, the first source is low-res and SGSR1 writes full-res output. + VkOffscreen* src_offscreen = scene_target; + uint32_t dst_idx = (scene_target == &r->offscreen[0]) ? 1u : 0u; + for (uint32_t i = 0; i < snap.effect_count; i++) { + bool last = (i == snap.effect_count - 1); + VkEffectSlot* eff = &snap.effects[i]; + + if (last) { + rpbi.renderPass = r->pipelines.swapchain_pass; + rpbi.framebuffer = r->swapchain_framebuffers[image_index]; + rpbi.renderArea.extent = r->swapchain_extent; + } else { + rpbi.renderPass = r->pipelines.offscreen_pass; + rpbi.framebuffer = r->offscreen[dst_idx].framebuffer; + rpbi.renderArea.extent.width = r->offscreen[dst_idx].width; + rpbi.renderArea.extent.height = r->offscreen[dst_idx].height; + } + vkCmdBeginRenderPass(f->cmd, &rpbi, VK_SUBPASS_CONTENTS_INLINE); + uint32_t target_w = last ? r->swapchain_extent.width : rpbi.renderArea.extent.width; + uint32_t target_h = last ? r->swapchain_extent.height : rpbi.renderArea.extent.height; + run_effect(r, f->cmd, eff, src_offscreen->descriptor_set, target_w, target_h, !last); + vkCmdEndRenderPass(f->cmd); + if (!last) { + src_offscreen = &r->offscreen[dst_idx]; + dst_idx ^= 1u; + } + } + } else { + VkRenderPassBeginInfo rpbi = {VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO}; + rpbi.renderPass = r->pipelines.swapchain_pass; + rpbi.framebuffer = r->swapchain_framebuffers[image_index]; + rpbi.renderArea.extent = r->swapchain_extent; + rpbi.clearValueCount = 1; + rpbi.pClearValues = &clear; + vkCmdBeginRenderPass(f->cmd, &rpbi, VK_SUBPASS_CONTENTS_INLINE); + draw_scene_pass(r, f->cmd, &snap, false, + r->swapchain_extent.width, r->swapchain_extent.height); + vkCmdEndRenderPass(f->cmd); + } + + // Blit the final composited image (in PRESENT_SRC after the render pass) into the encoder image. + if (rec_this_frame) { + VkImage disp_img = r->swapchain_images[image_index]; + VkImage rec_img = r->rec.images[rec_index]; + vkr_image_barrier(f->cmd, disp_img, + VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT); + vkr_image_barrier(f->cmd, rec_img, + VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, VK_ACCESS_TRANSFER_WRITE_BIT); + + VkImageBlit blit = {0}; + blit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + blit.srcSubresource.layerCount = 1; + blit.srcOffsets[1].x = (int32_t)r->swapchain_extent.width; + blit.srcOffsets[1].y = (int32_t)r->swapchain_extent.height; + blit.srcOffsets[1].z = 1; + blit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + blit.dstSubresource.layerCount = 1; + blit.dstOffsets[1].x = (int32_t)r->rec.extent.width; + blit.dstOffsets[1].y = (int32_t)r->rec.extent.height; + blit.dstOffsets[1].z = 1; + vkCmdBlitImage(f->cmd, disp_img, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + rec_img, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &blit, VK_FILTER_LINEAR); + + vkr_image_barrier(f->cmd, disp_img, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + VK_ACCESS_TRANSFER_READ_BIT, 0); + + // Record UI: blend the overlay over the game, else just transition to presentable. + bool do_ui = r->rec.ui_enabled && r->rec.fb_built && r->rec.ui_pipeline + && r->rec.ui_texture && r->rec.ui_texture->ready + && rec_index < r->rec.image_count && r->rec.framebuffers[rec_index]; + if (do_ui) { + vkr_image_barrier(f->cmd, rec_img, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + VK_ACCESS_TRANSFER_WRITE_BIT, + VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT); + VkRenderPassBeginInfo rp = {VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO}; + rp.renderPass = r->rec.ui_pass; + rp.framebuffer = r->rec.framebuffers[rec_index]; + rp.renderArea.extent = r->rec.extent; + vkCmdBeginRenderPass(f->cmd, &rp, VK_SUBPASS_CONTENTS_INLINE); + vkCmdBindPipeline(f->cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, r->rec.ui_pipeline); + VkViewport vp = {0, 0, (float)r->rec.extent.width, (float)r->rec.extent.height, 0.0f, 1.0f}; + VkRect2D scr = {{0, 0}, {r->rec.extent.width, r->rec.extent.height}}; + vkCmdSetViewport(f->cmd, 0, 1, &vp); + vkCmdSetScissor(f->cmd, 0, 1, &scr); + float pc[6] = {(float)r->rec.extent.width, (float)r->rec.extent.height, 0.0f, 0.0f, 0.0f, 0.0f}; + vkCmdPushConstants(f->cmd, r->pipelines.effect_layout, VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(pc), pc); + vkCmdBindDescriptorSets(f->cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, r->pipelines.effect_layout, + 0, 1, &r->rec.ui_texture->descriptor_set, 0, NULL); + vkCmdDraw(f->cmd, 3, 1, 0, 0); + vkCmdEndRenderPass(f->cmd); // leaves rec image in PRESENT_SRC + } else { + vkr_image_barrier(f->cmd, rec_img, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + VK_ACCESS_TRANSFER_WRITE_BIT, 0); + } + } + + vkEndCommandBuffer(f->cmd); + + // The mirror's acquire/present-ready semaphores are appended only when capturing this frame. + VkPipelineStageFlags wait_stages[2] = { + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT }; + VkSemaphore wait_sems[2] = { f->image_available, r->rec.acquire[r->frame_index] }; + VkSemaphore signal_sems[2] = { + render_finished, rec_this_frame ? r->rec.present_ready[rec_index] : VK_NULL_HANDLE }; + VkSubmitInfo si = {VK_STRUCTURE_TYPE_SUBMIT_INFO}; + si.waitSemaphoreCount = rec_this_frame ? 2u : 1u; + si.pWaitSemaphores = wait_sems; + si.pWaitDstStageMask = wait_stages; + si.commandBufferCount = 1; + si.pCommandBuffers = &f->cmd; + si.signalSemaphoreCount = rec_this_frame ? 2u : 1u; + si.pSignalSemaphores = signal_sems; + + pthread_mutex_lock(&r->queue_mutex); + VkResult sr = vkQueueSubmit(r->graphics_queue, 1, &si, f->in_flight); + pthread_mutex_unlock(&r->queue_mutex); + if (sr != VK_SUCCESS) { + VK_LOGE("vkQueueSubmit -> %d", sr); + // The frame fence was reset before submit. If submit fails, nothing will ever signal + // it, so restore a signaled fence before returning or the next frame can block forever. + vkDestroyFence(r->device, f->in_flight, NULL); + VkFenceCreateInfo rfi = {VK_STRUCTURE_TYPE_FENCE_CREATE_INFO}; + rfi.flags = VK_FENCE_CREATE_SIGNALED_BIT; + if (vkCreateFence(r->device, &rfi, NULL, &f->in_flight) != VK_SUCCESS) { + f->in_flight = VK_NULL_HANDLE; + VK_LOGE("Failed to recreate frame fence after submit failure"); + } + pthread_mutex_unlock(&r->render_mutex); + return false; + } + + VkPresentInfoKHR pi = {VK_STRUCTURE_TYPE_PRESENT_INFO_KHR}; + pi.waitSemaphoreCount = 1; + pi.pWaitSemaphores = &render_finished; + pi.swapchainCount = 1; + pi.pSwapchains = &r->swapchain; + pi.pImageIndices = &image_index; + + pthread_mutex_lock(&r->queue_mutex); + VkResult pr = vkQueuePresentKHR(r->graphics_queue, &pi); + // Present the mirror separately so its result doesn't disturb the display recreate logic below. + if (rec_this_frame) { + VkPresentInfoKHR rpi = {VK_STRUCTURE_TYPE_PRESENT_INFO_KHR}; + rpi.waitSemaphoreCount = 1; + rpi.pWaitSemaphores = &r->rec.present_ready[rec_index]; + rpi.swapchainCount = 1; + rpi.pSwapchains = &r->rec.swapchain; + rpi.pImageIndices = &rec_index; + VkResult rpr = vkQueuePresentKHR(r->graphics_queue, &rpi); + if (rpr != VK_SUCCESS && rpr != VK_SUBOPTIMAL_KHR) { + r->rec.disabled = true; + VK_LOGW("record: mirror present failed (%d); capture disabled", rpr); + } + } + pthread_mutex_unlock(&r->queue_mutex); + + bool present_suboptimal = (pr == VK_SUBOPTIMAL_KHR) && !r->ignore_suboptimal; + if (recreate_after_present || pr == VK_ERROR_OUT_OF_DATE_KHR || present_suboptimal) { + r->surface_ready = false; + pthread_mutex_lock(&r->queue_mutex); + vkQueueWaitIdle(r->graphics_queue); + pthread_mutex_unlock(&r->queue_mutex); + destroy_swapchain_resources(r); + r->surface_ready = create_swapchain(r, r->surface_extent.width, r->surface_extent.height); + } + + pthread_mutex_unlock(&r->render_mutex); + + r->frame_index = (r->frame_index + 1) % VK_FRAMES_IN_FLIGHT; + r->graveyard_index = (r->graveyard_index + 1) % (VK_FRAMES_IN_FLIGHT + 1); + + return true; +} + +// ============================================================ +// JNI entry points +// ============================================================ + +#define JNI_FN(name) Java_com_winlator_cmod_runtime_display_renderer_VulkanRenderer_##name + +JNIEXPORT jlong JNICALL JNI_FN(nativeCreate)(JNIEnv* env, jclass clazz, + jboolean enableValidationLayers, + jstring driverName, + jobject context) { + (void)clazz; + VkRenderer* r = calloc(1, sizeof(VkRenderer)); + if (!r) return 0; + r->target_present_mode = VK_PRESENT_MODE_FIFO_KHR; + r->validation_enabled = (enableValidationLayers == JNI_TRUE); + pthread_mutex_init(&r->scene_mutex, NULL); + pthread_mutex_init(&r->queue_mutex, NULL); + pthread_mutex_init(&r->texture_mutex, NULL); + pthread_mutex_init(&r->render_mutex, NULL); + pthread_mutex_init(&r->descriptor_mutex, NULL); + + const char* driver_name_c = NULL; + if (driverName != NULL) driver_name_c = (*env)->GetStringUTFChars(env, driverName, NULL); + r->vulkan_handle = winlator_open_vulkan(env, context, driver_name_c); + if (driver_name_c != NULL) (*env)->ReleaseStringUTFChars(env, driverName, driver_name_c); + + if (!r->vulkan_handle) { + VK_LOGE("winlator_open_vulkan returned NULL"); + goto fail; + } + if (!vkd_init(r->vulkan_handle)) { + VK_LOGE("vkd_init failed"); + goto fail; + } + + if (!create_instance(r)) goto fail; + if (!pick_physical_device(r)) goto fail; + if (!create_device(r)) goto fail; + query_device_caps(r); + if (!create_command_pool(r)) goto fail; + if (!create_descriptor_pool(r, r->caps.descriptor_pool_capacity)) goto fail; + if (!create_quad_vbo(r)) goto fail; + if (!vkr_create_sampler(r, VK_NULL_HANDLE, &r->shared_sampler)) goto fail; + { + VkSamplerCreateInfo nsi = {VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO}; + nsi.magFilter = VK_FILTER_NEAREST; + nsi.minFilter = VK_FILTER_NEAREST; + nsi.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + nsi.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + nsi.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + nsi.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK; + nsi.unnormalizedCoordinates = VK_FALSE; + nsi.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; + if (vkCreateSampler(r->device, &nsi, NULL, &r->shared_sampler_nearest) != VK_SUCCESS) goto fail; + } + if (r->ext_filter_cubic) { + VkSamplerCreateInfo csi = {VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO}; + csi.magFilter = VK_FILTER_CUBIC_EXT; + csi.minFilter = VK_FILTER_LINEAR; + csi.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + csi.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + csi.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + csi.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK; + csi.unnormalizedCoordinates = VK_FALSE; + csi.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; + if (vkCreateSampler(r->device, &csi, NULL, &r->shared_sampler_cubic) != VK_SUCCESS) { + r->shared_sampler_cubic = VK_NULL_HANDLE; + r->ext_filter_cubic = false; + } + } + if (!vkr_staging_pool_init(r)) goto fail; + vkr_suballoc_init(r); + + r->initialized = true; + return (jlong)(intptr_t)r; + +fail: + VK_LOGE("VulkanRenderer init failed"); + vkr_staging_pool_destroy(r); + vkr_suballoc_destroy(r); // safe if never initialized + if (r->shared_sampler) vkDestroySampler(r->device, r->shared_sampler, NULL); + if (r->shared_sampler_nearest) vkDestroySampler(r->device, r->shared_sampler_nearest, NULL); + if (r->shared_sampler_cubic) vkDestroySampler(r->device, r->shared_sampler_cubic, NULL); + if (r->cmd_pool) vkDestroyCommandPool(r->device, r->cmd_pool, NULL); + free(r->descriptor_free_list); r->descriptor_free_list = NULL; r->descriptor_free_count = 0; + if (r->descriptor_pool) vkDestroyDescriptorPool(r->device, r->descriptor_pool, NULL); + if (r->device) vkDestroyDevice(r->device, NULL); + destroy_debug_messenger(r); + if (r->instance) vkDestroyInstance(r->instance, NULL); + vkd_unload(); + if (r->vulkan_handle) { dlclose(r->vulkan_handle); r->vulkan_handle = NULL; } + pthread_mutex_destroy(&r->scene_mutex); + pthread_mutex_destroy(&r->queue_mutex); + pthread_mutex_destroy(&r->texture_mutex); + pthread_mutex_destroy(&r->render_mutex); + pthread_mutex_destroy(&r->descriptor_mutex); + free(r); + return 0; +} + +JNIEXPORT void JNICALL JNI_FN(nativeDestroy)(JNIEnv* env, jclass clazz, jlong handle) { + (void)env; (void)clazz; + VkRenderer* r = (VkRenderer*)(intptr_t)handle; + if (!r) return; + + if (r->device) vkDeviceWaitIdle(r->device); + + // Drain any in-flight uploads and tear down the staging pool before destroying images. + vkr_staging_pool_destroy(r); + + for (uint32_t i = 0; i < VK_FRAMES_IN_FLIGHT + 1; i++) { + VkTexture** dead = NULL; + uint32_t dead_count = 0; + detach_graveyard_slot(r, i, &dead, &dead_count); + destroy_graveyard_textures(r, dead, dead_count); + } + vkr_texture_destroy_all_live(r); + free(r->live_textures); + + // All textures gone -> all spans returned. Reclaim pooled blocks + batch scratch. + vkr_suballoc_destroy(r); + free(r->batch_entry_scratch); + free(r->batch_prepared_scratch); + + destroy_record_swapchain(r); + destroy_sgsr1_resources(r); + destroy_offscreen(r); + destroy_swapchain(r); + destroy_pipelines(r); + destroy_quad_vbo(r); + + for (uint32_t i = 0; i < VK_FRAMES_IN_FLIGHT; i++) { + VkFrame* f = &r->frames[i]; + if (f->image_available) vkDestroySemaphore(r->device, f->image_available, NULL); + if (f->in_flight) vkDestroyFence(r->device, f->in_flight, NULL); + } + + if (r->shared_sampler) vkDestroySampler(r->device, r->shared_sampler, NULL); + if (r->shared_sampler_nearest) vkDestroySampler(r->device, r->shared_sampler_nearest, NULL); + if (r->shared_sampler_cubic) vkDestroySampler(r->device, r->shared_sampler_cubic, NULL); + if (r->cmd_pool) vkDestroyCommandPool(r->device, r->cmd_pool, NULL); + free(r->descriptor_free_list); r->descriptor_free_list = NULL; r->descriptor_free_count = 0; + if (r->descriptor_pool) vkDestroyDescriptorPool(r->device, r->descriptor_pool, NULL); + if (r->surface) vkDestroySurfaceKHR(r->instance, r->surface, NULL); + if (r->anw) ANativeWindow_release(r->anw); + if (r->device) vkDestroyDevice(r->device, NULL); + destroy_debug_messenger(r); + if (r->instance)vkDestroyInstance(r->instance, NULL); + + // Clear dispatch BEFORE dlclose so a stray call from another thread faults on NULL + // rather than jumping into freed library memory. + vkd_unload(); + if (r->vulkan_handle) { dlclose(r->vulkan_handle); r->vulkan_handle = NULL; } + + pthread_mutex_destroy(&r->scene_mutex); + pthread_mutex_destroy(&r->queue_mutex); + pthread_mutex_destroy(&r->texture_mutex); + pthread_mutex_destroy(&r->render_mutex); + pthread_mutex_destroy(&r->descriptor_mutex); + free(r); +} + +// Lifecycle helper: take render_mutex (waits for any in-flight render to finish), then +// briefly take scene_mutex to clear surface_ready so producers see a consistent state. +// Returns with only render_mutex held; caller must release it. +static void lifecycle_begin(VkRenderer* r) { + pthread_mutex_lock(&r->render_mutex); + pthread_mutex_lock(&r->scene_mutex); + r->surface_ready = false; + pthread_mutex_unlock(&r->scene_mutex); +} + +JNIEXPORT void JNICALL JNI_FN(nativeSurfaceCreated)(JNIEnv* env, jclass clazz, jlong handle, jobject surface) { + (void)clazz; + VkRenderer* r = (VkRenderer*)(intptr_t)handle; + if (!r) return; + + lifecycle_begin(r); + + if (r->surface) { + vkDeviceWaitIdle(r->device); + destroy_sgsr1_resources(r); + destroy_offscreen(r); + destroy_swapchain(r); + vkDestroySurfaceKHR(r->instance, r->surface, NULL); + r->surface = VK_NULL_HANDLE; + } + if (r->anw) { + ANativeWindow_release(r->anw); + r->anw = NULL; + } + + r->anw = ANativeWindow_fromSurface(env, surface); + if (!r->anw) { + VK_LOGE("ANativeWindow_fromSurface failed"); + pthread_mutex_unlock(&r->render_mutex); + return; + } + + VkAndroidSurfaceCreateInfoKHR aci = {VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR}; + aci.window = r->anw; + if (vkCreateAndroidSurfaceKHR(r->instance, &aci, NULL, &r->surface) != VK_SUCCESS) { + VK_LOGE("vkCreateAndroidSurfaceKHR failed"); + ANativeWindow_release(r->anw); + r->anw = NULL; + pthread_mutex_unlock(&r->render_mutex); + return; + } + + VkBool32 supported = VK_FALSE; + vkGetPhysicalDeviceSurfaceSupportKHR(r->physical_device, r->graphics_queue_family, + r->surface, &supported); + if (!supported) { + VK_LOGE("Selected queue family does not support presentation"); + } + + // Wait for SurfaceHolder.surfaceChanged() before creating the swapchain. On Android + // the surface can be created while the activity is still completing a rotation, so + // creating it here can lock in stale portrait dimensions for a landscape launch. + pthread_mutex_unlock(&r->render_mutex); +} + +JNIEXPORT void JNICALL JNI_FN(nativeSurfaceChanged)(JNIEnv* env, jclass clazz, jlong handle, jint w, jint h) { + (void)env; (void)clazz; + VkRenderer* r = (VkRenderer*)(intptr_t)handle; + if (!r || !r->surface) return; + + lifecycle_begin(r); + vkDeviceWaitIdle(r->device); + destroy_sgsr1_resources(r); + destroy_offscreen(r); + destroy_swapchain(r); + if (!create_swapchain(r, (uint32_t)w, (uint32_t)h)) { + VK_LOGE("Swapchain re-create failed in nativeSurfaceChanged"); + } else { + pthread_mutex_lock(&r->scene_mutex); + r->surface_ready = true; + pthread_mutex_unlock(&r->scene_mutex); + } + pthread_mutex_unlock(&r->render_mutex); +} + +JNIEXPORT jboolean JNICALL JNI_FN(nativeStartRecording)(JNIEnv* env, jclass clazz, jlong handle, jobject surface, jint fps, jboolean recordUI) { + (void)clazz; + VkRenderer* r = (VkRenderer*)(intptr_t)handle; + if (!r || !surface) return JNI_FALSE; + + lifecycle_begin(r); + + jboolean ok = JNI_FALSE; + do { + if (r->rec.active) { ok = JNI_TRUE; break; } + + ANativeWindow* anw = ANativeWindow_fromSurface(env, surface); + if (!anw) { VK_LOGE("record: ANativeWindow_fromSurface failed"); break; } + r->rec.anw = anw; + r->rec.ui_enabled = (recordUI == JNI_TRUE); + + if (r->device) vkDeviceWaitIdle(r->device); + + // Recreate the display swapchain so its images carry TRANSFER_SRC (the blit source). + r->record_blit_src = true; + if (r->surface) { + destroy_sgsr1_resources(r); + destroy_offscreen(r); + destroy_swapchain(r); + if (!create_swapchain(r, r->surface_extent.width, r->surface_extent.height)) { + VK_LOGE("record: display swapchain recreate failed"); + r->record_blit_src = false; + ANativeWindow_release(r->rec.anw); r->rec.anw = NULL; + break; + } + } + + if (!create_record_swapchain(r)) { + VK_LOGE("record: create_record_swapchain failed; recording will not capture"); + r->record_blit_src = false; // revert the display swapchain to its plain usage + if (r->surface) { + destroy_sgsr1_resources(r); + destroy_offscreen(r); + destroy_swapchain(r); + create_swapchain(r, r->surface_extent.width, r->surface_extent.height); + } + break; + } + + r->rec.active = true; + r->rec.disabled = false; + r->rec.captured = 0; + r->rec.skipped = 0; + r->rec.min_interval_ns = (fps > 0) ? (uint64_t)(1000000000.0 / (double)fps) : 0; + r->rec.last_capture_ns = 0; + ok = JNI_TRUE; + VK_LOGI("record: started (display %ux%u -> mirror %ux%u @%dfps)", + r->swapchain_extent.width, r->swapchain_extent.height, + r->rec.extent.width, r->rec.extent.height, (int)fps); + } while (0); + + pthread_mutex_lock(&r->scene_mutex); + r->surface_ready = (r->swapchain != VK_NULL_HANDLE); + pthread_mutex_unlock(&r->scene_mutex); + pthread_mutex_unlock(&r->render_mutex); + return ok; +} + +JNIEXPORT void JNICALL JNI_FN(nativeStopRecording)(JNIEnv* env, jclass clazz, jlong handle) { + (void)env; (void)clazz; + VkRenderer* r = (VkRenderer*)(intptr_t)handle; + if (!r) return; + + lifecycle_begin(r); + if (r->device) vkDeviceWaitIdle(r->device); + + destroy_record_swapchain(r); + + if (r->record_blit_src) { + r->record_blit_src = false; + if (r->surface) { + destroy_sgsr1_resources(r); + destroy_offscreen(r); + destroy_swapchain(r); + if (!create_swapchain(r, r->surface_extent.width, r->surface_extent.height)) { + VK_LOGE("record: display swapchain restore failed after stop"); + } + } + } + + pthread_mutex_lock(&r->scene_mutex); + r->surface_ready = (r->swapchain != VK_NULL_HANDLE); + pthread_mutex_unlock(&r->scene_mutex); + pthread_mutex_unlock(&r->render_mutex); +} + +// Upload the latest overlay snapshot (direct ByteBuffer of BGRA pixels) for the Record UI composite. +JNIEXPORT void JNICALL JNI_FN(nativeUpdateRecordUITexture)(JNIEnv* env, jclass clazz, jlong handle, + jobject buffer, jint w, jint h) { + (void)clazz; + VkRenderer* r = (VkRenderer*)(intptr_t)handle; + if (!r || !buffer || w <= 0 || h <= 0) return; + void* data = (*env)->GetDirectBufferAddress(env, buffer); + jlong cap = (*env)->GetDirectBufferCapacity(env, buffer); + if (!data || cap < (jlong)w * (jlong)h * 4) return; + + pthread_mutex_lock(&r->render_mutex); + if (r->rec.active && r->rec.ui_enabled) { + size_t bytes = (size_t)w * (size_t)h * 4u; + if (r->rec.ui_texture == NULL + || r->rec.ui_texture->width != (uint32_t)w + || r->rec.ui_texture->height != (uint32_t)h) { + if (r->rec.ui_texture) { vkr_texture_destroy(r, r->rec.ui_texture); r->rec.ui_texture = NULL; } + r->rec.ui_texture = vkr_texture_create_uploaded(r, (uint32_t)w, (uint32_t)h, data, bytes, (uint32_t)w); + } else { + vkr_texture_update(r, r->rec.ui_texture, (uint32_t)w, (uint32_t)h, data, bytes, + (uint32_t)w, 0, 0, (uint32_t)w, (uint32_t)h); + } + } + pthread_mutex_unlock(&r->render_mutex); +} + +// Dimensions of the composited image (swapchain extent), which differ from the SurfaceView size +// under display rotation; the encoder is sized to these so the capture isn't squished. +JNIEXPORT jint JNICALL JNI_FN(nativeGetRecordWidth)(JNIEnv* env, jclass clazz, jlong handle) { + (void)env; (void)clazz; + VkRenderer* r = (VkRenderer*)(intptr_t)handle; + return (r != NULL) ? (jint)r->swapchain_extent.width : 0; +} + +JNIEXPORT jint JNICALL JNI_FN(nativeGetRecordHeight)(JNIEnv* env, jclass clazz, jlong handle) { + (void)env; (void)clazz; + VkRenderer* r = (VkRenderer*)(intptr_t)handle; + return (r != NULL) ? (jint)r->swapchain_extent.height : 0; +} + +// Clockwise degrees to rotate the recording for upright playback (undoes the display preTransform). +JNIEXPORT jint JNICALL JNI_FN(nativeGetRecordOrientationHint)(JNIEnv* env, jclass clazz, jlong handle) { + (void)env; (void)clazz; + VkRenderer* r = (VkRenderer*)(intptr_t)handle; + if (r == NULL) return 0; + switch (r->swapchain_transform) { + case VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR: return 270; + case VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR: return 180; + case VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR: return 90; + default: return 0; + } +} + +JNIEXPORT void JNICALL JNI_FN(nativeSurfaceDestroyed)(JNIEnv* env, jclass clazz, jlong handle) { + (void)env; (void)clazz; + VkRenderer* r = (VkRenderer*)(intptr_t)handle; + if (!r) return; + + lifecycle_begin(r); + + if (r->device) vkDeviceWaitIdle(r->device); + destroy_sgsr1_resources(r); + destroy_offscreen(r); + destroy_swapchain(r); + if (r->surface) { + vkDestroySurfaceKHR(r->instance, r->surface, NULL); + r->surface = VK_NULL_HANDLE; + } + if (r->anw) { + ANativeWindow_release(r->anw); + r->anw = NULL; + } + pthread_mutex_unlock(&r->render_mutex); +} + +JNIEXPORT jboolean JNICALL JNI_FN(nativeRenderFrame)(JNIEnv* env, jclass clazz, jlong handle) { + (void)env; (void)clazz; + VkRenderer* r = (VkRenderer*)(intptr_t)handle; + if (!r || !r->surface_ready) return JNI_FALSE; + return record_and_submit_frame(r) ? JNI_TRUE : JNI_FALSE; +} + +// Scene byte buffer layout (must mirror VulkanRenderer.java offsets). Native-endian, packed. +// Using a single direct ByteBuffer instead of 6 separate jarray params avoids per-frame JNI +// critical regions (each ~3-8µs on ART) and the temporary array shadow allocations they +// trigger. +#define SCENE_OFF_CURSOR_HANDLE 0 +#define SCENE_OFF_WINDOW_HANDLES 8 /* int64 × VK_MAX_RENDERABLE_WINDOWS */ +#define SCENE_OFF_WINDOW_COUNT 520 +#define SCENE_OFF_CURSOR_VISIBLE 524 +#define SCENE_OFF_CURSOR_GEOM 528 /* int32 × 4 */ +#define SCENE_OFF_XFORM 544 /* float32 × 6 */ +#define SCENE_OFF_VIEWPORT 568 /* int32 × 4 */ +#define SCENE_OFF_SCISSOR_ENABLED 584 +#define SCENE_OFF_SCISSOR 588 /* int32 × 4 */ +#define SCENE_OFF_SCREEN_W 604 +#define SCENE_OFF_SCREEN_H 608 +#define SCENE_OFF_EFFECT_COUNT 612 +#define SCENE_OFF_EFFECT_TYPES 616 /* int32 × VK_MAX_EFFECTS */ +#define SCENE_OFF_EFFECT_PARAMS 648 /* float32 × VK_MAX_EFFECTS × 4 */ +#define SCENE_OFF_WINDOW_GEOM 776 /* int32 × VK_MAX_RENDERABLE_WINDOWS × 4 */ +#define SCENE_OFF_WINDOW_UV 1800 /* float32 × VK_MAX_RENDERABLE_WINDOWS × 4 */ +#define SCENE_OFF_SWAP_RB 2824 +#define SCENE_OFF_SOURCE_W 2828 +#define SCENE_OFF_SOURCE_H 2832 +#define SCENE_BUF_SIZE 2836 + +JNIEXPORT void JNICALL JNI_FN(nativeSetScene)(JNIEnv* env, jclass clazz, jlong handle, + jobject sceneBuf) +{ + (void)clazz; + VkRenderer* r = (VkRenderer*)(intptr_t)handle; + if (!r || !sceneBuf) return; + + const uint8_t* base = (const uint8_t*)(*env)->GetDirectBufferAddress(env, sceneBuf); + if (!base) return; + // Defensive: a future Java-side layout change with a stale SCENE_BUF_SIZE would silently + // read past the buffer here. GetDirectBufferCapacity is one JNI call; cheap insurance. + jlong cap = (*env)->GetDirectBufferCapacity(env, sceneBuf); + if (cap < SCENE_BUF_SIZE) { + VK_LOGE("nativeSetScene: scene buffer too small (%lld < %d)", + (long long)cap, SCENE_BUF_SIZE); + return; + } + + pthread_mutex_lock(&r->scene_mutex); + VkScene* s = &r->scene; + + // Windows + int32_t window_count; + memcpy(&window_count, base + SCENE_OFF_WINDOW_COUNT, sizeof(int32_t)); + if (window_count < 0) window_count = 0; + if (window_count > VK_MAX_RENDERABLE_WINDOWS) window_count = VK_MAX_RENDERABLE_WINDOWS; + s->window_count = (uint32_t)window_count; + for (int32_t i = 0; i < window_count; i++) { + VkRenderableWindow* w = &s->windows[i]; + int64_t h64; + memcpy(&h64, base + SCENE_OFF_WINDOW_HANDLES + (size_t)i * 8, sizeof(int64_t)); + w->texture = (VkTexture*)(intptr_t)h64; + int32_t g[4]; + memcpy(g, base + SCENE_OFF_WINDOW_GEOM + (size_t)i * 16, sizeof(g)); + w->x = g[0]; + w->y = g[1]; + w->width = (uint32_t)g[2]; + w->height = (uint32_t)g[3]; + float uv[4]; + memcpy(uv, base + SCENE_OFF_WINDOW_UV + (size_t)i * 16, sizeof(uv)); + w->u0 = uv[0]; + w->v0 = uv[1]; + w->u1 = uv[2]; + w->v1 = uv[3]; + w->direct_scanout = false; + } + + // Cursor + int32_t cursor_visible; + memcpy(&cursor_visible, base + SCENE_OFF_CURSOR_VISIBLE, sizeof(int32_t)); + s->cursor_visible = cursor_visible != 0; + int64_t cursor_h64; + memcpy(&cursor_h64, base + SCENE_OFF_CURSOR_HANDLE, sizeof(int64_t)); + s->cursor_texture = (VkTexture*)(intptr_t)cursor_h64; + int32_t cg[4]; + memcpy(cg, base + SCENE_OFF_CURSOR_GEOM, sizeof(cg)); + s->cursor_x = cg[0]; + s->cursor_y = cg[1]; + s->cursor_width = (uint32_t)cg[2]; + s->cursor_height = (uint32_t)cg[3]; + + // XForm + memcpy(s->xform, base + SCENE_OFF_XFORM, sizeof(float) * 6); + + // Viewport (set bit derived from positive dims, matching the previous semantics) + int32_t vp[4]; + memcpy(vp, base + SCENE_OFF_VIEWPORT, sizeof(vp)); + s->viewport_x = vp[0]; + s->viewport_y = vp[1]; + s->viewport_w = vp[2]; + s->viewport_h = vp[3]; + s->viewport_set = (vp[2] > 0 && vp[3] > 0); + + // Scissor — Java sends an explicit enabled flag (replaces the old "scissor==null" check) + int32_t scissor_enabled; + memcpy(&scissor_enabled, base + SCENE_OFF_SCISSOR_ENABLED, sizeof(int32_t)); + int32_t sc[4]; + memcpy(sc, base + SCENE_OFF_SCISSOR, sizeof(sc)); + s->scissor_x = sc[0]; + s->scissor_y = sc[1]; + s->scissor_w = sc[2]; + s->scissor_h = sc[3]; + s->scissor_enabled = (scissor_enabled != 0) && (sc[2] > 0) && (sc[3] > 0); + + int32_t screen_w, screen_h; + memcpy(&screen_w, base + SCENE_OFF_SCREEN_W, sizeof(int32_t)); + memcpy(&screen_h, base + SCENE_OFF_SCREEN_H, sizeof(int32_t)); + s->screen_width = (uint32_t)screen_w; + s->screen_height = (uint32_t)screen_h; + int32_t swap_rb; + memcpy(&swap_rb, base + SCENE_OFF_SWAP_RB, sizeof(int32_t)); + s->swap_rb = swap_rb != 0; + int32_t source_w, source_h; + memcpy(&source_w, base + SCENE_OFF_SOURCE_W, sizeof(int32_t)); + memcpy(&source_h, base + SCENE_OFF_SOURCE_H, sizeof(int32_t)); + s->source_width = source_w > 0 ? (uint32_t)source_w : 0; + s->source_height = source_h > 0 ? (uint32_t)source_h : 0; + + // Effects + int32_t effect_count; + memcpy(&effect_count, base + SCENE_OFF_EFFECT_COUNT, sizeof(int32_t)); + if (effect_count < 0) effect_count = 0; + if (effect_count > VK_MAX_EFFECTS) effect_count = VK_MAX_EFFECTS; + s->effect_count = (uint32_t)effect_count; + for (int32_t i = 0; i < effect_count; i++) { + int32_t etype; + memcpy(&etype, base + SCENE_OFF_EFFECT_TYPES + (size_t)i * 4, sizeof(int32_t)); + s->effects[i].type = (VkEffectType)etype; + float ep[4]; + memcpy(ep, base + SCENE_OFF_EFFECT_PARAMS + (size_t)i * 16, sizeof(ep)); + s->effects[i].mode = (int)ep[0]; + s->effects[i].param0 = ep[1]; + s->effects[i].param1 = ep[2]; + s->effects[i].param2 = ep[3]; + } + + pthread_mutex_unlock(&r->scene_mutex); + // Offscreen rebuild is handled in record_and_submit_frame under render_mutex; nothing + // here needs to touch swapchain-tied resources. +} + +// FPS pacing is enforced by delaying PresentIdleNotify (buffer-release back-pressure) on the +// Present extension's pacer thread, plus the swapchain present mode + Choreographer-coalesced +// render requests. The compositor used to run its own sleep+busy-spin here too, which +// duplicated the pacing and burned CPU; this entry point is kept as a no-op for Java-side +// ABI compatibility. +JNIEXPORT void JNICALL JNI_FN(nativeSetFpsLimit)(JNIEnv* env, jclass clazz, jlong handle, jint fps) { + (void)env; (void)clazz; (void)handle; (void)fps; +} + +// 0=off/linear, 1=nearest, 2=linear, 3=bicubic (linear fallback when cubic unsupported). +JNIEXPORT void JNICALL JNI_FN(nativeSetScaleFilter)(JNIEnv* env, jclass clazz, jlong handle, jint mode) { + (void)env; (void)clazz; + VkRenderer* r = (VkRenderer*)(intptr_t)handle; + if (!r) return; + + if (r->scale_filter == mode) return; + + pthread_mutex_lock(&r->render_mutex); + wait_inflight_frames(r); + r->scale_filter = mode; + vkr_retarget_shared_sampler(r); + pthread_mutex_unlock(&r->render_mutex); +} + +// Set the compositor present mode. Java passes 0=FIFO, 1=MAILBOX, 2=IMMEDIATE; anything else +// is treated as FIFO. Triggers a swapchain rebuild if a surface is currently active so the +// change takes effect on the next frame. +JNIEXPORT void JNICALL JNI_FN(nativeSetPresentMode)(JNIEnv* env, jclass clazz, jlong handle, jint mode) { + (void)env; (void)clazz; + VkRenderer* r = (VkRenderer*)(intptr_t)handle; + if (!r) return; + + VkPresentModeKHR vk_mode; + switch (mode) { + case 1: vk_mode = VK_PRESENT_MODE_MAILBOX_KHR; break; + case 2: vk_mode = VK_PRESENT_MODE_IMMEDIATE_KHR; break; + default: vk_mode = VK_PRESENT_MODE_FIFO_KHR; break; + } + if (r->target_present_mode == vk_mode) return; + + if (!r->surface) { + pthread_mutex_lock(&r->render_mutex); + r->target_present_mode = vk_mode; + pthread_mutex_unlock(&r->render_mutex); + return; + } + lifecycle_begin(r); + r->target_present_mode = vk_mode; + if (r->device) vkDeviceWaitIdle(r->device); + uint32_t fw = r->surface_extent.width; + uint32_t fh = r->surface_extent.height; + destroy_sgsr1_resources(r); + destroy_offscreen(r); + destroy_swapchain(r); + if (!create_swapchain(r, fw, fh)) { + VK_LOGE("Swapchain re-create failed in nativeSetPresentMode"); + } else { + pthread_mutex_lock(&r->scene_mutex); + r->surface_ready = true; + pthread_mutex_unlock(&r->scene_mutex); + } + pthread_mutex_unlock(&r->render_mutex); +} + +// ============================================================ +// JNI entry points for Java Texture / GPUImage +// ============================================================ + +#define TEX_FN(name) Java_com_winlator_cmod_runtime_display_renderer_Texture_##name +#define GPU_FN(name) Java_com_winlator_cmod_runtime_display_renderer_GPUImage_##name + +JNIEXPORT jlong JNICALL TEX_FN(nativeAllocate)(JNIEnv* env, jclass clazz, jlong rendererHandle, + jint width, jint height, + jobject dataBuffer, jint stridePixels) +{ + (void)clazz; + VkRenderer* r = (VkRenderer*)(intptr_t)rendererHandle; + if (!r) return 0; + + void* data = NULL; + size_t size = 0; + if (dataBuffer) { + data = (*env)->GetDirectBufferAddress(env, dataBuffer); + size = (size_t)(*env)->GetDirectBufferCapacity(env, dataBuffer); + } + VkTexture* t = vkr_texture_create_uploaded(r, (uint32_t)width, (uint32_t)height, + data, size, (uint32_t)stridePixels); + return (jlong)(intptr_t)t; +} + +JNIEXPORT jboolean JNICALL TEX_FN(nativeUpdate)(JNIEnv* env, jclass clazz, jlong rendererHandle, + jlong texHandle, jint width, jint height, + jobject dataBuffer, jint stridePixels, + jint dirtyX, jint dirtyY, + jint dirtyWidth, jint dirtyHeight) +{ + (void)clazz; + VkRenderer* r = (VkRenderer*)(intptr_t)rendererHandle; + VkTexture* t = (VkTexture*)(intptr_t)texHandle; + if (!r || !t || !dataBuffer) return JNI_FALSE; + void* data = (*env)->GetDirectBufferAddress(env, dataBuffer); + size_t size = (size_t)(*env)->GetDirectBufferCapacity(env, dataBuffer); + return vkr_texture_update(r, t, (uint32_t)width, (uint32_t)height, data, size, + (uint32_t)stridePixels, + dirtyX < 0 ? 0 : (uint32_t)dirtyX, + dirtyY < 0 ? 0 : (uint32_t)dirtyY, + dirtyWidth < 0 ? 0 : (uint32_t)dirtyWidth, + dirtyHeight < 0 ? 0 : (uint32_t)dirtyHeight) + ? JNI_TRUE : JNI_FALSE; +} + +// Grow-only scratch for parsed batch entries. Render-thread-only, so unlocked; every element +// is fully populated before use, so no zeroing. (Mirrors get_prepared_scratch in vk_image.c.) +static VkTextureBatchUpload* get_entry_scratch(VkRenderer* r, uint32_t count) { + if (r->batch_entry_cap < count) { + uint32_t new_cap = r->batch_entry_cap ? r->batch_entry_cap : 64; + while (new_cap < count) new_cap *= 2; + VkTextureBatchUpload* p = realloc(r->batch_entry_scratch, + (size_t)new_cap * sizeof(VkTextureBatchUpload)); + if (!p) return NULL; + r->batch_entry_scratch = p; + r->batch_entry_cap = new_cap; + } + return r->batch_entry_scratch; +} + +JNIEXPORT jboolean JNICALL TEX_FN(nativeBatchUpdate)(JNIEnv* env, jclass clazz, jlong rendererHandle, + jobject entriesBuffer, + jobjectArray dataBuffers, + jint count) +{ + (void)clazz; + VkRenderer* r = (VkRenderer*)(intptr_t)rendererHandle; + if (!r || !entriesBuffer || !dataBuffers || count <= 0) return JNI_FALSE; + + const uint8_t* base = (const uint8_t*)(*env)->GetDirectBufferAddress(env, entriesBuffer); + jlong cap = (*env)->GetDirectBufferCapacity(env, entriesBuffer); + if (!base || cap < (jlong)count * 48) return JNI_FALSE; + jsize buffer_count = (*env)->GetArrayLength(env, dataBuffers); + if (buffer_count < count) return JNI_FALSE; + + VkTextureBatchUpload* uploads = get_entry_scratch(r, (uint32_t)count); + if (!uploads) return JNI_FALSE; + + for (jint i = 0; i < count; i++) { + const uint8_t* e = base + (size_t)i * 48; + int64_t tex_h; + int32_t width, height, stride, dirty_x, dirty_y, dirty_w, dirty_h, data_index; + memcpy(&tex_h, e, sizeof(tex_h)); + memcpy(&width, e + 8, sizeof(width)); + memcpy(&height, e + 12, sizeof(height)); + memcpy(&stride, e + 16, sizeof(stride)); + memcpy(&dirty_x, e + 20, sizeof(dirty_x)); + memcpy(&dirty_y, e + 24, sizeof(dirty_y)); + memcpy(&dirty_w, e + 28, sizeof(dirty_w)); + memcpy(&dirty_h, e + 32, sizeof(dirty_h)); + memcpy(&data_index, e + 36, sizeof(data_index)); + if (data_index < 0 || data_index >= buffer_count) { + return JNI_FALSE; + } + + jobject data_buffer = (*env)->GetObjectArrayElement(env, dataBuffers, data_index); + if (!data_buffer) { + return JNI_FALSE; + } + void* data = (*env)->GetDirectBufferAddress(env, data_buffer); + jlong data_size = (*env)->GetDirectBufferCapacity(env, data_buffer); + (*env)->DeleteLocalRef(env, data_buffer); + if (!data || data_size <= 0) { + return JNI_FALSE; + } + + uploads[i].texture = (VkTexture*)(intptr_t)tex_h; + uploads[i].data = data; + uploads[i].data_size = (size_t)data_size; + uploads[i].width = width < 0 ? 0u : (uint32_t)width; + uploads[i].height = height < 0 ? 0u : (uint32_t)height; + uploads[i].stride_pixels = stride < 0 ? 0u : (uint32_t)stride; + uploads[i].dirty_x = dirty_x < 0 ? 0u : (uint32_t)dirty_x; + uploads[i].dirty_y = dirty_y < 0 ? 0u : (uint32_t)dirty_y; + uploads[i].dirty_w = dirty_w < 0 ? 0u : (uint32_t)dirty_w; + uploads[i].dirty_h = dirty_h < 0 ? 0u : (uint32_t)dirty_h; + } + + bool ok = vkr_texture_batch_update(r, uploads, (uint32_t)count); + return ok ? JNI_TRUE : JNI_FALSE; +} + +JNIEXPORT void JNICALL TEX_FN(nativeDestroy)(JNIEnv* env, jclass clazz, jlong rendererHandle, jlong texHandle) { + (void)env; (void)clazz; + VkRenderer* r = (VkRenderer*)(intptr_t)rendererHandle; + VkTexture* t = (VkTexture*)(intptr_t)texHandle; + if (!r || !t) return; + vkr_texture_schedule_destroy(r, t); +} + +JNIEXPORT jlong JNICALL GPU_FN(nativeImportAhbToVulkan)(JNIEnv* env, jclass clazz, + jlong rendererHandle, jlong ahbPtr, + jboolean transferOwnership) +{ + (void)env; (void)clazz; + VkRenderer* r = (VkRenderer*)(intptr_t)rendererHandle; + AHardwareBuffer* ahb = (AHardwareBuffer*)(intptr_t)ahbPtr; + if (!r || !ahb) { + VK_LOGW("nativeImportAhbToVulkan skipped: renderer=%p ahb=%p", (void*)r, (void*)ahb); + return 0; + } + VkTexture* t = vkr_texture_import_ahb(r, ahb, transferOwnership); + if (!t) { + VK_LOGW("nativeImportAhbToVulkan failed: ahb=%p transfer=%d", (void*)ahb, transferOwnership); + } + return (jlong)(intptr_t)t; +} diff --git a/app/src/main/cpp/winlator/vk/vk_state.h b/app/src/main/cpp/winlator/vk/vk_state.h new file mode 100644 index 000000000..0eae82af5 --- /dev/null +++ b/app/src/main/cpp/winlator/vk/vk_state.h @@ -0,0 +1,520 @@ +// Master state header for the Vulkan compositor. +// Internal use only — JNI entry points expose a long handle that wraps VkRenderer*. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +// All vk* calls route through the dispatch table — vk_dispatch.h is the Vulkan header for +// this translation unit (do not include directly). +#include "vk_dispatch.h" + +#define VK_LOG_TAG "VkRenderer" +#define VK_LOGI(...) __android_log_print(ANDROID_LOG_INFO, VK_LOG_TAG, __VA_ARGS__) +#define VK_LOGW(...) __android_log_print(ANDROID_LOG_WARN, VK_LOG_TAG, __VA_ARGS__) +#define VK_LOGE(...) __android_log_print(ANDROID_LOG_ERROR, VK_LOG_TAG, __VA_ARGS__) + +#define VK_FRAMES_IN_FLIGHT 2 +#define VK_MAX_SWAPCHAIN_IMAGES 8 +// Encoder input-surface swapchains can expose many more images than a display swapchain. +#define VK_MAX_RECORD_IMAGES 32 +#define VK_MAX_EFFECTS 8 +#define VK_MAX_RENDERABLE_WINDOWS 64 +// Number of in-flight upload slots. Each slot owns a persistently-mapped staging buffer, +// fence, and command pool. An upload only blocks when this many uploads are still pending +// on the GPU — with 8 slots and ~100µs GPU upload time, we can sustain ~80k uploads/sec +// without ever waiting. +#define VK_STAGING_POOL_SIZE 8 + +#define VK_CHECK(expr) do { \ + VkResult _r = (expr); \ + if (_r != VK_SUCCESS) { \ + VK_LOGE("%s:%d: %s -> %d", __FILE__, __LINE__, #expr, _r); \ + } \ +} while (0) + +// ============================================================ +// Texture (drives both regular CPU-uploaded images and AHB imports) +// ============================================================ + +// A CPU-uploaded texture's slice of an image sub-allocator block (defined below). +struct VkMemBlock; +typedef struct VkSuballoc { + struct VkMemBlock* block; // owning block (NULL => not sub-allocated) + VkDeviceMemory memory; // == block->memory, cached for vkBindImageMemory + VkDeviceSize bind_offset; // aligned offset the image is bound at + VkDeviceSize span_offset; // reserved span start/length, returned on free + VkDeviceSize span_size; +} VkSuballoc; + +typedef struct VkTexture { + VkImage image; + VkImageView view; + VkDeviceMemory memory; + VkSampler sampler; // owned per-texture (simple); could be cached + VkSamplerYcbcrConversion ycbcr; // VK_NULL_HANDLE if unused + VkDescriptorSet descriptor_set; // one per texture, lives until destruction + + uint32_t width; + uint32_t height; + VkFormat format; + VkImageLayout layout; + + // Lifetime: when set, owned by texture and freed on destroy. + AHardwareBuffer* ahb; + + // Track readiness: true once image+view+sampler are valid. + bool ready; + // True if this texture should never be uploaded to (e.g. AHB scanout). + bool external; + // Prevent duplicate deferred frees if Java schedules destruction more than once. + bool destroy_scheduled; + + // suballocated: backing comes from the shared sub-allocator (suballoc) and `memory` is + // unused. AHB imports and the dedicated fallback leave this false. + bool suballocated; + VkSuballoc suballoc; +} VkTexture; + +typedef struct VkTextureBatchUpload { + VkTexture* texture; + const void* data; + size_t data_size; + uint32_t width; + uint32_t height; + uint32_t stride_pixels; + uint32_t dirty_x; + uint32_t dirty_y; + uint32_t dirty_w; + uint32_t dirty_h; +} VkTextureBatchUpload; + +// ============================================================ +// Effects +// ============================================================ + +typedef enum VkEffectType { + VK_EFFECT_CRT = 0, + VK_EFFECT_VIVID = 1, + VK_EFFECT_HDR = 2, + VK_EFFECT_NATURAL = 3, + VK_EFFECT_SGSR1 = 4, + VK_EFFECT_TOON = 5, + VK_EFFECT_NTSC = 6, + VK_EFFECT_COLORADJ = 7, + VK_EFFECT_COLORGRADE = 8, + VK_EFFECT_SHARPEN = 9, + VK_EFFECT_SCANLINES = 10, + VK_EFFECT_NTSC2 = 11, + VK_EFFECT_COLORBLIND = 12, + VK_EFFECT_PIXELATE = 13, + VK_EFFECT_COUNT +} VkEffectType; + +typedef struct VkEffectSlot { + VkEffectType type; + int mode; // effect-specific mode + float param0; // generic + float param1; + float param2; // generic +} VkEffectSlot; + +// ============================================================ +// Scene snapshot (mutex-protected, written from Java threads, read on render thread) +// ============================================================ + +typedef struct VkRenderableWindow { + VkTexture* texture; // borrowed; not owned + int x, y; + uint32_t width, height; + float u0, v0, u1, v1; + bool direct_scanout; // hint, currently unused +} VkRenderableWindow; + +typedef struct VkScene { + VkRenderableWindow windows[VK_MAX_RENDERABLE_WINDOWS]; + uint32_t window_count; + + VkTexture* cursor_texture; + int cursor_x; + int cursor_y; + uint32_t cursor_width; + uint32_t cursor_height; + bool cursor_visible; + + // Transform parameters - tmpXForm2 of GLRenderer applied to all windows. + float xform[6]; + bool scissor_enabled; + int scissor_x, scissor_y, scissor_w, scissor_h; + int viewport_x, viewport_y, viewport_w, viewport_h; + bool viewport_set; + + // Render dims (logical screen size). + uint32_t screen_width; + uint32_t screen_height; + uint32_t source_width; + uint32_t source_height; + bool swap_rb; + + VkEffectSlot effects[VK_MAX_EFFECTS]; + uint32_t effect_count; + + bool dirty; +} VkScene; + +// ============================================================ +// Pipelines - one per shader pass type +// ============================================================ + +typedef struct VkPipelineSet { + VkDescriptorSetLayout sampler_set_layout; + VkPipelineLayout window_layout; // push constants: xform[6] + viewSize + VkPipelineLayout effect_layout; // push constants: resolution + effect params + VkPipeline window_pipeline; + VkPipeline cursor_pipeline; + VkPipeline blit_pipeline; + VkPipeline effect_pipelines[VK_EFFECT_COUNT]; + VkPipeline offscreen_window_pipeline; + VkPipeline offscreen_cursor_pipeline; + VkPipeline offscreen_blit_pipeline; + VkPipeline offscreen_effect_pipelines[VK_EFFECT_COUNT]; + + // Render passes + VkRenderPass swapchain_pass; // load=clear, store=store, final=present + VkRenderPass offscreen_pass; // load=clear, store=store, final=shader-read +} VkPipelineSet; + +// ============================================================ +// Per-frame resources +// ============================================================ + +typedef struct VkFrame { + VkSemaphore image_available; + VkFence in_flight; + VkCommandBuffer cmd; +} VkFrame; + +// ============================================================ +// Offscreen targets (for effect ping-pong) +// ============================================================ + +typedef struct VkOffscreen { + VkImage image; + VkImageView view; + VkDeviceMemory memory; + VkSampler sampler; + VkDescriptorSet descriptor_set; + VkFramebuffer framebuffer; + uint32_t width, height; +} VkOffscreen; + +typedef struct VkSgsr1State { + VkOffscreen source; + bool built; + uint32_t width; + uint32_t height; +} VkSgsr1State; + +// Recording mirror: a second swapchain on a MediaCodec input surface; each frame is blitted from +// the display swapchain into it and co-presented. Gated on rec.active. +typedef struct VkRecordSwap { + bool active; + bool disabled; + ANativeWindow* anw; + VkSurfaceKHR surface; + VkSwapchainKHR swapchain; + VkFormat format; + VkExtent2D extent; + uint32_t image_count; + VkImage images[VK_MAX_RECORD_IMAGES]; + VkSemaphore acquire[VK_FRAMES_IN_FLIGHT]; + VkSemaphore present_ready[VK_MAX_RECORD_IMAGES]; + uint64_t captured; + uint64_t skipped; + uint64_t min_interval_ns; + uint64_t last_capture_ns; + + // Record UI: alpha-blend an overlay texture over each captured frame. + bool ui_enabled; + VkRenderPass ui_pass; + VkPipeline ui_pipeline; + VkImageView views[VK_MAX_RECORD_IMAGES]; + VkFramebuffer framebuffers[VK_MAX_RECORD_IMAGES]; + bool fb_built; + struct VkTexture* ui_texture; +} VkRecordSwap; + +// ============================================================ +// Staging pool for async texture uploads +// ============================================================ + +typedef struct VkStagingSlot { + pthread_mutex_t mutex; // held by current owner from acquire to release + VkCommandPool cmd_pool; // exclusive to this slot, no global cmd pool sync needed + VkCommandBuffer cmd; + VkBuffer buffer; + VkDeviceMemory memory; + void* mapped; // persistently mapped HOST_VISIBLE memory + VkDeviceSize size; // current allocation; grows on demand + VkFence fence; // signaled when this slot's last submission completes +} VkStagingSlot; + +typedef struct VkStagingPool { + VkStagingSlot slots[VK_STAGING_POOL_SIZE]; + uint32_t valid_slots; // count of slots whose per-slot mutex is initialized + uint64_t next; // round-robin counter + pthread_mutex_t mutex; // protects `next` only + bool mutex_init; // pool-mutex initialization flag (for safe destroy) + bool initialized; +} VkStagingPool; + +// ============================================================ +// Deferred destruction graveyard +// ============================================================ + +typedef struct VkGraveSlot { + VkTexture** textures; + uint32_t count; + uint32_t capacity; +} VkGraveSlot; + +// ============================================================ +// Device caps (queried once after create_device) +// ============================================================ + +typedef struct VkDeviceCaps { + // Identity + uint32_t vendor_id; + uint32_t device_id; + uint32_t driver_version; + bool is_adreno; // vendor_id == 0x5143 (Qualcomm) + + // Limits / sizing + VkPhysicalDeviceLimits limits; + uint32_t descriptor_pool_capacity; + + // Format choices resolved against driver feature support + VkFormat offscreen_format; // BGRA preferred, RGBA fallback + VkFormat upload_format; // BGRA preferred; RGBA fallback uses CPU-side swizzle + bool upload_needs_bgra_swizzle; + + // Diagnostic + bool ahb_bgra_supported; // VK_FORMAT_B8G8R8A8_UNORM importable from AHB +} VkDeviceCaps; + +// ============================================================ +// Image sub-allocator +// ============================================================ +// +// CPU-uploaded textures share large DEVICE_LOCAL blocks instead of each taking a dedicated +// vkAllocateMemory — avoids per-pixmap allocator latency and hitting maxMemoryAllocationCount +// (~4096 on Adreno) under X-server pixmap churn. Each block has a first-fit free list (offset- +// sorted, coalesced on free); fully-drained blocks are returned. AHB imports stay dedicated. + +#define VK_SUBALLOC_BLOCK_SIZE (32u * 1024u * 1024u) // 32 MiB default block + +typedef struct VkMemRegion { + VkDeviceSize offset; + VkDeviceSize size; + struct VkMemRegion* next; // free list, sorted ascending by offset +} VkMemRegion; + +typedef struct VkMemBlock { + VkDeviceMemory memory; + VkDeviceSize size; + uint32_t memory_type_index; + VkMemRegion* free_list; + struct VkMemBlock* next; +} VkMemBlock; + +typedef struct VkImageSuballocator { + VkMemBlock* blocks; + VkDeviceSize block_size; // size used when carving a new block + pthread_mutex_t mutex; // alloc (producer threads) vs free (render thread) + bool mutex_init; +} VkImageSuballocator; + +// ============================================================ +// Master state +// ============================================================ + +typedef struct VkRenderer { + // Lifecycle + bool initialized; + bool surface_ready; + // True when we deliberately create a fallback swapchain with a preTransform that differs + // from caps.currentTransform (Adreno reports SUBOPTIMAL on every present in that case). + bool ignore_suboptimal; + pthread_mutex_t scene_mutex; // guards r->scene + graveyard slots; held briefly by all + pthread_mutex_t queue_mutex; // serializes vkQueueSubmit across threads + pthread_mutex_t texture_mutex; // guards live_textures + pthread_mutex_t descriptor_mutex;// external sync for descriptor_pool alloc/free + pthread_mutex_t render_mutex; // serializes lifecycle vs render; held by render thread for + // the full acquire+record+submit+present, and by lifecycle + // ops (surface create/change/destroy) before they touch the + // swapchain. Scene producers do NOT take this — they only + // touch scene_mutex, so they never stall behind a frame. + + // Instance + physical/logical device + // dlopen handle for the libvulkan we resolved through. dlclose'd in nativeDestroy AFTER + // vkd_unload() to avoid stale dispatch pointers calling into freed memory. + void* vulkan_handle; + VkInstance instance; + bool validation_enabled; + bool debug_utils_enabled; + VkDebugUtilsMessengerEXT debug_messenger; + VkPhysicalDevice physical_device; + VkPhysicalDeviceMemoryProperties mem_props; + uint32_t graphics_queue_family; + VkDevice device; + VkQueue graphics_queue; + + // Surface + swapchain + ANativeWindow* anw; + VkSurfaceKHR surface; + VkSwapchainKHR swapchain; + VkFormat swapchain_format; + VkSurfaceTransformFlagBitsKHR swapchain_transform; + VkExtent2D surface_extent; + VkExtent2D swapchain_extent; + uint32_t swapchain_image_count; + VkImage swapchain_images[VK_MAX_SWAPCHAIN_IMAGES]; + VkImageView swapchain_views[VK_MAX_SWAPCHAIN_IMAGES]; + VkFramebuffer swapchain_framebuffers[VK_MAX_SWAPCHAIN_IMAGES]; + VkSemaphore swapchain_render_finished[VK_MAX_SWAPCHAIN_IMAGES]; + + // Pipelines / passes + VkPipelineSet pipelines; + bool pipelines_built; + + // Offscreen ping-pong (created lazily when effects are present) + VkOffscreen offscreen[2]; + bool offscreen_built; + VkSgsr1State sgsr1; + + // record_blit_src adds TRANSFER_SRC usage to the display swapchain (toggled by start/stop recording). + bool record_blit_src; + VkRecordSwap rec; + + // Quad vertex buffer (window/cursor) + VkBuffer quad_vbo; + VkDeviceMemory quad_vbo_memory; + + // Shared sampler for all CPU-uploaded textures and AHB textures that don't need a Ycbcr + // conversion. Created once at init; vkCreateSampler costs ~50-200µs on Adreno, so giving + // every texture its own sampler is a non-trivial CPU+GPU tax during pixmap churn. + VkSampler shared_sampler; + VkSampler shared_sampler_nearest; + VkSampler shared_sampler_cubic; + bool ext_filter_cubic; + int scale_filter; + + // Per-frame + VkCommandPool cmd_pool; + VkFrame frames[VK_FRAMES_IN_FLIGHT]; + uint32_t frame_index; + + // Descriptor pool (for texture sampler descriptors) + VkDescriptorPool descriptor_pool; + uint32_t descriptor_pool_capacity; + uint32_t descriptor_pool_used; + VkDescriptorSet* descriptor_free_list; + uint32_t descriptor_free_count; + uint32_t descriptor_free_capacity; + + // Graveyard + VkGraveSlot graveyard[VK_FRAMES_IN_FLIGHT + 1]; + uint32_t graveyard_index; + + // Live native textures owned by this renderer/device. Java Texture objects can outlive a + // renderer teardown, so nativeDestroy drains this list before the device is destroyed. + VkTexture** live_textures; + uint32_t live_texture_count; + uint32_t live_texture_capacity; + + // Extensions present + bool ext_ahb; + bool ext_ycbcr; + + // Cached device capabilities populated by query_device_caps(). + VkDeviceCaps caps; + + // Function pointers loaded via vkGetDeviceProcAddr (not all are statically exported by + // the Android Vulkan loader, even in Vulkan 1.1). + PFN_vkGetAndroidHardwareBufferPropertiesANDROID fnGetAhbProps; + PFN_vkCreateSamplerYcbcrConversion fnCreateYcbcr; + PFN_vkDestroySamplerYcbcrConversion fnDestroyYcbcr; + PFN_vkCreateDebugUtilsMessengerEXT fnCreateDebugUtilsMessenger; + PFN_vkDestroyDebugUtilsMessengerEXT fnDestroyDebugUtilsMessenger; + + // Async upload pool (created in nativeCreate after device). + VkStagingPool staging_pool; + + // Image sub-allocator for CPU-uploaded textures (created in nativeCreate after device). + VkImageSuballocator image_suballoc; + + // Grow-only scratch for the batch upload path (render-thread-only, so unlocked; zero + // steady-state heap traffic). + VkTextureBatchUpload* batch_entry_scratch; // nativeBatchUpdate: parsed JNI entries + uint32_t batch_entry_cap; + void* batch_prepared_scratch; // vkr_texture_batch_update: PreparedBatchUpload[] + uint32_t batch_prepared_cap; + + // Scene state + VkScene scene; + + // Compositor present mode requested by Java (default FIFO). Validated against + // device-supported modes in create_swapchain; falls back to FIFO if unavailable. + VkPresentModeKHR target_present_mode; +} VkRenderer; + +// ============================================================ +// Public functions implemented in vk_image.c +// ============================================================ + +VkTexture* vkr_texture_create_uploaded(VkRenderer* r, uint32_t width, uint32_t height, + const void* data, size_t data_size, uint32_t stride_pixels); +bool vkr_texture_update(VkRenderer* r, VkTexture* tex, uint32_t width, uint32_t height, + const void* data, size_t data_size, uint32_t stride_pixels, + uint32_t dirty_x, uint32_t dirty_y, + uint32_t dirty_w, uint32_t dirty_h); +bool vkr_texture_batch_update(VkRenderer* r, const VkTextureBatchUpload* uploads, + uint32_t upload_count); +VkTexture* vkr_texture_import_ahb(VkRenderer* r, AHardwareBuffer* ahb, bool transfer_ownership); +void vkr_texture_destroy(VkRenderer* r, VkTexture* tex); +void vkr_texture_destroy_all_live(VkRenderer* r); +void vkr_texture_schedule_destroy(VkRenderer* r, VkTexture* tex); + +// Helpers +uint32_t vkr_find_memory_type(VkRenderer* r, uint32_t type_bits, VkMemoryPropertyFlags props); +void vkr_run_one_shot_cmd(VkRenderer* r, void (*fn)(VkCommandBuffer, void*), void* user); +void vkr_image_barrier(VkCommandBuffer cmd, VkImage image, VkImageLayout from, VkImageLayout to, + VkPipelineStageFlags src_stage, VkPipelineStageFlags dst_stage, + VkAccessFlags src_access, VkAccessFlags dst_access); +bool vkr_create_sampler(VkRenderer* r, VkSamplerYcbcrConversion ycbcr, VkSampler* out); +void vkr_retarget_shared_sampler(VkRenderer* r); +// Async layout transition through the staging pool. Submits a tiny command buffer that runs +// the requested barrier, but does NOT wait for the GPU. The barrier is ordered before all +// subsequent submits on the same queue per Vulkan spec, so callers can sample the image as +// soon as the next render submit happens. Returns false on submit failure. +bool vkr_submit_async_transition(VkRenderer* r, VkImage image, + VkImageLayout from, VkImageLayout to, + VkPipelineStageFlags src_stage, VkPipelineStageFlags dst_stage, + VkAccessFlags src_access, VkAccessFlags dst_access); + +// Staging pool — created in nativeCreate, drained + destroyed in nativeDestroy. +bool vkr_staging_pool_init(VkRenderer* r); +void vkr_staging_pool_destroy(VkRenderer* r); +VkStagingSlot* vkr_staging_pool_acquire(VkRenderer* r, VkDeviceSize needed); +void vkr_staging_pool_release(VkStagingSlot* slot); + +// Image sub-allocator lifecycle (alloc/free are static in vk_image.c). Destroy after all +// textures are released. +void vkr_suballoc_init(VkRenderer* r); +void vkr_suballoc_destroy(VkRenderer* r); diff --git a/app/src/main/cpp/winlator/vulkan.c b/app/src/main/cpp/winlator/vulkan.c index ea5753f0d..32cd11a5c 100644 --- a/app/src/main/cpp/winlator/vulkan.c +++ b/app/src/main/cpp/winlator/vulkan.c @@ -169,54 +169,73 @@ static char *get_library_name(JNIEnv *env, jobject context, return library_name; } +static void preload_first_existing(const char **candidates) { + for (int i = 0; candidates[i]; i++) { + if (dlopen(candidates[i], RTLD_GLOBAL | RTLD_NOW)) + return; + } +} + static void preload_vendor_icd_deps() { - // Some OEM Vulkan ICDs (e.g. Samsung's /system_ext/lib64/libvendorutils.so) - // declare unresolved refs to OpenSSL symbols like BIO_flush. When the Android - // Vulkan loader pulls in the vendor ICD via dlopen, those symbols must already - // be visible in a preceding RTLD_GLOBAL library or the dlopen fails with - // "cannot locate symbol BIO_flush", and vkCreateInstance returns -9. - const char *candidates[] = { + // Keep OEM Vulkan ICD dependencies globally visible before the loader pulls + // them in, or DXVK can misreport missing VK_KHR_surface. + const char *jpeg_candidates[] = { + "/system/lib64/libjpeg.so", + "/system_ext/lib64/libjpeg.so", + "libjpeg.so", + NULL, + }; + preload_first_existing(jpeg_candidates); + + const char *crypto_candidates[] = { "libcrypto.so", NULL, }; - for (int i = 0; candidates[i]; i++) { - if (dlopen(candidates[i], RTLD_GLOBAL | RTLD_NOW)) - break; - } + preload_first_existing(crypto_candidates); } -static void init_original_vulkan() { +void *winlator_open_system_vulkan(void) { preload_vendor_icd_deps(); - vulkan_handle = dlopen("/system/lib64/libvulkan.so", RTLD_LOCAL | RTLD_NOW); + return dlopen("/system/lib64/libvulkan.so", RTLD_LOCAL | RTLD_NOW); } -static void init_vulkan(JNIEnv *env, jobject context, const char *driver_name) { - char *tmpdir = NULL; - char *library_name = NULL; - char *native_library_dir = NULL; +void *winlator_open_vulkan(JNIEnv *env, jobject context, const char *driver_name) { + if (!driver_name || strcmp(driver_name, "System") == 0) { + return winlator_open_system_vulkan(); + } - const char *driver_path = get_driver_path(env, context, driver_name); + preload_vendor_icd_deps(); + const char *driver_path = get_driver_path(env, context, driver_name); if (!driver_path || access(driver_path, F_OK) != 0) { - init_original_vulkan(); - return; + return winlator_open_system_vulkan(); } - library_name = get_library_name(env, context, driver_name); - native_library_dir = get_native_library_dir(env, context); + char *library_name = get_library_name(env, context, driver_name); + char *native_library_dir = get_native_library_dir(env, context); if (!library_name || !native_library_dir) { - init_original_vulkan(); - return; + return winlator_open_system_vulkan(); } + char *tmpdir = NULL; asprintf(&tmpdir, "%s%s", driver_path, "temp"); mkdir(tmpdir, S_IRWXU | S_IRWXG); - vulkan_handle = adrenotools_open_libvulkan( + void *handle = adrenotools_open_libvulkan( RTLD_LOCAL | RTLD_NOW, ADRENOTOOLS_DRIVER_CUSTOM, tmpdir, native_library_dir, driver_path, library_name, NULL, NULL); + if (!handle) return winlator_open_system_vulkan(); + return handle; +} + +static void init_original_vulkan() { + vulkan_handle = winlator_open_system_vulkan(); +} + +static void init_vulkan(JNIEnv *env, jobject context, const char *driver_name) { + vulkan_handle = winlator_open_vulkan(env, context, driver_name); } static VkResult create_instance(jstring driverName, JNIEnv *env, diff --git a/app/src/main/cpp/wn-libsteamclient/CMakeLists.txt b/app/src/main/cpp/wn-libsteamclient/CMakeLists.txt new file mode 100644 index 000000000..4e143966a --- /dev/null +++ b/app/src/main/cpp/wn-libsteamclient/CMakeLists.txt @@ -0,0 +1,74 @@ +# wn-libsteamclient — an open-source libsteamclient.so we build ourselves. +# +# Background. The Bionic Steam path needs a `libsteamclient.so` to host +# Valve's Steam client surface in the Android process so Wine's +# `lsteamclient.dll` (running inside the Proton prefix) has a peer to +# talk to over the Steam3Master / SteamClientService TCP services. We +# were bundling a prebuilt copy; this module replaces it with our own +# from-scratch implementation that exposes the same public C ABI but +# is backed by the open-source Rust `wnsteam` CM client we already +# ship. +# +# Initial scope (this commit): the FLAT C entry points (CreateInterface +# + Steam_BLoggedOn + Steam_BGetCallback + the rest of the +# Steam_*/* pipe + user lifecycle exports) plus a stub ISteamClient +# whose GetISteam* methods return empty objects. That's enough for the +# bootstrap's nativeInit to dlopen + dlsym + walk the pipe API without +# crashing. Real backend wiring lands incrementally. +# +# SONAME is `libsteamclient.so` exactly so the staged file at +# /imagefs/usr/lib/libsteamclient.so behaves as a drop-in. + +cmake_minimum_required(VERSION 3.22.1) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# IMPORTANT: the output `.so` name must be `libsteamclient.so` (not +# `libwnlibsteamclient.so`) because the Wine `lsteamclient.dll` bridge +# dlopens by that exact name. Use OUTPUT_NAME to override the CMake +# default (`lib.so`). +add_library(wn_libsteamclient SHARED + src/api_entry.cpp + src/callback_registry.cpp + src/iclient_engine.cpp + src/isteam_client.cpp + src/isteam_stubs.cpp + src/jni_pushed_state.cpp + src/runtime_state.cpp + src/tcp_services.cpp +) +set_target_properties(wn_libsteamclient PROPERTIES + OUTPUT_NAME steamclient + PREFIX "lib" + SUFFIX ".so" +) + +target_include_directories(wn_libsteamclient PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/include +) + +target_compile_options(wn_libsteamclient PRIVATE + -fno-rtti + -fvisibility=hidden + -Wall -Wno-unused-parameter +) + +# Mirror the public-ABI NEEDED list of Valve's binary so any consumer +# that probes our .so against the original sees the same dynamic deps. +# All of these are part of Android's bionic / NDK; nothing extra. +target_link_libraries(wn_libsteamclient PRIVATE + android log dl m + # libwnsteam.so — resolves the wn_cm_* C-ABI bridge symbols at link + # time. Both .so's load in the app process; the dynamic linker pairs + # the unresolved bridge references in libsteamclient.so to their + # default-visibility implementations in libwnsteam.so. Without this + # link, --no-undefined fails the link. + wnsteam +) + +# Surface unresolved symbols at link time so a missing export shows up +# in the build, not at dlopen on device. +target_link_options(wn_libsteamclient PRIVATE + -Wl,--no-undefined +) diff --git a/app/src/main/cpp/wn-libsteamclient/include/wn_libsteamclient/callback_registry.h b/app/src/main/cpp/wn-libsteamclient/include/wn_libsteamclient/callback_registry.h new file mode 100644 index 000000000..9792a5870 --- /dev/null +++ b/app/src/main/cpp/wn-libsteamclient/include/wn_libsteamclient/callback_registry.h @@ -0,0 +1,29 @@ +#pragma once + + +#include +#include + +namespace wn_libsteamclient { + +constexpr size_t kCCallbackBaseFlagsOffset = 8; +constexpr size_t kCCallbackBaseIdOffset = 12; +constexpr uint8_t kCallbackFlagsRegistered = 0x01; +constexpr uint8_t kCallbackFlagsGameServer = 0x02; + +void register_callback(void* cb, int iCallback); + +void unregister_callback(void* cb); + +[[nodiscard]] std::vector find_callbacks(int iCallback); + +[[nodiscard]] size_t registry_size(); + + +void register_call_result(void* cb, uint64_t hCall); +void unregister_call_result(void* cb, uint64_t hCall); +[[nodiscard]] std::vector find_call_result_cbs(uint64_t hCall); + +[[nodiscard]] size_t call_result_registry_size(); + +} // namespace wn_libsteamclient diff --git a/app/src/main/cpp/wn-libsteamclient/include/wn_libsteamclient/callbacks.h b/app/src/main/cpp/wn-libsteamclient/include/wn_libsteamclient/callbacks.h new file mode 100644 index 000000000..b14e2e0f0 --- /dev/null +++ b/app/src/main/cpp/wn-libsteamclient/include/wn_libsteamclient/callbacks.h @@ -0,0 +1,644 @@ +#pragma once + + +#include +#include + +namespace wn_libsteamclient::callbacks { + +constexpr int kSteamServersConnected = 101; +constexpr int kSteamServerConnectFailure = 102; +constexpr int kSteamServersDisconnected = 103; +constexpr int kIPCFailure = 117; // k_iSteamUserCallbacks + 17 — single-byte failure type +constexpr int kValidateAuthTicketResponse = 143; // k_iSteamUserCallbacks + 43 +constexpr int kEncryptedAppTicketResponse = 154; // k_iSteamUserCallbacks + 54 +constexpr int kGetAuthSessionTicketResponse = 163; // k_iSteamUserCallbacks + 63 +constexpr int kGetTicketForWebApiResponse = 168; // k_iSteamUserCallbacks + 68 +constexpr int kStoreAuthURLResponse = 165; // k_iSteamUserCallbacks + 65 +constexpr int kMarketEligibilityResponse = 166; // k_iSteamUserCallbacks + 66 +constexpr int kDurationControl = 167; // k_iSteamUserCallbacks + 67 + +constexpr int kSteamShutdown = 704; // k_iSteamUtilsCallbacks + 4 — empty marker +constexpr int kSteamAPICallCompleted = 703; // k_iSteamUtilsCallbacks + 3 +constexpr int kCheckFileSignature = 705; // k_iSteamUtilsCallbacks + 5 +constexpr int kLeaderboardFindResult = 1104; // base + 4 +constexpr int kLeaderboardScoresDownloaded = 1105; // base + 5 +constexpr int kLeaderboardScoreUploaded = 1106; // base + 6 +constexpr int kNumberOfCurrentPlayers = 1107; // base + 7 +constexpr int kGlobalAchievementPercentages = 1110; // base + 10 +constexpr int kLeaderboardUGCSet = 1111; // base + 11 +constexpr int kGlobalStatsReceived = 1112; // base + 12 +constexpr int kClanOfficerListResponse = 1335; // base + 35 +constexpr int kDownloadClanActivityCountsResult = 1341; // base + 41 +constexpr int kJoinClanChatRoomCompletion = 1342; // base + 42 +constexpr int kFriendsGetFollowerCount = 1344; +constexpr int kFriendsIsFollowing = 1345; +constexpr int kFriendsEnumerateFollowingList = 1346; +constexpr int kEquippedProfileItems = 1351; // base + 51 +constexpr int kRemoteStorageSubscribePublishedFile = 1313; // base + 13 +constexpr int kRemoteStorageUnsubscribePublishedFile = 1315; // base + 15 +constexpr int kRemoteStorageDownloadUGC = 1317; // base + 17 +constexpr int kSteamUGCQueryCompleted = 3401; // base + 1 +constexpr int kSteamUGCRequestUGCDetails = 3402; // base + 2 +constexpr int kSteamInventoryEligiblePromoItemDefIDs = 4703; +constexpr int kSteamInventoryStartPurchaseResult = 4704; +constexpr int kSteamInventoryRequestPricesResult = 4705; +constexpr int kLobbyEnter = 504; // base + 4 +constexpr int kLobbyMatchList = 510; // base + 10 +constexpr int kLobbyCreated = 513; // base + 13 +constexpr int kGameOverlayActivated = 731; // k_iSteamUtilsCallbacks + 31 — bool m_bActive + +constexpr int kUserStatsReceived = 1101; +constexpr int kUserStatsStored = 1102; +constexpr int kUserAchievementStored = 1103; + +constexpr int kPersonaStateChange = 1304; +constexpr int kSetPersonaNameResponse = 1332; +constexpr int kAvatarImageLoaded = 1334; +constexpr int kFriendRichPresenceUpdate = 1336; + +constexpr int kRemoteStorageAppSyncedClient = 1301; +constexpr int kRemoteStorageAppSyncedServer = 1302; +constexpr int kRemoteStorageFileWriteAsyncComplete = 1331; // base+31 +constexpr int kRemoteStorageFileReadAsyncComplete = 1332; // base+32 +constexpr int kRemoteStorageFileShareResult = 1307; // base+7 +constexpr int kFileDetailsResult = 1063; // 1040 + 23 + +constexpr int kPersonaChangeName = 0x0001; +constexpr int kPersonaChangeStatus = 0x0002; +constexpr int kPersonaChangeComeOnline = 0x0004; +constexpr int kPersonaChangeGoneOffline = 0x0008; +constexpr int kPersonaChangeGamePlayed = 0x0010; +constexpr int kPersonaChangeAvatar = 0x0040; +constexpr int kPersonaChangeNameFirstSet = 0x0400; +constexpr int kPersonaChangeNickname = 0x1000; + +struct UserStatsReceived { + uint64_t m_nGameID; + int32_t m_eResult; // EResult: 1 = OK, 2 = Fail + uint32_t _pad; // pack=8 → uint64 at offset 16 + uint64_t m_steamIDUser; +}; +static_assert(sizeof(UserStatsReceived) == 24, "UserStatsReceived size"); +static_assert(offsetof(UserStatsReceived, m_nGameID) == 0, "off m_nGameID"); +static_assert(offsetof(UserStatsReceived, m_eResult) == 8, "off m_eResult"); +static_assert(offsetof(UserStatsReceived, m_steamIDUser) == 16, "off m_steamIDUser"); + +struct UserStatsStored { + uint64_t m_nGameID; + int32_t m_eResult; + uint32_t _pad; // pack=8 trailing pad +}; +static_assert(sizeof(UserStatsStored) == 16, "UserStatsStored size"); +static_assert(offsetof(UserStatsStored, m_nGameID) == 0, "off m_nGameID"); +static_assert(offsetof(UserStatsStored, m_eResult) == 8, "off m_eResult"); + +struct SteamServersConnected { + char _placeholder; +}; + +struct SteamServerConnectFailure { + int32_t m_eResult; + bool m_bStillRetrying; + uint8_t _pad[3]; +}; +static_assert(sizeof(SteamServerConnectFailure) == 8, "SteamServerConnectFailure size"); +static_assert(offsetof(SteamServerConnectFailure, m_eResult) == 0, "off m_eResult"); +static_assert(offsetof(SteamServerConnectFailure, m_bStillRetrying) == 4, "off m_bStillRetrying"); + +struct IPCFailure { + uint8_t m_eFailureType; + uint8_t _pad[7]; +}; +static_assert(sizeof(IPCFailure) == 8, "IPCFailure size"); +static_assert(offsetof(IPCFailure, m_eFailureType) == 0, "off m_eFailureType"); +constexpr uint8_t kFailureFlushedCallbackQueue = 0; +constexpr uint8_t kFailurePipeFail = 1; + +struct SteamShutdown { + char _placeholder; +}; + +struct GameOverlayActivated { + bool m_bActive; + uint8_t _pad[7]; +}; +static_assert(sizeof(GameOverlayActivated) == 8, "GameOverlayActivated size"); +static_assert(offsetof(GameOverlayActivated, m_bActive) == 0, "off m_bActive"); + +struct EncryptedAppTicketResponse { + int32_t m_eResult; +}; +static_assert(sizeof(EncryptedAppTicketResponse) == 4, "EncryptedAppTicketResponse size"); +static_assert(offsetof(EncryptedAppTicketResponse, m_eResult) == 0, "off m_eResult"); + +struct GetAuthSessionTicketResponse { + uint32_t m_hAuthTicket; + int32_t m_eResult; +}; +static_assert(sizeof(GetAuthSessionTicketResponse) == 8, "GetAuthSessionTicketResponse size"); +static_assert(offsetof(GetAuthSessionTicketResponse, m_hAuthTicket) == 0, "off m_hAuthTicket"); +static_assert(offsetof(GetAuthSessionTicketResponse, m_eResult) == 4, "off m_eResult"); + +struct GetTicketForWebApiResponse { + uint32_t m_hAuthTicket; + int32_t m_eResult; + int32_t m_cubTicket; + uint8_t m_rgubTicket[2560]; +}; +static_assert(sizeof(GetTicketForWebApiResponse) == 2572, + "GetTicketForWebApiResponse size"); +static_assert(offsetof(GetTicketForWebApiResponse, m_hAuthTicket) == 0, "off m_hAuthTicket"); +static_assert(offsetof(GetTicketForWebApiResponse, m_eResult) == 4, "off m_eResult"); +static_assert(offsetof(GetTicketForWebApiResponse, m_cubTicket) == 8, "off m_cubTicket"); +static_assert(offsetof(GetTicketForWebApiResponse, m_rgubTicket) == 12, "off m_rgubTicket"); + +struct LeaderboardFindResult { + uint64_t m_hSteamLeaderboard; + uint8_t m_bLeaderboardFound; + uint8_t _pad[7]; +}; +static_assert(sizeof(LeaderboardFindResult) == 16, "LeaderboardFindResult size"); +static_assert(offsetof(LeaderboardFindResult, m_hSteamLeaderboard) == 0, "off m_hSteamLeaderboard"); +static_assert(offsetof(LeaderboardFindResult, m_bLeaderboardFound) == 8, "off m_bLeaderboardFound"); + +struct LeaderboardScoresDownloaded { + uint64_t m_hSteamLeaderboard; + uint64_t m_hSteamLeaderboardEntries; + int32_t m_cEntryCount; + uint32_t _pad; +}; +static_assert(sizeof(LeaderboardScoresDownloaded) == 24, "LeaderboardScoresDownloaded size"); +static_assert(offsetof(LeaderboardScoresDownloaded, m_hSteamLeaderboard) == 0, "off m_hSteamLeaderboard"); +static_assert(offsetof(LeaderboardScoresDownloaded, m_hSteamLeaderboardEntries) == 8, "off m_hSteamLeaderboardEntries"); +static_assert(offsetof(LeaderboardScoresDownloaded, m_cEntryCount) == 16, "off m_cEntryCount"); + +struct LeaderboardScoreUploaded { + uint8_t m_bSuccess; + uint8_t _pad0[7]; + uint64_t m_hSteamLeaderboard; + int32_t m_nScore; + uint8_t m_bScoreChanged; + uint8_t _pad1[3]; + int32_t m_nGlobalRankNew; + int32_t m_nGlobalRankPrevious; +}; +static_assert(sizeof(LeaderboardScoreUploaded) == 32, "LeaderboardScoreUploaded size"); +static_assert(offsetof(LeaderboardScoreUploaded, m_bSuccess) == 0, "off m_bSuccess"); +static_assert(offsetof(LeaderboardScoreUploaded, m_hSteamLeaderboard) == 8, "off m_hSteamLeaderboard"); +static_assert(offsetof(LeaderboardScoreUploaded, m_nScore) == 16, "off m_nScore"); +static_assert(offsetof(LeaderboardScoreUploaded, m_bScoreChanged) == 20, "off m_bScoreChanged"); +static_assert(offsetof(LeaderboardScoreUploaded, m_nGlobalRankNew) == 24, "off m_nGlobalRankNew"); +static_assert(offsetof(LeaderboardScoreUploaded, m_nGlobalRankPrevious) == 28, "off m_nGlobalRankPrevious"); + +struct NumberOfCurrentPlayers { + uint8_t m_bSuccess; + uint8_t _pad0[3]; + int32_t m_cPlayers; +}; +static_assert(sizeof(NumberOfCurrentPlayers) == 8, "NumberOfCurrentPlayers size"); +static_assert(offsetof(NumberOfCurrentPlayers, m_bSuccess) == 0, "off m_bSuccess"); +static_assert(offsetof(NumberOfCurrentPlayers, m_cPlayers) == 4, "off m_cPlayers"); + +struct GlobalAchievementPercentagesReady { + uint64_t m_nGameID; + int32_t m_eResult; + uint32_t _pad; +}; +static_assert(sizeof(GlobalAchievementPercentagesReady) == 16, "GlobalAchievementPercentagesReady size"); +static_assert(offsetof(GlobalAchievementPercentagesReady, m_nGameID) == 0, "off m_nGameID"); +static_assert(offsetof(GlobalAchievementPercentagesReady, m_eResult) == 8, "off m_eResult"); + +struct LeaderboardUGCSet { + int32_t m_eResult; + uint32_t _pad; + uint64_t m_hSteamLeaderboard; +}; +static_assert(sizeof(LeaderboardUGCSet) == 16, "LeaderboardUGCSet size"); +static_assert(offsetof(LeaderboardUGCSet, m_eResult) == 0, "off m_eResult"); +static_assert(offsetof(LeaderboardUGCSet, m_hSteamLeaderboard) == 8, "off m_hSteamLeaderboard"); + +struct GlobalStatsReceived { + uint64_t m_nGameID; + int32_t m_eResult; + uint32_t _pad; +}; +static_assert(sizeof(GlobalStatsReceived) == 16, "GlobalStatsReceived size"); +static_assert(offsetof(GlobalStatsReceived, m_nGameID) == 0, "off m_nGameID"); +static_assert(offsetof(GlobalStatsReceived, m_eResult) == 8, "off m_eResult"); + +struct LobbyMatchList { + uint32_t m_nLobbiesMatching; +}; +static_assert(sizeof(LobbyMatchList) == 4, "LobbyMatchList size"); +static_assert(offsetof(LobbyMatchList, m_nLobbiesMatching) == 0, "off m_nLobbiesMatching"); + +struct LobbyCreated { + int32_t m_eResult; + uint32_t _pad; + uint64_t m_ulSteamIDLobby; +}; +static_assert(sizeof(LobbyCreated) == 16, "LobbyCreated size"); +static_assert(offsetof(LobbyCreated, m_eResult) == 0, "off m_eResult"); +static_assert(offsetof(LobbyCreated, m_ulSteamIDLobby) == 8, "off m_ulSteamIDLobby"); + +struct LobbyEnter { + uint64_t m_ulSteamIDLobby; + uint32_t m_rgfChatPermissions; + uint8_t m_bLocked; + uint8_t _pad[3]; + uint32_t m_EChatRoomEnterResponse; + uint32_t _trail; +}; +static_assert(sizeof(LobbyEnter) == 24, "LobbyEnter size"); +static_assert(offsetof(LobbyEnter, m_ulSteamIDLobby) == 0, "off m_ulSteamIDLobby"); +static_assert(offsetof(LobbyEnter, m_rgfChatPermissions) == 8, "off m_rgfChatPermissions"); +static_assert(offsetof(LobbyEnter, m_bLocked) == 12, "off m_bLocked"); +static_assert(offsetof(LobbyEnter, m_EChatRoomEnterResponse) == 16, "off m_EChatRoomEnterResponse"); + +struct SteamInventoryEligiblePromoItemDefIDs { + int32_t m_result; + uint32_t _pad0; + uint64_t m_steamID; + int32_t m_numEligiblePromoItemDefs; + uint8_t m_bCachedData; + uint8_t _pad1[3]; +}; +static_assert(sizeof(SteamInventoryEligiblePromoItemDefIDs) == 24, + "SteamInventoryEligiblePromoItemDefIDs size"); +static_assert(offsetof(SteamInventoryEligiblePromoItemDefIDs, m_result) == 0, "off m_result"); +static_assert(offsetof(SteamInventoryEligiblePromoItemDefIDs, m_steamID) == 8, "off m_steamID"); +static_assert(offsetof(SteamInventoryEligiblePromoItemDefIDs, m_numEligiblePromoItemDefs) == 16, "off m_numEligiblePromoItemDefs"); +static_assert(offsetof(SteamInventoryEligiblePromoItemDefIDs, m_bCachedData) == 20, "off m_bCachedData"); + +struct SteamInventoryStartPurchaseResult { + int32_t m_result; + uint32_t _pad; + uint64_t m_ulOrderID; + uint64_t m_ulTransID; +}; +static_assert(sizeof(SteamInventoryStartPurchaseResult) == 24, + "SteamInventoryStartPurchaseResult size"); +static_assert(offsetof(SteamInventoryStartPurchaseResult, m_result) == 0, "off m_result"); +static_assert(offsetof(SteamInventoryStartPurchaseResult, m_ulOrderID) == 8, "off m_ulOrderID"); +static_assert(offsetof(SteamInventoryStartPurchaseResult, m_ulTransID) == 16, "off m_ulTransID"); + +struct SteamInventoryRequestPricesResult { + int32_t m_result; + char m_rgchCurrency[4]; +}; +static_assert(sizeof(SteamInventoryRequestPricesResult) == 8, + "SteamInventoryRequestPricesResult size"); +static_assert(offsetof(SteamInventoryRequestPricesResult, m_result) == 0, "off m_result"); +static_assert(offsetof(SteamInventoryRequestPricesResult, m_rgchCurrency) == 4, "off m_rgchCurrency"); + +struct ClanOfficerListResponse { + uint64_t m_steamIDClan; + int32_t m_cOfficers; + uint8_t m_bSuccess; + uint8_t _pad[3]; +}; +static_assert(sizeof(ClanOfficerListResponse) == 16, + "ClanOfficerListResponse size"); +static_assert(offsetof(ClanOfficerListResponse, m_steamIDClan) == 0, "off m_steamIDClan"); +static_assert(offsetof(ClanOfficerListResponse, m_cOfficers) == 8, "off m_cOfficers"); +static_assert(offsetof(ClanOfficerListResponse, m_bSuccess) == 12, "off m_bSuccess"); + +struct DownloadClanActivityCountsResult { + uint8_t m_bSuccess; +}; +static_assert(sizeof(DownloadClanActivityCountsResult) == 1, + "DownloadClanActivityCountsResult size"); + +struct JoinClanChatRoomCompletionResult { + uint64_t m_steamIDClanChat; + int32_t m_eChatRoomEnterResponse; + uint32_t _pad; +}; +static_assert(sizeof(JoinClanChatRoomCompletionResult) == 16, + "JoinClanChatRoomCompletionResult size"); +static_assert(offsetof(JoinClanChatRoomCompletionResult, m_steamIDClanChat) == 0, "off m_steamIDClanChat"); +static_assert(offsetof(JoinClanChatRoomCompletionResult, m_eChatRoomEnterResponse) == 8, "off m_eChatRoomEnterResponse"); + +struct EquippedProfileItems { + int32_t m_eResult; + uint32_t _pad0; + uint64_t m_steamID; + uint8_t m_bHasAnimatedAvatar; + uint8_t m_bHasAvatarFrame; + uint8_t m_bHasProfileModifier; + uint8_t m_bHasProfileBackground; + uint8_t m_bHasMiniProfileBackground; + uint8_t _pad1[3]; +}; +static_assert(sizeof(EquippedProfileItems) == 24, + "EquippedProfileItems size"); +static_assert(offsetof(EquippedProfileItems, m_eResult) == 0, "off m_eResult"); +static_assert(offsetof(EquippedProfileItems, m_steamID) == 8, "off m_steamID"); +static_assert(offsetof(EquippedProfileItems, m_bHasAnimatedAvatar) == 16, "off m_bHasAnimatedAvatar"); +static_assert(offsetof(EquippedProfileItems, m_bHasAvatarFrame) == 17, "off m_bHasAvatarFrame"); +static_assert(offsetof(EquippedProfileItems, m_bHasProfileModifier) == 18, "off m_bHasProfileModifier"); +static_assert(offsetof(EquippedProfileItems, m_bHasProfileBackground) == 19, "off m_bHasProfileBackground"); +static_assert(offsetof(EquippedProfileItems, m_bHasMiniProfileBackground) == 20, "off m_bHasMiniProfileBackground"); + +struct FriendsGetFollowerCount { + int32_t m_eResult; + uint32_t _pad0; + uint64_t m_steamID; + int32_t m_nCount; + uint32_t _pad1; +}; +static_assert(sizeof(FriendsGetFollowerCount) == 24, + "FriendsGetFollowerCount size"); +static_assert(offsetof(FriendsGetFollowerCount, m_eResult) == 0, "off m_eResult"); +static_assert(offsetof(FriendsGetFollowerCount, m_steamID) == 8, "off m_steamID"); +static_assert(offsetof(FriendsGetFollowerCount, m_nCount) == 16, "off m_nCount"); + +struct FriendsIsFollowing { + int32_t m_eResult; + uint32_t _pad0; + uint64_t m_steamID; + uint8_t m_bIsFollowing; + uint8_t _pad1[7]; +}; +static_assert(sizeof(FriendsIsFollowing) == 24, + "FriendsIsFollowing size"); +static_assert(offsetof(FriendsIsFollowing, m_eResult) == 0, "off m_eResult"); +static_assert(offsetof(FriendsIsFollowing, m_steamID) == 8, "off m_steamID"); +static_assert(offsetof(FriendsIsFollowing, m_bIsFollowing) == 16, "off m_bIsFollowing"); + +struct FriendsEnumerateFollowingList { + int32_t m_eResult; + uint32_t _pad; + uint64_t m_rgSteamID[50]; + int32_t m_nResultsReturned; + int32_t m_nTotalResultCount; +}; +static_assert(sizeof(FriendsEnumerateFollowingList) == 416, + "FriendsEnumerateFollowingList size"); +static_assert(offsetof(FriendsEnumerateFollowingList, m_eResult) == 0, "off m_eResult"); +static_assert(offsetof(FriendsEnumerateFollowingList, m_rgSteamID) == 8, "off m_rgSteamID"); +static_assert(offsetof(FriendsEnumerateFollowingList, m_nResultsReturned) == 408, "off m_nResultsReturned"); +static_assert(offsetof(FriendsEnumerateFollowingList, m_nTotalResultCount) == 412, "off m_nTotalResultCount"); + +struct RemoteStorageDownloadUGCResult { + int32_t m_eResult; + uint32_t _pad0; + uint64_t m_hFile; + uint32_t m_nAppID; + int32_t m_nSizeInBytes; + char m_pchFileName[260]; + uint32_t _pad1; + uint64_t m_ulSteamIDOwner; +}; +static_assert(sizeof(RemoteStorageDownloadUGCResult) == 296, + "RemoteStorageDownloadUGCResult size"); +static_assert(offsetof(RemoteStorageDownloadUGCResult, m_eResult) == 0, "off m_eResult"); +static_assert(offsetof(RemoteStorageDownloadUGCResult, m_hFile) == 8, "off m_hFile"); +static_assert(offsetof(RemoteStorageDownloadUGCResult, m_nAppID) == 16, "off m_nAppID"); +static_assert(offsetof(RemoteStorageDownloadUGCResult, m_nSizeInBytes) == 20, "off m_nSizeInBytes"); +static_assert(offsetof(RemoteStorageDownloadUGCResult, m_pchFileName) == 24, "off m_pchFileName"); +static_assert(offsetof(RemoteStorageDownloadUGCResult, m_ulSteamIDOwner) == 288, "off m_ulSteamIDOwner"); + +struct RemoteStorageSubscribePublishedFileResult { + int32_t m_eResult; + uint32_t _pad; + uint64_t m_nPublishedFileId; +}; +static_assert(sizeof(RemoteStorageSubscribePublishedFileResult) == 16, + "RemoteStorageSubscribePublishedFileResult size"); +static_assert(offsetof(RemoteStorageSubscribePublishedFileResult, m_eResult) == 0, + "off m_eResult"); +static_assert(offsetof(RemoteStorageSubscribePublishedFileResult, m_nPublishedFileId) == 8, + "off m_nPublishedFileId"); + +struct RemoteStorageUnsubscribePublishedFileResult { + int32_t m_eResult; + uint32_t _pad; + uint64_t m_nPublishedFileId; +}; +static_assert(sizeof(RemoteStorageUnsubscribePublishedFileResult) == 16, + "RemoteStorageUnsubscribePublishedFileResult size"); +static_assert(offsetof(RemoteStorageUnsubscribePublishedFileResult, m_eResult) == 0, + "off m_eResult"); +static_assert(offsetof(RemoteStorageUnsubscribePublishedFileResult, m_nPublishedFileId) == 8, + "off m_nPublishedFileId"); + +struct SteamUGCQueryCompleted { + uint64_t m_handle; + int32_t m_eResult; + uint32_t m_unNumResultsReturned; + uint32_t m_unTotalMatchingResults; + uint8_t m_bCachedData; + char m_rgchNextCursor[256]; + uint8_t _pad[3]; +}; +static_assert(sizeof(SteamUGCQueryCompleted) == 280, + "SteamUGCQueryCompleted size"); +static_assert(offsetof(SteamUGCQueryCompleted, m_handle) == 0, "off m_handle"); +static_assert(offsetof(SteamUGCQueryCompleted, m_eResult) == 8, "off m_eResult"); +static_assert(offsetof(SteamUGCQueryCompleted, m_unNumResultsReturned) == 12, "off m_unNumResultsReturned"); +static_assert(offsetof(SteamUGCQueryCompleted, m_unTotalMatchingResults) == 16, "off m_unTotalMatchingResults"); +static_assert(offsetof(SteamUGCQueryCompleted, m_bCachedData) == 20, "off m_bCachedData"); +static_assert(offsetof(SteamUGCQueryCompleted, m_rgchNextCursor) == 21, "off m_rgchNextCursor"); + +struct SteamUGCRequestUGCDetailsResultMinimal { + int32_t m_eResult; + uint32_t _pad; +}; +static_assert(sizeof(SteamUGCRequestUGCDetailsResultMinimal) == 8, + "SteamUGCRequestUGCDetailsResultMinimal size"); + +struct SteamAPICallCompleted { + uint64_t m_hAsyncCall; + int32_t m_iCallback; + uint32_t m_cubParam; +}; +static_assert(sizeof(SteamAPICallCompleted) == 16, + "SteamAPICallCompleted size"); +static_assert(offsetof(SteamAPICallCompleted, m_hAsyncCall) == 0, "off m_hAsyncCall"); +static_assert(offsetof(SteamAPICallCompleted, m_iCallback) == 8, "off m_iCallback"); +static_assert(offsetof(SteamAPICallCompleted, m_cubParam) == 12, "off m_cubParam"); + +struct CheckFileSignature { + int32_t m_eCheckFileSignature; +}; +static_assert(sizeof(CheckFileSignature) == 4, "CheckFileSignature size"); +static_assert(offsetof(CheckFileSignature, m_eCheckFileSignature) == 0, + "off m_eCheckFileSignature"); + +struct StoreAuthURLResponse { + char m_szURL[512]; +}; +static_assert(sizeof(StoreAuthURLResponse) == 512, "StoreAuthURLResponse size"); +static_assert(offsetof(StoreAuthURLResponse, m_szURL) == 0, "off m_szURL"); + +struct MarketEligibilityResponse { + bool m_bAllowed; + uint8_t _pad0[3]; + int32_t m_eNotAllowedReason; + uint32_t m_rtAllowedAtTime; + int32_t m_cdaySteamGuardRequiredDays; + int32_t m_cdayNewDeviceCooldown; +}; +static_assert(sizeof(MarketEligibilityResponse) == 20, "MarketEligibilityResponse size"); +static_assert(offsetof(MarketEligibilityResponse, m_bAllowed) == 0, "off m_bAllowed"); +static_assert(offsetof(MarketEligibilityResponse, m_eNotAllowedReason) == 4, "off m_eNotAllowedReason"); +static_assert(offsetof(MarketEligibilityResponse, m_rtAllowedAtTime) == 8, "off m_rtAllowedAtTime"); +static_assert(offsetof(MarketEligibilityResponse, m_cdaySteamGuardRequiredDays) == 12, "off m_cdaySteamGuardRequiredDays"); +static_assert(offsetof(MarketEligibilityResponse, m_cdayNewDeviceCooldown) == 16, "off m_cdayNewDeviceCooldown"); + +struct DurationControl { + int32_t m_eResult; + uint32_t m_appid; + bool m_bApplicable; + uint8_t _pad0[3]; + int32_t m_csecsLast5h; + int32_t m_progress; + int32_t m_notification; + int32_t m_csecsToday; + int32_t m_csecsRemaining; +}; +static_assert(sizeof(DurationControl) == 32, "DurationControl size"); +static_assert(offsetof(DurationControl, m_eResult) == 0, "off m_eResult"); +static_assert(offsetof(DurationControl, m_appid) == 4, "off m_appid"); +static_assert(offsetof(DurationControl, m_bApplicable) == 8, "off m_bApplicable"); +static_assert(offsetof(DurationControl, m_csecsLast5h) == 12, "off m_csecsLast5h"); +static_assert(offsetof(DurationControl, m_progress) == 16, "off m_progress"); +static_assert(offsetof(DurationControl, m_notification) == 20, "off m_notification"); +static_assert(offsetof(DurationControl, m_csecsToday) == 24, "off m_csecsToday"); +static_assert(offsetof(DurationControl, m_csecsRemaining) == 28, "off m_csecsRemaining"); + +struct ValidateAuthTicketResponse { + uint64_t m_SteamID; + int32_t m_eAuthSessionResponse; + uint32_t _pad; + uint64_t m_OwnerSteamID; +}; +static_assert(sizeof(ValidateAuthTicketResponse) == 24, "ValidateAuthTicketResponse size"); +static_assert(offsetof(ValidateAuthTicketResponse, m_SteamID) == 0, "off m_SteamID"); +static_assert(offsetof(ValidateAuthTicketResponse, m_eAuthSessionResponse) == 8, "off m_eAuthSessionResponse"); +static_assert(offsetof(ValidateAuthTicketResponse, m_OwnerSteamID) == 16, "off m_OwnerSteamID"); + +struct SteamServersDisconnected { + int32_t m_eResult; +}; +static_assert(sizeof(SteamServersDisconnected) == 4, "SteamServersDisconnected size"); +static_assert(offsetof(SteamServersDisconnected, m_eResult) == 0, "off m_eResult"); + +struct SetPersonaNameResponse { + bool m_bSuccess; + bool m_bLocalSuccess; + uint8_t _pad[2]; + int32_t m_result; +}; +static_assert(sizeof(SetPersonaNameResponse) == 8, "SetPersonaNameResponse size"); +static_assert(offsetof(SetPersonaNameResponse, m_bSuccess) == 0, "off m_bSuccess"); +static_assert(offsetof(SetPersonaNameResponse, m_bLocalSuccess) == 1, "off m_bLocalSuccess"); +static_assert(offsetof(SetPersonaNameResponse, m_result) == 4, "off m_result"); + +struct RemoteStorageAppSyncedClient { + uint32_t m_nAppID; + int32_t m_eResult; + int32_t m_unNumDownloads; +}; +static_assert(sizeof(RemoteStorageAppSyncedClient) == 12, + "RemoteStorageAppSyncedClient size"); +static_assert(offsetof(RemoteStorageAppSyncedClient, m_nAppID) == 0, "off m_nAppID"); +static_assert(offsetof(RemoteStorageAppSyncedClient, m_eResult) == 4, "off m_eResult"); +static_assert(offsetof(RemoteStorageAppSyncedClient, m_unNumDownloads) == 8, "off m_unNumDownloads"); + +struct RemoteStorageFileWriteAsyncComplete { + int32_t m_eResult; +}; +static_assert(sizeof(RemoteStorageFileWriteAsyncComplete) == 4, + "RemoteStorageFileWriteAsyncComplete size"); +static_assert(offsetof(RemoteStorageFileWriteAsyncComplete, m_eResult) == 0, + "off m_eResult"); + +struct RemoteStorageFileReadAsyncComplete { + uint64_t m_hFileReadAsync; + int32_t m_eResult; + uint32_t m_nOffset; + uint32_t m_cubRead; + uint32_t _pad; +}; +static_assert(sizeof(RemoteStorageFileReadAsyncComplete) == 24, + "RemoteStorageFileReadAsyncComplete size"); +static_assert(offsetof(RemoteStorageFileReadAsyncComplete, m_hFileReadAsync) == 0, "off m_hFileReadAsync"); +static_assert(offsetof(RemoteStorageFileReadAsyncComplete, m_eResult) == 8, "off m_eResult"); +static_assert(offsetof(RemoteStorageFileReadAsyncComplete, m_nOffset) == 12, "off m_nOffset"); +static_assert(offsetof(RemoteStorageFileReadAsyncComplete, m_cubRead) == 16, "off m_cubRead"); + +struct RemoteStorageFileShareResult { + int32_t m_eResult; + uint32_t _pad0; + uint64_t m_hFile; + char m_rgchFilename[260]; + uint8_t _pad1[4]; +}; +static_assert(sizeof(RemoteStorageFileShareResult) == 280, + "RemoteStorageFileShareResult size"); +static_assert(offsetof(RemoteStorageFileShareResult, m_eResult) == 0, "off m_eResult"); +static_assert(offsetof(RemoteStorageFileShareResult, m_hFile) == 8, "off m_hFile"); +static_assert(offsetof(RemoteStorageFileShareResult, m_rgchFilename) == 16, "off m_rgchFilename"); + +struct FileDetailsResult { + int32_t m_eResult; + uint32_t _pad0; + uint64_t m_ulFileSize; + uint8_t m_FileSHA[20]; + uint32_t m_unFlags; +}; +static_assert(sizeof(FileDetailsResult) == 40, + "FileDetailsResult size"); +static_assert(offsetof(FileDetailsResult, m_eResult) == 0, "off m_eResult"); +static_assert(offsetof(FileDetailsResult, m_ulFileSize) == 8, "off m_ulFileSize"); +static_assert(offsetof(FileDetailsResult, m_FileSHA) == 16, "off m_FileSHA"); +static_assert(offsetof(FileDetailsResult, m_unFlags) == 36, "off m_unFlags"); + +struct PersonaStateChange { + uint64_t m_ulSteamID; + int32_t m_nChangeFlags; + uint32_t _pad; +}; +static_assert(sizeof(PersonaStateChange) == 16, "PersonaStateChange size"); +static_assert(offsetof(PersonaStateChange, m_ulSteamID) == 0, "off m_ulSteamID"); +static_assert(offsetof(PersonaStateChange, m_nChangeFlags) == 8, "off m_nChangeFlags"); + +struct AvatarImageLoaded { + uint64_t m_steamID; + int32_t m_iImage; + int32_t m_iWide; + int32_t m_iTall; +}; +static_assert(sizeof(AvatarImageLoaded) == 24, "AvatarImageLoaded size"); +static_assert(offsetof(AvatarImageLoaded, m_steamID) == 0, "off m_steamID"); +static_assert(offsetof(AvatarImageLoaded, m_iImage) == 8, "off m_iImage"); +static_assert(offsetof(AvatarImageLoaded, m_iWide) == 12, "off m_iWide"); +static_assert(offsetof(AvatarImageLoaded, m_iTall) == 16, "off m_iTall"); + +struct FriendRichPresenceUpdate { + uint64_t m_steamIDFriend; + uint32_t m_nAppID; +}; +static_assert(sizeof(FriendRichPresenceUpdate) == 16, "FriendRichPresenceUpdate size"); +static_assert(offsetof(FriendRichPresenceUpdate, m_steamIDFriend) == 0, "off m_steamIDFriend"); +static_assert(offsetof(FriendRichPresenceUpdate, m_nAppID) == 8, "off m_nAppID"); + +constexpr size_t kAchievementNameMax = 128; +struct UserAchievementStored { + uint64_t m_nGameID; + bool m_bGroupAchievement; + char m_rgchAchievementName[kAchievementNameMax]; + uint32_t m_nCurProgress; + uint32_t m_nMaxProgress; +}; +static_assert(sizeof(UserAchievementStored) == 152, "UserAchievementStored size"); +static_assert(offsetof(UserAchievementStored, m_nGameID) == 0, "off m_nGameID"); +static_assert(offsetof(UserAchievementStored, m_bGroupAchievement) == 8, "off m_bGroupAchievement"); +static_assert(offsetof(UserAchievementStored, m_rgchAchievementName)== 9, "off m_rgchAchievementName"); +static_assert(offsetof(UserAchievementStored, m_nCurProgress) == 140, "off m_nCurProgress"); +static_assert(offsetof(UserAchievementStored, m_nMaxProgress) == 144, "off m_nMaxProgress"); + +} // namespace wn_libsteamclient::callbacks diff --git a/app/src/main/cpp/wn-libsteamclient/include/wn_libsteamclient/runtime_state.h b/app/src/main/cpp/wn-libsteamclient/include/wn_libsteamclient/runtime_state.h new file mode 100644 index 000000000..dcaea13b3 --- /dev/null +++ b/app/src/main/cpp/wn-libsteamclient/include/wn_libsteamclient/runtime_state.h @@ -0,0 +1,270 @@ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace wn_libsteamclient { + +using HSteamPipe = int; +using HSteamUser = int; + +struct CallbackMsg { + int user; + int id; + std::vector body; +}; + +struct PushedState { + std::atomic steam_id{0}; + std::atomic account_id{0}; + std::atomic persona_state{0}; // EPersonaState (0=Offline … 7=Invisible) + std::atomic app_id{0}; // for ISteamUtils.GetAppID + std::atomic ip_country_set{0}; + std::atomic server_realtime{0}; // unix epoch reported by CM at the anchor moment + std::atomic server_realtime_anchor_local_ms{0}; // local steady_clock::now() ms when server_realtime was captured + std::string persona_name; // guarded by state_mutex() + std::string ip_country; // guarded by state_mutex() + std::string ui_language; // guarded by state_mutex() + + std::unordered_set owned_apps; + std::unordered_set installed_apps; + std::unordered_map app_install_dirs; + std::unordered_map app_current_beta; + struct DlProgress { + uint64_t bytes_downloaded = 0; + uint64_t bytes_total = 0; + }; + std::unordered_map app_dl_progress; + std::unordered_map app_cloud_remote_dirs; + std::unordered_set app_low_violence; + std::unordered_set app_vac_banned; + std::atomic account_phone_verified{false}; + std::atomic account_two_factor_enabled{false}; + std::atomic account_phone_identifying{false}; + std::atomic account_phone_requires_verification{false}; + std::unordered_set apps_marked_corrupt; + + struct WorkshopItemInfo { + std::string install_dir; // absolute Windows path or wine guest path + uint64_t size_bytes = 0; // total disk footprint for ISteamUGC slot 73 bytes + uint32_t timestamp = 0; // unix32 last-update — slot 73 timestamp + bool installed = true; // currently we only ever push installed entries + }; + std::unordered_map> + subscribed_workshop_items; + + std::unordered_map>> + inventory_item_defs; + + struct LobbyMember { + std::string persona_name; + std::unordered_map data; + }; + struct LobbyState { + uint32_t app_id = 0; + uint64_t owner_sid = 0; + int32_t max_members = 0; + int32_t lobby_type = 0; + int32_t lobby_flags = 0; + bool joinable = true; + uint32_t game_server_ip = 0; + uint16_t game_server_port = 0; + uint64_t game_server_sid = 0; + std::unordered_map data; + std::unordered_map members; + }; + std::unordered_map active_lobbies; + std::vector lobby_match_list; + + struct LobbyChatEntry { + uint64_t sender_sid; + uint8_t chat_type; // EChatEntryType (1=ChatMsg) + std::vector body; + }; + std::unordered_map> + lobby_chat_buffer; + + struct P2PSessionState { + uint64_t last_session_error = 0; // EP2PSessionError on close + bool connection_active = false; + bool connecting = false; + uint32_t bytes_queued_for_send = 0; + uint32_t remote_ip = 0; // little-endian / IPv4 + uint16_t remote_port = 0; + bool using_relay = false; + }; + std::unordered_map active_p2p_sessions; + + struct P2PInboundPacket { + uint64_t sender_sid; + int32_t channel; + std::vector body; + }; + std::unordered_map> p2p_inbound_queue; + std::atomic p2p_relay_allowed{true}; + + struct OverlayRequest { + std::string kind; // "webpage" | "store" | "user" | "invite" | "dialog" + std::string arg1; // URL / dialog name + uint64_t sid = 0; // user SID / lobby SID (depending on kind) + uint32_t app_id = 0; // store appid + }; + std::deque overlay_request_queue; + std::unordered_map friend_steam_levels; + std::unordered_map player_nicknames; + std::atomic self_player_level{0}; + std::unordered_map self_game_badges; + + struct LicenseEntry { + uint32_t package_id = 0; + uint32_t owner_id = 0; // AccountID; != self_account → family-shared + uint32_t time_created = 0; // unix32 of purchase + uint32_t license_type = 0; // ELicenseType + uint32_t flags = 0; // ELicenseFlags bitfield + int32_t change_number = 0; // PICS change_number on this package + int32_t minute_limit = 0; // 0 = unlimited; >0 = timed-trial cap (minutes) + int32_t minutes_used = 0; // current playtime against minute_limit + }; + std::unordered_map licenses; + + std::unordered_map> app_source_packages; + struct DlcEntry { + uint32_t app_id; + std::string name; + bool available = true; // DLC currently purchasable + }; + std::unordered_map> app_dlcs; + std::unordered_map> app_installed_depots; + std::unordered_map app_names; + std::unordered_map app_build_ids; + std::vector friends; + std::unordered_map friend_persona_names; + std::unordered_map friend_persona_states; + std::unordered_map friend_game_played_app; + + using RichPresenceMap = std::vector>; + std::unordered_map rich_presence; + + struct ImageEntry { + int32_t width = 0; + int32_t height = 0; + std::vector rgba; // size = width*height*4 + }; + std::unordered_map image_registry; + struct FriendAvatarHandles { + int32_t small = 0; + int32_t medium = 0; + int32_t large = 0; + }; + std::unordered_map friend_avatars; + int32_t next_image_handle = 1; + + std::unordered_map> friend_avatar_hashes; + + std::atomic cloud_enabled_account{true}; + std::atomic cloud_enabled_app{false}; + std::atomic cloud_quota_total{0}; + std::atomic cloud_quota_available{0}; + struct CloudFileEntry { + std::string name; + int32_t size = 0; // ISteamRemoteStorage uses int32 here + int64_t timestamp = 0; // unix seconds + }; + std::vector cloud_files; + + struct AchievementEntry { + std::string api_name; // internal name ("ACH_FIRST_BLOOD") + std::unordered_map display_names; + std::unordered_map descriptions; + std::string icon; // icon URL or empty + bool hidden = false; + bool achieved = false; + uint32_t unlock_time = 0; // unix seconds + int32_t icon_handle = 0; // synthetic id for slot-11 + bool pending_store = false; + int32_t block_id = -1; + int32_t bit_index = 0; + }; + std::vector achievements; + std::unordered_map achievement_index; + std::unordered_map stats_int; + std::unordered_map stats_float; + std::unordered_map stat_name_to_id; + std::unordered_set dirty_stats_int; + std::unordered_set dirty_stats_float; + + struct AvgRateAccum { + double total_count = 0.0; + double total_time = 0.0; + }; + std::unordered_map stats_avg_rate; + std::atomic stats_ready{false}; + + std::atomic overlay_active{false}; + + struct AuthTicket { + uint32_t h_ticket; // returned to caller + uint32_t app_id; + std::vector body; + }; + std::atomic next_auth_ticket_handle{1}; + std::unordered_map auth_tickets; + + std::string launch_command_line; + std::atomic app_is_family_shared{false}; + + std::unordered_map> encrypted_app_tickets; + std::atomic encrypted_app_ticket_eresult{0}; +}; + +PushedState& pushed(); + +struct CallResultMsg { + uint64_t h_call; + int callback_id; + bool io_failure; + std::vector body; +}; + +struct State { + std::atomic pipe{0}; + std::atomic user{0}; + + std::atomic logged_on{false}; + std::atomic connected{false}; + + std::mutex callback_mu; + std::deque callback_queue; + std::vector last_param; + + std::mutex call_results_mu; + std::unordered_map call_results_pending; + uint64_t next_api_call_handle = 1; +}; + +State& state(); +std::mutex& state_mutex(); + +HSteamPipe alloc_pipe(); +bool release_pipe(HSteamPipe pipe); +HSteamUser alloc_global_user(HSteamPipe pipe); +void release_user(HSteamPipe pipe, HSteamUser user); + +void push_callback(int user, int id, const void* data, size_t n); + +void push_call_result(uint64_t h_call, int callback_id, + const void* data, size_t n, bool io_failure); + +uint64_t alloc_api_call_handle(); + +void set_logged_on(bool logged_on, int eresult_on_disconnect = 6); + +} // namespace wn_libsteamclient diff --git a/app/src/main/cpp/wn-libsteamclient/include/wn_libsteamclient/tcp_services.h b/app/src/main/cpp/wn-libsteamclient/include/wn_libsteamclient/tcp_services.h new file mode 100644 index 000000000..b90a489e1 --- /dev/null +++ b/app/src/main/cpp/wn-libsteamclient/include/wn_libsteamclient/tcp_services.h @@ -0,0 +1,12 @@ +#pragma once + + +#include + +namespace wn_libsteamclient { + +bool start_tcp_services(); + +int accepted_connection_count(); + +} // namespace wn_libsteamclient diff --git a/app/src/main/cpp/wn-libsteamclient/include/wn_steam/cm_bridge.h b/app/src/main/cpp/wn-libsteamclient/include/wn_steam/cm_bridge.h new file mode 100644 index 000000000..660edc217 --- /dev/null +++ b/app/src/main/cpp/wn-libsteamclient/include/wn_steam/cm_bridge.h @@ -0,0 +1,285 @@ + +#pragma once + +#include +#include + +namespace wn_steam { +class CMClient; + +void wn_cm_bridge_set_active(std::shared_ptr client); +void wn_cm_bridge_clear_active(); + +} // namespace wn_steam + +extern "C" { + +__attribute__((visibility("default"))) +bool wn_cm_set_persona_state(int32_t persona_state); + +__attribute__((visibility("default"))) +bool wn_cm_set_persona_name(const char* name, int32_t persona_state); + +__attribute__((visibility("default"))) +bool wn_cm_request_user_info(uint64_t steam_id, int32_t flags); + +__attribute__((visibility("default"))) +bool wn_cm_request_user_info_bulk(const uint64_t* sids, size_t count, int32_t flags); + +__attribute__((visibility("default"))) +bool wn_cm_get_cached_app_ownership_ticket(uint32_t app_id, + uint8_t* out_buf, + size_t max_len, + size_t* out_len); + +__attribute__((visibility("default"))) +bool wn_cm_bridge_inject_test_ownership_ticket(uint32_t app_id, + const uint8_t* bytes, + size_t len); + +__attribute__((visibility("default"))) +bool wn_cm_notify_games_played(uint32_t app_id); + +__attribute__((visibility("default"))) +bool wn_cm_set_rich_presence(uint32_t app_id, + const char* const* keys, + const char* const* values, + size_t count); + +__attribute__((visibility("default"))) +bool wn_cm_store_user_stats(uint32_t app_id, + uint32_t crc_stats, + const uint32_t* stat_ids, + const uint32_t* stat_values, + size_t count); + +struct WnCmRichPresenceKV { + const char* key; + const char* value; +}; + +struct WnCmPersonaEvent { + uint64_t sid; // CSteamID64 (0 if message lacks one) + uint32_t persona_state; // EPersonaState (0..7); UINT32_MAX = absent + uint32_t game_played_app; // AppID; 0 = not in game + const char* name; // UTF-8, null if absent + const uint8_t* avatar_hash; // SHA-1 (typically 20 bytes), null if absent + size_t avatar_hash_len; + const WnCmRichPresenceKV* rp_pairs; + size_t rp_count; +}; + +typedef void (*WnCmPersonaObserverFn)(const WnCmPersonaEvent*); + +__attribute__((visibility("default"))) +void wn_cm_bridge_register_persona_observer(WnCmPersonaObserverFn fn); + +__attribute__((visibility("default"))) +void wn_cm_bridge_dispatch_persona(const WnCmPersonaEvent* ev); + + +typedef void (*WnCmLogonStateObserverFn)(bool logged_on); + +__attribute__((visibility("default"))) +void wn_cm_bridge_register_logon_state_observer(WnCmLogonStateObserverFn fn); + +__attribute__((visibility("default"))) +void wn_cm_bridge_dispatch_logon_state(bool logged_on); + +__attribute__((visibility("default"))) +void wn_cm_bridge_inject_test_logon_state(bool logged_on); + + +typedef void (*WnCmFriendsListObserverFn)(const uint64_t* sids, size_t count); + +__attribute__((visibility("default"))) +void wn_cm_bridge_register_friends_list_observer(WnCmFriendsListObserverFn fn); + +__attribute__((visibility("default"))) +void wn_cm_bridge_dispatch_friends_list(const uint64_t* sids, size_t count); + +__attribute__((visibility("default"))) +void wn_cm_bridge_inject_test_friends_list(const uint64_t* sids, size_t count); + + +struct WnCmLicenseEntry { + uint32_t package_id; + uint32_t owner_id; + uint32_t time_created; + uint32_t license_type; + uint32_t flags; + int32_t change_number; + int32_t minute_limit; // 0 = unlimited + int32_t minutes_used; +}; + +typedef void (*WnCmLicenseListObserverFn)(const WnCmLicenseEntry* licenses, + size_t count); + +__attribute__((visibility("default"))) +void wn_cm_bridge_register_license_list_observer(WnCmLicenseListObserverFn fn); + +__attribute__((visibility("default"))) +void wn_cm_bridge_dispatch_license_list(const WnCmLicenseEntry* licenses, + size_t count); + +__attribute__((visibility("default"))) +void wn_cm_bridge_inject_test_license_list(const WnCmLicenseEntry* licenses, + size_t count); + + +struct WnCmAccountInfo { + const char* persona_name; // may be NULL if not provided + size_t persona_name_len; + const char* ip_country; // may be NULL if not provided + size_t ip_country_len; + bool two_factor_enabled; + bool phone_verified; + bool phone_identifying; + bool phone_requires_verification; +}; + +typedef void (*WnCmAccountInfoObserverFn)(const WnCmAccountInfo* info); + +__attribute__((visibility("default"))) +void wn_cm_bridge_register_account_info_observer(WnCmAccountInfoObserverFn fn); + +__attribute__((visibility("default"))) +void wn_cm_bridge_dispatch_account_info(const WnCmAccountInfo* info); + +__attribute__((visibility("default"))) +void wn_cm_bridge_inject_test_account_info(const WnCmAccountInfo* info); + +typedef void (*WnCmServerRealTimeObserverFn)(uint32_t server_realtime); + +__attribute__((visibility("default"))) +void wn_cm_bridge_register_server_realtime_observer(WnCmServerRealTimeObserverFn fn); + +__attribute__((visibility("default"))) +void wn_cm_bridge_dispatch_server_realtime(uint32_t server_realtime); + + +typedef struct WnCmLobbyEntry { + uint64_t steam_id; + int32_t max_members; + int32_t num_members; + int32_t lobby_type; + int32_t lobby_flags; + int32_t ping_ms; + int64_t weight; + float distance; +} WnCmLobbyEntry; + +typedef struct WnCmLobbyMember { + uint64_t steam_id; + const char* persona_name; // UTF-8, valid only during callback + const uint8_t* metadata_bytes; + size_t metadata_len; +} WnCmLobbyMember; + +typedef struct WnCmLobbyData { + uint64_t steam_id_lobby; + uint64_t steam_id_owner; + uint32_t app_id; + int32_t max_members; + int32_t num_members; + int32_t lobby_type; + int32_t lobby_flags; + const uint8_t* metadata_bytes; + size_t metadata_len; + const WnCmLobbyMember* members; + size_t member_count; +} WnCmLobbyData; + +typedef void (*WnCmLobbyListCb)(uint64_t hCall, + int32_t eresult, + const WnCmLobbyEntry* lobbies, + size_t count); + +typedef void (*WnCmLobbyDataObserverFn)(const WnCmLobbyData* data); + +__attribute__((visibility("default"))) +bool wn_cm_lobby_get_list(uint64_t hCall, + uint32_t app_id, + int32_t num_lobbies_requested, + const char* const* filter_keys, + const char* const* filter_values, + const int32_t* filter_comparisons, + const int32_t* filter_types, + size_t filter_count, + WnCmLobbyListCb cb); + +__attribute__((visibility("default"))) +void wn_cm_bridge_register_lobby_data_observer(WnCmLobbyDataObserverFn fn); + +typedef void (*WnCmLobbyCreatedCb)(uint64_t hCall, + int32_t eresult, + uint64_t lobby_sid); +__attribute__((visibility("default"))) +bool wn_cm_lobby_create(uint64_t hCall, + uint32_t app_id, + int32_t lobby_type, + int32_t max_members, + WnCmLobbyCreatedCb cb); + +typedef void (*WnCmLobbyJoinedCb)(uint64_t hCall, + int32_t chat_room_enter_response, + uint64_t lobby_sid); +__attribute__((visibility("default"))) +bool wn_cm_lobby_join(uint64_t hCall, + uint32_t app_id, + uint64_t lobby_sid, + WnCmLobbyJoinedCb cb); + +__attribute__((visibility("default"))) +bool wn_cm_lobby_leave(uint32_t app_id, uint64_t lobby_sid); + +typedef void (*WnCmLobbySetDataCb)(uint64_t hCall, int32_t eresult); +__attribute__((visibility("default"))) +bool wn_cm_lobby_set_data(uint64_t hCall, + uint32_t app_id, + uint64_t lobby_sid, + uint64_t steam_id_member, + const uint8_t* metadata, size_t metadata_len, + int32_t max_members, int32_t lobby_type, + int32_t lobby_flags, + WnCmLobbySetDataCb cb); + +__attribute__((visibility("default"))) +bool wn_cm_lobby_send_chat(uint32_t app_id, uint64_t lobby_sid, + const uint8_t* data, size_t n); + +typedef void (*WnCmLobbySetOwnerCb)(uint64_t hCall, int32_t eresult); +__attribute__((visibility("default"))) +bool wn_cm_lobby_set_owner(uint64_t hCall, + uint32_t app_id, + uint64_t lobby_sid, + uint64_t new_owner_sid, + WnCmLobbySetOwnerCb cb); + +__attribute__((visibility("default"))) +bool wn_cm_lobby_invite_user(uint32_t app_id, + uint64_t lobby_sid, + uint64_t invitee_sid); + +typedef void (*WnCmLobbyChatMsgObserverFn)(uint64_t lobby_sid, + uint64_t sender_sid, + const uint8_t* data, + size_t n); +__attribute__((visibility("default"))) +void wn_cm_bridge_register_lobby_chat_msg_observer(WnCmLobbyChatMsgObserverFn fn); + +typedef void (*WnCmLobbyMembershipObserverFn)(int32_t joined /*1=joined,0=left*/, + uint64_t lobby_sid, + uint64_t user_sid, + const char* persona_name); +__attribute__((visibility("default"))) +void wn_cm_bridge_register_lobby_membership_observer(WnCmLobbyMembershipObserverFn fn); + +__attribute__((visibility("default"))) +void wn_cm_bridge_start_state_sync_poller(void); + +__attribute__((visibility("default"))) +void wn_cm_bridge_stop_state_sync_poller(void); + +} // extern "C" diff --git a/app/src/main/cpp/wn-libsteamclient/src/api_entry.cpp b/app/src/main/cpp/wn-libsteamclient/src/api_entry.cpp new file mode 100644 index 000000000..35ca1a93a --- /dev/null +++ b/app/src/main/cpp/wn-libsteamclient/src/api_entry.cpp @@ -0,0 +1,478 @@ + +#include "wn_libsteamclient/runtime_state.h" +#include "wn_libsteamclient/callbacks.h" +#include "wn_libsteamclient/callback_registry.h" + +#include +#include +#include +#include + +namespace lsc = wn_libsteamclient; + +#define WN_TAG "WnLibSteamClient" +#define WN_LOGI(...) __android_log_print(ANDROID_LOG_INFO, WN_TAG, __VA_ARGS__) +#define WN_LOGW(...) __android_log_print(ANDROID_LOG_WARN, WN_TAG, __VA_ARGS__) +#define WN_LOGE(...) __android_log_print(ANDROID_LOG_ERROR, WN_TAG, __VA_ARGS__) + +extern "C" __attribute__((visibility("default"))) +void* CreateInterface(const char* version_name, int* return_code); + + +extern "C" __attribute__((visibility("default"))) +int Steam_CreateSteamPipe(void) { + auto pipe = lsc::alloc_pipe(); + if (pipe == 0) { + pipe = lsc::state().pipe.load(); + } + WN_LOGI("Steam_CreateSteamPipe() -> %d", pipe); + return pipe; +} + +extern "C" __attribute__((visibility("default"))) +bool Steam_BReleaseSteamPipe(int pipe) { + int h_user = lsc::state().user.load(); + namespace cb = wn_libsteamclient::callbacks; + cb::SteamShutdown sd_payload{}; + lsc::push_callback(h_user, cb::kSteamShutdown, &sd_payload, 0); + bool ok = lsc::release_pipe(pipe); + WN_LOGI("Steam_BReleaseSteamPipe(%d) -> %d (SteamShutdown_t emitted)", + pipe, ok ? 1 : 0); + return ok; +} + +extern "C" __attribute__((visibility("default"))) +int Steam_ConnectToGlobalUser(int pipe) { + auto user = lsc::alloc_global_user(pipe); + WN_LOGI("Steam_ConnectToGlobalUser(pipe=%d) -> %d", pipe, user); + return user; +} + +extern "C" __attribute__((visibility("default"))) +int Steam_CreateGlobalUser(int* pipe_inout) { + if (!pipe_inout) return 0; + int pipe = lsc::alloc_pipe(); + if (pipe == 0) pipe = lsc::state().pipe.load(); // already exists; reuse + int user = lsc::alloc_global_user(pipe); + WN_LOGI("Steam_CreateGlobalUser(*pipe=%d) -> user=%d", pipe, user); + return user; +} + +extern "C" __attribute__((visibility("default"))) +int Steam_CreateLocalUser(int* pipe_inout, int /*account_type*/) { + return Steam_CreateGlobalUser(pipe_inout); +} + +extern "C" __attribute__((visibility("default"))) +void Steam_ReleaseUser(int pipe, int user) { + lsc::release_user(pipe, user); + WN_LOGI("Steam_ReleaseUser(pipe=%d, user=%d)", pipe, user); +} + + +extern "C" __attribute__((visibility("default"))) +bool Steam_BLoggedOn(int pipe, int user) { + auto& s = lsc::state(); + if (pipe == 0 || user == 0) return false; + if (s.pipe.load() != pipe || s.user.load() != user) return false; + return s.logged_on.load(); +} + +extern "C" __attribute__((visibility("default"))) +bool Steam_BConnected(int pipe, int user) { + auto& s = lsc::state(); + if (pipe == 0 || user == 0) return false; + if (s.pipe.load() != pipe || s.user.load() != user) return false; + return s.connected.load(); +} + +extern "C" __attribute__((visibility("default"))) +void Steam_LogOn(int pipe, int user, uint64_t /*steamid*/) { + auto& s = lsc::state(); + if (s.pipe.load() == pipe && s.user.load() == user) { + s.logged_on.store(true); + s.connected.store(true); + } +} + +extern "C" __attribute__((visibility("default"))) +void Steam_LogOff(int pipe, int user) { + auto& s = lsc::state(); + if (s.pipe.load() == pipe && s.user.load() == user) { + s.logged_on.store(false); + s.connected.store(false); + } + WN_LOGI("Steam_LogOff(pipe=%d, user=%d)", pipe, user); +} + + +extern "C" __attribute__((visibility("default"))) +bool Steam_BGetCallback(int pipe, void* cb_msg) { + if (!cb_msg) return false; + auto& s = lsc::state(); + std::lock_guard lk(s.callback_mu); + if (s.callback_queue.empty()) return false; + lsc::CallbackMsg msg = std::move(s.callback_queue.front()); + s.callback_queue.pop_front(); + s.last_param = std::move(msg.body); + auto* dst = static_cast(cb_msg); + int h_user = msg.user; + int i_cb = msg.id; + void* pubParam = s.last_param.empty() ? nullptr : s.last_param.data(); + int cubParam = static_cast(s.last_param.size()); + std::memcpy(dst + 0, &h_user, sizeof(int)); + std::memcpy(dst + 4, &i_cb, sizeof(int)); + std::memcpy(dst + 8, &pubParam, sizeof(void*)); + std::memcpy(dst + 16, &cubParam, sizeof(int)); + return true; +} + +extern "C" __attribute__((visibility("default"))) +void Steam_FreeLastCallback(int /*pipe*/) { + auto& s = lsc::state(); + std::lock_guard lk(s.callback_mu); + s.last_param.clear(); + s.last_param.shrink_to_fit(); +} + +extern "C" __attribute__((visibility("default"))) +bool Steam_GetAPICallResult(int /*pipe*/, uint64_t hCall, + void* pCallback, int cubCallback, + int iCallbackExpected, bool* pbFailed) { + if (hCall == 0) return false; + auto& s = lsc::state(); + std::lock_guard lk(s.call_results_mu); + auto it = s.call_results_pending.find(hCall); + if (it == s.call_results_pending.end()) return false; + const auto& msg = it->second; + if (iCallbackExpected != 0 && msg.callback_id != iCallbackExpected) { + return false; + } + if (pCallback && cubCallback > 0 && !msg.body.empty()) { + size_t n = std::min(static_cast(cubCallback), msg.body.size()); + std::memcpy(pCallback, msg.body.data(), n); + } + if (pbFailed) *pbFailed = msg.io_failure; + s.call_results_pending.erase(it); + return true; +} + +extern "C" __attribute__((visibility("default"))) +bool Steam_IsAPICallCompleted(int /*pipe*/, uint64_t hCall, bool* pbFailed) { + if (hCall == 0) return false; + auto& s = lsc::state(); + std::lock_guard lk(s.call_results_mu); + auto it = s.call_results_pending.find(hCall); + if (it == s.call_results_pending.end()) return false; + if (pbFailed) *pbFailed = it->second.io_failure; + return true; +} + + +extern "C" __attribute__((visibility("default"))) +bool Steam_IsKnownInterface(const char* /*pszInterfaceName*/) { + return false; +} + +extern "C" __attribute__((visibility("default"))) +void Steam_NotifyMissingInterface(int /*pipe*/, const char* iface) { + WN_LOGW("Steam_NotifyMissingInterface: %s", iface ? iface : "(null)"); +} + +extern "C" __attribute__((visibility("default"))) +void Steam_SetLocalIPBinding(int /*ip*/, int /*port*/) {} + +extern "C" __attribute__((visibility("default"))) +void Steam_ReleaseThreadLocalMemory(int /*bThreadExit*/) {} + +extern "C" __attribute__((visibility("default"))) +int Steam_GetGSHandle(int /*pipe*/, int /*user*/) { return 0; } + +extern "C" __attribute__((visibility("default"))) +bool Steam_InitiateGameConnection(int /*pipe*/, int /*user*/, + void* /*pAuthBlob*/, int /*cbMaxAuthBlob*/, + uint64_t /*steamIDGameServer*/, + uint32_t /*unIPServer*/, + uint16_t /*usPortServer*/, + bool /*bSecure*/) { + return false; +} + +extern "C" __attribute__((visibility("default"))) +void Steam_TerminateGameConnection(int /*pipe*/, int /*user*/, + uint32_t /*unIPServer*/, + uint16_t /*usPortServer*/) {} + + +extern "C" __attribute__((visibility("default"))) bool Steam_GSBLoggedOn (int, int) { return false; } +extern "C" __attribute__((visibility("default"))) bool Steam_GSBSecure (int, int) { return false; } +extern "C" __attribute__((visibility("default"))) uint64_t Steam_GSGetSteamID(int, int) { return 0; } +extern "C" __attribute__((visibility("default"))) void Steam_GSLogOff (int, int) {} +extern "C" __attribute__((visibility("default"))) bool Steam_GSLogOn (int, int, uint64_t, uint32_t, uint16_t, uint16_t, int, bool) { return false; } +extern "C" __attribute__((visibility("default"))) void Steam_GSRemoveUserConnect (int, int, uint64_t) {} +extern "C" __attribute__((visibility("default"))) bool Steam_GSSendSteam2UserConnect (int, int, uint64_t, uint32_t, uint32_t, uint16_t, const void*, int) { return false; } +extern "C" __attribute__((visibility("default"))) bool Steam_GSSendSteam3UserConnect (int, int, uint64_t, uint32_t, const void*, int) { return false; } +extern "C" __attribute__((visibility("default"))) void Steam_GSSendUserDisconnect (int, int, uint64_t, uint32_t) {} +extern "C" __attribute__((visibility("default"))) bool Steam_GSSendUserStatusResponse (int, int, uint64_t, int, const void*, int) { return false; } +extern "C" __attribute__((visibility("default"))) void Steam_GSSetServerType (int, int, uint32_t, uint32_t, uint16_t, uint16_t, uint16_t, const char*, const char*, bool) {} +extern "C" __attribute__((visibility("default"))) void Steam_GSSetSpawnCount (int, int, uint32_t) {} +extern "C" __attribute__((visibility("default"))) bool Steam_GSUpdateStatus (int, int, int, int, int, const char*, const char*, const char*) { return false; } +extern "C" __attribute__((visibility("default"))) bool Steam_GSGetSteam2GetEncryptionKeyToSendToNewClient(int, int, void*, uint32_t*, uint32_t) { return false; } + + +extern "C" __attribute__((visibility("default"))) +bool SteamAPI_RestartAppIfNecessary(uint32_t unOwnAppID) { + WN_LOGI("SteamAPI_RestartAppIfNecessary(appId=%u) -> false (no restart needed)", + static_cast(unOwnAppID)); + return false; +} + +extern "C" __attribute__((visibility("default"))) +bool SteamAPI_Init(void) { + int pipe = lsc::alloc_pipe(); + if (pipe == 0) pipe = lsc::state().pipe.load(); + int user = lsc::alloc_global_user(pipe); + (void)user; + WN_LOGI("SteamAPI_Init() -> true (pipe=%d user=%d)", pipe, user); + return true; +} + +extern "C" __attribute__((visibility("default"))) +int SteamAPI_InitEx(char* p_outErrMsg) { + SteamAPI_Init(); + if (p_outErrMsg) p_outErrMsg[0] = '\0'; + return 0; // k_ESteamAPIInitResult_OK +} + +extern "C" __attribute__((visibility("default"))) +void SteamAPI_Shutdown(void) { + int pipe = lsc::state().pipe.load(); + if (pipe != 0) { + Steam_BReleaseSteamPipe(pipe); + } + WN_LOGI("SteamAPI_Shutdown()"); +} + +extern "C" __attribute__((visibility("default"))) +bool SteamAPI_IsSteamRunning(void) { return true; } + +extern "C" __attribute__((visibility("default"))) +int SteamAPI_GetHSteamPipe(void) { return lsc::state().pipe.load(); } +extern "C" __attribute__((visibility("default"))) +int SteamAPI_GetHSteamUser(void) { return lsc::state().user.load(); } + +extern "C" __attribute__((visibility("default"))) +void SteamAPI_ReleaseCurrentThreadMemory(void) {} + +extern "C" __attribute__((visibility("default"))) +void SteamAPI_SetTryCatchCallbacks(bool /*bTryCatchCallbacks*/) {} + +extern "C" __attribute__((visibility("default"))) +void SteamAPI_WriteMiniDump(uint32_t /*uStructuredExceptionCode*/, + void* /*pvExceptionInfo*/, + uint32_t /*uBuildID*/) {} + +extern "C" __attribute__((visibility("default"))) +void SteamAPI_RegisterCallback(void* pCallback, int iCallback) { + lsc::register_callback(pCallback, iCallback); +} +extern "C" __attribute__((visibility("default"))) +void SteamAPI_UnregisterCallback(void* pCallback) { + lsc::unregister_callback(pCallback); +} +extern "C" __attribute__((visibility("default"))) +void SteamAPI_RegisterCallResult(void* pCallback, uint64_t hAPICall) { + lsc::register_call_result(pCallback, hAPICall); +} +extern "C" __attribute__((visibility("default"))) +void SteamAPI_UnregisterCallResult(void* pCallback, uint64_t hAPICall) { + lsc::unregister_call_result(pCallback, hAPICall); +} + +extern "C" __attribute__((visibility("default"))) +void SteamAPI_RunCallbacks(void) { + auto& s = lsc::state(); + + for (;;) { + lsc::CallbackMsg msg; + { + std::lock_guard lk(s.callback_mu); + if (s.callback_queue.empty()) break; + msg = std::move(s.callback_queue.front()); + s.callback_queue.pop_front(); + } + auto cbs = lsc::find_callbacks(msg.id); + for (void* cb : cbs) { + using RunFn = void (*)(void* /*this*/, void* /*pvParam*/); + void* payload = msg.body.empty() ? nullptr : msg.body.data(); + long** vtable_ptr = reinterpret_cast(cb); + long* vtable = *vtable_ptr; + auto run = reinterpret_cast(vtable[0]); + run(cb, payload); + } + } + + struct PendingDispatch { + uint64_t h_call; + bool io_failure; + std::vector body; + std::vector cbs; + }; + std::vector to_dispatch; + { + std::lock_guard lk(s.call_results_mu); + for (auto it = s.call_results_pending.begin(); + it != s.call_results_pending.end(); ) { + auto cbs = lsc::find_call_result_cbs(it->first); + if (cbs.empty()) { + ++it; + continue; + } + to_dispatch.push_back({ + it->first, it->second.io_failure, + std::move(it->second.body), std::move(cbs)}); + it = s.call_results_pending.erase(it); + } + } + for (auto& d : to_dispatch) { + void* payload = d.body.empty() ? nullptr : d.body.data(); + for (void* cb : d.cbs) { + using RunResultFn = void (*)(void* /*this*/, void* /*pvParam*/, + bool /*bIOFailure*/, + uint64_t /*hSteamAPICall*/); + long** vtable_ptr = reinterpret_cast(cb); + long* vtable = *vtable_ptr; + auto run = reinterpret_cast(vtable[1]); + run(cb, payload, d.io_failure, d.h_call); + } + } +} + +extern "C" __attribute__((visibility("default"))) +const char* SteamAPI_GetSteamInstallPath(void) { return nullptr; } + +extern "C" __attribute__((visibility("default"))) +void* SteamClient(void) { + int err = 0; + return CreateInterface("SteamClient020", &err); +} + +extern "C" __attribute__((visibility("default"))) +bool SteamGameServer_Init(uint32_t /*unIP*/, uint16_t /*usGamePort*/, + uint16_t /*usQueryPort*/, int /*eServerMode*/, + const char* /*pchVersionString*/) { + WN_LOGI("SteamGameServer_Init -> false (game-server mode not implemented)"); + return false; +} +extern "C" __attribute__((visibility("default"))) +void SteamGameServer_Shutdown(void) {} +extern "C" __attribute__((visibility("default"))) +bool SteamGameServer_BSecure(void) { return false; } +extern "C" __attribute__((visibility("default"))) +uint64_t SteamGameServer_GetSteamID(void) { return 0; } +extern "C" __attribute__((visibility("default"))) +int SteamGameServer_GetHSteamPipe(void) { return 0; } +extern "C" __attribute__((visibility("default"))) +int SteamGameServer_GetHSteamUser(void) { return 0; } +extern "C" __attribute__((visibility("default"))) +void SteamGameServer_RunCallbacks(void) {} + +namespace { +struct CallbackMsgWire { + int32_t h_steam_user; + int32_t i_callback; + uint8_t* pub_param; + int32_t cub_param; + int32_t _pad; +}; +static_assert(sizeof(CallbackMsgWire) == 24, "CallbackMsg_t must be 24B"); + +std::atomic g_manual_dispatch_active{false}; +} // namespace + +extern "C" __attribute__((visibility("default"))) +void Breakpad_SteamMiniDumpInit(uint32_t /*unAppID*/, + const char* /*pchVersion*/, + const char* /*pchDate*/) {} +extern "C" __attribute__((visibility("default"))) +void Breakpad_SteamSendMiniDump(void* /*pvException*/, + uint32_t /*ulSeconds*/, + const char* /*pchAssertMsg*/) {} +extern "C" __attribute__((visibility("default"))) +void Breakpad_SteamSetAppID(uint32_t /*unAppID*/) {} +extern "C" __attribute__((visibility("default"))) +void Breakpad_SteamSetSteamID(uint64_t /*ulSteamID*/) {} +extern "C" __attribute__((visibility("default"))) +void Breakpad_SteamWriteMiniDumpSetComment(const char* /*pchMsg*/) {} +extern "C" __attribute__((visibility("default"))) +void Breakpad_SteamWriteMiniDumpUsingExceptionInfoWithBuildId( + unsigned int /*uStructuredExceptionCode*/, + void* /*pExceptionInfo*/, + unsigned int /*uBuildID*/) {} + +extern "C" __attribute__((visibility("default"))) +void SteamAPI_ManualDispatch_Init(void) { + g_manual_dispatch_active.store(true, std::memory_order_release); + WN_LOGI("SteamAPI_ManualDispatch_Init() — manual callback dispatch armed"); +} + +extern "C" __attribute__((visibility("default"))) +void SteamAPI_ManualDispatch_RunFrame(int /*hSteamPipe*/) { +} + +extern "C" __attribute__((visibility("default"))) +bool SteamAPI_ManualDispatch_GetNextCallback(int /*hSteamPipe*/, void* p_msg_out) { + if (!p_msg_out) return false; + auto& s = lsc::state(); + lsc::CallbackMsg msg; + { + std::lock_guard lk(s.callback_mu); + if (s.callback_queue.empty()) return false; + msg = std::move(s.callback_queue.front()); + s.callback_queue.pop_front(); + s.last_param = std::move(msg.body); + } + auto* out = static_cast(p_msg_out); + out->h_steam_user = msg.user; + out->i_callback = msg.id; + out->pub_param = s.last_param.empty() ? nullptr : s.last_param.data(); + out->cub_param = static_cast(s.last_param.size()); + out->_pad = 0; + return true; +} + +extern "C" __attribute__((visibility("default"))) +void SteamAPI_ManualDispatch_FreeLastCallback(int /*hSteamPipe*/) { + auto& s = lsc::state(); + std::lock_guard lk(s.callback_mu); + s.last_param.clear(); +} + +extern "C" __attribute__((visibility("default"))) +bool SteamAPI_ManualDispatch_GetAPICallResult(int /*hSteamPipe*/, + uint64_t hCall, + void* p_callback, + int cb_callback, + int i_callback_expected, + bool* pb_failed) { + auto& s = lsc::state(); + std::lock_guard lk(s.call_results_mu); + auto it = s.call_results_pending.find(hCall); + if (it == s.call_results_pending.end()) return false; + auto msg = std::move(it->second); + s.call_results_pending.erase(it); + if (i_callback_expected != 0 && msg.callback_id != i_callback_expected) { + const int got = msg.callback_id; + s.call_results_pending[hCall] = std::move(msg); + WN_LOGI("ManualDispatch_GetAPICallResult: hCall=0x%llx callback mismatch " + "(expected=%d got=%d) — re-queueing", + (unsigned long long)hCall, i_callback_expected, got); + return false; + } + if (p_callback && cb_callback > 0 && !msg.body.empty()) { + const int n = std::min(cb_callback, static_cast(msg.body.size())); + std::memcpy(p_callback, msg.body.data(), static_cast(n)); + } + if (pb_failed) *pb_failed = msg.io_failure; + return true; +} diff --git a/app/src/main/cpp/wn-libsteamclient/src/callback_registry.cpp b/app/src/main/cpp/wn-libsteamclient/src/callback_registry.cpp new file mode 100644 index 000000000..1aa3440a4 --- /dev/null +++ b/app/src/main/cpp/wn-libsteamclient/src/callback_registry.cpp @@ -0,0 +1,114 @@ +#include "wn_libsteamclient/callback_registry.h" + +#include +#include +#include +#include + +#define WN_TAG "WnLibSteamClient" +#define WN_LOGI(...) __android_log_print(ANDROID_LOG_INFO, WN_TAG, __VA_ARGS__) + +namespace wn_libsteamclient { + +namespace { + +std::mutex g_registry_mu; +std::unordered_map> g_registry; +size_t g_total_size = 0; + +inline uint8_t* flags_ptr(void* cb) { + return reinterpret_cast(cb) + kCCallbackBaseFlagsOffset; +} + +} // namespace + +void register_callback(void* cb, int iCallback) { + if (!cb) return; + std::lock_guard lk(g_registry_mu); + auto& bucket = g_registry[iCallback]; + for (void* existing : bucket) { + if (existing == cb) return; + } + bucket.push_back(cb); + ++g_total_size; + WN_LOGI("register_callback(cb=%p, iCallback=%d) total=%zu", + cb, iCallback, g_total_size); +} + +void unregister_callback(void* cb) { + if (!cb) return; + std::lock_guard lk(g_registry_mu); + for (auto& [id, bucket] : g_registry) { + for (auto it = bucket.begin(); it != bucket.end(); ) { + if (*it == cb) { + it = bucket.erase(it); + --g_total_size; + } else { + ++it; + } + } + } +} + +std::vector find_callbacks(int iCallback) { + std::lock_guard lk(g_registry_mu); + auto it = g_registry.find(iCallback); + if (it == g_registry.end()) return {}; + return it->second; // copy — caller invokes outside the lock +} + +size_t registry_size() { + std::lock_guard lk(g_registry_mu); + return g_total_size; +} + + +namespace { +std::mutex g_cr_mu; +std::unordered_map> g_cr_registry; +size_t g_cr_total_size = 0; +} // namespace + +void register_call_result(void* cb, uint64_t hCall) { + if (!cb || hCall == 0) return; + std::lock_guard lk(g_cr_mu); + auto& bucket = g_cr_registry[hCall]; + for (void* existing : bucket) { + if (existing == cb) return; // idempotent + } + bucket.push_back(cb); + ++g_cr_total_size; + WN_LOGI("register_call_result(cb=%p, hCall=%llu) total=%zu", + cb, static_cast(hCall), g_cr_total_size); +} + +void unregister_call_result(void* cb, uint64_t hCall) { + if (!cb) return; + std::lock_guard lk(g_cr_mu); + auto unbind_one = [&](std::vector& bucket) { + for (auto it = bucket.begin(); it != bucket.end(); ) { + if (*it == cb) { it = bucket.erase(it); --g_cr_total_size; } + else { ++it; } + } + }; + if (hCall == 0) { + for (auto& [_, bucket] : g_cr_registry) unbind_one(bucket); + } else { + auto it = g_cr_registry.find(hCall); + if (it != g_cr_registry.end()) unbind_one(it->second); + } +} + +std::vector find_call_result_cbs(uint64_t hCall) { + std::lock_guard lk(g_cr_mu); + auto it = g_cr_registry.find(hCall); + if (it == g_cr_registry.end()) return {}; + return it->second; +} + +size_t call_result_registry_size() { + std::lock_guard lk(g_cr_mu); + return g_cr_total_size; +} + +} // namespace wn_libsteamclient diff --git a/app/src/main/cpp/wn-libsteamclient/src/iclient_engine.cpp b/app/src/main/cpp/wn-libsteamclient/src/iclient_engine.cpp new file mode 100644 index 000000000..057370e78 --- /dev/null +++ b/app/src/main/cpp/wn-libsteamclient/src/iclient_engine.cpp @@ -0,0 +1,205 @@ + +#include "wn_libsteamclient/runtime_state.h" + +#include +#include +#include + +namespace wn_libsteamclient { + +#define WN_LOGI(...) __android_log_print(ANDROID_LOG_INFO, "WnLibSteamClient", __VA_ARGS__) +#define WN_LOGW(...) __android_log_print(ANDROID_LOG_WARN, "WnLibSteamClient", __VA_ARGS__) + + +class IClientUserImpl { +public: + virtual int GetHSteamUser() { return state().user.load(); } // 0 / 0x00 + + virtual void SetSteamID(uint64_t sid) { // 1 / 0x08 + pushed().steam_id.store(sid); + pushed().account_id.store(static_cast(sid & 0xFFFFFFFFu)); + WN_LOGI("IClientUser.SetSteamID(%llu)", + static_cast(sid)); + } + + virtual void _slot02() {} // 2 / 0x010 + virtual void _slot03() {} // 3 / 0x018 + virtual void _slot04() {} // 4 / 0x020 + virtual void _slot05() {} // 5 / 0x028 + virtual void _slot06() {} // 6 / 0x030 + virtual void _slot07() {} // 7 / 0x038 + virtual void _slot08() {} // 8 / 0x040 + virtual void _slot09() {} // 9 / 0x048 + virtual void _slot10() {} // 10 / 0x050 + virtual void _slot11() {} // 11 / 0x058 + virtual void _slot12() {} // 12 / 0x060 + virtual void _slot13() {} // 13 / 0x068 + virtual void _slot14() {} // 14 / 0x070 + virtual void _slot15() {} // 15 / 0x078 + virtual void _slot16() {} // 16 / 0x080 + virtual void _slot17() {} // 17 / 0x088 + virtual void _slot18() {} // 18 / 0x090 + virtual void _slot19() {} // 19 / 0x098 + virtual void _slot20() {} // 20 / 0x0A0 + virtual void _slot21() {} // 21 / 0x0A8 + virtual void _slot22() {} // 22 / 0x0B0 + virtual void _slot23() {} // 23 / 0x0B8 + virtual void _slot24() {} // 24 / 0x0C0 + virtual void _slot25() {} // 25 / 0x0C8 + virtual void _slot26() {} // 26 / 0x0D0 + virtual void _slot27() {} // 27 / 0x0D8 + virtual void _slot28() {} // 28 / 0x0E0 + virtual void _slot29() {} // 29 / 0x0E8 + virtual void _slot30() {} // 30 / 0x0F0 + virtual void _slot31() {} // 31 / 0x0F8 + virtual void _slot32() {} // 32 / 0x100 + virtual void _slot33() {} // 33 / 0x108 + virtual void _slot34() {} // 34 / 0x110 + virtual void _slot35() {} // 35 / 0x118 + virtual void _slot36() {} // 36 / 0x120 + virtual void _slot37() {} // 37 / 0x128 + virtual void _slot38() {} // 38 / 0x130 + virtual void _slot39() {} // 39 / 0x138 + virtual void _slot40() {} // 40 / 0x140 + virtual void _slot41() {} // 41 / 0x148 + virtual void _slot42() {} // 42 / 0x150 + virtual void _slot43() {} // 43 / 0x158 + virtual void _slot44() {} // 44 / 0x160 + virtual void _slot45() {} // 45 / 0x168 + virtual void _slot46() {} // 46 / 0x170 + virtual void _slot47() {} // 47 / 0x178 + virtual void _slot48() {} // 48 / 0x180 + + virtual bool IsAccountLoggedIn(const char* account) { // 49 / 0x188 + WN_LOGI("IClientUser.IsAccountLoggedIn(%s) -> 0 (no persisted session yet)", + account ? account : "(null)"); + return false; + } + + virtual void SetAccount(const char* account, const char* /*password*/, int /*remember*/) { // 50 / 0x190 + WN_LOGI("IClientUser.SetAccount(%s)", account ? account : "(null)"); + } + + virtual void _slot51() {} // 51 / 0x198 + virtual void _slot52() {} // 52 / 0x1A0 + virtual void _slot53() {} // 53 / 0x1A8 + + virtual bool SetLoginInformation(const char* account, + const char* /*password*/, + int /*remember*/) { // 54 / 0x1B0 + WN_LOGI("IClientUser.SetLoginInformation(%s, \"\", *)", + account ? account : "(null)"); + return true; + } + + virtual void _slot55() {} // 55 / 0x1B8 + + virtual void LogonWithRefreshToken(const char* token, const char* account) { // 56 / 0x1C0 + WN_LOGI("IClientUser.LogonWithRefreshToken(token=%zu bytes, account=%s)", + token ? std::strlen(token) : 0, + account ? account : "(null)"); + set_logged_on(true); + } +}; + + + +class IClientEngineImpl { +public: + virtual void* _slot00() { return nullptr; } // 0 / 0x00 + virtual void* _slot01() { return nullptr; } // 1 + virtual void* _slot02() { return nullptr; } // 2 + virtual void* _slot03() { return nullptr; } // 3 + virtual void* _slot04() { return nullptr; } // 4 + virtual void* _slot05() { return nullptr; } // 5 + virtual void* _slot06() { return nullptr; } // 6 + virtual void* _slot07() { return nullptr; } // 7 + + virtual void* GetIClientUser(int /*user*/, int /*pipe*/); // 8 + + virtual void* _slot09() { return nullptr; } + virtual void* _slot10() { return nullptr; } + virtual void* _slot11() { return nullptr; } + virtual void* _slot12() { return nullptr; } + virtual void* _slot13() { return nullptr; } + virtual void* _slot14() { return nullptr; } + virtual void* _slot15() { return nullptr; } + virtual void* _slot16() { return nullptr; } + virtual void* _slot17() { return nullptr; } + virtual void* _slot18() { return nullptr; } + virtual void* _slot19() { return nullptr; } + virtual void* _slot20() { return nullptr; } + virtual void* _slot21() { return nullptr; } + virtual void* _slot22() { return nullptr; } + virtual void* _slot23() { return nullptr; } + virtual void* _slot24() { return nullptr; } + virtual void* _slot25() { return nullptr; } + virtual void* _slot26() { return nullptr; } + virtual void* _slot27() { return nullptr; } + virtual void* _slot28() { return nullptr; } + virtual void* _slot29() { return nullptr; } + virtual void* _slot30() { return nullptr; } + virtual void* _slot31() { return nullptr; } + virtual void* _slot32() { return nullptr; } + virtual void* _slot33() { return nullptr; } + virtual void* _slot34() { return nullptr; } + virtual void* _slot35() { return nullptr; } + virtual void* _slot36() { return nullptr; } + virtual void* _slot37() { return nullptr; } + virtual void* _slot38() { return nullptr; } + virtual void* _slot39() { return nullptr; } + virtual void* _slot40() { return nullptr; } + virtual void* _slot41() { return nullptr; } + virtual void* _slot42() { return nullptr; } + virtual void* _slot43() { return nullptr; } + virtual void* _slot44() { return nullptr; } + virtual void* _slot45() { return nullptr; } + virtual void* _slot46() { return nullptr; } + virtual void* _slot47() { return nullptr; } + virtual void* _slot48() { return nullptr; } + virtual void* _slot49() { return nullptr; } + virtual void* _slot50() { return nullptr; } + virtual void* _slot51() { return nullptr; } + virtual void* _slot52() { return nullptr; } + virtual void* _slot53() { return nullptr; } + virtual void* _slot54() { return nullptr; } + virtual void* _slot55() { return nullptr; } + virtual void* _slot56() { return nullptr; } + virtual void* _slot57() { return nullptr; } + virtual void* _slot58() { return nullptr; } + virtual void* _slot59() { return nullptr; } + virtual void* _slot60() { return nullptr; } + virtual void* _slot61() { return nullptr; } + virtual void* _slot62() { return nullptr; } + virtual void* _slot63() { return nullptr; } + virtual void* _slot64() { return nullptr; } + virtual void* _slot65() { return nullptr; } + virtual void* _slot66() { return nullptr; } + virtual void* _slot67() { return nullptr; } + virtual void* _slot68() { return nullptr; } + virtual void* _slot69() { return nullptr; } + virtual void* _slot70() { return nullptr; } + virtual void* _slot71() { return nullptr; } + virtual void* _slot72() { return nullptr; } + virtual void* _slot73() { return nullptr; } + virtual void* _slot74() { return nullptr; } + virtual void* _slot75() { return nullptr; } + virtual void* _slot76() { return nullptr; } + virtual void* _slot77() { return nullptr; } + virtual void* _slot78() { return nullptr; } + virtual void* _slot79() { return nullptr; } +}; + + +static IClientUserImpl g_client_user; +static IClientEngineImpl g_client_engine; + +void* IClientEngineImpl::GetIClientUser(int /*user*/, int /*pipe*/) { + return &g_client_user; +} + +extern "C" void* wn_get_iclient_engine() { + return &g_client_engine; +} + +} // namespace wn_libsteamclient diff --git a/app/src/main/cpp/wn-libsteamclient/src/isteam_client.cpp b/app/src/main/cpp/wn-libsteamclient/src/isteam_client.cpp new file mode 100644 index 000000000..77e7de392 --- /dev/null +++ b/app/src/main/cpp/wn-libsteamclient/src/isteam_client.cpp @@ -0,0 +1,173 @@ + +#include "wn_libsteamclient/runtime_state.h" + +#include +#include +#include + +namespace wn_libsteamclient { + +extern "C" void* wn_get_isteam_utils(); +extern "C" void* wn_get_isteam_user(); +extern "C" void* wn_get_isteam_apps(); +extern "C" void* wn_get_isteam_friends(); +extern "C" void* wn_get_isteam_remote_storage(); +extern "C" void* wn_get_isteam_user_stats(); +extern "C" void* wn_get_isteam_inventory(); +extern "C" void* wn_get_isteam_screenshots(); +extern "C" void* wn_get_isteam_music(); +extern "C" void* wn_get_isteam_app_list(); +extern "C" void* wn_get_isteam_video(); +extern "C" void* wn_get_isteam_parental(); +extern "C" void* wn_get_isteam_matchmaking_servers(); +extern "C" void* wn_get_isteam_matchmaking(); +extern "C" void* wn_get_isteam_networking(); +extern "C" void* wn_get_isteam_ugc(); +extern "C" void* wn_get_isteam_game_server(); +extern "C" void* wn_get_isteam_music_remote(); +extern "C" void* wn_get_isteam_html_surface(); +extern "C" void* wn_get_isteam_input(); +extern "C" void* wn_get_isteam_parties(); +extern "C" void* wn_get_isteam_remote_play(); +extern "C" void* wn_get_isteam_networking_sockets(); +extern "C" void* wn_get_isteam_networking_utils(); +extern "C" void* wn_get_isteam_networking_messages(); +extern "C" void* wn_get_iclient_engine(); + +extern "C" void* CreateInterface(const char* version_name, int* return_code); + +class ISteamClientImpl { +public: + virtual int CreateSteamPipe() { + int pipe = alloc_pipe(); + if (pipe == 0) pipe = state().pipe.load(); + return pipe; + } + virtual bool BReleaseSteamPipe(int pipe) { return release_pipe(pipe); } + virtual int ConnectToGlobalUser(int pipe) { return alloc_global_user(pipe); } + virtual int CreateLocalUser(int* pipe_inout, int /*type*/) { + if (!pipe_inout) return 0; + int p = alloc_pipe(); if (p == 0) p = state().pipe.load(); + int u = alloc_global_user(p); + return u; + } + virtual void ReleaseUser(int pipe, int user) { release_user(pipe, user); } + virtual void* GetISteamUser(int /*u*/, int /*p*/, const char* /*v*/) { return wn_get_isteam_user(); } + virtual void* GetISteamGameServer(int, int, const char*) { return wn_get_isteam_game_server(); } + virtual void SetLocalIPBinding(uint32_t, uint16_t) {} + virtual void* GetISteamFriends(int /*u*/, int /*p*/, const char* /*v*/) { return wn_get_isteam_friends(); } + virtual void* GetISteamUtils(int /*p*/, const char* /*v*/) { return wn_get_isteam_utils(); } + virtual void* GetISteamMatchmaking(int, int, const char*) { return wn_get_isteam_matchmaking(); } + virtual void* GetISteamMatchmakingServers(int, int, const char*) { return wn_get_isteam_matchmaking_servers(); } + virtual void* GetISteamGenericInterface(int, int, const char* version) { + int err = 0; + return CreateInterface(version, &err); + } + virtual void* GetISteamUserStats(int /*u*/, int /*p*/, const char* /*v*/) { return wn_get_isteam_user_stats(); } + virtual void* GetISteamApps(int /*u*/, int /*p*/, const char* /*v*/) { return wn_get_isteam_apps(); } + virtual void* GetISteamNetworking(int, int, const char*) { return wn_get_isteam_networking(); } + virtual void* GetISteamRemoteStorage(int /*u*/, int /*p*/, const char* /*v*/) { return wn_get_isteam_remote_storage(); } + virtual void* GetISteamScreenshots(int, int, const char*) { return wn_get_isteam_screenshots(); } + virtual void* GetISteamUGC(int, int, const char*) { return wn_get_isteam_ugc(); } + virtual void* GetISteamAppList(int, int, const char*) { return wn_get_isteam_app_list(); } + virtual void* GetISteamMusic(int, int, const char*) { return wn_get_isteam_music(); } + virtual void* GetISteamMusicRemote(int, int, const char*) { return wn_get_isteam_music_remote(); } + virtual void* GetISteamHTMLSurface(int, int, const char*) { return wn_get_isteam_html_surface(); } + virtual void Set_SteamAPI_CPostAPIResultInProcess(void*) {} + virtual void Remove_SteamAPI_CPostAPIResultInProcess(void*) {} + virtual void Set_SteamAPI_CCheckCallbackRegisteredInProcess(void*) {} + virtual void* GetISteamInventory(int, int, const char*) { return wn_get_isteam_inventory(); } + virtual void* GetISteamVideo(int, int, const char*) { return wn_get_isteam_video(); } + virtual void* GetISteamParentalSettings(int, int, const char*) { return wn_get_isteam_parental(); } + virtual void* GetISteamInput(int, int, const char*) { return wn_get_isteam_input(); } + virtual void* GetISteamParties(int, int, const char*) { return wn_get_isteam_parties(); } + virtual void* GetISteamRemotePlay(int, int, const char*) { return wn_get_isteam_remote_play(); } +}; + +static ISteamClientImpl g_steam_client; + +} // namespace wn_libsteamclient + + +extern "C" __attribute__((visibility("default"))) +void* CreateInterface(const char* version_name, int* return_code) { + if (!version_name) { + if (return_code) *return_code = -1; + return nullptr; + } + __android_log_print(ANDROID_LOG_INFO, "WnLibSteamClient", + "CreateInterface(%s)", version_name); + if (std::strncmp(version_name, "SteamClient", 11) == 0) { + if (return_code) *return_code = 0; + return &wn_libsteamclient::g_steam_client; + } + if (std::strncmp(version_name, "SteamNetworkingSockets", 22) == 0) { + if (return_code) *return_code = 0; + return wn_libsteamclient::wn_get_isteam_networking_sockets(); + } + if (std::strncmp(version_name, "SteamNetworkingUtils", 20) == 0) { + if (return_code) *return_code = 0; + return wn_libsteamclient::wn_get_isteam_networking_utils(); + } + if (std::strncmp(version_name, "SteamNetworkingMessages", 23) == 0) { + if (return_code) *return_code = 0; + return wn_libsteamclient::wn_get_isteam_networking_messages(); + } + auto dispatch_iface = [&](const char* prefix, int prefix_len, + void* (*getter)()) -> void* { + if (std::strncmp(version_name, prefix, prefix_len) != 0) return nullptr; + if (return_code) *return_code = 0; + return getter(); + }; + if (void* p = dispatch_iface("SteamMatchMaking", 16, wn_libsteamclient::wn_get_isteam_matchmaking)) return p; + if (void* p = dispatch_iface("SteamMatchMakingServers", 23, wn_libsteamclient::wn_get_isteam_matchmaking_servers)) return p; + if (void* p = dispatch_iface("SteamUser", 9, wn_libsteamclient::wn_get_isteam_user)) return p; + if (void* p = dispatch_iface("SteamFriends", 12, wn_libsteamclient::wn_get_isteam_friends)) return p; + if (void* p = dispatch_iface("SteamUtils", 10, wn_libsteamclient::wn_get_isteam_utils)) return p; + if (void* p = dispatch_iface("STEAMAPPS_INTERFACE_VERSION", 26, wn_libsteamclient::wn_get_isteam_apps)) return p; + if (void* p = dispatch_iface("STEAMUSERSTATS_INTERFACE_VERSION", 31, wn_libsteamclient::wn_get_isteam_user_stats)) return p; + if (void* p = dispatch_iface("STEAMREMOTESTORAGE_INTERFACE_VERSION", 35, wn_libsteamclient::wn_get_isteam_remote_storage)) return p; + if (void* p = dispatch_iface("STEAMSCREENSHOTS_INTERFACE_VERSION", 33, wn_libsteamclient::wn_get_isteam_screenshots)) return p; + if (void* p = dispatch_iface("STEAMINVENTORY_INTERFACE_V", 26, wn_libsteamclient::wn_get_isteam_inventory)) return p; + if (void* p = dispatch_iface("STEAMVIDEO_INTERFACE_V", 22, wn_libsteamclient::wn_get_isteam_video)) return p; + if (void* p = dispatch_iface("STEAMMUSIC_INTERFACE_VERSION", 28, wn_libsteamclient::wn_get_isteam_music)) return p; + if (void* p = dispatch_iface("STEAMMUSICREMOTE_INTERFACE_VERSION", 33, wn_libsteamclient::wn_get_isteam_music_remote)) return p; + if (void* p = dispatch_iface("STEAMHTMLSURFACE_INTERFACE_",27, wn_libsteamclient::wn_get_isteam_html_surface)) return p; + if (void* p = dispatch_iface("STEAMUGC_INTERFACE_VERSION", 26, wn_libsteamclient::wn_get_isteam_ugc)) return p; + if (void* p = dispatch_iface("STEAMAPPLIST_INTERFACE_VERSION", 30, wn_libsteamclient::wn_get_isteam_app_list)) return p; + if (void* p = dispatch_iface("STEAMPARENTALSETTINGS_INTERFACE_VERSION", 38, wn_libsteamclient::wn_get_isteam_parental)) return p; + if (void* p = dispatch_iface("SteamGameServer", 15, wn_libsteamclient::wn_get_isteam_game_server)) return p; + if (void* p = dispatch_iface("SteamNetworking", 15, wn_libsteamclient::wn_get_isteam_networking)) return p; + if (void* p = dispatch_iface("SteamInput", 10, wn_libsteamclient::wn_get_isteam_input)) return p; + if (void* p = dispatch_iface("SteamParties", 12, wn_libsteamclient::wn_get_isteam_parties)) return p; + if (void* p = dispatch_iface("SteamRemotePlay", 15, wn_libsteamclient::wn_get_isteam_remote_play)) return p; + if (std::strncmp(version_name, + "CLIENTENGINE_INTERFACE_VERSION", 30) == 0) { + if (return_code) *return_code = 0; + return wn_libsteamclient::wn_get_iclient_engine(); + } + if (return_code) *return_code = -1; + __android_log_print(ANDROID_LOG_WARN, "WnLibSteamClient", + "CreateInterface: unknown name='%s' — returning null", version_name); + return nullptr; +} + +extern "C" __attribute__((visibility("default"))) +void* SteamInternal_FindOrCreateUserInterface(int /*hSteamUser*/, + const char* version_name) { + int rc = 0; + return CreateInterface(version_name, &rc); +} + +extern "C" __attribute__((visibility("default"))) +void* SteamInternal_FindOrCreateGameServerInterface(int /*hSteamUser*/, + const char* version_name) { + int rc = 0; + return CreateInterface(version_name, &rc); +} + +extern "C" __attribute__((visibility("default"))) +void* SteamInternal_CreateInterface(const char* version_name) { + int rc = 0; + return CreateInterface(version_name, &rc); +} diff --git a/app/src/main/cpp/wn-libsteamclient/src/isteam_stubs.cpp b/app/src/main/cpp/wn-libsteamclient/src/isteam_stubs.cpp new file mode 100644 index 000000000..286b56b0a --- /dev/null +++ b/app/src/main/cpp/wn-libsteamclient/src/isteam_stubs.cpp @@ -0,0 +1,3804 @@ + +#include "wn_libsteamclient/runtime_state.h" +#include "wn_libsteamclient/callbacks.h" +#include "wn_steam/cm_bridge.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // std::getenv, std::strtoul — GetAppID env fallback +#include +#include +#include + +namespace lsc_cb = wn_libsteamclient::callbacks; + +namespace wn_libsteamclient { + +static std::mutex& async_read_mu() { + static std::mutex m; + return m; +} +static std::unordered_map>& async_read_buffers() { + static std::unordered_map> m; + return m; +} + +struct StreamSlot { + int fd = -1; + std::string tempPath; + std::string finalPath; + std::string name; // original pchFile passed to Open + int64_t bytes = 0; +}; +static std::mutex& stream_mu() { static std::mutex m; return m; } +static std::unordered_map& streams() { + static std::unordered_map m; + return m; +} + +class ISteamUtilsStub { +public: + virtual uint32_t GetSecondsSinceAppActive() { return 0; } // 0 + virtual uint32_t GetSecondsSinceComputerActive() { return 0; } // 1 + virtual int GetConnectedUniverse() { return 1; /*Public*/ } // 2 + virtual uint32_t GetServerRealTime() { // 3 + auto anchor = pushed().server_realtime.load(); + auto anchor_local_ms = pushed().server_realtime_anchor_local_ms.load(); + if (anchor != 0 && anchor_local_ms != 0) { + const auto now = std::chrono::steady_clock::now(); + const auto now_ms = std::chrono::duration_cast( + now.time_since_epoch()).count(); + auto elapsed_s = (now_ms - anchor_local_ms) / 1000; + if (elapsed_s < 0) elapsed_s = 0; // clock went backwards + return static_cast(anchor + static_cast(elapsed_s)); + } + return static_cast(::time(nullptr)); + } + virtual const char* GetIPCountry() { // 4 + auto& p = pushed(); + if (p.ip_country_set.load() == 0) return "US"; + return p.ip_country.c_str(); + } + virtual bool GetImageSize(int iImage, uint32_t* pnWidth, uint32_t* pnHeight) { + if (iImage <= 0) return false; + std::lock_guard lk(state_mutex()); + auto it = pushed().image_registry.find(iImage); + if (it == pushed().image_registry.end()) return false; + if (pnWidth) *pnWidth = static_cast(it->second.width); + if (pnHeight) *pnHeight = static_cast(it->second.height); + return true; + } + virtual bool GetImageRGBA(int iImage, uint8_t* pubDest, int nDestBufferSize) { + if (iImage <= 0 || !pubDest || nDestBufferSize <= 0) return false; + std::lock_guard lk(state_mutex()); + auto it = pushed().image_registry.find(iImage); + if (it == pushed().image_registry.end()) return false; + const auto& img = it->second; + if (static_cast(img.rgba.size()) > nDestBufferSize) return false; + std::memcpy(pubDest, img.rgba.data(), img.rgba.size()); + return true; + } + virtual bool GetCSERIPPort(uint32_t*, uint16_t*) { return false; } // 7 + virtual uint8_t GetCurrentBatteryPower() { return 255; /*AC*/ } // 8 + virtual uint32_t GetAppID() { + uint32_t app = pushed().app_id.load(); + if (app != 0) return app; + const char* env = std::getenv("SteamAppId"); + if (env && *env) { + char* end = nullptr; + unsigned long v = std::strtoul(env, &end, 10); + if (end != env && v != 0 && v <= 0x7fffffffu) { + return static_cast(v); + } + } + return 0; + } // 9 + virtual void SetOverlayNotificationPosition(int) {} // 10 + virtual bool IsAPICallCompleted(uint64_t hCall, bool* pbFailed) { + if (hCall == 0) return false; + auto& s = state(); + std::lock_guard lk(s.call_results_mu); + auto it = s.call_results_pending.find(hCall); + if (it == s.call_results_pending.end()) return false; + if (pbFailed) *pbFailed = it->second.io_failure; + return true; + } + virtual int GetAPICallFailureReason(uint64_t /*hCall*/) { return -1; } + virtual bool GetAPICallResult(uint64_t hCall, void* pCallback, + int cubCallback, int iCallbackExpected, + bool* pbFailed) { + if (hCall == 0) return false; + auto& s = state(); + std::lock_guard lk(s.call_results_mu); + auto it = s.call_results_pending.find(hCall); + if (it == s.call_results_pending.end()) return false; + const auto& msg = it->second; + if (iCallbackExpected != 0 && msg.callback_id != iCallbackExpected) { + return false; // keep the entry — caller asked wrong type + } + if (pCallback && cubCallback > 0 && !msg.body.empty()) { + size_t n = std::min(static_cast(cubCallback), msg.body.size()); + std::memcpy(pCallback, msg.body.data(), n); + } + if (pbFailed) *pbFailed = msg.io_failure; + s.call_results_pending.erase(it); + return true; + } + virtual void RunFrame() {} // 14 + virtual uint32_t GetIPCCallCount() { return 0; } // 15 + virtual void SetWarningMessageHook(void*) {} // 16 + virtual bool IsOverlayEnabled() { return true; } // 17 + virtual bool BOverlayNeedsPresent() { return false; } // 18 + virtual uint64_t CheckFileSignature(const char* /*pszFileName*/) { + uint64_t hCall = alloc_api_call_handle(); + lsc_cb::CheckFileSignature cb{}; + cb.m_eCheckFileSignature = 4; // NoSignaturesFoundForThisFile + push_call_result(hCall, lsc_cb::kCheckFileSignature, + &cb, sizeof(cb), /*io_failure=*/false); + return hCall; + } + virtual bool ShowGamepadTextInput(int, int, const char*, uint32_t, const char*) { return false; } // 20 + virtual uint32_t GetEnteredGamepadTextLength() { return 0; } // 21 + virtual bool GetEnteredGamepadTextInput(char*, uint32_t) { return false; } // 22 + virtual const char* GetSteamUILanguage() { // 23 + auto& p = pushed(); + if (p.ui_language.empty()) return "english"; + return p.ui_language.c_str(); + } + virtual bool IsSteamRunningInVR() { return false; } // 24 + virtual void SetOverlayNotificationInset(int, int) {} // 25 + virtual bool IsSteamInBigPictureMode() { return false; } // 26 + virtual void StartVRDashboard() {} // 27 + virtual bool IsVRHeadsetStreamingEnabled() { return false; } // 28 + virtual void SetVRHeadsetStreamingEnabled(bool) {} // 29 + virtual bool IsSteamChinaLauncher() { return false; } + virtual bool InitFilterText(uint32_t) { return false; } + virtual int FilterText(int /*eContext*/, uint64_t /*srcSid*/, + const char* in, char* out, uint32_t outSize) { + if (!out || outSize == 0) return 0; + if (!in) { out[0] = '\0'; return 0; } + uint32_t n = static_cast(std::strlen(in)); + uint32_t copy = std::min(n, outSize - 1); + if (copy > 0) std::memcpy(out, in, copy); + out[copy] = '\0'; + return static_cast(copy); + } + virtual int GetIPv6ConnectivityState(int) { return 0; } + virtual bool IsSteamRunningOnSteamDeck() { return false; } + virtual bool ShowFloatingGamepadTextInput(int, int, int, int, int) { return false; } + virtual void SetGameLauncherMode(bool) {} + virtual bool DismissFloatingGamepadTextInput() { return false; } +}; + +class ISteamUserStub { +public: + virtual int GetHSteamUser() { return state().user.load(); } // 0 + virtual bool BLoggedOn() { return state().logged_on.load(); } // 1 + virtual uint64_t GetSteamID() { return pushed().steam_id.load(); } // 2 + virtual int InitiateGameConnection_DEPRECATED(void*, int, uint64_t, uint32_t, uint16_t, bool) { return 0; } // 3 + virtual void TerminateGameConnection_DEPRECATED(uint32_t, uint16_t) {} // 4 + virtual void TrackAppUsageEvent(uint64_t, int, const char*) {} // 5 + virtual bool GetUserDataFolder(char* pchBuffer, int cubBuffer) { + if (!pchBuffer || cubBuffer <= 0) return false; + uint32_t app = pushed().app_id.load(); + if (app == 0) return false; + std::string ud; + { + std::lock_guard lk(state_mutex()); + auto it = pushed().app_cloud_remote_dirs.find(app); + if (it == pushed().app_cloud_remote_dirs.end()) return false; + ud = it->second; + } + if (ud.size() > 7 && ud.compare(ud.size() - 7, 7, "/remote") == 0) { + ud.resize(ud.size() - 7); + } else if (ud.size() > 8 && ud.compare(ud.size() - 8, 8, "/remote/") == 0) { + ud.resize(ud.size() - 8); + } + size_t copy = std::min(ud.size(), + static_cast(cubBuffer - 1)); + std::memcpy(pchBuffer, ud.data(), copy); + pchBuffer[copy] = '\0'; + return true; + } + virtual void StartVoiceRecording() {} // 7 + virtual void StopVoiceRecording() {} // 8 + virtual int GetAvailableVoice(uint32_t*, uint32_t*, uint32_t) { return 0; }// 9 + virtual int GetVoice(bool, void*, uint32_t, uint32_t*, bool, void*, uint32_t, uint32_t*, uint32_t) { return 0; } // 10 + virtual int DecompressVoice(const void*, uint32_t, void*, uint32_t, uint32_t*, uint32_t) { return 0; } // 11 + virtual uint32_t GetVoiceOptimalSampleRate() { return 11025; } // 12 + virtual uint64_t GetAuthSessionTicket(void* buf, int maxLen, + uint32_t* pcbTicket, + const void* /*pSteamNetworkingIdentity*/) { + uint32_t h = pushed().next_auth_ticket_handle.fetch_add(1); + if (h == 0) h = pushed().next_auth_ticket_handle.fetch_add(1); // skip 0 + uint32_t app_id = pushed().app_id.load(); + std::vector body; + bool cm_backed = false; + if (app_id != 0) { + size_t need = 0; + wn_cm_get_cached_app_ownership_ticket(app_id, nullptr, 0, &need); + if (need > 0 && need <= 16 * 1024) { // sanity-cap at 16KB + std::vector ownership(need); + size_t got = 0; + if (wn_cm_get_cached_app_ownership_ticket( + app_id, ownership.data(), ownership.size(), &got) + && got == need) { + body.reserve(24 + ownership.size()); + body.resize(24, 0); + auto put_u32 = [&](size_t off, uint32_t v) { + body[off + 0] = static_cast(v & 0xFF); + body[off + 1] = static_cast((v >> 8) & 0xFF); + body[off + 2] = static_cast((v >> 16) & 0xFF); + body[off + 3] = static_cast((v >> 24) & 0xFF); + }; + put_u32(0, 20); // fixed prefix + put_u32(4, 0); // padding + put_u32(8, 0); // padding + put_u32(12, h); // ConnectionID + put_u32(16, static_cast(::time(nullptr))); + put_u32(20, 1); // ConnectionCount + body.insert(body.end(), ownership.begin(), ownership.end()); + cm_backed = true; + } + } + } + if (!cm_backed) { + body.assign(32, 0); + body[0] = 'W'; body[1] = 'N'; body[2] = 'A'; body[3] = 'T'; + std::memcpy(body.data() + 4, &h, sizeof(h)); + uint64_t sid = pushed().steam_id.load(); + std::memcpy(body.data() + 8, &sid, sizeof(sid)); + uint64_t ts = static_cast(::time(nullptr)); + std::memcpy(body.data() + 16, &ts, sizeof(ts)); + } + { + std::lock_guard lk(state_mutex()); + pushed().auth_tickets[h] = {h, app_id, body}; + } + if (buf && maxLen > 0) { + uint32_t copy = std::min(body.size(), static_cast(maxLen)); + std::memcpy(buf, body.data(), copy); + } + if (pcbTicket) *pcbTicket = static_cast(body.size()); + lsc_cb::GetAuthSessionTicketResponse cb{}; + cb.m_hAuthTicket = h; + cb.m_eResult = 1; // k_EResultOK + push_callback(state().user.load(), + lsc_cb::kGetAuthSessionTicketResponse, + &cb, sizeof(cb)); + __android_log_print(ANDROID_LOG_INFO, "WnLibSteamClient", + "GetAuthSessionTicket(app=%u) → h=%u size=%zu (%s)", + app_id, h, body.size(), cm_backed ? "CM-backed" : "synthetic"); + return h; + } + virtual uint64_t GetAuthTicketForWebApi(const char* pchIdentity) { + uint32_t app_id = pushed().app_id.load(); + std::vector ownership; + if (app_id != 0) { + size_t need = 0; + wn_cm_get_cached_app_ownership_ticket(app_id, nullptr, 0, &need); + if (need > 0 && need <= 16 * 1024) { + ownership.resize(need); + size_t got = 0; + if (!wn_cm_get_cached_app_ownership_ticket( + app_id, ownership.data(), ownership.size(), &got) + || got != need) { + ownership.clear(); + } + } + } + bool have_cm = !ownership.empty(); + uint32_t h = static_cast(alloc_api_call_handle() & 0xFFFFFFFF); + if (h == 0) h = static_cast(alloc_api_call_handle() & 0xFFFFFFFF); + std::vector body; + if (have_cm) { + body.reserve(24 + ownership.size()); + body.resize(24, 0); + auto put_u32 = [&](size_t off, uint32_t v) { + body[off + 0] = static_cast(v & 0xFF); + body[off + 1] = static_cast((v >> 8) & 0xFF); + body[off + 2] = static_cast((v >> 16) & 0xFF); + body[off + 3] = static_cast((v >> 24) & 0xFF); + }; + put_u32(0, 20); // fixed prefix + put_u32(12, h); // ConnectionID + put_u32(16, static_cast(::time(nullptr))); + put_u32(20, 1); // ConnectionCount + body.insert(body.end(), ownership.begin(), ownership.end()); + } else { + body.assign(32, 0); + body[0] = 'W'; body[1] = 'N'; body[2] = 'A'; body[3] = 'W'; + std::memcpy(body.data() + 4, &h, sizeof(h)); + uint64_t sid = pushed().steam_id.load(); + std::memcpy(body.data() + 8, &sid, sizeof(sid)); + uint64_t ts = static_cast(::time(nullptr)); + std::memcpy(body.data() + 16, &ts, sizeof(ts)); + } + { + std::lock_guard lk(state_mutex()); + pushed().auth_tickets[h] = {h, app_id, body}; + } + lsc_cb::GetTicketForWebApiResponse cb{}; + cb.m_hAuthTicket = h; + cb.m_eResult = 1; // k_EResultOK + size_t copy = std::min(body.size(), sizeof(cb.m_rgubTicket)); + cb.m_cubTicket = static_cast(copy); + std::memcpy(cb.m_rgubTicket, body.data(), copy); + push_callback(state().user.load(), + lsc_cb::kGetTicketForWebApiResponse, + &cb, sizeof(cb)); + __android_log_print(ANDROID_LOG_INFO, "WnLibSteamClient", + "GetAuthTicketForWebApi(identity=\"%s\") → h=%u size=%zu (%s)", + pchIdentity ? pchIdentity : "(null)", h, body.size(), + have_cm ? "CM-backed" : "synthetic"); + return h; + } + virtual int BeginAuthSession(const void* /*ticket*/, int cbTicket, + uint64_t steamID) { + lsc_cb::ValidateAuthTicketResponse cb{}; + cb.m_SteamID = steamID; + cb.m_eAuthSessionResponse = 0; // k_EAuthSessionResponseOK + cb.m_OwnerSteamID = steamID; // owner == user when not family-shared + push_callback(state().user.load(), + lsc_cb::kValidateAuthTicketResponse, + &cb, sizeof(cb)); + __android_log_print(ANDROID_LOG_INFO, "WnLibSteamClient", + "BeginAuthSession(cbTicket=%d, steamID=%llu) -> OK (synthetic validation)", + cbTicket, static_cast(steamID)); + return 0; + } + virtual void EndAuthSession(uint64_t) {} + virtual void CancelAuthTicket(uint64_t hAuthTicket) { + std::lock_guard lk(state_mutex()); + pushed().auth_tickets.erase(static_cast(hAuthTicket)); + } + virtual int UserHasLicenseForApp(uint64_t steamID, uint32_t appID) { + if (appID == 0) return 2; + uint64_t self = pushed().steam_id.load(); + if (steamID != 0 && steamID == self) { + std::lock_guard lk(state_mutex()); + return pushed().owned_apps.count(appID) > 0 ? 0 : 1; + } + return 2; /*NoAuth — we can't speak for other users*/ + } + virtual bool BIsBehindNAT() { return true; } // 19 + virtual void AdvertiseGame(uint64_t, uint32_t, uint16_t) {} // 20 + virtual uint64_t RequestEncryptedAppTicket(void* rgubData, int cbData) { + uint32_t app = pushed().app_id.load(); + uint64_t h = alloc_api_call_handle(); + bool have_real_bytes = false; + std::vector body; + { + std::lock_guard lk(state_mutex()); + auto it = pushed().encrypted_app_tickets.find(app); + if (it != pushed().encrypted_app_tickets.end() && !it->second.empty()) { + body = it->second; + have_real_bytes = true; + } else { + body.resize(32); + std::memcpy(body.data(), "WNETKT\0\0\0\0\0\0\0\0\0\0", 16); + std::memcpy(body.data() + 16, &app, sizeof(app)); + uint32_t h32 = static_cast(h); + std::memcpy(body.data() + 20, &h32, sizeof(h32)); + uint64_t sid = pushed().steam_id.load(); + std::memcpy(body.data() + 24, &sid, sizeof(sid)); + pushed().encrypted_app_tickets[app] = body; + } + } + int32_t eresult = have_real_bytes ? 1 : 1; // SDK contract: synthetic returns OK + pushed().encrypted_app_ticket_eresult.store(eresult); + (void)rgubData; (void)cbData; + lsc_cb::EncryptedAppTicketResponse cb{}; + cb.m_eResult = eresult; + push_call_result(h, lsc_cb::kEncryptedAppTicketResponse, + &cb, sizeof(cb), /*io_failure=*/false); + __android_log_print(ANDROID_LOG_INFO, "WnLibSteamClient", + "RequestEncryptedAppTicket(app=%u) -> hCall=%llu (body=%zu B, %s)", + app, static_cast(h), body.size(), + have_real_bytes ? "real" : "synthetic"); + return h; + } + virtual bool GetEncryptedAppTicket(void* buf, int cbMax, uint32_t* pcbTicket) { + uint32_t app = pushed().app_id.load(); + std::lock_guard lk(state_mutex()); + auto it = pushed().encrypted_app_tickets.find(app); + if (it == pushed().encrypted_app_tickets.end() || it->second.empty()) { + if (pcbTicket) *pcbTicket = 0; + return false; + } + const auto& body = it->second; + uint32_t copy = std::min(body.size(), + static_cast(std::max(0, cbMax))); + if (buf && copy > 0) std::memcpy(buf, body.data(), copy); + if (pcbTicket) *pcbTicket = static_cast(body.size()); + return true; + } + virtual int GetGameBadgeLevel(int nSeries, bool bFoil) { + uint32_t app = pushed().app_id.load(); + if (app == 0) return 0; + int32_t key = (static_cast(app) & 0x0FFFFFFF) + | ((nSeries & 0x07) << 28) + | (bFoil ? (1 << 31) : 0); + std::lock_guard lk(state_mutex()); + auto it = pushed().self_game_badges.find(key); + return it == pushed().self_game_badges.end() ? 0 : it->second; + } + virtual int GetPlayerSteamLevel() { + return pushed().self_player_level.load(); + } + virtual uint64_t RequestStoreAuthURL(const char* pchRedirectURL) { + uint64_t hCall = alloc_api_call_handle(); + lsc_cb::StoreAuthURLResponse cb{}; + const char* redirect = pchRedirectURL ? pchRedirectURL : ""; + std::snprintf(cb.m_szURL, sizeof(cb.m_szURL), + "https://store.steampowered.com/login/?redir=%s", + redirect); + push_call_result(hCall, lsc_cb::kStoreAuthURLResponse, + &cb, sizeof(cb), /*io_failure=*/false); + return hCall; + } + virtual bool BIsPhoneVerified() { + return pushed().account_phone_verified.load(); + } + virtual bool BIsTwoFactorEnabled() { + return pushed().account_two_factor_enabled.load(); + } + virtual bool BIsPhoneIdentifying() { + return pushed().account_phone_identifying.load(); + } + virtual bool BIsPhoneRequiringVerification() { + return pushed().account_phone_requires_verification.load(); + } + virtual uint64_t GetMarketEligibility() { + uint64_t hCall = alloc_api_call_handle(); + lsc_cb::MarketEligibilityResponse cb{}; + bool twoFA = pushed().account_two_factor_enabled.load(); + bool phone = pushed().account_phone_verified.load(); + cb.m_bAllowed = (twoFA && phone); + cb.m_eNotAllowedReason = cb.m_bAllowed ? 0 : 2; + cb.m_rtAllowedAtTime = 0; + cb.m_cdaySteamGuardRequiredDays = cb.m_bAllowed ? 0 : 15; + cb.m_cdayNewDeviceCooldown = 0; + push_call_result(hCall, lsc_cb::kMarketEligibilityResponse, + &cb, sizeof(cb), /*io_failure=*/false); + return hCall; + } + virtual uint64_t GetDurationControl() { + uint64_t hCall = alloc_api_call_handle(); + lsc_cb::DurationControl cb{}; + cb.m_eResult = 1; // k_EResultOK + cb.m_appid = pushed().app_id.load(); + cb.m_bApplicable = false; // non-CN + cb.m_csecsLast5h = 0; + cb.m_progress = 0; // k_EDurationControlProgress_Full + cb.m_notification = 0; // k_EDurationControlNotification_None + cb.m_csecsToday = 0; + cb.m_csecsRemaining = 0; + push_call_result(hCall, lsc_cb::kDurationControl, + &cb, sizeof(cb), /*io_failure=*/false); + return hCall; + } + virtual bool BSetDurationControlOnlineState(int /*state*/) { return true; } // 32 +}; + +class ISteamAppsStub { +public: + static bool env_app_id_matches(uint32_t app) { + if (app == 0) return false; + const char* env = std::getenv("SteamAppId"); + if (!env || !*env) return false; + char* end = nullptr; + unsigned long v = std::strtoul(env, &end, 10); + return (end != env && v == app); + } + + virtual bool BIsSubscribed() { + uint32_t app = pushed().app_id.load(); + if (app == 0) return false; + { + std::lock_guard lk(state_mutex()); + if (pushed().owned_apps.count(app) > 0) return true; + } + return env_app_id_matches(app); + } + virtual bool BIsLowViolence() { + uint32_t app = pushed().app_id.load(); + if (app == 0) return false; + std::lock_guard lk(state_mutex()); + return pushed().app_low_violence.count(app) > 0; + } + virtual bool BIsCybercafe() { return false; } // 2 + virtual bool BIsVACBanned() { + uint32_t app = pushed().app_id.load(); + if (app == 0) return false; + std::lock_guard lk(state_mutex()); + return pushed().app_vac_banned.count(app) > 0; + } + virtual const char* GetCurrentGameLanguage() { + static thread_local std::string tls_lang; + { + std::lock_guard lk(state_mutex()); + tls_lang = pushed().ui_language; + } + if (tls_lang.empty()) tls_lang = "english"; + return tls_lang.c_str(); + } + virtual const char* GetAvailableGameLanguages() { return "english"; } // 5 + virtual bool BIsSubscribedApp(uint32_t appId) { // 6 + { + auto& p = pushed(); + std::lock_guard lk(state_mutex()); + if (p.owned_apps.count(appId) > 0) return true; + } + return env_app_id_matches(appId); + } + virtual bool BIsDlcInstalled(uint32_t appId) { // 7 + auto& p = pushed(); + std::lock_guard lk(state_mutex()); + if (p.installed_apps.count(appId) > 0 && + p.owned_apps.count(appId) > 0) { + return true; + } + if (p.owned_apps.count(appId) == 0) return false; + for (const auto& kv : p.app_dlcs) { + for (const auto& d : kv.second) { + if (d.app_id == appId && + p.installed_apps.count(kv.first) > 0) { + return true; + } + } + } + return false; + } + virtual uint32_t GetEarliestPurchaseUnixTime(uint32_t app_id) { + if (app_id == 0) return 0; + std::lock_guard lk(state_mutex()); + auto pit = pushed().app_source_packages.find(app_id); + if (pit == pushed().app_source_packages.end()) return 0; + uint32_t earliest = 0; + for (uint32_t pkg : pit->second) { + auto lit = pushed().licenses.find(pkg); + if (lit == pushed().licenses.end()) continue; + uint32_t t = lit->second.time_created; + if (t == 0) continue; + if (earliest == 0 || t < earliest) earliest = t; + } + return earliest; + } + virtual bool BIsSubscribedFromFreeWeekend() { + uint32_t app_id = pushed().app_id.load(); + if (app_id == 0) return false; + std::lock_guard lk(state_mutex()); + auto pit = pushed().app_source_packages.find(app_id); + if (pit == pushed().app_source_packages.end()) return false; + for (uint32_t pkg : pit->second) { + auto lit = pushed().licenses.find(pkg); + if (lit == pushed().licenses.end()) continue; + if (lit->second.license_type == 11 /*FreeWeekend*/) return true; + } + return false; + } + virtual int GetDLCCount(uint32_t appId) { + std::lock_guard lk(state_mutex()); + auto it = pushed().app_dlcs.find(appId); + return it == pushed().app_dlcs.end() ? 0 : static_cast(it->second.size()); + } + virtual bool BGetDLCDataByIndex(uint32_t appId, int iDLC, + uint32_t* pAppID, bool* pbAvailable, + char* pchName, int cchNameBufferSize) { + std::lock_guard lk(state_mutex()); + auto it = pushed().app_dlcs.find(appId); + if (it == pushed().app_dlcs.end()) return false; + const auto& dlcs = it->second; + if (iDLC < 0 || static_cast(iDLC) >= dlcs.size()) return false; + const auto& d = dlcs[static_cast(iDLC)]; + if (pAppID) *pAppID = d.app_id; + if (pbAvailable) *pbAvailable = d.available; + if (pchName && cchNameBufferSize > 0) { + int copy = std::min(static_cast(d.name.size()), + cchNameBufferSize - 1); + if (copy > 0) std::memcpy(pchName, d.name.data(), copy); + pchName[copy] = '\0'; + } + return true; + } + virtual void InstallDLC(uint32_t) {} // 12 + virtual void UninstallDLC(uint32_t) {} // 13 + virtual void RequestAppProofOfPurchaseKey(uint32_t) {} // 14 + virtual bool GetCurrentBetaName(char* pchName, int cchNameBufferSize) { + if (!pchName || cchNameBufferSize <= 0) return false; + uint32_t app_id = pushed().app_id.load(); + if (app_id == 0) return false; + std::lock_guard lk(state_mutex()); + auto it = pushed().app_current_beta.find(app_id); + if (it == pushed().app_current_beta.end()) return false; + const std::string& name = it->second; + if (name.empty()) return false; + size_t copy = std::min(name.size(), + static_cast(cchNameBufferSize - 1)); + std::memcpy(pchName, name.data(), copy); + pchName[copy] = '\0'; + return true; + } + virtual bool MarkContentCorrupt(bool /*bMissingFilesOnly*/) { + uint32_t app = pushed().app_id.load(); + if (app == 0) return false; + std::lock_guard lk(state_mutex()); + pushed().apps_marked_corrupt.insert(app); + return true; + } + virtual uint32_t GetInstalledDepots(uint32_t appID, uint32_t* pvecDepots, + uint32_t cMaxDepots) { + std::lock_guard lk(state_mutex()); + auto it = pushed().app_installed_depots.find(appID); + if (it == pushed().app_installed_depots.end()) return 0; + const auto& depots = it->second; + uint32_t copy = std::min(static_cast(depots.size()), + cMaxDepots); + if (pvecDepots && copy > 0) { + for (uint32_t i = 0; i < copy; ++i) pvecDepots[i] = depots[i]; + } + return copy; + } + virtual uint32_t GetAppInstallDir(uint32_t appId, char* buf, uint32_t cap) { // 18 + if (!buf || cap == 0) return 0; + auto& p = pushed(); + std::lock_guard lk(state_mutex()); + auto it = p.app_install_dirs.find(appId); + if (it == p.app_install_dirs.end()) { + buf[0] = '\0'; + return 0; + } + const std::string& d = it->second; + uint32_t n = static_cast(d.size()); + uint32_t copy = (n + 1 < cap) ? n : cap - 1; + std::memcpy(buf, d.data(), copy); + buf[copy] = '\0'; + return n + 1; // documented: returns length INCLUDING null + } + virtual bool BIsAppInstalled(uint32_t appId) { // 19 + auto& p = pushed(); + std::lock_guard lk(state_mutex()); + return p.installed_apps.count(appId) > 0; + } + virtual uint64_t GetAppOwner() { + uint32_t app = pushed().app_id.load(); + if (app == 0) return 0; + uint64_t self_sid = pushed().steam_id.load(); + if (self_sid == 0) return 0; + uint32_t self_account = static_cast(self_sid & 0xFFFFFFFFu); + std::lock_guard lk(state_mutex()); + bool in_owned = pushed().owned_apps.count(app) > 0; + auto pit = pushed().app_source_packages.find(app); + bool has_pkgs = (pit != pushed().app_source_packages.end()); + if (!in_owned && !has_pkgs) return 0; + if (!has_pkgs) return self_sid; // owned but no pkg map — assume self + uint32_t fallback_owner = 0; + bool has_self_match = false; + for (uint32_t pkg : pit->second) { + auto lit = pushed().licenses.find(pkg); + if (lit == pushed().licenses.end()) continue; + if (lit->second.owner_id == self_account) { + has_self_match = true; + break; + } + if (fallback_owner == 0) fallback_owner = lit->second.owner_id; + } + if (has_self_match) return self_sid; + if (fallback_owner != 0) { + return 0x0110000100000000ULL | + static_cast(fallback_owner); + } + return self_sid; + } + virtual const char* GetLaunchQueryParam(const char*) { return ""; } // 21 + virtual bool GetDlcDownloadProgress(uint32_t appID, uint64_t* pBytesDownloaded, + uint64_t* pBytesTotal) { + if (appID == 0) return false; + std::lock_guard lk(state_mutex()); + auto it = pushed().app_dl_progress.find(appID); + if (it == pushed().app_dl_progress.end()) return false; + if (it->second.bytes_total == 0) return false; + if (pBytesDownloaded) *pBytesDownloaded = it->second.bytes_downloaded; + if (pBytesTotal) *pBytesTotal = it->second.bytes_total; + return true; + } + virtual int GetAppBuildId() { + uint32_t app = pushed().app_id.load(); + if (app == 0) return 0; + std::lock_guard lk(state_mutex()); + auto it = pushed().app_build_ids.find(app); + return it == pushed().app_build_ids.end() ? 0 : static_cast(it->second); + } + virtual void RequestAllProofOfPurchaseKeys() {} // 24 + virtual uint64_t GetFileDetails(const char* pchFile) { + uint64_t hCall = alloc_api_call_handle(); + lsc_cb::FileDetailsResult cb{}; + uint32_t app = pushed().app_id.load(); + std::string base; + if (app != 0 && pchFile && *pchFile) { + std::lock_guard lk(state_mutex()); + auto it = pushed().app_install_dirs.find(app); + if (it != pushed().app_install_dirs.end()) base = it->second; + } + if (base.empty()) { + cb.m_eResult = 9; // k_EResultFileNotFound + push_call_result(hCall, lsc_cb::kFileDetailsResult, + &cb, sizeof(cb), /*io_failure=*/false); + return hCall; + } + std::string fname(pchFile); + if (fname.find("..") != std::string::npos || fname[0] == '/') { + cb.m_eResult = 9; + push_call_result(hCall, lsc_cb::kFileDetailsResult, + &cb, sizeof(cb), /*io_failure=*/false); + return hCall; + } + std::string path = base; + if (!path.empty() && path.back() != '/') path.push_back('/'); + path.append(fname); + int fd = ::open(path.c_str(), O_RDONLY); + if (fd < 0) { + cb.m_eResult = 9; + push_call_result(hCall, lsc_cb::kFileDetailsResult, + &cb, sizeof(cb), /*io_failure=*/false); + return hCall; + } + struct stat st {}; + if (::fstat(fd, &st) != 0 || st.st_size > 64LL * 1024 * 1024) { + ::close(fd); + cb.m_eResult = 2; // k_EResultFail + push_call_result(hCall, lsc_cb::kFileDetailsResult, + &cb, sizeof(cb), /*io_failure=*/false); + return hCall; + } + uint64_t h64 = 0xcbf29ce484222325ULL; + uint8_t buf[8192]; + ssize_t n; + while ((n = ::read(fd, buf, sizeof(buf))) > 0) { + for (ssize_t i = 0; i < n; ++i) { + h64 ^= buf[i]; + h64 *= 0x100000001b3ULL; + } + } + ::close(fd); + cb.m_eResult = 1; // k_EResultOK + cb.m_ulFileSize = static_cast(st.st_size); + std::memcpy(cb.m_FileSHA + 0, &h64, sizeof(h64)); + uint64_t h64_rot = (h64 << 32) | (h64 >> 32); + std::memcpy(cb.m_FileSHA + 8, &h64_rot, sizeof(h64_rot)); + uint32_t h32 = static_cast(h64 ^ h64_rot); + std::memcpy(cb.m_FileSHA + 16, &h32, sizeof(h32)); + cb.m_unFlags = 0; + push_call_result(hCall, lsc_cb::kFileDetailsResult, + &cb, sizeof(cb), /*io_failure=*/false); + return hCall; + } + virtual int GetLaunchCommandLine(char* buf, int cubMax) { + if (!buf || cubMax <= 0) return 0; + std::lock_guard lk(state_mutex()); + const std::string& cl = pushed().launch_command_line; + int n = static_cast(cl.size()); + int copy = std::min(n, cubMax - 1); + if (copy > 0) std::memcpy(buf, cl.data(), copy); + buf[copy] = '\0'; + return copy; + } + virtual bool BIsSubscribedFromFamilySharing() { + uint32_t app_id = pushed().app_id.load(); + if (app_id == 0) return pushed().app_is_family_shared.load(); + uint64_t sid = pushed().steam_id.load(); + if (sid == 0) return pushed().app_is_family_shared.load(); + uint32_t self_account = static_cast(sid & 0xFFFFFFFFu); + std::lock_guard lk(state_mutex()); + auto pit = pushed().app_source_packages.find(app_id); + if (pit == pushed().app_source_packages.end()) { + return pushed().app_is_family_shared.load(); + } + bool any_license_match = false; + bool any_self_owned = false; + for (uint32_t pkg : pit->second) { + auto lit = pushed().licenses.find(pkg); + if (lit == pushed().licenses.end()) continue; + any_license_match = true; + if (lit->second.owner_id == self_account) { + any_self_owned = true; + break; + } + } + if (!any_license_match) { + return pushed().app_is_family_shared.load(); + } + return !any_self_owned; + } + virtual bool BIsTimedTrial(uint32_t* pcSecondsAllowed, + uint32_t* pcSecondsPlayed) { + uint32_t app = pushed().app_id.load(); + if (app == 0) return false; + std::lock_guard lk(state_mutex()); + auto pit = pushed().app_source_packages.find(app); + if (pit == pushed().app_source_packages.end()) return false; + for (uint32_t pkg : pit->second) { + auto lit = pushed().licenses.find(pkg); + if (lit == pushed().licenses.end()) continue; + if (lit->second.minute_limit > 0) { + if (pcSecondsAllowed) { + *pcSecondsAllowed = static_cast( + lit->second.minute_limit * 60); + } + if (pcSecondsPlayed) { + *pcSecondsPlayed = static_cast( + std::max(0, lit->second.minutes_used) * 60); + } + return true; + } + } + return false; + } + virtual bool SetDlcContext(uint32_t /*appID*/) { return true; } // 29 +}; + +class ISteamFriendsStub { +public: + virtual const char* GetPersonaName() { // 0 + auto& p = pushed(); + std::lock_guard lk(state_mutex()); + return p.persona_name.empty() ? "Player" : p.persona_name.c_str(); + } + virtual uint64_t SetPersonaName(const char* pchPersonaName) { + if (!pchPersonaName) return 0; + uint64_t h = alloc_api_call_handle(); + std::string name(pchPersonaName); + uint64_t self; + bool name_changed; + int current_state; + { + std::lock_guard lk(state_mutex()); + name_changed = (pushed().persona_name != name); + pushed().persona_name = name; // copy: keep one for the bridge call below + self = pushed().steam_id.load(); + current_state = pushed().persona_state.load(); + } + if (name_changed && self != 0) { + lsc_cb::PersonaStateChange psc{}; + psc.m_ulSteamID = self; + psc.m_nChangeFlags = lsc_cb::kPersonaChangeName; + push_callback(state().user.load(), + lsc_cb::kPersonaStateChange, &psc, sizeof(psc)); + } + wn_cm_set_persona_name(name.c_str(), + current_state > 0 ? current_state : 1); + lsc_cb::SetPersonaNameResponse resp{}; + resp.m_bSuccess = state().logged_on.load(); + resp.m_bLocalSuccess = true; + resp.m_result = state().logged_on.load() ? 1 : 6; // OK / NoConnection + push_call_result(h, lsc_cb::kSetPersonaNameResponse, + &resp, sizeof(resp), /*io_failure=*/false); + return h; + } + virtual int GetPersonaState() { return pushed().persona_state.load(); } // 2 + virtual int GetFriendCount(int /*flags*/) { // 3 + auto& p = pushed(); + std::lock_guard lk(state_mutex()); + return static_cast(p.friends.size()); + } + virtual uint64_t GetFriendByIndex(int idx, int /*flags*/) { // 4 + auto& p = pushed(); + std::lock_guard lk(state_mutex()); + if (idx < 0 || static_cast(idx) >= p.friends.size()) return 0; + return p.friends[idx]; + } + virtual int GetFriendRelationship(uint64_t sid) { + if (sid == 0) return 0; + std::lock_guard lk(state_mutex()); + for (uint64_t f : pushed().friends) { + if (f == sid) return 3 /*Friend*/; + } + return 0 /*None*/; + } + virtual int GetFriendPersonaState(uint64_t sid) { + std::lock_guard lk(state_mutex()); + auto it = pushed().friend_persona_states.find(sid); + return it == pushed().friend_persona_states.end() ? 0 : static_cast(it->second); + } + virtual const char* GetFriendPersonaName(uint64_t sid) { // 7 + auto& p = pushed(); + std::lock_guard lk(state_mutex()); + auto it = p.friend_persona_names.find(sid); + return it == p.friend_persona_names.end() ? "" : it->second.c_str(); + } + virtual bool GetFriendGamePlayed(uint64_t sid, void* pFriendGameInfo) { + if (!pFriendGameInfo) return false; + std::memset(pFriendGameInfo, 0, 24); + uint32_t app; + { + std::lock_guard lk(state_mutex()); + auto it = pushed().friend_game_played_app.find(sid); + if (it == pushed().friend_game_played_app.end()) return false; + app = it->second; + } + if (app == 0) return false; + uint64_t gameID = static_cast(app); + std::memcpy(pFriendGameInfo, &gameID, sizeof(gameID)); + return true; + } + virtual const char* GetFriendPersonaNameHistory(uint64_t, int) { return ""; } // 9 + virtual int GetFriendSteamLevel(uint64_t sid) { + if (sid == 0) return 0; + std::lock_guard lk(state_mutex()); + auto it = pushed().friend_steam_levels.find(sid); + return it == pushed().friend_steam_levels.end() ? 0 : it->second; + } + virtual const char* GetPlayerNickname(uint64_t sid) { + if (sid == 0) return nullptr; + static thread_local std::string tls; + { + std::lock_guard lk(state_mutex()); + auto it = pushed().player_nicknames.find(sid); + if (it == pushed().player_nicknames.end()) return nullptr; + tls = it->second; + } + return tls.empty() ? nullptr : tls.c_str(); + } + virtual int GetFriendsGroupCount() { return 0; } // 12 + virtual int16_t GetFriendsGroupIDByIndex(int) { return 0; } // 13 + virtual const char* GetFriendsGroupName(int16_t) { return ""; } // 14 + virtual int GetFriendsGroupMembersCount(int16_t) { return 0; } // 15 + virtual void GetFriendsGroupMembersList(int16_t, uint64_t*, int) {} // 16 + virtual bool HasFriend(uint64_t sid, int iFriendFlags) { + if (sid == 0) return false; + constexpr int kImmediate = 0x10; + if ((iFriendFlags & kImmediate) == 0) return false; + std::lock_guard lk(state_mutex()); + for (uint64_t f : pushed().friends) { + if (f == sid) return true; + } + return false; + } + virtual int GetClanCount() { return 0; } // 18 + virtual uint64_t GetClanByIndex(int) { return 0; } // 19 + virtual const char* GetClanName(uint64_t) { return ""; } // 20 + virtual const char* GetClanTag(uint64_t) { return ""; } // 21 + virtual bool GetClanActivityCounts(uint64_t, int*, int*, int*) { return false; }// 22 + virtual uint64_t DownloadClanActivityCounts(uint64_t* /*clans*/, int /*n*/) { + uint64_t h = alloc_api_call_handle(); + lsc_cb::DownloadClanActivityCountsResult cb{}; + cb.m_bSuccess = 0; // no real clan data + push_call_result(h, lsc_cb::kDownloadClanActivityCountsResult, + &cb, sizeof(cb), /*io_failure=*/false); + return h; + } + virtual int GetFriendCountFromSource(uint64_t) { return 0; } // 24 + virtual uint64_t GetFriendFromSourceByIndex(uint64_t, int) { return 0; } // 25 + virtual bool IsUserInSource(uint64_t, uint64_t) { return false; } // 26 + virtual void SetInGameVoiceSpeaking(uint64_t, bool) {} // 27 + static void enqueue_overlay(PushedState::OverlayRequest req) { + std::lock_guard lk(state_mutex()); + auto& q = pushed().overlay_request_queue; + if (q.size() >= 32) q.pop_front(); + q.push_back(std::move(req)); + } + virtual void ActivateGameOverlay(const char* dialog) { // 28 + PushedState::OverlayRequest r; + r.kind = "dialog"; + r.arg1 = dialog ? dialog : ""; + enqueue_overlay(std::move(r)); + } + virtual void ActivateGameOverlayToUser(const char* dialog, uint64_t sid) { // 29 + PushedState::OverlayRequest r; + r.kind = "user"; + r.arg1 = dialog ? dialog : ""; + r.sid = sid; + enqueue_overlay(std::move(r)); + } + virtual void ActivateGameOverlayToWebPage(const char* url, int /*mode*/) { // 30 + if (!url || !*url) return; + PushedState::OverlayRequest r; + r.kind = "webpage"; + r.arg1 = url; + enqueue_overlay(std::move(r)); + } + virtual void ActivateGameOverlayToStore(uint32_t appid, int /*flag*/) { // 31 + PushedState::OverlayRequest r; + r.kind = "store"; + r.app_id = appid; + enqueue_overlay(std::move(r)); + } + virtual void SetPlayedWith(uint64_t) {} // 32 + virtual void ActivateGameOverlayInviteDialog(uint64_t lobby_sid) { // 33 + PushedState::OverlayRequest r; + r.kind = "invite"; + r.sid = lobby_sid; + enqueue_overlay(std::move(r)); + } + virtual int GetSmallFriendAvatar(uint64_t steamID) { + std::lock_guard lk(state_mutex()); + auto it = pushed().friend_avatars.find(steamID); + return (it == pushed().friend_avatars.end()) ? 0 : it->second.small; + } + virtual int GetMediumFriendAvatar(uint64_t steamID) { + std::lock_guard lk(state_mutex()); + auto it = pushed().friend_avatars.find(steamID); + return (it == pushed().friend_avatars.end()) ? 0 : it->second.medium; + } + virtual int GetLargeFriendAvatar(uint64_t steamID) { + std::lock_guard lk(state_mutex()); + auto it = pushed().friend_avatars.find(steamID); + return (it == pushed().friend_avatars.end()) ? 0 : it->second.large; + } + virtual bool RequestUserInformation(uint64_t steamID, bool bRequireNameOnly) { + if (steamID == 0) return false; + { + std::lock_guard lk(state_mutex()); + auto it = pushed().friend_persona_names.find(steamID); + if (it != pushed().friend_persona_names.end() && !it->second.empty()) { + return false; + } + } + int32_t flags = bRequireNameOnly ? 0x01 : 0x47; // PlayerName / std set + wn_cm_request_user_info(steamID, flags); + return true; + } + virtual uint64_t RequestClanOfficerList(uint64_t clanSid) { + uint64_t h = alloc_api_call_handle(); + lsc_cb::ClanOfficerListResponse cb{}; + cb.m_steamIDClan = clanSid; + cb.m_cOfficers = 0; + cb.m_bSuccess = 0; + push_call_result(h, lsc_cb::kClanOfficerListResponse, + &cb, sizeof(cb), /*io_failure=*/false); + return h; + } + virtual uint64_t GetClanOwner(uint64_t) { return 0; } // 39 + virtual int GetClanOfficerCount(uint64_t) { return 0; } + virtual uint64_t GetClanOfficerByIndex(uint64_t, int) { return 0; } + virtual uint32_t GetUserRestrictions() { return 0; } + virtual bool SetRichPresence(const char* pchKey, const char* pchValue) { + if (!pchKey || !*pchKey) return false; + uint64_t self = pushed().steam_id.load(); + if (self == 0) return false; + std::vector keys, values; + uint32_t app_id; + { + std::lock_guard lk(state_mutex()); + auto& rp = pushed().rich_presence[self]; + auto it = std::find_if(rp.begin(), rp.end(), + [&](const auto& kv) { return kv.first == pchKey; }); + if (!pchValue || !*pchValue) { + if (it != rp.end()) rp.erase(it); + } else if (it == rp.end()) { + rp.emplace_back(pchKey, pchValue); + } else { + it->second = pchValue; + } + keys.reserve(rp.size()); + values.reserve(rp.size()); + for (const auto& kv : rp) { + keys.push_back(kv.first); + values.push_back(kv.second); + } + app_id = pushed().app_id.load(); + } + std::vector ck(keys.size()), cv(values.size()); + for (size_t i = 0; i < keys.size(); ++i) { + ck[i] = keys[i].c_str(); + cv[i] = values[i].c_str(); + } + wn_cm_set_rich_presence(app_id, + ck.empty() ? nullptr : ck.data(), + cv.empty() ? nullptr : cv.data(), + ck.size()); + lsc_cb::FriendRichPresenceUpdate ev{}; + ev.m_steamIDFriend = self; + ev.m_nAppID = app_id; + push_callback(state().user.load(), + lsc_cb::kFriendRichPresenceUpdate, + &ev, sizeof(ev)); + return true; + } + virtual void ClearRichPresence() { + uint64_t self = pushed().steam_id.load(); + if (self == 0) return; + uint32_t app_id; + { + std::lock_guard lk(state_mutex()); + pushed().rich_presence.erase(self); + app_id = pushed().app_id.load(); + } + wn_cm_set_rich_presence(app_id, nullptr, nullptr, 0); + lsc_cb::FriendRichPresenceUpdate ev{}; + ev.m_steamIDFriend = self; + ev.m_nAppID = app_id; + push_callback(state().user.load(), + lsc_cb::kFriendRichPresenceUpdate, + &ev, sizeof(ev)); + } + virtual const char* GetFriendRichPresence(uint64_t steamID, const char* pchKey) { + static thread_local std::string tls_rp; + tls_rp.clear(); + if (!pchKey) return ""; + std::lock_guard lk(state_mutex()); + auto it = pushed().rich_presence.find(steamID); + if (it == pushed().rich_presence.end()) return ""; + auto kv = std::find_if(it->second.begin(), it->second.end(), + [&](const auto& p) { return p.first == pchKey; }); + if (kv == it->second.end()) return ""; + tls_rp = kv->second; + return tls_rp.c_str(); + } + virtual int GetFriendRichPresenceKeyCount(uint64_t steamID) { + std::lock_guard lk(state_mutex()); + auto it = pushed().rich_presence.find(steamID); + if (it == pushed().rich_presence.end()) return 0; + return static_cast(it->second.size()); + } + virtual const char* GetFriendRichPresenceKeyByIndex(uint64_t steamID, int idx) { + static thread_local std::string tls_key; + tls_key.clear(); + if (idx < 0) return ""; + std::lock_guard lk(state_mutex()); + auto it = pushed().rich_presence.find(steamID); + if (it == pushed().rich_presence.end()) return ""; + if (static_cast(idx) >= it->second.size()) return ""; + tls_key = it->second[idx].first; + return tls_key.c_str(); + } + virtual void RequestFriendRichPresence(uint64_t steamID) { + if (steamID == 0) return; + wn_cm_request_user_info(steamID, 0x800); // RichPresence flag bit + lsc_cb::FriendRichPresenceUpdate ev{}; + ev.m_steamIDFriend = steamID; + ev.m_nAppID = pushed().app_id.load(); + push_callback(state().user.load(), + lsc_cb::kFriendRichPresenceUpdate, + &ev, sizeof(ev)); + } + virtual bool InviteUserToGame(uint64_t, const char*) { return false; } + virtual int GetCoplayFriendCount() { return 0; } + virtual uint64_t GetCoplayFriend(int) { return 0; } + virtual int GetFriendCoplayTime(uint64_t) { return 0; } + virtual uint32_t GetFriendCoplayGame(uint64_t) { return 0; } + virtual uint64_t JoinClanChatRoom(uint64_t clanSid) { + uint64_t h = alloc_api_call_handle(); + lsc_cb::JoinClanChatRoomCompletionResult cb{}; + cb.m_steamIDClanChat = clanSid; + cb.m_eChatRoomEnterResponse = 2; // k_EChatRoomEnterResponseError + push_call_result(h, lsc_cb::kJoinClanChatRoomCompletion, + &cb, sizeof(cb), /*io_failure=*/false); + return h; + } + virtual bool LeaveClanChatRoom(uint64_t) { return false; } + virtual int GetClanChatMemberCount(uint64_t) { return 0; } + virtual uint64_t GetChatMemberByIndex(uint64_t, int) { return 0; } + virtual bool SendClanChatMessage(uint64_t, const char*) { return false; } + virtual int GetClanChatMessage(uint64_t, int, void*, int, int*, uint64_t*) { return 0; } + virtual bool IsClanChatAdmin(uint64_t, uint64_t) { return false; } + virtual bool IsClanChatWindowOpenInSteam(uint64_t) { return false; } + virtual bool OpenClanChatWindowInSteam(uint64_t) { return false; } + virtual bool CloseClanChatWindowInSteam(uint64_t) { return false; } + virtual bool SetListenForFriendsMessages(bool) { return false; } + virtual bool ReplyToFriendMessage(uint64_t, const char*) { return false; } + virtual int GetFriendMessage(uint64_t, int, void*, int, int*) { return 0; } + virtual uint64_t GetFollowerCount(uint64_t sid) { + uint64_t h = alloc_api_call_handle(); + lsc_cb::FriendsGetFollowerCount cb{}; + cb.m_eResult = 2; // k_EResultFail + cb.m_steamID = sid; + cb.m_nCount = 0; + push_call_result(h, lsc_cb::kFriendsGetFollowerCount, + &cb, sizeof(cb), /*io_failure=*/false); + return h; + } + virtual uint64_t IsFollowing(uint64_t sid) { + uint64_t h = alloc_api_call_handle(); + lsc_cb::FriendsIsFollowing cb{}; + cb.m_eResult = 2; + cb.m_steamID = sid; + cb.m_bIsFollowing = 0; + push_call_result(h, lsc_cb::kFriendsIsFollowing, + &cb, sizeof(cb), /*io_failure=*/false); + return h; + } + virtual uint64_t EnumerateFollowingList(uint32_t /*unStartIndex*/) { + uint64_t h = alloc_api_call_handle(); + lsc_cb::FriendsEnumerateFollowingList cb{}; + cb.m_eResult = 2; + cb.m_nResultsReturned = 0; + cb.m_nTotalResultCount = 0; + push_call_result(h, lsc_cb::kFriendsEnumerateFollowingList, + &cb, sizeof(cb), /*io_failure=*/false); + return h; + } + virtual bool IsClanPublic(uint64_t) { return false; } + virtual bool IsClanOfficialGameGroup(uint64_t) { return false; } + virtual int GetNumChatsWithUnreadPriorityMessages() { return 0; } + virtual void ActivateGameOverlayRemotePlayTogetherInviteDialog(uint64_t) {} + virtual bool RegisterProtocolInOverlayBrowser(const char*) { return false; } + virtual void ActivateGameOverlayInviteDialogConnectString(const char*) {} + virtual uint64_t RequestEquippedProfileItems(uint64_t sid) { + uint64_t h = alloc_api_call_handle(); + lsc_cb::EquippedProfileItems cb{}; + cb.m_eResult = 2; // k_EResultFail + cb.m_steamID = sid; + cb.m_bHasAnimatedAvatar = 0; + cb.m_bHasAvatarFrame = 0; + cb.m_bHasProfileModifier = 0; + cb.m_bHasProfileBackground = 0; + cb.m_bHasMiniProfileBackground = 0; + push_call_result(h, lsc_cb::kEquippedProfileItems, + &cb, sizeof(cb), /*io_failure=*/false); + return h; + } + virtual bool BHasEquippedProfileItem(uint64_t, int) { return false; } + virtual const char* GetProfileItemPropertyString(uint64_t, int, int) { return ""; } + virtual uint32_t GetProfileItemPropertyUint(uint64_t, int, int) { return 0; } +}; + +class ISteamRemoteStorageStub { +public: + static std::string resolve_cloud_path(const char* pchFile) { + if (!pchFile || !*pchFile) return {}; + for (const char* p = pchFile; *p; ++p) { + if (*p == '\\') return {}; + } + if (pchFile[0] == '/') return {}; + std::string fname(pchFile); + if (fname.find("..") != std::string::npos) return {}; + uint32_t app = pushed().app_id.load(); + if (app == 0) return {}; + std::lock_guard lk(state_mutex()); + auto it = pushed().app_cloud_remote_dirs.find(app); + if (it == pushed().app_cloud_remote_dirs.end()) return {}; + std::string out = it->second; + if (!out.empty() && out.back() != '/') out.push_back('/'); + out.append(fname); + return out; + } + virtual bool FileWrite(const char* pchFile, const void* pvData, int cubData) { + if (!pvData || cubData <= 0) return false; + std::string path = resolve_cloud_path(pchFile); + if (path.empty()) return false; + size_t slash = path.find_last_of('/'); + if (slash != std::string::npos) { + std::string dir = path.substr(0, slash); + mkdir(dir.c_str(), 0755); + } + int fd = ::open(path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd < 0) return false; + ssize_t total = 0; + const char* p = static_cast(pvData); + while (total < cubData) { + ssize_t n = ::write(fd, p + total, cubData - total); + if (n < 0) { + ::close(fd); + ::unlink(path.c_str()); + return false; + } + total += n; + } + ::close(fd); + std::lock_guard lk(state_mutex()); + auto& files = pushed().cloud_files; + std::string name(pchFile); + bool patched = false; + for (auto& f : files) { + if (f.name == name) { + f.size = static_cast(cubData); + f.timestamp = static_cast(::time(nullptr)); + patched = true; + break; + } + } + if (!patched) { + wn_libsteamclient::PushedState::CloudFileEntry e; + e.name = std::move(name); + e.size = static_cast(cubData); + e.timestamp = static_cast(::time(nullptr)); + files.push_back(std::move(e)); + } + return true; + } + virtual int FileRead(const char* pchFile, void* pvData, int cubDataToRead) { + if (!pvData || cubDataToRead <= 0) return 0; + std::string path = resolve_cloud_path(pchFile); + if (path.empty()) return 0; + int fd = ::open(path.c_str(), O_RDONLY); + if (fd < 0) return 0; + ssize_t total = 0; + char* p = static_cast(pvData); + while (total < cubDataToRead) { + ssize_t n = ::read(fd, p + total, cubDataToRead - total); + if (n < 0) { ::close(fd); return 0; } + if (n == 0) break; // EOF + total += n; + } + ::close(fd); + return static_cast(total); + } + virtual uint64_t FileWriteAsync(const char* pchFile, const void* pvData, uint32_t cubData) { + if (!pvData || cubData == 0) return 0; + std::string path = resolve_cloud_path(pchFile); + if (path.empty()) return 0; + bool ok = FileWrite(pchFile, pvData, static_cast(cubData)); + uint64_t hCall = alloc_api_call_handle(); + wn_libsteamclient::callbacks::RemoteStorageFileWriteAsyncComplete cb{}; + cb.m_eResult = ok ? 1 /*k_EResultOK*/ : 2 /*k_EResultFail*/; + push_call_result(hCall, + lsc_cb::kRemoteStorageFileWriteAsyncComplete, + &cb, sizeof(cb), /*io_failure=*/false); + return hCall; + } + virtual uint64_t FileReadAsync(const char* pchFile, uint32_t nOffset, uint32_t cubToRead) { + if (cubToRead == 0) return 0; + std::string path = resolve_cloud_path(pchFile); + if (path.empty()) return 0; + int fd = ::open(path.c_str(), O_RDONLY); + if (fd < 0) return 0; + if (nOffset > 0 && ::lseek(fd, nOffset, SEEK_SET) == (off_t)-1) { + ::close(fd); + return 0; + } + std::vector buf(cubToRead); + ssize_t total = 0; + while (total < (ssize_t)cubToRead) { + ssize_t n = ::read(fd, buf.data() + total, cubToRead - total); + if (n < 0) { ::close(fd); return 0; } + if (n == 0) break; + total += n; + } + ::close(fd); + buf.resize(total); + uint64_t hCall = alloc_api_call_handle(); + { + std::lock_guard lk(async_read_mu()); + async_read_buffers()[hCall] = std::move(buf); + } + wn_libsteamclient::callbacks::RemoteStorageFileReadAsyncComplete cb{}; + cb.m_hFileReadAsync = hCall; + cb.m_eResult = 1 /*k_EResultOK*/; + cb.m_nOffset = nOffset; + cb.m_cubRead = static_cast(total); + push_call_result(hCall, + lsc_cb::kRemoteStorageFileReadAsyncComplete, + &cb, sizeof(cb), /*io_failure=*/false); + return hCall; + } + virtual bool FileReadAsyncComplete(uint64_t hCall, void* pvBuffer, uint32_t cubToRead) { + if (!pvBuffer || cubToRead == 0 || hCall == 0) return false; + std::lock_guard lk(async_read_mu()); + auto& m = async_read_buffers(); + auto it = m.find(hCall); + if (it == m.end()) return false; + const auto& buf = it->second; + if (cubToRead < buf.size()) { + return false; + } + std::memcpy(pvBuffer, buf.data(), buf.size()); + m.erase(it); + return true; + } + virtual bool FileForget(const char* pchFile) { + if (!pchFile || !*pchFile) return false; + std::lock_guard lk(state_mutex()); + auto& files = pushed().cloud_files; + std::string name(pchFile); + bool found = false; + for (auto it = files.begin(); it != files.end(); ) { + if (it->name == name) { it = files.erase(it); found = true; } + else ++it; + } + return found; + } + virtual bool FileDelete(const char* pchFile) { + std::string path = resolve_cloud_path(pchFile); + if (path.empty()) return false; + int rc = ::unlink(path.c_str()); + std::lock_guard lk(state_mutex()); + auto& files = pushed().cloud_files; + std::string name(pchFile); + for (auto it = files.begin(); it != files.end(); ) { + if (it->name == name) it = files.erase(it); + else ++it; + } + return rc == 0; + } + virtual uint64_t FileShare(const char* pchFile) { + if (!pchFile || !*pchFile) return 0; + uint64_t hCall = alloc_api_call_handle(); + lsc_cb::RemoteStorageFileShareResult cb{}; + std::strncpy(cb.m_rgchFilename, pchFile, sizeof(cb.m_rgchFilename) - 1); + bool found = false; + int32_t size = 0; + int64_t ts = 0; + { + std::lock_guard lk(state_mutex()); + for (const auto& f : pushed().cloud_files) { + if (f.name == pchFile) { + found = true; + size = f.size; + ts = f.timestamp; + break; + } + } + } + if (!found) { + cb.m_eResult = 9; // k_EResultFileNotFound + cb.m_hFile = 0; + } else { + cb.m_eResult = 1; // k_EResultOK + uint64_t h = 0xcbf29ce484222325ULL; // FNV-1a 64 seed + auto mix = [&](const void* d, size_t n) { + const uint8_t* p = static_cast(d); + for (size_t i = 0; i < n; ++i) { + h ^= p[i]; + h *= 0x100000001b3ULL; + } + }; + uint32_t app = pushed().app_id.load(); + mix(&app, sizeof(app)); + mix(pchFile, std::strlen(pchFile)); + mix(&size, sizeof(size)); + mix(&ts, sizeof(ts)); + cb.m_hFile = h | (1ULL << 63); // set hi bit so it never collides with hCall + } + push_call_result(hCall, lsc_cb::kRemoteStorageFileShareResult, + &cb, sizeof(cb), /*io_failure=*/false); + return hCall; + } + virtual bool SetSyncPlatforms(const char* pchFile, int /*ePlatform*/) { + if (!pchFile || !*pchFile) return false; + std::lock_guard lk(state_mutex()); + for (const auto& f : pushed().cloud_files) { + if (f.name == pchFile) return true; + } + return false; + } + virtual uint64_t FileWriteStreamOpen(const char* pchFile) { + std::string finalPath = resolve_cloud_path(pchFile); + if (finalPath.empty()) return 0; + size_t slash = finalPath.find_last_of('/'); + if (slash != std::string::npos) { + mkdir(finalPath.substr(0, slash).c_str(), 0755); + } + uint64_t h = alloc_api_call_handle(); + char suffix[64]; + std::snprintf(suffix, sizeof(suffix), ".wnstream-%llu", + static_cast(h)); + std::string tempPath = finalPath + suffix; + int fd = ::open(tempPath.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd < 0) return 0; + std::lock_guard lk(stream_mu()); + StreamSlot s; + s.fd = fd; + s.tempPath = std::move(tempPath); + s.finalPath = std::move(finalPath); + s.name = std::string(pchFile); + s.bytes = 0; + streams()[h] = std::move(s); + return h; + } + virtual bool FileWriteStreamWriteChunk(uint64_t h, const void* pvData, int cubData) { + if (!pvData || cubData <= 0) return false; + std::lock_guard lk(stream_mu()); + auto it = streams().find(h); + if (it == streams().end()) return false; + const char* p = static_cast(pvData); + int total = 0; + while (total < cubData) { + ssize_t n = ::write(it->second.fd, p + total, cubData - total); + if (n < 0) { + return false; + } + total += n; + } + it->second.bytes += total; + return true; + } + virtual bool FileWriteStreamClose(uint64_t h) { + StreamSlot slot; + { + std::lock_guard lk(stream_mu()); + auto it = streams().find(h); + if (it == streams().end()) return false; + slot = std::move(it->second); + streams().erase(it); + } + ::fsync(slot.fd); + ::close(slot.fd); + if (::rename(slot.tempPath.c_str(), slot.finalPath.c_str()) != 0) { + ::unlink(slot.tempPath.c_str()); + return false; + } + std::lock_guard lk(state_mutex()); + auto& files = pushed().cloud_files; + bool patched = false; + for (auto& f : files) { + if (f.name == slot.name) { + f.size = static_cast(slot.bytes); + f.timestamp = static_cast(::time(nullptr)); + patched = true; + break; + } + } + if (!patched) { + wn_libsteamclient::PushedState::CloudFileEntry e; + e.name = slot.name; + e.size = static_cast(slot.bytes); + e.timestamp = static_cast(::time(nullptr)); + files.push_back(std::move(e)); + } + return true; + } + virtual bool FileWriteStreamCancel(uint64_t h) { + std::lock_guard lk(stream_mu()); + auto it = streams().find(h); + if (it == streams().end()) return false; + ::close(it->second.fd); + ::unlink(it->second.tempPath.c_str()); + streams().erase(it); + return true; + } + + virtual bool FileExists(const char* pchFile) { + if (!pchFile || !*pchFile) return false; + std::lock_guard lk(state_mutex()); + for (const auto& f : pushed().cloud_files) { + if (f.name == pchFile) return true; + } + return false; + } + virtual bool FilePersisted(const char* pchFile) { + if (!pchFile || !*pchFile) return false; + std::lock_guard lk(state_mutex()); + for (const auto& f : pushed().cloud_files) { + if (f.name == pchFile) return true; + } + return false; + } + + virtual int GetFileSize(const char* pchFile) { + if (!pchFile || !*pchFile) return 0; + std::lock_guard lk(state_mutex()); + for (const auto& f : pushed().cloud_files) { + if (f.name == pchFile) return static_cast(f.size); + } + return 0; + } + virtual int64_t GetFileTimestamp(const char* pchFile) { + if (!pchFile || !*pchFile) return 0; + std::lock_guard lk(state_mutex()); + for (const auto& f : pushed().cloud_files) { + if (f.name == pchFile) return f.timestamp; + } + return 0; + } + virtual int GetSyncPlatforms(const char* pchFile) { + if (!pchFile || !*pchFile) return 0; + std::lock_guard lk(state_mutex()); + for (const auto& f : pushed().cloud_files) { + if (f.name == pchFile) return -1; // k_ERemoteStoragePlatformAll + } + return 0; + } + + virtual int GetFileCount() { + std::lock_guard lk(state_mutex()); + return static_cast(pushed().cloud_files.size()); + } + virtual const char* GetFileNameAndSize(int iFile, int32_t* pnFileSizeInBytes) { + static thread_local std::string tls_name; + tls_name.clear(); + int32_t size = 0; + { + std::lock_guard lk(state_mutex()); + const auto& files = pushed().cloud_files; + if (iFile >= 0 && static_cast(iFile) < files.size()) { + tls_name = files[iFile].name; + size = files[iFile].size; + } + } + if (pnFileSizeInBytes) *pnFileSizeInBytes = size; + return tls_name.c_str(); + } + virtual void GetQuota(uint64_t* total, uint64_t* avail) { + if (total) *total = pushed().cloud_quota_total.load(); + if (avail) *avail = pushed().cloud_quota_available.load(); + } + virtual bool IsCloudEnabledForAccount() { + return pushed().cloud_enabled_account.load(); + } + virtual bool IsCloudEnabledForApp() { + return pushed().cloud_enabled_app.load(); + } + virtual void SetCloudEnabledForApp(bool enabled) { + pushed().cloud_enabled_app.store(enabled); + } + virtual uint64_t UGCDownload(uint64_t hContent, uint32_t /*priority*/) { + uint64_t h = alloc_api_call_handle(); + lsc_cb::RemoteStorageDownloadUGCResult cb{}; + cb.m_eResult = 2; // k_EResultFail — no UGC backend + cb.m_hFile = hContent; + cb.m_nAppID = pushed().app_id.load(); + cb.m_nSizeInBytes = 0; + cb.m_pchFileName[0] = '\0'; + cb.m_ulSteamIDOwner = 0; + push_call_result(h, lsc_cb::kRemoteStorageDownloadUGC, + &cb, sizeof(cb), /*io_failure=*/false); + return h; + } + virtual bool GetUGCDownloadProgress(uint64_t, int32_t* d, int32_t* e) { + if (d) *d = 0; if (e) *e = 0; return false; + } + virtual bool GetUGCDetails(uint64_t /*content*/, uint32_t* appID, + char** ppchName, int32_t* pcbFile, uint64_t* steamIDOwner) { + if (appID) *appID = 0; + if (ppchName) *ppchName = nullptr; + if (pcbFile) *pcbFile = 0; + if (steamIDOwner) *steamIDOwner = 0; + return false; + } + virtual int32_t UGCRead(uint64_t /*content*/, void* /*buf*/, int32_t /*cubData*/, + uint32_t /*offset*/, int /*action*/) { return 0; } + virtual int32_t GetCachedUGCCount() { return 0; } + virtual uint64_t GetCachedUGCHandle(int32_t /*idx*/) { return 0; } + virtual uint64_t PublishWorkshopFile_DEPRECATED(const char*, const char*, uint32_t, const char*, const char*, int, void*, void*, int) { return 0; } + virtual uint64_t CreatePublishedFileUpdateRequest_DEPRECATED(uint64_t) { return 0; } + virtual bool UpdatePublishedFileFile_DEPRECATED(uint64_t, const char*) { return false; } + virtual bool UpdatePublishedFilePreviewFile_DEPRECATED(uint64_t, const char*) { return false; } + virtual bool UpdatePublishedFileTitle_DEPRECATED(uint64_t, const char*) { return false; } + virtual bool UpdatePublishedFileDescription_DEPRECATED(uint64_t, const char*) { return false; } + virtual bool UpdatePublishedFileVisibility_DEPRECATED(uint64_t, int) { return false; } + virtual bool UpdatePublishedFileTags_DEPRECATED(uint64_t, void*) { return false; } + virtual uint64_t CommitPublishedFileUpdate_DEPRECATED(uint64_t) { return 0; } + virtual uint64_t GetPublishedFileDetails_DEPRECATED(uint64_t, uint32_t) { return 0; } + virtual uint64_t DeletePublishedFile_DEPRECATED(uint64_t) { return 0; } + virtual uint64_t EnumerateUserPublishedFiles_DEPRECATED(uint32_t) { return 0; } + virtual uint64_t SubscribePublishedFile_DEPRECATED(uint64_t) { return 0; } + virtual uint64_t EnumerateUserSubscribedFiles_DEPRECATED(uint32_t) { return 0; } + virtual uint64_t UnsubscribePublishedFile_DEPRECATED(uint64_t) { return 0; } + virtual bool UpdatePublishedFileSetChangeDescription_DEPRECATED(uint64_t, const char*) { return false; } + virtual uint64_t GetPublishedItemVoteDetails_DEPRECATED(uint64_t) { return 0; } + virtual uint64_t UpdateUserPublishedItemVote_DEPRECATED(uint64_t, bool) { return 0; } + virtual uint64_t GetUserPublishedItemVoteDetails_DEPRECATED(uint64_t) { return 0; } + virtual uint64_t EnumerateUserSharedWorkshopFiles_DEPRECATED(uint64_t, uint32_t, void*, void*) { return 0; } + virtual uint64_t PublishVideo_DEPRECATED(int, const char*, uint32_t, const char*, const char*, uint32_t, void*) { return 0; } + virtual uint64_t SetUserPublishedFileAction_DEPRECATED(uint64_t, int) { return 0; } + virtual uint64_t EnumeratePublishedFilesByUserAction_DEPRECATED(int, uint32_t) { return 0; } + virtual uint64_t EnumeratePublishedWorkshopFiles_DEPRECATED(int, uint32_t, uint32_t, uint32_t, void*, void*) { return 0; } + virtual uint64_t UGCDownloadToLocation(uint64_t hContent, const char* /*location*/, uint32_t /*priority*/) { + uint64_t h = alloc_api_call_handle(); + lsc_cb::RemoteStorageDownloadUGCResult cb{}; + cb.m_eResult = 2; + cb.m_hFile = hContent; + cb.m_nAppID = pushed().app_id.load(); + cb.m_nSizeInBytes = 0; + cb.m_pchFileName[0] = '\0'; + cb.m_ulSteamIDOwner = 0; + push_call_result(h, lsc_cb::kRemoteStorageDownloadUGC, + &cb, sizeof(cb), /*io_failure=*/false); + return h; + } + virtual int32_t GetLocalFileChangeCount() { return 0; } + virtual const char* GetLocalFileChange(int32_t /*idx*/, int* peChangeType, int* pePathType) { + if (peChangeType) *peChangeType = 0; + if (pePathType) *pePathType = 0; + return ""; + } + virtual bool BeginFileWriteBatch() { return true; } + virtual bool EndFileWriteBatch() { return true; } +}; + +class ISteamUserStatsStub { +public: + virtual bool RequestCurrentStats() { + if (!state().logged_on.load()) return false; + if (pushed().stats_ready.load()) { + lsc_cb::UserStatsReceived payload{}; + payload.m_nGameID = static_cast(pushed().app_id.load()); + payload.m_eResult = 1; // k_EResultOK + payload.m_steamIDUser = pushed().steam_id.load(); + push_callback(state().user.load(), + lsc_cb::kUserStatsReceived, + &payload, sizeof(payload)); + } + return true; + } + virtual bool GetStatInt(const char* pchName, int32_t* pData) { + if (!pchName || !pData) return false; + std::lock_guard lk(state_mutex()); + auto it = pushed().stats_int.find(pchName); + if (it == pushed().stats_int.end()) return false; + return true; + } + virtual bool GetStatFloat(const char* pchName, float* pData) { + if (!pchName || !pData) return false; + std::lock_guard lk(state_mutex()); + auto it = pushed().stats_float.find(pchName); + if (it == pushed().stats_float.end()) return false; + return true; + } + virtual bool SetStatInt(const char* pchName, int32_t nData) { + if (!pchName) return false; + std::lock_guard lk(state_mutex()); + pushed().stats_int[pchName] = nData; + pushed().dirty_stats_int.insert(pchName); + return true; + } + virtual bool SetStatFloat(const char* pchName, float fData) { + if (!pchName) return false; + std::lock_guard lk(state_mutex()); + pushed().stats_float[pchName] = fData; + pushed().dirty_stats_float.insert(pchName); + return true; + } + virtual bool UpdateAvgRateStat(const char* pchName, + float flCountThisSession, + double dSessionLength) { + if (!pchName || dSessionLength <= 0.0) return false; + std::lock_guard lk(state_mutex()); + auto& acc = pushed().stats_avg_rate[pchName]; + acc.total_count += static_cast(flCountThisSession); + acc.total_time += dSessionLength; + if (acc.total_time > 0.0) { + pushed().stats_float[pchName] = + static_cast(acc.total_count / acc.total_time); + } + pushed().dirty_stats_float.insert(pchName); + return true; + } + virtual bool GetAchievement(const char* pchName, bool* pbAchieved) { + if (!pchName || !pbAchieved) return false; + std::lock_guard lk(state_mutex()); + auto it = pushed().achievement_index.find(pchName); + if (it == pushed().achievement_index.end()) return false; + return true; + } + virtual bool SetAchievement(const char* pchName) { + if (!pchName) return false; + std::lock_guard lk(state_mutex()); + auto it = pushed().achievement_index.find(pchName); + if (it == pushed().achievement_index.end()) return false; + auto& a = pushed().achievements[it->second]; + if (!a.achieved) { + a.achieved = true; + a.unlock_time = static_cast(::time(nullptr)); + a.pending_store = true; + } + return true; + } + virtual bool ClearAchievement(const char* pchName) { + if (!pchName) return false; + std::lock_guard lk(state_mutex()); + auto it = pushed().achievement_index.find(pchName); + if (it == pushed().achievement_index.end()) return false; + auto& a = pushed().achievements[it->second]; + bool was = a.achieved; + a.achieved = false; + a.unlock_time = 0; + if (was) a.pending_store = true; + return true; + } + virtual bool GetAchievementAndUnlockTime(const char* pchName, + bool* pbAchieved, + uint32_t* punlockTime) { + if (!pchName) return false; + std::lock_guard lk(state_mutex()); + auto it = pushed().achievement_index.find(pchName); + if (it == pushed().achievement_index.end()) return false; + const auto& a = pushed().achievements[it->second]; + if (pbAchieved) *pbAchieved = a.achieved; + if (punlockTime) *punlockTime = a.unlock_time; + return true; + } + virtual bool StoreStats() { + bool ready = pushed().stats_ready.load(); + uint32_t app_id = pushed().app_id.load(); + uint64_t game_id = static_cast(app_id); + + struct DirtyAch { std::string name; int32_t block_id; int32_t bit_index; bool achieved; }; + std::vector dirty; + std::unordered_map stats_int_snapshot; + std::vector> dirty_stat_uploads; + { + std::lock_guard lk(state_mutex()); + for (auto& a : pushed().achievements) { + if (a.pending_store) { + dirty.push_back(DirtyAch{a.api_name, a.block_id, + a.bit_index, a.achieved}); + a.pending_store = false; + } + } + stats_int_snapshot = pushed().stats_int; + for (const auto& name : pushed().dirty_stats_int) { + auto idIt = pushed().stat_name_to_id.find(name); + if (idIt == pushed().stat_name_to_id.end()) continue; + auto vIt = pushed().stats_int.find(name); + uint32_t v = (vIt != pushed().stats_int.end()) + ? static_cast(vIt->second) : 0u; + dirty_stat_uploads.emplace_back(idIt->second, v); + } + for (const auto& name : pushed().dirty_stats_float) { + auto idIt = pushed().stat_name_to_id.find(name); + if (idIt == pushed().stat_name_to_id.end()) continue; + auto vIt = pushed().stats_float.find(name); + uint32_t bits = 0; + if (vIt != pushed().stats_float.end()) { + float f = vIt->second; + std::memcpy(&bits, &f, sizeof(bits)); + } + dirty_stat_uploads.emplace_back(idIt->second, bits); + } + pushed().dirty_stats_int.clear(); + pushed().dirty_stats_float.clear(); + } + + for (const auto& d : dirty) { + lsc_cb::UserAchievementStored ach{}; + ach.m_nGameID = game_id; + ach.m_bGroupAchievement = false; + std::strncpy(ach.m_rgchAchievementName, + d.name.c_str(), + lsc_cb::kAchievementNameMax - 1); + ach.m_rgchAchievementName[lsc_cb::kAchievementNameMax - 1] = '\0'; + ach.m_nCurProgress = 0; + ach.m_nMaxProgress = 0; + push_callback(state().user.load(), + lsc_cb::kUserAchievementStored, + &ach, sizeof(ach)); + } + + if (app_id != 0 && (!dirty.empty() || !dirty_stat_uploads.empty())) { + std::unordered_map stat_id_to_value; + for (const auto& [id, v] : dirty_stat_uploads) { + stat_id_to_value[id] = v; + } + for (const auto& d : dirty) { + if (d.block_id < 0) continue; + uint32_t stat_id = static_cast(d.block_id); + auto it = stat_id_to_value.find(stat_id); + if (it == stat_id_to_value.end()) { + char key[16]; + std::snprintf(key, sizeof(key), "%u", stat_id); + auto sit = stats_int_snapshot.find(key); + uint32_t cur = (sit != stats_int_snapshot.end()) + ? static_cast(sit->second) + : 0u; + it = stat_id_to_value.emplace(stat_id, cur).first; + } + if (d.bit_index >= 0 && d.bit_index < 32) { + uint32_t mask = 1u << static_cast(d.bit_index); + if (d.achieved) it->second |= mask; + else it->second &= ~mask; + } + } + if (!stat_id_to_value.empty()) { + std::vector ids; + std::vector vals; + ids.reserve(stat_id_to_value.size()); + vals.reserve(stat_id_to_value.size()); + for (auto& [k, v] : stat_id_to_value) { + ids.push_back(k); + vals.push_back(v); + } + wn_cm_store_user_stats(app_id, /*crc_stats=*/0, + ids.data(), vals.data(), + ids.size()); + __android_log_print(ANDROID_LOG_INFO, "WnLibSteamClient", + "StoreStats: pushed %zu stat(s) to Steam (app=%u, " + "ach_dirty=%zu, stat_dirty=%zu)", + ids.size(), app_id, dirty.size(), dirty_stat_uploads.size()); + } + } + + lsc_cb::UserStatsStored payload{}; + payload.m_nGameID = game_id; + payload.m_eResult = ready ? 1 : 2; + push_callback(state().user.load(), + lsc_cb::kUserStatsStored, + &payload, sizeof(payload)); + return ready; + } + virtual int GetAchievementIcon(const char* pchName) { + if (!pchName) return 0; + std::lock_guard lk(state_mutex()); + auto it = pushed().achievement_index.find(pchName); + if (it == pushed().achievement_index.end()) return 0; + return pushed().achievements[it->second].icon_handle; + } + virtual const char* GetAchievementDisplayAttribute(const char* pchName, + const char* pchKey) { + static thread_local std::string tls_attr; + tls_attr.clear(); + if (!pchName || !pchKey) return ""; + std::lock_guard lk(state_mutex()); + auto it = pushed().achievement_index.find(pchName); + if (it == pushed().achievement_index.end()) return ""; + const auto& a = pushed().achievements[it->second]; + + auto pick_locale = [&](const std::unordered_map& m) + -> const std::string& { + static const std::string kEmpty; + const std::string& ui = pushed().ui_language; + if (!ui.empty()) { + auto h = m.find(ui); + if (h != m.end() && !h->second.empty()) return h->second; + } + auto h = m.find("english"); + if (h != m.end() && !h->second.empty()) return h->second; + for (const auto& kv : m) { + if (!kv.second.empty()) return kv.second; + } + return kEmpty; + }; + + if (std::strcmp(pchKey, "name") == 0) tls_attr = pick_locale(a.display_names); + else if (std::strcmp(pchKey, "desc") == 0) tls_attr = pick_locale(a.descriptions); + else if (std::strcmp(pchKey, "hidden") == 0) tls_attr = a.hidden ? "1" : "0"; + return tls_attr.c_str(); + } + virtual bool IndicateAchievementProgress(const char* pchName, + uint32_t nCurProgress, + uint32_t nMaxProgress) { + if (!pchName) return false; + uint64_t game_id = static_cast(pushed().app_id.load()); + { + std::lock_guard lk(state_mutex()); + auto it = pushed().achievement_index.find(pchName); + if (it == pushed().achievement_index.end()) return false; + if (pushed().achievements[it->second].achieved) return false; + } + lsc_cb::UserAchievementStored ach{}; + ach.m_nGameID = game_id; + ach.m_bGroupAchievement = false; + std::strncpy(ach.m_rgchAchievementName, pchName, + lsc_cb::kAchievementNameMax - 1); + ach.m_rgchAchievementName[lsc_cb::kAchievementNameMax - 1] = '\0'; + ach.m_nCurProgress = nCurProgress; + ach.m_nMaxProgress = nMaxProgress; + push_callback(state().user.load(), + lsc_cb::kUserAchievementStored, + &ach, sizeof(ach)); + return true; + } + virtual uint32_t GetNumAchievements() { + std::lock_guard lk(state_mutex()); + return static_cast(pushed().achievements.size()); + } + virtual const char* GetAchievementName(uint32_t idx) { + static thread_local std::string tls_name; + tls_name.clear(); + std::lock_guard lk(state_mutex()); + const auto& a = pushed().achievements; + if (idx < a.size()) tls_name = a[idx].api_name; + return tls_name.c_str(); + } + virtual uint64_t RequestUserStats(uint64_t steamID) { + uint64_t h = alloc_api_call_handle(); + lsc_cb::UserStatsReceived payload{}; + payload.m_nGameID = static_cast(pushed().app_id.load()); + payload.m_eResult = 6; // k_EResultNoConnection — we never asked + payload.m_steamIDUser = steamID; + push_call_result(h, lsc_cb::kUserStatsReceived, + &payload, sizeof(payload), /*io_failure=*/false); + return h; + } + virtual bool GetUserStatInt(uint64_t steamID, const char* pchName, int32_t* pData) { + if (!pchName || !pData) return false; + if (steamID != pushed().steam_id.load()) return false; + return GetStatInt(pchName, pData); + } + virtual bool GetUserStatFloat(uint64_t steamID, const char* pchName, float* pData) { + if (!pchName || !pData) return false; + if (steamID != pushed().steam_id.load()) return false; + return GetStatFloat(pchName, pData); + } + virtual bool GetUserAchievement(uint64_t steamID, const char* pchName, bool* pbAchieved) { + if (steamID != pushed().steam_id.load()) return false; + return GetAchievement(pchName, pbAchieved); + } + virtual bool GetUserAchievementAndUnlockTime(uint64_t steamID, const char* pchName, + bool* pbAchieved, uint32_t* punlockTime) { + if (steamID != pushed().steam_id.load()) return false; + return GetAchievementAndUnlockTime(pchName, pbAchieved, punlockTime); + } + virtual bool ResetAllStats(bool bAchievementsToo) { + std::lock_guard lk(state_mutex()); + for (auto& [name, _] : pushed().stats_int) { + pushed().dirty_stats_int.insert(name); + } + pushed().stats_int.clear(); + for (auto& [name, _] : pushed().stats_float) { + pushed().dirty_stats_float.insert(name); + } + pushed().stats_float.clear(); + if (bAchievementsToo) { + for (auto& a : pushed().achievements) { + bool was = a.achieved; + a.achieved = false; + a.unlock_time = 0; + if (was) a.pending_store = true; + } + } + return true; + } + virtual uint64_t FindOrCreateLeaderboard(const char* /*name*/, + int /*sortMethod*/, int /*displayType*/) { + uint64_t h = alloc_api_call_handle(); + lsc_cb::LeaderboardFindResult cb{}; + cb.m_hSteamLeaderboard = 0; + cb.m_bLeaderboardFound = 0; + push_call_result(h, lsc_cb::kLeaderboardFindResult, + &cb, sizeof(cb), /*io_failure=*/false); + return h; + } + virtual uint64_t FindLeaderboard(const char* /*name*/) { + uint64_t h = alloc_api_call_handle(); + lsc_cb::LeaderboardFindResult cb{}; + cb.m_hSteamLeaderboard = 0; + cb.m_bLeaderboardFound = 0; + push_call_result(h, lsc_cb::kLeaderboardFindResult, + &cb, sizeof(cb), /*io_failure=*/false); + return h; + } + virtual const char* GetLeaderboardName(uint64_t) { return ""; } + virtual int GetLeaderboardEntryCount(uint64_t) { return 0; } + virtual int GetLeaderboardSortMethod(uint64_t) { return 0; } + virtual int GetLeaderboardDisplayType(uint64_t) { return 0; } + virtual uint64_t DownloadLeaderboardEntries(uint64_t hLeaderboard, + int /*eRange*/, int /*rangeStart*/, + int /*rangeEnd*/) { + uint64_t h = alloc_api_call_handle(); + lsc_cb::LeaderboardScoresDownloaded cb{}; + cb.m_hSteamLeaderboard = hLeaderboard; + cb.m_hSteamLeaderboardEntries = 0; + cb.m_cEntryCount = 0; + push_call_result(h, lsc_cb::kLeaderboardScoresDownloaded, + &cb, sizeof(cb), /*io_failure=*/false); + return h; + } + virtual uint64_t DownloadLeaderboardEntriesForUsers(uint64_t hLeaderboard, + uint64_t* /*pUsers*/, + int /*cUsers*/) { + uint64_t h = alloc_api_call_handle(); + lsc_cb::LeaderboardScoresDownloaded cb{}; + cb.m_hSteamLeaderboard = hLeaderboard; + cb.m_hSteamLeaderboardEntries = 0; + cb.m_cEntryCount = 0; + push_call_result(h, lsc_cb::kLeaderboardScoresDownloaded, + &cb, sizeof(cb), /*io_failure=*/false); + return h; + } + virtual bool GetDownloadedLeaderboardEntry(uint64_t, int, void*, int32_t*, int) { return false; } + virtual uint64_t UploadLeaderboardScore(uint64_t hLeaderboard, + int /*method*/, int32_t score, + const int32_t* /*details*/, + int /*cDetails*/) { + uint64_t h = alloc_api_call_handle(); + lsc_cb::LeaderboardScoreUploaded cb{}; + cb.m_bSuccess = 0; // server-side store not implemented + cb.m_hSteamLeaderboard = hLeaderboard; + cb.m_nScore = score; + cb.m_bScoreChanged = 0; + cb.m_nGlobalRankNew = 0; + cb.m_nGlobalRankPrevious = 0; + push_call_result(h, lsc_cb::kLeaderboardScoreUploaded, + &cb, sizeof(cb), /*io_failure=*/false); + return h; + } + virtual uint64_t AttachLeaderboardUGC(uint64_t hLeaderboard, uint64_t /*hUGC*/) { + uint64_t h = alloc_api_call_handle(); + lsc_cb::LeaderboardUGCSet cb{}; + cb.m_eResult = 2; // k_EResultFail + cb.m_hSteamLeaderboard = hLeaderboard; + push_call_result(h, lsc_cb::kLeaderboardUGCSet, + &cb, sizeof(cb), /*io_failure=*/false); + return h; + } + virtual uint64_t GetNumberOfCurrentPlayers() { + uint64_t h = alloc_api_call_handle(); + lsc_cb::NumberOfCurrentPlayers cb{}; + cb.m_bSuccess = 1; + cb.m_cPlayers = 0; + push_call_result(h, lsc_cb::kNumberOfCurrentPlayers, + &cb, sizeof(cb), /*io_failure=*/false); + return h; + } + virtual uint64_t RequestGlobalAchievementPercentages() { + uint64_t h = alloc_api_call_handle(); + lsc_cb::GlobalAchievementPercentagesReady cb{}; + cb.m_nGameID = static_cast(pushed().app_id.load()); + cb.m_eResult = 2; // k_EResultFail + push_call_result(h, lsc_cb::kGlobalAchievementPercentages, + &cb, sizeof(cb), /*io_failure=*/false); + return h; + } + virtual int GetMostAchievedAchievementInfo(char*, uint32_t, float*, bool*) { return -1; } + virtual int GetNextMostAchievedAchievementInfo(int, char*, uint32_t, float*, bool*) { return -1; } + virtual bool GetAchievementAchievedPercent(const char*, float* p) { + if (p) *p = 0.0f; + return false; + } + virtual uint64_t RequestGlobalStats(int /*historicalDays*/) { + uint64_t h = alloc_api_call_handle(); + lsc_cb::GlobalStatsReceived cb{}; + cb.m_nGameID = static_cast(pushed().app_id.load()); + cb.m_eResult = 2; // k_EResultFail + push_call_result(h, lsc_cb::kGlobalStatsReceived, + &cb, sizeof(cb), /*io_failure=*/false); + return h; + } + virtual bool GetGlobalStatInt64(const char*, int64_t* p) { if (p) *p = 0; return false; } + virtual bool GetGlobalStatDouble(const char*, double* p) { if (p) *p = 0.0; return false; } + virtual int GetGlobalStatHistoryInt64(const char*, int64_t*, uint32_t) { return 0; } + virtual int GetGlobalStatHistoryDouble(const char*, double*, uint32_t) { return 0; } +}; + +class ISteamInventoryStub { +public: + virtual int GetResultStatus(int /*resultHandle*/) { return 8; /*InvalidParam*/ } + virtual bool GetResultItems(int, void*, uint32_t* pcb) { if (pcb) *pcb = 0; return false; } + virtual bool GetResultItemProperty(int, uint32_t, const char*, char* buf, uint32_t* cb) { + if (buf && cb && *cb > 0) buf[0] = '\0'; + if (cb) *cb = 0; + return false; + } + virtual uint32_t GetResultTimestamp(int) { return 0; } + virtual bool CheckResultSteamID(int, uint64_t) { return false; } + virtual void DestroyResult(int) {} + virtual bool GetAllItems(int* phRes) { if (phRes) *phRes = -1; return false; } + virtual bool GetItemsByID(int* phRes, const uint64_t*, uint32_t) { if (phRes) *phRes = -1; return false; } + virtual bool SerializeResult(int, void*, uint32_t* pcb) { if (pcb) *pcb = 0; return false; } + virtual bool DeserializeResult(int* phRes, const void*, uint32_t, bool) { if (phRes) *phRes = -1; return false; } + virtual bool GenerateItems(int* phRes, const int32_t*, const uint32_t*, uint32_t) { if (phRes) *phRes = -1; return false; } + virtual bool GrantPromoItems(int* phRes) { if (phRes) *phRes = -1; return false; } + virtual bool AddPromoItem(int* phRes, int32_t) { if (phRes) *phRes = -1; return false; } + virtual bool AddPromoItems(int* phRes, const int32_t*, uint32_t) { if (phRes) *phRes = -1; return false; } + virtual bool ConsumeItem(int* phRes, uint64_t, uint32_t) { if (phRes) *phRes = -1; return false; } + virtual bool ExchangeItems(int* phRes, const int32_t*, const uint32_t*, uint32_t, + const uint64_t*, const uint32_t*, uint32_t) { + if (phRes) *phRes = -1; return false; + } + virtual bool TransferItemQuantity(int* phRes, uint64_t, uint32_t, uint64_t) { if (phRes) *phRes = -1; return false; } + virtual void SendItemDropHeartbeat() {} + virtual bool TriggerItemDrop(int* phRes, int32_t) { if (phRes) *phRes = -1; return false; } + virtual bool TradeItems(int* phRes, uint64_t, const uint64_t*, const uint32_t*, + uint32_t, const uint64_t*, const uint32_t*, uint32_t) { + if (phRes) *phRes = -1; return false; + } + virtual bool LoadItemDefinitions() { + push_callback(state().user.load(), /*kSteamInventoryDefinitionUpdate*/ 4707, + nullptr, 0); + return true; + } + virtual bool GetItemDefinitionIDs(int32_t* defs, uint32_t* pcb) { + const auto app = pushed().app_id.load(); + auto guard = std::lock_guard{state_mutex()}; + auto it = pushed().inventory_item_defs.find(app); + if (it == pushed().inventory_item_defs.end()) { + if (pcb) *pcb = 0; + return true; + } + const auto& table = it->second; + if (!defs) { + if (pcb) *pcb = static_cast(table.size()); + return true; + } + const uint32_t cap = pcb ? *pcb : 0; + uint32_t n = 0; + for (const auto& kv : table) { + if (n >= cap) break; + defs[n++] = kv.first; + } + if (pcb) *pcb = n; + return true; + } + virtual bool GetItemDefinitionProperty(int32_t iDef, const char* propName, + char* buf, uint32_t* cb) { + const auto app = pushed().app_id.load(); + auto guard = std::lock_guard{state_mutex()}; + auto ait = pushed().inventory_item_defs.find(app); + if (ait == pushed().inventory_item_defs.end()) { + if (buf && cb && *cb > 0) buf[0] = '\0'; + if (cb) *cb = 0; + return false; + } + auto dit = ait->second.find(iDef); + if (dit == ait->second.end()) { + if (buf && cb && *cb > 0) buf[0] = '\0'; + if (cb) *cb = 0; + return false; + } + std::string value; + if (!propName || propName[0] == '\0') { + for (const auto& kv : dit->second) { + if (!value.empty()) value.push_back(','); + value.append(kv.first); + } + } else { + auto pit = dit->second.find(propName); + if (pit == dit->second.end()) { + if (buf && cb && *cb > 0) buf[0] = '\0'; + if (cb) *cb = 0; + return false; + } + value = pit->second; + } + const uint32_t needed = static_cast(value.size()) + 1; // include NUL + const uint32_t cap = cb ? *cb : 0; + if (buf && cap > 0) { + const uint32_t copy = (needed <= cap ? needed : cap) - 1; + std::memcpy(buf, value.data(), copy); + buf[copy] = '\0'; + } + if (cb) *cb = needed; + return true; + } + virtual uint64_t RequestEligiblePromoItemDefinitionsIDs(uint64_t sid) { + uint64_t h = alloc_api_call_handle(); + lsc_cb::SteamInventoryEligiblePromoItemDefIDs cb{}; + cb.m_result = 2; // k_EResultFail + cb.m_steamID = sid; + cb.m_numEligiblePromoItemDefs = 0; + cb.m_bCachedData = 0; + push_call_result(h, lsc_cb::kSteamInventoryEligiblePromoItemDefIDs, + &cb, sizeof(cb), /*io_failure=*/false); + return h; + } + virtual bool GetEligiblePromoItemDefinitionIDs(uint64_t, int32_t*, uint32_t* pcb) { if (pcb) *pcb = 0; return false; } + virtual uint64_t StartPurchase(const int32_t* /*defs*/, const uint32_t* /*qtys*/, uint32_t /*n*/) { + uint64_t h = alloc_api_call_handle(); + lsc_cb::SteamInventoryStartPurchaseResult cb{}; + cb.m_result = 2; + cb.m_ulOrderID = 0; + cb.m_ulTransID = 0; + push_call_result(h, lsc_cb::kSteamInventoryStartPurchaseResult, + &cb, sizeof(cb), /*io_failure=*/false); + return h; + } + virtual uint64_t RequestPrices() { + uint64_t h = alloc_api_call_handle(); + lsc_cb::SteamInventoryRequestPricesResult cb{}; + cb.m_result = 2; + cb.m_rgchCurrency[0] = 'U'; + cb.m_rgchCurrency[1] = 'S'; + cb.m_rgchCurrency[2] = 'D'; + cb.m_rgchCurrency[3] = '\0'; + push_call_result(h, lsc_cb::kSteamInventoryRequestPricesResult, + &cb, sizeof(cb), /*io_failure=*/false); + return h; + } + virtual uint32_t GetNumItemsWithPrices() { return 0; } + virtual bool GetItemsWithPrices(int32_t*, uint64_t*, uint64_t*, uint32_t) { return false; } + virtual bool GetItemPrice(int32_t, uint64_t* p, uint64_t* bp) { + if (p) *p = 0; if (bp) *bp = 0; return false; + } + virtual uint64_t StartUpdateProperties() { return 0; } + virtual bool RemoveProperty(uint64_t, uint64_t, const char*) { return false; } + virtual bool SetProperty_String(uint64_t, uint64_t, const char*, const char*) { return false; } + virtual bool SetProperty_Bool (uint64_t, uint64_t, const char*, bool) { return false; } + virtual bool SetProperty_Int64 (uint64_t, uint64_t, const char*, int64_t) { return false; } + virtual bool SetProperty_Float (uint64_t, uint64_t, const char*, float) { return false; } + virtual bool SubmitUpdateProperties(uint64_t, int* phRes) { if (phRes) *phRes = -1; return false; } + virtual bool InspectItem(int* phRes, const char*) { if (phRes) *phRes = -1; return false; } +}; + +class ISteamScreenshotsStub { +public: + virtual uint32_t WriteScreenshot(const void*, uint32_t, int, int) { return 0; } + virtual uint32_t AddScreenshotToLibrary(const char*, const char*, int, int) { return 0; } + virtual void TriggerScreenshot() {} + virtual void HookScreenshots(bool hooked) { hooked_.store(hooked); } + virtual bool SetLocation(uint32_t, const char*) { return false; } + virtual bool TagUser(uint32_t, uint64_t) { return false; } + virtual bool TagPublishedFile(uint32_t, uint64_t) { return false; } + virtual bool IsScreenshotsHooked() { return hooked_.load(); } + virtual uint32_t AddVRScreenshotToLibrary(int, const char*, const char*) { return 0; } +private: + std::atomic hooked_{false}; +}; + +class ISteamMusicStub { +public: + virtual bool BIsEnabled() { return false; } + virtual bool BIsPlaying() { return false; } + virtual int GetPlaybackStatus() { return 0; } + virtual void Play() {} + virtual void Pause() {} + virtual void PlayPrevious() {} + virtual void PlayNext() {} + virtual void SetVolume(float) {} + virtual float GetVolume() { return 0.0f; } +}; + +class ISteamAppListStub { +public: + virtual uint32_t GetNumInstalledApps() { + std::lock_guard lk(state_mutex()); + return static_cast(pushed().installed_apps.size()); + } + virtual uint32_t GetInstalledApps(uint32_t* pvecAppID, uint32_t cMax) { + std::lock_guard lk(state_mutex()); + const auto& set = pushed().installed_apps; + uint32_t total = static_cast(set.size()); + uint32_t copy = std::min(total, cMax); + if (pvecAppID && copy > 0) { + uint32_t i = 0; + for (uint32_t id : set) { + if (i >= copy) break; + pvecAppID[i++] = id; + } + } + return copy; + } + virtual int GetAppName(uint32_t appId, char* pName, int cMaxName) { + if (!pName || cMaxName <= 0) return 0; + std::lock_guard lk(state_mutex()); + auto it = pushed().app_names.find(appId); + if (it == pushed().app_names.end()) { pName[0] = '\0'; return 0; } + const std::string& n = it->second; + int copy = std::min(static_cast(n.size()), cMaxName - 1); + if (copy > 0) std::memcpy(pName, n.data(), copy); + pName[copy] = '\0'; + return copy; + } + virtual int GetAppInstallDir(uint32_t appId, char* pDir, int cMaxDir) { + if (!pDir || cMaxDir <= 0) return 0; + std::lock_guard lk(state_mutex()); + auto it = pushed().app_install_dirs.find(appId); + if (it == pushed().app_install_dirs.end()) { pDir[0] = '\0'; return 0; } + const std::string& d = it->second; + int copy = std::min(static_cast(d.size()), cMaxDir - 1); + if (copy > 0) std::memcpy(pDir, d.data(), copy); + pDir[copy] = '\0'; + return copy; + } + virtual int GetAppBuildId(uint32_t) { return 0; } +}; + +class ISteamVideoStub { +public: + virtual uint64_t GetVideoURL_DEPRECATED(uint32_t) { return 0; } + virtual bool IsBroadcasting(int* pnNumViewers) { + if (pnNumViewers) *pnNumViewers = 0; + return false; + } + virtual uint64_t GetOPFSettings(uint32_t) { return 0; } + virtual bool GetOPFStringForApp(uint32_t, char* buf, int32_t* pnBufSize) { + if (buf && pnBufSize && *pnBufSize > 0) buf[0] = '\0'; + if (pnBufSize) *pnBufSize = 0; + return false; + } +}; + +class ISteamParentalSettingsStub { +public: + virtual bool BIsParentalLockEnabled() { return false; } + virtual bool BIsParentalLockLocked() { return false; } + virtual bool BIsAppBlocked(uint32_t) { return false; } + virtual bool BIsAppInBlockList(uint32_t) { return false; } + virtual bool BIsFeatureBlocked(int) { return false; } + virtual bool BIsFeatureInBlockList(int) { return false; } +}; + +class ISteamMatchmakingServersStub { +public: + static void* fake_handle() { + return reinterpret_cast(uintptr_t{1}); + } + virtual void* RequestInternetServerList(uint32_t app, void**, uint32_t n, void*) { + __android_log_print(ANDROID_LOG_INFO, "WnLibSteamClient", + "ISteamMatchmakingServers.RequestInternetServerList app=%u nFilters=%u", + app, n); + return fake_handle(); + } + virtual void* RequestLANServerList(uint32_t app, void*) { + __android_log_print(ANDROID_LOG_INFO, "WnLibSteamClient", + "ISteamMatchmakingServers.RequestLANServerList app=%u", app); + return fake_handle(); + } + virtual void* RequestFriendsServerList(uint32_t app, void**, uint32_t, void*) { + __android_log_print(ANDROID_LOG_INFO, "WnLibSteamClient", + "ISteamMatchmakingServers.RequestFriendsServerList app=%u", app); + return fake_handle(); + } + virtual void* RequestFavoritesServerList(uint32_t app, void**, uint32_t, void*) { + __android_log_print(ANDROID_LOG_INFO, "WnLibSteamClient", + "ISteamMatchmakingServers.RequestFavoritesServerList app=%u", app); + return fake_handle(); + } + virtual void* RequestHistoryServerList(uint32_t app, void**, uint32_t, void*) { + __android_log_print(ANDROID_LOG_INFO, "WnLibSteamClient", + "ISteamMatchmakingServers.RequestHistoryServerList app=%u", app); + return fake_handle(); + } + virtual void* RequestSpectatorServerList(uint32_t app, void**, uint32_t, void*) { + __android_log_print(ANDROID_LOG_INFO, "WnLibSteamClient", + "ISteamMatchmakingServers.RequestSpectatorServerList app=%u", app); + return fake_handle(); + } + virtual void ReleaseRequest(void*) {} + virtual void* GetServerDetails(void*, int) { return nullptr; } + virtual void CancelQuery(void*) {} + virtual void RefreshQuery(void*) {} + virtual bool IsRefreshing(void*) { return false; } + virtual int GetServerCount(void*) { return 0; } + virtual void RefreshServer(void*, int) {} + virtual int PingServer(uint32_t, uint16_t, void*) { return -1; /*HSERVERQUERY_INVALID*/ } + virtual int PlayerDetails(uint32_t, uint16_t, void*) { return -1; } + virtual int ServerRules(uint32_t, uint16_t, void*) { return -1; } + virtual void CancelServerQuery(int) {} +}; + +struct PendingLobbyFilters { + struct Entry { + std::string key; + std::string value; + int32_t comparison = 0; + int32_t filter_type = 0; + }; + std::vector entries; + int32_t num_results = 50; + int32_t distance = 1; // k_ELobbyDistanceFilterDefault +}; + +static thread_local PendingLobbyFilters tls_lobby_filters; + +class ISteamMatchmakingStub { +public: + virtual int GetFavoriteGameCount() { return 0; } + virtual bool GetFavoriteGame(int, uint32_t*, uint32_t*, uint16_t*, + uint16_t*, uint32_t*, uint32_t*) { return false; } + virtual int AddFavoriteGame(uint32_t, uint32_t, uint16_t, + uint16_t, uint32_t, uint32_t) { return -1; } + virtual bool RemoveFavoriteGame(uint32_t, uint32_t, uint16_t, + uint16_t, uint32_t) { return false; } + virtual uint64_t RequestLobbyList() { + uint64_t h = alloc_api_call_handle(); + PendingLobbyFilters f = std::move(tls_lobby_filters); + tls_lobby_filters = PendingLobbyFilters{}; + __android_log_print(ANDROID_LOG_INFO, "WnLibSteamClient", + "ISteamMatchmaking.RequestLobbyList hCall=0x%llx app=%u filters=%zu", + (unsigned long long)h, pushed().app_id.load(), + f.entries.size()); + + std::vector keys_storage, values_storage; + std::vector keys, values; + std::vector comparisons, types; + keys_storage.reserve(f.entries.size()); + values_storage.reserve(f.entries.size()); + keys.reserve(f.entries.size()); + values.reserve(f.entries.size()); + comparisons.reserve(f.entries.size()); + types.reserve(f.entries.size()); + for (auto& e : f.entries) { + keys_storage.push_back(std::move(e.key)); + values_storage.push_back(std::move(e.value)); + keys.push_back(keys_storage.back().c_str()); + values.push_back(values_storage.back().c_str()); + comparisons.push_back(e.comparison); + types.push_back(e.filter_type); + } + + const uint32_t app = pushed().app_id.load(); + bool dispatched = wn_cm_lobby_get_list( + h, + app, + f.num_results, + keys.empty() ? nullptr : keys.data(), + values.empty() ? nullptr : values.data(), + comparisons.empty() ? nullptr : comparisons.data(), + types.empty() ? nullptr : types.data(), + keys.size(), + [](uint64_t hCall, int32_t eresult, + const WnCmLobbyEntry* lobbies, size_t count) { + std::vector sids; + sids.reserve(count); + if (lobbies && eresult >= 0) { + auto guard = std::lock_guard{state_mutex()}; + for (size_t i = 0; i < count; ++i) { + sids.push_back(lobbies[i].steam_id); + auto& L = pushed().active_lobbies[lobbies[i].steam_id]; + L.max_members = lobbies[i].max_members; + } + pushed().lobby_match_list = sids; + } + lsc_cb::LobbyMatchList cb{}; + cb.m_nLobbiesMatching = static_cast(sids.size()); + push_call_result(hCall, lsc_cb::kLobbyMatchList, + &cb, sizeof(cb), + /*io_failure=*/(eresult < 0)); + }); + if (!dispatched) { + lsc_cb::LobbyMatchList cb{}; + cb.m_nLobbiesMatching = 0; + push_call_result(h, lsc_cb::kLobbyMatchList, + &cb, sizeof(cb), /*io_failure=*/true); + } + return h; + } + virtual void AddRequestLobbyListStringFilter(const char* k, const char* v, int cmp) { + if (!k || !v) return; + tls_lobby_filters.entries.push_back({k, v, cmp, /*String*/ 0}); + } + virtual void AddRequestLobbyListNumericalFilter(const char* k, int v, int cmp) { + if (!k) return; + tls_lobby_filters.entries.push_back({k, std::to_string(v), cmp, /*Numerical*/ 1}); + } + virtual void AddRequestLobbyListNearValueFilter(const char* k, int v) { + if (!k) return; + tls_lobby_filters.entries.push_back({k, std::to_string(v), /*cmp*/ 0, /*NearValue*/ 3}); + } + virtual void AddRequestLobbyListFilterSlotsAvailable(int slots) { + tls_lobby_filters.entries.push_back({"", std::to_string(slots), 0, /*SlotsAvail*/ 2}); + } + virtual void AddRequestLobbyListDistanceFilter(int eDist) { + tls_lobby_filters.distance = eDist; + tls_lobby_filters.entries.push_back({"", std::to_string(eDist), 0, /*Distance*/ 4}); + } + virtual void AddRequestLobbyListResultCountFilter(int n) { + if (n > 0) tls_lobby_filters.num_results = n; + } + virtual void AddRequestLobbyListCompatibleMembersFilter(uint64_t /*sid*/) {} + virtual uint64_t GetLobbyByIndex(int idx) { + if (idx < 0) return 0; + auto guard = std::lock_guard{state_mutex()}; + const auto& v = pushed().lobby_match_list; + if (static_cast(idx) >= v.size()) return 0; + return v[idx]; + } + virtual uint64_t CreateLobby(int eLobbyType, int maxMembers) { + const uint64_t h = alloc_api_call_handle(); + __android_log_print(ANDROID_LOG_INFO, "WnLibSteamClient", + "ISteamMatchmaking.CreateLobby hCall=0x%llx type=%d maxMembers=%d", + (unsigned long long)h, eLobbyType, maxMembers); + bool dispatched = wn_cm_lobby_create( + h, pushed().app_id.load(), + static_cast(eLobbyType), + static_cast(maxMembers > 0 ? maxMembers : 4), + [](uint64_t hCall, int32_t eresult, uint64_t lobby_sid) { + lsc_cb::LobbyCreated cb{}; + cb.m_eResult = (eresult > 0) ? eresult : 2; // synthetic fail → Fail + cb.m_ulSteamIDLobby = lobby_sid; + push_call_result(hCall, lsc_cb::kLobbyCreated, + &cb, sizeof(cb), + /*io_failure=*/(eresult < 0)); + if (cb.m_eResult == 1 && lobby_sid != 0) { + lsc_cb::LobbyEnter le{}; + le.m_ulSteamIDLobby = lobby_sid; + le.m_rgfChatPermissions = 0; + le.m_bLocked = 0; + le.m_EChatRoomEnterResponse = 1; // Success + push_callback(state().user.load(), + lsc_cb::kLobbyEnter, &le, sizeof(le)); + } + }); + if (!dispatched) { + lsc_cb::LobbyCreated cb{}; + cb.m_eResult = 2; // k_EResultFail + cb.m_ulSteamIDLobby = 0; + push_call_result(h, lsc_cb::kLobbyCreated, + &cb, sizeof(cb), /*io_failure=*/true); + } + return h; + } + virtual uint64_t JoinLobby(uint64_t lobbySid) { + const uint64_t h = alloc_api_call_handle(); + __android_log_print(ANDROID_LOG_INFO, "WnLibSteamClient", + "ISteamMatchmaking.JoinLobby hCall=0x%llx lobby=0x%llx", + (unsigned long long)h, (unsigned long long)lobbySid); + bool dispatched = wn_cm_lobby_join( + h, pushed().app_id.load(), lobbySid, + [](uint64_t hCall, int32_t chat_resp, uint64_t lobby_sid) { + lsc_cb::LobbyEnter cb{}; + cb.m_ulSteamIDLobby = lobby_sid; + cb.m_rgfChatPermissions = 0; + cb.m_bLocked = 0; + cb.m_EChatRoomEnterResponse = (chat_resp > 0) ? chat_resp : 2; + push_call_result(hCall, lsc_cb::kLobbyEnter, + &cb, sizeof(cb), + /*io_failure=*/(chat_resp < 0)); + }); + if (!dispatched) { + lsc_cb::LobbyEnter cb{}; + cb.m_ulSteamIDLobby = lobbySid; + cb.m_EChatRoomEnterResponse = 2; // Error + push_call_result(h, lsc_cb::kLobbyEnter, + &cb, sizeof(cb), /*io_failure=*/true); + } + return h; + } + virtual void LeaveLobby(uint64_t sid) { + if (sid == 0) return; + __android_log_print(ANDROID_LOG_INFO, "WnLibSteamClient", + "ISteamMatchmaking.LeaveLobby lobby=0x%llx", + (unsigned long long)sid); + wn_cm_lobby_leave(pushed().app_id.load(), sid); + auto guard = std::lock_guard{state_mutex()}; + pushed().active_lobbies.erase(sid); + } + virtual bool InviteUserToLobby(uint64_t sid, uint64_t invitee) { + if (sid == 0 || invitee == 0) return false; + { + auto guard = std::lock_guard{state_mutex()}; + if (pushed().active_lobbies.find(sid) + == pushed().active_lobbies.end()) { + return false; + } + } + return wn_cm_lobby_invite_user(pushed().app_id.load(), sid, invitee); + } + virtual int GetNumLobbyMembers(uint64_t sid) { + auto guard = std::lock_guard{state_mutex()}; + auto it = pushed().active_lobbies.find(sid); + if (it == pushed().active_lobbies.end()) return 0; + return static_cast(it->second.members.size()); + } + virtual uint64_t GetLobbyMemberByIndex(uint64_t sid, int idx) { + if (idx < 0) return 0; + auto guard = std::lock_guard{state_mutex()}; + auto it = pushed().active_lobbies.find(sid); + if (it == pushed().active_lobbies.end()) return 0; + int n = 0; + for (const auto& kv : it->second.members) { + if (n++ == idx) return kv.first; + } + return 0; + } + virtual const char* GetLobbyData(uint64_t sid, const char* key) { + static thread_local std::string tls; + if (!key) { tls.clear(); return tls.c_str(); } + auto guard = std::lock_guard{state_mutex()}; + auto it = pushed().active_lobbies.find(sid); + if (it == pushed().active_lobbies.end()) { tls.clear(); return tls.c_str(); } + auto kt = it->second.data.find(key); + tls = (kt == it->second.data.end()) ? std::string{} : kt->second; + return tls.c_str(); + } + virtual bool SetLobbyData(uint64_t sid, const char* key, const char* val) { + if (sid == 0 || !key) return false; + std::string blob; + int32_t max_members = 0; + int32_t lobby_type = 0; + int32_t lobby_flags = 0; + { + auto guard = std::lock_guard{state_mutex()}; + auto& L = pushed().active_lobbies[sid]; + L.data[key] = val ? val : ""; + max_members = L.max_members; + lobby_type = L.lobby_type; + lobby_flags = L.lobby_flags; + for (const auto& kv : L.data) { + blob.append(kv.first); + blob.push_back('\0'); + blob.append(kv.second); + blob.push_back('\0'); + } + blob.push_back('\0'); // double-null terminator + } + const uint64_t h = alloc_api_call_handle(); + wn_cm_lobby_set_data(h, pushed().app_id.load(), sid, + /*steam_id_member=*/0, + reinterpret_cast(blob.data()), + blob.size(), + max_members, lobby_type, lobby_flags, + [](uint64_t /*hCall*/, int32_t /*eresult*/) { + }); + return true; + } + virtual int GetLobbyDataCount(uint64_t sid) { + auto guard = std::lock_guard{state_mutex()}; + auto it = pushed().active_lobbies.find(sid); + if (it == pushed().active_lobbies.end()) return 0; + return static_cast(it->second.data.size()); + } + virtual bool GetLobbyDataByIndex(uint64_t sid, int idx, char* key, int kn, + char* val, int vn) { + if (key && kn > 0) key[0] = '\0'; + if (val && vn > 0) val[0] = '\0'; + if (idx < 0) return false; + auto guard = std::lock_guard{state_mutex()}; + auto it = pushed().active_lobbies.find(sid); + if (it == pushed().active_lobbies.end()) return false; + int n = 0; + for (const auto& kv : it->second.data) { + if (n++ != idx) continue; + if (key && kn > 0) { + const auto cc = (kv.first.size() < static_cast(kn - 1) + ? kv.first.size() : static_cast(kn - 1)); + std::memcpy(key, kv.first.data(), cc); + key[cc] = '\0'; + } + if (val && vn > 0) { + const auto cc = (kv.second.size() < static_cast(vn - 1) + ? kv.second.size() : static_cast(vn - 1)); + std::memcpy(val, kv.second.data(), cc); + val[cc] = '\0'; + } + return true; + } + return false; + } + virtual bool DeleteLobbyData(uint64_t sid, const char* key) { + if (sid == 0 || !key) return false; + auto guard = std::lock_guard{state_mutex()}; + auto it = pushed().active_lobbies.find(sid); + if (it == pushed().active_lobbies.end()) return false; + return it->second.data.erase(key) > 0; + } + virtual const char* GetLobbyMemberData(uint64_t sid, uint64_t member, const char* key) { + static thread_local std::string tls; + tls.clear(); + if (!key) return tls.c_str(); + auto guard = std::lock_guard{state_mutex()}; + auto it = pushed().active_lobbies.find(sid); + if (it == pushed().active_lobbies.end()) return tls.c_str(); + auto mt = it->second.members.find(member); + if (mt == it->second.members.end()) return tls.c_str(); + auto kt = mt->second.data.find(key); + if (kt == mt->second.data.end()) return tls.c_str(); + tls = kt->second; + return tls.c_str(); + } + virtual void SetLobbyMemberData(uint64_t sid, const char* key, + const char* val) { + if (sid == 0 || !key) return; + const uint64_t self = pushed().steam_id.load(); + if (self == 0) return; + std::string blob; + int32_t max_members = 0; + int32_t lobby_type = 0; + int32_t lobby_flags = 0; + { + auto guard = std::lock_guard{state_mutex()}; + auto& L = pushed().active_lobbies[sid]; + auto& M = L.members[self]; + if (val && *val) M.data[key] = val; + else M.data.erase(key); + max_members = L.max_members; + lobby_type = L.lobby_type; + lobby_flags = L.lobby_flags; + for (const auto& kv : M.data) { + blob.append(kv.first); + blob.push_back('\0'); + blob.append(kv.second); + blob.push_back('\0'); + } + blob.push_back('\0'); + } + const uint64_t h = alloc_api_call_handle(); + wn_cm_lobby_set_data(h, pushed().app_id.load(), sid, + /*steam_id_member=*/self, + reinterpret_cast(blob.data()), + blob.size(), + max_members, lobby_type, lobby_flags, + [](uint64_t /*hCall*/, int32_t /*eresult*/) { + }); + } + virtual bool SendLobbyChatMsg(uint64_t sid, const void* body, int n) { + if (sid == 0 || !body || n <= 0) return false; + return wn_cm_lobby_send_chat(pushed().app_id.load(), sid, + static_cast(body), + static_cast(n)); + } + virtual int GetLobbyChatEntry(uint64_t sid, int idx, + uint64_t* speaker_out, + void* body_out, int body_cap, + int* chat_type_out) { + if (speaker_out) *speaker_out = 0; + if (chat_type_out) *chat_type_out = 0; + if (sid == 0 || idx < 0 || !body_out || body_cap <= 0) return 0; + auto guard = std::lock_guard{state_mutex()}; + auto bt = pushed().lobby_chat_buffer.find(sid); + if (bt == pushed().lobby_chat_buffer.end()) return 0; + const auto& ring = bt->second; + if (static_cast(idx) >= ring.size()) return 0; + const auto& e = ring[static_cast(idx)]; + if (speaker_out) *speaker_out = e.sender_sid; + if (chat_type_out) *chat_type_out = e.chat_type; + const int n = static_cast( + std::min(e.body.size(), + static_cast(body_cap))); + if (n > 0) std::memcpy(body_out, e.body.data(), + static_cast(n)); + return n; + } + virtual bool RequestLobbyData(uint64_t sid) { + if (sid == 0) return false; + bool have = false; + { + auto guard = std::lock_guard{state_mutex()}; + have = pushed().active_lobbies.find(sid) != + pushed().active_lobbies.end(); + } + struct LobbyDataUpdate { + uint64_t lobby; + uint64_t member; + uint8_t success; + uint8_t _pad[7]; + }; + LobbyDataUpdate cb{}; + cb.lobby = sid; + cb.member = sid; + cb.success = have ? 1 : 0; + push_callback(state().user.load(), /*kLobbyDataUpdate*/ 505, + &cb, sizeof(cb)); + return true; + } + virtual void SetLobbyGameServer(uint64_t sid, uint32_t ip, uint16_t port, uint64_t gs) { + if (sid == 0) return; + auto guard = std::lock_guard{state_mutex()}; + auto& L = pushed().active_lobbies[sid]; + L.game_server_ip = ip; + L.game_server_port = port; + L.game_server_sid = gs; + } + virtual bool GetLobbyGameServer(uint64_t sid, uint32_t* ip, + uint16_t* port, uint64_t* sid_out) { + if (ip) *ip = 0; if (port) *port = 0; if (sid_out) *sid_out = 0; + auto guard = std::lock_guard{state_mutex()}; + auto it = pushed().active_lobbies.find(sid); + if (it == pushed().active_lobbies.end()) return false; + if (it->second.game_server_sid == 0 && it->second.game_server_ip == 0) return false; + if (ip) *ip = it->second.game_server_ip; + if (port) *port = it->second.game_server_port; + if (sid_out) *sid_out = it->second.game_server_sid; + return true; + } + virtual bool SetLobbyMemberLimit(uint64_t sid, int max_members) { + if (sid == 0 || max_members <= 0) return false; + auto guard = std::lock_guard{state_mutex()}; + auto& L = pushed().active_lobbies[sid]; + L.max_members = max_members; + return true; + } + virtual int GetLobbyMemberLimit(uint64_t sid) { + auto guard = std::lock_guard{state_mutex()}; + auto it = pushed().active_lobbies.find(sid); + if (it == pushed().active_lobbies.end()) return 0; + return it->second.max_members; + } + virtual bool SetLobbyType(uint64_t sid, int eLobbyType) { + if (sid == 0) return false; + std::string blob; + int32_t max_members = 0; + int32_t lobby_flags = 0; + { + auto guard = std::lock_guard{state_mutex()}; + auto it = pushed().active_lobbies.find(sid); + if (it == pushed().active_lobbies.end()) return false; + auto& L = it->second; + if (L.owner_sid != pushed().steam_id.load()) return false; + L.lobby_type = eLobbyType; + max_members = L.max_members; + lobby_flags = L.lobby_flags; + for (const auto& kv : L.data) { + blob.append(kv.first); blob.push_back('\0'); + blob.append(kv.second); blob.push_back('\0'); + } + blob.push_back('\0'); + } + const uint64_t h = alloc_api_call_handle(); + wn_cm_lobby_set_data(h, pushed().app_id.load(), sid, /*member=*/0, + reinterpret_cast(blob.data()), + blob.size(), + max_members, eLobbyType, lobby_flags, + [](uint64_t, int32_t) {}); + return true; + } + virtual bool SetLobbyJoinable(uint64_t sid, bool joinable) { + if (sid == 0) return false; + std::string blob; + int32_t max_members = 0; + int32_t lobby_type = 0; + int32_t new_flags = 0; + { + auto guard = std::lock_guard{state_mutex()}; + auto it = pushed().active_lobbies.find(sid); + if (it == pushed().active_lobbies.end()) return false; + auto& L = it->second; + if (L.owner_sid != pushed().steam_id.load()) return false; + L.joinable = joinable; + new_flags = joinable + ? (L.lobby_flags & ~0x1) // clear "non-joinable" bit + : (L.lobby_flags | 0x1); // set it + L.lobby_flags = new_flags; + max_members = L.max_members; + lobby_type = L.lobby_type; + for (const auto& kv : L.data) { + blob.append(kv.first); blob.push_back('\0'); + blob.append(kv.second); blob.push_back('\0'); + } + blob.push_back('\0'); + } + const uint64_t h = alloc_api_call_handle(); + wn_cm_lobby_set_data(h, pushed().app_id.load(), sid, /*member=*/0, + reinterpret_cast(blob.data()), + blob.size(), + max_members, lobby_type, new_flags, + [](uint64_t, int32_t) {}); + return true; + } + virtual uint64_t GetLobbyOwner(uint64_t sid) { + auto guard = std::lock_guard{state_mutex()}; + auto it = pushed().active_lobbies.find(sid); + if (it == pushed().active_lobbies.end()) return 0; + return it->second.owner_sid; + } + virtual bool SetLobbyOwner(uint64_t sid, uint64_t new_owner) { + if (sid == 0 || new_owner == 0) return false; + { + auto guard = std::lock_guard{state_mutex()}; + auto it = pushed().active_lobbies.find(sid); + if (it == pushed().active_lobbies.end()) return false; + if (it->second.owner_sid != pushed().steam_id.load()) return false; + it->second.owner_sid = new_owner; + } + const uint64_t h = alloc_api_call_handle(); + return wn_cm_lobby_set_owner(h, pushed().app_id.load(), sid, + new_owner, + [](uint64_t, int32_t) {}); + } + virtual bool SetLinkedLobby(uint64_t, uint64_t) { return false; } +}; + +class ISteamNetworkingStub { +public: + virtual bool SendP2PPacket(uint64_t sid, const void* /*data*/, + uint32_t n, int /*eP2PSendType*/, + int /*nChannel*/) { + if (sid == 0) return false; + auto guard = std::lock_guard{state_mutex()}; + auto& s = pushed().active_p2p_sessions[sid]; + if (!s.connection_active && !s.connecting) { + s.connecting = true; + s.using_relay = pushed().p2p_relay_allowed.load(); + } + s.bytes_queued_for_send += n; + return true; + } + virtual bool IsP2PPacketAvailable(uint32_t* pcub, int nChannel) { + auto guard = std::lock_guard{state_mutex()}; + auto& q = pushed().p2p_inbound_queue[nChannel]; + if (q.empty()) { if (pcub) *pcub = 0; return false; } + if (pcub) *pcub = static_cast(q.front().body.size()); + return true; + } + virtual bool ReadP2PPacket(void* dest, uint32_t cubDest, + uint32_t* pcub, uint64_t* sidOut, int nChannel) { + if (pcub) *pcub = 0; + if (sidOut) *sidOut = 0; + auto guard = std::lock_guard{state_mutex()}; + auto& q = pushed().p2p_inbound_queue[nChannel]; + if (q.empty()) return false; + auto& pkt = q.front(); + if (sidOut) *sidOut = pkt.sender_sid; + const auto copy = static_cast( + pkt.body.size() < cubDest ? pkt.body.size() : cubDest); + if (dest && copy > 0) std::memcpy(dest, pkt.body.data(), copy); + if (pcub) *pcub = copy; + q.pop_front(); + return true; + } + virtual bool AcceptP2PSessionWithUser(uint64_t sid) { + if (sid == 0) return false; + auto guard = std::lock_guard{state_mutex()}; + auto& s = pushed().active_p2p_sessions[sid]; + s.connection_active = true; + s.connecting = false; + s.last_session_error = 0; + return true; + } + virtual bool CloseP2PSessionWithUser(uint64_t sid) { + if (sid == 0) return false; + auto guard = std::lock_guard{state_mutex()}; + if (!pushed().active_p2p_sessions.erase(sid)) return false; + for (auto& kv : pushed().p2p_inbound_queue) { + auto& q = kv.second; + q.erase(std::remove_if(q.begin(), q.end(), + [sid](const PushedState::P2PInboundPacket& p) { + return p.sender_sid == sid; + }), + q.end()); + } + return true; + } + virtual bool CloseP2PChannelWithUser(uint64_t sid, int nChannel) { + if (sid == 0) return false; + auto guard = std::lock_guard{state_mutex()}; + auto& q = pushed().p2p_inbound_queue[nChannel]; + const auto before = q.size(); + q.erase(std::remove_if(q.begin(), q.end(), + [sid](const PushedState::P2PInboundPacket& p) { + return p.sender_sid == sid; + }), + q.end()); + return q.size() != before; + } + virtual bool GetP2PSessionState(uint64_t sid, void* pState) { + if (!pState) return false; + struct P2PSessionStateWire { + uint8_t m_bConnectionActive; + uint8_t m_bConnecting; + uint8_t m_eP2PSessionError; + uint8_t m_bUsingRelay; + int32_t m_nBytesQueuedForSend; + int32_t m_nPacketsQueuedForSend; + uint32_t m_nRemoteIP; + uint16_t m_nRemotePort; + uint16_t _pad; + }; + auto* out = reinterpret_cast(pState); + std::memset(out, 0, sizeof(P2PSessionStateWire)); + auto guard = std::lock_guard{state_mutex()}; + auto it = pushed().active_p2p_sessions.find(sid); + if (it == pushed().active_p2p_sessions.end()) return false; + const auto& s = it->second; + out->m_bConnectionActive = s.connection_active ? 1 : 0; + out->m_bConnecting = s.connecting ? 1 : 0; + out->m_eP2PSessionError = static_cast(s.last_session_error); + out->m_bUsingRelay = s.using_relay ? 1 : 0; + out->m_nBytesQueuedForSend = static_cast(s.bytes_queued_for_send); + out->m_nPacketsQueuedForSend = s.bytes_queued_for_send > 0 ? 1 : 0; + out->m_nRemoteIP = s.remote_ip; + out->m_nRemotePort = s.remote_port; + return true; + } + virtual bool AllowP2PPacketRelay(bool bAllow) { + pushed().p2p_relay_allowed.store(bAllow); + return true; + } + + virtual int CreateListenSocket(int, uint32_t, uint16_t, bool) { return -1; } + virtual int CreateP2PConnectionSocket(uint64_t, int, int, bool) { return -1; } + virtual int CreateConnectionSocket(uint32_t, uint16_t, int) { return -1; } + virtual bool DestroySocket(int, bool) { return false; } + virtual bool DestroyListenSocket(int, bool) { return false; } + virtual bool SendDataOnSocket(int, void*, uint32_t, bool) { return false; } + virtual bool IsDataAvailableOnSocket(int, uint32_t* pcb) { + if (pcb) *pcb = 0; + return false; + } + virtual bool RetrieveDataFromSocket(int, void*, uint32_t, uint32_t* pcb) { + if (pcb) *pcb = 0; + return false; + } + virtual bool IsDataAvailable(int, uint32_t* pcb, int*) { + if (pcb) *pcb = 0; + return false; + } + virtual bool RetrieveData(int, void*, uint32_t, uint32_t* pcb, int*) { + if (pcb) *pcb = 0; + return false; + } + virtual bool GetSocketInfo(int, uint64_t* sid, int* status, + uint32_t* ip, uint16_t* port, int* lsock) { + if (sid) *sid = 0; + if (status) *status = 0; + if (ip) *ip = 0; + if (port) *port = 0; + if (lsock) *lsock = -1; + return false; + } + virtual bool GetListenSocketInfo(int, uint32_t* ip, uint16_t* port) { + if (ip) *ip = 0; + if (port) *port = 0; + return false; + } + virtual int GetSocketConnectionType(int) { return 0; } + virtual int GetMaxPacketSize(int) { return 0; } +}; + +class ISteamUGCStub { +public: + virtual uint64_t CreateQueryUserUGCRequest(uint32_t, int, int, int, uint32_t, uint32_t, uint32_t) { return 0; } + virtual uint64_t CreateQueryAllUGCRequest_Page(int, int, uint32_t, uint32_t, uint32_t) { return 0; } + virtual uint64_t CreateQueryAllUGCRequest_Cursor(int, int, uint32_t, uint32_t, const char*) { return 0; } + virtual uint64_t CreateQueryUGCDetailsRequest(const uint64_t*, uint32_t) { return 0; } + virtual uint64_t SendQueryUGCRequest(uint64_t handle) { + uint64_t h = alloc_api_call_handle(); + lsc_cb::SteamUGCQueryCompleted cb{}; + cb.m_handle = handle; + cb.m_eResult = 1; // k_EResultOK — empty success + cb.m_unNumResultsReturned = 0; + cb.m_unTotalMatchingResults = 0; + cb.m_bCachedData = 0; + cb.m_rgchNextCursor[0] = '\0'; + push_call_result(h, lsc_cb::kSteamUGCQueryCompleted, + &cb, sizeof(cb), /*io_failure=*/false); + return h; + } + virtual bool GetQueryUGCResult(uint64_t, uint32_t, void*) { return false; } + virtual uint32_t GetQueryUGCNumTags(uint64_t, uint32_t) { return 0; } + virtual bool GetQueryUGCTag(uint64_t, uint32_t, uint32_t, char* v, uint32_t vn) { if (v && vn) v[0] = '\0'; return false; } + virtual bool GetQueryUGCTagDisplayName(uint64_t, uint32_t, uint32_t, char* v, uint32_t vn) { if (v && vn) v[0] = '\0'; return false; } + virtual bool GetQueryUGCPreviewURL(uint64_t, uint32_t, char* v, uint32_t vn) { if (v && vn) v[0] = '\0'; return false; } + virtual bool GetQueryUGCMetadata(uint64_t, uint32_t, char* v, uint32_t vn) { if (v && vn) v[0] = '\0'; return false; } + virtual bool GetQueryUGCChildren(uint64_t, uint32_t, uint64_t*, uint32_t) { return false; } + virtual bool GetQueryUGCStatistic(uint64_t, uint32_t, int, uint64_t* out) { if (out) *out = 0; return false; } + virtual uint32_t GetQueryUGCNumAdditionalPreviews(uint64_t, uint32_t) { return 0; } + virtual bool GetQueryUGCAdditionalPreview(uint64_t, uint32_t, uint32_t, char* url, uint32_t uns, char* orig, uint32_t os, int*) { + if (url && uns) url[0] = '\0'; + if (orig && os) orig[0] = '\0'; + return false; + } + virtual uint32_t GetQueryUGCNumKeyValueTags(uint64_t, uint32_t) { return 0; } + virtual bool GetQueryUGCKeyValueTagByIndex(uint64_t, uint32_t, uint32_t, char* k, uint32_t kn, char* v, uint32_t vn) { + if (k && kn) k[0] = '\0'; + if (v && vn) v[0] = '\0'; + return false; + } + virtual bool GetQueryUGCKeyValueTagByName(uint64_t, uint32_t, const char*, char* v, uint32_t vn) { + if (v && vn) v[0] = '\0'; + return false; + } + virtual uint32_t GetQueryUGCContentDescriptors(uint64_t, uint32_t, int*, uint32_t) { return 0; } + virtual bool ReleaseQueryUGCRequest(uint64_t) { return false; } + virtual bool AddRequiredTag(uint64_t, const char*) { return false; } + virtual bool AddRequiredTagGroup(uint64_t, const void*) { return false; } + virtual bool AddExcludedTag(uint64_t, const char*) { return false; } + virtual bool SetReturnOnlyIDs(uint64_t, bool) { return false; } + virtual bool SetReturnKeyValueTags(uint64_t, bool) { return false; } + virtual bool SetReturnLongDescription(uint64_t, bool) { return false; } + virtual bool SetReturnMetadata(uint64_t, bool) { return false; } + virtual bool SetReturnChildren(uint64_t, bool) { return false; } + virtual bool SetReturnAdditionalPreviews(uint64_t, bool) { return false; } + virtual bool SetReturnTotalOnly(uint64_t, bool) { return false; } + virtual bool SetReturnPlaytimeStats(uint64_t, uint32_t) { return false; } + virtual bool SetLanguage(uint64_t, const char*) { return false; } + virtual bool SetAllowCachedResponse(uint64_t, uint32_t) { return false; } + virtual bool SetCloudFileNameFilter(uint64_t, const char*) { return false; } + virtual bool SetMatchAnyTag(uint64_t, bool) { return false; } + virtual bool SetSearchText(uint64_t, const char*) { return false; } + virtual bool SetRankedByTrendDays(uint64_t, uint32_t) { return false; } + virtual bool SetTimeCreatedDateRange(uint64_t, uint32_t, uint32_t) { return false; } + virtual bool SetTimeUpdatedDateRange(uint64_t, uint32_t, uint32_t) { return false; } + virtual bool AddRequiredKeyValueTag(uint64_t, const char*, const char*) { return false; } + virtual uint64_t RequestUGCDetails(uint64_t /*publishedFileId*/, uint32_t /*maxAgeSeconds*/) { + uint64_t h = alloc_api_call_handle(); + lsc_cb::SteamUGCRequestUGCDetailsResultMinimal cb{}; + cb.m_eResult = 2; // k_EResultFail + push_call_result(h, lsc_cb::kSteamUGCRequestUGCDetails, + &cb, sizeof(cb), /*io_failure=*/false); + return h; + } + virtual uint64_t CreateItem(uint32_t, int) { return 0; } + virtual uint64_t StartItemUpdate(uint32_t, uint64_t) { return 0; } + virtual bool SetItemTitle(uint64_t, const char*) { return false; } + virtual bool SetItemDescription(uint64_t, const char*) { return false; } + virtual bool SetItemUpdateLanguage(uint64_t, const char*) { return false; } + virtual bool SetItemMetadata(uint64_t, const char*) { return false; } + virtual bool SetItemVisibility(uint64_t, int) { return false; } + virtual bool SetItemTags(uint64_t, const void*, bool) { return false; } + virtual bool SetItemContent(uint64_t, const char*) { return false; } + virtual bool SetItemPreview(uint64_t, const char*) { return false; } + virtual bool SetAllowLegacyUpload(uint64_t, bool) { return false; } + virtual bool RemoveAllItemKeyValueTags(uint64_t) { return false; } + virtual bool RemoveItemKeyValueTags(uint64_t, const char*) { return false; } + virtual bool AddItemKeyValueTag(uint64_t, const char*, const char*) { return false; } + virtual bool AddItemPreviewFile(uint64_t, const char*, int) { return false; } + virtual bool AddItemPreviewVideo(uint64_t, const char*) { return false; } + virtual bool UpdateItemPreviewFile(uint64_t, uint32_t, const char*) { return false; } + virtual bool UpdateItemPreviewVideo(uint64_t, uint32_t, const char*) { return false; } + virtual bool RemoveItemPreview(uint64_t, uint32_t) { return false; } + virtual bool AddContentDescriptor(uint64_t, int) { return false; } + virtual bool RemoveContentDescriptor(uint64_t, int) { return false; } + virtual uint64_t SubmitItemUpdate(uint64_t, const char*) { return 0; } + virtual int GetItemUpdateProgress(uint64_t, uint64_t* bp, uint64_t* bt) { + if (bp) *bp = 0; + if (bt) *bt = 0; + return 0; + } + virtual uint64_t SetUserItemVote(uint64_t, bool) { return 0; } + virtual uint64_t GetUserItemVote(uint64_t) { return 0; } + virtual uint64_t AddItemToFavorites(uint32_t, uint64_t) { return 0; } + virtual uint64_t RemoveItemFromFavorites(uint32_t, uint64_t) { return 0; } + virtual uint64_t SubscribeItem(uint64_t publishedFileId) { + uint64_t h = alloc_api_call_handle(); + lsc_cb::RemoteStorageSubscribePublishedFileResult cb{}; + cb.m_eResult = 2; // k_EResultFail (no UGC backend) + cb.m_nPublishedFileId = publishedFileId; + push_call_result(h, lsc_cb::kRemoteStorageSubscribePublishedFile, + &cb, sizeof(cb), /*io_failure=*/false); + return h; + } + virtual uint64_t UnsubscribeItem(uint64_t publishedFileId) { + uint64_t h = alloc_api_call_handle(); + lsc_cb::RemoteStorageUnsubscribePublishedFileResult cb{}; + cb.m_eResult = 2; // k_EResultFail + cb.m_nPublishedFileId = publishedFileId; + push_call_result(h, lsc_cb::kRemoteStorageUnsubscribePublishedFile, + &cb, sizeof(cb), /*io_failure=*/false); + return h; + } + virtual uint32_t GetNumSubscribedItems() { + const auto app = pushed().app_id.load(); + auto guard = std::lock_guard{state_mutex()}; + auto it = pushed().subscribed_workshop_items.find(app); + if (it == pushed().subscribed_workshop_items.end()) return 0; + return static_cast(it->second.size()); + } + virtual uint32_t GetSubscribedItems(uint64_t* pIds, uint32_t cMax) { + if (!pIds || cMax == 0) return 0; + const auto app = pushed().app_id.load(); + auto guard = std::lock_guard{state_mutex()}; + auto it = pushed().subscribed_workshop_items.find(app); + if (it == pushed().subscribed_workshop_items.end()) return 0; + uint32_t n = 0; + for (const auto& kv : it->second) { + if (n >= cMax) break; + pIds[n++] = kv.first; + } + return n; + } + virtual uint32_t GetItemState(uint64_t publishedFileId) { + const auto app = pushed().app_id.load(); + auto guard = std::lock_guard{state_mutex()}; + auto it = pushed().subscribed_workshop_items.find(app); + if (it == pushed().subscribed_workshop_items.end()) return 0; + auto jt = it->second.find(publishedFileId); + if (jt == it->second.end() || !jt->second.installed) return 0; + return /*k_EItemStateSubscribed*/ 1u | /*k_EItemStateInstalled*/ 4u; + } + virtual bool GetItemInstallInfo(uint64_t publishedFileId, + uint64_t* bytes, + char* folder, uint32_t fn, + uint32_t* timestamp) { + if (bytes) *bytes = 0; + if (folder && fn) folder[0] = '\0'; + if (timestamp) *timestamp = 0; + const auto app = pushed().app_id.load(); + auto guard = std::lock_guard{state_mutex()}; + auto it = pushed().subscribed_workshop_items.find(app); + if (it == pushed().subscribed_workshop_items.end()) return false; + auto jt = it->second.find(publishedFileId); + if (jt == it->second.end() || !jt->second.installed) return false; + if (bytes) *bytes = jt->second.size_bytes; + if (timestamp) *timestamp = jt->second.timestamp; + if (folder && fn) { + const auto& src = jt->second.install_dir; + const auto copy = (src.size() < fn ? src.size() : fn - 1); + std::memcpy(folder, src.data(), copy); + folder[copy] = '\0'; + } + return true; + } + virtual bool GetItemDownloadInfo(uint64_t publishedFileId, uint64_t* bd, uint64_t* bt) { + if (bd) *bd = 0; + if (bt) *bt = 0; + const auto app = pushed().app_id.load(); + auto guard = std::lock_guard{state_mutex()}; + auto it = pushed().subscribed_workshop_items.find(app); + if (it == pushed().subscribed_workshop_items.end()) return false; + auto jt = it->second.find(publishedFileId); + if (jt == it->second.end() || !jt->second.installed) return false; + if (bd) *bd = jt->second.size_bytes; + if (bt) *bt = jt->second.size_bytes; + return true; + } + virtual bool DownloadItem(uint64_t publishedFileId, bool /*bHighPriority*/) { + const auto app = pushed().app_id.load(); + bool installed = false; + { + auto guard = std::lock_guard{state_mutex()}; + auto it = pushed().subscribed_workshop_items.find(app); + if (it != pushed().subscribed_workshop_items.end()) { + auto jt = it->second.find(publishedFileId); + if (jt != it->second.end() && jt->second.installed) installed = true; + } + } + if (!installed) return false; + struct DownloadItemResult { uint32_t app_id; uint64_t pfid; int32_t eResult; }; + struct ItemInstalled { uint32_t app_id; uint64_t pfid; }; + DownloadItemResult dr{ app, publishedFileId, /*k_EResultOK*/ 1 }; + ItemInstalled ii{ app, publishedFileId }; + push_callback(state().user.load(), 3406, &dr, sizeof(dr)); + push_callback(state().user.load(), 3414, &ii, sizeof(ii)); + return true; + } + virtual bool BInitWorkshopForGameServer(uint32_t, const char*) { return false; } + virtual void SuspendDownloads(bool) {} + virtual uint64_t StartPlaytimeTracking(uint64_t*, uint32_t) { return 0; } + virtual uint64_t StopPlaytimeTracking(uint64_t*, uint32_t) { return 0; } + virtual uint64_t StopPlaytimeTrackingForAllItems() { return 0; } + virtual uint64_t AddDependency(uint64_t, uint64_t) { return 0; } + virtual uint64_t RemoveDependency(uint64_t, uint64_t) { return 0; } + virtual uint64_t AddAppDependency(uint64_t, uint32_t) { return 0; } + virtual uint64_t RemoveAppDependency(uint64_t, uint32_t) { return 0; } + virtual uint64_t GetAppDependencies(uint64_t) { return 0; } + virtual uint64_t DeleteItem(uint64_t) { return 0; } + virtual bool ShowWorkshopEULA() { return false; } + virtual uint64_t GetWorkshopEULAStatus() { return 0; } + virtual uint32_t GetUserContentDescriptorPreferences(int*, uint32_t) { return 0; } +}; + +class ISteamGameServerStub { +public: + virtual void SetProduct(const char*) {} + virtual void SetGameDescription(const char*) {} + virtual void SetModDir(const char*) {} + virtual void SetDedicatedServer(bool) {} + virtual void LogOn(const char*) {} + virtual void LogOnAnonymous() {} + virtual void LogOff() {} + virtual bool BLoggedOn() { return false; } + virtual bool BSecure() { return false; } + virtual uint64_t GetSteamID() { return 0; } + virtual bool WasRestartRequested() { return false; } + virtual void SetMaxPlayerCount(int) {} + virtual void SetBotPlayerCount(int) {} + virtual void SetServerName(const char*) {} + virtual void SetMapName(const char*) {} + virtual void SetPasswordProtected(bool) {} + virtual void SetSpectatorPort(uint16_t) {} + virtual void SetSpectatorServerName(const char*) {} + virtual void ClearAllKeyValues() {} + virtual void SetKeyValue(const char*, const char*) {} + virtual void SetGameTags(const char*) {} + virtual void SetGameData(const char*) {} + virtual void SetRegion(const char*) {} + virtual void SetAdvertiseServerActive(bool) {} + virtual uint64_t GetAuthSessionTicket(void*, int, uint32_t* pcb, const void*) { + if (pcb) *pcb = 0; + return 0; // k_HAuthTicketInvalid + } + virtual int BeginAuthSession(const void*, int, uint64_t) { return 5; /*ServerNotConnectedToSteam*/ } + virtual void EndAuthSession(uint64_t) {} + virtual void CancelAuthTicket(uint64_t) {} + virtual int UserHasLicenseForApp(uint64_t, uint32_t) { return 2; /*NoAuth*/ } + virtual bool RequestUserGroupStatus(uint64_t, uint64_t) { return false; } + virtual void GetGameplayStats() {} + virtual uint64_t GetServerReputation() { return 0; } + virtual void GetPublicIP(void* out) { + if (out) std::memset(out, 0, 16); + } + virtual bool HandleIncomingPacket(const void*, int, uint32_t, uint16_t) { return false; } + virtual int GetNextOutgoingPacket(void*, int, uint32_t*, uint16_t*) { return 0; } + virtual uint64_t AssociateWithClan(uint64_t) { return 0; } + virtual uint64_t ComputeNewPlayerCompatibility(uint64_t) { return 0; } + virtual bool SendUserConnectAndAuthenticate_DEPRECATED(uint32_t, const void*, uint32_t, uint64_t*) { return false; } + virtual uint64_t CreateUnauthenticatedUserConnection() { return 0; } + virtual void SendUserDisconnect_DEPRECATED(uint64_t) {} + virtual bool BUpdateUserData(uint64_t, const char*, uint32_t) { return false; } + virtual uint64_t GetAuthTicketForWebApi(const char*) { return 0; } +}; + +class ISteamMusicRemoteStub { +public: + virtual bool RegisterSteamMusicRemote(const char*) { return false; } + virtual bool DeregisterSteamMusicRemote() { return false; } + virtual bool BIsCurrentMusicRemote() { return false; } + virtual bool BActivationSuccess(bool) { return false; } + virtual bool SetDisplayName(const char*) { return false; } + virtual bool SetPNGIcon_64x64(void*, uint32_t) { return false; } + virtual bool EnablePlayPrevious(bool) { return false; } + virtual bool EnablePlayNext(bool) { return false; } + virtual bool EnableShuffled(bool) { return false; } + virtual bool EnableLooped(bool) { return false; } + virtual bool EnableQueue(bool) { return false; } + virtual bool EnablePlaylists(bool) { return false; } + virtual bool UpdatePlaybackStatus(int) { return false; } + virtual bool UpdateShuffled(bool) { return false; } + virtual bool UpdateLooped(bool) { return false; } + virtual bool UpdateVolume(float) { return false; } + virtual bool CurrentEntryWillChange() { return false; } + virtual bool CurrentEntryIsAvailable(bool) { return false; } + virtual bool UpdateCurrentEntryText(const char*) { return false; } + virtual bool UpdateCurrentEntryElapsedSeconds(int) { return false; } + virtual bool UpdateCurrentEntryCoverArt(void*, uint32_t) { return false; } + virtual bool CurrentEntryDidChange() { return false; } + virtual bool QueueWillChange() { return false; } + virtual bool ResetQueueEntries() { return false; } + virtual bool SetQueueEntry(int, int, const char*) { return false; } + virtual bool SetCurrentQueueEntry(int) { return false; } + virtual bool QueueDidChange() { return false; } + virtual bool PlaylistWillChange() { return false; } + virtual bool ResetPlaylistEntries() { return false; } + virtual bool SetPlaylistEntry(int, int, const char*) { return false; } + virtual bool SetCurrentPlaylistEntry(int) { return false; } + virtual bool PlaylistDidChange() { return false; } +}; + +class ISteamHTMLSurfaceStub { +public: + virtual bool Init() { return false; } + virtual bool Shutdown() { return false; } + virtual uint64_t CreateBrowser(const char*, const char*) { return 0; } + virtual void RemoveBrowser(uint32_t) {} + virtual void LoadURL(uint32_t, const char*, const char*) {} + virtual void SetSize(uint32_t, uint32_t, uint32_t) {} + virtual void StopLoad(uint32_t) {} + virtual void Reload(uint32_t) {} + virtual void GoBack(uint32_t) {} + virtual void GoForward(uint32_t) {} + virtual void AddHeader(uint32_t, const char*, const char*) {} + virtual void ExecuteJavascript(uint32_t, const char*) {} + virtual void MouseUp(uint32_t, int) {} + virtual void MouseDown(uint32_t, int) {} + virtual void MouseDoubleClick(uint32_t, int) {} + virtual void MouseMove(uint32_t, int, int) {} + virtual void MouseWheel(uint32_t, int32_t) {} + virtual void KeyDown(uint32_t, uint32_t, int) {} + virtual void KeyUp(uint32_t, uint32_t, int) {} + virtual void KeyChar(uint32_t, uint32_t, int) {} + virtual void SetHorizontalScroll(uint32_t, uint32_t) {} + virtual void SetVerticalScroll(uint32_t, uint32_t) {} + virtual void SetKeyFocus(uint32_t, bool) {} + virtual void ViewSource(uint32_t) {} + virtual void CopyToClipboard(uint32_t) {} + virtual void PasteFromClipboard(uint32_t) {} + virtual void Find(uint32_t, const char*, bool, bool) {} + virtual void StopFind(uint32_t) {} + virtual void GetLinkAtPosition(uint32_t, int, int) {} + virtual void SetCookie(const char*, const char*, const char*, const char*, uint32_t, bool, bool) {} + virtual void SetPageScaleFactor(uint32_t, float, int, int) {} + virtual void SetBackgroundMode(uint32_t, bool) {} + virtual void SetDPIScalingFactor(uint32_t, float) {} + virtual void OpenDeveloperTools(uint32_t) {} + virtual void AllowStartRequest(uint32_t, bool) {} + virtual void JSDialogResponse(uint32_t, bool) {} + virtual void FileLoadDialogResponse(uint32_t, const char**) {} +}; + +class ISteamInputStub { +public: + virtual bool Init(bool) { return false; } + virtual bool Shutdown() { return false; } + virtual bool SetInputActionManifestFilePath(const char*) { return false; } + virtual void RunFrame(bool) {} + virtual bool BWaitForData(bool, uint32_t) { return false; } + virtual bool BNewDataAvailable() { return false; } + virtual int GetConnectedControllers(uint64_t*) { return 0; } + virtual void EnableDeviceCallbacks() {} + virtual void EnableActionEventCallbacks(void*) {} + virtual uint64_t GetActionSetHandle(const char*) { return 0; } + virtual void ActivateActionSet(uint64_t, uint64_t) {} + virtual uint64_t GetCurrentActionSet(uint64_t) { return 0; } + virtual void ActivateActionSetLayer(uint64_t, uint64_t) {} + virtual void DeactivateActionSetLayer(uint64_t, uint64_t) {} + virtual void DeactivateAllActionSetLayers(uint64_t) {} + virtual int GetActiveActionSetLayers(uint64_t, uint64_t*) { return 0; } + virtual uint64_t GetDigitalActionHandle(const char*) { return 0; } + virtual void GetDigitalActionData(uint64_t, uint64_t, void* outData) { + if (outData) std::memset(outData, 0, 2); + } + virtual int GetDigitalActionOrigins(uint64_t, uint64_t, uint64_t, int*) { return 0; } + virtual const char* GetStringForDigitalActionName(uint64_t) { return ""; } + virtual uint64_t GetAnalogActionHandle(const char*) { return 0; } + virtual void GetAnalogActionData(uint64_t, uint64_t, void* outData) { + if (outData) std::memset(outData, 0, 16); + } + virtual int GetAnalogActionOrigins(uint64_t, uint64_t, uint64_t, int*) { return 0; } + virtual const char* GetGlyphPNGForActionOrigin(int, int, uint32_t) { return ""; } + virtual const char* GetGlyphSVGForActionOrigin(int, uint32_t) { return ""; } + virtual const char* GetGlyphForActionOrigin_Legacy(int) { return ""; } + virtual const char* GetStringForActionOrigin(int) { return ""; } + virtual const char* GetStringForAnalogActionName(uint64_t) { return ""; } + virtual void StopAnalogActionMomentum(uint64_t, uint64_t) {} + virtual void GetMotionData(uint64_t) {} + virtual void TriggerVibration(uint64_t, uint16_t, uint16_t) {} + virtual void TriggerVibrationExtended(uint64_t, uint16_t, uint16_t, uint16_t, uint16_t) {} + virtual void TriggerSimpleHapticEvent(uint64_t, int, uint8_t, char, uint8_t, char) {} + virtual void SetLEDColor(uint64_t, uint8_t, uint8_t, uint8_t, uint32_t) {} + virtual void Legacy_TriggerHapticPulse(uint64_t, int, uint16_t) {} + virtual void Legacy_TriggerRepeatedHapticPulse(uint64_t, int, uint16_t, uint16_t, uint16_t, uint32_t) {} + virtual bool ShowBindingPanel(uint64_t) { return false; } + virtual int GetInputTypeForHandle(uint64_t) { return 0; /*ESteamInputType_Unknown*/ } + virtual uint64_t GetControllerForGamepadIndex(int) { return 0; } + virtual int GetGamepadIndexForController(uint64_t) { return -1; } + virtual const char* GetStringForXboxOrigin(int) { return ""; } + virtual const char* GetGlyphForXboxOrigin(int) { return ""; } + virtual int GetActionOriginFromXboxOrigin(uint64_t, int) { return 0; } + virtual int TranslateActionOrigin(int, int) { return 0; } + virtual bool GetDeviceBindingRevision(uint64_t, int*, int*) { return false; } + virtual uint32_t GetRemotePlaySessionID(uint64_t) { return 0; } + virtual uint32_t GetSessionInputConfigurationSettings() { return 0; } + virtual void SetDualSenseTriggerEffect(uint64_t, const void*) {} +}; + +class ISteamPartiesStub { +public: + virtual uint32_t GetNumActiveBeacons() { return 0; } + virtual uint64_t GetBeaconByIndex(uint32_t) { return 0; } + virtual bool GetBeaconDetails(uint64_t, uint64_t*, void*, char* meta, int mn) { + if (meta && mn > 0) meta[0] = '\0'; + return false; + } + virtual uint64_t JoinParty(uint64_t) { return 0; } + virtual bool GetNumAvailableBeaconLocations(uint32_t* pNum) { + if (pNum) *pNum = 0; + return false; + } + virtual bool GetAvailableBeaconLocations(void*, uint32_t) { return false; } + virtual uint64_t CreateBeacon(uint32_t, void*, int, const char*, const char*) { return 0; } + virtual void OnReservationCompleted(uint64_t, uint64_t) {} + virtual void CancelReservation(uint64_t, uint64_t) {} + virtual uint64_t ChangeNumOpenSlots(uint64_t, uint32_t) { return 0; } + virtual bool DestroyBeacon(uint64_t) { return false; } + virtual bool GetBeaconLocationData(void*, int, char* str, int sn) { + if (str && sn > 0) str[0] = '\0'; + return false; + } +}; + +class ISteamRemotePlayStub { +public: + virtual uint32_t GetSessionCount() { return 0; } + virtual uint32_t GetSessionID(int) { return 0; } + virtual uint64_t GetSessionSteamID(uint32_t) { return 0; } + virtual const char* GetSessionClientName(uint32_t) { return ""; } + virtual int GetSessionClientFormFactor(uint32_t) { return 0; } + virtual bool BGetSessionClientResolution(uint32_t, int* w, int* h) { + if (w) *w = 0; + if (h) *h = 0; + return false; + } + virtual bool BStartRemotePlayTogether(bool) { return false; } + virtual bool BSendRemotePlayTogetherInvite(uint64_t) { return false; } +}; + +class ISteamNetworkingSocketsStub { +public: + virtual uint32_t CreateListenSocketIP(const void* /*pSteamNetworkingIPAddr*/, + int, const void*) { return 0; } + virtual uint32_t ConnectByIPAddress(const void*, int, const void*) { return 0; } + virtual uint32_t CreateListenSocketP2P(int, int, const void*) { return 0; } + virtual uint32_t ConnectP2P(const void* /*identityRemote*/, int, int, const void*) { return 0; } + virtual int AcceptConnection(uint32_t) { return 3; } + virtual bool CloseConnection(uint32_t, int, const char*, bool) { return false; } + virtual bool CloseListenSocket(uint32_t) { return false; } + virtual bool SetConnectionUserData(uint32_t, int64_t) { return false; } + virtual int64_t GetConnectionUserData(uint32_t) { return -1; } + virtual void SetConnectionName(uint32_t, const char*) {} + virtual bool GetConnectionName(uint32_t, char* buf, int cap) { + if (buf && cap > 0) buf[0] = '\0'; + return false; + } + virtual int SendMessageToConnection(uint32_t, const void*, uint32_t, int, int64_t*) { return 3; } + virtual void SendMessages(int, const void* const*, int64_t*) {} + virtual int FlushMessagesOnConnection(uint32_t) { return 3; } + virtual int ReceiveMessagesOnConnection(uint32_t, void** /*ppOutMessages*/, int) { return 0; } + virtual uint32_t CreatePollGroup() { return 0; } + virtual bool DestroyPollGroup(uint32_t) { return false; } + virtual bool SetConnectionPollGroup(uint32_t, uint32_t) { return false; } + virtual int ReceiveMessagesOnPollGroup(uint32_t, void**, int) { return 0; } + virtual bool GetConnectionInfo(uint32_t, void*) { return false; } + virtual int GetConnectionRealTimeStatus(uint32_t, void*, int, void*) { return 3; } + virtual int GetDetailedConnectionStatus(uint32_t, char* buf, int cap) { + if (buf && cap > 0) buf[0] = '\0'; + return -1; + } + virtual bool GetListenSocketAddress(uint32_t, void*) { return false; } + virtual bool CreateSocketPair(uint32_t* a, uint32_t* b, bool, const void*, const void*) { + if (a) *a = 0; + if (b) *b = 0; + return false; + } + virtual int ConfigureConnectionLanes(uint32_t, int, const int*, const uint16_t*) { return 3; } + virtual bool GetIdentity(void* pIdentity) { + if (!pIdentity) return false; + const uint64_t sid = pushed().steam_id.load(); + if (sid == 0) return false; + struct NetIdentitySteamIDPrefix { + int32_t e_type; + int32_t cb_size; + uint64_t steam_id64; + }; + std::memset(pIdentity, 0, 136); + auto* out = reinterpret_cast(pIdentity); + out->e_type = 16; // k_ESteamNetworkingIdentityType_SteamID + out->cb_size = sizeof(uint64_t); + out->steam_id64 = sid; + return true; + } + virtual int InitAuthentication() { return -102; } + virtual int GetAuthenticationStatus(void*) { return -102; } + virtual bool ReceivedRelayAuthTicket(const void*, int, void*) { return false; } + virtual int FindRelayAuthTicketForServer(const void*, int, void*) { return 0; } + virtual uint32_t ConnectToHostedDedicatedServer(const void*, int, int, const void*) { return 0; } + virtual uint16_t GetHostedDedicatedServerPort() { return 0; } + virtual uint32_t GetHostedDedicatedServerPOPID() { return 0; } + virtual int GetHostedDedicatedServerAddress(void*) { return 3; } + virtual uint32_t CreateHostedDedicatedServerListenSocket(int, int, const void*) { return 0; } + virtual int GetGameCoordinatorServerLogin(void*, int*, void*) { return 3; } + virtual uint32_t ConnectP2PCustomSignaling(void*, const void*, int, int, const void*) { return 0; } + virtual bool ReceivedP2PCustomSignal(const void*, int, void*) { return false; } + virtual bool GetCertificateRequest(int*, void*, void*) { return false; } + virtual bool SetCertificate(const void*, int, void*) { return false; } + virtual void ResetIdentity(const void*) {} + virtual void RunCallbacks() {} + virtual bool BeginAsyncRequestFakeIP(int) { return false; } + virtual void GetFakeIP(int, void*) {} + virtual uint32_t CreateListenSocketP2PFakeIP(int, int, const void*) { return 0; } + virtual int GetRemoteFakeIPForConnection(uint32_t, void*) { return 3; } + virtual void* CreateFakeUDPPort(int) { return nullptr; } +}; + +class ISteamNetworkingUtilsStub { +public: + virtual void* AllocateMessage(int) { return nullptr; } + virtual void InitRelayNetworkAccess() {} + virtual int GetRelayNetworkStatus(void*) { return -102; } + virtual float GetLocalPingLocation(void*) { return -1.0f; } + virtual int EstimatePingTimeBetweenTwoLocations(const void*, const void*) { return -1; } + virtual int EstimatePingTimeFromLocalHost(const void*) { return -1; } + virtual void ConvertPingLocationToString(const void*, char* buf, int cap) { + if (buf && cap > 0) buf[0] = '\0'; + } + virtual bool ParsePingLocationString(const char*, void*) { return false; } + virtual bool CheckPingDataUpToDate(float) { return true; } + virtual int GetPingToDataCenter(uint32_t, uint32_t*) { return -1; } + virtual int GetDirectPingToPOP(uint32_t) { return -1; } + virtual int GetPOPCount() { return 0; } + virtual int GetPOPList(uint32_t*, int) { return 0; } + virtual int64_t GetLocalTimestamp() { + return std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch()).count(); + } + virtual void SetDebugOutputFunction(int, void*) {} + virtual bool IsFakeIPv4(uint32_t) { return false; } + virtual int GetIPv4FakeIPType(uint32_t) { return 0; } + virtual int GetRealIdentityForFakeIP(const void*, void*) { return 3; } + virtual bool SetGlobalConfigValueInt32(int, int) { return false; } + virtual bool SetGlobalConfigValueFloat(int, float) { return false; } + virtual bool SetGlobalConfigValueString(int, const char*) { return false; } + virtual bool SetGlobalConfigValuePtr(int, void*) { return false; } + virtual bool SetConnectionConfigValueInt32(uint32_t, int, int) { return false; } + virtual bool SetConnectionConfigValueFloat(uint32_t, int, float) { return false; } + virtual bool SetConnectionConfigValueString(uint32_t, int, const char*) { return false; } + virtual bool SetConfigValue(int, int, intptr_t, int, const void*) { return false; } + virtual bool SetConfigValueStruct(const void*, int, intptr_t) { return false; } + virtual int GetConfigValue(int, int, intptr_t, int*, void*, uint64_t*) { return -1; } + virtual const char* GetConfigValueInfo(int, int*, int*, int*) { return nullptr; } + virtual int IterateGenericEditableConfigValues(int, bool) { return 0; } + virtual void SteamNetworkingIPAddr_ToString(const void* pAddr, + char* buf, uint32_t cap, + bool with_port) { + if (!buf || cap == 0) return; + buf[0] = '\0'; + if (!pAddr) return; + const auto* p = reinterpret_cast(pAddr); + bool v4mapped = true; + for (int i = 0; i < 10; ++i) if (p[i] != 0) { v4mapped = false; break; } + if (v4mapped && (p[10] != 0xff || p[11] != 0xff)) v4mapped = false; + const uint16_t port_be = (uint16_t(p[16]) << 8) | uint16_t(p[17]); + if (v4mapped) { + if (with_port) { + std::snprintf(buf, cap, "%u.%u.%u.%u:%u", + p[12], p[13], p[14], p[15], port_be); + } else { + std::snprintf(buf, cap, "%u.%u.%u.%u", + p[12], p[13], p[14], p[15]); + } + } else { + if (with_port) { + std::snprintf(buf, cap, + "[%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x]:%u", + p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], + p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15], + port_be); + } else { + std::snprintf(buf, cap, + "%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x", + p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], + p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]); + } + } + } + virtual bool SteamNetworkingIPAddr_ParseString(void* pAddr, const char* s) { + if (!pAddr || !s || !*s) return false; + unsigned a=0,b=0,c=0,d=0,port=0; + int matched = std::sscanf(s, "%u.%u.%u.%u:%u", &a, &b, &c, &d, &port); + if (matched < 4 || a>255 || b>255 || c>255 || d>255 || port>65535) { + matched = std::sscanf(s, "%u.%u.%u.%u", &a, &b, &c, &d); + if (matched < 4 || a>255 || b>255 || c>255 || d>255) return false; + port = 0; + } + auto* out = reinterpret_cast(pAddr); + std::memset(out, 0, 18); + out[10] = 0xff; + out[11] = 0xff; + out[12] = static_cast(a); + out[13] = static_cast(b); + out[14] = static_cast(c); + out[15] = static_cast(d); + out[16] = static_cast((port >> 8) & 0xff); + out[17] = static_cast(port & 0xff); + return true; + } + virtual int SteamNetworkingIPAddr_GetFakeIPType(const void*) { return 0; } + virtual void SteamNetworkingIdentity_ToString(const void* pId, char* buf, uint32_t cap) { + if (!buf || cap == 0) return; + buf[0] = '\0'; + if (!pId) return; + struct NetIdentityHead { + int32_t e_type; + int32_t cb_size; + uint64_t steam_id64; // SteamID variant + }; + const auto* h = reinterpret_cast(pId); + if (h->e_type == 16 /*SteamID*/) { + std::snprintf(buf, cap, "steamid:%llu", + static_cast(h->steam_id64)); + } else if (h->e_type == 1 /*IPAddress*/) { + char tmp[64] = {}; + SteamNetworkingIPAddr_ToString( + reinterpret_cast(pId) + 8, tmp, sizeof(tmp), true); + std::snprintf(buf, cap, "ip:%s", tmp); + } + } + virtual bool SteamNetworkingIdentity_ParseString(void*, const char*) { return false; } +}; + +class ISteamNetworkingMessagesStub { +public: + virtual int SendMessageToUser(const void*, const void*, uint32_t, int, int) { return 3; } + virtual int ReceiveMessagesOnChannel(int, void**, int) { return 0; } + virtual bool AcceptSessionWithUser(const void*) { return false; } + virtual bool CloseSessionWithUser(const void*) { return false; } + virtual bool CloseChannelWithUser(const void*, int) { return false; } + virtual int GetSessionConnectionInfo(const void*, void*, void*) { return 0; } +}; + + +static ISteamUtilsStub g_steam_utils; +static ISteamUserStub g_steam_user; +static ISteamAppsStub g_steam_apps; +static ISteamFriendsStub g_steam_friends; +static ISteamRemoteStorageStub g_steam_remote_storage; +static ISteamUserStatsStub g_steam_user_stats; +static ISteamInventoryStub g_steam_inventory; +static ISteamScreenshotsStub g_steam_screenshots; +static ISteamMusicStub g_steam_music; +static ISteamAppListStub g_steam_app_list; +static ISteamVideoStub g_steam_video; +static ISteamParentalSettingsStub g_steam_parental; +static ISteamMatchmakingServersStub g_steam_matchmaking_servers; +static ISteamMatchmakingStub g_steam_matchmaking; +static ISteamNetworkingStub g_steam_networking; +static ISteamUGCStub g_steam_ugc; +static ISteamGameServerStub g_steam_game_server; +static ISteamMusicRemoteStub g_steam_music_remote; +static ISteamHTMLSurfaceStub g_steam_html_surface; +static ISteamInputStub g_steam_input; +static ISteamPartiesStub g_steam_parties; +static ISteamRemotePlayStub g_steam_remote_play; +static ISteamNetworkingSocketsStub g_steam_networking_sockets; +static ISteamNetworkingUtilsStub g_steam_networking_utils; +static ISteamNetworkingMessagesStub g_steam_networking_messages; + +extern "C" void* wn_get_isteam_utils() { return &g_steam_utils; } +extern "C" void* wn_get_isteam_user() { return &g_steam_user; } +extern "C" void* wn_get_isteam_apps() { return &g_steam_apps; } +extern "C" void* wn_get_isteam_friends() { return &g_steam_friends; } +extern "C" void* wn_get_isteam_remote_storage(){ return &g_steam_remote_storage; } +extern "C" void* wn_get_isteam_user_stats() { return &g_steam_user_stats; } +extern "C" void* wn_get_isteam_inventory() { return &g_steam_inventory; } +extern "C" void* wn_get_isteam_screenshots() { return &g_steam_screenshots; } +extern "C" void* wn_get_isteam_music() { return &g_steam_music; } +extern "C" void* wn_get_isteam_app_list() { return &g_steam_app_list; } +extern "C" void* wn_get_isteam_video() { return &g_steam_video; } +extern "C" void* wn_get_isteam_parental() { return &g_steam_parental; } +extern "C" void* wn_get_isteam_matchmaking_servers() { return &g_steam_matchmaking_servers; } +extern "C" void* wn_get_isteam_matchmaking() { return &g_steam_matchmaking; } +extern "C" void* wn_get_isteam_networking() { return &g_steam_networking; } +extern "C" void* wn_get_isteam_ugc() { return &g_steam_ugc; } +extern "C" void* wn_get_isteam_game_server() { return &g_steam_game_server; } +extern "C" void* wn_get_isteam_music_remote() { return &g_steam_music_remote; } +extern "C" void* wn_get_isteam_html_surface() { return &g_steam_html_surface; } +extern "C" void* wn_get_isteam_input() { return &g_steam_input; } +extern "C" void* wn_get_isteam_parties() { return &g_steam_parties; } +extern "C" void* wn_get_isteam_remote_play() { return &g_steam_remote_play; } +extern "C" void* wn_get_isteam_networking_sockets() { return &g_steam_networking_sockets; } +extern "C" void* wn_get_isteam_networking_utils() { return &g_steam_networking_utils; } +extern "C" void* wn_get_isteam_networking_messages() { return &g_steam_networking_messages; } + +} // namespace wn_libsteamclient diff --git a/app/src/main/cpp/wn-libsteamclient/src/jni_pushed_state.cpp b/app/src/main/cpp/wn-libsteamclient/src/jni_pushed_state.cpp new file mode 100644 index 000000000..bf51eb3d4 --- /dev/null +++ b/app/src/main/cpp/wn-libsteamclient/src/jni_pushed_state.cpp @@ -0,0 +1,2739 @@ + +#include "wn_libsteamclient/runtime_state.h" +#include "wn_libsteamclient/callbacks.h" +#include "wn_libsteamclient/callback_registry.h" +#include "wn_libsteamclient/tcp_services.h" +#include "wn_steam/cm_bridge.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace cb = wn_libsteamclient::callbacks; +namespace lsc = wn_libsteamclient; + +namespace { +void emit_persona_state_change(uint64_t steam_id, int32_t flags) { + if (steam_id == 0) return; + cb::PersonaStateChange payload{}; + payload.m_ulSteamID = steam_id; + payload.m_nChangeFlags = flags; + lsc::push_callback(lsc::state().user.load(), + cb::kPersonaStateChange, + &payload, sizeof(payload)); +} + +void on_persona_event(const WnCmPersonaEvent* ev) { + if (!ev || ev->sid == 0) return; + int32_t flags = 0; + { + auto& p = lsc::pushed(); + std::lock_guard lk(lsc::state_mutex()); + const bool is_self = (ev->sid == p.steam_id.load()); + if (ev->name && ev->name[0]) { + if (is_self) { + if (p.persona_name != ev->name) { + p.persona_name = ev->name; + flags |= cb::kPersonaChangeName; + } + } else { + std::string& slot = p.friend_persona_names[ev->sid]; + if (slot != ev->name) { + slot = ev->name; + flags |= cb::kPersonaChangeName; + } + } + } + if (ev->persona_state != UINT32_MAX) { + uint32_t prev; + if (is_self) { + prev = static_cast(p.persona_state.load()); + } else { + auto it = p.friend_persona_states.find(ev->sid); + prev = (it == p.friend_persona_states.end()) ? 0 : it->second; + } + if (prev != ev->persona_state) { + if (is_self) { + p.persona_state.store(static_cast(ev->persona_state)); + } else { + p.friend_persona_states[ev->sid] = ev->persona_state; + } + flags |= cb::kPersonaChangeStatus; + if (prev == 0 && ev->persona_state != 0) { + flags |= cb::kPersonaChangeComeOnline; + } + if (prev != 0 && ev->persona_state == 0) { + flags |= cb::kPersonaChangeGoneOffline; + } + } + } + { + uint32_t& slot = p.friend_game_played_app[ev->sid]; + if (slot != ev->game_played_app) { + slot = ev->game_played_app; + flags |= cb::kPersonaChangeGamePlayed; + } + } + if (ev->avatar_hash && ev->avatar_hash_len > 0) { + std::vector hash( + ev->avatar_hash, ev->avatar_hash + ev->avatar_hash_len); + auto& slot = p.friend_avatar_hashes[ev->sid]; + if (slot != hash) { + slot = std::move(hash); + flags |= cb::kPersonaChangeAvatar; + } + } + if (ev->rp_pairs && ev->rp_count > 0) { + std::vector> fresh; + fresh.reserve(ev->rp_count); + for (size_t i = 0; i < ev->rp_count; ++i) { + const auto& kv = ev->rp_pairs[i]; + fresh.emplace_back( + kv.key ? kv.key : "", + kv.value ? kv.value : ""); + } + auto& slot = p.rich_presence[ev->sid]; + if (slot != fresh) { + slot = std::move(fresh); + } + } + } + if (flags != 0) emit_persona_state_change(ev->sid, flags); + if (ev->rp_pairs && ev->rp_count > 0) { + cb::FriendRichPresenceUpdate rp{}; + rp.m_steamIDFriend = ev->sid; + rp.m_nAppID = lsc::pushed().app_id.load(); + lsc::push_callback(lsc::state().user.load(), + cb::kFriendRichPresenceUpdate, + &rp, sizeof(rp)); + } +} + +__attribute__((constructor)) +void register_persona_observer() { + wn_cm_bridge_register_persona_observer(&on_persona_event); +} + +void on_logon_state_event(bool logged_on) { + lsc::set_logged_on(logged_on); +} + +__attribute__((constructor)) +void register_logon_state_observer() { + wn_cm_bridge_register_logon_state_observer(&on_logon_state_event); +} + +void on_friends_list_event(const uint64_t* sids, size_t count) { + auto& p = lsc::pushed(); + std::lock_guard lk(lsc::state_mutex()); + p.friends.clear(); + if (sids && count > 0) { + p.friends.reserve(count); + for (size_t i = 0; i < count; ++i) { + if (sids[i] != 0) p.friends.push_back(sids[i]); + } + } + __android_log_print(ANDROID_LOG_INFO, "WnLibSteamClient", + "friends-list observer: %zu mutual friend(s) mirrored", p.friends.size()); +} + +__attribute__((constructor)) +void register_friends_list_observer() { + wn_cm_bridge_register_friends_list_observer(&on_friends_list_event); +} + +void on_license_list_event(const WnCmLicenseEntry* licenses, size_t count) { + auto& p = lsc::pushed(); + std::lock_guard lk(lsc::state_mutex()); + p.licenses.clear(); + if (licenses && count > 0) { + p.licenses.reserve(count); + for (size_t i = 0; i < count; ++i) { + const auto& src = licenses[i]; + if (src.package_id == 0) continue; + p.licenses[src.package_id] = lsc::PushedState::LicenseEntry{ + src.package_id, + src.owner_id, + src.time_created, + src.license_type, + src.flags, + src.change_number, + src.minute_limit, + src.minutes_used, + }; + } + } + __android_log_print(ANDROID_LOG_INFO, "WnLibSteamClient", + "license-list observer: %zu license(s) mirrored", p.licenses.size()); +} + +__attribute__((constructor)) +void register_license_list_observer() { + wn_cm_bridge_register_license_list_observer(&on_license_list_event); +} + +void on_account_info_event(const WnCmAccountInfo* info) { + if (!info) return; + auto& p = lsc::pushed(); + p.account_two_factor_enabled.store(info->two_factor_enabled); + p.account_phone_verified.store(info->phone_verified); + p.account_phone_identifying.store(info->phone_identifying); + p.account_phone_requires_verification.store(info->phone_requires_verification); + + if (info->persona_name && info->persona_name_len > 0) { + std::lock_guard lk(lsc::state_mutex()); + p.persona_name.assign(info->persona_name, info->persona_name_len); + } + if (info->ip_country && info->ip_country_len > 0) { + std::lock_guard lk(lsc::state_mutex()); + p.ip_country.assign(info->ip_country, info->ip_country_len); + p.ip_country_set.store(1); + } + + __android_log_print(ANDROID_LOG_INFO, "WnLibSteamClient", + "account-info observer: persona='%.*s' ip='%.*s' 2FA=%d phone_v=%d phone_id=%d phone_nv=%d", + static_cast(info->persona_name_len), info->persona_name ? info->persona_name : "", + static_cast(info->ip_country_len), info->ip_country ? info->ip_country : "", + info->two_factor_enabled, info->phone_verified, + info->phone_identifying, info->phone_requires_verification); +} + +__attribute__((constructor)) +void register_account_info_observer() { + wn_cm_bridge_register_account_info_observer(&on_account_info_event); +} + +void on_lobby_data_event(const WnCmLobbyData* data) { + if (!data) return; + { + auto& p = lsc::pushed(); + std::lock_guard lk(lsc::state_mutex()); + auto& L = p.active_lobbies[data->steam_id_lobby]; + L.app_id = data->app_id; + L.owner_sid = data->steam_id_owner; + L.max_members = data->max_members; + L.lobby_type = data->lobby_type; + L.lobby_flags = data->lobby_flags; + L.members.clear(); + for (size_t i = 0; i < data->member_count; ++i) { + const auto& m = data->members[i]; + auto& mb = L.members[m.steam_id]; + if (m.persona_name) mb.persona_name = m.persona_name; + if (m.metadata_bytes && m.metadata_len > 0) { + mb.data["__raw_metadata"] = std::string( + reinterpret_cast(m.metadata_bytes), + m.metadata_len); + } + } + } + struct LobbyDataUpdate { uint64_t lobby; uint64_t member; uint8_t success; uint8_t _pad[7]; }; + LobbyDataUpdate cb{}; + cb.lobby = data->steam_id_lobby; + cb.member = 0; + cb.success = 1; + lsc::push_callback(lsc::state().user.load(), /*kLobbyDataUpdate*/ 505, + &cb, sizeof(cb)); +} + +__attribute__((constructor)) +void register_lobby_data_observer() { + wn_cm_bridge_register_lobby_data_observer(&on_lobby_data_event); +} + +void on_lobby_chat_msg_event(uint64_t lobby_sid, uint64_t sender_sid, + const uint8_t* data, size_t n) { + if (lobby_sid == 0) return; + uint32_t chat_id = 0; + { + auto& p = lsc::pushed(); + std::lock_guard lk(lsc::state_mutex()); + auto& ring = p.lobby_chat_buffer[lobby_sid]; + if (ring.size() >= 1024) ring.erase(ring.begin()); + lsc::PushedState::LobbyChatEntry e; + e.sender_sid = sender_sid; + e.chat_type = 1; // EChatEntryType::ChatMsg + if (data && n > 0) e.body.assign(data, data + n); + ring.push_back(std::move(e)); + chat_id = static_cast(ring.size() - 1); + } + struct LobbyChatMsg { + uint64_t lobby; + uint64_t user; + uint8_t chat_type; + uint8_t _pad[3]; + uint32_t chat_id; + }; + LobbyChatMsg cb{}; + cb.lobby = lobby_sid; + cb.user = sender_sid; + cb.chat_type = 1; + cb.chat_id = chat_id; + lsc::push_callback(lsc::state().user.load(), /*kLobbyChatMsg*/ 507, + &cb, sizeof(cb)); +} + +__attribute__((constructor)) +void register_lobby_chat_msg_observer() { + wn_cm_bridge_register_lobby_chat_msg_observer(&on_lobby_chat_msg_event); +} + +void on_lobby_membership_event(int32_t joined, + uint64_t lobby_sid, + uint64_t user_sid, + const char* persona_name) { + if (lobby_sid == 0 || user_sid == 0) return; + { + auto& p = lsc::pushed(); + std::lock_guard lk(lsc::state_mutex()); + auto& L = p.active_lobbies[lobby_sid]; + if (joined) { + auto& mb = L.members[user_sid]; + if (persona_name) mb.persona_name = persona_name; + } else { + L.members.erase(user_sid); + } + } + struct LobbyChatUpdate { + uint64_t lobby; + uint64_t user_changed; + uint64_t making_change; + uint32_t state_change; + uint32_t _pad; + }; + LobbyChatUpdate cb{}; + cb.lobby = lobby_sid; + cb.user_changed = user_sid; + cb.making_change = user_sid; // best effort — we don't know admin + cb.state_change = joined ? 0x1u : 0x2u; + lsc::push_callback(lsc::state().user.load(), /*kLobbyChatUpdate*/ 506, + &cb, sizeof(cb)); +} + +__attribute__((constructor)) +void register_lobby_membership_observer() { + wn_cm_bridge_register_lobby_membership_observer(&on_lobby_membership_event); +} + +void on_server_realtime_event(uint32_t server_realtime) { + if (server_realtime == 0) return; + auto& p = lsc::pushed(); + const auto now = std::chrono::steady_clock::now(); + const auto now_ms = std::chrono::duration_cast( + now.time_since_epoch()).count(); + p.server_realtime.store(server_realtime); + p.server_realtime_anchor_local_ms.store(static_cast(now_ms)); + __android_log_print(ANDROID_LOG_INFO, "WnLibSteamClient", + "server-realtime observer: %u (anchored at local %lld ms)", + server_realtime, static_cast(now_ms)); +} + +__attribute__((constructor)) +void register_server_realtime_observer() { + wn_cm_bridge_register_server_realtime_observer(&on_server_realtime_event); +} +} // namespace + +#define WN_TAG "WnLibSteamClient" +#define WN_LOGI(...) __android_log_print(ANDROID_LOG_INFO, WN_TAG, __VA_ARGS__) + +namespace { +std::string jstr(JNIEnv* env, jstring s) { + if (!s) return {}; + const char* c = env->GetStringUTFChars(s, nullptr); + if (!c) return {}; + std::string out(c); + env->ReleaseStringUTFChars(s, c); + return out; +} +} // namespace + +extern "C" { + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeSetSteamId( + JNIEnv* /*env*/, jclass /*cls*/, jlong steamId64) { + auto& p = lsc::pushed(); + p.steam_id.store(static_cast(steamId64)); + p.account_id.store(static_cast(static_cast(steamId64) & 0xFFFFFFFFu)); + WN_LOGI("set_steam_id(%llu)", + static_cast(steamId64)); +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeSetLoggedOn( + JNIEnv* /*env*/, jclass /*cls*/, jboolean loggedOn) { + bool now = (loggedOn == JNI_TRUE); + bool prev = lsc::state().logged_on.load(); + lsc::set_logged_on(now); + WN_LOGI("set_logged_on(%d) prev=%d emitted_cb=%d", + now ? 1 : 0, prev ? 1 : 0, (now != prev) ? 1 : 0); +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeSetPersonaName( + JNIEnv* env, jclass /*cls*/, jstring jname) { + auto& p = lsc::pushed(); + std::string name = jstr(env, jname); + uint64_t self; + bool changed; + { + std::lock_guard lk(lsc::state_mutex()); + changed = (p.persona_name != name); + p.persona_name = std::move(name); + self = p.steam_id.load(); + } + if (changed) { + emit_persona_state_change(self, cb::kPersonaChangeName); + } + WN_LOGI("set_persona_name(\"%s\") changed=%d", p.persona_name.c_str(), + changed ? 1 : 0); +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeSetPersonaState( + JNIEnv* /*env*/, jclass /*cls*/, jint state) { + auto& p = lsc::pushed(); + int prev = p.persona_state.exchange(static_cast(state)); + if (prev == state) return; + int32_t flags = cb::kPersonaChangeStatus; + if (prev == 0 && state != 0) flags |= cb::kPersonaChangeComeOnline; + if (prev != 0 && state == 0) flags |= cb::kPersonaChangeGoneOffline; + emit_persona_state_change(p.steam_id.load(), flags); + wn_cm_set_persona_state(state); +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeSetAppId( + JNIEnv* /*env*/, jclass /*cls*/, jint appId) { + uint32_t app = static_cast(appId); + uint32_t prev = lsc::pushed().app_id.exchange(app); + if (prev == app) return; + wn_cm_notify_games_played(app); +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeSetIPCountry( + JNIEnv* env, jclass /*cls*/, jstring jcc) { + auto& p = lsc::pushed(); + std::string cc = jstr(env, jcc); + std::lock_guard lk(lsc::state_mutex()); + p.ip_country = std::move(cc); + p.ip_country_set.store(1); +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeSetUiLanguage( + JNIEnv* env, jclass /*cls*/, jstring jlang) { + auto& p = lsc::pushed(); + std::string lang = jstr(env, jlang); + std::lock_guard lk(lsc::state_mutex()); + p.ui_language = std::move(lang); +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeSetOwnedApps( + JNIEnv* env, jclass /*cls*/, jintArray appIds) { + auto& p = lsc::pushed(); + std::lock_guard lk(lsc::state_mutex()); + p.owned_apps.clear(); + if (!appIds) return; + jsize n = env->GetArrayLength(appIds); + if (n <= 0) return; + jint* arr = env->GetIntArrayElements(appIds, nullptr); + if (!arr) return; + p.owned_apps.reserve(n); + for (jsize i = 0; i < n; ++i) { + if (arr[i] > 0) p.owned_apps.insert(static_cast(arr[i])); + } + env->ReleaseIntArrayElements(appIds, arr, JNI_ABORT); + WN_LOGI("set_owned_apps: %zu entries", p.owned_apps.size()); +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeSetInstalledApps( + JNIEnv* env, jclass /*cls*/, jintArray appIds) { + auto& p = lsc::pushed(); + std::lock_guard lk(lsc::state_mutex()); + p.installed_apps.clear(); + if (!appIds) return; + jsize n = env->GetArrayLength(appIds); + if (n <= 0) return; + jint* arr = env->GetIntArrayElements(appIds, nullptr); + if (!arr) return; + p.installed_apps.reserve(n); + for (jsize i = 0; i < n; ++i) { + if (arr[i] > 0) p.installed_apps.insert(static_cast(arr[i])); + } + env->ReleaseIntArrayElements(appIds, arr, JNI_ABORT); + WN_LOGI("set_installed_apps: %zu entries", p.installed_apps.size()); +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeSetAppInstallDir( + JNIEnv* env, jclass /*cls*/, jint appId, jstring jdir) { + if (appId <= 0) return; + auto& p = lsc::pushed(); + std::string dir = jstr(env, jdir); + std::lock_guard lk(lsc::state_mutex()); + if (dir.empty()) { + p.app_install_dirs.erase(static_cast(appId)); + } else { + p.app_install_dirs[static_cast(appId)] = std::move(dir); + } +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeSetFriendsList( + JNIEnv* env, jclass /*cls*/, jlongArray steamIds) { + auto& p = lsc::pushed(); + std::lock_guard lk(lsc::state_mutex()); + p.friends.clear(); + if (!steamIds) return; + jsize n = env->GetArrayLength(steamIds); + if (n <= 0) return; + jlong* arr = env->GetLongArrayElements(steamIds, nullptr); + if (!arr) return; + p.friends.reserve(n); + for (jsize i = 0; i < n; ++i) { + if (arr[i] != 0) p.friends.push_back(static_cast(arr[i])); + } + env->ReleaseLongArrayElements(steamIds, arr, JNI_ABORT); + WN_LOGI("set_friends_list: %zu entries", p.friends.size()); +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeSetAppBuildId( + JNIEnv* /*env*/, jclass /*cls*/, jint appId, jint buildId) { + if (appId <= 0) return; + auto& p = lsc::pushed(); + std::lock_guard lk(lsc::state_mutex()); + if (buildId <= 0) { + p.app_build_ids.erase(static_cast(appId)); + } else { + p.app_build_ids[static_cast(appId)] = + static_cast(buildId); + } +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeSetAppNames( + JNIEnv* env, jclass /*cls*/, jintArray appIds, jobjectArray names) { + if (!appIds || !names) return; + jsize n = env->GetArrayLength(appIds); + if (n <= 0 || env->GetArrayLength(names) != n) return; + jint* ids = env->GetIntArrayElements(appIds, nullptr); + if (!ids) return; + auto& p = lsc::pushed(); + std::lock_guard lk(lsc::state_mutex()); + size_t set_count = 0, clear_count = 0; + for (jsize i = 0; i < n; ++i) { + if (ids[i] <= 0) continue; + auto js = reinterpret_cast(env->GetObjectArrayElement(names, i)); + if (!js) { + p.app_names.erase(static_cast(ids[i])); + ++clear_count; + continue; + } + const char* c = env->GetStringUTFChars(js, nullptr); + if (c && *c) { + p.app_names[static_cast(ids[i])] = c; + ++set_count; + } else { + p.app_names.erase(static_cast(ids[i])); + ++clear_count; + } + if (c) env->ReleaseStringUTFChars(js, c); + env->DeleteLocalRef(js); + } + env->ReleaseIntArrayElements(appIds, ids, JNI_ABORT); + WN_LOGI("set_app_names: set=%zu clear=%zu total=%zu", + set_count, clear_count, p.app_names.size()); +} + +extern "C" void* wn_get_isteam_apps(); +extern "C" void* wn_get_isteam_remote_storage(); +extern "C" void* wn_get_isteam_user(); +extern "C" void* wn_get_isteam_friends(); +extern "C" void* wn_get_isteam_utils(); + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticInjectAccountInfo( + JNIEnv* /*env*/, jclass /*cls*/, jboolean twoFA, jboolean phoneV, + jboolean phoneId, jboolean phoneNV) { + WnCmAccountInfo info{}; + info.two_factor_enabled = twoFA == JNI_TRUE; + info.phone_verified = phoneV == JNI_TRUE; + info.phone_identifying = phoneId == JNI_TRUE; + info.phone_requires_verification = phoneNV == JNI_TRUE; + wn_cm_bridge_inject_test_account_info(&info); +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeSetAccountFlag( + JNIEnv* /*env*/, jclass /*cls*/, jint flagKind, jboolean on) { + auto& p = lsc::pushed(); + switch (flagKind) { + case 0: p.account_phone_verified.store(on); break; + case 1: p.account_two_factor_enabled.store(on); break; + case 2: p.account_phone_identifying.store(on); break; + case 3: p.account_phone_requires_verification.store(on); break; + default: break; + } +} +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticUserBool( + JNIEnv* /*env*/, jclass /*cls*/, jint slot) { + if (slot < 26 || slot > 29) return JNI_FALSE; + void* obj = wn_get_isteam_user(); + if (!obj) return JNI_FALSE; + long* vt = *reinterpret_cast(obj); + using Fn = bool (*)(void*); + auto fn = reinterpret_cast(vt[slot]); + return fn(obj) ? JNI_TRUE : JNI_FALSE; +} +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeSetPlayerNickname( + JNIEnv* env, jclass /*cls*/, jlong sid, jstring jNickname) { + if (sid == 0) return; + auto& p = lsc::pushed(); + bool changed = false; + { + std::lock_guard lk(lsc::state_mutex()); + const uint64_t key = static_cast(sid); + if (!jNickname) { + changed = (p.player_nicknames.erase(key) > 0); + } else { + const char* c = env->GetStringUTFChars(jNickname, nullptr); + if (!c || *c == '\0') { + if (c) env->ReleaseStringUTFChars(jNickname, c); + changed = (p.player_nicknames.erase(key) > 0); + } else { + std::string newName(c); + env->ReleaseStringUTFChars(jNickname, c); + auto it = p.player_nicknames.find(key); + if (it == p.player_nicknames.end()) { + p.player_nicknames[key] = std::move(newName); + changed = true; + } else if (it->second != newName) { + it->second = std::move(newName); + changed = true; + } + } + } + } + if (changed) { + emit_persona_state_change(static_cast(sid), + cb::kPersonaChangeNickname); + } +} +JNIEXPORT jstring JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticGetPlayerNickname( + JNIEnv* env, jclass /*cls*/, jlong sid) { + void* obj = wn_get_isteam_friends(); + if (!obj) return nullptr; + long* vt = *reinterpret_cast(obj); + using Fn = const char* (*)(void*, uint64_t); + auto fn = reinterpret_cast(vt[11]); + const char* nick = fn(obj, static_cast(sid)); + return nick ? env->NewStringUTF(nick) : nullptr; +} + +JNIEXPORT jlong JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticCheckFileSignature( + JNIEnv* env, jclass /*cls*/, jstring jName) { + void* obj = wn_get_isteam_utils(); + if (!obj) return 0; + long* vt = *reinterpret_cast(obj); + using Fn = uint64_t (*)(void*, const char*); + auto fn = reinterpret_cast(vt[19]); + const char* c = jName ? env->GetStringUTFChars(jName, nullptr) : nullptr; + uint64_t h = fn(obj, c); + if (c) env->ReleaseStringUTFChars(jName, c); + return static_cast(h); +} + +JNIEXPORT jlong JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeGetPushedSteamId( + JNIEnv* /*env*/, jclass /*cls*/) { + return static_cast(lsc::pushed().steam_id.load()); +} +JNIEXPORT jstring JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeGetPushedPersonaName( + JNIEnv* env, jclass /*cls*/) { + std::string name; + { + std::lock_guard lk(lsc::state_mutex()); + name = lsc::pushed().persona_name; + } + return env->NewStringUTF(name.c_str()); +} +JNIEXPORT jstring JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeGetPushedIpCountry( + JNIEnv* env, jclass /*cls*/) { + std::string c; + if (lsc::pushed().ip_country_set.load() != 0) { + std::lock_guard lk(lsc::state_mutex()); + c = lsc::pushed().ip_country; + } + return env->NewStringUTF(c.c_str()); +} +JNIEXPORT jstring JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeGetPushedUiLanguage( + JNIEnv* env, jclass /*cls*/) { + std::string s; + { + std::lock_guard lk(lsc::state_mutex()); + s = lsc::pushed().ui_language; + } + return env->NewStringUTF(s.c_str()); +} +JNIEXPORT jint JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeGetPushedServerRealTime( + JNIEnv* /*env*/, jclass /*cls*/) { + auto anchor = lsc::pushed().server_realtime.load(); + auto anchor_local_ms = lsc::pushed().server_realtime_anchor_local_ms.load(); + if (anchor == 0 || anchor_local_ms == 0) return 0; + const auto now = std::chrono::steady_clock::now(); + const auto now_ms = std::chrono::duration_cast( + now.time_since_epoch()).count(); + auto elapsed_s = (now_ms - anchor_local_ms) / 1000; + if (elapsed_s < 0) elapsed_s = 0; + return static_cast(anchor + static_cast(elapsed_s)); +} +JNIEXPORT jint JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeGetPushedPersonaState( + JNIEnv* /*env*/, jclass /*cls*/) { + return static_cast(lsc::pushed().persona_state.load()); +} +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeGetPushedLoggedOn( + JNIEnv* /*env*/, jclass /*cls*/) { + return lsc::state().logged_on.load() ? JNI_TRUE : JNI_FALSE; +} +JNIEXPORT jint JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeGetPushedAppId( + JNIEnv* /*env*/, jclass /*cls*/) { + return static_cast(lsc::pushed().app_id.load()); +} +JNIEXPORT jint JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeGetPushedOwnedAppCount( + JNIEnv* /*env*/, jclass /*cls*/) { + std::lock_guard lk(lsc::state_mutex()); + return static_cast(lsc::pushed().owned_apps.size()); +} +JNIEXPORT jint JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeGetPushedInstalledAppCount( + JNIEnv* /*env*/, jclass /*cls*/) { + std::lock_guard lk(lsc::state_mutex()); + return static_cast(lsc::pushed().installed_apps.size()); +} +JNIEXPORT jint JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeGetPushedFriendCount( + JNIEnv* /*env*/, jclass /*cls*/) { + std::lock_guard lk(lsc::state_mutex()); + return static_cast(lsc::pushed().friends.size()); +} +JNIEXPORT jlong JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeGetPushedFirstFriend( + JNIEnv* /*env*/, jclass /*cls*/) { + std::lock_guard lk(lsc::state_mutex()); + auto& fs = lsc::pushed().friends; + return fs.empty() ? 0L : static_cast(fs.front()); +} +JNIEXPORT jint JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeGetPushedCloudFileCount( + JNIEnv* /*env*/, jclass /*cls*/) { + std::lock_guard lk(lsc::state_mutex()); + return static_cast(lsc::pushed().cloud_files.size()); +} +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeGetPushedCloudEnabledAccount( + JNIEnv* /*env*/, jclass /*cls*/) { + return lsc::pushed().cloud_enabled_account.load() ? JNI_TRUE : JNI_FALSE; +} +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeGetPushedCloudEnabledApp( + JNIEnv* /*env*/, jclass /*cls*/) { + return lsc::pushed().cloud_enabled_app.load() ? JNI_TRUE : JNI_FALSE; +} +JNIEXPORT jint JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeGetPushedEncryptedAppTicketSize( + JNIEnv* /*env*/, jclass /*cls*/, jint appId) { + if (appId <= 0) return 0; + std::lock_guard lk(lsc::state_mutex()); + auto it = lsc::pushed().encrypted_app_tickets.find( + static_cast(appId)); + if (it == lsc::pushed().encrypted_app_tickets.end()) return 0; + return static_cast(it->second.size()); +} + +JNIEXPORT jlong JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticCloudFileShare( + JNIEnv* env, jclass /*cls*/, jstring jName) { + void* obj = wn_get_isteam_remote_storage(); + if (!obj || !jName) return 0; + long* vt = *reinterpret_cast(obj); + using Fn = uint64_t (*)(void*, const char*); + auto fn = reinterpret_cast(vt[7]); + const char* name = env->GetStringUTFChars(jName, nullptr); + uint64_t h = fn(obj, name); + env->ReleaseStringUTFChars(jName, name); + return static_cast(h); +} +JNIEXPORT jlong JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticAppsGetFileDetails( + JNIEnv* env, jclass /*cls*/, jstring jName) { + void* obj = wn_get_isteam_apps(); + if (!obj || !jName) return 0; + long* vt = *reinterpret_cast(obj); + using Fn = uint64_t (*)(void*, const char*); + auto fn = reinterpret_cast(vt[25]); + const char* name = env->GetStringUTFChars(jName, nullptr); + uint64_t h = fn(obj, name); + env->ReleaseStringUTFChars(jName, name); + return static_cast(h); +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeSetSelfPlayerLevel( + JNIEnv* /*env*/, jclass /*cls*/, jint level) { + lsc::pushed().self_player_level.store(level < 0 ? 0 : level); +} +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeSetSelfGameBadge( + JNIEnv* /*env*/, jclass /*cls*/, jint appId, jint nSeries, + jboolean bFoil, jint tier) { + if (appId <= 0) return; + int32_t key = (static_cast(appId) & 0x0FFFFFFF) + | ((nSeries & 0x07) << 28) + | (bFoil ? (1 << 31) : 0); + auto& p = lsc::pushed(); + std::lock_guard lk(lsc::state_mutex()); + if (tier < 0) p.self_game_badges.erase(key); + else p.self_game_badges[key] = tier; +} +JNIEXPORT jint JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticGetPlayerSteamLevel( + JNIEnv* /*env*/, jclass /*cls*/) { + void* obj = wn_get_isteam_user(); + if (!obj) return 0; + long* vt = *reinterpret_cast(obj); + using Fn = int (*)(void*); + auto fn = reinterpret_cast(vt[24]); + return fn(obj); +} +JNIEXPORT jint JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticGetGameBadgeLevel( + JNIEnv* /*env*/, jclass /*cls*/, jint nSeries, jboolean bFoil) { + void* obj = wn_get_isteam_user(); + if (!obj) return 0; + long* vt = *reinterpret_cast(obj); + using Fn = int (*)(void*, int, bool); + auto fn = reinterpret_cast(vt[23]); + return fn(obj, nSeries, bFoil ? true : false); +} +JNIEXPORT jlong JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticRequestStoreAuthURL( + JNIEnv* env, jclass /*cls*/, jstring jRedirect) { + void* obj = wn_get_isteam_user(); + if (!obj) return 0; + long* vt = *reinterpret_cast(obj); + using Fn = uint64_t (*)(void*, const char*); + auto fn = reinterpret_cast(vt[25]); + const char* c = jRedirect ? env->GetStringUTFChars(jRedirect, nullptr) : nullptr; + uint64_t h = fn(obj, c); + if (c) env->ReleaseStringUTFChars(jRedirect, c); + return static_cast(h); +} +JNIEXPORT jlong JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticGetMarketEligibility( + JNIEnv* /*env*/, jclass /*cls*/) { + void* obj = wn_get_isteam_user(); + if (!obj) return 0; + long* vt = *reinterpret_cast(obj); + using Fn = uint64_t (*)(void*); + auto fn = reinterpret_cast(vt[30]); + return static_cast(fn(obj)); +} +JNIEXPORT jlong JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticGetDurationControl( + JNIEnv* /*env*/, jclass /*cls*/) { + void* obj = wn_get_isteam_user(); + if (!obj) return 0; + long* vt = *reinterpret_cast(obj); + using Fn = uint64_t (*)(void*); + auto fn = reinterpret_cast(vt[31]); + return static_cast(fn(obj)); +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeSetFriendSteamLevel( + JNIEnv* /*env*/, jclass /*cls*/, jlong sid, jint level) { + if (sid == 0) return; + auto& p = lsc::pushed(); + std::lock_guard lk(lsc::state_mutex()); + if (level < 0) p.friend_steam_levels.erase(static_cast(sid)); + else p.friend_steam_levels[static_cast(sid)] = level; +} +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeIsAppMarkedCorrupt( + JNIEnv* /*env*/, jclass /*cls*/, jint appId) { + if (appId <= 0) return JNI_FALSE; + auto& p = lsc::pushed(); + std::lock_guard lk(lsc::state_mutex()); + return p.apps_marked_corrupt.count(static_cast(appId)) > 0 + ? JNI_TRUE : JNI_FALSE; +} +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeClearAppCorruptFlag( + JNIEnv* /*env*/, jclass /*cls*/, jint appId) { + if (appId <= 0) return; + auto& p = lsc::pushed(); + std::lock_guard lk(lsc::state_mutex()); + p.apps_marked_corrupt.erase(static_cast(appId)); +} +JNIEXPORT jint JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticUserHasLicense( + JNIEnv* /*env*/, jclass /*cls*/, jlong sid, jint appId) { + void* obj = wn_get_isteam_user(); + if (!obj) return 2; + long* vt = *reinterpret_cast(obj); + using Fn = int (*)(void*, uint64_t, uint32_t); + auto fn = reinterpret_cast(vt[18]); + return fn(obj, static_cast(sid), static_cast(appId)); +} +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticMarkContentCorrupt( + JNIEnv* /*env*/, jclass /*cls*/, jboolean missingOnly) { + void* obj = wn_get_isteam_apps(); + if (!obj) return JNI_FALSE; + long* vt = *reinterpret_cast(obj); + using Fn = bool (*)(void*, bool); + auto fn = reinterpret_cast(vt[16]); + return fn(obj, missingOnly ? true : false) ? JNI_TRUE : JNI_FALSE; +} +JNIEXPORT jint JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticGetFriendSteamLevel( + JNIEnv* /*env*/, jclass /*cls*/, jlong sid) { + void* obj = wn_get_isteam_friends(); + if (!obj) return 0; + long* vt = *reinterpret_cast(obj); + using Fn = int (*)(void*, uint64_t); + auto fn = reinterpret_cast(vt[10]); + return fn(obj, static_cast(sid)); +} + +JNIEXPORT jlong JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticGetAuthTicketForWebApi( + JNIEnv* env, jclass /*cls*/, jstring jIdentity) { + void* obj = wn_get_isteam_user(); + if (!obj) return 0; + long* vt = *reinterpret_cast(obj); + using Fn = uint64_t (*)(void*, const char*); + auto fn = reinterpret_cast(vt[14]); + const char* c = jIdentity ? env->GetStringUTFChars(jIdentity, nullptr) : nullptr; + uint64_t h = fn(obj, c); + if (c) env->ReleaseStringUTFChars(jIdentity, c); + return static_cast(h); +} +JNIEXPORT jint JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticGetFriendRelationship( + JNIEnv* /*env*/, jclass /*cls*/, jlong sid) { + void* obj = wn_get_isteam_friends(); + if (!obj) return 0; + long* vt = *reinterpret_cast(obj); + using Fn = int (*)(void*, uint64_t); + auto fn = reinterpret_cast(vt[5]); + return fn(obj, static_cast(sid)); +} +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticHasFriend( + JNIEnv* /*env*/, jclass /*cls*/, jlong sid, jint flags) { + void* obj = wn_get_isteam_friends(); + if (!obj) return JNI_FALSE; + long* vt = *reinterpret_cast(obj); + using Fn = bool (*)(void*, uint64_t, int); + auto fn = reinterpret_cast(vt[17]); + return fn(obj, static_cast(sid), flags) ? JNI_TRUE : JNI_FALSE; +} +JNIEXPORT jstring JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticGetUserDataFolder( + JNIEnv* env, jclass /*cls*/) { + void* obj = wn_get_isteam_user(); + if (!obj) return nullptr; + long* vt = *reinterpret_cast(obj); + using Fn = bool (*)(void*, char*, int); + auto fn = reinterpret_cast(vt[6]); + char buf[512]; + buf[0] = '\0'; + if (!fn(obj, buf, sizeof(buf))) return nullptr; + return env->NewStringUTF(buf); +} +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticSetDurationControl( + JNIEnv* /*env*/, jclass /*cls*/, jint state) { + void* obj = wn_get_isteam_user(); + if (!obj) return JNI_FALSE; + long* vt = *reinterpret_cast(obj); + using Fn = bool (*)(void*, int); + auto fn = reinterpret_cast(vt[32]); + return fn(obj, state) ? JNI_TRUE : JNI_FALSE; +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeSetAppFlag( + JNIEnv* /*env*/, jclass /*cls*/, jint flagKind, jint appId, jboolean on) { + if (appId <= 0) return; + auto& p = lsc::pushed(); + std::lock_guard lk(lsc::state_mutex()); + auto& set = (flagKind == 0) ? p.app_low_violence + : p.app_vac_banned; + if (on) set.insert(static_cast(appId)); + else set.erase(static_cast(appId)); +} + +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticAppsBool( + JNIEnv* /*env*/, jclass /*cls*/, jint slot) { + void* obj = wn_get_isteam_apps(); + if (!obj || slot < 0 || slot > 3) return JNI_FALSE; + long* vt = *reinterpret_cast(obj); + using Fn = bool (*)(void*); + auto fn = reinterpret_cast(vt[slot]); + return fn(obj) ? JNI_TRUE : JNI_FALSE; +} +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticSetDlcContext( + JNIEnv* /*env*/, jclass /*cls*/, jint appId) { + void* obj = wn_get_isteam_apps(); + if (!obj) return JNI_FALSE; + long* vt = *reinterpret_cast(obj); + using Fn = bool (*)(void*, uint32_t); + auto fn = reinterpret_cast(vt[29]); + return fn(obj, static_cast(appId)) ? JNI_TRUE : JNI_FALSE; +} +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticCloudFileForget( + JNIEnv* env, jclass /*cls*/, jstring jName) { + void* obj = wn_get_isteam_remote_storage(); + if (!obj || !jName) return JNI_FALSE; + long* vt = *reinterpret_cast(obj); + using Fn = bool (*)(void*, const char*); + auto fn = reinterpret_cast(vt[5]); + const char* name = env->GetStringUTFChars(jName, nullptr); + bool ok = fn(obj, name); + env->ReleaseStringUTFChars(jName, name); + return ok ? JNI_TRUE : JNI_FALSE; +} +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticCloudFilePersisted( + JNIEnv* env, jclass /*cls*/, jstring jName) { + void* obj = wn_get_isteam_remote_storage(); + if (!obj || !jName) return JNI_FALSE; + long* vt = *reinterpret_cast(obj); + using Fn = bool (*)(void*, const char*); + auto fn = reinterpret_cast(vt[14]); + const char* name = env->GetStringUTFChars(jName, nullptr); + bool ok = fn(obj, name); + env->ReleaseStringUTFChars(jName, name); + return ok ? JNI_TRUE : JNI_FALSE; +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeSetAppCloudRemoteDir( + JNIEnv* env, jclass /*cls*/, jint appId, jstring jPath) { + if (appId <= 0) return; + auto& p = lsc::pushed(); + std::lock_guard lk(lsc::state_mutex()); + if (!jPath) { + p.app_cloud_remote_dirs.erase(static_cast(appId)); + return; + } + const char* c = env->GetStringUTFChars(jPath, nullptr); + if (!c || *c == '\0') { + if (c) env->ReleaseStringUTFChars(jPath, c); + p.app_cloud_remote_dirs.erase(static_cast(appId)); + return; + } + p.app_cloud_remote_dirs[static_cast(appId)] = std::string(c); + env->ReleaseStringUTFChars(jPath, c); +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeSetAppCurrentBeta( + JNIEnv* env, jclass /*cls*/, jint appId, jstring jBranch) { + if (appId <= 0) return; + auto& p = lsc::pushed(); + std::lock_guard lk(lsc::state_mutex()); + if (!jBranch) { + p.app_current_beta.erase(static_cast(appId)); + return; + } + const char* c = env->GetStringUTFChars(jBranch, nullptr); + if (!c || *c == '\0') { + if (c) env->ReleaseStringUTFChars(jBranch, c); + p.app_current_beta.erase(static_cast(appId)); + return; + } + p.app_current_beta[static_cast(appId)] = std::string(c); + env->ReleaseStringUTFChars(jBranch, c); +} + +extern "C" void* wn_get_isteam_apps(); + +extern "C" void* wn_get_isteam_remote_storage(); +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticCloudFileWrite( + JNIEnv* env, jclass /*cls*/, jstring jName, jbyteArray jData) { + void* obj = wn_get_isteam_remote_storage(); + if (!obj || !jName || !jData) return JNI_FALSE; + long* vt = *reinterpret_cast(obj); + using Fn = bool (*)(void*, const char*, const void*, int); + auto fn = reinterpret_cast(vt[0]); + const char* name = env->GetStringUTFChars(jName, nullptr); + jsize len = env->GetArrayLength(jData); + jbyte* buf = env->GetByteArrayElements(jData, nullptr); + bool ok = fn(obj, name, buf, static_cast(len)); + env->ReleaseByteArrayElements(jData, buf, JNI_ABORT); + env->ReleaseStringUTFChars(jName, name); + return ok ? JNI_TRUE : JNI_FALSE; +} +JNIEXPORT jbyteArray JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticCloudFileRead( + JNIEnv* env, jclass /*cls*/, jstring jName, jint maxBytes) { + if (!jName || maxBytes <= 0) return nullptr; + void* obj = wn_get_isteam_remote_storage(); + if (!obj) return nullptr; + long* vt = *reinterpret_cast(obj); + using Fn = int (*)(void*, const char*, void*, int); + auto fn = reinterpret_cast(vt[1]); + const char* name = env->GetStringUTFChars(jName, nullptr); + std::vector buf(static_cast(maxBytes)); + int n = fn(obj, name, buf.data(), maxBytes); + env->ReleaseStringUTFChars(jName, name); + if (n <= 0) return nullptr; + jbyteArray out = env->NewByteArray(n); + env->SetByteArrayRegion(out, 0, n, reinterpret_cast(buf.data())); + return out; +} +JNIEXPORT jlong JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticCloudStreamOpen( + JNIEnv* env, jclass /*cls*/, jstring jName) { + void* obj = wn_get_isteam_remote_storage(); + if (!obj || !jName) return 0; + long* vt = *reinterpret_cast(obj); + using Fn = uint64_t (*)(void*, const char*); + auto fn = reinterpret_cast(vt[9]); + const char* name = env->GetStringUTFChars(jName, nullptr); + uint64_t h = fn(obj, name); + env->ReleaseStringUTFChars(jName, name); + return static_cast(h); +} +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticCloudStreamWriteChunk( + JNIEnv* env, jclass /*cls*/, jlong hStream, jbyteArray jData) { + void* obj = wn_get_isteam_remote_storage(); + if (!obj || !jData) return JNI_FALSE; + long* vt = *reinterpret_cast(obj); + using Fn = bool (*)(void*, uint64_t, const void*, int); + auto fn = reinterpret_cast(vt[10]); + jsize len = env->GetArrayLength(jData); + jbyte* buf = env->GetByteArrayElements(jData, nullptr); + bool ok = fn(obj, static_cast(hStream), buf, static_cast(len)); + env->ReleaseByteArrayElements(jData, buf, JNI_ABORT); + return ok ? JNI_TRUE : JNI_FALSE; +} +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticCloudStreamClose( + JNIEnv* /*env*/, jclass /*cls*/, jlong hStream) { + void* obj = wn_get_isteam_remote_storage(); + if (!obj) return JNI_FALSE; + long* vt = *reinterpret_cast(obj); + using Fn = bool (*)(void*, uint64_t); + auto fn = reinterpret_cast(vt[11]); + return fn(obj, static_cast(hStream)) ? JNI_TRUE : JNI_FALSE; +} +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticCloudStreamCancel( + JNIEnv* /*env*/, jclass /*cls*/, jlong hStream) { + void* obj = wn_get_isteam_remote_storage(); + if (!obj) return JNI_FALSE; + long* vt = *reinterpret_cast(obj); + using Fn = bool (*)(void*, uint64_t); + auto fn = reinterpret_cast(vt[12]); + return fn(obj, static_cast(hStream)) ? JNI_TRUE : JNI_FALSE; +} +JNIEXPORT jlong JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticCloudFileWriteAsync( + JNIEnv* env, jclass /*cls*/, jstring jName, jbyteArray jData) { + void* obj = wn_get_isteam_remote_storage(); + if (!obj || !jName || !jData) return 0; + long* vt = *reinterpret_cast(obj); + using Fn = uint64_t (*)(void*, const char*, const void*, uint32_t); + auto fn = reinterpret_cast(vt[2]); + const char* name = env->GetStringUTFChars(jName, nullptr); + jsize len = env->GetArrayLength(jData); + jbyte* buf = env->GetByteArrayElements(jData, nullptr); + uint64_t h = fn(obj, name, buf, static_cast(len)); + env->ReleaseByteArrayElements(jData, buf, JNI_ABORT); + env->ReleaseStringUTFChars(jName, name); + return static_cast(h); +} +JNIEXPORT jlong JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticCloudFileReadAsync( + JNIEnv* env, jclass /*cls*/, jstring jName, jint nOffset, jint cubToRead) { + void* obj = wn_get_isteam_remote_storage(); + if (!obj || !jName || cubToRead <= 0) return 0; + long* vt = *reinterpret_cast(obj); + using Fn = uint64_t (*)(void*, const char*, uint32_t, uint32_t); + auto fn = reinterpret_cast(vt[3]); + const char* name = env->GetStringUTFChars(jName, nullptr); + uint64_t h = fn(obj, name, static_cast(nOffset), + static_cast(cubToRead)); + env->ReleaseStringUTFChars(jName, name); + return static_cast(h); +} +JNIEXPORT jbyteArray JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticCloudFileReadAsyncComplete( + JNIEnv* env, jclass /*cls*/, jlong hCall, jint cubToRead) { + if (hCall == 0 || cubToRead <= 0) return nullptr; + void* obj = wn_get_isteam_remote_storage(); + if (!obj) return nullptr; + long* vt = *reinterpret_cast(obj); + using Fn = bool (*)(void*, uint64_t, void*, uint32_t); + auto fn = reinterpret_cast(vt[4]); + std::vector buf(static_cast(cubToRead)); + bool ok = fn(obj, static_cast(hCall), buf.data(), + static_cast(cubToRead)); + if (!ok) return nullptr; + jbyteArray out = env->NewByteArray(cubToRead); + env->SetByteArrayRegion(out, 0, cubToRead, reinterpret_cast(buf.data())); + return out; +} +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticCloudFileDelete( + JNIEnv* env, jclass /*cls*/, jstring jName) { + void* obj = wn_get_isteam_remote_storage(); + if (!obj || !jName) return JNI_FALSE; + long* vt = *reinterpret_cast(obj); + using Fn = bool (*)(void*, const char*); + auto fn = reinterpret_cast(vt[6]); + const char* name = env->GetStringUTFChars(jName, nullptr); + bool ok = fn(obj, name); + env->ReleaseStringUTFChars(jName, name); + return ok ? JNI_TRUE : JNI_FALSE; +} + +JNIEXPORT jstring JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticGetCurrentBetaName( + JNIEnv* env, jclass /*cls*/) { + void* obj = wn_get_isteam_apps(); + if (!obj) return nullptr; + long* vt = *reinterpret_cast(obj); + using Fn = bool (*)(void*, char*, int); + auto fn = reinterpret_cast(vt[15]); + char buf[128]; + buf[0] = '\0'; + if (!fn(obj, buf, sizeof(buf))) return nullptr; + return env->NewStringUTF(buf); +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeSetAppDownloadProgress( + JNIEnv* /*env*/, jclass /*cls*/, jint appId, + jlong bytesDownloaded, jlong bytesTotal) { + if (appId <= 0) return; + auto& p = lsc::pushed(); + std::lock_guard lk(lsc::state_mutex()); + if (bytesTotal <= 0) { + p.app_dl_progress.erase(static_cast(appId)); + return; + } + auto& e = p.app_dl_progress[static_cast(appId)]; + e.bytes_downloaded = static_cast(std::max(0, bytesDownloaded)); + e.bytes_total = static_cast(bytesTotal); +} + +static uint64_t s_diag_dl_downloaded = 0; +static uint64_t s_diag_dl_total = 0; +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticGetDlcDownloadProgress( + JNIEnv* /*env*/, jclass /*cls*/, jint appId) { + void* obj = wn_get_isteam_apps(); + if (!obj) return JNI_FALSE; + long* vt = *reinterpret_cast(obj); + using Fn = bool (*)(void*, uint32_t, uint64_t*, uint64_t*); + auto fn = reinterpret_cast(vt[22]); + s_diag_dl_downloaded = 0; + s_diag_dl_total = 0; + return fn(obj, static_cast(appId), + &s_diag_dl_downloaded, &s_diag_dl_total) ? JNI_TRUE : JNI_FALSE; +} +JNIEXPORT jlong JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticGetDlcDownloadProgressBytes( + JNIEnv* /*env*/, jclass /*cls*/) { + return static_cast(s_diag_dl_downloaded); +} +JNIEXPORT jlong JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticGetDlcDownloadProgressTotal( + JNIEnv* /*env*/, jclass /*cls*/) { + return static_cast(s_diag_dl_total); +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeSetAppInstalledDepots( + JNIEnv* env, jclass /*cls*/, jint appId, jintArray depotIds) { + if (appId <= 0) return; + auto& p = lsc::pushed(); + std::lock_guard lk(lsc::state_mutex()); + if (!depotIds) { + p.app_installed_depots.erase(static_cast(appId)); + return; + } + jsize n = env->GetArrayLength(depotIds); + if (n <= 0) { + p.app_installed_depots.erase(static_cast(appId)); + return; + } + jint* arr = env->GetIntArrayElements(depotIds, nullptr); + std::vector depots; + depots.reserve(n); + for (jsize i = 0; i < n; ++i) { + if (arr[i] > 0) depots.push_back(static_cast(arr[i])); + } + env->ReleaseIntArrayElements(depotIds, arr, JNI_ABORT); + p.app_installed_depots[static_cast(appId)] = std::move(depots); + WN_LOGI("set_app_installed_depots: app=%d count=%zu", + appId, p.app_installed_depots[static_cast(appId)].size()); +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeSetAppDlcs( + JNIEnv* env, jclass /*cls*/, jint parentAppId, + jintArray dlcAppIds, jobjectArray dlcNames, jbooleanArray available) { + if (parentAppId <= 0) return; + auto& p = lsc::pushed(); + std::lock_guard lk(lsc::state_mutex()); + if (!dlcAppIds) { + p.app_dlcs.erase(static_cast(parentAppId)); + return; + } + jsize n = env->GetArrayLength(dlcAppIds); + if (n <= 0) { + p.app_dlcs.erase(static_cast(parentAppId)); + return; + } + jint* ids = env->GetIntArrayElements(dlcAppIds, nullptr); + jboolean* av = (available && env->GetArrayLength(available) == n) + ? env->GetBooleanArrayElements(available, nullptr) : nullptr; + auto read_name = [&](jsize i) -> std::string { + if (!dlcNames || env->GetArrayLength(dlcNames) <= i) return {}; + auto js = reinterpret_cast(env->GetObjectArrayElement(dlcNames, i)); + if (!js) return {}; + const char* c = env->GetStringUTFChars(js, nullptr); + std::string out = c ? c : ""; + if (c) env->ReleaseStringUTFChars(js, c); + env->DeleteLocalRef(js); + return out; + }; + std::vector entries; + entries.reserve(n); + for (jsize i = 0; i < n; ++i) { + if (ids[i] <= 0) continue; + wn_libsteamclient::PushedState::DlcEntry e; + e.app_id = static_cast(ids[i]); + e.name = read_name(i); + e.available = av ? (av[i] == JNI_TRUE) : true; + entries.push_back(std::move(e)); + } + env->ReleaseIntArrayElements(dlcAppIds, ids, JNI_ABORT); + if (av) env->ReleaseBooleanArrayElements(available, av, JNI_ABORT); + p.app_dlcs[static_cast(parentAppId)] = std::move(entries); + WN_LOGI("set_app_dlcs: parent=%d count=%zu", + parentAppId, p.app_dlcs[static_cast(parentAppId)].size()); +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeSetAppWorkshopItems( + JNIEnv* env, jclass /*cls*/, jint appId, + jlongArray publishedFileIds, jobjectArray installDirs, + jlongArray sizesBytes, jlongArray timestamps) { + if (appId <= 0) return; + auto& p = lsc::pushed(); + std::lock_guard lk(lsc::state_mutex()); + if (!publishedFileIds) { + p.subscribed_workshop_items.erase(static_cast(appId)); + return; + } + jsize n = env->GetArrayLength(publishedFileIds); + if (n <= 0) { + p.subscribed_workshop_items.erase(static_cast(appId)); + return; + } + jlong* ids = env->GetLongArrayElements(publishedFileIds, nullptr); + jlong* sizes = (sizesBytes && env->GetArrayLength(sizesBytes) == n) + ? env->GetLongArrayElements(sizesBytes, nullptr) : nullptr; + jlong* tims = (timestamps && env->GetArrayLength(timestamps) == n) + ? env->GetLongArrayElements(timestamps, nullptr) : nullptr; + auto read_str = [&](jsize i) -> std::string { + if (!installDirs || env->GetArrayLength(installDirs) <= i) return {}; + auto js = reinterpret_cast(env->GetObjectArrayElement(installDirs, i)); + if (!js) return {}; + const char* c = env->GetStringUTFChars(js, nullptr); + std::string out = c ? c : ""; + if (c) env->ReleaseStringUTFChars(js, c); + env->DeleteLocalRef(js); + return out; + }; + std::unordered_map items; + items.reserve(n); + for (jsize i = 0; i < n; ++i) { + if (ids[i] <= 0) continue; + wn_libsteamclient::PushedState::WorkshopItemInfo info; + info.install_dir = read_str(i); + info.size_bytes = sizes ? static_cast(sizes[i]) : 0u; + info.timestamp = tims ? static_cast(tims[i]) : 0u; + info.installed = true; + items.emplace(static_cast(ids[i]), std::move(info)); + } + env->ReleaseLongArrayElements(publishedFileIds, ids, JNI_ABORT); + if (sizes) env->ReleaseLongArrayElements(sizesBytes, sizes, JNI_ABORT); + if (tims) env->ReleaseLongArrayElements(timestamps, tims, JNI_ABORT); + if (items.empty()) { + p.subscribed_workshop_items.erase(static_cast(appId)); + } else { + p.subscribed_workshop_items[static_cast(appId)] = std::move(items); + } + WN_LOGI("set_app_workshop_items: app=%d count=%zu", appId, + p.subscribed_workshop_items[static_cast(appId)].size()); +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeSetInventoryItemDefs( + JNIEnv* env, jclass /*cls*/, jint appId, + jintArray defIds, jintArray propCountsPerDef, + jobjectArray propKeys, jobjectArray propVals) { + if (appId <= 0) return; + auto& p = lsc::pushed(); + std::lock_guard lk(lsc::state_mutex()); + if (!defIds) { + p.inventory_item_defs.erase(static_cast(appId)); + return; + } + jsize n = env->GetArrayLength(defIds); + if (n <= 0) { + p.inventory_item_defs.erase(static_cast(appId)); + return; + } + if (!propCountsPerDef || env->GetArrayLength(propCountsPerDef) != n) return; + jint* ids = env->GetIntArrayElements(defIds, nullptr); + jint* counts = env->GetIntArrayElements(propCountsPerDef, nullptr); + auto read_str = [&](jobjectArray arr, jsize i) -> std::string { + if (!arr || env->GetArrayLength(arr) <= i) return {}; + auto js = reinterpret_cast(env->GetObjectArrayElement(arr, i)); + if (!js) return {}; + const char* c = env->GetStringUTFChars(js, nullptr); + std::string out = c ? c : ""; + if (c) env->ReleaseStringUTFChars(js, c); + env->DeleteLocalRef(js); + return out; + }; + std::unordered_map> table; + table.reserve(n); + jsize cursor = 0; + for (jsize i = 0; i < n; ++i) { + if (ids[i] <= 0) { cursor += counts[i]; continue; } + std::unordered_map props; + const jsize end = cursor + counts[i]; + props.reserve(counts[i]); + for (jsize j = cursor; j < end; ++j) { + auto k = read_str(propKeys, j); + if (k.empty()) continue; + props.emplace(std::move(k), read_str(propVals, j)); + } + cursor = end; + table.emplace(static_cast(ids[i]), std::move(props)); + } + env->ReleaseIntArrayElements(defIds, ids, JNI_ABORT); + env->ReleaseIntArrayElements(propCountsPerDef, counts, JNI_ABORT); + if (table.empty()) { + p.inventory_item_defs.erase(static_cast(appId)); + } else { + p.inventory_item_defs[static_cast(appId)] = std::move(table); + } + WN_LOGI("set_inventory_item_defs: app=%d count=%zu", appId, + p.inventory_item_defs[static_cast(appId)].size()); +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeSetFriendPersonaState( + JNIEnv* /*env*/, jclass /*cls*/, jlong steamId64, jint state) { + if (steamId64 == 0) return; + int32_t flags = 0; + { + auto& p = lsc::pushed(); + std::lock_guard lk(lsc::state_mutex()); + auto sid = static_cast(steamId64); + if (state < 0) { + auto it = p.friend_persona_states.find(sid); + if (it != p.friend_persona_states.end() && it->second != 0) { + flags = cb::kPersonaChangeStatus | cb::kPersonaChangeGoneOffline; + } + p.friend_persona_states.erase(sid); + } else { + uint32_t prev = 0; + bool prev_known = false; + auto it = p.friend_persona_states.find(sid); + if (it != p.friend_persona_states.end()) { + prev = it->second; + prev_known = true; + } + p.friend_persona_states[sid] = static_cast(state); + if (!prev_known || prev != static_cast(state)) { + flags = cb::kPersonaChangeStatus; + if (prev == 0 && state != 0) flags |= cb::kPersonaChangeComeOnline; + if (prev != 0 && state == 0) flags |= cb::kPersonaChangeGoneOffline; + } + } + } + if (flags != 0) emit_persona_state_change(static_cast(steamId64), flags); +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeSetFriendGamePlayed( + JNIEnv* /*env*/, jclass /*cls*/, jlong steamId64, jint appId) { + if (steamId64 == 0) return; + auto& p = lsc::pushed(); + std::lock_guard lk(lsc::state_mutex()); + if (appId <= 0) { + p.friend_game_played_app.erase(static_cast(steamId64)); + } else { + p.friend_game_played_app[static_cast(steamId64)] = + static_cast(appId); + } +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeSetFriendPersonaName( + JNIEnv* env, jclass /*cls*/, jlong steamId64, jstring jname) { + if (steamId64 == 0) return; + auto& p = lsc::pushed(); + std::string name = jstr(env, jname); + bool changed = false; + { + std::lock_guard lk(lsc::state_mutex()); + const uint64_t sid = static_cast(steamId64); + if (name.empty()) { + changed = (p.friend_persona_names.erase(sid) > 0); + } else { + auto it = p.friend_persona_names.find(sid); + changed = (it == p.friend_persona_names.end() || it->second != name); + p.friend_persona_names[sid] = std::move(name); + } + } + if (changed) { + emit_persona_state_change(static_cast(steamId64), + cb::kPersonaChangeName); + } +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeSetLaunchCommandLine( + JNIEnv* env, jclass /*cls*/, jstring jcli) { + std::string cl = jstr(env, jcli); + auto& p = lsc::pushed(); + std::lock_guard lk(lsc::state_mutex()); + p.launch_command_line = std::move(cl); + WN_LOGI("set_launch_command_line(\"%s\")", p.launch_command_line.c_str()); +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeSetAppFamilyShared( + JNIEnv* /*env*/, jclass /*cls*/, jboolean familyShared) { + lsc::pushed().app_is_family_shared.store(familyShared == JNI_TRUE); +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeSetEncryptedAppTicket( + JNIEnv* env, jclass /*cls*/, jint appId, jbyteArray body, jint eresult) { + if (appId <= 0) return; + auto& p = lsc::pushed(); + std::lock_guard lk(lsc::state_mutex()); + if (!body) { + p.encrypted_app_tickets.erase(static_cast(appId)); + } else { + jsize n = env->GetArrayLength(body); + if (n <= 0) { + p.encrypted_app_tickets.erase(static_cast(appId)); + } else { + std::vector buf(n); + env->GetByteArrayRegion(body, 0, n, reinterpret_cast(buf.data())); + p.encrypted_app_tickets[static_cast(appId)] = std::move(buf); + } + } + p.encrypted_app_ticket_eresult.store(static_cast(eresult)); + WN_LOGI("set_encrypted_app_ticket: app=%d bytes=%d eresult=%d", + appId, body ? env->GetArrayLength(body) : 0, eresult); +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeReportLogonFailure( + JNIEnv* /*env*/, jclass /*cls*/, jint eresult, jboolean stillRetrying) { + cb::SteamServerConnectFailure payload{}; + payload.m_eResult = static_cast(eresult); + payload.m_bStillRetrying = (stillRetrying == JNI_TRUE); + lsc::push_callback(lsc::state().user.load(), + cb::kSteamServerConnectFailure, + &payload, sizeof(payload)); + WN_LOGI("report_logon_failure: eresult=%d stillRetrying=%d " + "(SteamServerConnectFailure_t emitted)", + eresult, payload.m_bStillRetrying ? 1 : 0); +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeSetServerRealTime( + JNIEnv* /*env*/, jclass /*cls*/, jint serverRealTimeUnix) { + auto& p = lsc::pushed(); + const auto now = std::chrono::steady_clock::now(); + const auto now_ms = std::chrono::duration_cast( + now.time_since_epoch()).count(); + p.server_realtime.store(static_cast(serverRealTimeUnix)); + p.server_realtime_anchor_local_ms.store(static_cast(now_ms)); +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeSetCloudEnabledForAccount( + JNIEnv* /*env*/, jclass /*cls*/, jboolean enabled) { + lsc::pushed().cloud_enabled_account.store(enabled == JNI_TRUE); +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeSetCloudEnabledForApp( + JNIEnv* /*env*/, jclass /*cls*/, jboolean enabled) { + lsc::pushed().cloud_enabled_app.store(enabled == JNI_TRUE); +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeSetCloudQuota( + JNIEnv* /*env*/, jclass /*cls*/, jlong totalBytes, jlong availBytes) { + auto& p = lsc::pushed(); + p.cloud_quota_total.store(static_cast(totalBytes)); + p.cloud_quota_available.store(static_cast(availBytes)); +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeSetCloudFiles( + JNIEnv* env, jclass /*cls*/, jobjectArray names, jintArray sizes, + jlongArray timestamps) { + auto& p = lsc::pushed(); + std::lock_guard lk(lsc::state_mutex()); + p.cloud_files.clear(); + if (!names || !sizes || !timestamps) return; + jsize n = env->GetArrayLength(names); + jsize ns = env->GetArrayLength(sizes); + jsize nt = env->GetArrayLength(timestamps); + if (n <= 0 || n != ns || n != nt) return; + jint* sbuf = env->GetIntArrayElements(sizes, nullptr); + jlong* tbuf = env->GetLongArrayElements(timestamps, nullptr); + if (!sbuf || !tbuf) { + if (sbuf) env->ReleaseIntArrayElements(sizes, sbuf, JNI_ABORT); + if (tbuf) env->ReleaseLongArrayElements(timestamps, tbuf, JNI_ABORT); + return; + } + p.cloud_files.reserve(n); + for (jsize i = 0; i < n; ++i) { + auto jname = reinterpret_cast(env->GetObjectArrayElement(names, i)); + std::string name; + if (jname) { + const char* c = env->GetStringUTFChars(jname, nullptr); + if (c) { name = c; env->ReleaseStringUTFChars(jname, c); } + env->DeleteLocalRef(jname); + } + if (name.empty()) continue; + wn_libsteamclient::PushedState::CloudFileEntry e; + e.name = std::move(name); + e.size = static_cast(sbuf[i]); + e.timestamp = static_cast(tbuf[i]); + p.cloud_files.push_back(std::move(e)); + } + env->ReleaseIntArrayElements(sizes, sbuf, JNI_ABORT); + env->ReleaseLongArrayElements(timestamps, tbuf, JNI_ABORT); + size_t pushed_count = p.cloud_files.size(); + uint32_t app = p.app_id.load(); + if (app != 0) { + cb::RemoteStorageAppSyncedClient payload{}; + payload.m_nAppID = app; + payload.m_eResult = pushed_count > 0 ? 1 : 2; // OK / Fail + payload.m_unNumDownloads = static_cast(pushed_count); + lsc::push_callback(lsc::state().user.load(), + cb::kRemoteStorageAppSyncedClient, + &payload, sizeof(payload)); + } + WN_LOGI("set_cloud_files: %zu entries app=%u (cb emitted=%d)", + pushed_count, app, app != 0 ? 1 : 0); +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeSetAchievementSchema( + JNIEnv* env, jclass /*cls*/, jobjectArray apiNames, + jobjectArray displayNames, jobjectArray descriptions, + jobjectArray icons, jbooleanArray hidden) { + auto& p = lsc::pushed(); + std::lock_guard lk(lsc::state_mutex()); + p.achievements.clear(); + p.achievement_index.clear(); + p.dirty_stats_int.clear(); + p.dirty_stats_float.clear(); + if (!apiNames) { p.stats_ready.store(true); return; } + jsize n = env->GetArrayLength(apiNames); + jsize nd = displayNames ? env->GetArrayLength(displayNames) : 0; + jsize nx = descriptions ? env->GetArrayLength(descriptions) : 0; + jsize ni = icons ? env->GetArrayLength(icons) : 0; + jsize nh = hidden ? env->GetArrayLength(hidden) : 0; + if (n <= 0) { p.stats_ready.store(true); return; } + jboolean* hbuf = (hidden && nh == n) ? env->GetBooleanArrayElements(hidden, nullptr) : nullptr; + p.achievements.reserve(n); + p.achievement_index.reserve(n); + auto read = [&](jobjectArray arr, jsize len, jsize i) -> std::string { + if (!arr || i >= len) return {}; + auto js = reinterpret_cast(env->GetObjectArrayElement(arr, i)); + if (!js) return {}; + const char* c = env->GetStringUTFChars(js, nullptr); + std::string out = c ? c : ""; + if (c) env->ReleaseStringUTFChars(js, c); + env->DeleteLocalRef(js); + return out; + }; + for (jsize i = 0; i < n; ++i) { + wn_libsteamclient::PushedState::AchievementEntry e; + e.api_name = read(apiNames, n, i); + if (e.api_name.empty()) continue; + std::string dn = read(displayNames, nd, i); + std::string ds = read(descriptions, nx, i); + if (!dn.empty()) e.display_names.emplace("english", std::move(dn)); + if (!ds.empty()) e.descriptions.emplace("english", std::move(ds)); + e.icon = read(icons, ni, i); + e.hidden = hbuf && hbuf[i] == JNI_TRUE; + e.icon_handle = static_cast(p.achievements.size()) + 1; // non-zero + p.achievement_index.emplace(e.api_name, p.achievements.size()); + p.achievements.push_back(std::move(e)); + } + if (hbuf) env->ReleaseBooleanArrayElements(hidden, hbuf, JNI_ABORT); + p.stats_ready.store(true); + size_t pushed_count = p.achievements.size(); + cb::UserStatsReceived payload{}; + payload.m_nGameID = static_cast(p.app_id.load()); + payload.m_eResult = pushed_count > 0 ? 1 : 2; // 1=k_EResultOK, 2=k_EResultFail + payload.m_steamIDUser = p.steam_id.load(); + int h_user = lsc::state().user.load(); + lsc::push_callback(h_user, cb::kUserStatsReceived, &payload, sizeof(payload)); + WN_LOGI("set_achievement_schema: %zu entries (UserStatsReceived_t emitted " + "user=%d game=%llu eresult=%d)", + pushed_count, h_user, + static_cast(payload.m_nGameID), + payload.m_eResult); +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeSetStatIds( + JNIEnv* env, jclass /*cls*/, jobjectArray jNames, jintArray jIds) { + if (!jNames || !jIds) return; + jsize n = env->GetArrayLength(jNames); + if (n <= 0 || env->GetArrayLength(jIds) != n) return; + jint* ids = env->GetIntArrayElements(jIds, nullptr); + auto& p = lsc::pushed(); + { + std::lock_guard lk(lsc::state_mutex()); + p.stat_name_to_id.clear(); + for (jsize i = 0; i < n; ++i) { + jstring js = static_cast(env->GetObjectArrayElement(jNames, i)); + if (!js) continue; + const char* c = env->GetStringUTFChars(js, nullptr); + if (c && *c && ids[i] >= 0) { + p.stat_name_to_id[c] = static_cast(ids[i]); + } + if (c) env->ReleaseStringUTFChars(js, c); + env->DeleteLocalRef(js); + } + } + env->ReleaseIntArrayElements(jIds, ids, JNI_ABORT); + WN_LOGI("set_stat_ids: %d entries", n); +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeSetAchievementBlockBits( + JNIEnv* env, jclass /*cls*/, jobjectArray apiNames, + jintArray blockIds, jintArray bitIndices) { + if (!apiNames || !blockIds || !bitIndices) return; + jsize n = env->GetArrayLength(apiNames); + if (n <= 0) return; + if (env->GetArrayLength(blockIds) != n || + env->GetArrayLength(bitIndices) != n) { + WN_LOGI("set_achievement_block_bits: array length mismatch (n=%d) — ignoring", n); + return; + } + jint* blocks = env->GetIntArrayElements(blockIds, nullptr); + jint* bits = env->GetIntArrayElements(bitIndices, nullptr); + auto& p = lsc::pushed(); + size_t applied = 0; + { + std::lock_guard lk(lsc::state_mutex()); + for (jsize i = 0; i < n; ++i) { + jstring js = static_cast(env->GetObjectArrayElement(apiNames, i)); + if (!js) continue; + const char* c = env->GetStringUTFChars(js, nullptr); + if (!c) { env->DeleteLocalRef(js); continue; } + auto it = p.achievement_index.find(c); + if (it != p.achievement_index.end() && it->second < p.achievements.size()) { + auto& ach = p.achievements[it->second]; + ach.block_id = blocks[i]; + ach.bit_index = bits[i]; + ++applied; + } + env->ReleaseStringUTFChars(js, c); + env->DeleteLocalRef(js); + } + } + env->ReleaseIntArrayElements(blockIds, blocks, JNI_ABORT); + env->ReleaseIntArrayElements(bitIndices, bits, JNI_ABORT); + WN_LOGI("set_achievement_block_bits: applied %zu / %d", applied, n); +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeAddAchievementLocale( + JNIEnv* env, jclass /*cls*/, jstring jApiName, jstring jLocale, + jstring jDisplayName, jstring jDescription) { + if (!jApiName || !jLocale) return; + std::string api = jstr(env, jApiName); + std::string locale = jstr(env, jLocale); + std::string dn = jstr(env, jDisplayName); + std::string ds = jstr(env, jDescription); + if (api.empty() || locale.empty() || (dn.empty() && ds.empty())) return; + auto& p = lsc::pushed(); + std::lock_guard lk(lsc::state_mutex()); + auto it = p.achievement_index.find(api); + if (it == p.achievement_index.end()) return; + auto& a = p.achievements[it->second]; + if (!dn.empty()) a.display_names[locale] = std::move(dn); + if (!ds.empty()) a.descriptions[locale] = std::move(ds); +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeSetAchievementProgress( + JNIEnv* env, jclass /*cls*/, jstring jApiName, jboolean achieved, + jint unlockTimeUnix) { + if (!jApiName) return; + std::string name = jstr(env, jApiName); + if (name.empty()) return; + auto& p = lsc::pushed(); + std::lock_guard lk(lsc::state_mutex()); + auto it = p.achievement_index.find(name); + if (it == p.achievement_index.end()) return; + auto& a = p.achievements[it->second]; + a.achieved = (achieved == JNI_TRUE); + a.unlock_time = static_cast(unlockTimeUnix); +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeSetStatInt( + JNIEnv* env, jclass /*cls*/, jstring jName, jint value) { + if (!jName) return; + std::string name = jstr(env, jName); + if (name.empty()) return; + auto& p = lsc::pushed(); + std::lock_guard lk(lsc::state_mutex()); + p.stats_int[std::move(name)] = static_cast(value); +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeSetStatFloat( + JNIEnv* env, jclass /*cls*/, jstring jName, jfloat value) { + if (!jName) return; + std::string name = jstr(env, jName); + if (name.empty()) return; + auto& p = lsc::pushed(); + std::lock_guard lk(lsc::state_mutex()); + p.stats_float[std::move(name)] = value; +} + +JNIEXPORT jint JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticAchievementCount( + JNIEnv* /*env*/, jclass /*cls*/) { + std::lock_guard lk(lsc::state_mutex()); + return static_cast(lsc::pushed().achievements.size()); +} + +JNIEXPORT jint JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticCallbackDepth( + JNIEnv* /*env*/, jclass /*cls*/) { + auto& s = lsc::state(); + std::lock_guard lk(s.callback_mu); + return static_cast(s.callback_queue.size()); +} + +JNIEXPORT jint JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticTcpAccepted( + JNIEnv* /*env*/, jclass /*cls*/) { + return static_cast(lsc::accepted_connection_count()); +} + +extern "C" void* wn_get_isteam_utils(); +JNIEXPORT jint JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticUtilsGetAPICallResult( + JNIEnv* /*env*/, jclass /*cls*/, jint iCallback, jint eresultIn) { + uint64_t h = lsc::alloc_api_call_handle(); + int32_t body = eresultIn; + lsc::push_call_result(h, static_cast(iCallback), + &body, sizeof(body), /*io_failure=*/false); + void* obj = wn_get_isteam_utils(); + if (!obj) return -1; + long* vt = *reinterpret_cast(obj); + using GetResultFn = bool (*)(void*, uint64_t, void*, int, int, bool*); + auto getr = reinterpret_cast(vt[13]); + int32_t out = -1; + bool failed = false; + bool ok = getr(obj, h, &out, sizeof(out), static_cast(iCallback), &failed); + return ok ? out : -1; +} + +extern "C" void* wn_get_isteam_user(); +JNIEXPORT jlong JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticRequestEncryptedAppTicket( + JNIEnv* env, jclass /*cls*/, jbyteArray outBody) { + void* obj = wn_get_isteam_user(); + if (!obj) return 0; + long* vt = *reinterpret_cast(obj); + using ReqFn = uint64_t (*)(void*, void*, int); + auto req = reinterpret_cast(vt[21]); + uint64_t h = req(obj, nullptr, 0); + using GetFn = bool (*)(void*, void*, int, uint32_t*); + auto get = reinterpret_cast(vt[22]); + uint8_t scratch[128] = {0}; + uint32_t actual = 0; + bool ok = get(obj, scratch, sizeof(scratch), &actual); + if (ok && outBody && env->GetArrayLength(outBody) >= static_cast(actual)) { + env->SetByteArrayRegion(outBody, 0, static_cast(actual), + reinterpret_cast(scratch)); + } + return static_cast(h); +} + +extern "C" void* wn_get_isteam_user(); +JNIEXPORT jint JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticGetAuthTicket( + JNIEnv* env, jclass /*cls*/, jbyteArray jbuf) { + void* obj = wn_get_isteam_user(); + if (!obj) return 0; + long* vt = *reinterpret_cast(obj); + using GetTicketFn = uint64_t (*)(void*, void*, int, uint32_t*, const void*); + auto get_ticket = reinterpret_cast(vt[13]); + uint8_t scratch[64] = {0}; + uint32_t actual = 0; + uint64_t h = get_ticket(obj, scratch, sizeof(scratch), &actual, nullptr); + if (jbuf && env->GetArrayLength(jbuf) >= static_cast(actual)) { + env->SetByteArrayRegion(jbuf, 0, static_cast(actual), + reinterpret_cast(scratch)); + } + return static_cast(h); +} + +extern "C" __attribute__((visibility("default"))) +void SteamAPI_RunCallbacks(void); + +namespace { + +struct DiagnosticCallback { + void** vptr; + uint8_t flags; + uint8_t _pad0[3]; + int32_t iCallback; + int32_t runs; // bumped on each Run() + int32_t last_user; // copied from msg payload[0] (m_nGameID low 32) + int32_t last_eresult; // copied from msg payload offset 8 (m_eResult) +}; + +void diagnostic_run(DiagnosticCallback* self, void* payload) { + if (!self) return; + ++self->runs; + if (payload) { + self->last_user = static_cast( + *reinterpret_cast(payload)); + self->last_eresult = *reinterpret_cast( + static_cast(payload) + 8); + } +} + +void diagnostic_run_result(DiagnosticCallback*, void*, bool, uint64_t) {} +int diagnostic_get_size(DiagnosticCallback*) { return 24; } // sizeof(UserStatsReceived_t) + +void* const kDiagnosticVtable[] = { + reinterpret_cast(&diagnostic_run), + reinterpret_cast(&diagnostic_run_result), + reinterpret_cast(&diagnostic_get_size), +}; + +DiagnosticCallback g_diagnostic_cb; +bool g_diagnostic_registered = false; + +} // namespace + +namespace { +struct DiagnosticCallResultCb { + void** vptr; + uint8_t flags; + uint8_t _pad0[3]; + int32_t iCallback; + int32_t runs; + uint64_t last_h_call; + int32_t last_io_failure; + int32_t last_eresult; +}; + +void diag_cr_run(DiagnosticCallResultCb*, void*) {} // slot 0 unused for CCallResult +void diag_cr_run_result(DiagnosticCallResultCb* self, void* payload, + bool ioFailure, uint64_t hCall) { + if (!self) return; + ++self->runs; + self->last_h_call = hCall; + self->last_io_failure = ioFailure ? 1 : 0; + if (payload) { + self->last_eresult = *reinterpret_cast(payload); + } +} +int diag_cr_get_size(DiagnosticCallResultCb*) { return 0; } + +void* const kDiagnosticCallResultVtable[] = { + reinterpret_cast(&diag_cr_run), + reinterpret_cast(&diag_cr_run_result), + reinterpret_cast(&diag_cr_get_size), +}; + +DiagnosticCallResultCb g_diag_cr_cb; +} // namespace + +extern "C" __attribute__((visibility("default"))) +void SteamAPI_RunCallbacks(void); +extern "C" __attribute__((visibility("default"))) +void SteamAPI_RegisterCallResult(void*, uint64_t); + +JNIEXPORT jlong JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticPushAndDrainCallResult( + JNIEnv* /*env*/, jclass /*cls*/, jint callbackId, jint eresult) { + uint64_t h_call = lsc::alloc_api_call_handle(); + int32_t body_eresult = eresult; + lsc::push_call_result(h_call, static_cast(callbackId), + &body_eresult, sizeof(body_eresult), + /*io_failure=*/false); + + g_diag_cr_cb.vptr = const_cast(kDiagnosticCallResultVtable); + g_diag_cr_cb.flags = 0; + g_diag_cr_cb.iCallback = static_cast(callbackId); + g_diag_cr_cb.runs = 0; + g_diag_cr_cb.last_h_call = 0; + g_diag_cr_cb.last_io_failure = -1; + g_diag_cr_cb.last_eresult = 0; + SteamAPI_RegisterCallResult(&g_diag_cr_cb, h_call); + + SteamAPI_RunCallbacks(); + + return (static_cast(g_diag_cr_cb.runs) << 32) | + (static_cast(g_diag_cr_cb.last_eresult) & 0xFFFFFFFFL); +} + +JNIEXPORT jint JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticRegisterAndDrain( + JNIEnv* /*env*/, jclass /*cls*/, jint iCallback) { + g_diagnostic_cb.vptr = const_cast(kDiagnosticVtable); + g_diagnostic_cb.flags = 0; + g_diagnostic_cb.iCallback = static_cast(iCallback); + g_diagnostic_cb.runs = 0; + g_diagnostic_cb.last_user = 0; + g_diagnostic_cb.last_eresult = 0; + if (!g_diagnostic_registered) { + lsc::register_callback(&g_diagnostic_cb, static_cast(iCallback)); + g_diagnostic_registered = true; + } + SteamAPI_RunCallbacks(); + return g_diagnostic_cb.runs; +} + +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticShutdownPipe( + JNIEnv* /*env*/, jclass /*cls*/) { + int pipe = lsc::state().pipe.load(); + if (pipe == 0) return JNI_FALSE; + namespace cb = wn_libsteamclient::callbacks; + cb::SteamShutdown sd{}; + lsc::push_callback(lsc::state().user.load(), + cb::kSteamShutdown, &sd, 0); + bool ok = lsc::release_pipe(pipe); + (void)lsc::alloc_pipe(); + return ok ? JNI_TRUE : JNI_FALSE; +} + +extern "C" void* wn_get_isteam_user_stats(); +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticStoreStats( + JNIEnv* /*env*/, jclass /*cls*/) { + void* obj = wn_get_isteam_user_stats(); + if (!obj) return JNI_FALSE; + long* vt = *reinterpret_cast(obj); + using StoreFn = bool (*)(void*); + auto fn = reinterpret_cast(vt[10]); + return fn(obj) ? JNI_TRUE : JNI_FALSE; +} + +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticSetAchievement( + JNIEnv* env, jclass /*cls*/, jstring jName) { + if (!jName) return JNI_FALSE; + void* obj = wn_get_isteam_user_stats(); + if (!obj) return JNI_FALSE; + std::string name = jstr(env, jName); + long* vt = *reinterpret_cast(obj); + using SetFn = bool (*)(void*, const char*); + auto fn = reinterpret_cast(vt[7]); + return fn(obj, name.c_str()) ? JNI_TRUE : JNI_FALSE; +} + +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticIndicateAchievementProgress( + JNIEnv* env, jclass /*cls*/, jstring jName, jint cur, jint max) { + if (!jName) return JNI_FALSE; + void* obj = wn_get_isteam_user_stats(); + if (!obj) return JNI_FALSE; + std::string name = jstr(env, jName); + long* vt = *reinterpret_cast(obj); + using IndFn = bool (*)(void*, const char*, uint32_t, uint32_t); + auto fn = reinterpret_cast(vt[13]); + return fn(obj, name.c_str(), + static_cast(cur), + static_cast(max)) ? JNI_TRUE : JNI_FALSE; +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeSetFriendRichPresence( + JNIEnv* env, jclass /*cls*/, jlong jSteamId, jstring jKey, jstring jValue) { + uint64_t steam_id = static_cast(jSteamId); + if (steam_id == 0 || !jKey) return; + std::string key = jstr(env, jKey); + std::string value = jstr(env, jValue); + if (key.empty()) return; + auto& p = lsc::pushed(); + { + std::lock_guard lk(lsc::state_mutex()); + auto& rp = p.rich_presence[steam_id]; + auto it = std::find_if(rp.begin(), rp.end(), + [&](const auto& kv) { return kv.first == key; }); + if (value.empty()) { + if (it != rp.end()) rp.erase(it); + } else if (it == rp.end()) { + rp.emplace_back(std::move(key), std::move(value)); + } else { + it->second = std::move(value); + } + } + cb::FriendRichPresenceUpdate ev{}; + ev.m_steamIDFriend = steam_id; + ev.m_nAppID = p.app_id.load(); + lsc::push_callback(lsc::state().user.load(), + cb::kFriendRichPresenceUpdate, + &ev, sizeof(ev)); +} + +extern "C" void* wn_get_isteam_friends(); +JNIEXPORT jlong JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticSetPersonaName( + JNIEnv* env, jclass /*cls*/, jstring jName) { + if (!jName) return 0; + void* obj = wn_get_isteam_friends(); + if (!obj) return 0; + std::string name = jstr(env, jName); + long* vt = *reinterpret_cast(obj); + using SetFn = uint64_t (*)(void*, const char*); + auto fn = reinterpret_cast(vt[1]); + return static_cast(fn(obj, name.c_str())); +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticRequestFriendRichPresence( + JNIEnv* /*env*/, jclass /*cls*/, jlong jSteamId) { + void* obj = wn_get_isteam_friends(); + if (!obj) return; + long* vt = *reinterpret_cast(obj); + using ReqFn = void (*)(void*, uint64_t); + auto fn = reinterpret_cast(vt[48]); + fn(obj, static_cast(jSteamId)); +} + +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticRequestUserInformation( + JNIEnv* /*env*/, jclass /*cls*/, jlong jSteamId, jboolean jNameOnly) { + void* obj = wn_get_isteam_friends(); + if (!obj) return JNI_FALSE; + long* vt = *reinterpret_cast(obj); + using ReqFn = bool (*)(void*, uint64_t, bool); + auto fn = reinterpret_cast(vt[37]); + return fn(obj, static_cast(jSteamId), jNameOnly == JNI_TRUE) + ? JNI_TRUE : JNI_FALSE; +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticInjectLogonState( + JNIEnv* /*env*/, jclass /*cls*/, jboolean jLoggedOn) { + wn_cm_bridge_inject_test_logon_state(jLoggedOn == JNI_TRUE); +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticInjectLicenseList( + JNIEnv* env, jclass /*cls*/, jintArray jPackageIds, jintArray jOwnerIds) { + if (!jPackageIds) { + wn_cm_bridge_inject_test_license_list(nullptr, 0); + return; + } + jsize n = env->GetArrayLength(jPackageIds); + if (n <= 0) { + wn_cm_bridge_inject_test_license_list(nullptr, 0); + return; + } + jint* pkg_ids = env->GetIntArrayElements(jPackageIds, nullptr); + jint* own_ids = jOwnerIds ? env->GetIntArrayElements(jOwnerIds, nullptr) : nullptr; + if (!pkg_ids) return; + std::vector entries; + entries.reserve(static_cast(n)); + uint32_t now = static_cast(::time(nullptr)); + for (jsize i = 0; i < n; ++i) { + WnCmLicenseEntry e{}; + e.package_id = static_cast(pkg_ids[i]); + e.owner_id = (own_ids && i < env->GetArrayLength(jOwnerIds)) + ? static_cast(own_ids[i]) : 0u; + e.time_created = now; + entries.push_back(e); + } + wn_cm_bridge_inject_test_license_list(entries.data(), entries.size()); + env->ReleaseIntArrayElements(jPackageIds, pkg_ids, JNI_ABORT); + if (own_ids) env->ReleaseIntArrayElements(jOwnerIds, own_ids, JNI_ABORT); +} + +JNIEXPORT jint JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticGetLicenseOwner( + JNIEnv* /*env*/, jclass /*cls*/, jint jPackageId) { + auto& p = lsc::pushed(); + std::lock_guard lk(lsc::state_mutex()); + auto it = p.licenses.find(static_cast(jPackageId)); + if (it == p.licenses.end()) return -1; + return static_cast(it->second.owner_id); +} + +extern "C" void* wn_get_isteam_apps(); +JNIEXPORT jint JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticGetEarliestPurchaseUnixTime( + JNIEnv* /*env*/, jclass /*cls*/, jint jAppId) { + void* obj = wn_get_isteam_apps(); + if (!obj) return 0; + long* vt = *reinterpret_cast(obj); + using Fn = uint32_t (*)(void*, uint32_t); + auto fn = reinterpret_cast(vt[8]); + return static_cast(fn(obj, static_cast(jAppId))); +} + +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticBIsSubscribedFromFreeWeekend( + JNIEnv* /*env*/, jclass /*cls*/) { + void* obj = wn_get_isteam_apps(); + if (!obj) return JNI_FALSE; + long* vt = *reinterpret_cast(obj); + using Fn = bool (*)(void*); + auto fn = reinterpret_cast(vt[9]); + return fn(obj) ? JNI_TRUE : JNI_FALSE; +} + +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticBIsSubscribedFromFamilySharing( + JNIEnv* /*env*/, jclass /*cls*/) { + void* obj = wn_get_isteam_apps(); + if (!obj) return JNI_FALSE; + long* vt = *reinterpret_cast(obj); + using Fn = bool (*)(void*); + auto fn = reinterpret_cast(vt[27]); + return fn(obj) ? JNI_TRUE : JNI_FALSE; +} + +JNIEXPORT jfloat JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticUpdateAvgRateStat( + JNIEnv* env, jclass /*cls*/, jstring jName, + jfloat jCountThisSession, jdouble jSessionLength) { + if (!jName) return 0.0f; + void* obj = wn_get_isteam_user_stats(); + if (!obj) return 0.0f; + std::string name = jstr(env, jName); + long* vt = *reinterpret_cast(obj); + using UpdateFn = bool (*)(void*, const char*, float, double); + auto upd = reinterpret_cast(vt[5]); + if (!upd(obj, name.c_str(), jCountThisSession, jSessionLength)) return 0.0f; + using GetFn = bool (*)(void*, const char*, float*); + auto get = reinterpret_cast(vt[2]); + float out = 0.0f; + get(obj, name.c_str(), &out); + return static_cast(out); +} + +JNIEXPORT jlong JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticGetAppOwner( + JNIEnv* /*env*/, jclass /*cls*/) { + void* obj = wn_get_isteam_apps(); + if (!obj) return 0; + long* vt = *reinterpret_cast(obj); + using Fn = uint64_t (*)(void*); + auto fn = reinterpret_cast(vt[20]); + return static_cast(fn(obj)); +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticInjectTrialLicense( + JNIEnv* /*env*/, jclass /*cls*/, jint jPackageId, + jint jMinuteLimit, jint jMinutesUsed) { + if (jPackageId <= 0) return; + auto& p = lsc::pushed(); + std::lock_guard lk(lsc::state_mutex()); + auto& slot = p.licenses[static_cast(jPackageId)]; + slot.package_id = static_cast(jPackageId); + slot.minute_limit = jMinuteLimit; + slot.minutes_used = jMinutesUsed; +} + +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticBIsDlcInstalled( + JNIEnv* /*env*/, jclass /*cls*/, jint jAppId) { + void* obj = wn_get_isteam_apps(); + if (!obj) return JNI_FALSE; + long* vt = *reinterpret_cast(obj); + using Fn = bool (*)(void*, uint32_t); + auto fn = reinterpret_cast(vt[7]); + return fn(obj, static_cast(jAppId)) ? JNI_TRUE : JNI_FALSE; +} + +JNIEXPORT jlong JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticBIsTimedTrial( + JNIEnv* /*env*/, jclass /*cls*/) { + void* obj = wn_get_isteam_apps(); + if (!obj) return 0; + long* vt = *reinterpret_cast(obj); + using Fn = bool (*)(void*, uint32_t*, uint32_t*); + auto fn = reinterpret_cast(vt[28]); + uint32_t allowed = 0, played = 0; + bool ok = fn(obj, &allowed, &played); + if (!ok) return 0; + return (1LL << 63) | + (static_cast(allowed) << 32) | + static_cast(played); +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeSetAppSourcePackages( + JNIEnv* env, jclass /*cls*/, jint appId, jintArray packageIds) { + if (appId <= 0) return; + auto& p = lsc::pushed(); + std::lock_guard lk(lsc::state_mutex()); + auto key = static_cast(appId); + if (!packageIds) { + p.app_source_packages.erase(key); + return; + } + jsize n = env->GetArrayLength(packageIds); + if (n <= 0) { + p.app_source_packages.erase(key); + return; + } + jint* arr = env->GetIntArrayElements(packageIds, nullptr); + if (!arr) return; + std::vector pkgs; + pkgs.reserve(static_cast(n)); + for (jsize i = 0; i < n; ++i) { + if (arr[i] > 0) pkgs.push_back(static_cast(arr[i])); + } + env->ReleaseIntArrayElements(packageIds, arr, JNI_ABORT); + if (pkgs.empty()) { + p.app_source_packages.erase(key); + } else { + p.app_source_packages[key] = std::move(pkgs); + } +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticInjectFriendsList( + JNIEnv* env, jclass /*cls*/, jlongArray jSids) { + if (!jSids) { + wn_cm_bridge_inject_test_friends_list(nullptr, 0); + return; + } + jsize n = env->GetArrayLength(jSids); + if (n <= 0) { + wn_cm_bridge_inject_test_friends_list(nullptr, 0); + return; + } + jlong* arr = env->GetLongArrayElements(jSids, nullptr); + if (!arr) return; + static_assert(sizeof(jlong) == sizeof(uint64_t), "jlong/uint64 size mismatch"); + wn_cm_bridge_inject_test_friends_list( + reinterpret_cast(arr), static_cast(n)); + env->ReleaseLongArrayElements(jSids, arr, JNI_ABORT); +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticInjectPersonaEvent( + JNIEnv* env, jclass /*cls*/, + jlong jSteamId, + jint jPersonaState, + jint jGameAppId, + jstring jName, + jbyteArray jAvatarHash, + jobjectArray jRpKeys, + jobjectArray jRpValues) { + WnCmPersonaEvent ev{}; + ev.sid = static_cast(jSteamId); + ev.persona_state = (jPersonaState < 0) ? UINT32_MAX + : static_cast(jPersonaState); + ev.game_played_app = (jGameAppId <= 0) ? 0 : static_cast(jGameAppId); + + std::string name_storage; + if (jName) { + name_storage = jstr(env, jName); + if (!name_storage.empty()) ev.name = name_storage.c_str(); + } + + std::vector hash_storage; + if (jAvatarHash) { + jsize n = env->GetArrayLength(jAvatarHash); + if (n > 0) { + hash_storage.resize(static_cast(n)); + env->GetByteArrayRegion(jAvatarHash, 0, n, + reinterpret_cast(hash_storage.data())); + ev.avatar_hash = hash_storage.data(); + ev.avatar_hash_len = hash_storage.size(); + } + } + + std::vector key_storage, value_storage; + std::vector rp_kv; + if (jRpKeys && jRpValues) { + jsize kn = env->GetArrayLength(jRpKeys); + jsize vn = env->GetArrayLength(jRpValues); + jsize count = std::min(kn, vn); + key_storage.reserve(count); + value_storage.reserve(count); + rp_kv.reserve(count); + for (jsize i = 0; i < count; ++i) { + auto k_obj = reinterpret_cast(env->GetObjectArrayElement(jRpKeys, i)); + auto v_obj = reinterpret_cast(env->GetObjectArrayElement(jRpValues, i)); + key_storage.push_back(jstr(env, k_obj)); + value_storage.push_back(jstr(env, v_obj)); + if (k_obj) env->DeleteLocalRef(k_obj); + if (v_obj) env->DeleteLocalRef(v_obj); + rp_kv.push_back({key_storage.back().c_str(), + value_storage.back().c_str()}); + } + if (!rp_kv.empty()) { + ev.rp_pairs = rp_kv.data(); + ev.rp_count = rp_kv.size(); + } + } + + wn_cm_bridge_dispatch_persona(&ev); +} + +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticInjectOwnershipTicket( + JNIEnv* env, jclass /*cls*/, jint jAppId, jbyteArray jBytes) { + if (jAppId <= 0 || !jBytes) return JNI_FALSE; + jsize n = env->GetArrayLength(jBytes); + if (n <= 0) return JNI_FALSE; + std::vector tmp(static_cast(n)); + env->GetByteArrayRegion(jBytes, 0, n, reinterpret_cast(tmp.data())); + return wn_cm_bridge_inject_test_ownership_ticket( + static_cast(jAppId), tmp.data(), tmp.size()) + ? JNI_TRUE : JNI_FALSE; +} + +JNIEXPORT jint JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticGetCachedOwnershipTicket( + JNIEnv* env, jclass /*cls*/, jint jAppId, jbyteArray jOut) { + if (jAppId <= 0) return 0; + size_t out_len = 0; + if (!jOut) { + wn_cm_get_cached_app_ownership_ticket(static_cast(jAppId), + nullptr, 0, &out_len); + return static_cast(out_len); + } + jsize max = env->GetArrayLength(jOut); + std::vector tmp(static_cast(max)); + bool ok = wn_cm_get_cached_app_ownership_ticket( + static_cast(jAppId), + tmp.data(), static_cast(max), &out_len); + if (!ok) return static_cast(out_len); // 0 = miss; >0 = need bigger buf + env->SetByteArrayRegion(jOut, 0, static_cast(out_len), + reinterpret_cast(tmp.data())); + return static_cast(out_len); +} + +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticRequestUserInfoBulk( + JNIEnv* env, jclass /*cls*/, jlongArray jSids, jint jFlags) { + if (!jSids) return JNI_FALSE; + jsize n = env->GetArrayLength(jSids); + if (n <= 0) return JNI_FALSE; + jlong* arr = env->GetLongArrayElements(jSids, nullptr); + if (!arr) return JNI_FALSE; + static_assert(sizeof(jlong) == sizeof(uint64_t), "jlong/uint64 size mismatch"); + bool ok = wn_cm_request_user_info_bulk(reinterpret_cast(arr), + static_cast(n), + static_cast(jFlags)); + env->ReleaseLongArrayElements(jSids, arr, JNI_ABORT); + return ok ? JNI_TRUE : JNI_FALSE; +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticClearRichPresence( + JNIEnv* /*env*/, jclass /*cls*/) { + void* obj = wn_get_isteam_friends(); + if (!obj) return; + long* vt = *reinterpret_cast(obj); + using ClrFn = void (*)(void*); + auto fn = reinterpret_cast(vt[44]); + fn(obj); +} + +extern "C" void* wn_get_isteam_friends(); +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticSetRichPresence( + JNIEnv* env, jclass /*cls*/, jstring jKey, jstring jValue) { + void* obj = wn_get_isteam_friends(); + if (!obj || !jKey) return JNI_FALSE; + std::string key = jstr(env, jKey); + std::string value = jstr(env, jValue); + long* vt = *reinterpret_cast(obj); + using SetFn = bool (*)(void*, const char*, const char*); + auto fn = reinterpret_cast(vt[43]); + return fn(obj, key.c_str(), + value.empty() ? nullptr : value.c_str()) ? JNI_TRUE : JNI_FALSE; +} + +JNIEXPORT jint JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticGetFriendPersonaState( + JNIEnv* /*env*/, jclass /*cls*/, jlong jSteamId) { + uint64_t sid = static_cast(jSteamId); + auto& p = lsc::pushed(); + std::lock_guard lk(lsc::state_mutex()); + auto it = p.friend_persona_states.find(sid); + if (it == p.friend_persona_states.end()) return -1; + return static_cast(it->second); +} + +JNIEXPORT jint JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticRichPresenceKeyCount( + JNIEnv* /*env*/, jclass /*cls*/, jlong jSteamId) { + void* obj = wn_get_isteam_friends(); + if (!obj) return 0; + long* vt = *reinterpret_cast(obj); + using CountFn = int (*)(void*, uint64_t); + auto fn = reinterpret_cast(vt[46]); + return fn(obj, static_cast(jSteamId)); +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeSetFriendAvatarHash( + JNIEnv* env, jclass /*cls*/, jlong jSteamId, jbyteArray jHash) { + uint64_t sid = static_cast(jSteamId); + if (sid == 0) return; + bool changed = false; + { + auto& p = lsc::pushed(); + std::lock_guard lk(lsc::state_mutex()); + auto& slot = p.friend_avatar_hashes[sid]; + if (!jHash) { + if (!slot.empty()) { slot.clear(); changed = true; } + } else { + jsize n = env->GetArrayLength(jHash); + std::vector bytes(static_cast(n)); + if (n > 0) { + env->GetByteArrayRegion(jHash, 0, n, + reinterpret_cast(bytes.data())); + } + if (bytes != slot) { + slot = std::move(bytes); + changed = true; + } + } + } + if (!changed) return; + cb::PersonaStateChange payload{}; + payload.m_ulSteamID = sid; + payload.m_nChangeFlags = cb::kPersonaChangeAvatar; + lsc::push_callback(lsc::state().user.load(), + cb::kPersonaStateChange, + &payload, sizeof(payload)); +} + +JNIEXPORT jstring JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticGetFriendAvatarHashHex( + JNIEnv* env, jclass /*cls*/, jlong jSteamId) { + uint64_t sid = static_cast(jSteamId); + std::string hex; + { + auto& p = lsc::pushed(); + std::lock_guard lk(lsc::state_mutex()); + auto it = p.friend_avatar_hashes.find(sid); + if (it != p.friend_avatar_hashes.end()) { + static constexpr char kHex[] = "0123456789abcdef"; + hex.reserve(it->second.size() * 2); + for (uint8_t b : it->second) { + hex.push_back(kHex[(b >> 4) & 0xF]); + hex.push_back(kHex[b & 0xF]); + } + } + } + return env->NewStringUTF(hex.c_str()); +} + +JNIEXPORT jint JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativePushFriendAvatar( + JNIEnv* env, jclass /*cls*/, + jlong jSteamId, jint jTier, jint jWidth, jint jHeight, jbyteArray jRgba) { + if (jSteamId == 0 || jWidth <= 0 || jHeight <= 0 || !jRgba) return 0; + if (jTier < 0 || jTier > 2) return 0; + jsize n = env->GetArrayLength(jRgba); + int expected = jWidth * jHeight * 4; + if (n != expected) return 0; + + int32_t handle; + { + auto& p = lsc::pushed(); + std::lock_guard lk(lsc::state_mutex()); + handle = p.next_image_handle++; + auto& img = p.image_registry[handle]; + img.width = jWidth; + img.height = jHeight; + img.rgba.resize(static_cast(n)); + env->GetByteArrayRegion(jRgba, 0, n, + reinterpret_cast(img.rgba.data())); + auto& a = p.friend_avatars[static_cast(jSteamId)]; + switch (jTier) { + case 0: a.small = handle; break; + case 1: a.medium = handle; break; + case 2: a.large = handle; break; + } + } + cb::AvatarImageLoaded ev{}; + ev.m_steamID = static_cast(jSteamId); + ev.m_iImage = handle; + ev.m_iWide = jWidth; + ev.m_iTall = jHeight; + lsc::push_callback(lsc::state().user.load(), + cb::kAvatarImageLoaded, + &ev, sizeof(ev)); + return handle; +} + +JNIEXPORT jlong JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticGetTieredAvatarSize( + JNIEnv* /*env*/, jclass /*cls*/, jlong jSteamId, jint jTier) { + if (jTier < 0 || jTier > 2) return 0; + void* friends = wn_get_isteam_friends(); + if (!friends) return 0; + int slot = 34 + jTier; // 34=small, 35=medium, 36=large + long* vt_f = *reinterpret_cast(friends); + using GetAv = int (*)(void*, uint64_t); + auto get_av = reinterpret_cast(vt_f[slot]); + int handle = get_av(friends, static_cast(jSteamId)); + if (handle <= 0) return 0; + void* utils = wn_get_isteam_utils(); + if (!utils) return (static_cast(handle) << 32); + long* vt_u = *reinterpret_cast(utils); + using SizeFn = bool (*)(void*, int, uint32_t*, uint32_t*); + auto fn_size = reinterpret_cast(vt_u[5]); + uint32_t w = 0, h = 0; + if (!fn_size(utils, handle, &w, &h)) return (static_cast(handle) << 32); + uint32_t lo = (w << 16) | (h & 0xFFFF); + return (static_cast(handle) << 32) | lo; +} + +JNIEXPORT jlong JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticGetSmallAvatarSize( + JNIEnv* /*env*/, jclass /*cls*/, jlong jSteamId) { + void* friends = wn_get_isteam_friends(); + if (!friends) return 0; + long* vt_f = *reinterpret_cast(friends); + using GetAv = int (*)(void*, uint64_t); + auto get_small = reinterpret_cast(vt_f[34]); + int handle = get_small(friends, static_cast(jSteamId)); + if (handle <= 0) return 0; + void* utils = wn_get_isteam_utils(); + if (!utils) return (static_cast(handle) << 32); + long* vt_u = *reinterpret_cast(utils); + using SizeFn = bool (*)(void*, int, uint32_t*, uint32_t*); + auto fn_size = reinterpret_cast(vt_u[5]); + uint32_t w = 0, h = 0; + bool ok = fn_size(utils, handle, &w, &h); + if (!ok) return (static_cast(handle) << 32); + uint32_t lo = (w << 16) | (h & 0xFFFF); + return (static_cast(handle) << 32) | lo; +} + +JNIEXPORT jint JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeDiagnosticGetImageRGBA( + JNIEnv* env, jclass /*cls*/, jint jHandle, jbyteArray jOut) { + if (jHandle <= 0 || !jOut) return 0; + void* utils = wn_get_isteam_utils(); + if (!utils) return 0; + jsize n = env->GetArrayLength(jOut); + std::vector tmp(static_cast(n)); + long* vt = *reinterpret_cast(utils); + using RgbaFn = bool (*)(void*, int, uint8_t*, int); + auto fn = reinterpret_cast(vt[6]); + if (!fn(utils, jHandle, tmp.data(), n)) return 0; + env->SetByteArrayRegion(jOut, 0, n, reinterpret_cast(tmp.data())); + return n; +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativeSetGameOverlayActive( + JNIEnv* /*env*/, jclass /*cls*/, jboolean jActive) { + bool active = (jActive == JNI_TRUE); + auto& p = lsc::pushed(); + bool prev = p.overlay_active.exchange(active); + if (prev == active) return; + cb::GameOverlayActivated ev{}; + ev.m_bActive = active; + lsc::push_callback(lsc::state().user.load(), + cb::kGameOverlayActivated, + &ev, sizeof(ev)); +} + +JNIEXPORT jstring JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnLibSteamClient_nativePollOverlayRequest( + JNIEnv* env, jclass /*cls*/) { + lsc::PushedState::OverlayRequest r; + { + auto& p = lsc::pushed(); + std::lock_guard lk(lsc::state_mutex()); + if (p.overlay_request_queue.empty()) return nullptr; + r = std::move(p.overlay_request_queue.front()); + p.overlay_request_queue.pop_front(); + } + char buf[512]; + int n = std::snprintf(buf, sizeof(buf), + "%s\x01%s\x01%llu\x01%u", + r.kind.c_str(), + r.arg1.c_str(), + static_cast(r.sid), + r.app_id); + if (n <= 0) return nullptr; + return env->NewStringUTF(buf); +} + +} // extern "C" diff --git a/app/src/main/cpp/wn-libsteamclient/src/runtime_state.cpp b/app/src/main/cpp/wn-libsteamclient/src/runtime_state.cpp new file mode 100644 index 000000000..0eb22e3be --- /dev/null +++ b/app/src/main/cpp/wn-libsteamclient/src/runtime_state.cpp @@ -0,0 +1,167 @@ +#include "wn_libsteamclient/runtime_state.h" +#include "wn_libsteamclient/callbacks.h" +#include "wn_libsteamclient/tcp_services.h" + +#include +#include +#include + +namespace wn_libsteamclient { + +namespace { + +void seed_state_from_env_once() { + static std::once_flag flag; + std::call_once(flag, []() { + const char* env_sid = std::getenv("STEAMID"); + const char* env_app = std::getenv("SteamAppId"); + const char* env_usr = std::getenv("SteamUser"); + uint64_t sid = 0; + if (env_sid && *env_sid) { + sid = std::strtoull(env_sid, nullptr, 10); + } + uint32_t app = 0; + if (env_app && *env_app) { + app = static_cast(std::strtoul(env_app, nullptr, 10)); + } + if (sid != 0) { + pushed().steam_id.store(sid); + state().user.store(1); + state().logged_on.store(true); + state().connected.store(true); + __android_log_print(ANDROID_LOG_INFO, "WnLibSteamClient", + "guest seed: STEAMID=%llu logged_on=true", + static_cast(sid)); + } + if (app != 0) { + pushed().app_id.store(app); + } + if (env_usr && *env_usr) { + std::lock_guard lk(state_mutex()); + if (pushed().persona_name.empty()) { + pushed().persona_name = env_usr; + } + } + }); +} + +__attribute__((constructor)) +static void wn_libsteamclient_so_loaded() { + seed_state_from_env_once(); +} + +} // namespace + +namespace { +State g_state_singleton; +PushedState g_pushed_singleton; +std::mutex g_state_mutex_singleton; +} // namespace + +State& state() { return g_state_singleton; } +std::mutex& state_mutex() { return g_state_mutex_singleton; } +PushedState& pushed() { return g_pushed_singleton; } + +HSteamPipe alloc_pipe() { + start_tcp_services(); + seed_state_from_env_once(); + std::lock_guard lk(state_mutex()); + auto& s = state(); + HSteamPipe cur = s.pipe.load(); + if (cur != 0) return 0; // already allocated; caller can read s.pipe + s.pipe.store(1); + return 1; +} + +bool release_pipe(HSteamPipe pipe) { + set_logged_on(false); + std::lock_guard lk(state_mutex()); + auto& s = state(); + if (pipe == 0 || s.pipe.load() != pipe) return false; + s.pipe.store(0); + s.user.store(0); + return true; +} + +HSteamUser alloc_global_user(HSteamPipe pipe) { + std::lock_guard lk(state_mutex()); + auto& s = state(); + if (pipe == 0 || s.pipe.load() != pipe) return 0; + HSteamUser cur = s.user.load(); + if (cur != 0) return cur; // idempotent: same global user across calls + s.user.store(1); + return 1; +} + +void release_user(HSteamPipe pipe, HSteamUser user) { + std::lock_guard lk(state_mutex()); + auto& s = state(); + if (pipe == 0 || user == 0) return; + if (s.pipe.load() != pipe || s.user.load() != user) return; + s.user.store(0); + s.logged_on.store(false); +} + +void push_callback(int user, int id, const void* data, size_t n) { + auto& s = state(); + std::lock_guard lk(s.callback_mu); + CallbackMsg m; + m.user = user; + m.id = id; + if (data && n > 0) { + m.body.assign(static_cast(data), + static_cast(data) + n); + } + s.callback_queue.push_back(std::move(m)); +} + +uint64_t alloc_api_call_handle() { + auto& s = state(); + std::lock_guard lk(s.call_results_mu); + uint64_t h = s.next_api_call_handle++; + if (s.next_api_call_handle == 0) s.next_api_call_handle = 1; + return h; +} + +void push_call_result(uint64_t h_call, int callback_id, + const void* data, size_t n, bool io_failure) { + if (h_call == 0) return; + auto& s = state(); + CallResultMsg m; + m.h_call = h_call; + m.callback_id = callback_id; + m.io_failure = io_failure; + if (data && n > 0) { + m.body.assign(static_cast(data), + static_cast(data) + n); + } + { + std::lock_guard lk(s.call_results_mu); + s.call_results_pending[h_call] = std::move(m); + } + callbacks::SteamAPICallCompleted ev{}; + ev.m_hAsyncCall = h_call; + ev.m_iCallback = callback_id; + ev.m_cubParam = static_cast(n); + push_callback(s.user.load(), + callbacks::kSteamAPICallCompleted, + &ev, sizeof(ev)); +} + +void set_logged_on(bool logged_on, int eresult_on_disconnect) { + auto& s = state(); + bool prev = s.logged_on.exchange(logged_on); + s.connected.store(logged_on); + if (prev == logged_on) return; // idempotent — no transition + int h_user = s.user.load(); + if (logged_on) { + push_callback(h_user, callbacks::kSteamServersConnected, nullptr, 0); + } else { + callbacks::SteamServersDisconnected payload{}; + payload.m_eResult = eresult_on_disconnect; + push_callback(h_user, callbacks::kSteamServersDisconnected, + &payload, sizeof(payload)); + } +} + +} // namespace wn_libsteamclient diff --git a/app/src/main/cpp/wn-libsteamclient/src/tcp_services.cpp b/app/src/main/cpp/wn-libsteamclient/src/tcp_services.cpp new file mode 100644 index 000000000..7bea56897 --- /dev/null +++ b/app/src/main/cpp/wn-libsteamclient/src/tcp_services.cpp @@ -0,0 +1,236 @@ +#include "wn_libsteamclient/tcp_services.h" +#include "wn_libsteamclient/callbacks.h" +#include "wn_libsteamclient/runtime_state.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define WN_TAG "WnLibSteamClient" +#define WN_LOGI(...) __android_log_print(ANDROID_LOG_INFO, WN_TAG, __VA_ARGS__) +#define WN_LOGW(...) __android_log_print(ANDROID_LOG_WARN, WN_TAG, __VA_ARGS__) +#define WN_LOGE(...) __android_log_print(ANDROID_LOG_ERROR, WN_TAG, __VA_ARGS__) + +namespace wn_libsteamclient { + +namespace { + +std::once_flag g_start_once; +std::atomic g_accepted_count{0}; +std::atomic g_any_bound{false}; + +int parse_port(const char* env_value, int fallback_port) { + if (!env_value || !*env_value) return fallback_port; + const char* colon = std::strchr(env_value, ':'); + const char* port_str = colon ? colon + 1 : env_value; + char* end = nullptr; + long p = std::strtol(port_str, &end, 10); + if (end == port_str || p <= 0 || p > 65535) { + WN_LOGW("tcp_services: malformed env value \"%s\", falling back to :%d", + env_value, fallback_port); + return fallback_port; + } + return static_cast(p); +} + +int bind_listener(int port, const char* svc_name) { + int fd = ::socket(AF_INET, SOCK_STREAM, 0); + if (fd < 0) { + WN_LOGE("tcp_services[%s]: socket() failed: %s", svc_name, std::strerror(errno)); + return -1; + } + int one = 1; + ::setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); + sockaddr_in addr{}; + addr.sin_family = AF_INET; + addr.sin_port = htons(static_cast(port)); + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); // 127.0.0.1 + if (::bind(fd, reinterpret_cast(&addr), sizeof(addr)) != 0) { + WN_LOGE("tcp_services[%s]: bind(127.0.0.1:%d) failed: %s " + "(port likely already in use — prebuilt libsteamclient.so " + "may still own it from a previous launch cycle)", + svc_name, port, std::strerror(errno)); + ::close(fd); + return -1; + } + if (::listen(fd, 8) != 0) { + WN_LOGE("tcp_services[%s]: listen() failed: %s", svc_name, std::strerror(errno)); + ::close(fd); + return -1; + } + WN_LOGI("tcp_services[%s]: listening on 127.0.0.1:%d (fd=%d)", svc_name, port, fd); + return fd; +} + +std::string hex_dump(const uint8_t* buf, size_t n, size_t max_bytes_in_log = 96) { + size_t shown = std::min(n, max_bytes_in_log); + std::string out; + out.reserve(shown * 3 + 16); + for (size_t i = 0; i < shown; ++i) { + char b[4]; + std::snprintf(b, sizeof(b), "%02x ", buf[i]); + out.append(b); + } + if (!out.empty()) out.pop_back(); // strip trailing space + if (shown < n) { + char tail[32]; + std::snprintf(tail, sizeof(tail), " ...(+%zu B)", n - shown); + out.append(tail); + } + return out; +} + +bool read_exact(int fd, uint8_t* out, size_t n) { + size_t got = 0; + while (got < n) { + ssize_t r = ::recv(fd, out + got, n - got, 0); + if (r > 0) { got += static_cast(r); continue; } + if (r == 0) return false; // EOF + if (errno == EINTR) continue; + return false; // hard error + } + return true; +} + +void handle_connection(int conn_fd, const char* svc_name) { + uint8_t header[4]; + if (!read_exact(conn_fd, header, sizeof(header))) { + WN_LOGI("tcp_services[%s]: conn fd=%d closed before any header", + svc_name, conn_fd); + ::close(conn_fd); + return; + } + uint32_t first_len = + static_cast(header[0]) | + (static_cast(header[1]) << 8) | + (static_cast(header[2]) << 16) | + (static_cast(header[3]) << 24); + const bool framed = (first_len > 0 && first_len <= 256 * 1024); + + if (!framed) { + WN_LOGI("tcp_services[%s]: conn fd=%d raw-stream (first 4B=%s)", + svc_name, conn_fd, hex_dump(header, sizeof(header)).c_str()); + uint8_t chunk[256]; + for (;;) { + ssize_t r = ::recv(conn_fd, chunk, sizeof(chunk), 0); + if (r > 0) { + WN_LOGI("tcp_services[%s]: conn fd=%d raw %zd B: %s", + svc_name, conn_fd, r, + hex_dump(chunk, static_cast(r)).c_str()); + continue; + } + if (r < 0 && errno == EINTR) continue; + break; + } + WN_LOGI("tcp_services[%s]: conn fd=%d closed (raw mode)", svc_name, conn_fd); + ::close(conn_fd); + return; + } + + WN_LOGI("tcp_services[%s]: conn fd=%d framed-mode entry, first body=%u B", + svc_name, conn_fd, first_len); + int frame_idx = 0; + uint32_t length = first_len; + for (;;) { + std::vector body(length); + if (!read_exact(conn_fd, body.data(), length)) { + WN_LOGI("tcp_services[%s]: conn fd=%d EOF mid-frame %d (expected %u B)", + svc_name, conn_fd, frame_idx, length); + break; + } + WN_LOGI("tcp_services[%s]: conn fd=%d frame[%d] %u B: %s", + svc_name, conn_fd, frame_idx, length, + hex_dump(body.data(), body.size()).c_str()); + ++frame_idx; + if (!read_exact(conn_fd, header, sizeof(header))) { + WN_LOGI("tcp_services[%s]: conn fd=%d closed cleanly after %d frame(s)", + svc_name, conn_fd, frame_idx); + break; + } + length = + static_cast(header[0]) | + (static_cast(header[1]) << 8) | + (static_cast(header[2]) << 16) | + (static_cast(header[3]) << 24); + if (length == 0 || length > 1024 * 1024) { + WN_LOGW("tcp_services[%s]: conn fd=%d unreasonable next-frame " + "length=%u after %d frame(s) — closing", svc_name, conn_fd, + length, frame_idx); + break; + } + } + ::close(conn_fd); +} + +void listener_loop(int listen_fd, std::string svc_name) { + for (;;) { + sockaddr_in peer{}; + socklen_t plen = sizeof(peer); + int conn = ::accept(listen_fd, reinterpret_cast(&peer), &plen); + if (conn < 0) { + if (errno == EINTR) continue; + WN_LOGW("tcp_services[%s]: accept() failed: %s — terminating loop " + "(emitting IPCFailure_t kFailurePipeFail)", + svc_name.c_str(), std::strerror(errno)); + namespace cb = wn_libsteamclient::callbacks; + cb::IPCFailure payload{}; + payload.m_eFailureType = cb::kFailurePipeFail; + wn_libsteamclient::push_callback( + wn_libsteamclient::state().user.load(), + cb::kIPCFailure, &payload, sizeof(payload)); + break; + } + g_accepted_count.fetch_add(1, std::memory_order_relaxed); + char peer_ip[INET_ADDRSTRLEN] = {0}; + ::inet_ntop(AF_INET, &peer.sin_addr, peer_ip, sizeof(peer_ip)); + WN_LOGI("tcp_services[%s]: accepted from %s:%u (conn fd=%d, total=%d)", + svc_name.c_str(), peer_ip, ntohs(peer.sin_port), + conn, g_accepted_count.load(std::memory_order_relaxed)); + std::thread(handle_connection, conn, svc_name.c_str()).detach(); + } + ::close(listen_fd); +} + +bool spawn_service(const char* env_key, int fallback_port, const char* svc_name) { + int port = parse_port(::getenv(env_key), fallback_port); + int fd = bind_listener(port, svc_name); + if (fd < 0) return false; + std::thread(listener_loop, fd, std::string(svc_name)).detach(); + g_any_bound.store(true); + return true; +} + +void start_tcp_services_once() { + bool a = spawn_service("Steam3Master", 57343, "Steam3Master"); + bool b = spawn_service("SteamClientService", 57344, "SteamClientService"); + WN_LOGI("tcp_services: Steam3Master=%s SteamClientService=%s", + a ? "OK" : "FAIL", b ? "OK" : "FAIL"); +} + +} // namespace + +bool start_tcp_services() { + std::call_once(g_start_once, start_tcp_services_once); + return g_any_bound.load(); +} + +int accepted_connection_count() { + return g_accepted_count.load(std::memory_order_relaxed); +} + +} // namespace wn_libsteamclient diff --git a/app/src/main/cpp/wn-refactor-size/refactorsize.c b/app/src/main/cpp/wn-refactor-size/refactorsize.c new file mode 100644 index 000000000..c3d64c68b --- /dev/null +++ b/app/src/main/cpp/wn-refactor-size/refactorsize.c @@ -0,0 +1,87 @@ +/* Borderless-fullscreen toggle for the foreground guest window (arg "on"/"off"). + * Build: + * x86_64-w64-mingw32-windres refactorsize.rc -O coff -o refactorsize.res.o + * x86_64-w64-mingw32-gcc -O2 -s -mwindows refactorsize.c refactorsize.res.o -o ../../assets/winnative/refactorsize.exe -luser32 */ +#include +#include + +#define STRIP_STYLES (WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_THICKFRAME) + +typedef struct { + unsigned long long hwnd; + long style; + long left, top, right, bottom; +} SavedState; + +static void state_path(char *buf, int n) { + (void)n; + lstrcpyA(buf, "C:\\ProgramData\\Microsoft\\Windows\\refactorsize.dat"); +} + +static int contains(const char *hay, const char *needle) { + if (!hay) return 0; + for (; *hay; hay++) { + const char *a = hay, *b = needle; + while (*b && *a == *b) { a++; b++; } + if (!*b) return 1; + } + return 0; +} + +int APIENTRY WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmd, int show) { + char path[MAX_PATH + 32]; + state_path(path, sizeof(path)); + + /* Argument is "on" or "off". Tolerate the exe path being prepended: + neither "on" nor "off" occurs in "C:\winnative\refactorsize.exe". */ + int enable = contains(cmd, "on") && !contains(cmd, "off"); + + if (enable) { + HWND hwnd = GetForegroundWindow(); + if (!hwnd) return 1; + + LONG style = GetWindowLong(hwnd, GWL_STYLE); + RECT r; + GetWindowRect(hwnd, &r); + + SavedState s; + s.hwnd = (unsigned long long)(uintptr_t)hwnd; + s.style = (long)style; + s.left = r.left; s.top = r.top; s.right = r.right; s.bottom = r.bottom; + + CreateDirectoryA("C:\\ProgramData\\Microsoft\\Windows", NULL); + HANDLE f = CreateFileA(path, GENERIC_WRITE, 0, NULL, + CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (f != INVALID_HANDLE_VALUE) { + DWORD wrote; + WriteFile(f, &s, sizeof(s), &wrote, NULL); + CloseHandle(f); + } + + SetWindowLong(hwnd, GWL_STYLE, style & ~STRIP_STYLES); + int sw = GetSystemMetrics(SM_CXSCREEN); + int sh = GetSystemMetrics(SM_CYSCREEN); + SetWindowPos(hwnd, HWND_TOP, 0, 0, sw, sh, + SWP_FRAMECHANGED | SWP_NOOWNERZORDER | SWP_SHOWWINDOW); + } else { + HANDLE f = CreateFileA(path, GENERIC_READ, 0, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (f != INVALID_HANDLE_VALUE) { + SavedState s; + DWORD read = 0; + BOOL ok = ReadFile(f, &s, sizeof(s), &read, NULL); + CloseHandle(f); + if (ok && read == sizeof(s)) { + HWND hwnd = (HWND)(uintptr_t)s.hwnd; + if (IsWindow(hwnd)) { + SetWindowLong(hwnd, GWL_STYLE, (LONG)s.style); + SetWindowPos(hwnd, HWND_TOP, s.left, s.top, + s.right - s.left, s.bottom - s.top, + SWP_FRAMECHANGED | SWP_NOOWNERZORDER | SWP_SHOWWINDOW); + } + } + DeleteFileA(path); + } + } + return 0; +} diff --git a/app/src/main/cpp/wn-refactor-size/refactorsize.rc b/app/src/main/cpp/wn-refactor-size/refactorsize.rc new file mode 100644 index 000000000..6e68ceb93 --- /dev/null +++ b/app/src/main/cpp/wn-refactor-size/refactorsize.rc @@ -0,0 +1,29 @@ +#include + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 0,1,1,0 + PRODUCTVERSION 0,1,1,0 + FILEFLAGSMASK 0x3fL + FILEFLAGS 0x0L + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "WinNative" + VALUE "FileDescription", "WinNative Refactor Size" + VALUE "FileVersion", "0.1.1" + VALUE "InternalName", "refactorsize" + VALUE "OriginalFilename", "refactorsize.exe" + VALUE "ProductName", "WinNative" + VALUE "ProductVersion", "0.1.1" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0409, 1200 + END +END diff --git a/app/src/main/cpp/wn-steam-bootstrap/CMakeLists.txt b/app/src/main/cpp/wn-steam-bootstrap/CMakeLists.txt new file mode 100644 index 000000000..a93ef944c --- /dev/null +++ b/app/src/main/cpp/wn-steam-bootstrap/CMakeLists.txt @@ -0,0 +1,45 @@ +# ---------------------------------------------------------------------------- +# wn-steam-bootstrap +# Small JNI shim that loads Valve's native libsteamclient.so in-process so +# Wine's lsteamclient.dll (running inside our Proton prefix) has a peer to +# talk to over the Steam3Master / SteamClientService TCP sockets. +# +# This module is intentionally tiny: +# • C++20, no third-party deps +# • links only `log` (Android logging) and `dl` (dynamic loader) +# • the heavy lifting is whatever libsteamclient.so does when we call +# setenv() + dlopen() + a small handful of SteamWorks entry points +# +# Hidden visibility + section gc + strip in release keep the .so small +# (a couple of KB) — this is *only* the bridge; the actual Steam logic +# lives in libsteamclient.so (downloaded/staged at runtime, NOT bundled +# in jniLibs since it's a Valve binary and we don't redistribute it). +# ---------------------------------------------------------------------------- + +cmake_minimum_required(VERSION 3.22.1) + +project(WnSteamBootstrap CXX) + +add_library(wnsteambootstrap SHARED + src/steam_bootstrap.cpp +) + +target_include_directories(wnsteambootstrap PRIVATE include) + +target_compile_features(wnsteambootstrap PRIVATE cxx_std_20) + +target_compile_options(wnsteambootstrap PRIVATE + -Wall -Wextra + -fvisibility=hidden + -fvisibility-inlines-hidden + -fno-asynchronous-unwind-tables + -fno-unwind-tables + -ffunction-sections + -fdata-sections) + +target_link_options(wnsteambootstrap PRIVATE + -Wl,--gc-sections + -Wl,--exclude-libs,ALL + "$<$>:-Wl,--strip-all>") + +target_link_libraries(wnsteambootstrap PRIVATE log dl) diff --git a/app/src/main/cpp/wn-steam-bootstrap/include/steam_iface.h b/app/src/main/cpp/wn-steam-bootstrap/include/steam_iface.h new file mode 100644 index 000000000..ed047bfe8 --- /dev/null +++ b/app/src/main/cpp/wn-steam-bootstrap/include/steam_iface.h @@ -0,0 +1,133 @@ +#pragma once + +// Minimal pure-C++ shim for the subset of the SteamWorks ABI we need to call +// into libsteamclient.so. The Valve SDK headers (isteamclient.h etc.) are +// publicly distributed but pull in a large transitive include graph and a +// dependency on `steam_api.h` which expects to be linked against +// libsteam_api.so. We don't want that — we want to load libsteamclient.so +// directly and call its CreateInterface() factory. +// +// So we redeclare just the vtables we touch, in the order they appear in +// Valve's published headers, using opaque pointer types where we don't care +// about the contents. ABI MUST match Valve's published headers for the +// interface versions named below — any layout drift will crash on call. +// +// Reference: SteamWorks SDK 1.59 (the latest publicly downloadable as of +// commit time). Interface version strings are matched against Valve's own +// implementation by the CreateInterface factory; pinning to the version +// strings below means "use this exact ABI or fail" — much safer than just +// asking for the newest. +// +// What we use the interfaces for: +// ISteamClient — pipe creation + per-interface lookup +// ISteamUser — pre-logon SetLoginInformation + LogOn driving so +// libsteamclient.so reaches LoggedOn state and Wine's +// lsteamclient.dll can issue authed IPC against it +// ISteamApps — GetAppOwnershipTicket pre-warm (PrepareApp path) +// ISteamRemoteStorage — per-app SetCloudEnabledForApp toggle + +#include + +namespace wnsteambs { + +using HSteamPipe = int32_t; +using HSteamUser = int32_t; + +enum EAccountType : int { + k_EAccountTypeInvalid = 0, + k_EAccountTypeIndividual = 1, + k_EAccountTypeMultiseat = 2, + k_EAccountTypeGameServer = 3, + k_EAccountTypeAnonGameServer = 4, + k_EAccountTypePending = 5, + k_EAccountTypeContentServer = 6, + k_EAccountTypeClan = 7, + k_EAccountTypeChat = 8, + k_EAccountTypeConsoleUser = 9, + k_EAccountTypeAnonUser = 10, +}; + +// Interface version strings — these MUST match what libsteamclient.so +// implements for CreateInterface to return a non-null pointer. +constexpr const char* kSteamClient020 = "SteamClient020"; +constexpr const char* kSteamUser023 = "SteamUser023"; +constexpr const char* kSteamApps008 = "STEAMAPPS_INTERFACE_VERSION008"; +constexpr const char* kSteamRemoteStorage016 = "STEAMREMOTESTORAGE_INTERFACE_VERSION016"; + +// CreateInterface signature exported by libsteamclient.so. Returns a +// pointer to a vtable-only object (Steam interfaces have no data members +// the caller needs to peek at). +using CreateInterfaceFn = void* (*)(const char* version, int* returnCode); + +// ISteamClient — only the methods we actually call. The rest of the vtable +// is irrelevant (we treat the object as opaque between virtual calls). +class ISteamClient { +public: + virtual HSteamPipe CreateSteamPipe() = 0; + virtual bool BReleaseSteamPipe(HSteamPipe pipe) = 0; + virtual HSteamUser ConnectToGlobalUser(HSteamPipe pipe) = 0; + virtual HSteamUser CreateLocalUser(HSteamPipe* pipe_out, EAccountType type) = 0; + virtual void ReleaseUser(HSteamPipe pipe, HSteamUser user) = 0; + virtual void* GetISteamUser(HSteamUser user, HSteamPipe pipe, const char* version) = 0; + virtual void* GetISteamGameServer(HSteamUser, HSteamPipe, const char*) = 0; + virtual void SetLocalIPBinding(uint32_t ip, uint16_t port) = 0; + virtual void* GetISteamFriends(HSteamUser user, HSteamPipe pipe, const char* version) = 0; + virtual void* GetISteamUtils(HSteamPipe pipe, const char* version) = 0; + virtual void* GetISteamMatchmaking(HSteamUser, HSteamPipe, const char*) = 0; + virtual void* GetISteamMatchmakingServers(HSteamUser, HSteamPipe, const char*) = 0; + virtual void* GetISteamGenericInterface(HSteamUser, HSteamPipe, const char*) = 0; + virtual void* GetISteamUserStats(HSteamUser, HSteamPipe, const char*) = 0; + virtual void* GetISteamGameServerStats(HSteamUser, HSteamPipe, const char*) = 0; + virtual void* GetISteamApps(HSteamUser user, HSteamPipe pipe, const char* version) = 0; + virtual void* GetISteamNetworking(HSteamUser, HSteamPipe, const char*) = 0; + virtual void* GetISteamRemoteStorage(HSteamUser user, HSteamPipe pipe, const char* version) = 0; + // ... rest of vtable is unused by us; we never call past this point so + // mismatches in later slots are harmless. +}; + +// ISteamUser — drive logon with a refresh token. The exact method names +// here track the SteamWorks SDK public header for SteamUser023; some method +// signatures depend on private headers and aren't reproduced verbatim. +// For 8b.2's *scaffolding* commit we only need GetSteamID + LoggedOn poll; +// the real logon driving (SetLoginInformation + LogOn) requires private +// vtable entries and lives in the Phase 8b.3 commit where we'll bring in +// the SteamWorks SDK header subset. +class ISteamUser { +public: + virtual HSteamUser GetHSteamUser() = 0; + virtual bool BLoggedOn() = 0; + virtual uint64_t GetSteamID() = 0; + // ... 30+ more slots; unused by 8b.2. +}; + +// ISteamApps — GetAppOwnershipTicket synchronous prewarm. +class ISteamApps { +public: + virtual bool BIsSubscribed() = 0; + virtual bool BIsLowViolence() = 0; + virtual bool BIsCybercafe() = 0; + virtual bool BIsVACBanned() = 0; + virtual const char* GetCurrentGameLanguage() = 0; + virtual const char* GetAvailableGameLanguages() = 0; + virtual bool BIsSubscribedApp(uint32_t app_id) = 0; + virtual bool BIsDlcInstalled(uint32_t app_id) = 0; + virtual uint32_t GetEarliestPurchaseUnixTime(uint32_t app_id) = 0; + virtual bool BIsSubscribedFromFreeWeekend() = 0; + virtual int GetDLCCount(uint32_t app_id) = 0; + virtual bool BGetDLCDataByIndex(uint32_t app_id, int index, uint32_t* app_id_out, bool* avail_out, char* name_out, int name_size) = 0; + virtual void InstallDLC(uint32_t app_id) = 0; + virtual void UninstallDLC(uint32_t app_id) = 0; + virtual void RequestAppProofOfPurchaseKey(uint32_t app_id) = 0; + virtual bool GetCurrentBetaName(char* name_out, int name_size) = 0; + virtual bool MarkContentCorrupt(bool missing_files_only) = 0; + virtual uint32_t GetInstalledDepots(uint32_t app_id, uint32_t* depots_out, uint32_t max_depots) = 0; + virtual uint32_t GetAppInstallDir(uint32_t app_id, char* folder_out, uint32_t folder_size) = 0; + virtual bool BIsAppInstalled(uint32_t app_id) = 0; + // ... GetAppOwnershipTicket is further down; we'll lock the offset in 8b.3. +}; + +// ISteamRemoteStorage — SetCloudEnabledForApp lives near the top of the +// vtable in v016 but the exact slot needs the SDK header subset in 8b.3. +class ISteamRemoteStorage {}; + +} // namespace wnsteambs diff --git a/app/src/main/cpp/wn-steam-bootstrap/src/steam_bootstrap.cpp b/app/src/main/cpp/wn-steam-bootstrap/src/steam_bootstrap.cpp new file mode 100644 index 000000000..595463c97 --- /dev/null +++ b/app/src/main/cpp/wn-steam-bootstrap/src/steam_bootstrap.cpp @@ -0,0 +1,1872 @@ +// wn-steam-bootstrap — JNI bridge that loads Valve's native +// libsteamclient.so in our Android process so Wine's lsteamclient.dll +// (running inside the Proton prefix) has a peer to talk to over the +// Steam3Master / SteamClientService TCP listeners that libsteamclient.so +// stands up internally when it sees those env vars. +// +// JNI surface (mirrors the Kotlin object WnSteamBootstrap.kt): +// +// nativeInit(context, libPath, home, steam3Master, steamClientService, +// extraEnv[], accountName, refreshToken, steamId64) → int +// 0 on success +// -1 binary missing at libPath +// -2 dlopen failed (see logcat for dlerror) +// -3 CreateInterface failed (libsteamclient.so present but +// didn't expose the expected SteamClient020 interface) +// -4 steam pipe / global user setup failed +// +// nativeShutdown() — tear down the pipe, ReleaseUser +// nativePrepareApp(parent, dlcs[]) — kick GetAppOwnershipTicket warmups +// nativeSetCloudEnabled(app, on) — toggle per-app cloud sync +// +// Implementation notes: +// • setenv() runs BEFORE dlopen() — libsteamclient.so reads the +// IPC endpoint env vars (Steam3Master / SteamClientService) at module +// init and binds the listening sockets there. Setting them later is a +// no-op. +// • RTLD_GLOBAL on the dlopen so the loaded .so's exports are visible +// to anything else that might later dlopen it (Wine's loader paths +// occasionally do). +// • All steam-side calls are guarded by g_sc != nullptr — if the +// libsteamclient.so binary isn't on disk yet (we don't bundle it), +// init returns -1 cleanly and Prepare/Shutdown are no-ops. This lets +// the rest of the launcher proceed (game will run without +// online-play, DLC checks fail soft) instead of crashing. +// • SteamWorks vtable layouts are taken from the public SDK 1.59 +// headers (steam_iface.h in this module). Drift between SDK versions +// would crash on virtual call — pin via interface version strings. + +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "steam_iface.h" + +namespace { + +constexpr const char* kLogTag = "WnSteamBoot"; +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, kLogTag, __VA_ARGS__) +#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, kLogTag, __VA_ARGS__) +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, kLogTag, __VA_ARGS__) + +// State held for the lifetime of the loaded libsteamclient.so. +// +// Verified by decompiling the reference embedded-Steam bootstrap with +// Ghidra (libsteambootstrap.so nativeInit at 0x14a8). The correct dance: +// +// 1. dlopen(libsteamclient.so, RTLD_NOW). Don't bother with explicit +// SteamService_StartThread — libsteamclient.so handles steamservice +// lifecycle internally (it dlopens steamservice.so itself when it +// needs it; we just preload it so the loader namespace knows it). +// 2. Steam_CreateGlobalUser(&pipe_out) — flat C function. Creates the +// Steam pipe AND connects a global user in one call. Returns the +// user handle and fills the pipe out-param. This is the correct +// entry point on Android — Steam_CreateSteamPipe() returns 0 +// because it expects the legacy fork/exec helper-child model. +// 3. CreateInterface("CLIENTENGINE_INTERFACE_VERSION005", &err) → engine +// This is the internal Valve IClientEngine. Public SteamClient020 +// interface is unsuitable for the refresh-token login path. +// 4. engine->vtable[8](user, pipe) → IClientUser sub-interface +// slot 49 (offset 0x188): bool IsAccountLoggedIn(account) +// slot 54 (offset 0x1B0): SetLoginInformation(account, password, remember) +// slot 56 (offset 0x1C0): LogonWithRefreshToken(refreshToken, account) +// slot 50 (offset 0x190): SetAccount(account, 1) [already-logged path] +// slot 1 (offset 0x08): SetSteamID(steamId64) +// 6. Poll loop, up to 100×100ms=10s: +// drain pending callbacks via Steam_BGetCallback/FreeLastCallback +// if Steam_BLoggedOn(pipe, user) — DONE +struct State { + ~State() { + pump_running.store(false, std::memory_order_release); + if (pump_thread.joinable()) pump_thread.join(); + } + + std::mutex mu; + void* lsc_handle = nullptr; // libsteamclient.so dlopen handle + bool initialized = false; + bool shutting_down = false; + + // libsteamclient.so flat C entry points (resolved via dlsym). + wnsteambs::CreateInterfaceFn fn_CreateInterface = nullptr; + int (*fn_Steam_CreateGlobalUser)(int* pipe_inout)= nullptr; + bool (*fn_Steam_BLoggedOn)(int pipe, int user) = nullptr; + void (*fn_Steam_LogOff)(int pipe, int user) = nullptr; + void (*fn_Steam_ReleaseUser)(int pipe, int user) = nullptr; + bool (*fn_Steam_BReleaseSteamPipe)(int pipe) = nullptr; + bool (*fn_Steam_BGetCallback)(int pipe, void* cb) = nullptr; + void (*fn_Steam_FreeLastCallback)(int pipe) = nullptr; + void (*fn_Breakpad_SteamSetAppID)(unsigned app_id)= nullptr; + + // Live pipe + global user handles. + int pipe = 0; + int user = 0; + + // Cached IClientUser pointer (returned from IClientEngine vtable[8]). + // We hold it across the session so prepareApp / setCloudEnabled + // can route through the same sub-interface if needed. Owned by + // libsteamclient.so — do NOT free. + void* iclient_user = nullptr; + + void* steamclient_iface = nullptr; // SteamClient020 + void* isteam_user = nullptr; // SteamUser023 + void* isteam_utils = nullptr; // SteamUtils010 + void* isteam_userstats = nullptr; // STEAMUSERSTATS_INTERFACE_VERSION013 + void* isteam_apps = nullptr; // ISteamApps (best matching ver) + void* isteam_remotestorage = nullptr; // ISteamRemoteStorage (best ver) + void* isteam_friends = nullptr; // ISteamFriends (best matching ver) + const char* isteam_apps_ver = nullptr; + const char* isteam_rs_ver = nullptr; + const char* isteam_friends_ver = nullptr; + + uint64_t cached_steam_id = 0; + + // Every env key nativeInit setenv()'d, recorded so nativeShutdown + // can unsetenv() them. Otherwise a bionic→real-Steam mode switch in + // the same process inherits leaked WINESTEAMCLIENTPATH / Steam3Master / + // SteamUser etc. into the real-Steam wine subprocess. + std::vector applied_env_keys; + + // Persistent callback pump. libsteamclient.so's logon — and every + // later Steam API round-trip the game makes — is message-driven: it + // only advances while something drains Steam_BGetCallback. start()'s + // 10s poll does that during init, but the session must keep being + // pumped while initialized. nativeShutdown stops and joins the thread + // before clearing the libsteamclient function pointers. + std::atomic pump_running{false}; + std::thread pump_thread; + + std::condition_variable cv_callback; + std::set subscribed_ids; + std::map> received_callbacks; +}; +State g_state; + +// IClientEngine vtable slots we call (offsets in bytes; aarch64 ABI has +// 8-byte fn ptrs). All confirmed by Ghidra decomp of reference bootstrap. +constexpr int kVtClientEngine_GetIClientUser = 0x40; // returns sub-iface + +constexpr int kVtClientUser_SetSteamID = 0x08; +constexpr int kVtClientUser_IsAccountLoggedIn = 0x188; // bool IsAccountLoggedIn(const char*) +constexpr int kVtClientUser_SetAccount = 0x190; // (already-logged path) +constexpr int kVtClientUser_SetLoginInformation = 0x1B0; // (account, "", remember) +constexpr int kVtClientUser_LogonWithRefresh = 0x1C0; // 2-arg (refreshToken, account) + +// Persistent callback-pump loop. Runs on a detached thread for the +// lifetime of the process: libsteamclient.so only advances its logon +// state machine — and processes every later Steam API response — while +// Steam_BGetCallback is being drained. start()'s init poll does this for +// the first 10s; this thread takes over so the session does not stall +// the moment start() returns (the bug that left Bionic games offline). +void callback_pump_loop() { + char cb_buf[64] = {0}; // CallbackMsg_t header; 64 is safe headroom + bool announced_logon = false; + int ticks = 0; + int cb_logged = 0; + while (g_state.pump_running.load(std::memory_order_acquire)) { + if (g_state.fn_Steam_BGetCallback && g_state.fn_Steam_FreeLastCallback) { + while (g_state.fn_Steam_BGetCallback(g_state.pipe, cb_buf)) { + int cb_id = *reinterpret_cast(cb_buf + 4); + void* cb_data = *reinterpret_cast(cb_buf + 8); + int cb_size = *reinterpret_cast(cb_buf + 16); + if (cb_logged < 120) { + LOGI("pump callback id=%d size=%d", cb_id, cb_size); + ++cb_logged; + } + bool wake = false; + { + std::lock_guard lk(g_state.mu); + if (g_state.subscribed_ids.count(cb_id)) { + auto& slot = g_state.received_callbacks[cb_id]; + if (cb_data && cb_size > 0) { + slot.assign( + reinterpret_cast(cb_data), + reinterpret_cast(cb_data) + cb_size); + } else { + slot.clear(); + } + wake = true; + } + } + if (wake) g_state.cv_callback.notify_all(); + g_state.fn_Steam_FreeLastCallback(g_state.pipe); + } + } + // Announce the logon transition so the launch log shows whether + // the session ever authenticates after start() returns. + if (!announced_logon && g_state.fn_Steam_BLoggedOn && + g_state.fn_Steam_BLoggedOn(g_state.pipe, g_state.user)) { + announced_logon = true; + LOGI("callback pump: session is now LOGGED ON (after %d ticks)", + ticks); + } + ++ticks; + ::usleep(20 * 1000); // ~50Hz, matching the Steam client's tick + } +} + +// Helper: pull a UTF-8 String from a jstring without leaking on the +// throw-path. Returns empty string for null. +std::string jstr(JNIEnv* env, jstring s) { + if (!s) return {}; + const char* c = env->GetStringUTFChars(s, nullptr); + if (!c) return {}; + std::string out(c); + env->ReleaseStringUTFChars(s, c); + return out; +} + +std::string android_files_dir(JNIEnv* env, jobject context) { + if (!context) return {}; + jclass ctxCls = env->GetObjectClass(context); + if (!ctxCls) return {}; + jmethodID mGetFilesDir = + env->GetMethodID(ctxCls, "getFilesDir", "()Ljava/io/File;"); + env->DeleteLocalRef(ctxCls); + if (!mGetFilesDir) return {}; + jobject fileObj = env->CallObjectMethod(context, mGetFilesDir); + if (!fileObj) return {}; + std::string out; + jclass fileCls = env->GetObjectClass(fileObj); + if (fileCls) { + jmethodID mGetPath = + env->GetMethodID(fileCls, "getAbsolutePath", "()Ljava/lang/String;"); + if (mGetPath) { + jstring js = static_cast( + env->CallObjectMethod(fileObj, mGetPath)); + out = jstr(env, js); + if (js) env->DeleteLocalRef(js); + } + env->DeleteLocalRef(fileCls); + } + env->DeleteLocalRef(fileObj); + return out; +} + +// Apply a Java String[] of "KEY=value" pairs (or alternating key/value +// slots, depending on the Kotlin convention we settled on) via setenv(). +// We accept both shapes: if a slot contains '=' we split on the first one, +// otherwise we pair slot i (key) with slot i+1 (value). +void apply_extra_env(JNIEnv* env, jobjectArray array) { + if (!array) return; + jsize n = env->GetArrayLength(array); + for (jsize i = 0; i < n; ++i) { + jstring slot = static_cast(env->GetObjectArrayElement(array, i)); + if (!slot) continue; + std::string s = jstr(env, slot); + env->DeleteLocalRef(slot); + if (s.empty()) continue; + auto eq = s.find('='); + if (eq != std::string::npos && eq > 0) { + std::string k = s.substr(0, eq); + std::string v = s.substr(eq + 1); + ::setenv(k.c_str(), v.c_str(), /*overwrite*/ 1); + g_state.applied_env_keys.push_back(k); + LOGI("setenv %s=%s", k.c_str(), v.c_str()); + } + } +} + +// Try to dlopen the staged libsteamclient.so at libPath. Returns null on +// failure — caller logs and bails. RTLD_GLOBAL so subsequent dlopens +// (Wine's loader poking around our process) can see its exports. +void* try_dlopen(const std::string& libPath) { + if (::access(libPath.c_str(), R_OK) != 0) { + LOGW("libsteamclient.so not present at %s; skipping native load. " + "Online-play / overlay features will be unavailable; basic " + "launch paths still work via our PICS / ticket cache.", + libPath.c_str()); + return nullptr; + } + // RTLD_NOW resolves all symbols up front so we crash here (with a + // diagnostic) instead of crashing later on a missing virtual. + void* h = ::dlopen(libPath.c_str(), RTLD_NOW | RTLD_GLOBAL); + if (!h) { + LOGE("dlopen(%s) failed: %s", libPath.c_str(), ::dlerror()); + } + return h; +} + +// libsteamclient.so does NOT statically NEED its siblings — readelf shows +// only libandroid/liblog/libm/libdl/libc in its NEEDED list. It dlopens +// steamservice.so, libsteamnetworkingsockets.so, libtier0_s.so and +// libvstdlib_s.so internally. On Android the app's restricted linker +// namespace won't find them under /data/.../imagefs/usr/lib/ unless we +// either (a) set LD_LIBRARY_PATH (honored by Bionic's loader inside the +// app namespace) or (b) preload them with RTLD_GLOBAL so they're already +// in the global symbol table when libsteamclient.so calls dlopen. +// +// Belt + suspenders: do both. Sibling preload order matters — tier0 first +// (base layer), then vstdlib (depends on tier0), then networking sockets + +// steamservice (depend on the lower two). Each failure is logged but +// non-fatal: libsteamclient.so might still init in a degraded mode. +void preload_steam_runtime_siblings(const std::string& lib_dir) { + constexpr const char* kSiblings[] = { + "libtier0_s.so", + "libvstdlib_s.so", + "libsteamnetworkingsockets.so", + "steamservice.so", + }; + for (const char* name : kSiblings) { + std::string path = lib_dir + "/" + name; + if (::access(path.c_str(), R_OK) != 0) { + LOGW("preload skip: %s not present", path.c_str()); + continue; + } + void* h = ::dlopen(path.c_str(), RTLD_NOW | RTLD_GLOBAL); + if (h) { + LOGI("preload OK: %s", path.c_str()); + } else { + LOGW("preload FAIL: %s — %s", path.c_str(), ::dlerror()); + } + } +} + +// Return the directory portion of a path (everything before the last '/'). +// Falls back to "." when no slash is found. +std::string dirname_of(const std::string& path) { + auto slash = path.rfind('/'); + if (slash == std::string::npos) return "."; + return path.substr(0, slash); +} + +// Create a directory + every missing parent. mkdir(2) doesn't recurse; we +// walk the path char-by-char and mkdir each component. Existing dirs are +// not an error. Failures other than EEXIST are logged. +void mkdir_p(const std::string& path, mode_t mode) { + std::string acc; + acc.reserve(path.size()); + for (size_t i = 0; i <= path.size(); ++i) { + if (i == path.size() || path[i] == '/') { + if (acc.empty()) { if (i < path.size()) acc.push_back(path[i]); continue; } + if (::mkdir(acc.c_str(), mode) != 0 && errno != EEXIST) { + LOGW("mkdir(%s) failed: %s", acc.c_str(), std::strerror(errno)); + } + } + if (i < path.size()) acc.push_back(path[i]); + } +} + +// Stage the Steam config dir + empty config/local VDFs. libsteamclient.so's +// CreateSteamPipe path stats /Steam/config/{config,local}.vdf at +// init; missing files cause it to bail without an obvious error. Other +// embedded-Steam bootstraps symlink session files in from a persistence +// dir — we just create empty stubs so the stat succeeds. Whatever ends up +// in them is governed by libsteamclient.so itself once it starts writing. +void stage_steam_config_dir(const std::string& home) { + if (home.empty()) return; + const std::string steam_dir = home + "/Steam"; + const std::string config_dir = steam_dir + "/config"; + mkdir_p(steam_dir, 0755); + mkdir_p(config_dir, 0755); + for (const char* name : {"config.vdf", "local.vdf"}) { + std::string p = config_dir + "/" + name; + struct stat st{}; + if (::stat(p.c_str(), &st) == 0) continue; // already there + int fd = ::open(p.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC, 0644); + if (fd < 0) { + LOGW("create %s failed: %s", p.c_str(), std::strerror(errno)); + } else { + ::close(fd); + LOGI("staged empty %s", p.c_str()); + } + } +} + +} // namespace + +// ============================================================================= +// JNI entry points — names match Kotlin's @JvmStatic external fun convention +// ============================================================================= +extern "C" { + +JNIEXPORT jint JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeInit( + JNIEnv* env, jclass /*cls*/, jobject context, + jstring jlibPath, jstring jhome, + jstring jsteam3Master, jstring jsteamClientService, + jobjectArray jextraEnv, + jstring jaccountName, jstring jrefreshToken, jlong jsteamId64, + jint jappId) { + std::unique_lock lk(g_state.mu); + if (g_state.shutting_down) { + LOGW("nativeInit: shutdown in progress"); + return -6; + } + if (g_state.initialized) { + LOGI("nativeInit: already initialized (lsc=%p pipe=%d user=%d)", + g_state.lsc_handle, g_state.pipe, g_state.user); + return 0; + } + + const std::string libPath = jstr(env, jlibPath); + const std::string home = jstr(env, jhome); + const std::string s3m = jstr(env, jsteam3Master); + const std::string scs = jstr(env, jsteamClientService); + const std::string user = jstr(env, jaccountName); + const std::string token = jstr(env, jrefreshToken); + const uint64_t steamId = static_cast(jsteamId64); + g_state.cached_steam_id = steamId; + + LOGI("nativeInit: libPath=%s home=%s steam3Master=%s " + "steamClientService=%s user=%s tokenLen=%zu steamId=%llu", + libPath.c_str(), home.c_str(), s3m.c_str(), scs.c_str(), + user.c_str(), token.size(), + static_cast(steamId)); + + // ---- setenv pass BEFORE dlopen ---- + // applied_env_keys accumulates every key we setenv() in this init so + // nativeShutdown can unsetenv() them on teardown. + g_state.applied_env_keys.clear(); + apply_extra_env(env, jextraEnv); + if (!home.empty()) { + ::setenv("HOME", home.c_str(), 1); + g_state.applied_env_keys.emplace_back("HOME"); + std::string state_dir = "/tmp"; + if (home.size() >= 5 && home.compare(home.size() - 5, 5, "/home") == 0) { + state_dir = home.substr(0, home.size() - 4) + "tmp"; + } else if (auto slash = home.rfind("/home/"); slash != std::string::npos) { + state_dir = home.substr(0, slash) + "/tmp"; + } + ::mkdir(state_dir.c_str(), 0755); // best-effort; ok if exists + ::setenv("WN_STATE_DIR", state_dir.c_str(), 1); + g_state.applied_env_keys.emplace_back("WN_STATE_DIR"); + LOGI("nativeInit: WN_STATE_DIR=%s", state_dir.c_str()); + } + if (!s3m.empty()) { + ::setenv("Steam3Master", s3m.c_str(), 1); + g_state.applied_env_keys.emplace_back("Steam3Master"); + } + if (jappId > 0) { + char app_buf[16]; + std::snprintf(app_buf, sizeof(app_buf), "%d", static_cast(jappId)); + ::setenv("SteamAppId", app_buf, 1); + ::setenv("SteamGameId", app_buf, 1); + g_state.applied_env_keys.emplace_back("SteamAppId"); + g_state.applied_env_keys.emplace_back("SteamGameId"); + LOGI("nativeInit: SteamAppId=%s (caller-supplied)", app_buf); + } else { + LOGI("nativeInit: appId=0 — no SteamAppId env set " + "(library/prewarm mode; ISteamApps/ISteamRemoteStorage/" + "ISteamUserStats will instantiate as null)"); + } + if (!scs.empty()) { + ::setenv("SteamClientService", scs.c_str(), 1); + g_state.applied_env_keys.emplace_back("SteamClientService"); + } + + if (!::getenv("STEAM_SSL_CERT_FILE")) { + const std::string files_dir = android_files_dir(env, context); + if (!files_dir.empty()) { + const std::string ca = files_dir + "/wnsteam_cacert.pem"; + struct stat st{}; + if (::stat(ca.c_str(), &st) == 0 && st.st_size > 0) { + ::setenv("STEAM_SSL_CERT_FILE", ca.c_str(), 1); + g_state.applied_env_keys.emplace_back("STEAM_SSL_CERT_FILE"); + LOGI("setenv STEAM_SSL_CERT_FILE=%s", ca.c_str()); + } else { + LOGW("STEAM_SSL_CERT_FILE not set: %s missing/empty — call " + "CaBundleExtractor.ensureBundle() before start() or " + "libsteamclient.so TLS logon may fail", ca.c_str()); + } + } + } + + // LD_LIBRARY_PATH — honored by Bionic for in-namespace dlopens. The + // path is the parent of libPath (everything libsteamclient.so will + // dlopen lives next to it). + const std::string lib_dir = dirname_of(libPath); + ::setenv("LD_LIBRARY_PATH", lib_dir.c_str(), 1); + g_state.applied_env_keys.emplace_back("LD_LIBRARY_PATH"); + LOGI("setenv LD_LIBRARY_PATH=%s", lib_dir.c_str()); + + // Preload runtime siblings with RTLD_GLOBAL so libsteamclient.so's + // later internal dlopens find them in the global symbol namespace. + preload_steam_runtime_siblings(lib_dir); + + // Stage /Steam/config/{config,local}.vdf as empty files. + // libsteamclient.so's CreateSteamPipe path stat's these and bails + // without an obvious error when missing. Other embedded-Steam + // bootstraps symlink real session-state files in; empty stubs are + // enough for the stat to succeed on a fresh launch. + stage_steam_config_dir(home); + + // ------------------------------------------------------------------- + // + typedef void* (*SteamService_StartThread_fn)(const char*); + auto svc_start = reinterpret_cast( + ::dlsym(RTLD_DEFAULT, "SteamService_StartThread")); + if (svc_start) { + void* svc_ctx = svc_start("SteamClientService"); + LOGI("SteamService_StartThread(\"SteamClientService\") -> %p", svc_ctx); + } else { + LOGW("SteamService_StartThread symbol not in global namespace — " + "preload may have failed; wine guest may crash on CreateInterface"); + } + + // ------------------------------------------------------------------- + void* lsc = try_dlopen(libPath); + if (!lsc) return -1; + LOGI("dlopen(libsteamclient.so) OK handle=%p", lsc); + + g_state.fn_CreateInterface = reinterpret_cast( + ::dlsym(lsc, "CreateInterface")); + g_state.fn_Steam_CreateGlobalUser = reinterpret_cast( + ::dlsym(lsc, "Steam_CreateGlobalUser")); + g_state.fn_Steam_BLoggedOn = reinterpret_cast( + ::dlsym(lsc, "Steam_BLoggedOn")); + g_state.fn_Steam_LogOff = reinterpret_cast( + ::dlsym(lsc, "Steam_LogOff")); + g_state.fn_Steam_ReleaseUser = reinterpret_cast( + ::dlsym(lsc, "Steam_ReleaseUser")); + g_state.fn_Steam_BReleaseSteamPipe = reinterpret_cast( + ::dlsym(lsc, "Steam_BReleaseSteamPipe")); + g_state.fn_Steam_BGetCallback = reinterpret_cast( + ::dlsym(lsc, "Steam_BGetCallback")); + g_state.fn_Steam_FreeLastCallback = reinterpret_cast( + ::dlsym(lsc, "Steam_FreeLastCallback")); + g_state.fn_Breakpad_SteamSetAppID = reinterpret_cast( + ::dlsym(lsc, "Breakpad_SteamSetAppID")); + + if (!g_state.fn_CreateInterface || !g_state.fn_Steam_CreateGlobalUser) { + LOGE("dlsym of required entry points failed: CreateInterface=%p " + "Steam_CreateGlobalUser=%p", + reinterpret_cast(g_state.fn_CreateInterface), + reinterpret_cast(g_state.fn_Steam_CreateGlobalUser)); + return -3; + } + LOGI("dlsym OK: CreateInterface=%p Steam_CreateGlobalUser=%p " + "Steam_BLoggedOn=%p Steam_ReleaseUser=%p " + "Steam_BReleaseSteamPipe=%p Steam_BGetCallback=%p", + reinterpret_cast(g_state.fn_CreateInterface), + reinterpret_cast(g_state.fn_Steam_CreateGlobalUser), + reinterpret_cast(g_state.fn_Steam_BLoggedOn), + reinterpret_cast(g_state.fn_Steam_ReleaseUser), + reinterpret_cast(g_state.fn_Steam_BReleaseSteamPipe), + reinterpret_cast(g_state.fn_Steam_BGetCallback)); + + if (g_state.fn_Breakpad_SteamSetAppID) { + g_state.fn_Breakpad_SteamSetAppID(0); + } + + // ------------------------------------------------------------------- + // STEP 2 — Steam_CreateGlobalUser. Creates the Steam pipe AND + // connects the global user in one call. This is the correct entry + // point on Android; Steam_CreateSteamPipe()'s legacy fork/exec + // helper-child path returns 0 here. + // ------------------------------------------------------------------- + int pipe_out = 0; + int user_h = g_state.fn_Steam_CreateGlobalUser(&pipe_out); + if (user_h == 0 || pipe_out == 0) { + LOGE("Steam_CreateGlobalUser failed: user=%d pipe_out=%d", + user_h, pipe_out); + return -4; + } + LOGI("Steam_CreateGlobalUser OK pipe=%d user=%d", pipe_out, user_h); + + g_state.lsc_handle = lsc; + g_state.pipe = pipe_out; + g_state.user = user_h; + g_state.initialized = true; + + // ------------------------------------------------------------------- + // STEP 3 — Refresh-token login via IClientEngine (optional). + // + // CreateInterface("CLIENTENGINE_INTERFACE_VERSION005", &err) → engine + // IClientUser_vt: + // + // If we don't have credentials, libsteamclient.so will sit at + // "connected but not logged on" — Wine IPC still functions for the + // non-authenticated SteamWorks calls. + // ------------------------------------------------------------------- + if (!user.empty() && !token.empty() && steamId != 0) { + int err = 0; + void* engine = g_state.fn_CreateInterface( + "CLIENTENGINE_INTERFACE_VERSION005", &err); + if (engine && err == 0) { + // vtable pointer is the first 8 bytes of the object. + long* engine_vt = *reinterpret_cast(engine); + + // engine_vt[8] = GetIClientUser(user, pipe). + using GetIClientUserFn = void* (*)(void*, int, int); + auto get_iclient_user = reinterpret_cast( + engine_vt[kVtClientEngine_GetIClientUser / 8]); + void* iuser = get_iclient_user(engine, user_h, pipe_out); + LOGI("IClientEngine.GetIClientUser(user=%d, pipe=%d) -> %p", + user_h, pipe_out, iuser); + + if (iuser) { + g_state.iclient_user = iuser; + long* iuser_vt = *reinterpret_cast(iuser); + + using IsAccountLoggedInFn = bool (*)(void*, const char*); + auto is_logged = reinterpret_cast( + iuser_vt[kVtClientUser_IsAccountLoggedIn / 8]); + bool already = is_logged(iuser, user.c_str()); + LOGI("IClientUser.IsAccountLoggedIn(%s) = %d", user.c_str(), + already ? 1 : 0); + + bool auto_logged_on = false; + if (already && g_state.fn_Steam_BLoggedOn) { + constexpr int kAutoPollMax = 30; // 30 × 100ms = 3s + for (int i = 0; i < kAutoPollMax; ++i) { + if (g_state.fn_Steam_BLoggedOn(g_state.pipe, g_state.user)) { + auto_logged_on = true; + LOGI("Auto-logon from cached session OK after %dx100ms — " + "skipping forced LogonWithRefreshToken (avoids the " + "stale-token AccessDenied)", i + 1); + break; + } + ::usleep(100 * 1000); + } + } + using SetSteamIDFn = void (*)(void*, uint64_t); + auto set_sid = reinterpret_cast( + iuser_vt[kVtClientUser_SetSteamID / 8]); + if (auto_logged_on) { + set_sid(iuser, steamId); + LOGI("IClientUser.SetSteamID(%llu) called (post auto-logon)", + static_cast(steamId)); + } else { + LOGI("No cached session usable; driving forced " + "LogonWithRefreshToken with the stored token " + "(account-known=%d)", already ? 1 : 0); + + using SetLoginInfoFn = void (*)(void*, const char*, const char*, int); + auto set_login = reinterpret_cast( + iuser_vt[kVtClientUser_SetLoginInformation / 8]); + set_login(iuser, user.c_str(), "", 1); + LOGI("IClientUser.SetLoginInformation(%s, \"\", 1) called", + user.c_str()); + + using LogonRefreshFn = void (*)(void*, const char*, const char*); + auto logon = reinterpret_cast( + iuser_vt[kVtClientUser_LogonWithRefresh / 8]); + logon(iuser, token.c_str(), user.c_str()); + LOGI("IClientUser.LogonWithRefreshToken called (token=%zu bytes)", + token.size()); + + set_sid(iuser, steamId); + LOGI("IClientUser.SetSteamID(%llu) called", + static_cast(steamId)); + } + } + } else { + LOGW("CreateInterface(CLIENTENGINE_INTERFACE_VERSION005) -> %p (err=%d)", + engine, err); + } + } else { + LOGI("no credentials provided - skipping refresh-token login"); + } + + // ------------------------------------------------------------------- + // STEP 4 — Poll Steam_BLoggedOn until logged on (up to 10s). Drain + // pending callbacks between polls so libsteamclient.so can process + // server responses + state transitions. + // ------------------------------------------------------------------- + constexpr int kMaxPolls = 100; + constexpr int kPollUsec = 100 * 1000; // 100ms each + bool logged_on = false; + int polls = 0; + char cb_buf[64] = {0}; // CCallbackBase header; 64 is safe + for (; polls < kMaxPolls; ++polls) { + if (g_state.fn_Steam_BGetCallback && g_state.fn_Steam_FreeLastCallback) { + while (g_state.fn_Steam_BGetCallback(pipe_out, cb_buf)) { + // CallbackMsg_t: m_hSteamUser@0, m_iCallback@4. The id tells + // us where the logon stalls (101 SteamServersConnected, + // 102 SteamServerConnectFailure, 113 SteamServersDisconnected, + // 3 LogonResponse-class, etc.). + int cb_id = *reinterpret_cast(cb_buf + 4); + // CallbackMsg_t: m_pubParam (the typed payload) @ offset 8. + // id 102 = SteamServerConnectFailure_t { EResult m_eResult; + // bool m_bStillRetrying; } — its EResult says network (3 = + // NoConnection / 16 = Timeout) vs auth (5 InvalidPassword, + // 6 LoggedInElsewhere, 7 InvalidProtocolVer, 65 Expired...). + if (cb_id == 102) { + void* p = *reinterpret_cast(cb_buf + 8); + LOGE("init-poll: SteamServerConnectFailure EResult=%d retrying=%d", + p ? *reinterpret_cast(p) : -1, + p ? *reinterpret_cast( + reinterpret_cast(p) + 4) : 0); + } else { + LOGI("init-poll callback id=%d", cb_id); + } + g_state.fn_Steam_FreeLastCallback(pipe_out); + } + } + if (g_state.fn_Steam_BLoggedOn && + g_state.fn_Steam_BLoggedOn(pipe_out, user_h)) { + logged_on = true; + break; + } + ::usleep(kPollUsec); + } + LOGI("Steam_BLoggedOn poll: logged_on=%d after %dx100ms", + logged_on ? 1 : 0, polls); + + // Hand off to the persistent callback pump. The init poll above ran + // single-threaded; only now that it has finished is it safe to let a + // background thread own Steam_BGetCallback. Without this the logon + // stalls the moment start() returns — Bionic games launch but never + // authenticate (online play / DLC stay broken). + if (!g_state.pump_running.exchange(true, std::memory_order_acq_rel)) { + if (g_state.pump_thread.joinable()) g_state.pump_thread.join(); + g_state.pump_thread = std::thread(callback_pump_loop); + LOGI("callback pump thread started (libsteamclient session kept live)"); + using StartFn = void (*)(void); + auto start = reinterpret_cast( + ::dlsym(g_state.lsc_handle, "wn_cm_bridge_start_state_sync_poller")); + if (start != nullptr) { + start(); + LOGI("cross-process state-sync poller started"); + } else { + LOGW("wn_cm_bridge_start_state_sync_poller not found: %s", + ::dlerror()); + } + } + + { + int sc_err = 0; + void* steamclient = g_state.fn_CreateInterface("SteamClient020", &sc_err); + LOGI("Stage2: CreateInterface(SteamClient020) -> %p err=%d logged_on=%d", + steamclient, sc_err, logged_on ? 1 : 0); + if (steamclient && sc_err == 0) { + g_state.steamclient_iface = nullptr; + g_state.isteam_user = nullptr; + g_state.isteam_utils = nullptr; + g_state.isteam_userstats = nullptr; + g_state.isteam_apps = nullptr; + g_state.isteam_remotestorage = nullptr; + g_state.isteam_friends = nullptr; + g_state.isteam_apps_ver = nullptr; + g_state.isteam_rs_ver = nullptr; + g_state.isteam_friends_ver = nullptr; + + g_state.steamclient_iface = steamclient; + long* sc_vt = *reinterpret_cast(steamclient); + + using GetUtilsFn = void* (*)(void*, int, const char*); + auto get_utils = reinterpret_cast(sc_vt[9]); + g_state.isteam_utils = get_utils(steamclient, pipe_out, "SteamUtils010"); + LOGI("Stage2: ISteamClient.GetISteamUtils(SteamUtils010) -> %p", + g_state.isteam_utils); + + using GetUserFn = void* (*)(void*, int, int, const char*); + auto get_user = reinterpret_cast(sc_vt[5]); + g_state.isteam_user = get_user(steamclient, user_h, pipe_out, + "SteamUser023"); + LOGI("Stage2: ISteamClient.GetISteamUser(SteamUser023) -> %p", + g_state.isteam_user); + + using GetStatsFn = void* (*)(void*, int, int, const char*); + auto get_stats = reinterpret_cast(sc_vt[13]); + g_state.isteam_userstats = get_stats( + steamclient, user_h, pipe_out, + "STEAMUSERSTATS_INTERFACE_VERSION013"); + LOGI("Stage2: ISteamClient.GetISteamUserStats(v013) -> %p", + g_state.isteam_userstats); + + using GetAppsFn = void* (*)(void*, int, int, const char*); + auto get_apps = reinterpret_cast(sc_vt[14]); + for (const char* v : {"STEAMAPPS_INTERFACE_VERSION009", + "STEAMAPPS_INTERFACE_VERSION008", + "STEAMAPPS_INTERFACE_VERSION007"}) { + void* o = get_apps(steamclient, user_h, pipe_out, v); + LOGI("Stage2: GetISteamApps(\"%s\") -> %p", v, o); + if (o) { + g_state.isteam_apps = o; + g_state.isteam_apps_ver = v; + break; + } + } + LOGI("Stage2: ISteamClient.GetISteamApps WINNER -> %p ver=%s", + g_state.isteam_apps, + g_state.isteam_apps_ver ? g_state.isteam_apps_ver : "(none)"); + + using GetRSFn = void* (*)(void*, int, int, const char*); + auto get_rs = reinterpret_cast(sc_vt[16]); + for (const char* v : {"STEAMREMOTESTORAGE_INTERFACE_VERSION016", + "STEAMREMOTESTORAGE_INTERFACE_VERSION014"}) { + void* o = get_rs(steamclient, user_h, pipe_out, v); + LOGI("Stage2: GetISteamRemoteStorage(\"%s\") -> %p", v, o); + if (o) { + g_state.isteam_remotestorage = o; + g_state.isteam_rs_ver = v; + break; + } + } + LOGI("Stage2: ISteamClient.GetISteamRemoteStorage WINNER -> %p ver=%s", + g_state.isteam_remotestorage, + g_state.isteam_rs_ver ? g_state.isteam_rs_ver : "(none)"); + + using GetFriendsFn = void* (*)(void*, int, int, const char*); + auto get_friends = reinterpret_cast(sc_vt[8]); + for (const char* v : {"SteamFriends017", + "SteamFriends018", + "SteamFriends015"}) { + void* o = get_friends(steamclient, user_h, pipe_out, v); + LOGI("Stage2: GetISteamFriends(\"%s\") -> %p", v, o); + if (o) { + g_state.isteam_friends = o; + g_state.isteam_friends_ver = v; + break; + } + } + LOGI("Stage2: ISteamClient.GetISteamFriends WINNER -> %p ver=%s", + g_state.isteam_friends, + g_state.isteam_friends_ver ? g_state.isteam_friends_ver : "(none)"); + + if (!logged_on && g_state.isteam_userstats) { + LOGW("Stage2: NULLING isteam_userstats (got non-null %p but " + "logon failed — half-baked iface would SIGSEGV)", + g_state.isteam_userstats); + g_state.isteam_userstats = nullptr; + } + + if (g_state.isteam_apps) { + long* apps_vt = *reinterpret_cast(g_state.isteam_apps); + using BIsSubAppFn = bool (*)(void*, unsigned int); + auto is_sub_app = reinterpret_cast(apps_vt[6]); + const unsigned probe = jappId > 0 + ? static_cast(jappId) + : 242760u; + bool owns = is_sub_app(g_state.isteam_apps, probe); + LOGI("Stage2: ISteamApps.BIsSubscribedApp(%u) = %d", probe, owns ? 1 : 0); + } + if (g_state.isteam_user) { + long* u_vt = *reinterpret_cast(g_state.isteam_user); + using GetSteamIDFn = uint64_t (*)(void*); + auto get_sid = reinterpret_cast(u_vt[2]); + uint64_t live_sid = get_sid(g_state.isteam_user); + LOGI("Stage2: ISteamUser.GetSteamID() = %llu (prefmgr=%llu, " + "match=%d)", + static_cast(live_sid), + static_cast(g_state.cached_steam_id), + live_sid == g_state.cached_steam_id ? 1 : 0); + } + if (g_state.isteam_utils) { + long* utils_vt = *reinterpret_cast(g_state.isteam_utils); + using GetAppIDFn = uint32_t (*)(void*); + auto get_app_id = reinterpret_cast(utils_vt[9]); + uint32_t live_app = get_app_id(g_state.isteam_utils); + LOGI("Stage2: ISteamUtils.GetAppID() = %u", live_app); + } + if (g_state.isteam_friends) { + long* fr_vt = *reinterpret_cast(g_state.isteam_friends); + using NameFn = const char* (*)(void*); + using StateFn = int (*)(void*); + using CountFn = int (*)(void*, int); + auto get_name = reinterpret_cast(fr_vt[0]); + auto get_state = reinterpret_cast(fr_vt[2]); + auto get_count = reinterpret_cast(fr_vt[3]); + const char* name = get_name(g_state.isteam_friends); + int st = get_state(g_state.isteam_friends); + int fcount = get_count(g_state.isteam_friends, 0x4); + LOGI("Stage2: ISteamFriends.GetPersonaName=\"%s\" " + "GetPersonaState=%d GetFriendCount(immediate)=%d", + name ? name : "(null)", st, fcount); + } + if (g_state.isteam_remotestorage) { + long* rs_vt = *reinterpret_cast(g_state.isteam_remotestorage); + using GetCountFn = int (*)(void*); + using GetQuotaFn = void (*)(void*, uint64_t*, uint64_t*); + using CloudAcctFn = bool (*)(void*); + using CloudAppFn = bool (*)(void*); + auto get_count = reinterpret_cast(rs_vt[18]); + auto get_quota = reinterpret_cast(rs_vt[20]); + auto cloud_acct = reinterpret_cast(rs_vt[21]); + auto cloud_app = reinterpret_cast(rs_vt[22]); + int files = get_count(g_state.isteam_remotestorage); + uint64_t total = 0, avail = 0; + get_quota(g_state.isteam_remotestorage, &total, &avail); + bool on_acct = cloud_acct(g_state.isteam_remotestorage); + bool on_app = cloud_app(g_state.isteam_remotestorage); + LOGI("Stage2: ISteamRemoteStorage cloud_account=%d cloud_app=%d " + "file_count=%d quota_total=%llu avail=%llu", + on_acct ? 1 : 0, on_app ? 1 : 0, files, + static_cast(total), + static_cast(avail)); + } + if (g_state.isteam_userstats && logged_on) { + long* us_vt = *reinterpret_cast(g_state.isteam_userstats); + using ReqFn = bool (*)(void*); + using GetNumAchFn = uint32_t (*)(void*); + auto req_stats = reinterpret_cast(us_vt[0]); + auto get_n = reinterpret_cast(us_vt[14]); + constexpr int kUserStatsReceived = 1101; + g_state.subscribed_ids.insert(kUserStatsReceived); + g_state.received_callbacks.erase(kUserStatsReceived); + bool req_ok = req_stats(g_state.isteam_userstats); + LOGI("Stage2: ISteamUserStats.RequestCurrentStats() = %d", + req_ok ? 1 : 0); + auto deadline = std::chrono::steady_clock::now() + + std::chrono::seconds(3); + g_state.cv_callback.wait_until(lk, deadline, [&] { + return g_state.received_callbacks.count(kUserStatsReceived) > 0; + }); + uint32_t n = get_n(g_state.isteam_userstats); + LOGI("Stage2: ISteamUserStats.GetNumAchievements() = %u " + "(UserStatsReceived_t %s)", + n, + g_state.received_callbacks.count(kUserStatsReceived) + ? "arrived" : "TIMEOUT"); + } + } + } + + return 0; +} + +// True iff libsteamclient.so is loaded AND Steam_BLoggedOn reports the +// pipe+user as authenticated. Cheap synchronous call — safe from any thread. +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeIsLoggedOn( + JNIEnv* /*env*/, jclass /*cls*/) { + std::lock_guard lk(g_state.mu); + if (!g_state.initialized || !g_state.fn_Steam_BLoggedOn) return JNI_FALSE; + return g_state.fn_Steam_BLoggedOn(g_state.pipe, g_state.user) + ? JNI_TRUE : JNI_FALSE; +} + +JNIEXPORT jlong JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeGetSteamId( + JNIEnv* /*env*/, jclass /*cls*/) { + std::lock_guard lk(g_state.mu); + if (!g_state.initialized) return 0; + return static_cast(g_state.cached_steam_id); +} + +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeBIsSubscribedApp( + JNIEnv* /*env*/, jclass /*cls*/, jint appId) { + std::lock_guard lk(g_state.mu); + if (!g_state.initialized) { + LOGW("nativeBIsSubscribedApp(%d): not initialized", appId); + return JNI_FALSE; + } + if (!g_state.isteam_apps) { + LOGW("nativeBIsSubscribedApp(%d): ISteamApps not cached " + "(auth-gated — needs a logged-on session)", appId); + return JNI_FALSE; + } + long* apps_vt = *reinterpret_cast(g_state.isteam_apps); + using BIsSubAppFn = bool (*)(void*, unsigned int); + auto is_sub_app = reinterpret_cast(apps_vt[6]); + const bool owns = is_sub_app(g_state.isteam_apps, + static_cast(appId)); + return owns ? JNI_TRUE : JNI_FALSE; +} + + +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeISteamAppsBIsAppInstalled( + JNIEnv* /*env*/, jclass /*cls*/, jint appId) { + std::lock_guard lk(g_state.mu); + if (!g_state.initialized || !g_state.isteam_apps) return JNI_FALSE; + long* apps_vt = *reinterpret_cast(g_state.isteam_apps); + using BIsInstFn = bool (*)(void*, unsigned int); + auto is_inst = reinterpret_cast(apps_vt[19]); + return is_inst(g_state.isteam_apps, + static_cast(appId)) ? JNI_TRUE : JNI_FALSE; +} + +JNIEXPORT jstring JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeISteamAppsGetAppInstallDir( + JNIEnv* env, jclass /*cls*/, jint appId) { + std::lock_guard lk(g_state.mu); + if (!g_state.initialized || !g_state.isteam_apps) return nullptr; + long* apps_vt = *reinterpret_cast(g_state.isteam_apps); + using GetDirFn = uint32_t (*)(void*, unsigned int, char*, uint32_t); + auto get_dir = reinterpret_cast(apps_vt[18]); + char buf[1024] = {0}; + uint32_t n = get_dir(g_state.isteam_apps, + static_cast(appId), + buf, sizeof(buf)); + if (n == 0) return nullptr; + return env->NewStringUTF(buf); +} + +JNIEXPORT jintArray JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeISteamAppsGetInstalledDepots( + JNIEnv* env, jclass /*cls*/, jint appId) { + std::lock_guard lk(g_state.mu); + if (!g_state.initialized || !g_state.isteam_apps) return env->NewIntArray(0); + long* apps_vt = *reinterpret_cast(g_state.isteam_apps); + using GetDepFn = uint32_t (*)(void*, unsigned int, unsigned int*, uint32_t); + auto get_dep = reinterpret_cast(apps_vt[17]); + unsigned int depots[64] = {0}; + uint32_t n = get_dep(g_state.isteam_apps, + static_cast(appId), + depots, 64); + if (n > 64) n = 64; + jintArray out = env->NewIntArray(static_cast(n)); + if (!out) return nullptr; + if (n > 0) { + env->SetIntArrayRegion(out, 0, static_cast(n), + reinterpret_cast(depots)); + } + return out; +} + +JNIEXPORT jstring JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeISteamAppsGetCurrentGameLanguage( + JNIEnv* env, jclass /*cls*/) { + std::lock_guard lk(g_state.mu); + if (!g_state.initialized || !g_state.isteam_apps) return nullptr; + long* apps_vt = *reinterpret_cast(g_state.isteam_apps); + using GetLangFn = const char* (*)(void*); + auto get_lang = reinterpret_cast(apps_vt[4]); + const char* v = get_lang(g_state.isteam_apps); + return (v && *v) ? env->NewStringUTF(v) : nullptr; +} + + +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeISteamAppsBIsDlcInstalled( + JNIEnv* /*env*/, jclass /*cls*/, jint dlcAppId) { + std::lock_guard lk(g_state.mu); + if (!g_state.initialized || !g_state.isteam_apps) return JNI_FALSE; + long* apps_vt = *reinterpret_cast(g_state.isteam_apps); + using DlcInstFn = bool (*)(void*, unsigned int); + auto dlc_inst = reinterpret_cast(apps_vt[7]); + return dlc_inst(g_state.isteam_apps, + static_cast(dlcAppId)) ? JNI_TRUE : JNI_FALSE; +} + +JNIEXPORT jint JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeISteamAppsGetEarliestPurchaseUnixTime( + JNIEnv* /*env*/, jclass /*cls*/, jint appId) { + std::lock_guard lk(g_state.mu); + if (!g_state.initialized || !g_state.isteam_apps) return 0; + long* apps_vt = *reinterpret_cast(g_state.isteam_apps); + using PurchaseFn = uint32_t (*)(void*, unsigned int); + auto purchase = reinterpret_cast(apps_vt[8]); + return static_cast(purchase(g_state.isteam_apps, + static_cast(appId))); +} + +JNIEXPORT jint JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeISteamAppsGetDLCCount( + JNIEnv* /*env*/, jclass /*cls*/, jint appId) { + std::lock_guard lk(g_state.mu); + if (!g_state.initialized || !g_state.isteam_apps) return 0; + long* apps_vt = *reinterpret_cast(g_state.isteam_apps); + using DlcCountFn = int (*)(void*, unsigned int); + auto dlc_count = reinterpret_cast(apps_vt[10]); + return static_cast(dlc_count(g_state.isteam_apps, + static_cast(appId))); +} + +JNIEXPORT jlong JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeISteamAppsGetAppOwner( + JNIEnv* /*env*/, jclass /*cls*/) { + std::lock_guard lk(g_state.mu); + if (!g_state.initialized || !g_state.isteam_apps) return 0; + long* apps_vt = *reinterpret_cast(g_state.isteam_apps); + using GetOwnerFn = uint64_t (*)(void*); + auto get_owner = reinterpret_cast(apps_vt[20]); + return static_cast(get_owner(g_state.isteam_apps)); +} + +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeISteamAppsBIsSubscribedFromFamilySharing( + JNIEnv* /*env*/, jclass /*cls*/) { + std::lock_guard lk(g_state.mu); + if (!g_state.initialized || !g_state.isteam_apps) return JNI_FALSE; + long* apps_vt = *reinterpret_cast(g_state.isteam_apps); + using FamShareFn = bool (*)(void*); + auto fam_share = reinterpret_cast(apps_vt[27]); + return fam_share(g_state.isteam_apps) ? JNI_TRUE : JNI_FALSE; +} + +JNIEXPORT jint JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeISteamAppsGetAppBuildId( + JNIEnv* /*env*/, jclass /*cls*/) { + std::lock_guard lk(g_state.mu); + if (!g_state.initialized || !g_state.isteam_apps) return 0; + long* apps_vt = *reinterpret_cast(g_state.isteam_apps); + using BuildIdFn = int (*)(void*); + auto build_id = reinterpret_cast(apps_vt[23]); + return static_cast(build_id(g_state.isteam_apps)); +} + +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeISteamUserBLoggedOn( + JNIEnv* /*env*/, jclass /*cls*/) { + std::lock_guard lk(g_state.mu); + if (!g_state.initialized || !g_state.isteam_user) return JNI_FALSE; + long* u_vt = *reinterpret_cast(g_state.isteam_user); + using BLoggedFn = bool (*)(void*); + auto bl = reinterpret_cast(u_vt[1]); + return bl(g_state.isteam_user) ? JNI_TRUE : JNI_FALSE; +} + +JNIEXPORT jint JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeISteamUserHasLicenseForApp( + JNIEnv* /*env*/, jclass /*cls*/, jlong steamId64, jint appId) { + std::lock_guard lk(g_state.mu); + if (!g_state.initialized || !g_state.isteam_user) return 2; // no-auth + long* u_vt = *reinterpret_cast(g_state.isteam_user); + using HasLicFn = int (*)(void*, uint64_t, unsigned int); + auto hl = reinterpret_cast(u_vt[18]); + return static_cast(hl(g_state.isteam_user, + static_cast(steamId64), + static_cast(appId))); +} + +JNIEXPORT jlong JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeISteamUserGetSteamID( + JNIEnv* /*env*/, jclass /*cls*/) { + std::lock_guard lk(g_state.mu); + if (!g_state.initialized || !g_state.isteam_user) return 0; + long* u_vt = *reinterpret_cast(g_state.isteam_user); + using GetSteamIDFn = uint64_t (*)(void*); + auto get_sid = reinterpret_cast(u_vt[2]); + return static_cast(get_sid(g_state.isteam_user)); +} + +JNIEXPORT jint JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeISteamUtilsGetAppID( + JNIEnv* /*env*/, jclass /*cls*/) { + std::lock_guard lk(g_state.mu); + if (!g_state.initialized || !g_state.isteam_utils) return 0; + long* utils_vt = *reinterpret_cast(g_state.isteam_utils); + using GetAppIDFn = uint32_t (*)(void*); + auto get_app_id = reinterpret_cast(utils_vt[9]); + return static_cast(get_app_id(g_state.isteam_utils)); +} + + +JNIEXPORT jint JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeISteamUtilsGetServerRealTime( + JNIEnv* /*env*/, jclass /*cls*/) { + std::lock_guard lk(g_state.mu); + if (!g_state.initialized || !g_state.isteam_utils) return 0; + long* utils_vt = *reinterpret_cast(g_state.isteam_utils); + using TimeFn = uint32_t (*)(void*); + auto get_time = reinterpret_cast(utils_vt[3]); + return static_cast(get_time(g_state.isteam_utils)); +} + +JNIEXPORT jstring JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeISteamUtilsGetIPCountry( + JNIEnv* env, jclass /*cls*/) { + std::lock_guard lk(g_state.mu); + if (!g_state.initialized || !g_state.isteam_utils) return nullptr; + long* utils_vt = *reinterpret_cast(g_state.isteam_utils); + using CountryFn = const char* (*)(void*); + auto get_country = reinterpret_cast(utils_vt[4]); + const char* v = get_country(g_state.isteam_utils); + return (v && *v) ? env->NewStringUTF(v) : nullptr; +} + +JNIEXPORT jstring JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeISteamUtilsGetSteamUILanguage( + JNIEnv* env, jclass /*cls*/) { + std::lock_guard lk(g_state.mu); + if (!g_state.initialized || !g_state.isteam_utils) return nullptr; + long* utils_vt = *reinterpret_cast(g_state.isteam_utils); + using LangFn = const char* (*)(void*); + auto get_lang = reinterpret_cast(utils_vt[23]); + const char* v = get_lang(g_state.isteam_utils); + return (v && *v) ? env->NewStringUTF(v) : nullptr; +} + +JNIEXPORT jint JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeISteamUtilsGetCurrentBatteryPower( + JNIEnv* /*env*/, jclass /*cls*/) { + std::lock_guard lk(g_state.mu); + if (!g_state.initialized || !g_state.isteam_utils) return 255; // assume AC + long* utils_vt = *reinterpret_cast(g_state.isteam_utils); + using BatteryFn = uint8_t (*)(void*); + auto get_battery = reinterpret_cast(utils_vt[8]); + return static_cast(get_battery(g_state.isteam_utils)); +} + +JNIEXPORT jintArray JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeISteamUtilsGetImageSize( + JNIEnv* env, jclass /*cls*/, jint imageHandle) { + std::lock_guard lk(g_state.mu); + jintArray out = env->NewIntArray(2); + if (!out) return nullptr; + if (!g_state.initialized || !g_state.isteam_utils || imageHandle <= 0) return out; + long* utils_vt = *reinterpret_cast(g_state.isteam_utils); + using SizeFn = bool (*)(void*, int, uint32_t*, uint32_t*); + auto get_size = reinterpret_cast(utils_vt[5]); + uint32_t w = 0, h = 0; + if (get_size(g_state.isteam_utils, imageHandle, &w, &h)) { + jint values[2] = { static_cast(w), static_cast(h) }; + env->SetIntArrayRegion(out, 0, 2, values); + } + return out; +} + +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeISteamUtilsGetImageRGBA( + JNIEnv* env, jclass /*cls*/, jint imageHandle, jbyteArray outRgba) { + std::lock_guard lk(g_state.mu); + if (!g_state.initialized || !g_state.isteam_utils || !outRgba || imageHandle <= 0) { + return JNI_FALSE; + } + jsize n = env->GetArrayLength(outRgba); + if (n <= 0) return JNI_FALSE; + long* utils_vt = *reinterpret_cast(g_state.isteam_utils); + using RGBAFn = bool (*)(void*, int, uint8_t*, int); + auto get_rgba = reinterpret_cast(utils_vt[6]); + jbyte* buf = env->GetByteArrayElements(outRgba, nullptr); + if (!buf) return JNI_FALSE; + bool ok = get_rgba(g_state.isteam_utils, imageHandle, + reinterpret_cast(buf), static_cast(n)); + env->ReleaseByteArrayElements(outRgba, buf, 0); + return ok ? JNI_TRUE : JNI_FALSE; +} + +JNIEXPORT jint JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeISteamRemoteStorageGetFileCount( + JNIEnv* /*env*/, jclass /*cls*/) { + std::lock_guard lk(g_state.mu); + if (!g_state.initialized || !g_state.isteam_remotestorage) return 0; + long* rs_vt = *reinterpret_cast(g_state.isteam_remotestorage); + using GetCountFn = int (*)(void*); + auto get_count = reinterpret_cast(rs_vt[18]); + return static_cast(get_count(g_state.isteam_remotestorage)); +} + +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeISteamRemoteStorageIsCloudEnabledForAccount( + JNIEnv* /*env*/, jclass /*cls*/) { + std::lock_guard lk(g_state.mu); + if (!g_state.initialized || !g_state.isteam_remotestorage) return JNI_FALSE; + long* rs_vt = *reinterpret_cast(g_state.isteam_remotestorage); + using CloudFn = bool (*)(void*); + auto cloud = reinterpret_cast(rs_vt[21]); + return cloud(g_state.isteam_remotestorage) ? JNI_TRUE : JNI_FALSE; +} + +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeISteamRemoteStorageIsCloudEnabledForApp( + JNIEnv* /*env*/, jclass /*cls*/) { + std::lock_guard lk(g_state.mu); + if (!g_state.initialized || !g_state.isteam_remotestorage) return JNI_FALSE; + long* rs_vt = *reinterpret_cast(g_state.isteam_remotestorage); + using CloudFn = bool (*)(void*); + auto cloud = reinterpret_cast(rs_vt[22]); + return cloud(g_state.isteam_remotestorage) ? JNI_TRUE : JNI_FALSE; +} + +JNIEXPORT jlongArray JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeISteamRemoteStorageGetQuota( + JNIEnv* env, jclass /*cls*/) { + std::lock_guard lk(g_state.mu); + jlongArray out = env->NewLongArray(2); + if (!out) return nullptr; + if (g_state.initialized && g_state.isteam_remotestorage) { + long* rs_vt = *reinterpret_cast(g_state.isteam_remotestorage); + using GetQuotaFn = void (*)(void*, uint64_t*, uint64_t*); + auto get_quota = reinterpret_cast(rs_vt[20]); + uint64_t total = 0, avail = 0; + get_quota(g_state.isteam_remotestorage, &total, &avail); + jlong values[2] = { static_cast(total), static_cast(avail) }; + env->SetLongArrayRegion(out, 0, 2, values); + } + return out; +} + +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeISteamUserStatsRequestCurrentStats( + JNIEnv* /*env*/, jclass /*cls*/) { + std::lock_guard lk(g_state.mu); + if (!g_state.initialized || !g_state.isteam_userstats) return JNI_FALSE; + long* us_vt = *reinterpret_cast(g_state.isteam_userstats); + using ReqFn = bool (*)(void*); + auto req = reinterpret_cast(us_vt[0]); + return req(g_state.isteam_userstats) ? JNI_TRUE : JNI_FALSE; +} + +JNIEXPORT jint JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeISteamUserStatsGetNumAchievements( + JNIEnv* /*env*/, jclass /*cls*/) { + std::lock_guard lk(g_state.mu); + if (!g_state.initialized || !g_state.isteam_userstats) return 0; + long* us_vt = *reinterpret_cast(g_state.isteam_userstats); + using GetNumFn = uint32_t (*)(void*); + auto n = reinterpret_cast(us_vt[14]); + return static_cast(n(g_state.isteam_userstats)); +} + + +JNIEXPORT jobjectArray JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeISteamRemoteStorageListFiles( + JNIEnv* env, jclass /*cls*/) { + std::lock_guard lk(g_state.mu); + jclass strCls = env->FindClass("java/lang/String"); + if (!g_state.initialized || !g_state.isteam_remotestorage) { + return env->NewObjectArray(0, strCls, nullptr); + } + long* rs_vt = *reinterpret_cast(g_state.isteam_remotestorage); + using GetCountFn = int (*)(void*); + using GetNameSizeFn = const char* (*)(void*, int, int32_t*); + auto get_count = reinterpret_cast(rs_vt[18]); + auto get_ns = reinterpret_cast(rs_vt[19]); + int n = get_count(g_state.isteam_remotestorage); + if (n < 0) n = 0; + jobjectArray out = env->NewObjectArray(n, strCls, nullptr); + if (!out) return nullptr; + for (int i = 0; i < n; ++i) { + int32_t size = 0; + const char* name = get_ns(g_state.isteam_remotestorage, i, &size); + if (!name) name = ""; + char buf[1024]; + std::snprintf(buf, sizeof(buf), "%s\t%d", name, size); + jstring js = env->NewStringUTF(buf); + env->SetObjectArrayElement(out, i, js); + env->DeleteLocalRef(js); + } + return out; +} + +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeISteamRemoteStorageFileExists( + JNIEnv* env, jclass /*cls*/, jstring jname) { + std::lock_guard lk(g_state.mu); + if (!g_state.initialized || !g_state.isteam_remotestorage || !jname) return JNI_FALSE; + std::string name = jstr(env, jname); + long* rs_vt = *reinterpret_cast(g_state.isteam_remotestorage); + using ExistsFn = bool (*)(void*, const char*); + auto exists = reinterpret_cast(rs_vt[13]); + return exists(g_state.isteam_remotestorage, name.c_str()) ? JNI_TRUE : JNI_FALSE; +} + +JNIEXPORT jbyteArray JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeISteamRemoteStorageFileRead( + JNIEnv* env, jclass /*cls*/, jstring jname) { + std::lock_guard lk(g_state.mu); + if (!g_state.initialized || !g_state.isteam_remotestorage || !jname) return nullptr; + std::string name = jstr(env, jname); + long* rs_vt = *reinterpret_cast(g_state.isteam_remotestorage); + using GetSizeFn = int32_t (*)(void*, const char*); + using FileReadFn = int32_t (*)(void*, const char*, void*, int32_t); + auto get_size = reinterpret_cast(rs_vt[15]); + auto file_read = reinterpret_cast(rs_vt[1]); + int32_t size = get_size(g_state.isteam_remotestorage, name.c_str()); + if (size <= 0) return nullptr; + jbyteArray out = env->NewByteArray(size); + if (!out) return nullptr; + jbyte* buf = env->GetByteArrayElements(out, nullptr); + if (!buf) return nullptr; + int32_t read = file_read(g_state.isteam_remotestorage, name.c_str(), + buf, size); + env->ReleaseByteArrayElements(out, buf, 0); + if (read != size) { + return nullptr; + } + return out; +} + +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeISteamRemoteStorageFileWrite( + JNIEnv* env, jclass /*cls*/, jstring jname, jbyteArray jdata) { + std::lock_guard lk(g_state.mu); + if (!g_state.initialized || !g_state.isteam_remotestorage || !jname || !jdata) { + return JNI_FALSE; + } + std::string name = jstr(env, jname); + jsize n = env->GetArrayLength(jdata); + long* rs_vt = *reinterpret_cast(g_state.isteam_remotestorage); + using FileWriteFn = bool (*)(void*, const char*, const void*, int32_t); + auto file_write = reinterpret_cast(rs_vt[0]); + jbyte* buf = env->GetByteArrayElements(jdata, nullptr); + if (!buf) return JNI_FALSE; + bool ok = file_write(g_state.isteam_remotestorage, name.c_str(), + buf, static_cast(n)); + env->ReleaseByteArrayElements(jdata, buf, JNI_ABORT); + return ok ? JNI_TRUE : JNI_FALSE; +} + +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeISteamRemoteStorageFileDelete( + JNIEnv* env, jclass /*cls*/, jstring jname) { + std::lock_guard lk(g_state.mu); + if (!g_state.initialized || !g_state.isteam_remotestorage || !jname) return JNI_FALSE; + std::string name = jstr(env, jname); + long* rs_vt = *reinterpret_cast(g_state.isteam_remotestorage); + using DeleteFn = bool (*)(void*, const char*); + auto file_del = reinterpret_cast(rs_vt[6]); + return file_del(g_state.isteam_remotestorage, name.c_str()) ? JNI_TRUE : JNI_FALSE; +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeISteamRemoteStorageSetCloudEnabledForApp( + JNIEnv* /*env*/, jclass /*cls*/, jboolean enabled) { + std::lock_guard lk(g_state.mu); + if (!g_state.initialized || !g_state.isteam_remotestorage) return; + long* rs_vt = *reinterpret_cast(g_state.isteam_remotestorage); + using SetEnabledFn = void (*)(void*, bool); + auto set_en = reinterpret_cast(rs_vt[23]); + set_en(g_state.isteam_remotestorage, enabled == JNI_TRUE); +} + + +JNIEXPORT jobjectArray JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeISteamUserStatsListAchievements( + JNIEnv* env, jclass /*cls*/) { + std::lock_guard lk(g_state.mu); + jclass strCls = env->FindClass("java/lang/String"); + if (!g_state.initialized || !g_state.isteam_userstats) { + return env->NewObjectArray(0, strCls, nullptr); + } + long* us_vt = *reinterpret_cast(g_state.isteam_userstats); + using GetNumFn = uint32_t (*)(void*); + using GetNameFn = const char* (*)(void*, uint32_t); + auto get_n = reinterpret_cast(us_vt[14]); + auto get_name = reinterpret_cast(us_vt[15]); + uint32_t n = get_n(g_state.isteam_userstats); + jobjectArray out = env->NewObjectArray(static_cast(n), strCls, nullptr); + if (!out) return nullptr; + for (uint32_t i = 0; i < n; ++i) { + const char* name = get_name(g_state.isteam_userstats, i); + if (!name) name = ""; + jstring js = env->NewStringUTF(name); + env->SetObjectArrayElement(out, static_cast(i), js); + env->DeleteLocalRef(js); + } + return out; +} + +JNIEXPORT jintArray JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeISteamUserStatsGetAchievementAndUnlockTime( + JNIEnv* env, jclass /*cls*/, jstring jname) { + std::lock_guard lk(g_state.mu); + jintArray out = env->NewIntArray(2); + if (!out) return nullptr; + if (!g_state.initialized || !g_state.isteam_userstats || !jname) return out; + std::string name = jstr(env, jname); + long* us_vt = *reinterpret_cast(g_state.isteam_userstats); + using GetAchUtFn = bool (*)(void*, const char*, bool*, uint32_t*); + auto get_aut = reinterpret_cast(us_vt[9]); + bool achieved = false; + uint32_t unlock_time = 0; + bool ok = get_aut(g_state.isteam_userstats, name.c_str(), + &achieved, &unlock_time); + if (!ok) return out; + jint values[2] = { achieved ? 1 : 0, static_cast(unlock_time) }; + env->SetIntArrayRegion(out, 0, 2, values); + return out; +} + +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeISteamUserStatsSetAchievement( + JNIEnv* env, jclass /*cls*/, jstring jname) { + std::lock_guard lk(g_state.mu); + if (!g_state.initialized || !g_state.isteam_userstats || !jname) return JNI_FALSE; + std::string name = jstr(env, jname); + long* us_vt = *reinterpret_cast(g_state.isteam_userstats); + using SetAchFn = bool (*)(void*, const char*); + auto set_ach = reinterpret_cast(us_vt[7]); + return set_ach(g_state.isteam_userstats, name.c_str()) ? JNI_TRUE : JNI_FALSE; +} + +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeISteamUserStatsClearAchievement( + JNIEnv* env, jclass /*cls*/, jstring jname) { + std::lock_guard lk(g_state.mu); + if (!g_state.initialized || !g_state.isteam_userstats || !jname) return JNI_FALSE; + std::string name = jstr(env, jname); + long* us_vt = *reinterpret_cast(g_state.isteam_userstats); + using ClrAchFn = bool (*)(void*, const char*); + auto clr_ach = reinterpret_cast(us_vt[8]); + return clr_ach(g_state.isteam_userstats, name.c_str()) ? JNI_TRUE : JNI_FALSE; +} + +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeISteamUserStatsStoreStats( + JNIEnv* /*env*/, jclass /*cls*/) { + std::lock_guard lk(g_state.mu); + if (!g_state.initialized || !g_state.isteam_userstats) return JNI_FALSE; + long* us_vt = *reinterpret_cast(g_state.isteam_userstats); + using StoreFn = bool (*)(void*); + auto store = reinterpret_cast(us_vt[10]); + return store(g_state.isteam_userstats) ? JNI_TRUE : JNI_FALSE; +} + + +JNIEXPORT jint JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeISteamUserStatsGetStatInt( + JNIEnv* env, jclass /*cls*/, jstring jname) { + std::lock_guard lk(g_state.mu); + if (!g_state.initialized || !g_state.isteam_userstats || !jname) return 0; + std::string name = jstr(env, jname); + long* us_vt = *reinterpret_cast(g_state.isteam_userstats); + using GetStatIFn = bool (*)(void*, const char*, int32_t*); + auto get_stat = reinterpret_cast(us_vt[1]); + int32_t data = 0; + if (!get_stat(g_state.isteam_userstats, name.c_str(), &data)) return 0; + return static_cast(data); +} + +JNIEXPORT jfloat JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeISteamUserStatsGetStatFloat( + JNIEnv* env, jclass /*cls*/, jstring jname) { + std::lock_guard lk(g_state.mu); + if (!g_state.initialized || !g_state.isteam_userstats || !jname) return 0.0f; + std::string name = jstr(env, jname); + long* us_vt = *reinterpret_cast(g_state.isteam_userstats); + using GetStatFFn = bool (*)(void*, const char*, float*); + auto get_stat = reinterpret_cast(us_vt[2]); + float data = 0.0f; + if (!get_stat(g_state.isteam_userstats, name.c_str(), &data)) return 0.0f; + return static_cast(data); +} + +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeISteamUserStatsSetStatInt( + JNIEnv* env, jclass /*cls*/, jstring jname, jint jdata) { + std::lock_guard lk(g_state.mu); + if (!g_state.initialized || !g_state.isteam_userstats || !jname) return JNI_FALSE; + std::string name = jstr(env, jname); + long* us_vt = *reinterpret_cast(g_state.isteam_userstats); + using SetStatIFn = bool (*)(void*, const char*, int32_t); + auto set_stat = reinterpret_cast(us_vt[3]); + return set_stat(g_state.isteam_userstats, name.c_str(), + static_cast(jdata)) ? JNI_TRUE : JNI_FALSE; +} + +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeISteamUserStatsSetStatFloat( + JNIEnv* env, jclass /*cls*/, jstring jname, jfloat jdata) { + std::lock_guard lk(g_state.mu); + if (!g_state.initialized || !g_state.isteam_userstats || !jname) return JNI_FALSE; + std::string name = jstr(env, jname); + long* us_vt = *reinterpret_cast(g_state.isteam_userstats); + using SetStatFFn = bool (*)(void*, const char*, float); + auto set_stat = reinterpret_cast(us_vt[4]); + return set_stat(g_state.isteam_userstats, name.c_str(), + static_cast(jdata)) ? JNI_TRUE : JNI_FALSE; +} + +JNIEXPORT jboolean JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeISteamUserStatsUpdateAvgRateStat( + JNIEnv* env, jclass /*cls*/, jstring jname, + jfloat jcountThisSession, jdouble jsessionLength) { + std::lock_guard lk(g_state.mu); + if (!g_state.initialized || !g_state.isteam_userstats || !jname) return JNI_FALSE; + std::string name = jstr(env, jname); + long* us_vt = *reinterpret_cast(g_state.isteam_userstats); + using UpdateRateFn = bool (*)(void*, const char*, float, double); + auto upd = reinterpret_cast(us_vt[5]); + return upd(g_state.isteam_userstats, name.c_str(), + static_cast(jcountThisSession), + static_cast(jsessionLength)) ? JNI_TRUE : JNI_FALSE; +} + +JNIEXPORT jstring JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeISteamUserStatsGetAchievementDisplayAttribute( + JNIEnv* env, jclass /*cls*/, jstring jname, jstring jkey) { + std::lock_guard lk(g_state.mu); + if (!g_state.initialized || !g_state.isteam_userstats || !jname || !jkey) return nullptr; + std::string name = jstr(env, jname); + std::string key = jstr(env, jkey); + long* us_vt = *reinterpret_cast(g_state.isteam_userstats); + using GetAttrFn = const char* (*)(void*, const char*, const char*); + auto get_attr = reinterpret_cast(us_vt[12]); + const char* v = get_attr(g_state.isteam_userstats, name.c_str(), key.c_str()); + if (!v || !*v) return nullptr; + return env->NewStringUTF(v); +} + +JNIEXPORT jint JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeISteamUserStatsGetAchievementIcon( + JNIEnv* env, jclass /*cls*/, jstring jname) { + std::lock_guard lk(g_state.mu); + if (!g_state.initialized || !g_state.isteam_userstats || !jname) return 0; + std::string name = jstr(env, jname); + long* us_vt = *reinterpret_cast(g_state.isteam_userstats); + using IconFn = int (*)(void*, const char*); + auto icon = reinterpret_cast(us_vt[11]); + return static_cast(icon(g_state.isteam_userstats, name.c_str())); +} + + +JNIEXPORT jstring JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeISteamFriendsGetPersonaName( + JNIEnv* env, jclass /*cls*/) { + std::lock_guard lk(g_state.mu); + if (!g_state.initialized || !g_state.isteam_friends) return nullptr; + long* vt = *reinterpret_cast(g_state.isteam_friends); + using NameFn = const char* (*)(void*); + auto get_name = reinterpret_cast(vt[0]); + const char* n = get_name(g_state.isteam_friends); + return (n && *n) ? env->NewStringUTF(n) : nullptr; +} + +JNIEXPORT jint JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeISteamFriendsGetPersonaState( + JNIEnv* /*env*/, jclass /*cls*/) { + std::lock_guard lk(g_state.mu); + if (!g_state.initialized || !g_state.isteam_friends) return 0; // 0=Offline + long* vt = *reinterpret_cast(g_state.isteam_friends); + using StateFn = int (*)(void*); + auto get_state = reinterpret_cast(vt[2]); + return static_cast(get_state(g_state.isteam_friends)); +} + +JNIEXPORT jint JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeISteamFriendsGetFriendCount( + JNIEnv* /*env*/, jclass /*cls*/, jint flags) { + std::lock_guard lk(g_state.mu); + if (!g_state.initialized || !g_state.isteam_friends) return 0; + long* vt = *reinterpret_cast(g_state.isteam_friends); + using CountFn = int (*)(void*, int); + auto get_count = reinterpret_cast(vt[3]); + return static_cast(get_count(g_state.isteam_friends, + static_cast(flags))); +} + +JNIEXPORT jlongArray JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeISteamFriendsListFriends( + JNIEnv* env, jclass /*cls*/, jint flags) { + std::lock_guard lk(g_state.mu); + if (!g_state.initialized || !g_state.isteam_friends) return env->NewLongArray(0); + long* vt = *reinterpret_cast(g_state.isteam_friends); + using CountFn = int (*)(void*, int); + using ByIdxFn = uint64_t (*)(void*, int, int); + auto get_count = reinterpret_cast(vt[3]); + auto get_byidx = reinterpret_cast(vt[4]); + int n = get_count(g_state.isteam_friends, static_cast(flags)); + if (n < 0) n = 0; + jlongArray out = env->NewLongArray(n); + if (!out || n == 0) return out; + for (int i = 0; i < n; ++i) { + uint64_t sid = get_byidx(g_state.isteam_friends, i, + static_cast(flags)); + jlong v = static_cast(sid); + env->SetLongArrayRegion(out, i, 1, &v); + } + return out; +} + +JNIEXPORT jstring JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeISteamFriendsGetFriendPersonaName( + JNIEnv* env, jclass /*cls*/, jlong steamId) { + std::lock_guard lk(g_state.mu); + if (!g_state.initialized || !g_state.isteam_friends) return nullptr; + long* vt = *reinterpret_cast(g_state.isteam_friends); + using FNameFn = const char* (*)(void*, uint64_t); + auto get_fname = reinterpret_cast(vt[7]); + const char* n = get_fname(g_state.isteam_friends, + static_cast(steamId)); + return (n && *n) ? env->NewStringUTF(n) : nullptr; +} + +JNIEXPORT jint JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeISteamFriendsGetFriendPersonaState( + JNIEnv* /*env*/, jclass /*cls*/, jlong steamId) { + std::lock_guard lk(g_state.mu); + if (!g_state.initialized || !g_state.isteam_friends) return 0; + long* vt = *reinterpret_cast(g_state.isteam_friends); + using FStateFn = int (*)(void*, uint64_t); + auto get_fstate = reinterpret_cast(vt[6]); + return static_cast(get_fstate(g_state.isteam_friends, + static_cast(steamId))); +} + + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeSubscribeCallback( + JNIEnv* /*env*/, jclass /*cls*/, jint id) { + std::lock_guard lk(g_state.mu); + g_state.subscribed_ids.insert(static_cast(id)); +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeUnsubscribeCallback( + JNIEnv* /*env*/, jclass /*cls*/, jint id) { + std::lock_guard lk(g_state.mu); + g_state.subscribed_ids.erase(static_cast(id)); + g_state.received_callbacks.erase(static_cast(id)); +} + +JNIEXPORT jbyteArray JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeAwaitCallback( + JNIEnv* env, jclass /*cls*/, jint id, jint timeoutMs) { + std::unique_lock lk(g_state.mu); + const int cb_id = static_cast(id); + g_state.subscribed_ids.insert(cb_id); + auto deadline = std::chrono::steady_clock::now() + + std::chrono::milliseconds(std::max(0, timeoutMs)); + bool got = g_state.cv_callback.wait_until(lk, deadline, [&] { + return g_state.received_callbacks.count(cb_id) > 0 + || !g_state.initialized + || g_state.shutting_down; + }); + if (!got) return nullptr; + auto it = g_state.received_callbacks.find(cb_id); + if (it == g_state.received_callbacks.end()) return nullptr; + std::vector payload = std::move(it->second); + g_state.received_callbacks.erase(it); + lk.unlock(); + jbyteArray out = env->NewByteArray(static_cast(payload.size())); + if (!out) return nullptr; + if (!payload.empty()) { + env->SetByteArrayRegion(out, 0, static_cast(payload.size()), + reinterpret_cast(payload.data())); + } + return out; +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeShutdown( + JNIEnv* /*env*/, jclass /*cls*/) { + std::thread pump_thread; + void* lsc_handle = nullptr; + { + std::lock_guard lk(g_state.mu); + if (!g_state.initialized) return; + g_state.shutting_down = true; + // Make concurrent JNI entry points fail fast while the pump thread is + // being joined outside the global lock. + g_state.initialized = false; + g_state.pump_running.store(false, std::memory_order_release); + lsc_handle = g_state.lsc_handle; + if (g_state.pump_thread.joinable()) { + pump_thread = std::move(g_state.pump_thread); + } + g_state.cv_callback.notify_all(); + } + if (pump_thread.joinable()) pump_thread.join(); + if (lsc_handle) { + using StopFn = void (*)(void); + ::dlerror(); + auto stop = reinterpret_cast( + ::dlsym(lsc_handle, "wn_cm_bridge_stop_state_sync_poller")); + if (stop != nullptr) { + stop(); + LOGI("cross-process state-sync poller stopped"); + } else { + const char* err = ::dlerror(); + LOGW("wn_cm_bridge_stop_state_sync_poller not found: %s", + err ? err : "symbol missing"); + } + } + + std::lock_guard lk(g_state.mu); + // Tear down in reverse order of init: log off the user, release the + // global user, then drop the pipe. We don't dlclose libsteamclient.so — it leaves background + // threads that crash on unload (the same pattern every embedded + // Steam launcher we surveyed follows). + if (g_state.fn_Steam_LogOff && g_state.user != 0 && g_state.pipe != 0) { + g_state.fn_Steam_LogOff(g_state.pipe, g_state.user); + LOGI("nativeShutdown: Steam_LogOff(pipe=%d, user=%d)", + g_state.pipe, g_state.user); + } + if (g_state.user != 0 && g_state.pipe != 0) { + if (g_state.fn_Steam_ReleaseUser) { + g_state.fn_Steam_ReleaseUser(g_state.pipe, g_state.user); + LOGI("nativeShutdown: Steam_ReleaseUser(pipe=%d, user=%d)", + g_state.pipe, g_state.user); + } else if (g_state.steamclient_iface) { + auto* steamclient = + reinterpret_cast(g_state.steamclient_iface); + steamclient->ReleaseUser(g_state.pipe, g_state.user); + LOGI("nativeShutdown: ISteamClient.ReleaseUser(pipe=%d, user=%d)", + g_state.pipe, g_state.user); + } else { + LOGW("nativeShutdown: no ReleaseUser entry point available"); + } + } + if (g_state.pipe != 0) { + if (g_state.fn_Steam_BReleaseSteamPipe) { + bool ok = g_state.fn_Steam_BReleaseSteamPipe(g_state.pipe); + LOGI("nativeShutdown: Steam_BReleaseSteamPipe(%d) -> %d", + g_state.pipe, ok ? 1 : 0); + } else if (g_state.steamclient_iface) { + auto* steamclient = + reinterpret_cast(g_state.steamclient_iface); + bool ok = steamclient->BReleaseSteamPipe(g_state.pipe); + LOGI("nativeShutdown: ISteamClient.BReleaseSteamPipe(%d) -> %d", + g_state.pipe, ok ? 1 : 0); + } else { + LOGW("nativeShutdown: no BReleaseSteamPipe entry point available"); + } + } + // Roll back every env var nativeInit set. The Android process outlives + // a single wine launch; without this pass a subsequent Launch-Steam-Client + // (real Steam) launch in the same process inherits the bionic env keys + // (WINESTEAMCLIENTPATH, Steam3Master, SteamUser, …) and steam.exe gets + // confused / hits "Steam installation problem". + for (const auto& k : g_state.applied_env_keys) { + ::unsetenv(k.c_str()); + LOGI("unsetenv %s", k.c_str()); + } + g_state.applied_env_keys.clear(); + g_state.lsc_handle = nullptr; + g_state.pipe = 0; + g_state.user = 0; + g_state.iclient_user = nullptr; + g_state.steamclient_iface = nullptr; + g_state.isteam_user = nullptr; + g_state.isteam_utils = nullptr; + g_state.isteam_userstats = nullptr; + g_state.isteam_apps = nullptr; + g_state.isteam_remotestorage = nullptr; + g_state.isteam_friends = nullptr; + g_state.isteam_apps_ver = nullptr; + g_state.isteam_rs_ver = nullptr; + g_state.isteam_friends_ver = nullptr; + g_state.cached_steam_id = 0; + g_state.subscribed_ids.clear(); + g_state.received_callbacks.clear(); + g_state.cv_callback.notify_all(); + g_state.fn_CreateInterface = nullptr; + g_state.fn_Steam_CreateGlobalUser = nullptr; + g_state.fn_Steam_BLoggedOn = nullptr; + g_state.fn_Steam_LogOff = nullptr; + g_state.fn_Steam_ReleaseUser = nullptr; + g_state.fn_Steam_BReleaseSteamPipe = nullptr; + g_state.fn_Steam_BGetCallback = nullptr; + g_state.fn_Steam_FreeLastCallback = nullptr; + g_state.fn_Breakpad_SteamSetAppID = nullptr; + g_state.shutting_down = false; + LOGI("nativeShutdown done"); +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativePrepareApp( + JNIEnv* env, jclass /*cls*/, jintArray jappIds) { + // Phase 8b.6+: drive ISteamApps via the IClientEngine sub-interface to + // warm libsteamclient.so's own PICS cache for the given appids. For + // now we log and let the Rust wnsteam runtime's own prepareApp (Phase 4.5) + // do the heavy lifting. + if (!jappIds) return; + jsize n = env->GetArrayLength(jappIds); + LOGI("nativePrepareApp: %d ids (passed through to log; not yet wired " + "to libsteamclient.so PICS)", n); +} + +JNIEXPORT void JNICALL +Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamBootstrap_nativeSetCloudEnabled( + JNIEnv* /*env*/, jclass /*cls*/, jint app_id, jboolean enabled) { + // Phase 8b.6+: route through IClientRemoteStorage. Vtable slot needs + // additional RE; deferred. + LOGI("setCloudEnabled(app=%d, on=%d) — not yet wired", + app_id, enabled ? 1 : 0); +} + +} // extern "C" diff --git a/app/src/main/cpp/wn-steam-client/CMakeLists.txt b/app/src/main/cpp/wn-steam-client/CMakeLists.txt new file mode 100644 index 000000000..949f00483 --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/CMakeLists.txt @@ -0,0 +1,81 @@ +# ---------------------------------------------------------------------------- +# wn-steam-client +# Rust native Steam protocol client for WinNative. +# ---------------------------------------------------------------------------- + +cmake_minimum_required(VERSION 3.22.1) + +set(WN_STEAM_CLIENT_VERSION_MAJOR 0) +set(WN_STEAM_CLIENT_VERSION_MINOR 1) +set(WN_STEAM_CLIENT_VERSION_PATCH 0) +set(WN_STEAM_CLIENT_VERSION_STRING + "${WN_STEAM_CLIENT_VERSION_MAJOR}.${WN_STEAM_CLIENT_VERSION_MINOR}.${WN_STEAM_CLIENT_VERSION_PATCH}") + +find_program(CARGO_EXECUTABLE cargo REQUIRED) + +if(NOT CMAKE_ANDROID_ARCH_ABI STREQUAL "arm64-v8a") + message(FATAL_ERROR "Rust wnsteam currently supports arm64-v8a; got ${CMAKE_ANDROID_ARCH_ABI}") +endif() +if(NOT DEFINED ANDROID_NDK) + message(FATAL_ERROR "ANDROID_NDK not defined; Rust wnsteam must be built via Android Gradle/CMake") +endif() + +set(WNSTEAM_RUST_TARGET "aarch64-linux-android") +if(DEFINED ANDROID_PLATFORM) + string(REGEX REPLACE "^android-" "" WNSTEAM_ANDROID_API "${ANDROID_PLATFORM}") +elseif(DEFINED CMAKE_SYSTEM_VERSION) + set(WNSTEAM_ANDROID_API "${CMAKE_SYSTEM_VERSION}") +else() + set(WNSTEAM_ANDROID_API "26") +endif() + +if(CMAKE_HOST_WIN32) + set(WNSTEAM_NDK_HOST_TAG "windows-x86_64") + set(WNSTEAM_LINKER_SUFFIX ".cmd") + set(WNSTEAM_AR_SUFFIX ".exe") +elseif(CMAKE_HOST_APPLE) + set(WNSTEAM_NDK_HOST_TAG "darwin-x86_64") + set(WNSTEAM_LINKER_SUFFIX "") + set(WNSTEAM_AR_SUFFIX "") +else() + set(WNSTEAM_NDK_HOST_TAG "linux-x86_64") + set(WNSTEAM_LINKER_SUFFIX "") + set(WNSTEAM_AR_SUFFIX "") +endif() + +set(WNSTEAM_RUST_DIR "${CMAKE_CURRENT_SOURCE_DIR}/rust") +file(GLOB_RECURSE WNSTEAM_RUST_SOURCES CONFIGURE_DEPENDS + "${WNSTEAM_RUST_DIR}/src/*.rs") +set(WNSTEAM_RUST_BUILD_INPUTS + "${WNSTEAM_RUST_DIR}/Cargo.toml" + "${WNSTEAM_RUST_DIR}/Cargo.lock" + ${WNSTEAM_RUST_SOURCES}) +set(WNSTEAM_RUST_LIB + "${WNSTEAM_RUST_DIR}/target/${WNSTEAM_RUST_TARGET}/release/libwnsteam.so") +set(WNSTEAM_RUST_OUT "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/libwnsteam.so") +set(WNSTEAM_RUST_LINKER + "${ANDROID_NDK}/toolchains/llvm/prebuilt/${WNSTEAM_NDK_HOST_TAG}/bin/${WNSTEAM_RUST_TARGET}${WNSTEAM_ANDROID_API}-clang${WNSTEAM_LINKER_SUFFIX}") + +add_custom_command( + OUTPUT "${WNSTEAM_RUST_OUT}" + COMMAND "${CMAKE_COMMAND}" -E env + "CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER=${WNSTEAM_RUST_LINKER}" + "CC_aarch64_linux_android=${WNSTEAM_RUST_LINKER}" + "AR_aarch64_linux_android=${ANDROID_NDK}/toolchains/llvm/prebuilt/${WNSTEAM_NDK_HOST_TAG}/bin/llvm-ar${WNSTEAM_AR_SUFFIX}" + "${CARGO_EXECUTABLE}" rustc --release --target "${WNSTEAM_RUST_TARGET}" --lib -- + -Clink-arg=-Wl,-soname,libwnsteam.so + COMMAND "${CMAKE_COMMAND}" -E copy_if_different + "${WNSTEAM_RUST_LIB}" + "${WNSTEAM_RUST_OUT}" + WORKING_DIRECTORY "${WNSTEAM_RUST_DIR}" + DEPENDS ${WNSTEAM_RUST_BUILD_INPUTS} + COMMENT "Building Rust wnsteam for ${WNSTEAM_RUST_TARGET}" + VERBATIM +) + +add_custom_target(wnsteam_rust_build DEPENDS "${WNSTEAM_RUST_OUT}") +add_library(wnsteam SHARED IMPORTED GLOBAL) +set_target_properties(wnsteam PROPERTIES + IMPORTED_LOCATION "${WNSTEAM_RUST_OUT}" + IMPORTED_SONAME "libwnsteam.so") +add_dependencies(wnsteam wnsteam_rust_build) diff --git a/app/src/main/cpp/wn-steam-client/rust/.gitignore b/app/src/main/cpp/wn-steam-client/rust/.gitignore new file mode 100644 index 000000000..2f7896d1d --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/.gitignore @@ -0,0 +1 @@ +target/ diff --git a/app/src/main/cpp/wn-steam-client/rust/Cargo.lock b/app/src/main/cpp/wn-steam-client/rust/Cargo.lock new file mode 100644 index 000000000..3af23907c --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/Cargo.lock @@ -0,0 +1,2101 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "async-compression" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e79b3f8a79cccc2898f31920fc69f304859b3bd567490f75ebf51ae1c792a9ac" +dependencies = [ + "compression-codecs", + "compression-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + +[[package]] +name = "cc" +version = "1.2.62" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "compression-codecs" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce2548391e9c1929c21bf6aa2680af86fe4c1b33e6cea9ac1cfeec0bd11218cf" +dependencies = [ + "compression-core", + "flate2", + "memchr", +] + +[[package]] +name = "compression-core" +version = "0.4.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc14f565cf027a105f7a44ccf9e5b424348421a1d8952a8fc9d499d313107789" + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "data-encoding" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "http" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb92f162bf56536459fc83c79b974bb12837acfed43d6bc370a7916d0ae15ecc" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots 1.0.7", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys 0.3.1", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "js-sys" +version = "0.3.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "log" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5" + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "lzma-rs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "297e814c836ae64db86b36cf2a557ba54368d03f6afcd7d947c266692f71115e" +dependencies = [ + "byteorder", + "crc", +] + +[[package]] +name = "memchr" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" +dependencies = [ + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.6", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +dependencies = [ + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.4", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.60.2", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots 1.0.7", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rsa" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "signature", + "spki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + +[[package]] +name = "rustls" +version = "0.23.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ruzstd" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fad02996bfc73da3e301efe90b1837be9ed8f4a462b6ed410aa35d00381de89f" +dependencies = [ + "twox-hash", +] + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "indexmap", + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "async-compression", + "bitflags", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "pin-project-lite", + "tokio", + "tokio-util", + "tower", + "tower-layer", + "tower-service", + "url", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "tungstenite" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand 0.9.4", + "rustls", + "rustls-pki-types", + "sha1", + "thiserror 2.0.18", + "utf-8", + "webpki-roots 0.26.11", +] + +[[package]] +name = "twox-hash" +version = "1.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" +dependencies = [ + "cfg-if", + "static_assertions", +] + +[[package]] +name = "typenum" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.3+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9473dbd2991ae90b6291c3c32c30c6187ac49aa32f9905d1cce280ec1e110b0f" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621441cfc37b84979402712047321980c178f299193a3589d05b99e8763436" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.7", +] + +[[package]] +name = "webpki-roots" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wnsteam" +version = "0.1.0" +dependencies = [ + "aes", + "cbc", + "crc32fast", + "flate2", + "hmac", + "jni", + "lzma-rs", + "rand 0.8.6", + "reqwest", + "rsa", + "ruzstd", + "serde_json", + "sha1", + "sha2", + "tungstenite", + "zeroize", +] + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "yoke" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bce33a6288fa3f072a8c2c7d0f2fdbb90e28298f0135c1f99b96c3db2efcc60b" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd425244944f4ab65ccff928e7323354c5a018c75838362fdce749dfad2ee1e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/app/src/main/cpp/wn-steam-client/rust/Cargo.toml b/app/src/main/cpp/wn-steam-client/rust/Cargo.toml new file mode 100644 index 000000000..520bde59c --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "wnsteam" +version = "0.1.0" +edition = "2021" +license = "GPL-3.0-or-later" +publish = false + +[lib] +crate-type = ["rlib", "staticlib", "cdylib"] + +[dependencies] +aes = "0.8" +cbc = { version = "0.1", features = ["alloc", "block-padding"] } +crc32fast = "1" +hmac = "0.12" +jni = "0.21" +flate2 = { version = "1", default-features = false, features = ["rust_backend"] } +lzma-rs = { version = "0.3", features = ["raw_decoder"] } +rand = "0.8" +reqwest = { version = "0.12", default-features = false, features = ["blocking", "rustls-tls", "gzip"] } +rsa = "0.9" +ruzstd = "0.7" +# preserve_order keeps JSON object keys in source order. The Kotlin appinfo +# decoder groups DLC content depots positionally (a marker depot followed by +# its content), so reordering depots (BTreeMap's default key sort) misgroups +# base content depots and zeroes a game's download/install size. C++ emitted +# source order; match it. +serde_json = { version = "1", features = ["preserve_order"] } +sha1 = "0.10" +sha2 = "0.10" +tungstenite = { version = "0.26", default-features = false, features = ["handshake", "rustls-tls-webpki-roots"] } +zeroize = "1" + +[profile.release] +codegen-units = 1 +lto = "thin" +strip = "symbols" diff --git a/app/src/main/cpp/wn-steam-client/rust/src/auth_session.rs b/app/src/main/cpp/wn-steam-client/rust/src/auth_session.rs new file mode 100644 index 000000000..2dbe60712 --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/auth_session.rs @@ -0,0 +1,608 @@ +use crate::base64; +use crate::job_manager::JobResult; +use crate::pb::cauthentication::{ + AllowedConfirmation, BeginAuthSessionViaCredentialsRequest, + BeginAuthSessionViaCredentialsResponse, BeginAuthSessionViaQrRequest, + BeginAuthSessionViaQrResponse, EAuthSessionGuardType, EAuthTokenPlatformType, + ESessionPersistence, GetPasswordRsaPublicKeyRequest, GetPasswordRsaPublicKeyResponse, + PollAuthSessionStatusRequest, PollAuthSessionStatusResponse, + UpdateAuthSessionWithSteamGuardCodeRequest, +}; +use crate::rsa_password::rsa_pkcs1v15_encrypt_password_with_hex_key; +use std::time::Duration; + +pub const AUTH_WEBSITE_ID: &str = "Client"; +pub const DEFAULT_WINDOWS_OS_TYPE: i32 = 16; +pub const DEFAULT_POLL_INTERVAL_SECONDS: f32 = 5.0; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct AuthSessionResult { + pub success: bool, + pub eresult: i32, + pub error_message: String, + pub account_name: String, + pub refresh_token: String, + pub access_token: String, + pub new_guard_data: String, + pub steamid: u64, + pub had_remote_interaction: bool, + pub agreement_session_url: String, +} + +impl Default for AuthSessionResult { + fn default() -> Self { + Self { + success: false, + eresult: 2, + error_message: String::new(), + account_name: String::new(), + refresh_token: String::new(), + access_token: String::new(), + new_guard_data: String::new(), + steamid: 0, + had_remote_interaction: false, + agreement_session_url: String::new(), + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct CredentialsAuthConfig { + pub username: String, + pub password: String, + pub device_friendly_name: String, + pub guard_data: String, + pub persistent_session: bool, +} + +impl Default for CredentialsAuthConfig { + fn default() -> Self { + Self { + username: String::new(), + password: String::new(), + device_friendly_name: "WN-Steam-Client".to_string(), + guard_data: String::new(), + persistent_session: true, + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct QrAuthConfig { + pub device_friendly_name: String, +} + +impl Default for QrAuthConfig { + fn default() -> Self { + Self { + device_friendly_name: "WN-Steam-Client".to_string(), + } + } +} + +pub fn secure_clear_string(s: &mut String) { + if !s.is_empty() { + // Zero bytes are valid UTF-8, so the String remains well-formed until + // `clear` drops its logical contents. + let bytes = unsafe { s.as_bytes_mut() }; + for byte in bytes { + *byte = 0; + } + } + s.clear(); +} + +pub fn sleep_slices(total: Duration, tick: Duration) -> impl Iterator { + let mut remaining = total; + std::iter::from_fn(move || { + if remaining.is_zero() { + return None; + } + let next = remaining.min(tick); + remaining -= next; + Some(next) + }) +} + +#[derive(Clone, Debug, PartialEq)] +pub struct PendingCredentialsAuthSession { + pub client_id: u64, + pub request_id: Vec, + pub poll_interval_seconds: f32, + pub allowed_confirmations: Vec, + pub steamid: u64, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct PendingQrAuthSession { + pub client_id: u64, + pub request_id: Vec, + pub poll_interval_seconds: f32, + pub challenge_url: String, +} + +pub fn build_password_rsa_request( + config: &CredentialsAuthConfig, +) -> GetPasswordRsaPublicKeyRequest { + GetPasswordRsaPublicKeyRequest { + account_name: config.username.clone(), + } +} + +pub fn build_credentials_begin_request( + config: &mut CredentialsAuthConfig, + key: &GetPasswordRsaPublicKeyResponse, +) -> Result { + let encrypted = rsa_pkcs1v15_encrypt_password_with_hex_key( + &config.password, + &key.publickey_mod, + &key.publickey_exp, + ); + secure_clear_string(&mut config.password); + let Some(encrypted) = encrypted else { + return Err(AuthSessionResult { + error_message: "password RSA encryption failed".to_string(), + ..Default::default() + }); + }; + + let guard_data = std::mem::take(&mut config.guard_data); + let mut req = BeginAuthSessionViaCredentialsRequest { + account_name: config.username.clone(), + encrypted_password: base64::encode(&encrypted), + encryption_timestamp: key.timestamp, + website_id: AUTH_WEBSITE_ID.to_string(), + persistence: if config.persistent_session { + ESessionPersistence::Persistent + } else { + ESessionPersistence::Ephemeral + }, + guard_data, + ..Default::default() + }; + req.device_details.device_friendly_name = config.device_friendly_name.clone(); + req.device_details.platform_type = EAuthTokenPlatformType::SteamClient; + req.device_details.os_type = DEFAULT_WINDOWS_OS_TYPE; + Ok(req) +} + +pub fn pending_credentials_from_begin_response( + resp: BeginAuthSessionViaCredentialsResponse, +) -> Result { + if resp.client_id == 0 || resp.request_id.is_empty() { + return Err(AuthSessionResult { + eresult: 5, + error_message: if resp.extended_error_message.is_empty() { + "Steam rejected the credentials (no auth session created - likely bad password or unrecognized device)" + .to_string() + } else { + resp.extended_error_message + }, + ..Default::default() + }); + } + Ok(PendingCredentialsAuthSession { + client_id: resp.client_id, + request_id: resp.request_id, + poll_interval_seconds: resp.interval, + allowed_confirmations: resp.allowed_confirmations, + steamid: resp.steamid, + }) +} + +pub fn choose_guard_confirmation(confirmations: &[AllowedConfirmation]) -> EAuthSessionGuardType { + confirmations + .iter() + .find(|confirmation| { + confirmation.confirmation_type == EAuthSessionGuardType::DeviceConfirmation + }) + .or_else(|| { + confirmations.iter().find(|confirmation| { + matches!( + confirmation.confirmation_type, + EAuthSessionGuardType::DeviceCode | EAuthSessionGuardType::EmailCode + ) + }) + }) + .or_else(|| { + confirmations + .iter() + .find(|confirmation| confirmation.confirmation_type == EAuthSessionGuardType::None) + }) + .map(|confirmation| confirmation.confirmation_type) + .unwrap_or(EAuthSessionGuardType::None) +} + +pub fn build_guard_code_request( + client_id: u64, + steamid: u64, + code_type: EAuthSessionGuardType, + code: String, +) -> UpdateAuthSessionWithSteamGuardCodeRequest { + UpdateAuthSessionWithSteamGuardCodeRequest { + client_id, + steamid, + code, + code_type, + } +} + +pub fn guard_update_succeeded(job: &JobResult) -> bool { + !job.synthetic_failure && matches!(job.eresult, 1 | 29) +} + +pub fn build_poll_request(client_id: u64, request_id: Vec) -> PollAuthSessionStatusRequest { + PollAuthSessionStatusRequest { + client_id, + request_id, + token_to_revoke: 0, + } +} + +pub fn auth_result_from_poll( + resp: PollAuthSessionStatusResponse, + steamid: u64, +) -> Option { + if resp.refresh_token.is_empty() { + return None; + } + Some(AuthSessionResult { + success: true, + eresult: 1, + account_name: resp.account_name, + refresh_token: resp.refresh_token, + access_token: resp.access_token, + new_guard_data: resp.new_guard_data, + steamid, + had_remote_interaction: resp.had_remote_interaction, + agreement_session_url: resp.agreement_session_url, + ..Default::default() + }) +} + +pub fn apply_account_name_fallback( + result: &mut AuthSessionResult, + fallback_account_name: &str, +) -> bool { + if result.account_name.is_empty() && !fallback_account_name.is_empty() { + result.account_name = fallback_account_name.to_string(); + return true; + } + false +} + +pub fn apply_new_client_id(current_client_id: &mut u64, new_client_id: u64) -> bool { + if new_client_id == 0 { + return false; + } + *current_client_id = new_client_id; + true +} + +pub fn take_qr_challenge_update( + last_challenge_url: &mut String, + resp: &PollAuthSessionStatusResponse, +) -> Option { + if resp.new_challenge_url.is_empty() || resp.new_challenge_url == *last_challenge_url { + return None; + } + *last_challenge_url = resp.new_challenge_url.clone(); + Some(last_challenge_url.clone()) +} + +pub fn take_qr_remote_interaction( + reported_remote_interaction: &mut bool, + resp: &PollAuthSessionStatusResponse, +) -> bool { + if *reported_remote_interaction || !resp.had_remote_interaction { + return false; + } + *reported_remote_interaction = true; + true +} + +pub fn build_qr_begin_request(config: &QrAuthConfig) -> BeginAuthSessionViaQrRequest { + let mut req = BeginAuthSessionViaQrRequest { + device_friendly_name: config.device_friendly_name.clone(), + platform_type: EAuthTokenPlatformType::SteamClient, + website_id: AUTH_WEBSITE_ID.to_string(), + ..Default::default() + }; + req.device_details.device_friendly_name = config.device_friendly_name.clone(); + req.device_details.platform_type = EAuthTokenPlatformType::SteamClient; + req.device_details.os_type = DEFAULT_WINDOWS_OS_TYPE; + req +} + +pub fn pending_qr_from_begin_response(resp: BeginAuthSessionViaQrResponse) -> PendingQrAuthSession { + PendingQrAuthSession { + client_id: resp.client_id, + request_id: resp.request_id, + poll_interval_seconds: resp.interval, + challenge_url: resp.challenge_url, + } +} + +pub fn job_error(job: JobResult, default_message: &'static str) -> AuthSessionResult { + AuthSessionResult { + eresult: job.eresult, + error_message: if job.error_message.is_empty() { + default_message.to_string() + } else { + job.error_message + }, + ..Default::default() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::proto_wire::Writer; + use rand::rngs::OsRng; + use rsa::traits::PublicKeyParts; + + #[test] + fn default_auth_configs_match_cpp_defaults() { + assert_eq!( + CredentialsAuthConfig::default().device_friendly_name, + "WN-Steam-Client" + ); + assert!(CredentialsAuthConfig::default().persistent_session); + assert_eq!( + QrAuthConfig::default().device_friendly_name, + "WN-Steam-Client" + ); + } + + #[test] + fn sleep_slices_cover_total_duration() { + let slices: Vec<_> = + sleep_slices(Duration::from_millis(250), Duration::from_millis(100)).collect(); + assert_eq!( + slices, + [ + Duration::from_millis(100), + Duration::from_millis(100), + Duration::from_millis(50) + ] + ); + } + + #[test] + fn credentials_begin_request_matches_desktop_client_shape() { + let private = rsa::RsaPrivateKey::new(&mut OsRng, 1024).unwrap(); + let public = private.to_public_key(); + let key = GetPasswordRsaPublicKeyResponse { + publickey_mod: hex_encode(&public.n().to_bytes_be()), + publickey_exp: hex_encode(&public.e().to_bytes_be()), + timestamp: 123, + }; + let mut config = CredentialsAuthConfig { + username: "ada".into(), + password: "correct horse".into(), + device_friendly_name: "WN".into(), + guard_data: "guard".into(), + persistent_session: false, + }; + + let req = build_credentials_begin_request(&mut config, &key).unwrap(); + + assert_eq!(req.account_name, "ada"); + assert_eq!(req.encryption_timestamp, 123); + assert!(!req.encrypted_password.is_empty()); + assert_eq!(req.website_id, "Client"); + assert_eq!(req.persistence, ESessionPersistence::Ephemeral); + assert_eq!( + req.device_details.platform_type, + EAuthTokenPlatformType::SteamClient + ); + assert_eq!(req.device_details.os_type, DEFAULT_WINDOWS_OS_TYPE); + assert_eq!(req.guard_data, "guard"); + assert!(config.password.is_empty()); + assert!(config.guard_data.is_empty()); + } + + #[test] + fn begin_response_rejects_empty_session_like_cpp() { + let err = pending_credentials_from_begin_response(BeginAuthSessionViaCredentialsResponse { + interval: 1.0, + ..Default::default() + }) + .unwrap_err(); + assert_eq!(err.eresult, 5); + assert!(err.error_message.contains("Steam rejected")); + } + + #[test] + fn guard_confirmation_preference_matches_cpp() { + let confirmations = [ + AllowedConfirmation { + confirmation_type: EAuthSessionGuardType::EmailCode, + associated_message: String::new(), + }, + AllowedConfirmation { + confirmation_type: EAuthSessionGuardType::DeviceConfirmation, + associated_message: String::new(), + }, + ]; + assert_eq!( + choose_guard_confirmation(&confirmations), + EAuthSessionGuardType::DeviceConfirmation + ); + + let confirmations = [AllowedConfirmation { + confirmation_type: EAuthSessionGuardType::DeviceCode, + associated_message: String::new(), + }]; + assert_eq!( + choose_guard_confirmation(&confirmations), + EAuthSessionGuardType::DeviceCode + ); + assert_eq!(choose_guard_confirmation(&[]), EAuthSessionGuardType::None); + } + + #[test] + fn qr_begin_request_uses_steam_client_audience() { + let req = build_qr_begin_request(&QrAuthConfig { + device_friendly_name: "Deck".into(), + }); + assert_eq!(req.device_friendly_name, "Deck"); + assert_eq!(req.platform_type, EAuthTokenPlatformType::SteamClient); + assert_eq!(req.website_id, "Client"); + assert_eq!( + req.device_details.platform_type, + EAuthTokenPlatformType::SteamClient + ); + assert_eq!(req.device_details.os_type, DEFAULT_WINDOWS_OS_TYPE); + } + + #[test] + fn poll_response_becomes_success_only_when_refresh_token_exists() { + assert!(auth_result_from_poll(PollAuthSessionStatusResponse::default(), 123).is_none()); + let result = auth_result_from_poll( + PollAuthSessionStatusResponse { + refresh_token: "refresh".into(), + access_token: "access".into(), + account_name: "ada".into(), + new_guard_data: "guard".into(), + had_remote_interaction: true, + agreement_session_url: "https://steam.example/agreement".into(), + ..Default::default() + }, + 765, + ) + .unwrap(); + assert!(result.success); + assert_eq!(result.steamid, 765); + assert_eq!(result.account_name, "ada"); + assert!(result.had_remote_interaction); + } + + #[test] + fn account_name_fallback_restores_credentials_username() { + let mut result = auth_result_from_poll( + PollAuthSessionStatusResponse { + refresh_token: "refresh".into(), + access_token: "access".into(), + ..Default::default() + }, + 765, + ) + .unwrap(); + + assert!(apply_account_name_fallback(&mut result, "ada")); + assert_eq!(result.account_name, "ada"); + assert!(!apply_account_name_fallback(&mut result, "ignored")); + assert_eq!(result.account_name, "ada"); + } + + #[test] + fn poll_updates_client_id_and_qr_challenge_like_cpp() { + let mut client_id = 10; + assert!(!apply_new_client_id(&mut client_id, 0)); + assert_eq!(client_id, 10); + assert!(apply_new_client_id(&mut client_id, 20)); + assert_eq!(client_id, 20); + + let mut last = "old".to_string(); + assert_eq!( + take_qr_challenge_update( + &mut last, + &PollAuthSessionStatusResponse { + new_challenge_url: "new".into(), + ..Default::default() + } + ), + Some("new".to_string()) + ); + assert_eq!(last, "new"); + assert_eq!( + take_qr_challenge_update( + &mut last, + &PollAuthSessionStatusResponse { + new_challenge_url: "new".into(), + ..Default::default() + } + ), + None + ); + } + + #[test] + fn poll_reports_remote_interaction_only_once() { + let mut reported = false; + assert!(!take_qr_remote_interaction( + &mut reported, + &PollAuthSessionStatusResponse::default() + )); + assert!(take_qr_remote_interaction( + &mut reported, + &PollAuthSessionStatusResponse { + had_remote_interaction: true, + ..Default::default() + } + )); + assert!(reported); + assert!(!take_qr_remote_interaction( + &mut reported, + &PollAuthSessionStatusResponse { + had_remote_interaction: true, + ..Default::default() + } + )); + } + + #[test] + fn guard_update_accepts_duplicate_request() { + assert!(guard_update_succeeded(&JobResult { + eresult: 29, + error_message: String::new(), + body: Vec::new(), + synthetic_failure: false, + })); + assert!(!guard_update_succeeded(&JobResult { + eresult: 29, + error_message: String::new(), + body: Vec::new(), + synthetic_failure: true, + })); + } + + #[test] + fn request_builders_serialize_expected_identity_fields() { + let rsa_req = build_password_rsa_request(&CredentialsAuthConfig { + username: "user".into(), + ..Default::default() + }); + assert_eq!(rsa_req.serialize(), { + let mut out = Vec::new(); + Writer::new(&mut out).string_field(1, "user"); + out + }); + + let poll = build_poll_request(42, vec![1, 2, 3]); + assert_eq!(poll.client_id, 42); + assert_eq!(poll.request_id, [1, 2, 3]); + + let guard = build_guard_code_request( + 7, + 765, + EAuthSessionGuardType::EmailCode, + "12345".to_string(), + ); + assert_eq!(guard.client_id, 7); + assert_eq!(guard.steamid, 765); + assert_eq!(guard.code_type, EAuthSessionGuardType::EmailCode); + } + + fn hex_encode(bytes: &[u8]) -> String { + const HEX: &[u8; 16] = b"0123456789abcdef"; + let mut out = String::with_capacity(bytes.len() * 2); + for b in bytes { + out.push(HEX[(b >> 4) as usize] as char); + out.push(HEX[(b & 0x0f) as usize] as char); + } + out + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/authenticator.rs b/app/src/main/cpp/wn-steam-client/rust/src/authenticator.rs new file mode 100644 index 000000000..e5e6ce0d8 --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/authenticator.rs @@ -0,0 +1,10 @@ +pub trait Authenticator: Send + Sync { + fn accept_device_confirmation(&self, cb: Box); + fn get_device_code(&self, previous_was_incorrect: bool, cb: Box); + fn get_email_code( + &self, + email: String, + previous_was_incorrect: bool, + cb: Box, + ); +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/base64.rs b/app/src/main/cpp/wn-steam-client/rust/src/base64.rs new file mode 100644 index 000000000..168fcb660 --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/base64.rs @@ -0,0 +1,97 @@ +const ALPHABET: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +pub fn encode(bytes: &[u8]) -> String { + if bytes.is_empty() { + return String::new(); + } + + let mut out = String::with_capacity(bytes.len().div_ceil(3) * 4); + let mut chunks = bytes.chunks_exact(3); + for chunk in &mut chunks { + let v = ((chunk[0] as u32) << 16) | ((chunk[1] as u32) << 8) | chunk[2] as u32; + out.push(ALPHABET[((v >> 18) & 0x3f) as usize] as char); + out.push(ALPHABET[((v >> 12) & 0x3f) as usize] as char); + out.push(ALPHABET[((v >> 6) & 0x3f) as usize] as char); + out.push(ALPHABET[(v & 0x3f) as usize] as char); + } + + match chunks.remainder() { + [a] => { + let v = (*a as u32) << 16; + out.push(ALPHABET[((v >> 18) & 0x3f) as usize] as char); + out.push(ALPHABET[((v >> 12) & 0x3f) as usize] as char); + out.push('='); + out.push('='); + } + [a, b] => { + let v = ((*a as u32) << 16) | ((*b as u32) << 8); + out.push(ALPHABET[((v >> 18) & 0x3f) as usize] as char); + out.push(ALPHABET[((v >> 12) & 0x3f) as usize] as char); + out.push(ALPHABET[((v >> 6) & 0x3f) as usize] as char); + out.push('='); + } + [] => {} + _ => unreachable!(), + } + + out +} + +pub fn decode(s: &str) -> Option> { + let mut out = Vec::with_capacity((s.len() / 4) * 3 + 3); + let mut acc = 0u32; + let mut bits = 0u32; + + for b in s.bytes() { + let v = value_of(b)?; + let Some(v) = v else { + continue; + }; + acc = (acc << 6) | v as u32; + bits += 6; + if bits >= 8 { + bits -= 8; + out.push(((acc >> bits) & 0xff) as u8); + } + } + + Some(out) +} + +fn value_of(b: u8) -> Option> { + match b { + b'A'..=b'Z' => Some(Some(b - b'A')), + b'a'..=b'z' => Some(Some(b - b'a' + 26)), + b'0'..=b'9' => Some(Some(b - b'0' + 52)), + b'+' | b'-' => Some(Some(62)), + b'/' | b'_' => Some(Some(63)), + b'=' | b' ' | b'\t' | b'\n' | b'\r' => Some(None), + _ => None, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn encodes_rfc4648_padding() { + assert_eq!(encode(b""), ""); + assert_eq!(encode(b"f"), "Zg=="); + assert_eq!(encode(b"fo"), "Zm8="); + assert_eq!(encode(b"foo"), "Zm9v"); + assert_eq!(encode(b"foobar"), "Zm9vYmFy"); + } + + #[test] + fn decodes_standard_urlsafe_and_whitespace() { + assert_eq!(decode("Zm9v YmFy"), Some(b"foobar".to_vec())); + assert_eq!(decode("SGVsbG8td29ybGQ_"), Some(b"Hello-world?".to_vec())); + assert_eq!(decode("Zg=="), Some(b"f".to_vec())); + } + + #[test] + fn invalid_character_fails() { + assert_eq!(decode("Zm9v*"), None); + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/cdn_client.rs b/app/src/main/cpp/wn-steam-client/rust/src/cdn_client.rs new file mode 100644 index 000000000..9c5bc9b56 --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/cdn_client.rs @@ -0,0 +1,728 @@ +use crate::pb::ccontentserverdirectory::CContentServerDirectoryServerInfo; +use flate2::read::{DeflateDecoder, GzDecoder}; +use std::fs; +use std::io::Read; +use std::time::Duration; + +pub const USER_AGENT: &str = "Valve/Steam HTTP Client 1.0"; + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct CdnManifestResult { + pub raw_manifest: Vec, + pub error: String, + pub http_status: i32, +} + +impl CdnManifestResult { + pub fn ok(&self) -> bool { + self.error.is_empty() && !self.raw_manifest.is_empty() + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct CdnChunkResult { + pub data: Vec, + pub error: String, + pub http_status: i32, +} + +pub struct CdnConnection { + client: Option, + valid: bool, +} + +impl Default for CdnConnection { + fn default() -> Self { + Self::new() + } +} + +impl CdnConnection { + pub fn new() -> Self { + Self { + client: None, + valid: true, + } + } + + pub fn invalid() -> Self { + Self { + client: None, + valid: false, + } + } + + pub fn valid(&self) -> bool { + self.valid + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct CdnClient { + ca_bundle_path: String, +} + +impl CdnClient { + pub fn new(ca_bundle_path: impl Into) -> Self { + Self { + ca_bundle_path: ca_bundle_path.into(), + } + } + + pub fn ca_bundle_path(&self) -> &str { + &self.ca_bundle_path + } + + pub fn open_connection(&self) -> CdnConnection { + CdnConnection::new() + } + + pub fn build_manifest_url( + &self, + server: &CContentServerDirectoryServerInfo, + depot_id: u32, + manifest_id: u64, + request_code: u64, + cdn_auth_token: &str, + ) -> Result { + let host = preferred_host(server).ok_or_else(|| "cdn server has no host".to_string())?; + let mut url = format!( + "{}://{}:{}/depot/{}/manifest/{}/5", + if server.use_https() { "https" } else { "http" }, + host, + server.port(), + depot_id, + manifest_id + ); + if request_code != 0 { + url.push('/'); + url.push_str(&request_code.to_string()); + } + append_auth_query(&mut url, cdn_auth_token); + Ok(url) + } + + pub fn build_chunk_url( + &self, + server: &CContentServerDirectoryServerInfo, + depot_id: u32, + chunk_sha: &[u8], + cdn_auth_token: &str, + ) -> Result { + if chunk_sha.is_empty() { + return Err("empty chunk sha".to_string()); + } + let host = preferred_host(server).ok_or_else(|| "cdn server has no host".to_string())?; + let mut url = format!( + "{}://{}:{}/depot/{}/chunk/{}", + if server.use_https() { "https" } else { "http" }, + host, + server.port(), + depot_id, + hex_encode(chunk_sha) + ); + append_auth_query(&mut url, cdn_auth_token); + Ok(url) + } + + pub fn build_item_def_archive_url(app_id: u32, digest: &str) -> String { + format!( + "https://api.steampowered.com/IGameInventory/GetItemDefArchive/v1/?appid={app_id}&digest={}", + percent_encode_query_value(digest) + ) + } + + pub fn fetch_manifest( + &self, + server: &CContentServerDirectoryServerInfo, + depot_id: u32, + manifest_id: u64, + request_code: u64, + cdn_auth_token: &str, + timeout: Duration, + ) -> CdnManifestResult { + let url = match self.build_manifest_url( + server, + depot_id, + manifest_id, + request_code, + cdn_auth_token, + ) { + Ok(url) => url, + Err(error) => { + return CdnManifestResult { + error, + ..Default::default() + } + } + }; + match self.http_get(&url, timeout) { + Ok(response) => CdnClient::validate_manifest_response( + response.http_status, + response.body, + response.content_length, + ), + Err(error) => CdnManifestResult { + error, + ..Default::default() + }, + } + } + + pub fn fetch_chunk( + &self, + server: &CContentServerDirectoryServerInfo, + depot_id: u32, + chunk_sha: &[u8], + cdn_auth_token: &str, + timeout: Duration, + ) -> CdnChunkResult { + let url = match self.build_chunk_url(server, depot_id, chunk_sha, cdn_auth_token) { + Ok(url) => url, + Err(error) => { + return CdnChunkResult { + error, + ..Default::default() + } + } + }; + match self.http_get(&url, timeout) { + Ok(response) => CdnClient::validate_chunk_response( + response.http_status, + response.body, + response.content_length, + ), + Err(error) => CdnChunkResult { + error, + ..Default::default() + }, + } + } + + pub fn fetch_chunk_with_connection( + &self, + conn: &mut CdnConnection, + server: &CContentServerDirectoryServerInfo, + depot_id: u32, + chunk_sha: &[u8], + cdn_auth_token: &str, + timeout: Duration, + ) -> CdnChunkResult { + let url = match self.build_chunk_url(server, depot_id, chunk_sha, cdn_auth_token) { + Ok(url) => url, + Err(error) => { + return CdnChunkResult { + error, + ..Default::default() + } + } + }; + let client = match self.ensure_connection(conn) { + Ok(client) => client, + Err(error) => { + return CdnChunkResult { + error, + ..Default::default() + } + } + }; + match self.http_get_with_client(client, &url, timeout) { + Ok(response) => CdnClient::validate_chunk_response( + response.http_status, + response.body, + response.content_length, + ), + Err(error) => CdnChunkResult { + error, + ..Default::default() + }, + } + } + + pub fn fetch_item_def_archive( + &self, + app_id: u32, + digest: &str, + timeout: Duration, + ) -> Option> { + let url = CdnClient::build_item_def_archive_url(app_id, digest); + let response = self.http_get(&url, timeout).ok()?; + if response.http_status != 200 { + return None; + } + Some(CdnClient::strip_item_def_trailing_nul(response.body)) + } + + /// HTTP-delivered PICS appinfo URL (large appinfo the CM didn't inline). + pub fn build_appinfo_url(http_host: &str, app_id: u32, sha: &[u8]) -> String { + format!( + "http://{}/appinfo/{}/sha/{}.txt.gz", + http_host, + app_id, + hex_encode(sha) + ) + } + + /// Fetch + gunzip an HTTP-delivered appinfo blob (same VDF as inline `buffer`). + pub fn fetch_appinfo_with_connection( + &self, + conn: &mut CdnConnection, + http_host: &str, + app_id: u32, + sha: &[u8], + timeout: Duration, + ) -> Option> { + if http_host.is_empty() || sha.is_empty() { + return None; + } + let url = CdnClient::build_appinfo_url(http_host, app_id, sha); + let client = self.ensure_connection(conn).ok()?; + let response = self.http_get_with_client(client, &url, timeout).ok()?; + if response.http_status != 200 || response.body.is_empty() { + return None; + } + Some(maybe_gunzip(response.body)) + } + + pub fn validate_manifest_response( + http_status: i32, + body: Vec, + content_length: Option, + ) -> CdnManifestResult { + if http_status != 200 { + return CdnManifestResult { + http_status, + error: "non-200 HTTP status".to_string(), + ..Default::default() + }; + } + if let Some(expected) = content_length { + if body.len() as u64 != expected { + return CdnManifestResult { + http_status, + error: "manifest body truncated (length mismatch)".to_string(), + ..Default::default() + }; + } + } + let Some(raw_manifest) = unzip_first_entry(&body) else { + return CdnManifestResult { + http_status, + error: "manifest unzip failed".to_string(), + ..Default::default() + }; + }; + CdnManifestResult { + raw_manifest, + error: String::new(), + http_status, + } + } + + pub fn validate_chunk_response( + http_status: i32, + body: Vec, + content_length: Option, + ) -> CdnChunkResult { + if http_status != 200 { + return CdnChunkResult { + http_status, + error: "non-200 HTTP status".to_string(), + ..Default::default() + }; + } + if let Some(expected) = content_length { + if body.len() as u64 != expected { + return CdnChunkResult { + http_status, + error: "chunk body truncated (length mismatch)".to_string(), + ..Default::default() + }; + } + } + CdnChunkResult { + data: body, + error: String::new(), + http_status, + } + } + + pub fn validate_connection(conn: &CdnConnection) -> Result<(), CdnChunkResult> { + if conn.valid() { + Ok(()) + } else { + Err(CdnChunkResult { + error: "cdn connection invalid".to_string(), + ..Default::default() + }) + } + } + + pub fn strip_item_def_trailing_nul(mut body: Vec) -> Vec { + if body.last() == Some(&0) { + body.pop(); + } + body + } + + pub fn default_timeout() -> Duration { + Duration::from_secs(30) + } + + pub fn connect_timeout() -> Duration { + Duration::from_secs(15) + } + + fn http_get(&self, url: &str, timeout: Duration) -> Result { + let client = self.http_client()?; + self.http_get_with_client(&client, url, timeout) + } + + fn http_get_with_client( + &self, + client: &reqwest::blocking::Client, + url: &str, + timeout: Duration, + ) -> Result { + let response = client + .get(url) + .timeout(timeout) + .send() + .map_err(|err| format!("http get: {err}"))?; + let http_status = response.status().as_u16() as i32; + let content_length = response.content_length(); + let body = response + .bytes() + .map_err(|err| format!("http body: {err}"))? + .to_vec(); + Ok(HttpResponse { + http_status, + content_length, + body, + }) + } + + fn http_client(&self) -> Result { + let mut builder = reqwest::blocking::Client::builder() + .user_agent(USER_AGENT) + .connect_timeout(CdnClient::connect_timeout()); + if !self.ca_bundle_path.is_empty() { + let pem = + fs::read(&self.ca_bundle_path).map_err(|err| format!("read CA bundle: {err}"))?; + let certs = reqwest::Certificate::from_pem_bundle(&pem) + .map_err(|err| format!("parse CA bundle: {err}"))?; + for cert in certs { + builder = builder.add_root_certificate(cert); + } + } + builder.build().map_err(|err| format!("http client: {err}")) + } + + fn ensure_connection<'a>( + &self, + conn: &'a mut CdnConnection, + ) -> Result<&'a reqwest::blocking::Client, String> { + if let Err(result) = CdnClient::validate_connection(conn) { + return Err(result.error); + } + if conn.client.is_none() { + conn.client = Some(self.http_client()?); + } + conn.client + .as_ref() + .ok_or_else(|| "cdn connection not initialized".to_string()) + } +} + +struct HttpResponse { + http_status: i32, + content_length: Option, + body: Vec, +} + +impl CdnChunkResult { + pub fn ok(&self) -> bool { + self.error.is_empty() && !self.data.is_empty() + } +} + +pub fn unzip_first_entry(zip: &[u8]) -> Option> { + if zip.len() < 30 || read_u32(zip, 0)? != 0x0403_4b50 { + return None; + } + let flags = read_u16(zip, 6)?; + let method = read_u16(zip, 8)?; + let comp_size = read_u32(zip, 18)? as usize; + let uncomp_size = read_u32(zip, 22)? as usize; + let name_len = read_u16(zip, 26)? as usize; + let extra_len = read_u16(zip, 28)? as usize; + + if (flags & 0x08) != 0 { + return None; + } + + let data_off = 30usize.checked_add(name_len)?.checked_add(extra_len)?; + if data_off.checked_add(comp_size)? > zip.len() { + return None; + } + let data = &zip[data_off..data_off + comp_size]; + + match method { + 0 => Some(data.to_vec()), + 8 => { + let mut out = Vec::with_capacity(uncomp_size); + DeflateDecoder::new(data).read_to_end(&mut out).ok()?; + Some(out) + } + _ => None, + } +} + +/// Gunzip; pass through already-inflated bytes (edge caches may decompress). +pub fn maybe_gunzip(body: Vec) -> Vec { + if body.len() >= 2 && body[0] == 0x1f && body[1] == 0x8b { + let mut out = Vec::new(); + if GzDecoder::new(&body[..]).read_to_end(&mut out).is_ok() && !out.is_empty() { + return out; + } + } + body +} + +pub fn hex_encode(bytes: &[u8]) -> String { + const HEX: &[u8; 16] = b"0123456789abcdef"; + let mut out = String::with_capacity(bytes.len() * 2); + for b in bytes { + out.push(HEX[(b >> 4) as usize] as char); + out.push(HEX[(b & 0x0f) as usize] as char); + } + out +} + +fn preferred_host(server: &CContentServerDirectoryServerInfo) -> Option<&str> { + if !server.vhost.is_empty() { + Some(&server.vhost) + } else if !server.host.is_empty() { + Some(&server.host) + } else { + None + } +} + +fn append_auth_query(url: &mut String, cdn_auth_token: &str) { + if cdn_auth_token.is_empty() { + return; + } + url.push('?'); + url.push_str(cdn_auth_token.strip_prefix('?').unwrap_or(cdn_auth_token)); +} + +fn percent_encode_query_value(value: &str) -> String { + let mut out = String::with_capacity(value.len()); + for byte in value.bytes() { + match byte { + b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => { + out.push(byte as char); + } + _ => { + const HEX: &[u8; 16] = b"0123456789ABCDEF"; + out.push('%'); + out.push(HEX[(byte >> 4) as usize] as char); + out.push(HEX[(byte & 0x0f) as usize] as char); + } + } + } + out +} + +fn read_u16(buf: &[u8], off: usize) -> Option { + Some(u16::from_le_bytes(buf.get(off..off + 2)?.try_into().ok()?)) +} + +fn read_u32(buf: &[u8], off: usize) -> Option { + Some(u32::from_le_bytes(buf.get(off..off + 4)?.try_into().ok()?)) +} + +#[cfg(test)] +mod tests { + use super::*; + use flate2::{write::DeflateEncoder, Compression}; + use std::io::Write; + + #[test] + fn extracts_stored_zip_entry() { + let zip = zip_with_entry(0, b"manifest bytes"); + assert_eq!(unzip_first_entry(&zip), Some(b"manifest bytes".to_vec())); + } + + #[test] + fn extracts_deflated_zip_entry() { + let mut encoder = DeflateEncoder::new(Vec::new(), Compression::default()); + encoder.write_all(b"manifest bytes").unwrap(); + let deflated = encoder.finish().unwrap(); + let zip = zip_with_compressed_entry(8, &deflated, b"manifest bytes".len() as u32); + assert_eq!(unzip_first_entry(&zip), Some(b"manifest bytes".to_vec())); + } + + #[test] + fn rejects_data_descriptor_zip_entry() { + let mut zip = zip_with_entry(0, b"x"); + zip[6] = 0x08; + assert_eq!(unzip_first_entry(&zip), None); + } + + #[test] + fn hex_encodes_lowercase() { + assert_eq!(hex_encode(&[0, 1, 0xab, 0xff]), "0001abff"); + } + + #[test] + fn builds_http_appinfo_url() { + assert_eq!( + CdnClient::build_appinfo_url("cache1.steamcontent.com", 601150, &[0xab, 0xcd, 0x01]), + "http://cache1.steamcontent.com/appinfo/601150/sha/abcd01.txt.gz" + ); + } + + #[test] + fn gunzips_gzip_and_passes_through_plain_vdf() { + use flate2::{write::GzEncoder, Compression}; + use std::io::Write; + let mut encoder = GzEncoder::new(Vec::new(), Compression::default()); + encoder.write_all(b"\"appinfo\"{}").unwrap(); + let gz = encoder.finish().unwrap(); + assert_eq!(maybe_gunzip(gz), b"\"appinfo\"{}"); + // Already-inflated payload (gzip magic absent) is returned verbatim. + assert_eq!(maybe_gunzip(b"\"appinfo\"{}".to_vec()), b"\"appinfo\"{}"); + } + + #[test] + fn builds_manifest_and_chunk_urls_like_cpp() { + let client = CdnClient::new("/cacert.pem"); + let server = CContentServerDirectoryServerInfo { + host: "edge.steamcontent.com".into(), + vhost: "cdn.example".into(), + https_support: "mandatory".into(), + ..Default::default() + }; + assert_eq!( + client + .build_manifest_url(&server, 100, 200, 300, "?token=abc") + .unwrap(), + "https://cdn.example:443/depot/100/manifest/200/5/300?token=abc" + ); + assert_eq!( + client + .build_chunk_url(&server, 100, &[0xab, 0xcd], "token=abc") + .unwrap(), + "https://cdn.example:443/depot/100/chunk/abcd?token=abc" + ); + assert_eq!(client.ca_bundle_path(), "/cacert.pem"); + assert_eq!(CdnClient::default_timeout(), Duration::from_secs(30)); + assert_eq!(CdnClient::connect_timeout(), Duration::from_secs(15)); + } + + #[test] + fn validates_http_status_lengths_and_manifest_zip() { + let zip = zip_with_entry(0, b"manifest bytes"); + let ok = CdnClient::validate_manifest_response(200, zip.clone(), Some(zip.len() as u64)); + assert!(ok.ok()); + assert_eq!(ok.raw_manifest, b"manifest bytes"); + + let short = CdnClient::validate_manifest_response(200, zip, Some(999)); + assert_eq!(short.error, "manifest body truncated (length mismatch)"); + + let chunk = CdnClient::validate_chunk_response(200, vec![1, 2, 3], Some(3)); + assert!(chunk.ok()); + assert_eq!( + CdnClient::validate_chunk_response(404, vec![], None).error, + "non-200 HTTP status" + ); + } + + #[test] + fn itemdef_url_escapes_digest_and_strips_nul() { + assert_eq!( + CdnClient::build_item_def_archive_url(480, "abc+/="), + "https://api.steampowered.com/IGameInventory/GetItemDefArchive/v1/?appid=480&digest=abc%2B%2F%3D" + ); + assert_eq!( + CdnClient::strip_item_def_trailing_nul(b"[{\"x\":1}]\0".to_vec()), + b"[{\"x\":1}]" + ); + } + + #[test] + fn persistent_connection_state_matches_cpp_validity_contract() { + assert!(CdnClient::validate_connection(&CdnConnection::new()).is_ok()); + assert_eq!( + CdnClient::validate_connection(&CdnConnection::invalid()) + .unwrap_err() + .error, + "cdn connection invalid" + ); + } + + #[test] + fn persistent_connection_lazily_initializes_and_reuses_client() { + let client = CdnClient::new(""); + let server = CContentServerDirectoryServerInfo { + host: "127.0.0.1".into(), + ..Default::default() + }; + let mut conn = client.open_connection(); + assert!(conn.client.is_none()); + + let _ = client.fetch_chunk_with_connection( + &mut conn, + &server, + 100, + &[0xab, 0xcd], + "", + Duration::from_millis(50), + ); + assert!(conn.client.is_some()); + + let first_client = conn + .client + .as_ref() + .map(|inner| inner as *const reqwest::blocking::Client) + .unwrap(); + let _ = client.fetch_chunk_with_connection( + &mut conn, + &server, + 100, + &[0xab, 0xcd], + "", + Duration::from_millis(50), + ); + let second_client = conn + .client + .as_ref() + .map(|inner| inner as *const reqwest::blocking::Client) + .unwrap(); + assert_eq!(first_client, second_client); + } + + fn zip_with_entry(method: u16, data: &[u8]) -> Vec { + zip_with_compressed_entry(method, data, data.len() as u32) + } + + fn zip_with_compressed_entry(method: u16, data: &[u8], uncomp_size: u32) -> Vec { + let mut out = Vec::new(); + out.extend_from_slice(&0x0403_4b50u32.to_le_bytes()); + out.extend_from_slice(&20u16.to_le_bytes()); + out.extend_from_slice(&0u16.to_le_bytes()); + out.extend_from_slice(&method.to_le_bytes()); + out.extend_from_slice(&0u16.to_le_bytes()); + out.extend_from_slice(&0u16.to_le_bytes()); + out.extend_from_slice(&0u32.to_le_bytes()); + out.extend_from_slice(&(data.len() as u32).to_le_bytes()); + out.extend_from_slice(&uncomp_size.to_le_bytes()); + out.extend_from_slice(&1u16.to_le_bytes()); + out.extend_from_slice(&0u16.to_le_bytes()); + out.extend_from_slice(b"x"); + out.extend_from_slice(data); + out + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/chat_image.rs b/app/src/main/cpp/wn-steam-client/rust/src/chat_image.rs new file mode 100644 index 000000000..8ecd155a4 --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/chat_image.rs @@ -0,0 +1,239 @@ +use sha1::{Digest, Sha1}; +use std::time::Duration; + +const COMMUNITY: &str = "https://steamcommunity.com"; + +fn sha1_hex(bytes: &[u8]) -> String { + let mut hasher = Sha1::new(); + hasher.update(bytes); + hasher + .finalize() + .iter() + .map(|b| format!("{:02x}", b)) + .collect() +} + +fn random_sessionid() -> String { + let mut bytes = [0u8; 12]; + rand::Rng::fill(&mut rand::thread_rng(), &mut bytes[..]); + bytes.iter().map(|b| format!("{:02x}", b)).collect() +} + +/// Best-effort image dimensions for PNG/JPEG/GIF without an image crate. +fn image_dimensions(bytes: &[u8]) -> (u32, u32) { + if bytes.len() > 24 && &bytes[0..8] == b"\x89PNG\r\n\x1a\n" { + let w = u32::from_be_bytes([bytes[16], bytes[17], bytes[18], bytes[19]]); + let h = u32::from_be_bytes([bytes[20], bytes[21], bytes[22], bytes[23]]); + return (w, h); + } + if bytes.len() > 10 && (&bytes[0..6] == b"GIF89a" || &bytes[0..6] == b"GIF87a") { + let w = u16::from_le_bytes([bytes[6], bytes[7]]) as u32; + let h = u16::from_le_bytes([bytes[8], bytes[9]]) as u32; + return (w, h); + } + if bytes.len() > 4 && bytes[0] == 0xFF && bytes[1] == 0xD8 { + let mut i = 2usize; + while i + 9 < bytes.len() { + if bytes[i] != 0xFF { + i += 1; + continue; + } + let marker = bytes[i + 1]; + if (0xC0..=0xCF).contains(&marker) + && marker != 0xC4 + && marker != 0xC8 + && marker != 0xCC + { + let h = u16::from_be_bytes([bytes[i + 5], bytes[i + 6]]) as u32; + let w = u16::from_be_bytes([bytes[i + 7], bytes[i + 8]]) as u32; + return (w, h); + } + let len = u16::from_be_bytes([bytes[i + 2], bytes[i + 3]]) as usize; + if len < 2 { + break; + } + i += 2 + len; + } + } + (0, 0) +} + +fn content_type(bytes: &[u8]) -> &'static str { + if bytes.len() > 8 && &bytes[0..8] == b"\x89PNG\r\n\x1a\n" { + "image/png" + } else if bytes.len() > 3 && bytes[0] == 0xFF && bytes[1] == 0xD8 { + "image/jpeg" + } else if bytes.len() > 6 && (&bytes[0..6] == b"GIF89a" || &bytes[0..6] == b"GIF87a") { + "image/gif" + } else if bytes.len() > 12 && &bytes[0..4] == b"RIFF" && &bytes[8..12] == b"WEBP" { + "image/webp" + } else { + "image/png" + } +} + +fn build_client(ca_bundle_path: &str) -> Result { + let mut builder = reqwest::blocking::Client::builder() + .user_agent("Mozilla/5.0") + .connect_timeout(Duration::from_secs(15)); + if !ca_bundle_path.is_empty() { + if let Ok(pem) = std::fs::read(ca_bundle_path) { + if let Ok(certs) = reqwest::Certificate::from_pem_bundle(&pem) { + for cert in certs { + builder = builder.add_root_certificate(cert); + } + } + } + } + builder + .build() + .map_err(|err| format!("http client: {err}")) +} + +fn json_get_str(value: &serde_json::Value, key: &str) -> String { + value + .get(key) + .and_then(|v| { + if v.is_string() { + v.as_str().map(|s| s.to_string()) + } else if v.is_number() { + Some(v.to_string()) + } else { + None + } + }) + .unwrap_or_default() +} + +/// Uploads an image to Steam's chat UGC and returns the resulting image URL. +pub fn upload( + ca_bundle_path: &str, + self_steamid: u64, + friend_steamid: u64, + access_token: &str, + image: &[u8], + file_name: &str, +) -> Result { + if image.is_empty() { + return Err("image is empty".into()); + } + let client = build_client(ca_bundle_path)?; + let sessionid = random_sessionid(); + let cookie = format!( + "sessionid={sessionid}; steamLoginSecure={self_steamid}%7C%7C{access_token}" + ); + let sha = sha1_hex(image); + let (width, height) = image_dimensions(image); + let size = image.len().to_string(); + let width_s = width.to_string(); + let height_s = height.to_string(); + + let begin = client + .post(format!("{COMMUNITY}/chat/beginfileupload/?l=english")) + .header("Cookie", cookie.as_str()) + .header("Referer", format!("{COMMUNITY}/chat/")) + .header("Origin", COMMUNITY) + .form(&[ + ("sessionid", sessionid.as_str()), + ("l", "english"), + ("file_size", size.as_str()), + ("file_name", file_name), + ("file_sha", sha.as_str()), + ("file_image_width", width_s.as_str()), + ("file_image_height", height_s.as_str()), + ("file_type", content_type(image)), + ]) + .timeout(Duration::from_secs(30)) + .send() + .map_err(|err| format!("begin send: {err}"))?; + let begin_status = begin.status().as_u16(); + let begin_body = begin.text().map_err(|err| format!("begin body: {err}"))?; + if begin_status != 200 { + return Err(format!("begin http {begin_status}: {begin_body}")); + } + let begin_json: serde_json::Value = + serde_json::from_str(&begin_body).map_err(|err| format!("begin json: {err}"))?; + let payload = begin_json.get("result").unwrap_or(&begin_json); + let ugcid = json_get_str(payload, "ugcid"); + let hmac = json_get_str(&begin_json, "hmac"); + let timestamp = { + let top = json_get_str(&begin_json, "timestamp"); + if top.is_empty() { json_get_str(payload, "timestamp") } else { top } + }; + let url_host = json_get_str(payload, "url_host"); + let url_path = json_get_str(payload, "url_path"); + let use_https = payload + .get("use_https") + .and_then(|v| v.as_bool()) + .unwrap_or(true); + if ugcid.is_empty() || url_host.is_empty() { + return Err(format!("begin missing ugcid/url_host: {begin_body}")); + } + + let scheme = if use_https { "https" } else { "http" }; + let put_url = format!("{scheme}://{url_host}{url_path}"); + let mut put = client + .put(&put_url) + .header("Content-Type", content_type(image)) + .body(image.to_vec()) + .timeout(Duration::from_secs(45)); + if let Some(headers) = payload.get("request_headers").and_then(|v| v.as_array()) { + for h in headers { + let name = json_get_str(h, "name"); + let value = json_get_str(h, "value"); + if !name.is_empty() { + put = put.header(name, value); + } + } + } + let put_resp = put.send().map_err(|err| format!("ugc put: {err}"))?; + let put_status = put_resp.status().as_u16(); + if !(200..300).contains(&put_status) { + return Err(format!("ugc put http {put_status}")); + } + + let commit = client + .post(format!("{COMMUNITY}/chat/commitfileupload/")) + .header("Cookie", cookie.as_str()) + .header("Referer", format!("{COMMUNITY}/chat/")) + .header("Origin", COMMUNITY) + .form(&[ + ("sessionid", sessionid.as_str()), + ("l", "english"), + ("file_name", file_name), + ("file_sha", sha.as_str()), + ("file_size", size.as_str()), + ("file_image_width", width_s.as_str()), + ("file_image_height", height_s.as_str()), + ("file_type", content_type(image)), + ("success", "1"), + ("ugcid", ugcid.as_str()), + ("timestamp", timestamp.as_str()), + ("hmac", hmac.as_str()), + ("friend_steamid", &friend_steamid.to_string()), + ("spoiler", "0"), + ]) + .timeout(Duration::from_secs(30)) + .send() + .map_err(|err| format!("commit send: {err}"))?; + let commit_status = commit.status().as_u16(); + let commit_body = commit.text().map_err(|err| format!("commit body: {err}"))?; + if commit_status != 200 { + return Err(format!("commit http {commit_status}: {commit_body}")); + } + let commit_json: serde_json::Value = + serde_json::from_str(&commit_body).map_err(|err| format!("commit json: {err}"))?; + let details = commit_json + .get("result") + .and_then(|r| r.get("details")) + .ok_or_else(|| format!("commit no details: {commit_body}"))?; + let file_sha = json_get_str(details, "file_sha"); + let sha_upper = if file_sha.is_empty() { + sha.to_uppercase() + } else { + file_sha.to_uppercase() + }; + Ok(format!( + "https://images.steamusercontent.com/ugc/{ugcid}/{sha_upper}/" + )) +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/cm_bridge.rs b/app/src/main/cpp/wn-steam-client/rust/src/cm_bridge.rs new file mode 100644 index 000000000..cf07a2028 --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/cm_bridge.rs @@ -0,0 +1,1538 @@ +use crate::cm_client::{AccountInfoSnapshot, CMClientCore, FriendPersonaSnapshot}; +use crate::emsg::EMsg; +use crate::pb::cmsg_client_license_list::License; +use crate::pb::cmsg_client_mms_lobby_data::CMsgClientMMSLobbyData; +use crate::pb::cmsg_client_mms_lobby_ops::{ + CMsgClientMMSLobbyChatMsg, CMsgClientMMSUserJoinedOrLeftLobby, +}; +use crate::pb::cmsg_client_persona::PersonaStateFriend; +use std::env; +use std::ffi::{CStr, CString}; +use std::fs; +use std::io::Write; +use std::os::raw::c_char; +use std::path::{Path, PathBuf}; +use std::ptr; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, Mutex, OnceLock}; +use std::thread; +use std::time::{Duration, SystemTime}; + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct WnCmRichPresenceKV { + pub key: *const c_char, + pub value: *const c_char, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct WnCmPersonaEvent { + pub sid: u64, + pub persona_state: u32, + pub game_played_app: u32, + pub name: *const c_char, + pub avatar_hash: *const u8, + pub avatar_hash_len: usize, + pub rp_pairs: *const WnCmRichPresenceKV, + pub rp_count: usize, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct WnCmLicenseEntry { + pub package_id: u32, + pub owner_id: u32, + pub time_created: u32, + pub license_type: u32, + pub flags: u32, + pub change_number: i32, + pub minute_limit: i32, + pub minutes_used: i32, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct WnCmAccountInfo { + pub persona_name: *const c_char, + pub persona_name_len: usize, + pub ip_country: *const c_char, + pub ip_country_len: usize, + pub two_factor_enabled: bool, + pub phone_verified: bool, + pub phone_identifying: bool, + pub phone_requires_verification: bool, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, PartialEq)] +pub struct WnCmLobbyEntry { + pub steam_id: u64, + pub max_members: i32, + pub num_members: i32, + pub lobby_type: i32, + pub lobby_flags: i32, + pub ping_ms: i32, + pub weight: i64, + pub distance: f32, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct WnCmLobbyMember { + pub steam_id: u64, + pub persona_name: *const c_char, + pub metadata_bytes: *const u8, + pub metadata_len: usize, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct WnCmLobbyData { + pub steam_id_lobby: u64, + pub steam_id_owner: u64, + pub app_id: u32, + pub max_members: i32, + pub num_members: i32, + pub lobby_type: i32, + pub lobby_flags: i32, + pub metadata_bytes: *const u8, + pub metadata_len: usize, + pub members: *const WnCmLobbyMember, + pub member_count: usize, +} + +pub type WnCmPersonaObserverFn = Option; +pub type WnCmLogonStateObserverFn = Option; +pub type WnCmFriendsListObserverFn = Option; +pub type WnCmLicenseListObserverFn = Option; +pub type WnCmAccountInfoObserverFn = Option; +pub type WnCmServerRealTimeObserverFn = Option; +pub type WnCmLobbyListCb = Option; +pub type WnCmLobbyDataObserverFn = Option; +pub type WnCmLobbyCreatedCb = Option; +pub type WnCmLobbyJoinedCb = Option; +pub type WnCmLobbySetDataCb = Option; +pub type WnCmLobbySetOwnerCb = Option; +pub type WnCmLobbyChatMsgObserverFn = Option; +pub type WnCmLobbyMembershipObserverFn = Option; + +#[derive(Default)] +pub struct CmBridgeObservers { + persona: Mutex, + logon_state: Mutex, + friends_list: Mutex, + license_list: Mutex, + account_info: Mutex, + server_realtime: Mutex, + lobby_data: Mutex, + lobby_chat_msg: Mutex, + lobby_membership: Mutex, +} + +impl CmBridgeObservers { + pub fn register_persona(&self, callback: WnCmPersonaObserverFn) { + *self.persona.lock().expect("persona observer poisoned") = callback; + } + + pub fn dispatch_persona(&self, event: &WnCmPersonaEvent) { + if let Some(callback) = *self.persona.lock().expect("persona observer poisoned") { + callback(event); + } + } + + pub fn register_logon_state(&self, callback: WnCmLogonStateObserverFn) { + *self.logon_state.lock().expect("logon observer poisoned") = callback; + } + + pub fn dispatch_logon_state(&self, logged_on: bool) { + if let Some(callback) = *self.logon_state.lock().expect("logon observer poisoned") { + callback(logged_on); + } + } + + pub fn register_friends_list(&self, callback: WnCmFriendsListObserverFn) { + *self.friends_list.lock().expect("friends observer poisoned") = callback; + } + + pub fn dispatch_friends_list(&self, friends: &[u64]) { + if let Some(callback) = *self.friends_list.lock().expect("friends observer poisoned") { + callback(friends.as_ptr(), friends.len()); + } + } + + pub fn register_license_list(&self, callback: WnCmLicenseListObserverFn) { + *self.license_list.lock().expect("license observer poisoned") = callback; + } + + pub fn dispatch_license_list(&self, licenses: &[WnCmLicenseEntry]) { + if let Some(callback) = *self.license_list.lock().expect("license observer poisoned") { + callback(licenses.as_ptr(), licenses.len()); + } + } + + pub fn register_account_info(&self, callback: WnCmAccountInfoObserverFn) { + *self.account_info.lock().expect("account observer poisoned") = callback; + } + + pub fn dispatch_account_info(&self, info: &WnCmAccountInfo) { + if let Some(callback) = *self.account_info.lock().expect("account observer poisoned") { + callback(info); + } + } + + pub fn register_server_realtime(&self, callback: WnCmServerRealTimeObserverFn) { + *self + .server_realtime + .lock() + .expect("server observer poisoned") = callback; + } + + pub fn dispatch_server_realtime(&self, server_realtime: u32) { + if server_realtime == 0 { + return; + } + if let Some(callback) = *self + .server_realtime + .lock() + .expect("server observer poisoned") + { + callback(server_realtime); + } + } + + pub fn register_lobby_data(&self, callback: WnCmLobbyDataObserverFn) { + *self + .lobby_data + .lock() + .expect("lobby data observer poisoned") = callback; + } + + pub fn dispatch_lobby_data(&self, data: &WnCmLobbyData) { + if let Some(callback) = *self + .lobby_data + .lock() + .expect("lobby data observer poisoned") + { + callback(data); + } + } + + pub fn register_lobby_chat_msg(&self, callback: WnCmLobbyChatMsgObserverFn) { + *self + .lobby_chat_msg + .lock() + .expect("lobby chat observer poisoned") = callback; + } + + pub fn dispatch_lobby_chat_msg(&self, lobby_sid: u64, sender_sid: u64, bytes: &[u8]) { + if let Some(callback) = *self + .lobby_chat_msg + .lock() + .expect("lobby chat observer poisoned") + { + callback(lobby_sid, sender_sid, bytes.as_ptr(), bytes.len()); + } + } + + pub fn register_lobby_membership(&self, callback: WnCmLobbyMembershipObserverFn) { + *self + .lobby_membership + .lock() + .expect("lobby membership observer poisoned") = callback; + } + + pub fn dispatch_lobby_membership( + &self, + joined: bool, + lobby_sid: u64, + user_sid: u64, + persona_name: &CStr, + ) { + if let Some(callback) = *self + .lobby_membership + .lock() + .expect("lobby membership observer poisoned") + { + callback( + if joined { 1 } else { 0 }, + lobby_sid, + user_sid, + persona_name.as_ptr(), + ); + } + } +} + +#[derive(Default)] +pub struct CmBridge { + active: Mutex>>, + observers: CmBridgeObservers, +} + +impl CmBridge { + pub fn set_active(&self, client: Arc) { + *self.active.lock().expect("active client poisoned") = Some(client); + } + + pub fn clear_active(&self) { + *self.active.lock().expect("active client poisoned") = None; + } + + pub fn active(&self) -> Option> { + self.active.lock().expect("active client poisoned").clone() + } + + pub fn observers(&self) -> &CmBridgeObservers { + &self.observers + } + + pub fn inject_ownership_ticket(&self, app_id: u32, ticket: &[u8]) -> bool { + if app_id == 0 || ticket.is_empty() { + return false; + } + let Some(client) = self.active() else { + return false; + }; + client.tickets().store(app_id, 1, ticket.to_vec()); + true + } + + pub fn cached_ownership_ticket(&self, app_id: u32) -> Option> { + if app_id == 0 { + return None; + } + self.active() + .and_then(|client| client.tickets().get(app_id)) + .filter(|ticket| ticket.eresult == 1 && !ticket.ticket.is_empty()) + .map(|ticket| ticket.ticket) + } + + pub fn dispatch_client_snapshots(&self, client: &CMClientCore) { + let friends = client.friends_list(); + self.observers.dispatch_friends_list(&friends); + + let licenses = client + .license_list() + .iter() + .map(WnCmLicenseEntry::from) + .collect::>(); + self.observers.dispatch_license_list(&licenses); + + if let Some(self_persona) = client.self_persona() { + self.dispatch_persona_friend(&self_persona); + } + + self.observers + .dispatch_server_realtime(client.server_realtime()); + } + + pub fn dispatch_persona_friend(&self, friend: &PersonaStateFriend) { + let name = CString::new(friend.player_name.as_str()).unwrap_or_default(); + let key_values = friend + .rich_presence + .iter() + .map(|(key, value)| { + ( + CString::new(key.as_str()).unwrap_or_default(), + CString::new(value.as_str()).unwrap_or_default(), + ) + }) + .collect::>(); + let pairs = key_values + .iter() + .map(|(key, value)| WnCmRichPresenceKV { + key: key.as_ptr(), + value: value.as_ptr(), + }) + .collect::>(); + let event = WnCmPersonaEvent { + sid: friend.friendid, + persona_state: friend.persona_state, + game_played_app: friend.game_played_app_id, + name: name.as_ptr(), + avatar_hash: friend.avatar_hash.as_ptr(), + avatar_hash_len: friend.avatar_hash.len(), + rp_pairs: pairs.as_ptr(), + rp_count: pairs.len(), + }; + self.observers.dispatch_persona(&event); + } + + pub fn dispatch_persona_snapshot(&self, snapshot: &FriendPersonaSnapshot) { + let name = CString::new(snapshot.player_name.as_str()).unwrap_or_default(); + let key_values = snapshot + .rich_presence + .iter() + .map(|(key, value)| { + ( + CString::new(key.as_str()).unwrap_or_default(), + CString::new(value.as_str()).unwrap_or_default(), + ) + }) + .collect::>(); + let pairs = key_values + .iter() + .map(|(key, value)| WnCmRichPresenceKV { + key: key.as_ptr(), + value: value.as_ptr(), + }) + .collect::>(); + let event = WnCmPersonaEvent { + sid: snapshot.sid, + persona_state: snapshot.persona_state, + game_played_app: snapshot.game_played_app_id, + name: if snapshot.player_name.is_empty() { + ptr::null() + } else { + name.as_ptr() + }, + avatar_hash: snapshot.avatar_hash.as_ptr(), + avatar_hash_len: snapshot.avatar_hash.len(), + rp_pairs: if pairs.is_empty() { + ptr::null() + } else { + pairs.as_ptr() + }, + rp_count: pairs.len(), + }; + self.observers.dispatch_persona(&event); + } + + pub fn dispatch_account_info_snapshot(&self, snapshot: &AccountInfoSnapshot) { + let persona_name = CString::new(snapshot.persona_name.as_str()).unwrap_or_default(); + let ip_country = CString::new(snapshot.ip_country.as_str()).unwrap_or_default(); + let info = WnCmAccountInfo { + persona_name: if snapshot.persona_name.is_empty() { + ptr::null() + } else { + persona_name.as_ptr() + }, + persona_name_len: snapshot.persona_name.len(), + ip_country: if snapshot.ip_country.is_empty() { + ptr::null() + } else { + ip_country.as_ptr() + }, + ip_country_len: snapshot.ip_country.len(), + two_factor_enabled: snapshot.two_factor_enabled, + phone_verified: snapshot.phone_verified, + phone_identifying: snapshot.phone_identifying, + phone_requires_verification: snapshot.phone_requires_verification, + }; + self.observers.dispatch_account_info(&info); + } + + pub fn dispatch_lobby_push(&self, emsg: EMsg, body: &[u8]) -> bool { + match emsg { + EMsg::CLIENT_MMS_LOBBY_DATA => { + let Some(msg) = CMsgClientMMSLobbyData::deserialize(body) else { + return false; + }; + self.dispatch_lobby_data_message(&msg); + true + } + EMsg::CLIENT_MMS_LOBBY_CHAT_MSG => { + let Some(msg) = CMsgClientMMSLobbyChatMsg::deserialize(body) else { + return false; + }; + self.observers.dispatch_lobby_chat_msg( + msg.steam_id_lobby, + msg.steam_id_sender, + &msg.lobby_message, + ); + true + } + EMsg::CLIENT_MMS_USER_JOINED_LOBBY | EMsg::CLIENT_MMS_USER_LEFT_LOBBY => { + let Some(msg) = CMsgClientMMSUserJoinedOrLeftLobby::deserialize(body) else { + return false; + }; + let persona_name = CString::new(msg.persona_name.as_str()).unwrap_or_default(); + self.observers.dispatch_lobby_membership( + emsg == EMsg::CLIENT_MMS_USER_JOINED_LOBBY, + msg.steam_id_lobby, + msg.steam_id_user, + &persona_name, + ); + true + } + _ => false, + } + } + + pub fn dispatch_lobby_data_message(&self, msg: &CMsgClientMMSLobbyData) { + let member_names = msg + .members + .iter() + .map(|member| CString::new(member.persona_name.as_str()).unwrap_or_default()) + .collect::>(); + let members = msg + .members + .iter() + .zip(member_names.iter()) + .map(|(member, name)| WnCmLobbyMember { + steam_id: member.steam_id, + persona_name: name.as_ptr(), + metadata_bytes: member.metadata.as_ptr(), + metadata_len: member.metadata.len(), + }) + .collect::>(); + let data = WnCmLobbyData { + steam_id_lobby: msg.steam_id_lobby, + steam_id_owner: msg.steam_id_owner, + app_id: msg.app_id, + max_members: msg.max_members, + num_members: msg.num_members, + lobby_type: msg.lobby_type, + lobby_flags: msg.lobby_flags, + metadata_bytes: msg.metadata.as_ptr(), + metadata_len: msg.metadata.len(), + members: members.as_ptr(), + member_count: members.len(), + }; + self.observers.dispatch_lobby_data(&data); + } +} + +impl From<&License> for WnCmLicenseEntry { + fn from(license: &License) -> Self { + Self { + package_id: license.package_id, + owner_id: license.owner_id, + time_created: license.time_created, + license_type: license.license_type, + flags: license.flags, + change_number: license.change_number, + minute_limit: license.minute_limit, + minutes_used: license.minutes_used, + } + } +} + +pub fn global_bridge() -> &'static CmBridge { + static BRIDGE: OnceLock = OnceLock::new(); + BRIDGE.get_or_init(CmBridge::default) +} + +pub fn set_active_core(client: Arc) { + global_bridge().set_active(client); +} + +pub fn clear_active_core() { + global_bridge().clear_active(); +} + +fn state_sync_poller_running() -> &'static AtomicBool { + static RUNNING: OnceLock = OnceLock::new(); + RUNNING.get_or_init(|| AtomicBool::new(false)) +} + +#[no_mangle] +pub extern "C" fn wn_cm_set_persona_state(persona_state: i32) -> bool { + if persona_state < 0 { + return false; + } + let Some(client) = global_bridge().active() else { + return false; + }; + client.enqueue_proto_message(client.build_set_persona_state(persona_state as u32)) +} + +#[no_mangle] +pub unsafe extern "C" fn wn_cm_set_persona_name(name: *const c_char, persona_state: i32) -> bool { + if name.is_null() || global_bridge().active().is_none() { + return false; + } + let name = unsafe { CStr::from_ptr(name) } + .to_string_lossy() + .into_owned(); + if name.is_empty() { + return false; + } + let persona_state = if persona_state < 0 { + 1 + } else { + persona_state as u32 + }; + let Some(client) = global_bridge().active() else { + return false; + }; + client.enqueue_proto_message(client.build_set_persona_name(name, persona_state)) +} + +#[no_mangle] +pub extern "C" fn wn_cm_request_user_info(steam_id: u64, flags: i32) -> bool { + if steam_id == 0 { + return false; + } + let Some(client) = global_bridge().active() else { + return false; + }; + let flags = if flags <= 0 { 0x47 } else { flags as u32 }; + client.enqueue_proto_message(client.build_request_friend_personas(&[steam_id], flags)) +} + +#[no_mangle] +pub unsafe extern "C" fn wn_cm_request_user_info_bulk( + sids: *const u64, + count: usize, + _flags: i32, +) -> bool { + if sids.is_null() || count == 0 || global_bridge().active().is_none() { + return false; + } + let slice = unsafe { std::slice::from_raw_parts(sids, count) }; + let sids = slice + .iter() + .copied() + .filter(|sid| *sid != 0) + .collect::>(); + if sids.is_empty() { + return false; + } + let Some(client) = global_bridge().active() else { + return false; + }; + let flags = if _flags <= 0 { 0x47 } else { _flags as u32 }; + client.enqueue_proto_message(client.build_request_friend_personas(&sids, flags)) +} + +#[no_mangle] +pub unsafe extern "C" fn wn_cm_get_cached_app_ownership_ticket( + app_id: u32, + out_buf: *mut u8, + max_len: usize, + out_len: *mut usize, +) -> bool { + if out_len.is_null() { + return false; + } + let Some(ticket) = global_bridge().cached_ownership_ticket(app_id) else { + unsafe { + *out_len = 0; + } + return false; + }; + unsafe { + *out_len = ticket.len(); + } + if out_buf.is_null() || max_len < ticket.len() { + return false; + } + unsafe { + ptr::copy_nonoverlapping(ticket.as_ptr(), out_buf, ticket.len()); + } + true +} + +#[no_mangle] +pub unsafe extern "C" fn wn_cm_bridge_inject_test_ownership_ticket( + app_id: u32, + bytes: *const u8, + len: usize, +) -> bool { + if bytes.is_null() && len != 0 { + return false; + } + let ticket = if len == 0 { + &[] + } else { + unsafe { std::slice::from_raw_parts(bytes, len) } + }; + global_bridge().inject_ownership_ticket(app_id, ticket) +} + +#[no_mangle] +pub extern "C" fn wn_cm_notify_games_played(app_id: u32) -> bool { + let Some(client) = global_bridge().active() else { + return false; + }; + client.enqueue_proto_message(client.build_notify_games_played(app_id)) +} + +#[no_mangle] +pub unsafe extern "C" fn wn_cm_set_rich_presence( + app_id: u32, + keys: *const *const c_char, + values: *const *const c_char, + count: usize, +) -> bool { + let Some(client) = global_bridge().active() else { + return false; + }; + if count == 0 { + return client.enqueue_service_call(client.build_rich_presence_call(app_id, Vec::new(), 0)); + } + if keys.is_null() { + return false; + } + let key_slice = unsafe { std::slice::from_raw_parts(keys, count) }; + let value_slice = if values.is_null() { + Vec::new() + } else { + unsafe { std::slice::from_raw_parts(values, count) }.to_vec() + }; + let kv = key_slice + .iter() + .enumerate() + .filter_map(|(idx, key)| { + if key.is_null() { + return None; + } + let key = unsafe { CStr::from_ptr(*key) } + .to_string_lossy() + .into_owned(); + if key.is_empty() { + return None; + } + let value = value_slice + .get(idx) + .copied() + .filter(|ptr| !ptr.is_null()) + .map(|ptr| { + unsafe { CStr::from_ptr(ptr) } + .to_string_lossy() + .into_owned() + }) + .unwrap_or_default(); + Some((key, value)) + }) + .collect::>(); + client.enqueue_service_call(client.build_rich_presence_call(app_id, kv, 0)) +} + +#[no_mangle] +pub unsafe extern "C" fn wn_cm_store_user_stats( + app_id: u32, + _crc_stats: u32, + stat_ids: *const u32, + stat_values: *const u32, + count: usize, +) -> bool { + if app_id == 0 { + return false; + } + let Some(client) = global_bridge().active() else { + return false; + }; + if client.steam_id() == 0 { + return false; + } + if count != 0 && (stat_ids.is_null() || stat_values.is_null()) { + return false; + } + let stats = if count == 0 { + Vec::new() + } else { + let ids = unsafe { std::slice::from_raw_parts(stat_ids, count) }; + let values = unsafe { std::slice::from_raw_parts(stat_values, count) }; + ids.iter() + .copied() + .zip(values.iter().copied()) + .collect::>() + }; + client.enqueue_proto_message(client.build_store_user_stats( + app_id, + client.steam_id(), + _crc_stats, + &stats, + )) +} + +#[no_mangle] +pub extern "C" fn wn_cm_bridge_register_persona_observer(callback: WnCmPersonaObserverFn) { + global_bridge().observers.register_persona(callback); +} + +#[no_mangle] +pub unsafe extern "C" fn wn_cm_bridge_dispatch_persona(event: *const WnCmPersonaEvent) { + if let Some(event) = unsafe { event.as_ref() } { + global_bridge().observers.dispatch_persona(event); + } +} + +#[no_mangle] +pub extern "C" fn wn_cm_bridge_register_logon_state_observer(callback: WnCmLogonStateObserverFn) { + global_bridge().observers.register_logon_state(callback); +} + +#[no_mangle] +pub extern "C" fn wn_cm_bridge_dispatch_logon_state(logged_on: bool) { + global_bridge().observers.dispatch_logon_state(logged_on); +} + +#[no_mangle] +pub extern "C" fn wn_cm_bridge_inject_test_logon_state(logged_on: bool) { + wn_cm_bridge_dispatch_logon_state(logged_on); +} + +#[no_mangle] +pub extern "C" fn wn_cm_bridge_register_friends_list_observer(callback: WnCmFriendsListObserverFn) { + global_bridge().observers.register_friends_list(callback); +} + +#[no_mangle] +pub unsafe extern "C" fn wn_cm_bridge_dispatch_friends_list(sids: *const u64, count: usize) { + if count == 0 { + global_bridge().observers.dispatch_friends_list(&[]); + return; + } + if sids.is_null() { + return; + } + let friends = unsafe { std::slice::from_raw_parts(sids, count) }; + global_bridge().observers.dispatch_friends_list(friends); +} + +#[no_mangle] +pub unsafe extern "C" fn wn_cm_bridge_inject_test_friends_list(sids: *const u64, count: usize) { + unsafe { wn_cm_bridge_dispatch_friends_list(sids, count) }; +} + +#[no_mangle] +pub extern "C" fn wn_cm_bridge_register_license_list_observer(callback: WnCmLicenseListObserverFn) { + global_bridge().observers.register_license_list(callback); +} + +#[no_mangle] +pub unsafe extern "C" fn wn_cm_bridge_dispatch_license_list( + licenses: *const WnCmLicenseEntry, + count: usize, +) { + if count == 0 { + global_bridge().observers.dispatch_license_list(&[]); + return; + } + if licenses.is_null() { + return; + } + let licenses = unsafe { std::slice::from_raw_parts(licenses, count) }; + global_bridge().observers.dispatch_license_list(licenses); +} + +#[no_mangle] +pub unsafe extern "C" fn wn_cm_bridge_inject_test_license_list( + licenses: *const WnCmLicenseEntry, + count: usize, +) { + unsafe { wn_cm_bridge_dispatch_license_list(licenses, count) }; +} + +#[no_mangle] +pub extern "C" fn wn_cm_bridge_register_account_info_observer(callback: WnCmAccountInfoObserverFn) { + global_bridge().observers.register_account_info(callback); +} + +#[no_mangle] +pub unsafe extern "C" fn wn_cm_bridge_dispatch_account_info(info: *const WnCmAccountInfo) { + if let Some(info) = unsafe { info.as_ref() } { + global_bridge().observers.dispatch_account_info(info); + } +} + +#[no_mangle] +pub unsafe extern "C" fn wn_cm_bridge_inject_test_account_info(info: *const WnCmAccountInfo) { + unsafe { wn_cm_bridge_dispatch_account_info(info) }; +} + +#[no_mangle] +pub extern "C" fn wn_cm_bridge_register_server_realtime_observer( + callback: WnCmServerRealTimeObserverFn, +) { + global_bridge().observers.register_server_realtime(callback); +} + +#[no_mangle] +pub extern "C" fn wn_cm_bridge_dispatch_server_realtime(server_realtime: u32) { + global_bridge() + .observers + .dispatch_server_realtime(server_realtime); +} + +#[no_mangle] +pub extern "C" fn wn_cm_bridge_register_lobby_data_observer(callback: WnCmLobbyDataObserverFn) { + global_bridge().observers.register_lobby_data(callback); +} + +#[no_mangle] +pub extern "C" fn wn_cm_bridge_register_lobby_chat_msg_observer( + callback: WnCmLobbyChatMsgObserverFn, +) { + global_bridge().observers.register_lobby_chat_msg(callback); +} + +#[no_mangle] +pub extern "C" fn wn_cm_bridge_register_lobby_membership_observer( + callback: WnCmLobbyMembershipObserverFn, +) { + global_bridge() + .observers + .register_lobby_membership(callback); +} + +#[no_mangle] +pub extern "C" fn wn_cm_bridge_start_state_sync_poller() { + state_sync_poller_running().store(true, Ordering::Release); +} + +#[no_mangle] +pub extern "C" fn wn_cm_bridge_stop_state_sync_poller() { + state_sync_poller_running().store(false, Ordering::Release); +} + +#[no_mangle] +pub unsafe extern "C" fn wn_cm_lobby_get_list( + hcall: u64, + app_id: u32, + num_lobbies_requested: i32, + filter_keys: *const *const c_char, + filter_values: *const *const c_char, + filter_comparisons: *const i32, + filter_types: *const i32, + filter_count: usize, + callback: WnCmLobbyListCb, +) -> bool { + let Some(callback) = callback else { + return false; + }; + if app_id == 0 { + return false; + } + if let Some(client) = global_bridge().active() { + let mut filters = Vec::new(); + if !filter_keys.is_null() && filter_count != 0 { + let keys = unsafe { std::slice::from_raw_parts(filter_keys, filter_count) }; + let values = if filter_values.is_null() { + Vec::new() + } else { + unsafe { std::slice::from_raw_parts(filter_values, filter_count) }.to_vec() + }; + let comparisons = if filter_comparisons.is_null() { + Vec::new() + } else { + unsafe { std::slice::from_raw_parts(filter_comparisons, filter_count) }.to_vec() + }; + let types = if filter_types.is_null() { + Vec::new() + } else { + unsafe { std::slice::from_raw_parts(filter_types, filter_count) }.to_vec() + }; + filters = keys + .iter() + .enumerate() + .filter_map(|(idx, key)| { + if key.is_null() { + return None; + } + let key = unsafe { CStr::from_ptr(*key) } + .to_string_lossy() + .into_owned(); + if key.is_empty() { + return None; + } + let value = values + .get(idx) + .copied() + .filter(|ptr| !ptr.is_null()) + .map(|ptr| unsafe { CStr::from_ptr(ptr) }.to_string_lossy().into_owned()) + .unwrap_or_default(); + Some(crate::pb::cmsg_client_mms_get_lobby_list::CMsgClientMMSGetLobbyListFilter { + key, + value, + comparision: comparisons.get(idx).copied().unwrap_or_default(), + filter_type: types.get(idx).copied().unwrap_or_default(), + }) + }) + .collect(); + } + return client.enqueue_proto_message(client.build_lobby_get_list( + app_id, + filters, + num_lobbies_requested, + hcall, + )); + } + try_lobby_list_from_file(hcall, app_id, callback) +} + +#[no_mangle] +pub extern "C" fn wn_cm_lobby_create( + hcall: u64, + app_id: u32, + _lobby_type: i32, + _max_members: i32, + callback: WnCmLobbyCreatedCb, +) -> bool { + let Some(client) = global_bridge().active() else { + return false; + }; + if app_id == 0 { + return false; + } + let ok = client.enqueue_proto_message(client.build_lobby_create( + app_id, + _lobby_type, + _max_members, + hcall, + )); + if !ok { + if let Some(callback) = callback { + callback(hcall, -1, 0); + } + } + ok +} + +#[no_mangle] +pub extern "C" fn wn_cm_lobby_join( + hcall: u64, + _app_id: u32, + lobby_sid: u64, + callback: WnCmLobbyJoinedCb, +) -> bool { + let Some(client) = global_bridge().active() else { + return false; + }; + if lobby_sid == 0 { + return false; + } + let ok = client.enqueue_proto_message(client.build_lobby_join(_app_id, lobby_sid, hcall)); + if !ok { + if let Some(callback) = callback { + callback(hcall, -1, lobby_sid); + } + } + ok +} + +#[no_mangle] +pub extern "C" fn wn_cm_lobby_leave(app_id: u32, lobby_sid: u64) -> bool { + let Some(client) = global_bridge().active() else { + return false; + }; + client.enqueue_proto_message(client.build_lobby_leave(app_id, lobby_sid)) +} + +#[no_mangle] +pub unsafe extern "C" fn wn_cm_lobby_send_chat( + _app_id: u32, + lobby_sid: u64, + data: *const u8, + len: usize, +) -> bool { + if lobby_sid == 0 || data.is_null() || len == 0 { + return false; + } + let Some(client) = global_bridge().active() else { + return false; + }; + let data = unsafe { std::slice::from_raw_parts(data, len) }.to_vec(); + client.enqueue_proto_message(client.build_lobby_send_chat(_app_id, lobby_sid, data)) +} + +#[no_mangle] +pub unsafe extern "C" fn wn_cm_lobby_set_data( + hcall: u64, + _app_id: u32, + lobby_sid: u64, + _steam_id_member: u64, + metadata: *const u8, + metadata_len: usize, + _max_members: i32, + _lobby_type: i32, + _lobby_flags: i32, + callback: WnCmLobbySetDataCb, +) -> bool { + if lobby_sid == 0 || (metadata.is_null() && metadata_len != 0) { + return false; + } + let Some(client) = global_bridge().active() else { + return false; + }; + let metadata = if metadata_len == 0 { + Vec::new() + } else { + unsafe { std::slice::from_raw_parts(metadata, metadata_len) }.to_vec() + }; + let ok = client.enqueue_proto_message(client.build_lobby_set_data( + _app_id, + lobby_sid, + _steam_id_member, + metadata, + _max_members, + _lobby_type, + _lobby_flags, + hcall, + )); + if !ok { + if let Some(callback) = callback { + callback(hcall, -1); + } + } + ok +} + +#[no_mangle] +pub extern "C" fn wn_cm_lobby_set_owner( + hcall: u64, + _app_id: u32, + lobby_sid: u64, + new_owner_sid: u64, + callback: WnCmLobbySetOwnerCb, +) -> bool { + let Some(client) = global_bridge().active() else { + return false; + }; + if lobby_sid == 0 || new_owner_sid == 0 { + return false; + } + let ok = client.enqueue_proto_message(client.build_lobby_set_owner( + _app_id, + lobby_sid, + new_owner_sid, + hcall, + )); + if !ok { + if let Some(callback) = callback { + callback(hcall, -1); + } + } + ok +} + +#[no_mangle] +pub extern "C" fn wn_cm_lobby_invite_user(app_id: u32, lobby_sid: u64, invitee_sid: u64) -> bool { + let Some(client) = global_bridge().active() else { + return false; + }; + client.enqueue_proto_message(client.build_lobby_invite_user(app_id, lobby_sid, invitee_sid)) +} + +pub fn state_dir() -> PathBuf { + env::var_os("WN_STATE_DIR") + .filter(|dir| !dir.is_empty()) + .map(PathBuf::from) + .unwrap_or_else(|| PathBuf::from("/tmp")) +} + +pub fn lobby_state_path(dir: &Path, app_id: u32) -> PathBuf { + dir.join(format!("wn_lobby_{app_id}.txt")) +} + +pub fn lobby_request_path(dir: &Path, app_id: u32) -> PathBuf { + dir.join(format!("wn_lobby_req_{app_id}.txt")) +} + +pub fn write_lobby_list_to_file( + dir: &Path, + app_id: u32, + eresult: i32, + entries: &[WnCmLobbyEntry], +) -> std::io::Result<()> { + fs::create_dir_all(dir)?; + let final_path = lobby_state_path(dir, app_id); + let tmp_path = final_path.with_extension("txt.tmp"); + { + let mut file = fs::File::create(&tmp_path)?; + writeln!(file, "app_id {app_id}")?; + writeln!( + file, + "fetched {}", + SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap_or_default() + .as_secs() + )?; + writeln!(file, "eresult {eresult}")?; + for entry in entries { + writeln!(file, "lobby {} {}", entry.steam_id, entry.max_members)?; + } + } + fs::rename(tmp_path, final_path) +} + +pub fn parse_lobby_state_file(path: &Path) -> std::io::Result<(i32, Vec)> { + let text = fs::read_to_string(path)?; + let mut eresult = 0; + let mut lobbies = Vec::new(); + for line in text.lines() { + let mut fields = line.split_whitespace(); + match fields.next() { + Some("eresult") => { + if let Some(value) = fields.next().and_then(|value| value.parse::().ok()) { + eresult = value; + } + } + Some("lobby") => { + let steam_id = fields + .next() + .and_then(|value| value.parse::().ok()) + .unwrap_or_default(); + let max_members = fields + .next() + .and_then(|value| value.parse::().ok()) + .unwrap_or_default(); + if steam_id != 0 { + lobbies.push(WnCmLobbyEntry { + steam_id, + max_members, + ..Default::default() + }); + } + } + _ => {} + } + } + Ok((eresult, lobbies)) +} + +pub fn write_lobby_request_file(dir: &Path, app_id: u32) -> std::io::Result<()> { + fs::create_dir_all(dir)?; + let final_path = lobby_request_path(dir, app_id); + let tmp_path = final_path.with_extension("txt.tmp"); + { + let mut file = fs::File::create(&tmp_path)?; + writeln!(file, "app_id {app_id}")?; + writeln!( + file, + "requested {}", + SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap_or_default() + .as_secs() + )?; + } + fs::rename(tmp_path, final_path) +} + +fn try_lobby_list_from_file( + hcall: u64, + app_id: u32, + callback: extern "C" fn(u64, i32, *const WnCmLobbyEntry, usize), +) -> bool { + let dir = state_dir(); + let path = lobby_state_path(&dir, app_id); + + for attempt in 0..=30 { + if let Ok((eresult, lobbies)) = parse_lobby_state_file(&path) { + callback(hcall, eresult, lobbies.as_ptr(), lobbies.len()); + return true; + } + if attempt == 0 { + let _ = write_lobby_request_file(&dir, app_id); + } + if attempt < 30 { + thread::sleep(Duration::from_millis(100)); + } + } + + callback(hcall, 1, ptr::null(), 0); + true +} + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering}; + + static LOGON_HIT: AtomicBool = AtomicBool::new(false); + static FRIEND_COUNT: AtomicUsize = AtomicUsize::new(0); + static LICENSE_PACKAGE: AtomicUsize = AtomicUsize::new(0); + static PERSONA_ID: AtomicU64 = AtomicU64::new(0); + static LOBBY_COUNT: AtomicUsize = AtomicUsize::new(0); + static ACCOUNT_FLAGS: AtomicUsize = AtomicUsize::new(0); + static SERVER_REALTIME: AtomicUsize = AtomicUsize::new(0); + static LOBBY_DATA_MEMBERS: AtomicUsize = AtomicUsize::new(0); + static LOBBY_CHAT_BYTES: AtomicUsize = AtomicUsize::new(0); + static LOBBY_MEMBERSHIP_JOINED: AtomicUsize = AtomicUsize::new(0); + + extern "C" fn logon_cb(logged_on: bool) { + LOGON_HIT.store(logged_on, Ordering::SeqCst); + } + + extern "C" fn friends_cb(_sids: *const u64, count: usize) { + FRIEND_COUNT.store(count, Ordering::SeqCst); + } + + extern "C" fn license_cb(licenses: *const WnCmLicenseEntry, count: usize) { + if count != 0 { + let first = unsafe { *licenses }; + LICENSE_PACKAGE.store(first.package_id as usize, Ordering::SeqCst); + } + } + + extern "C" fn persona_cb(event: *const WnCmPersonaEvent) { + let event = unsafe { event.as_ref() }.unwrap(); + PERSONA_ID.store(event.sid, Ordering::SeqCst); + } + + extern "C" fn account_cb(info: *const WnCmAccountInfo) { + let info = unsafe { info.as_ref() }.unwrap(); + let mut flags = 0usize; + if !info.persona_name.is_null() && info.persona_name_len == 3 { + flags |= 1; + } + if !info.ip_country.is_null() && info.ip_country_len == 2 { + flags |= 2; + } + if info.two_factor_enabled { + flags |= 4; + } + if info.phone_verified { + flags |= 8; + } + ACCOUNT_FLAGS.store(flags, Ordering::SeqCst); + } + + extern "C" fn server_realtime_cb(server_realtime: u32) { + SERVER_REALTIME.store(server_realtime as usize, Ordering::SeqCst); + } + + extern "C" fn lobby_cb( + _hcall: u64, + eresult: i32, + lobbies: *const WnCmLobbyEntry, + count: usize, + ) { + assert_eq!(eresult, 1); + assert!(!lobbies.is_null()); + LOBBY_COUNT.store(count, Ordering::SeqCst); + } + + extern "C" fn lobby_data_cb(data: *const WnCmLobbyData) { + let data = unsafe { data.as_ref() }.unwrap(); + LOBBY_DATA_MEMBERS.store(data.member_count, Ordering::SeqCst); + } + + extern "C" fn lobby_chat_cb(_lobby_sid: u64, _sender_sid: u64, _data: *const u8, len: usize) { + LOBBY_CHAT_BYTES.store(len, Ordering::SeqCst); + } + + extern "C" fn lobby_membership_cb( + joined: i32, + _lobby_sid: u64, + _user_sid: u64, + _persona_name: *const c_char, + ) { + LOBBY_MEMBERSHIP_JOINED.store(joined as usize, Ordering::SeqCst); + } + + fn serialize_lobby_chat_for_test(lobby_sid: u64, sender_sid: u64, message: &[u8]) -> Vec { + let mut body = Vec::new(); + let mut writer = crate::proto_wire::Writer::new(&mut body); + writer.fixed64_field(2, lobby_sid); + writer.fixed64_field(3, sender_sid); + writer.bytes_field(4, message); + body + } + + fn serialize_lobby_membership_for_test( + lobby_sid: u64, + user_sid: u64, + persona_name: &str, + ) -> Vec { + let mut body = Vec::new(); + let mut writer = crate::proto_wire::Writer::new(&mut body); + writer.fixed64_field(2, lobby_sid); + writer.fixed64_field(3, user_sid); + writer.string_field(4, persona_name); + body + } + + #[test] + fn observer_dispatches_match_c_bridge_contract() { + let bridge = CmBridge::default(); + bridge.observers().register_logon_state(Some(logon_cb)); + bridge.observers().register_friends_list(Some(friends_cb)); + bridge.observers().register_license_list(Some(license_cb)); + bridge.observers().register_persona(Some(persona_cb)); + bridge.observers().register_account_info(Some(account_cb)); + bridge + .observers() + .register_server_realtime(Some(server_realtime_cb)); + bridge.observers().register_lobby_data(Some(lobby_data_cb)); + bridge + .observers() + .register_lobby_chat_msg(Some(lobby_chat_cb)); + bridge + .observers() + .register_lobby_membership(Some(lobby_membership_cb)); + + bridge.observers().dispatch_logon_state(true); + bridge.observers().dispatch_friends_list(&[1, 2, 3]); + bridge.observers().dispatch_server_realtime(1_700_000_000); + bridge + .observers() + .dispatch_license_list(&[WnCmLicenseEntry { + package_id: 480, + ..Default::default() + }]); + bridge.dispatch_persona_friend(&PersonaStateFriend { + friendid: 765, + player_name: "Ada".into(), + rich_presence: vec![("status".into(), "Playing".into())], + ..Default::default() + }); + bridge.dispatch_account_info_snapshot(&AccountInfoSnapshot { + persona_name: "Ada".into(), + ip_country: "US".into(), + two_factor_enabled: true, + phone_verified: true, + ..Default::default() + }); + bridge.dispatch_lobby_data_message(&CMsgClientMMSLobbyData { + steam_id_lobby: 1, + steam_id_owner: 2, + members: vec![crate::pb::cmsg_client_mms_lobby_data::MMSLobbyDataMember { + steam_id: 2, + persona_name: "Ada".into(), + metadata: b"m".to_vec(), + }], + ..Default::default() + }); + assert!(bridge.dispatch_lobby_push( + EMsg::CLIENT_MMS_LOBBY_CHAT_MSG, + &serialize_lobby_chat_for_test(1, 2, b"hello") + )); + assert!(bridge.dispatch_lobby_push( + EMsg::CLIENT_MMS_USER_JOINED_LOBBY, + &serialize_lobby_membership_for_test(1, 2, "Ada") + )); + + assert!(LOGON_HIT.load(Ordering::SeqCst)); + assert_eq!(FRIEND_COUNT.load(Ordering::SeqCst), 3); + assert_eq!(LICENSE_PACKAGE.load(Ordering::SeqCst), 480); + assert_eq!(PERSONA_ID.load(Ordering::SeqCst), 765); + assert_eq!(ACCOUNT_FLAGS.load(Ordering::SeqCst), 1 | 2 | 4 | 8); + assert_eq!(SERVER_REALTIME.load(Ordering::SeqCst), 1_700_000_000); + assert_eq!(LOBBY_DATA_MEMBERS.load(Ordering::SeqCst), 1); + assert_eq!(LOBBY_CHAT_BYTES.load(Ordering::SeqCst), 5); + assert_eq!(LOBBY_MEMBERSHIP_JOINED.load(Ordering::SeqCst), 1); + } + + #[test] + fn state_sync_poller_exports_track_running_state() { + wn_cm_bridge_start_state_sync_poller(); + assert!(state_sync_poller_running().load(Ordering::Acquire)); + wn_cm_bridge_stop_state_sync_poller(); + assert!(!state_sync_poller_running().load(Ordering::Acquire)); + } + + #[test] + fn active_core_backs_ticket_cache_bridge() { + let bridge = CmBridge::default(); + let core = Arc::new(CMClientCore::default()); + bridge.set_active(core); + assert!(!bridge.inject_ownership_ticket(0, &[1])); + assert!(!bridge.inject_ownership_ticket(440, &[])); + assert!(bridge.inject_ownership_ticket(440, &[1, 2, 3])); + assert_eq!(bridge.cached_ownership_ticket(440), Some(vec![1, 2, 3])); + bridge.clear_active(); + assert_eq!(bridge.cached_ownership_ticket(440), None); + } + + #[test] + fn bridge_commands_enqueue_outbound_cm_messages() { + let bridge = global_bridge(); + let core = Arc::new(CMClientCore::default()); + core.set_state(crate::cm_client::ClientState::LoggedOn); + bridge.set_active(Arc::clone(&core)); + + assert!(wn_cm_set_persona_state(1)); + assert!(wn_cm_notify_games_played(480)); + + let name = CString::new("Ada").unwrap(); + assert!(unsafe { wn_cm_set_persona_name(name.as_ptr(), 1) }); + assert!(unsafe { + wn_cm_lobby_get_list( + 10, + 480, + 50, + ptr::null(), + ptr::null(), + ptr::null(), + ptr::null(), + 0, + Some(lobby_cb), + ) + }); + assert!(wn_cm_lobby_create(11, 480, 2, 4, None)); + assert!(wn_cm_lobby_leave(480, 123)); + + let wires = core.take_outbound_wires(); + assert_eq!(wires.len(), 6); + bridge.clear_active(); + } + + #[test] + fn lobby_state_files_roundtrip_cpp_fallback_format() { + let dir = env::temp_dir().join(format!( + "wnsteam-rust-bridge-{}", + SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_nanos() + )); + let entries = [WnCmLobbyEntry { + steam_id: 123, + max_members: 8, + ..Default::default() + }]; + + write_lobby_list_to_file(&dir, 480, 1, &entries).unwrap(); + let (eresult, parsed) = parse_lobby_state_file(&lobby_state_path(&dir, 480)).unwrap(); + assert_eq!(eresult, 1); + assert_eq!(parsed[0].steam_id, 123); + assert_eq!(parsed[0].max_members, 8); + + write_lobby_request_file(&dir, 480).unwrap(); + assert!(lobby_request_path(&dir, 480).exists()); + let _ = fs::remove_dir_all(dir); + } + + #[test] + fn lobby_get_list_callback_reads_existing_state_file() { + let dir = env::temp_dir().join(format!( + "wnsteam-rust-bridge-cb-{}", + SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_nanos() + )); + write_lobby_list_to_file( + &dir, + 481, + 1, + &[WnCmLobbyEntry { + steam_id: 456, + max_members: 4, + ..Default::default() + }], + ) + .unwrap(); + + let old = env::var_os("WN_STATE_DIR"); + env::set_var("WN_STATE_DIR", &dir); + LOBBY_COUNT.store(0, Ordering::SeqCst); + assert!(unsafe { + wn_cm_lobby_get_list( + 99, + 481, + 0, + ptr::null(), + ptr::null(), + ptr::null(), + ptr::null(), + 0, + Some(lobby_cb), + ) + }); + assert_eq!(LOBBY_COUNT.load(Ordering::SeqCst), 1); + if let Some(old) = old { + env::set_var("WN_STATE_DIR", old); + } else { + env::remove_var("WN_STATE_DIR"); + } + let _ = fs::remove_dir_all(dir); + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/cm_client.rs b/app/src/main/cpp/wn-steam-client/rust/src/cm_client.rs new file mode 100644 index 000000000..b4e8d3fe5 --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/cm_client.rs @@ -0,0 +1,2297 @@ +use crate::cmsg_protobuf_header::{CMsgProtoBufHeader, INVALID_JOB_ID}; +use crate::crypto::generate_session_key; +use crate::emsg::EMsg; +use crate::job_manager::JobResult; +use crate::library_store::WnLibraryStore; +use crate::pb::ccloud::{ + CCloudAppExitSyncDoneNotification, CCloudAppLaunchIntentRequest, + CCloudBeginAppUploadBatchRequest, CCloudClientBeginFileUploadRequest, + CCloudClientCommitFileUploadRequest, CCloudClientFileDownloadRequest, + CCloudCompleteAppUploadBatchRequest, CCloudGetAppFileChangelistRequest, + CCloudGetUserQuotaRequest, +}; +use crate::pb::ccontentserverdirectory::{ + CContentServerDirectoryGetManifestRequestCodeRequest, + CContentServerDirectoryGetServersForSteamPipeRequest, +}; +use crate::pb::cfamilygroups::CFamilyGroupsGetFamilyGroupRequest; +use crate::pb::cinventory::CInventoryGetItemDefMetaRequest; +use crate::pb::cmsg_client_change_status::CMsgClientChangeStatus; +use crate::pb::cmsg_client_friends_list::CMsgClientFriendsList; +use crate::pb::cmsg_client_games_played::{ + CMsgClientGamesPlayed, GamePlayedEntry, GamePlayedProcessInfo, +}; +use crate::pb::cmsg_client_get_app_ownership_ticket::CMsgClientGetAppOwnershipTicket; +use crate::pb::cmsg_client_get_depot_decryption_key::CMsgClientGetDepotDecryptionKey; +use crate::pb::cmsg_client_kick_playing_session::CMsgClientKickPlayingSession; +use crate::pb::cmsg_client_license_list::{CMsgClientLicenseList, License}; +use crate::pb::cmsg_client_mms_get_lobby_list::{ + CMsgClientMMSGetLobbyList, CMsgClientMMSGetLobbyListFilter, +}; +use crate::pb::cmsg_client_mms_lobby_ops::{ + CMsgClientMMSCreateLobby, CMsgClientMMSInviteToLobby, CMsgClientMMSJoinLobby, + CMsgClientMMSLeaveLobby, CMsgClientMMSSendLobbyChatMsg, CMsgClientMMSSetLobbyData, + CMsgClientMMSSetLobbyOwner, +}; +use crate::pb::cmsg_client_persona::{CMsgClientPersonaState, PersonaStateFriend}; +use crate::pb::cmsg_client_pics::{ + CMsgClientPICSAccessTokenRequest, CMsgClientPICSChangesSinceRequest, + CMsgClientPICSProductInfoRequest, PicsAppInfoReq, PicsPackageInfoReq, +}; +use crate::pb::cmsg_client_playing_session_state::CMsgClientPlayingSessionState; +use crate::pb::cmsg_client_store_user_stats::{CMsgClientStoreUserStats2, Stat}; +use crate::pb::cmsg_clientserver_login::{ + CMsgClientHeartBeat, CMsgClientHello, CMsgClientLogOff, CMsgClientLogon, + CMsgClientLogonResponse, +}; +use crate::pb::cplayer::{ + CPlayerGetOwnedGamesRequest, CPlayerSetRichPresenceKv, CPlayerSetRichPresenceRequest, +}; +use crate::pb::cpublishedfile::CPublishedFileGetUserFilesRequest; +use crate::proto_envelope::encode_proto_envelope; +use crate::ticket_cache::WnTicketCache; +use flate2::read::{GzDecoder, ZlibDecoder}; +use std::collections::HashMap; +use std::io::Read; +use std::sync::atomic::{AtomicBool, AtomicI32, AtomicU32, AtomicU64, AtomicU8, Ordering}; +use std::sync::Mutex; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[repr(u8)] +pub enum ClientState { + Disconnected, + Connecting, + Connected, + LoggedOn, +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct FriendPersonaSnapshot { + pub sid: u64, + pub player_name: String, + pub persona_state: u32, + pub game_played_app_id: u32, + pub avatar_hash: Vec, + pub rich_presence: Vec<(String, String)>, + pub game_name: String, + pub gameid: u64, +} + +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct GamesPlayedExtras { + pub process_id: u32, + pub owner_id: u32, + pub launch_source: u32, + pub game_build_id: u32, +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct AccountInfoSnapshot { + pub persona_name: String, + pub ip_country: String, + pub two_factor_enabled: bool, + pub phone_verified: bool, + pub phone_identifying: bool, + pub phone_requires_verification: bool, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum InboundAction { + DeliverJob(JobResult), + Multi, + PicsProductInfo, + LogonOk, + LoggedOff, + LicenseList(usize), + FriendsList(usize), + PersonaState(usize), + PlayingSessionState(bool), + AccountInfo(AccountInfoSnapshot), + LobbyPush, + ClientMessage, + Ignored, + ParseFailed(&'static str), +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct CMsgMultiBody { + pub size_unzipped: u32, + pub message_body: Vec, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct OutboundProtoMessage { + pub emsg: EMsg, + pub routing_appid: u32, + pub body: Vec, + pub wire: Vec, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct OutboundServiceCall { + pub method_name: String, + pub authed: bool, + pub job_id: u64, + pub request_body: Vec, + pub wire: Vec, +} + +pub struct CMClientCore { + state: AtomicU8, + steam_id: AtomicU64, + session_id: AtomicI32, + family_group_id: AtomicU64, + server_realtime: AtomicU32, + playing_blocked: AtomicBool, + license_list: Mutex>, + friends: Mutex>, + self_persona: Mutex>, + friend_personas: Mutex>, + incoming_messages: Mutex>, + library: WnLibraryStore, + tickets: WnTicketCache, + outbound_wires: Mutex>>, +} + +#[derive(Clone, Debug, Default)] +pub struct IncomingFriendMessage { + pub friend_id: u64, + pub from_self: bool, + pub message: String, + pub timestamp: u32, + pub ordinal: i32, +} + +impl Default for CMClientCore { + fn default() -> Self { + Self { + state: AtomicU8::new(ClientState::Disconnected as u8), + steam_id: AtomicU64::new(0), + session_id: AtomicI32::new(0), + family_group_id: AtomicU64::new(0), + server_realtime: AtomicU32::new(0), + playing_blocked: AtomicBool::new(false), + license_list: Mutex::new(Vec::new()), + friends: Mutex::new(HashMap::new()), + self_persona: Mutex::new(None), + friend_personas: Mutex::new(HashMap::new()), + incoming_messages: Mutex::new(Vec::new()), + library: WnLibraryStore::default(), + tickets: WnTicketCache::default(), + outbound_wires: Mutex::new(Vec::new()), + } + } +} + +impl CMClientCore { + pub fn state(&self) -> ClientState { + match self.state.load(Ordering::Relaxed) { + 1 => ClientState::Connecting, + 2 => ClientState::Connected, + 3 => ClientState::LoggedOn, + _ => ClientState::Disconnected, + } + } + + pub fn set_state(&self, state: ClientState) { + self.state.store(state as u8, Ordering::Relaxed); + } + + pub fn reset_session_identity(&self) { + self.steam_id.store(0, Ordering::Relaxed); + self.session_id.store(0, Ordering::Relaxed); + self.family_group_id.store(0, Ordering::Relaxed); + self.server_realtime.store(0, Ordering::Relaxed); + self.outbound_wires + .lock() + .expect("outbound queue poisoned") + .clear(); + } + + pub fn steam_id(&self) -> u64 { + self.steam_id.load(Ordering::Relaxed) + } + + pub fn session_id(&self) -> i32 { + self.session_id.load(Ordering::Relaxed) + } + + pub fn family_group_id(&self) -> u64 { + self.family_group_id.load(Ordering::Relaxed) + } + + pub fn server_realtime(&self) -> u32 { + self.server_realtime.load(Ordering::Relaxed) + } + + pub fn is_playing_blocked(&self) -> bool { + self.playing_blocked.load(Ordering::Relaxed) + } + + pub fn mark_playing_blocked(&self) { + self.playing_blocked.store(true, Ordering::Relaxed); + } + + pub fn library(&self) -> &WnLibraryStore { + &self.library + } + + pub fn tickets(&self) -> &WnTicketCache { + &self.tickets + } + + pub fn enqueue_proto_message(&self, message: Option) -> bool { + let Some(message) = message else { + return false; + }; + self.enqueue_wire(message.wire) + } + + pub fn enqueue_service_call(&self, call: Option) -> bool { + let Some(call) = call else { + return false; + }; + self.enqueue_wire(call.wire) + } + + pub fn enqueue_wire(&self, wire: Vec) -> bool { + if wire.is_empty() { + return false; + } + self.outbound_wires + .lock() + .expect("outbound queue poisoned") + .push(wire); + true + } + + pub fn restore_outbound_wires_front(&self, mut wires: Vec>) { + if wires.is_empty() { + return; + } + let mut queue = self.outbound_wires.lock().expect("outbound queue poisoned"); + if queue.is_empty() { + *queue = wires; + return; + } + let mut restored = Vec::with_capacity(wires.len() + queue.len()); + restored.append(&mut wires); + restored.append(&mut queue); + *queue = restored; + } + + pub fn take_outbound_wires(&self) -> Vec> { + std::mem::take(&mut *self.outbound_wires.lock().expect("outbound queue poisoned")) + } + + pub fn build_proto_message(&self, emsg: EMsg, body: &[u8], routing_appid: u32) -> Vec { + let mut header = CMsgProtoBufHeader { + steamid: self.steam_id(), + client_sessionid: self.session_id(), + routing_appid, + ..Default::default() + }; + if emsg == EMsg::CLIENT_LOGON && header.steamid == 0 { + header.steamid = 0x0110_0001_0000_0000; + } + encode_proto_envelope(emsg, &header, body) + } + + pub fn build_client_hello(&self) -> OutboundProtoMessage { + let body = CMsgClientHello::default().serialize(); + let wire = self.build_proto_message(EMsg::CLIENT_HELLO, &body, 0); + OutboundProtoMessage { + emsg: EMsg::CLIENT_HELLO, + routing_appid: 0, + body, + wire, + } + } + + pub fn build_heartbeat(&self) -> OutboundProtoMessage { + let body = CMsgClientHeartBeat::default().serialize(); + let wire = self.build_proto_message(EMsg::CLIENT_HEART_BEAT, &body, 0); + OutboundProtoMessage { + emsg: EMsg::CLIENT_HEART_BEAT, + routing_appid: 0, + body, + wire, + } + } + + pub fn build_logoff(&self) -> Option { + if self.state() != ClientState::LoggedOn { + return None; + } + self.build_outbound_proto_message(EMsg::CLIENT_LOG_OFF, CMsgClientLogOff.serialize(), 0) + } + + pub fn build_logon_with_refresh_token( + &self, + refresh_token: impl Into, + account_name: impl Into, + client_supplied_steam_id: u64, + ) -> Option { + if self.state() != ClientState::Connected { + return None; + } + let refresh_token = refresh_token.into(); + let account_name = account_name.into(); + if refresh_token.is_empty() { + return None; + } + + let mut msg = CMsgClientLogon { + access_token: refresh_token, + account_name, + client_supplied_steam_id, + machine_id: b"WN-Steam-Client".to_vec(), + protocol_version: 65580, + client_os_type: 16, + supports_rate_limit_response: true, + ..Default::default() + }; + if let Some(key) = generate_session_key() { + let mut client_instance_id = 0u64; + for (idx, byte) in key.bytes.iter().take(8).enumerate() { + client_instance_id |= (*byte as u64) << (idx * 8); + } + if client_instance_id == 0 { + client_instance_id = 1; + } + msg.client_instance_id = client_instance_id; + + let mut login_id = 0u32; + for (idx, byte) in key.bytes.iter().skip(8).take(4).enumerate() { + login_id |= (*byte as u32) << (idx * 8); + } + if login_id == 0 { + login_id = 0x574e_5301; + } + msg.obfuscated_private_ip = login_id; + } + + let body = msg.serialize(); + let wire = self.build_proto_message(EMsg::CLIENT_LOGON, &body, 0); + Some(OutboundProtoMessage { + emsg: EMsg::CLIENT_LOGON, + routing_appid: 0, + body, + wire, + }) + } + + pub fn build_service_method_call( + &self, + method_name: &str, + authed: bool, + job_id: u64, + request_body: &[u8], + ) -> Vec { + let header = CMsgProtoBufHeader { + steamid: self.steam_id(), + client_sessionid: self.session_id(), + jobid_source: job_id, + jobid_target: INVALID_JOB_ID, + target_job_name: method_name.to_string(), + ..Default::default() + }; + let emsg = if authed { + EMsg::SERVICE_METHOD_CALL_FROM_CLIENT + } else { + EMsg::SERVICE_METHOD_CALL_FROM_CLIENT_NON_AUTHED + }; + encode_proto_envelope(emsg, &header, request_body) + } + + pub fn build_outbound_proto_message( + &self, + emsg: EMsg, + body: Vec, + routing_appid: u32, + ) -> Option { + if self.state() != ClientState::LoggedOn { + return None; + } + let wire = self.build_proto_message(emsg, &body, routing_appid); + Some(OutboundProtoMessage { + emsg, + routing_appid, + body, + wire, + }) + } + + pub fn build_job_proto_message( + &self, + emsg: EMsg, + job_id: u64, + body: Vec, + routing_appid: u32, + ) -> Option { + if self.state() != ClientState::LoggedOn { + return None; + } + let header = CMsgProtoBufHeader { + steamid: self.steam_id(), + client_sessionid: self.session_id(), + routing_appid, + jobid_source: job_id, + jobid_target: INVALID_JOB_ID, + ..Default::default() + }; + let wire = encode_proto_envelope(emsg, &header, &body); + Some(OutboundProtoMessage { + emsg, + routing_appid, + body, + wire, + }) + } + + pub fn build_authed_service_call( + &self, + method_name: &str, + job_id: u64, + request_body: Vec, + ) -> Option { + if self.state() != ClientState::LoggedOn { + return None; + } + let wire = self.build_service_method_call(method_name, true, job_id, &request_body); + Some(OutboundServiceCall { + method_name: method_name.to_string(), + authed: true, + job_id, + request_body, + wire, + }) + } + + pub fn build_request_friend_persona_states( + &self, + job_id: u64, + ) -> Option { + self.build_authed_service_call("Chat.RequestFriendPersonaStates#1", job_id, Vec::new()) + } + + pub fn build_send_friend_message( + &self, + steamid: u64, + message: &str, + contains_bbcode: bool, + job_id: u64, + ) -> Option { + self.build_authed_service_call( + "FriendMessages.SendMessage#1", + job_id, + crate::pb::cfriendmessages::CFriendMessagesSendMessageRequest { + steamid, + chat_entry_type: crate::pb::cfriendmessages::CHAT_ENTRY_TYPE_TEXT, + message: message.to_string(), + contains_bbcode, + echo_to_sender: true, + low_priority: false, + } + .serialize(), + ) + } + + pub fn build_get_recent_messages( + &self, + friend_id: u64, + count: u32, + job_id: u64, + ) -> Option { + let self_id = self.steam_id(); + if self_id == 0 || friend_id == 0 { + return None; + } + self.build_authed_service_call( + "FriendMessages.GetRecentMessages#1", + job_id, + crate::pb::cfriendmessages::CFriendMessagesGetRecentMessagesRequest { + steamid1: self_id, + steamid2: friend_id, + count, + most_recent_conversation: false, + } + .serialize(), + ) + } + + pub fn build_set_persona_state(&self, persona_state: u32) -> Option { + self.build_outbound_proto_message( + EMsg::CLIENT_CHANGE_STATUS, + CMsgClientChangeStatus { + persona_state, + player_name: String::new(), + persona_set_by_user: true, + need_persona_response: true, + } + .serialize(), + 0, + ) + } + + pub fn build_set_persona_name( + &self, + name: impl Into, + persona_state_keep_current: u32, + ) -> Option { + self.build_outbound_proto_message( + EMsg::CLIENT_CHANGE_STATUS, + CMsgClientChangeStatus { + persona_state: persona_state_keep_current, + player_name: name.into(), + persona_set_by_user: true, + need_persona_response: false, + } + .serialize(), + 0, + ) + } + + pub fn build_request_user_persona(&self) -> Option { + let steam_id = self.steam_id(); + if steam_id == 0 { + return None; + } + self.build_request_friend_personas(&[steam_id], 0xffff) + } + + pub fn build_request_friend_personas( + &self, + sids: &[u64], + persona_state_requested: u32, + ) -> Option { + if sids.is_empty() { + return None; + } + self.build_outbound_proto_message( + EMsg::CLIENT_REQUEST_FRIEND_DATA, + crate::pb::cmsg_client_persona::CMsgClientRequestFriendData { + persona_state_requested, + friends: sids.iter().copied().filter(|sid| *sid != 0).collect(), + } + .serialize(), + 0, + ) + } + + pub fn build_notify_games_played(&self, app_id: u32) -> Option { + self.build_notify_games_played_full( + app_id.into(), + &GamesPlayedExtras::default(), + &[], + 0, + ) + } + + pub fn build_notify_games_played_full( + &self, + game_id: u64, + extras: &GamesPlayedExtras, + processes: &[GamePlayedProcessInfo], + client_os_type: u32, + ) -> Option { + let mut msg = CMsgClientGamesPlayed { + games_played: Vec::new(), + client_os_type, + }; + if game_id != 0 { + msg.games_played.push(GamePlayedEntry { + game_id, + process_id: extras.process_id, + owner_id: extras.owner_id, + launch_source: extras.launch_source, + game_build_id: extras.game_build_id, + process_id_list: processes.to_vec(), + }); + } + self.build_outbound_proto_message( + EMsg::CLIENT_GAMES_PLAYED_WITH_DATA_BLOB, + msg.serialize(), + 0, + ) + } + + pub fn build_kick_playing_session(&self, only_stop_game: bool) -> Option { + self.build_outbound_proto_message( + EMsg::CLIENT_KICK_PLAYING_SESSION, + CMsgClientKickPlayingSession { only_stop_game }.serialize(), + 0, + ) + } + + pub fn build_store_user_stats( + &self, + app_id: u32, + steam_id: u64, + crc_stats: u32, + stats: &[(u32, u32)], + ) -> Option { + if app_id == 0 || steam_id == 0 { + return None; + } + self.build_outbound_proto_message( + EMsg::CLIENT_STORE_USER_STATS_2, + CMsgClientStoreUserStats2 { + game_id: app_id as u64, + settor_steam_id: steam_id, + settee_steam_id: steam_id, + crc_stats, + stats: stats + .iter() + .map(|(stat_id, stat_value)| Stat { + stat_id: *stat_id, + stat_value: *stat_value, + }) + .collect(), + } + .serialize(), + app_id, + ) + } + + pub fn build_rich_presence_call( + &self, + app_id: u32, + kv: impl IntoIterator, + job_id: u64, + ) -> Option { + let request = CPlayerSetRichPresenceRequest { + appid: app_id, + rich_presence: kv + .into_iter() + .filter(|(key, _)| !key.is_empty()) + .map(|(key, value)| CPlayerSetRichPresenceKv { key, value }) + .collect(), + }; + self.build_authed_service_call("Player.SetRichPresence#1", job_id, request.serialize()) + } + + pub fn build_get_app_ownership_ticket( + &self, + app_id: u32, + job_id: u64, + ) -> Option { + if app_id == 0 { + return None; + } + self.build_job_proto_message( + EMsg::CLIENT_GET_APP_OWNERSHIP_TICKET, + job_id, + CMsgClientGetAppOwnershipTicket { app_id }.serialize(), + 0, + ) + } + + pub fn build_request_encrypted_app_ticket( + &self, + app_id: u32, + job_id: u64, + ) -> Option { + if app_id == 0 { + return None; + } + self.build_job_proto_message( + EMsg::CLIENT_REQUEST_ENCRYPTED_APP_TICKET, + job_id, + crate::pb::cmsg_client_request_encrypted_app_ticket::CMsgClientRequestEncryptedAppTicket { + app_id, + } + .serialize(), + // Match the legacy C++ client: this request is not app-routed. + 0, + ) + } + + pub fn build_get_depot_decryption_key( + &self, + depot_id: u32, + app_id: u32, + job_id: u64, + ) -> Option { + if depot_id == 0 || app_id == 0 { + return None; + } + self.build_job_proto_message( + EMsg::CLIENT_GET_DEPOT_DECRYPTION_KEY, + job_id, + CMsgClientGetDepotDecryptionKey { depot_id, app_id }.serialize(), + 0, + ) + } + + pub fn build_manifest_request_code_call( + &self, + app_id: u32, + depot_id: u32, + manifest_id: u64, + branch: &str, + job_id: u64, + ) -> Option { + if app_id == 0 || depot_id == 0 || manifest_id == 0 { + return None; + } + let lower = branch.to_ascii_lowercase(); + let app_branch = if lower.is_empty() || lower == "public" { + String::new() + } else { + branch.to_string() + }; + self.build_authed_service_call( + "ContentServerDirectory.GetManifestRequestCode#1", + job_id, + CContentServerDirectoryGetManifestRequestCodeRequest { + app_id, + depot_id, + manifest_id, + app_branch, + branch_password_hash: String::new(), + } + .serialize(), + ) + } + + pub fn build_get_cdn_servers_call( + &self, + cell_id: u32, + job_id: u64, + ) -> Option { + self.build_authed_service_call( + "ContentServerDirectory.GetServersForSteamPipe#1", + job_id, + CContentServerDirectoryGetServersForSteamPipeRequest { + cell_id, + ..Default::default() + } + .serialize(), + ) + } + + pub fn build_pics_access_tokens( + &self, + packageids: Vec, + appids: Vec, + job_id: u64, + ) -> Option { + self.build_job_proto_message( + EMsg::CLIENT_PICS_ACCESS_TOKEN_REQUEST, + job_id, + CMsgClientPICSAccessTokenRequest { packageids, appids }.serialize(), + 0, + ) + } + + pub fn build_pics_changes_since( + &self, + since_change_number: u32, + job_id: u64, + ) -> Option { + self.build_job_proto_message( + EMsg::CLIENT_PICS_CHANGES_SINCE_REQUEST, + job_id, + CMsgClientPICSChangesSinceRequest { + since_change_number, + ..Default::default() + } + .serialize(), + 0, + ) + } + + pub fn build_pics_product_info( + &self, + packages: Vec, + apps: Vec, + meta_data_only: bool, + job_id: u64, + ) -> Option { + self.build_job_proto_message( + EMsg::CLIENT_PICS_PRODUCT_INFO_REQUEST, + job_id, + CMsgClientPICSProductInfoRequest { + packages, + apps, + meta_data_only, + single_response: false, + ..Default::default() + } + .serialize(), + 0, + ) + } + + pub fn prepare_app_ids(app_id: u32, dlc_app_ids: &[u32]) -> Vec { + let mut all_ids = Vec::with_capacity(1 + dlc_app_ids.len()); + if app_id != 0 { + all_ids.push(app_id); + } + for dlc in dlc_app_ids { + if *dlc == 0 || *dlc == app_id || all_ids.contains(dlc) { + continue; + } + all_ids.push(*dlc); + } + all_ids + } + + pub fn prepare_app_missing_token_ids(&self, all_ids: &[u32]) -> Vec { + all_ids + .iter() + .copied() + .filter(|id| { + self.library + .find_app(*id) + .is_some_and(|app| app.missing_token && app.access_token == 0) + }) + .collect() + } + + pub fn prepare_app_pics_requests(&self, all_ids: &[u32]) -> Vec { + all_ids + .iter() + .map(|id| PicsAppInfoReq { + appid: *id, + access_token: self + .library + .find_app(*id) + .map(|app| app.access_token) + .unwrap_or_default(), + only_public_obsolete: false, + }) + .collect() + } + + pub fn build_family_group_call( + &self, + family_group_id: u64, + job_id: u64, + ) -> Option { + self.build_authed_service_call( + "FamilyGroups.GetFamilyGroup#1", + job_id, + CFamilyGroupsGetFamilyGroupRequest { + family_groupid: family_group_id, + } + .serialize(), + ) + } + + pub fn build_owned_games_call( + &self, + steam_id: u64, + job_id: u64, + ) -> Option { + self.build_authed_service_call( + "Player.GetOwnedGames#1", + job_id, + CPlayerGetOwnedGamesRequest { + steamid: steam_id, + include_appinfo: true, + include_played_free_games: true, + include_free_sub: true, + include_extended_appinfo: true, + } + .serialize(), + ) + } + + pub fn build_inventory_item_def_meta_call( + &self, + app_id: u32, + job_id: u64, + ) -> Option { + self.build_authed_service_call( + "Inventory.GetItemDefMeta#1", + job_id, + CInventoryGetItemDefMetaRequest { appid: app_id }.serialize(), + ) + } + + pub fn build_published_file_subscribed_call( + &self, + app_id: u32, + page: u32, + num_per_page: u32, + job_id: u64, + ) -> Option { + self.build_authed_service_call( + "PublishedFile.GetUserFiles#1", + job_id, + CPublishedFileGetUserFilesRequest { + steamid: self.steam_id(), + appid: app_id, + page, + numperpage: num_per_page, + request_type: "mysubscriptions".to_string(), + filetype: u32::MAX, + } + .serialize(), + ) + } + + pub fn build_cloud_user_quota_call(&self, job_id: u64) -> Option { + self.build_authed_service_call( + "Cloud.GetUserQuota#1", + job_id, + CCloudGetUserQuotaRequest.serialize(), + ) + } + + pub fn build_cloud_app_file_changelist_call( + &self, + app_id: u32, + synced_change_number: u64, + job_id: u64, + ) -> Option { + self.build_authed_service_call( + "Cloud.GetAppFileChangelist#1", + job_id, + CCloudGetAppFileChangelistRequest { + appid: app_id, + synced_change_number, + } + .serialize(), + ) + } + + pub fn build_cloud_file_download_info_call( + &self, + app_id: u32, + filename: impl Into, + job_id: u64, + ) -> Option { + self.build_authed_service_call( + "Cloud.ClientFileDownload#1", + job_id, + CCloudClientFileDownloadRequest { + appid: app_id, + filename: filename.into(), + realm: 1, + } + .serialize(), + ) + } + + pub fn build_cloud_begin_app_upload_batch_call( + &self, + app_id: u32, + machine_name: impl Into, + files_to_upload: Vec, + files_to_delete: Vec, + client_id: u64, + job_id: u64, + ) -> Option { + self.build_authed_service_call( + "Cloud.BeginAppUploadBatch#1", + job_id, + CCloudBeginAppUploadBatchRequest { + appid: app_id, + machine_name: machine_name.into(), + files_to_upload, + files_to_delete, + client_id, + app_build_id: 0, + } + .serialize(), + ) + } + + #[allow(clippy::too_many_arguments)] + pub fn build_cloud_begin_file_upload_call( + &self, + app_id: u32, + filename: impl Into, + file_size: u32, + raw_file_size: u32, + file_sha: Vec, + time_stamp: u64, + upload_batch_id: u64, + job_id: u64, + ) -> Option { + self.build_authed_service_call( + "Cloud.ClientBeginFileUpload#1", + job_id, + CCloudClientBeginFileUploadRequest { + appid: app_id, + file_size, + raw_file_size, + file_sha, + time_stamp, + filename: filename.into(), + upload_batch_id, + } + .serialize(), + ) + } + + pub fn build_cloud_commit_file_upload_call( + &self, + transfer_succeeded: bool, + app_id: u32, + file_sha: Vec, + filename: impl Into, + job_id: u64, + ) -> Option { + self.build_authed_service_call( + "Cloud.ClientCommitFileUpload#1", + job_id, + CCloudClientCommitFileUploadRequest { + transfer_succeeded, + appid: app_id, + file_sha, + filename: filename.into(), + } + .serialize(), + ) + } + + pub fn build_cloud_complete_app_upload_batch_call( + &self, + app_id: u32, + batch_id: u64, + batch_eresult: u32, + job_id: u64, + ) -> Option { + self.build_authed_service_call( + "Cloud.CompleteAppUploadBatchBlocking#1", + job_id, + CCloudCompleteAppUploadBatchRequest { + appid: app_id, + batch_id, + batch_eresult, + } + .serialize(), + ) + } + + #[allow(clippy::too_many_arguments)] + pub fn build_cloud_launch_intent_call( + &self, + app_id: u32, + client_id: u64, + machine_name: impl Into, + ignore_pending_operations: bool, + os_type: i32, + job_id: u64, + ) -> Option { + self.build_authed_service_call( + "Cloud.SignalAppLaunchIntent#1", + job_id, + CCloudAppLaunchIntentRequest { + appid: app_id, + client_id, + machine_name: machine_name.into(), + ignore_pending_operations, + os_type, + } + .serialize(), + ) + } + + pub fn build_cloud_exit_sync_done_call( + &self, + app_id: u32, + client_id: u64, + uploads_completed: bool, + uploads_required: bool, + job_id: u64, + ) -> Option { + self.build_authed_service_call( + "Cloud.SignalAppExitSyncDone#1", + job_id, + CCloudAppExitSyncDoneNotification { + appid: app_id, + client_id, + uploads_completed, + uploads_required, + } + .serialize(), + ) + } + + pub fn build_lobby_get_list( + &self, + app_id: u32, + filters: Vec, + num_lobbies_requested: i32, + job_id: u64, + ) -> Option { + if app_id == 0 { + return None; + } + self.build_job_proto_message( + EMsg::CLIENT_MMS_GET_LOBBY_LIST, + job_id, + CMsgClientMMSGetLobbyList { + app_id, + num_lobbies_requested, + cell_id: 0, + filters, + } + .serialize(), + app_id, + ) + } + + pub fn build_lobby_create( + &self, + app_id: u32, + lobby_type: i32, + max_members: i32, + job_id: u64, + ) -> Option { + if app_id == 0 { + return None; + } + let persona_name = self + .self_persona() + .map(|persona| persona.player_name) + .unwrap_or_default(); + self.build_job_proto_message( + EMsg::CLIENT_MMS_CREATE_LOBBY, + job_id, + CMsgClientMMSCreateLobby { + app_id, + max_members, + lobby_type, + lobby_flags: 0, + metadata: Vec::new(), + persona_name_owner: persona_name, + } + .serialize(), + app_id, + ) + } + + pub fn build_lobby_join( + &self, + app_id: u32, + lobby_sid: u64, + job_id: u64, + ) -> Option { + if app_id == 0 || lobby_sid == 0 { + return None; + } + let persona_name = self + .self_persona() + .map(|persona| persona.player_name) + .unwrap_or_default(); + self.build_job_proto_message( + EMsg::CLIENT_MMS_JOIN_LOBBY, + job_id, + CMsgClientMMSJoinLobby { + app_id, + steam_id_lobby: lobby_sid, + persona_name, + } + .serialize(), + app_id, + ) + } + + pub fn build_lobby_leave(&self, app_id: u32, lobby_sid: u64) -> Option { + if app_id == 0 || lobby_sid == 0 { + return None; + } + self.build_outbound_proto_message( + EMsg::CLIENT_MMS_LEAVE_LOBBY, + CMsgClientMMSLeaveLobby { + app_id, + steam_id_lobby: lobby_sid, + } + .serialize(), + app_id, + ) + } + + pub fn build_lobby_send_chat( + &self, + app_id: u32, + lobby_sid: u64, + data: Vec, + ) -> Option { + if app_id == 0 || lobby_sid == 0 || data.is_empty() { + return None; + } + self.build_outbound_proto_message( + EMsg::CLIENT_MMS_SEND_LOBBY_CHAT_MSG, + CMsgClientMMSSendLobbyChatMsg { + app_id, + steam_id_lobby: lobby_sid, + lobby_message: data, + } + .serialize(), + app_id, + ) + } + + #[allow(clippy::too_many_arguments)] + pub fn build_lobby_set_data( + &self, + app_id: u32, + lobby_sid: u64, + steam_id_member: u64, + metadata: Vec, + max_members: i32, + lobby_type: i32, + lobby_flags: i32, + job_id: u64, + ) -> Option { + if app_id == 0 || lobby_sid == 0 { + return None; + } + self.build_job_proto_message( + EMsg::CLIENT_MMS_SET_LOBBY_DATA, + job_id, + CMsgClientMMSSetLobbyData { + app_id, + steam_id_lobby: lobby_sid, + steam_id_member, + max_members, + lobby_type, + lobby_flags, + metadata, + } + .serialize(), + app_id, + ) + } + + pub fn build_lobby_set_owner( + &self, + app_id: u32, + lobby_sid: u64, + new_owner_sid: u64, + job_id: u64, + ) -> Option { + if app_id == 0 || lobby_sid == 0 || new_owner_sid == 0 { + return None; + } + self.build_job_proto_message( + EMsg::CLIENT_MMS_SET_LOBBY_OWNER, + job_id, + CMsgClientMMSSetLobbyOwner { + app_id, + steam_id_lobby: lobby_sid, + steam_id_new_owner: new_owner_sid, + } + .serialize(), + app_id, + ) + } + + pub fn build_lobby_invite_user( + &self, + app_id: u32, + lobby_sid: u64, + invitee_sid: u64, + ) -> Option { + if app_id == 0 || lobby_sid == 0 || invitee_sid == 0 { + return None; + } + self.build_outbound_proto_message( + EMsg::CLIENT_MMS_INVITE_TO_LOBBY, + CMsgClientMMSInviteToLobby { + app_id, + steam_id_lobby: lobby_sid, + steam_id_user_invited: invitee_sid, + } + .serialize(), + app_id, + ) + } + + pub fn route_inbound( + &self, + emsg: EMsg, + header: &CMsgProtoBufHeader, + body: &[u8], + ) -> InboundAction { + match emsg { + EMsg::SERVICE_METHOD_RESPONSE + | EMsg::CLIENT_PICS_ACCESS_TOKEN_RESPONSE + | EMsg::CLIENT_PICS_CHANGES_SINCE_RESPONSE + | EMsg::CLIENT_GET_APP_OWNERSHIP_TICKET_RESPONSE + | EMsg::CLIENT_REQUEST_ENCRYPTED_APP_TICKET_RESPONSE + | EMsg::CLIENT_GET_USER_STATS_RESPONSE + | EMsg::CLIENT_GET_DEPOT_DECRYPTION_KEY_RESPONSE + | EMsg::CLIENT_MMS_CREATE_LOBBY_RESPONSE + | EMsg::CLIENT_MMS_JOIN_LOBBY_RESPONSE + | EMsg::CLIENT_MMS_LEAVE_LOBBY_RESPONSE + | EMsg::CLIENT_MMS_GET_LOBBY_LIST_RESPONSE + | EMsg::CLIENT_MMS_SET_LOBBY_DATA_RESPONSE + | EMsg::CLIENT_MMS_SET_LOBBY_OWNER_RESPONSE + | EMsg::CLIENT_MMS_GET_LOBBY_STATUS_RESPONSE => { + if header.jobid_target == INVALID_JOB_ID { + InboundAction::Ignored + } else { + let eresult = if header.eresult == -1 { + 1 + } else { + header.eresult + }; + InboundAction::DeliverJob(JobResult { + eresult, + error_message: header.error_message.clone(), + body: body.to_vec(), + synthetic_failure: false, + }) + } + } + EMsg::CLIENT_PICS_PRODUCT_INFO_RESPONSE => InboundAction::PicsProductInfo, + EMsg::MULTI => InboundAction::Multi, + EMsg::CLIENT_LOGON_RESPONSE => { + let Some(resp) = CMsgClientLogonResponse::deserialize(body) else { + return InboundAction::ParseFailed("ClientLogonResponse"); + }; + if resp.eresult == 1 { + self.state + .store(ClientState::LoggedOn as u8, Ordering::Relaxed); + self.steam_id + .store(resp.client_supplied_steamid, Ordering::Relaxed); + self.session_id + .store(header.client_sessionid, Ordering::Relaxed); + self.family_group_id + .store(resp.family_group_id, Ordering::Relaxed); + self.server_realtime + .store(resp.rtime32_server_time, Ordering::Relaxed); + InboundAction::LogonOk + } else { + InboundAction::LoggedOff + } + } + EMsg::CLIENT_LOGGED_OFF | EMsg::CLIENT_SERVER_UNAVAILABLE => { + if self.state() == ClientState::LoggedOn { + self.state + .store(ClientState::Connected as u8, Ordering::Relaxed); + } + self.steam_id.store(0, Ordering::Relaxed); + self.session_id.store(0, Ordering::Relaxed); + self.family_group_id.store(0, Ordering::Relaxed); + self.server_realtime.store(0, Ordering::Relaxed); + InboundAction::LoggedOff + } + EMsg::CLIENT_LICENSE_LIST => { + let Some(msg) = CMsgClientLicenseList::deserialize(body) else { + return InboundAction::ParseFailed("ClientLicenseList"); + }; + let count = msg.licenses.len(); + *self.license_list.lock().expect("license list poisoned") = msg.licenses.clone(); + self.library.ingest_license_list(&msg); + InboundAction::LicenseList(count) + } + EMsg::CLIENT_FRIENDS_LIST => { + let Some(msg) = CMsgClientFriendsList::deserialize(body) else { + return InboundAction::ParseFailed("ClientFriendsList"); + }; + let mut friends = self.friends.lock().expect("friends list poisoned"); + if !msg.bincremental { + friends.clear(); + } + for friend in &msg.friends { + if friend.efriendrelationship == 0 { + friends.remove(&friend.ulfriendid); + } else { + friends.insert(friend.ulfriendid, friend.efriendrelationship); + } + } + InboundAction::FriendsList(friends.len()) + } + EMsg::CLIENT_PERSONA_STATE => { + let Some(msg) = CMsgClientPersonaState::deserialize(body) else { + return InboundAction::ParseFailed("ClientPersonaState"); + }; + let count = msg.friends.len(); + let self_id = self.steam_id(); + let mut self_persona = self.self_persona.lock().expect("self persona poisoned"); + let mut friend_personas = self + .friend_personas + .lock() + .expect("friend personas poisoned"); + for friend in msg.friends { + if friend.friendid == self_id { + match self_persona.as_mut() { + Some(existing) => { + if !friend.player_name.is_empty() { + existing.player_name = friend.player_name; + } + if friend.has_persona_state { + existing.persona_state = friend.persona_state; + } + if friend.has_game { + existing.game_played_app_id = friend.game_played_app_id; + } + if !friend.game_name.is_empty() { + existing.game_name = friend.game_name; + } + if friend.gameid != 0 { + existing.gameid = friend.gameid; + } + if !friend.avatar_hash.is_empty() { + existing.avatar_hash = friend.avatar_hash; + } + if !friend.rich_presence.is_empty() { + existing.rich_presence = friend.rich_presence; + } + } + None => *self_persona = Some(friend), + } + } else { + let slot = friend_personas.entry(friend.friendid).or_default(); + slot.sid = friend.friendid; + if !friend.player_name.is_empty() { + slot.player_name = friend.player_name; + } + if friend.has_persona_state { + slot.persona_state = friend.persona_state; + } + if friend.has_game { + slot.game_played_app_id = friend.game_played_app_id; + } + if !friend.game_name.is_empty() { + slot.game_name = friend.game_name; + } + if friend.gameid != 0 { + slot.gameid = friend.gameid; + } + if !friend.avatar_hash.is_empty() { + slot.avatar_hash = friend.avatar_hash; + } + if !friend.rich_presence.is_empty() { + slot.rich_presence = friend.rich_presence; + } + } + } + InboundAction::PersonaState(count) + } + EMsg::CLIENT_PLAYING_SESSION_STATE => { + let Some(msg) = CMsgClientPlayingSessionState::deserialize(body) else { + return InboundAction::ParseFailed("ClientPlayingSessionState"); + }; + self.playing_blocked + .store(msg.playing_blocked, Ordering::Relaxed); + InboundAction::PlayingSessionState(msg.playing_blocked) + } + EMsg::CLIENT_ACCOUNT_INFO => parse_account_info(body) + .map(InboundAction::AccountInfo) + .unwrap_or(InboundAction::ParseFailed("ClientAccountInfo")), + EMsg::CLIENT_MMS_LOBBY_DATA + | EMsg::CLIENT_MMS_LOBBY_CHAT_MSG + | EMsg::CLIENT_MMS_USER_JOINED_LOBBY + | EMsg::CLIENT_MMS_USER_LEFT_LOBBY => InboundAction::LobbyPush, + EMsg::SERVICE_METHOD | EMsg::SERVICE_METHOD_SEND_TO_CLIENT => { + if header + .target_job_name + .starts_with("FriendMessagesClient.IncomingMessage") + { + if let Some(note) = + crate::pb::cfriendmessages::CFriendMessagesIncomingMessageNotification::deserialize(body) + { + if note.chat_entry_type + == crate::pb::cfriendmessages::CHAT_ENTRY_TYPE_TEXT + && !note.message.is_empty() + { + self.push_incoming_message(IncomingFriendMessage { + friend_id: note.steamid_friend, + from_self: note.local_echo, + message: note.message, + timestamp: note.rtime32_server_timestamp, + ordinal: note.ordinal, + }); + } + } + } + InboundAction::ClientMessage + } + _ => InboundAction::ClientMessage, + } + } + + pub fn license_list(&self) -> Vec { + self.license_list + .lock() + .expect("license list poisoned") + .clone() + } + + pub fn friends_list(&self) -> Vec { + self.friends + .lock() + .expect("friends list poisoned") + .iter() + .filter_map(|(sid, relationship)| (*relationship == 3).then_some(*sid)) + .collect() + } + + pub fn self_persona(&self) -> Option { + self.self_persona + .lock() + .expect("self persona poisoned") + .clone() + } + + pub fn friend_personas(&self) -> Vec { + self.friend_personas + .lock() + .expect("friend personas poisoned") + .values() + .filter(|snapshot| !snapshot.player_name.is_empty()) + .cloned() + .collect() + } + + pub fn push_incoming_message(&self, message: IncomingFriendMessage) { + self.incoming_messages + .lock() + .expect("incoming messages poisoned") + .push(message); + } + + pub fn drain_incoming_messages(&self) -> Vec { + std::mem::take( + &mut *self + .incoming_messages + .lock() + .expect("incoming messages poisoned"), + ) + } +} + +fn parse_account_info(body: &[u8]) -> Option { + let mut reader = crate::proto_wire::Reader::new(body); + let mut info = AccountInfoSnapshot::default(); + while !reader.eof() { + let Some(tag) = reader.next_tag() else { + return reader.ok().then_some(info); + }; + match tag.field_number { + 1 => info.persona_name = reader.string()?, + 2 => info.ip_country = reader.string()?, + 15 => info.two_factor_enabled = reader.u32()? != 0, + 17 => info.phone_verified = reader.boolean()?, + 19 => info.phone_identifying = reader.boolean()?, + 20 => info.phone_requires_verification = reader.boolean()?, + _ => { + if !reader.skip(tag.wire_type) { + return None; + } + } + } + } + Some(info) +} + +pub fn parse_cmsg_multi(body: &[u8]) -> Option { + let mut reader = crate::proto_wire::Reader::new(body); + let mut multi = CMsgMultiBody::default(); + while !reader.eof() { + let Some(tag) = reader.next_tag() else { + return reader.ok().then_some(multi); + }; + match tag.field_number { + 1 => multi.size_unzipped = reader.u32()?, + 2 => multi.message_body = reader.bytes()?.to_vec(), + _ => { + if !reader.skip(tag.wire_type) { + return None; + } + } + } + } + Some(multi) +} + +pub fn decode_multi_records(body: &[u8]) -> Option>> { + let multi = parse_cmsg_multi(body)?; + let records = if multi.size_unzipped > 0 { + gunzip_or_zlib(&multi.message_body, multi.size_unzipped as usize)? + } else { + multi.message_body + }; + + let mut out = Vec::new(); + let mut offset = 0usize; + while offset + 4 <= records.len() { + let inner_len = crate::wire_format::read_u32_le(&records[offset..]) as usize; + offset += 4; + if inner_len == 0 || offset + inner_len > records.len() { + return None; + } + out.push(records[offset..offset + inner_len].to_vec()); + offset += inner_len; + } + (offset == records.len()).then_some(out) +} + +fn gunzip_or_zlib(compressed: &[u8], expected_size: usize) -> Option> { + let mut out = Vec::with_capacity(expected_size); + if GzDecoder::new(compressed).read_to_end(&mut out).is_ok() && !out.is_empty() { + return Some(out); + } + out.clear(); + if ZlibDecoder::new(compressed).read_to_end(&mut out).is_ok() && !out.is_empty() { + return Some(out); + } + None +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::proto_wire::{WireType, Writer}; + use flate2::write::GzEncoder; + use flate2::Compression; + use std::io::Write; + + #[test] + fn service_method_call_sets_authed_and_non_authed_emsgs() { + let core = CMClientCore::default(); + core.steam_id.store(123, Ordering::Relaxed); + core.session_id.store(7, Ordering::Relaxed); + let authed = crate::proto_envelope::decode_proto_envelope(&core.build_service_method_call( + "Player.GetOwnedGames#1", + true, + 55, + b"req", + )) + .unwrap(); + assert_eq!(authed.emsg, EMsg::SERVICE_METHOD_CALL_FROM_CLIENT); + assert_eq!(authed.header.steamid, 123); + assert_eq!(authed.header.client_sessionid, 7); + assert_eq!(authed.header.jobid_source, 55); + assert_eq!(authed.body, b"req"); + + let non = crate::proto_envelope::decode_proto_envelope(&core.build_service_method_call( + "Authentication.Begin#1", + false, + 56, + b"req", + )) + .unwrap(); + assert_eq!(non.emsg, EMsg::SERVICE_METHOD_CALL_FROM_CLIENT_NON_AUTHED); + // Matches C++: header carries current steamid/session even on non-authed + // calls (post-logon pre-existing identity is harmless; pre-logon both are 0). + assert_eq!(non.header.steamid, 123); + assert_eq!(non.header.client_sessionid, 7); + } + + #[test] + fn cmsg_multi_decodes_plain_and_gzip_records() { + let first = encode_proto_envelope(EMsg::CLIENT_HELLO, &CMsgProtoBufHeader::default(), b"a"); + let second = encode_proto_envelope( + EMsg::CLIENT_FRIENDS_LIST, + &CMsgProtoBufHeader::default(), + b"b", + ); + let mut records = Vec::new(); + records.extend_from_slice(&(first.len() as u32).to_le_bytes()); + records.extend_from_slice(&first); + records.extend_from_slice(&(second.len() as u32).to_le_bytes()); + records.extend_from_slice(&second); + + let mut body = Vec::new(); + { + let mut writer = Writer::new(&mut body); + writer.bytes_field(2, &records); + } + assert_eq!( + decode_multi_records(&body).unwrap(), + vec![first.clone(), second.clone()] + ); + + let mut encoder = GzEncoder::new(Vec::new(), Compression::default()); + encoder.write_all(&records).unwrap(); + let zipped = encoder.finish().unwrap(); + let mut zipped_body = Vec::new(); + { + let mut writer = Writer::new(&mut zipped_body); + writer.uint32_field(1, records.len() as u32); + writer.bytes_field(2, &zipped); + } + assert_eq!( + decode_multi_records(&zipped_body).unwrap(), + vec![first, second] + ); + } + + #[test] + fn lifecycle_builders_match_cpp_session_flow() { + let core = CMClientCore::default(); + let hello = core.build_client_hello(); + assert_eq!(hello.emsg, EMsg::CLIENT_HELLO); + + assert!(core + .build_logon_with_refresh_token("refresh", "ada", 0) + .is_none()); + core.set_state(ClientState::Connected); + let logon = core + .build_logon_with_refresh_token("refresh", "ada", 123) + .unwrap(); + assert_eq!(logon.emsg, EMsg::CLIENT_LOGON); + let decoded = crate::proto_envelope::decode_proto_envelope(&logon.wire).unwrap(); + assert_eq!(decoded.header.steamid, 0x0110_0001_0000_0000); + assert!(logon + .body + .windows("refresh".len()) + .any(|window| window == b"refresh")); + assert!(core + .build_logon_with_refresh_token("refresh", "", 123) + .is_some()); + + assert!(core.build_logoff().is_none()); + core.set_state(ClientState::LoggedOn); + assert_eq!(core.build_logoff().unwrap().emsg, EMsg::CLIENT_LOG_OFF); + } + + #[test] + fn logon_response_updates_session_identity() { + let core = CMClientCore::default(); + let mut body = Vec::new(); + { + let mut w = Writer::new(&mut body); + w.int32_field(1, 1); + w.tag(20, WireType::Fixed64); + w.raw_bytes(&765u64.to_le_bytes()); + w.fixed32_field(5, 1_700_000_000); + w.uint64_field(31, 99); + } + let header = CMsgProtoBufHeader { + steamid: 999, + client_sessionid: 42, + ..Default::default() + }; + assert_eq!( + core.route_inbound(EMsg::CLIENT_LOGON_RESPONSE, &header, &body), + InboundAction::LogonOk + ); + assert_eq!(core.state(), ClientState::LoggedOn); + assert_eq!(core.steam_id(), 765); + assert_eq!(core.session_id(), 42); + assert_eq!(core.family_group_id(), 99); + assert_eq!(core.server_realtime(), 1_700_000_000); + + assert_eq!( + core.route_inbound(EMsg::CLIENT_LOGGED_OFF, &CMsgProtoBufHeader::default(), &[]), + InboundAction::LoggedOff + ); + assert_eq!(core.state(), ClientState::Connected); + assert_eq!(core.steam_id(), 0); + assert_eq!(core.session_id(), 0); + assert_eq!(core.family_group_id(), 0); + assert_eq!(core.server_realtime(), 0); + } + + #[test] + fn routes_license_friends_and_playing_state_pushes() { + let core = CMClientCore::default(); + let mut lic = Vec::new(); + Writer::new(&mut lic).uint32_field(1, 100); + let mut license_body = Vec::new(); + Writer::new(&mut license_body).submessage_field(2, &lic); + assert_eq!( + core.route_inbound( + EMsg::CLIENT_LICENSE_LIST, + &CMsgProtoBufHeader::default(), + &license_body + ), + InboundAction::LicenseList(1) + ); + assert_eq!(core.license_list()[0].package_id, 100); + + let mut friend = Vec::new(); + { + let mut w = Writer::new(&mut friend); + w.fixed64_field(1, 555); + w.uint32_field(2, 3); + } + let mut blocked_friend = Vec::new(); + { + let mut w = Writer::new(&mut blocked_friend); + w.fixed64_field(1, 999); + w.uint32_field(2, 1); + } + let mut friends_body = Vec::new(); + { + let mut w = Writer::new(&mut friends_body); + w.bool_field(1, false); + w.submessage_field(2, &friend); + w.submessage_field(2, &blocked_friend); + } + assert_eq!( + core.route_inbound( + EMsg::CLIENT_FRIENDS_LIST, + &CMsgProtoBufHeader::default(), + &friends_body + ), + InboundAction::FriendsList(2) + ); + assert_eq!(core.friends_list(), [555]); + + let mut playing = Vec::new(); + { + let mut w = Writer::new(&mut playing); + w.tag(2, WireType::Varint); + w.varint(1); + } + assert_eq!( + core.route_inbound( + EMsg::CLIENT_PLAYING_SESSION_STATE, + &CMsgProtoBufHeader::default(), + &playing + ), + InboundAction::PlayingSessionState(true) + ); + assert!(core.is_playing_blocked()); + } + + #[test] + fn friend_persona_snapshots_exclude_empty_names_like_cpp() { + let core = logged_on_core(); + let mut named = Vec::new(); + { + let mut w = Writer::new(&mut named); + w.fixed64_field(1, 111); + w.string_field(15, "Ada"); + } + let mut unnamed = Vec::new(); + Writer::new(&mut unnamed).fixed64_field(1, 222); + let mut body = Vec::new(); + { + let mut w = Writer::new(&mut body); + w.submessage_field(2, &named); + w.submessage_field(2, &unnamed); + } + assert_eq!( + core.route_inbound( + EMsg::CLIENT_PERSONA_STATE, + &CMsgProtoBufHeader::default(), + &body + ), + InboundAction::PersonaState(2) + ); + let personas = core.friend_personas(); + assert_eq!(personas.len(), 1); + assert_eq!(personas[0].sid, 111); + } + + #[test] + fn incremental_friend_removal_and_persona_partial_updates_match_cpp() { + let core = logged_on_core(); + let mut add = Vec::new(); + { + let mut friend = Vec::new(); + let mut w = Writer::new(&mut friend); + w.fixed64_field(1, 333); + w.uint32_field(2, 3); + Writer::new(&mut add).submessage_field(2, &friend); + } + assert_eq!( + core.route_inbound( + EMsg::CLIENT_FRIENDS_LIST, + &CMsgProtoBufHeader::default(), + &add + ), + InboundAction::FriendsList(1) + ); + assert_eq!(core.friends_list(), [333]); + + let mut remove = Vec::new(); + { + let mut friend = Vec::new(); + let mut w = Writer::new(&mut friend); + w.fixed64_field(1, 333); + w.uint32_field(2, 0); + let mut w = Writer::new(&mut remove); + w.bool_field(1, true); + w.submessage_field(2, &friend); + } + assert_eq!( + core.route_inbound( + EMsg::CLIENT_FRIENDS_LIST, + &CMsgProtoBufHeader::default(), + &remove + ), + InboundAction::FriendsList(0) + ); + assert!(core.friends_list().is_empty()); + + let mut first_friend = Vec::new(); + { + let mut w = Writer::new(&mut first_friend); + w.fixed64_field(1, 444); + w.string_field(15, "Grace"); + w.bytes_field(31, &[9, 9]); + } + let mut first_body = Vec::new(); + Writer::new(&mut first_body).submessage_field(2, &first_friend); + core.route_inbound( + EMsg::CLIENT_PERSONA_STATE, + &CMsgProtoBufHeader::default(), + &first_body, + ); + + let mut partial_friend = Vec::new(); + { + let mut w = Writer::new(&mut partial_friend); + w.fixed64_field(1, 444); + w.uint32_field(2, 2); + } + let mut partial_body = Vec::new(); + Writer::new(&mut partial_body).submessage_field(2, &partial_friend); + core.route_inbound( + EMsg::CLIENT_PERSONA_STATE, + &CMsgProtoBufHeader::default(), + &partial_body, + ); + let persona = core.friend_personas().pop().unwrap(); + assert_eq!(persona.player_name, "Grace"); + assert_eq!(persona.avatar_hash, [9, 9]); + assert_eq!(persona.persona_state, 2); + } + + #[test] + fn account_info_push_parses_bridge_fields() { + let core = CMClientCore::default(); + let mut body = Vec::new(); + { + let mut w = Writer::new(&mut body); + w.string_field(1, "Ada"); + w.string_field(2, "US"); + w.uint32_field(15, 1); + w.bool_field(17, true); + w.bool_field(19, true); + w.bool_field(20, false); + } + assert_eq!( + core.route_inbound( + EMsg::CLIENT_ACCOUNT_INFO, + &CMsgProtoBufHeader::default(), + &body + ), + InboundAction::AccountInfo(AccountInfoSnapshot { + persona_name: "Ada".into(), + ip_country: "US".into(), + two_factor_enabled: true, + phone_verified: true, + phone_identifying: true, + phone_requires_verification: false, + }) + ); + } + + #[test] + fn outbound_persona_and_friend_requests_match_cpp_emsgs() { + let core = logged_on_core(); + let persona = core.build_set_persona_name("Ada", 1).unwrap(); + assert_eq!(persona.emsg, EMsg::CLIENT_CHANGE_STATUS); + assert_eq!( + persona.body, + CMsgClientChangeStatus { + persona_state: 1, + player_name: "Ada".into(), + persona_set_by_user: true, + need_persona_response: false, + } + .serialize() + ); + + let request = core + .build_request_friend_personas(&[0, 123, 456], 0x47) + .unwrap(); + assert_eq!(request.emsg, EMsg::CLIENT_REQUEST_FRIEND_DATA); + assert!(request.body.windows(2).any(|w| w == [0x08, 0x47])); + + let self_request = core.build_request_user_persona().unwrap(); + assert_eq!(self_request.emsg, EMsg::CLIENT_REQUEST_FRIEND_DATA); + } + + #[test] + fn outbound_games_stats_and_ticket_requests_include_routing_and_jobs() { + let core = logged_on_core(); + let games = core.build_notify_games_played(480).unwrap(); + assert_eq!(games.emsg, EMsg::CLIENT_GAMES_PLAYED_WITH_DATA_BLOB); + assert_eq!( + games.body, + CMsgClientGamesPlayed { + games_played: vec![GamePlayedEntry { + game_id: 480, + ..Default::default() + }], + client_os_type: 0 + } + .serialize() + ); + assert!(core.build_notify_games_played(0).unwrap().body.len() <= games.body.len()); + + let stats = core + .build_store_user_stats(480, core.steam_id(), 0, &[(1, 2), (3, 0)]) + .unwrap(); + assert_eq!(stats.emsg, EMsg::CLIENT_STORE_USER_STATS_2); + assert_eq!(stats.routing_appid, 480); + + let ticket = core.build_get_app_ownership_ticket(480, 99).unwrap(); + let decoded = crate::proto_envelope::decode_proto_envelope(&ticket.wire).unwrap(); + assert_eq!(decoded.emsg, EMsg::CLIENT_GET_APP_OWNERSHIP_TICKET); + assert_eq!(decoded.header.jobid_source, 99); + assert_eq!(decoded.header.jobid_target, INVALID_JOB_ID); + + let encrypted = core.build_request_encrypted_app_ticket(480, 123).unwrap(); + let decoded = crate::proto_envelope::decode_proto_envelope(&encrypted.wire).unwrap(); + assert_eq!(decoded.emsg, EMsg::CLIENT_REQUEST_ENCRYPTED_APP_TICKET); + assert_eq!(decoded.header.jobid_source, 123); + assert_eq!(decoded.header.jobid_target, INVALID_JOB_ID); + assert_eq!(decoded.header.routing_appid, 0); + } + + #[test] + fn outbound_rich_presence_is_authed_service_call() { + let core = logged_on_core(); + let call = core + .build_rich_presence_call( + 480, + [ + ("status".to_string(), "Playing".to_string()), + ("".into(), "skip".into()), + ], + 77, + ) + .unwrap(); + assert_eq!(call.method_name, "Player.SetRichPresence#1"); + assert!(call.authed); + let decoded = crate::proto_envelope::decode_proto_envelope(&call.wire).unwrap(); + assert_eq!(decoded.emsg, EMsg::SERVICE_METHOD_CALL_FROM_CLIENT); + assert_eq!(decoded.header.jobid_source, 77); + assert_eq!(decoded.header.steamid, core.steam_id()); + } + + #[test] + fn downloader_service_builders_match_cpp_rules() { + let core = logged_on_core(); + let depot_key = core.build_get_depot_decryption_key(11, 22, 33).unwrap(); + let decoded = crate::proto_envelope::decode_proto_envelope(&depot_key.wire).unwrap(); + assert_eq!(decoded.emsg, EMsg::CLIENT_GET_DEPOT_DECRYPTION_KEY); + assert_eq!(decoded.header.jobid_source, 33); + + let public = core + .build_manifest_request_code_call(480, 100, 555, "public", 44) + .unwrap(); + assert_eq!( + public.method_name, + "ContentServerDirectory.GetManifestRequestCode#1" + ); + assert!(!public.request_body.windows(6).any(|w| w == b"public")); + + let beta = core + .build_manifest_request_code_call(480, 100, 555, "Beta", 45) + .unwrap(); + assert!(beta.request_body.windows(4).any(|w| w == b"Beta")); + + let servers = core.build_get_cdn_servers_call(7, 46).unwrap(); + assert_eq!( + servers.method_name, + "ContentServerDirectory.GetServersForSteamPipe#1" + ); + } + + #[test] + fn pics_builders_set_job_ids_and_emsgs() { + let core = logged_on_core(); + let access = core + .build_pics_access_tokens(vec![100], vec![480], 1) + .unwrap(); + assert_eq!(access.emsg, EMsg::CLIENT_PICS_ACCESS_TOKEN_REQUEST); + assert_eq!( + crate::proto_envelope::decode_proto_envelope(&access.wire) + .unwrap() + .header + .jobid_source, + 1 + ); + + let changes = core.build_pics_changes_since(99, 2).unwrap(); + assert_eq!(changes.emsg, EMsg::CLIENT_PICS_CHANGES_SINCE_REQUEST); + assert!(changes.body.windows(2).any(|w| w == [0x08, 99])); + + let product = core + .build_pics_product_info( + vec![PicsPackageInfoReq { + packageid: 100, + access_token: 7, + }], + vec![PicsAppInfoReq { + appid: 480, + access_token: 8, + only_public_obsolete: false, + }], + true, + 3, + ) + .unwrap(); + assert_eq!(product.emsg, EMsg::CLIENT_PICS_PRODUCT_INFO_REQUEST); + assert!(product.body.windows(2).any(|w| w == [0x18, 1])); + } + + #[test] + fn account_inventory_workshop_and_family_service_builders() { + let core = logged_on_core(); + assert_eq!( + core.build_family_group_call(123, 10).unwrap().method_name, + "FamilyGroups.GetFamilyGroup#1" + ); + + let owned = core.build_owned_games_call(765, 11).unwrap(); + assert_eq!(owned.method_name, "Player.GetOwnedGames#1"); + assert!(owned.request_body.windows(2).any(|w| w == [0x10, 1])); + assert!(owned.request_body.windows(2).any(|w| w == [0x18, 1])); + + assert_eq!( + core.build_inventory_item_def_meta_call(480, 12) + .unwrap() + .method_name, + "Inventory.GetItemDefMeta#1" + ); + + let workshop = core + .build_published_file_subscribed_call(480, 2, 50, 13) + .unwrap(); + assert_eq!(workshop.method_name, "PublishedFile.GetUserFiles#1"); + assert!(workshop + .request_body + .windows("mysubscriptions".len()) + .any(|w| w == b"mysubscriptions")); + assert!(workshop + .request_body + .windows(6) + .any(|w| w == [0x70, 0xff, 0xff, 0xff, 0xff, 0x0f])); + } + + #[test] + fn cloud_service_builders_use_cpp_method_names() { + let core = logged_on_core(); + assert_eq!( + core.build_cloud_user_quota_call(20).unwrap().method_name, + "Cloud.GetUserQuota#1" + ); + assert_eq!( + core.build_cloud_app_file_changelist_call(480, 7, 21) + .unwrap() + .method_name, + "Cloud.GetAppFileChangelist#1" + ); + assert_eq!( + core.build_cloud_file_download_info_call(480, "save.dat", 22) + .unwrap() + .method_name, + "Cloud.ClientFileDownload#1" + ); + assert_eq!( + core.build_cloud_begin_app_upload_batch_call( + 480, + "machine", + vec!["save.dat".into()], + vec![], + 99, + 23 + ) + .unwrap() + .method_name, + "Cloud.BeginAppUploadBatch#1" + ); + assert_eq!( + core.build_cloud_begin_file_upload_call( + 480, + "save.dat", + 10, + 20, + vec![1; 20], + 123, + 555, + 24 + ) + .unwrap() + .method_name, + "Cloud.ClientBeginFileUpload#1" + ); + assert_eq!( + core.build_cloud_commit_file_upload_call(true, 480, vec![1; 20], "save.dat", 25) + .unwrap() + .method_name, + "Cloud.ClientCommitFileUpload#1" + ); + assert_eq!( + core.build_cloud_complete_app_upload_batch_call(480, 555, 1, 26) + .unwrap() + .method_name, + "Cloud.CompleteAppUploadBatchBlocking#1" + ); + assert_eq!( + core.build_cloud_launch_intent_call(480, 99, "machine", false, 16, 27) + .unwrap() + .method_name, + "Cloud.SignalAppLaunchIntent#1" + ); + assert_eq!( + core.build_cloud_exit_sync_done_call(480, 99, true, false, 28) + .unwrap() + .method_name, + "Cloud.SignalAppExitSyncDone#1" + ); + } + + #[test] + fn prepare_app_helpers_dedupe_dlc_and_pick_tokens() { + let core = logged_on_core(); + let ids = CMClientCore::prepare_app_ids(480, &[0, 480, 481, 481, 482]); + assert_eq!(ids, [480, 481, 482]); + + core.library().ingest_app_pics_response( + &crate::pb::cmsg_client_pics::CMsgClientPICSProductInfoResponse { + apps: vec![crate::pb::cmsg_client_pics::PicsAppInfoResp { + appid: 481, + missing_token: true, + ..Default::default() + }], + ..Default::default() + }, + ); + core.library().ingest_app_access_tokens( + &crate::pb::cmsg_client_pics::CMsgClientPICSAccessTokenResponse { + app_access_tokens: vec![crate::pb::cmsg_client_pics::PicsAppToken { + appid: 482, + access_token: 99, + }], + ..Default::default() + }, + ); + + assert_eq!(core.prepare_app_missing_token_ids(&ids), [481]); + let reqs = core.prepare_app_pics_requests(&ids); + assert_eq!(reqs[0].appid, 480); + assert_eq!(reqs[1].access_token, 0); + assert_eq!(reqs[2].access_token, 99); + } + + #[test] + fn outbound_builders_drop_when_not_logged_on() { + let core = CMClientCore::default(); + assert!(core.build_set_persona_state(1).is_none()); + assert!(core.build_notify_games_played(480).is_none()); + assert!(core.build_get_app_ownership_ticket(480, 1).is_none()); + assert!(core + .build_rich_presence_call(480, [("status".into(), "Playing".into())], 1) + .is_none()); + } + + fn logged_on_core() -> CMClientCore { + let core = CMClientCore::default(); + core.state + .store(ClientState::LoggedOn as u8, Ordering::Relaxed); + core.steam_id.store(7656119, Ordering::Relaxed); + core.session_id.store(42, Ordering::Relaxed); + core + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/cm_runtime.rs b/app/src/main/cpp/wn-steam-client/rust/src/cm_runtime.rs new file mode 100644 index 000000000..25ae43617 --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/cm_runtime.rs @@ -0,0 +1,620 @@ +use crate::cm_bridge; +use crate::cm_client::{decode_multi_records, CMClientCore, ClientState, InboundAction}; +use crate::cmsg_protobuf_header::{CMsgProtoBufHeader, INVALID_JOB_ID}; +use crate::emsg::{has_proto_flag, strip_proto_flag, EMsg}; +use crate::encrypted_channel::{ChannelDisconnectReason, ChannelState, EncryptedChannel}; +use crate::heartbeat::Heartbeat; +use crate::job_manager::{JobManager, JobResult}; +use crate::pb::cmsg_client_pics::CMsgClientPICSProductInfoResponse; +use crate::pb::cmsg_clientserver_login::CMsgClientLogonResponse; +use crate::proto_envelope::decode_proto_envelope; +use crate::transport::Transport; +use crate::wire_format::read_u32_le; +use std::collections::HashMap; +use std::panic::{self, AssertUnwindSafe}; +use std::sync::{Arc, Mutex}; +use std::time::Duration; + +type StateCallback = Box; +type ClientMessageCallback = Box; + +pub struct CMClientRuntime { + core: Arc, + channel: EncryptedChannel, + jobs: JobManager, + heartbeat: Mutex, + pics_product_info: Mutex>, + on_state: Mutex>, + on_client_message: Mutex>, +} + +impl CMClientRuntime { + pub fn new(core: Arc, transport: Box) -> Arc { + let runtime = Arc::new(Self { + core, + channel: EncryptedChannel::new(transport), + jobs: JobManager::default(), + heartbeat: Mutex::new(Heartbeat::default()), + pics_product_info: Mutex::new(HashMap::new()), + on_state: Mutex::new(None), + on_client_message: Mutex::new(None), + }); + + let weak = Arc::downgrade(&runtime); + runtime.channel.set_on_connected(move || { + if let Some(runtime) = weak.upgrade() { + runtime.on_channel_connected(); + } + }); + + let weak = Arc::downgrade(&runtime); + runtime.channel.set_on_disconnected(move |reason, detail| { + if let Some(runtime) = weak.upgrade() { + runtime.on_channel_disconnected(reason, detail); + } + }); + + let weak = Arc::downgrade(&runtime); + runtime.channel.set_on_message(move |bytes| { + if let Some(runtime) = weak.upgrade() { + runtime.handle_channel_message(bytes); + } + }); + + runtime + } + + pub fn core(&self) -> &Arc { + &self.core + } + + pub fn connect(&self, url: &str) -> bool { + self.core.set_state(ClientState::Connecting); + self.notify_state(ClientState::Connecting); + if self.channel.connect(url) { + true + } else { + self.core.set_state(ClientState::Disconnected); + self.notify_state(ClientState::Disconnected); + false + } + } + + pub fn disconnect(&self) { + self.heartbeat.lock().expect("heartbeat poisoned").stop(); + self.channel.disconnect(); + self.jobs.fail_all("CMClient disconnected"); + self.core.reset_session_identity(); + self.core.set_state(ClientState::Disconnected); + self.notify_state(ClientState::Disconnected); + } + + pub fn set_ca_bundle_path(&self, path: &str) { + self.channel.set_ca_bundle_path(path); + } + + pub fn set_on_state(&self, callback: F) + where + F: Fn(ClientState) + Send + Sync + 'static, + { + *self.on_state.lock().expect("runtime callback poisoned") = Some(Box::new(callback)); + } + + pub fn set_on_client_message(&self, callback: F) + where + F: Fn(EMsg, &CMsgProtoBufHeader, &[u8]) + Send + Sync + 'static, + { + *self + .on_client_message + .lock() + .expect("runtime callback poisoned") = Some(Box::new(callback)); + } + + pub fn next_job_id(&self) -> u64 { + self.jobs.next_job_id() + } + + pub fn track_job(&self, job_id: u64, callback: F, timeout: Option) + where + F: FnOnce(JobResult) + Send + 'static, + { + self.jobs.track(job_id, callback, timeout); + } + + pub fn flush_outbound(&self) -> usize { + if self.channel.state() != ChannelState::Encrypted { + return 0; + } + let wires = self.core.take_outbound_wires(); + let mut sent = 0usize; + for (idx, wire) in wires.iter().enumerate() { + if self.channel.send(wire) { + sent += 1; + continue; + } + self.core + .restore_outbound_wires_front(wires[idx..].to_vec()); + break; + } + sent + } + + pub fn handle_channel_message(self: &Arc, bytes: &[u8]) -> InboundAction { + let Some(envelope) = decode_proto_envelope(bytes) else { + return self.handle_non_proto_message(bytes); + }; + self.process_envelope(envelope.emsg, &envelope.header, &envelope.body) + } + + fn process_envelope( + self: &Arc, + emsg: EMsg, + header: &CMsgProtoBufHeader, + body: &[u8], + ) -> InboundAction { + let action = self.core.route_inbound(emsg, header, body); + match &action { + InboundAction::Multi => { + if let Some(records) = decode_multi_records(body) { + for record in records { + self.handle_channel_message(&record); + } + } + } + InboundAction::DeliverJob(result) => { + self.jobs.deliver( + header.jobid_target, + result.eresult, + result.error_message.clone(), + &result.body, + ); + } + InboundAction::LogonOk => { + self.start_heartbeat_from_logon(body); + self.core + .enqueue_proto_message(self.core.build_set_persona_state(1)); + self.core + .enqueue_proto_message(self.core.build_request_user_persona()); + let job_id = self.jobs.next_job_id(); + self.core + .enqueue_service_call(self.core.build_request_friend_persona_states(job_id)); + self.flush_outbound(); + cm_bridge::global_bridge() + .observers() + .dispatch_logon_state(true); + cm_bridge::global_bridge() + .observers() + .dispatch_server_realtime(self.core.server_realtime()); + self.notify_state(ClientState::LoggedOn); + self.notify_client_message(emsg, header, body); + } + InboundAction::LoggedOff => { + self.heartbeat.lock().expect("heartbeat poisoned").stop(); + cm_bridge::global_bridge() + .observers() + .dispatch_logon_state(false); + self.notify_state(self.core.state()); + self.notify_client_message(emsg, header, body); + } + InboundAction::Ignored | InboundAction::ParseFailed(_) => {} + InboundAction::PicsProductInfo => self.handle_pics_product_info(header, body), + InboundAction::LobbyPush => { + cm_bridge::global_bridge().dispatch_lobby_push(emsg, body); + } + InboundAction::LicenseList(_) => { + let licenses = self + .core + .license_list() + .iter() + .map(cm_bridge::WnCmLicenseEntry::from) + .collect::>(); + cm_bridge::global_bridge() + .observers() + .dispatch_license_list(&licenses); + self.notify_client_message(emsg, header, body); + } + InboundAction::FriendsList(_) => { + let friends = self.core.friends_list(); + cm_bridge::global_bridge() + .observers() + .dispatch_friends_list(&friends); + self.notify_client_message(emsg, header, body); + } + InboundAction::PersonaState(_) => { + let bridge = cm_bridge::global_bridge(); + if let Some(self_persona) = self.core.self_persona() { + bridge.dispatch_persona_friend(&self_persona); + } + for snapshot in self.core.friend_personas() { + bridge.dispatch_persona_snapshot(&snapshot); + } + self.notify_client_message(emsg, header, body); + } + InboundAction::AccountInfo(snapshot) => { + cm_bridge::global_bridge().dispatch_account_info_snapshot(snapshot); + self.notify_client_message(emsg, header, body); + } + InboundAction::ClientMessage | InboundAction::PlayingSessionState(_) => { + self.notify_client_message(emsg, header, body); + } + } + action + } + + fn on_channel_connected(self: &Arc) { + self.core.set_state(ClientState::Connected); + self.notify_state(ClientState::Connected); + // Always send ClientHello first; pre-existing queued wires (e.g. a + // logon enqueued before the channel finished negotiating) must come + // after the Hello or Steam rejects the conversation as malformed. + // If a Hello was already prequeued, do not duplicate it. + let queued = self.core.take_outbound_wires(); + let head_is_hello = queued + .first() + .and_then(|wire| decode_proto_envelope(wire)) + .is_some_and(|env| env.emsg == EMsg::CLIENT_HELLO); + let combined = if head_is_hello { + queued + } else { + let hello = self.core.build_client_hello().wire; + let mut combined = Vec::with_capacity(queued.len() + 1); + combined.push(hello); + combined.extend(queued); + combined + }; + self.core.restore_outbound_wires_front(combined); + self.flush_outbound(); + } + + fn on_channel_disconnected(self: &Arc, _reason: ChannelDisconnectReason, detail: &str) { + self.heartbeat.lock().expect("heartbeat poisoned").stop(); + self.jobs + .fail_all(&format!("channel disconnected: {detail}")); + self.pics_product_info + .lock() + .expect("pics product info poisoned") + .clear(); + self.core.reset_session_identity(); + self.core.set_state(ClientState::Disconnected); + self.notify_state(ClientState::Disconnected); + } + + fn handle_pics_product_info(&self, header: &CMsgProtoBufHeader, body: &[u8]) { + if header.jobid_target == INVALID_JOB_ID { + return; + } + let Some(response) = CMsgClientPICSProductInfoResponse::deserialize(body) else { + self.jobs.deliver( + header.jobid_target, + -1, + "PICS product-info parse failed".to_string(), + &[], + ); + return; + }; + let mut pending = self + .pics_product_info + .lock() + .expect("pics product info poisoned"); + if response.response_pending { + let acc = pending.entry(header.jobid_target).or_default(); + merge_pics_product_info(acc, response); + return; + } + let mut merged = pending.remove(&header.jobid_target).unwrap_or_default(); + merge_pics_product_info(&mut merged, response); + let body = merged.serialize(); + self.jobs + .deliver(header.jobid_target, 1, String::new(), &body); + } + + fn start_heartbeat_from_logon(self: &Arc, body: &[u8]) { + let Some(resp) = CMsgClientLogonResponse::deserialize(body) else { + return; + }; + if resp.heartbeat_seconds <= 0 { + return; + } + let weak = Arc::downgrade(self); + let interval = Duration::from_secs(resp.heartbeat_seconds as u64); + self.heartbeat + .lock() + .expect("heartbeat poisoned") + .start(interval, move || { + if let Some(runtime) = weak.upgrade() { + runtime + .core + .enqueue_wire(runtime.core.build_heartbeat().wire); + runtime.flush_outbound(); + } + }); + } + + fn handle_non_proto_message(&self, bytes: &[u8]) -> InboundAction { + if bytes.len() >= 4 { + let raw = read_u32_le(bytes); + if !has_proto_flag(raw) { + let legacy = strip_proto_flag(raw); + if legacy == EMsg::CHANNEL_ENCRYPT_REQUEST + || legacy == EMsg::CHANNEL_ENCRYPT_RESPONSE + || legacy == EMsg::CHANNEL_ENCRYPT_RESULT + { + return InboundAction::Ignored; + } + } + } + InboundAction::ParseFailed("ProtoEnvelope") + } + + fn notify_state(&self, state: ClientState) { + if let Some(callback) = self + .on_state + .lock() + .expect("runtime callback poisoned") + .as_ref() + { + let _ = panic::catch_unwind(AssertUnwindSafe(|| callback(state))); + } + } + + fn notify_client_message(&self, emsg: EMsg, header: &CMsgProtoBufHeader, body: &[u8]) { + if let Some(callback) = self + .on_client_message + .lock() + .expect("runtime callback poisoned") + .as_ref() + { + let _ = panic::catch_unwind(AssertUnwindSafe(|| callback(emsg, header, body))); + } + } +} + +fn merge_pics_product_info( + acc: &mut CMsgClientPICSProductInfoResponse, + mut next: CMsgClientPICSProductInfoResponse, +) { + acc.apps.append(&mut next.apps); + acc.packages.append(&mut next.packages); + acc.unknown_appids.append(&mut next.unknown_appids); + acc.unknown_packageids.append(&mut next.unknown_packageids); + if next.http_min_size > 0 { + acc.http_min_size = next.http_min_size; + } + if !next.http_host.is_empty() { + acc.http_host = next.http_host; + } + acc.meta_data_only = next.meta_data_only; + acc.response_pending = false; +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::proto_envelope::encode_proto_envelope; + use crate::proto_wire::Writer as ProtoWriter; + use crate::transport::{ConnectedCallback, DisconnectedCallback, MessageCallback}; + use crate::transport::{TransportDisconnectReason, TransportState}; + use crate::wire_format::Writer as WireWriter; + use std::sync::mpsc; + + #[test] + fn connected_channel_sends_client_hello_and_reports_state() { + let shared = Arc::new(MockTransportState::default()); + let runtime = runtime_with_shared_transport(Arc::clone(&shared)); + let states = Arc::new(Mutex::new(Vec::new())); + let states_cb = Arc::clone(&states); + runtime.set_on_state(move |state| states_cb.lock().unwrap().push(state)); + + assert!(runtime.connect("wss://cm.example.com:443/cmsocket/")); + shared.fire_connected(); + + assert_eq!( + states.lock().unwrap().as_slice(), + &[ClientState::Connecting, ClientState::Connected] + ); + let sent = shared.take_sent(); + assert_eq!( + decode_proto_envelope(&sent[0]).unwrap().emsg, + EMsg::CLIENT_HELLO + ); + } + + #[test] + fn connected_channel_flushes_prequeued_hello_without_duplicate() { + let shared = Arc::new(MockTransportState::default()); + let runtime = runtime_with_shared_transport(Arc::clone(&shared)); + runtime + .core + .enqueue_wire(runtime.core.build_client_hello().wire); + + assert!(runtime.connect("wss://cm.example.com:443/cmsocket/")); + shared.fire_connected(); + + let sent = shared.take_sent(); + assert_eq!(sent.len(), 1); + assert_eq!( + decode_proto_envelope(&sent[0]).unwrap().emsg, + EMsg::CLIENT_HELLO + ); + } + + #[test] + fn flush_outbound_preserves_core_queue_order() { + let shared = Arc::new(MockTransportState::default()); + let runtime = runtime_with_shared_transport(Arc::clone(&shared)); + assert!(runtime.connect("wss://cm.example.com:443/cmsocket/")); + shared.fire_connected(); + shared.take_sent(); + + runtime.core.enqueue_wire(vec![1, 2, 3]); + runtime.core.enqueue_wire(vec![4, 5, 6]); + assert_eq!(runtime.flush_outbound(), 2); + assert_eq!(shared.take_sent(), vec![vec![1, 2, 3], vec![4, 5, 6]]); + } + + #[test] + fn inbound_job_response_is_delivered() { + let shared = Arc::new(MockTransportState::default()); + let runtime = runtime_with_shared_transport(shared); + let job_id = runtime.next_job_id(); + let (tx, rx) = mpsc::channel(); + runtime.track_job(job_id, move |result| tx.send(result).unwrap(), None); + + let header = CMsgProtoBufHeader { + jobid_target: job_id, + eresult: 1, + ..Default::default() + }; + let wire = encode_proto_envelope(EMsg::SERVICE_METHOD_RESPONSE, &header, b"reply"); + assert_eq!( + runtime.handle_channel_message(&wire), + InboundAction::DeliverJob(JobResult { + eresult: 1, + error_message: String::new(), + body: b"reply".to_vec(), + synthetic_failure: false, + }) + ); + assert_eq!(rx.recv().unwrap().body, b"reply"); + } + + #[test] + fn multi_records_are_redispatched() { + let shared = Arc::new(MockTransportState::default()); + let runtime = runtime_with_shared_transport(shared); + let (tx, rx) = mpsc::channel(); + runtime.set_on_client_message(move |emsg, _, body| { + tx.send((emsg, body.to_vec())).unwrap(); + }); + + let inner = encode_proto_envelope( + EMsg::CLIENT_ACCOUNT_INFO, + &CMsgProtoBufHeader::default(), + &account_info_body("Ada"), + ); + let mut records = Vec::new(); + WireWriter::new(&mut records).u32(inner.len() as u32); + records.extend_from_slice(&inner); + let mut multi_body = Vec::new(); + ProtoWriter::new(&mut multi_body).bytes_field(2, &records); + let multi = encode_proto_envelope(EMsg::MULTI, &CMsgProtoBufHeader::default(), &multi_body); + + assert_eq!(runtime.handle_channel_message(&multi), InboundAction::Multi); + assert_eq!( + rx.recv().unwrap(), + (EMsg::CLIENT_ACCOUNT_INFO, account_info_body("Ada")) + ); + } + + #[test] + fn legacy_channel_encrypt_frames_are_ignored() { + let shared = Arc::new(MockTransportState::default()); + let runtime = runtime_with_shared_transport(shared); + let mut legacy = Vec::new(); + WireWriter::new(&mut legacy).u32(EMsg::CHANNEL_ENCRYPT_REQUEST.0); + assert_eq!( + runtime.handle_channel_message(&legacy), + InboundAction::Ignored + ); + } + + fn runtime_with_shared_transport(shared: Arc) -> Arc { + CMClientRuntime::new( + Arc::new(CMClientCore::default()), + Box::new(MockTransport { shared }), + ) + } + + fn account_info_body(name: &str) -> Vec { + let mut body = Vec::new(); + ProtoWriter::new(&mut body).string_field(1, name); + body + } + + struct MockTransportState { + state: Mutex, + connected: Mutex>, + disconnected: Mutex>, + message: Mutex>, + sent: Mutex>>, + } + + impl Default for MockTransportState { + fn default() -> Self { + Self { + state: Mutex::new(TransportState::Disconnected), + connected: Mutex::new(None), + disconnected: Mutex::new(None), + message: Mutex::new(None), + sent: Mutex::new(Vec::new()), + } + } + } + + impl MockTransportState { + fn fire_connected(&self) { + *self.state.lock().unwrap() = TransportState::Connected; + if let Some(callback) = self.connected.lock().unwrap().as_ref() { + callback(); + } + } + + #[allow(dead_code)] + fn fire_disconnected(&self, reason: TransportDisconnectReason, detail: &str) { + *self.state.lock().unwrap() = TransportState::Disconnected; + if let Some(callback) = self.disconnected.lock().unwrap().as_ref() { + callback(reason, detail); + } + } + + #[allow(dead_code)] + fn fire_message(&self, bytes: &[u8]) { + if let Some(callback) = self.message.lock().unwrap().as_ref() { + callback(bytes); + } + } + + fn take_sent(&self) -> Vec> { + std::mem::take(&mut *self.sent.lock().unwrap()) + } + } + + struct MockTransport { + shared: Arc, + } + + impl Transport for MockTransport { + fn connect(&mut self, _url: &str) -> bool { + *self.shared.state.lock().unwrap() = TransportState::Connecting; + true + } + + fn send(&mut self, data: &[u8]) -> bool { + if self.state() != TransportState::Connected { + return false; + } + self.shared.sent.lock().unwrap().push(data.to_vec()); + true + } + + fn disconnect(&mut self) { + *self.shared.state.lock().unwrap() = TransportState::Disconnected; + } + + fn state(&self) -> TransportState { + *self.shared.state.lock().unwrap() + } + + fn set_on_message(&mut self, cb: MessageCallback) { + *self.shared.message.lock().unwrap() = Some(cb); + } + + fn set_on_connected(&mut self, cb: ConnectedCallback) { + *self.shared.connected.lock().unwrap() = Some(cb); + } + + fn set_on_disconnected(&mut self, cb: DisconnectedCallback) { + *self.shared.disconnected.lock().unwrap() = Some(cb); + } + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/cm_server.rs b/app/src/main/cpp/wn-steam-client/rust/src/cm_server.rs new file mode 100644 index 000000000..c1a1e098c --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/cm_server.rs @@ -0,0 +1,81 @@ +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +#[repr(u8)] +pub enum CmTransport { + #[default] + Unknown = 0, + WebSocket = 1, + Tcp = 2, +} + +#[derive(Clone, Debug, Default, PartialEq)] +pub struct CmServer { + pub endpoint: String, + pub host: String, + pub port: u16, + pub transport: CmTransport, + pub realm: String, + pub datacenter: String, + pub load: i32, + pub weighted_load: f32, +} + +impl CmServer { + pub fn websocket_url(&self) -> String { + if self.transport != CmTransport::WebSocket || self.host.is_empty() || self.port == 0 { + return String::new(); + } + format!("wss://{}:{}/cmsocket/", self.host, self.port) + } +} + +pub fn parse_endpoint(endpoint: &str) -> Option<(String, u16)> { + let colon = endpoint.rfind(':')?; + if colon == 0 || colon + 1 == endpoint.len() { + return None; + } + let mut host = &endpoint[..colon]; + if host.len() >= 2 && host.starts_with('[') && host.ends_with(']') { + host = &host[1..host.len() - 1]; + } + let port_text = &endpoint[colon + 1..]; + if !port_text.bytes().all(|b| b.is_ascii_digit()) { + return None; + } + let port = port_text.parse::().ok()?; + if port == 0 || port > u16::MAX as u32 { + return None; + } + Some((host.to_string(), port as u16)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parses_host_port_and_ipv6_brackets() { + assert_eq!( + parse_endpoint("cm.example.com:443"), + Some(("cm.example.com".to_string(), 443)) + ); + assert_eq!( + parse_endpoint("[2001:db8::1]:27017"), + Some(("2001:db8::1".to_string(), 27017)) + ); + assert_eq!(parse_endpoint("missing-port"), None); + assert_eq!(parse_endpoint("host:0"), None); + assert_eq!(parse_endpoint("host:+443"), None); + assert_eq!(parse_endpoint("host:70000"), None); + } + + #[test] + fn websocket_url_requires_websocket_transport() { + let server = CmServer { + host: "cm.example.com".to_string(), + port: 443, + transport: CmTransport::WebSocket, + ..Default::default() + }; + assert_eq!(server.websocket_url(), "wss://cm.example.com:443/cmsocket/"); + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/cm_server_list.rs b/app/src/main/cpp/wn-steam-client/rust/src/cm_server_list.rs new file mode 100644 index 000000000..b4f0a495c --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/cm_server_list.rs @@ -0,0 +1,205 @@ +use crate::cm_server::{CmServer, CmTransport}; +use std::sync::Mutex; +use std::time::{Duration, Instant}; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum ServerQuality { + Good, + Bad, +} + +pub const DEFAULT_BAD_MEMORY: Duration = Duration::from_secs(5 * 60); + +pub struct CmServerList { + inner: Mutex, + bad_memory: Duration, +} + +#[derive(Default)] +struct Inner { + entries: Vec, + next_index: usize, +} + +struct Entry { + server: CmServer, + quality: ServerQuality, + marked_bad_at: Option, +} + +impl Default for CmServerList { + fn default() -> Self { + Self::new(DEFAULT_BAD_MEMORY) + } +} + +impl CmServerList { + pub fn new(bad_memory: Duration) -> Self { + Self { + inner: Mutex::new(Inner::default()), + bad_memory, + } + } + + pub fn replace_all(&self, servers: &[CmServer]) { + let mut inner = self.inner.lock().unwrap(); + inner.entries = servers + .iter() + .cloned() + .map(|server| Entry { + server, + quality: ServerQuality::Good, + marked_bad_at: None, + }) + .collect(); + inner.next_index = 0; + } + + pub fn add(&self, server: CmServer) { + self.inner.lock().unwrap().entries.push(Entry { + server, + quality: ServerQuality::Good, + marked_bad_at: None, + }); + } + + pub fn size(&self) -> usize { + self.inner.lock().unwrap().entries.len() + } + + pub fn next_good(&self) -> Option { + let mut inner = self.inner.lock().unwrap(); + if inner.entries.is_empty() { + return None; + } + promote_expired(&mut inner.entries, self.bad_memory); + let n = inner.entries.len(); + for i in 0..n { + let idx = (inner.next_index + i) % n; + if inner.entries[idx].quality == ServerQuality::Good { + inner.next_index = (idx + 1) % n; + return Some(inner.entries[idx].server.clone()); + } + } + None + } + + pub fn mark_bad(&self, endpoint: &str) { + let mut inner = self.inner.lock().unwrap(); + if let Some(entry) = inner + .entries + .iter_mut() + .find(|e| e.server.endpoint == endpoint) + { + entry.quality = ServerQuality::Bad; + entry.marked_bad_at = Some(Instant::now()); + } + } + + pub fn mark_good(&self, endpoint: &str) { + let mut inner = self.inner.lock().unwrap(); + if let Some(entry) = inner + .entries + .iter_mut() + .find(|e| e.server.endpoint == endpoint) + { + entry.quality = ServerQuality::Good; + entry.marked_bad_at = None; + } + } + + pub fn reset_quality(&self) { + let mut inner = self.inner.lock().unwrap(); + for entry in &mut inner.entries { + entry.quality = ServerQuality::Good; + entry.marked_bad_at = None; + } + } +} + +fn promote_expired(entries: &mut [Entry], bad_memory: Duration) { + let now = Instant::now(); + for entry in entries { + if entry.quality == ServerQuality::Bad + && entry + .marked_bad_at + .is_some_and(|t| now.duration_since(t) >= bad_memory) + { + entry.quality = ServerQuality::Good; + entry.marked_bad_at = None; + } + } +} + +pub fn hardcoded_fallback_servers() -> Vec { + const FALLBACK: &[(&str, &str)] = &[ + ("ext1-sea1.steamserver.net", "sea1"), + ("ext2-sea1.steamserver.net", "sea1"), + ("ext1-iad1.steamserver.net", "iad1"), + ("ext2-iad1.steamserver.net", "iad1"), + ("ext1-fra1.steamserver.net", "fra1"), + ("ext2-fra1.steamserver.net", "fra1"), + ("ext1-lax1.steamserver.net", "lax1"), + ("ext1-sgp1.steamserver.net", "sgp1"), + ]; + FALLBACK + .iter() + .map(|(host, dc)| CmServer { + endpoint: format!("{host}:443"), + host: (*host).to_string(), + port: 443, + transport: CmTransport::WebSocket, + realm: "steamglobal".to_string(), + datacenter: (*dc).to_string(), + load: 0, + weighted_load: 0.0, + }) + .collect() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn next_good_round_robins_and_skips_bad() { + let list = CmServerList::default(); + let a = server("a:443"); + let b = server("b:443"); + list.replace_all(&[a.clone(), b.clone()]); + assert_eq!(list.next_good().unwrap().endpoint, a.endpoint); + list.mark_bad(&b.endpoint); + assert_eq!(list.next_good().unwrap().endpoint, a.endpoint); + list.mark_good(&b.endpoint); + assert_eq!(list.next_good().unwrap().endpoint, b.endpoint); + } + + #[test] + fn bad_servers_promote_after_memory_interval() { + let list = CmServerList::new(Duration::ZERO); + let a = server("a:443"); + list.replace_all(std::slice::from_ref(&a)); + list.mark_bad(&a.endpoint); + assert_eq!(list.next_good().unwrap().endpoint, a.endpoint); + } + + #[test] + fn fallback_servers_are_websocket_urls() { + let servers = hardcoded_fallback_servers(); + assert!(!servers.is_empty()); + assert!(servers + .iter() + .all(|s| s.websocket_url().starts_with("wss://"))); + } + + fn server(endpoint: &str) -> CmServer { + let (host, port) = crate::cm_server::parse_endpoint(endpoint).unwrap(); + CmServer { + endpoint: endpoint.to_string(), + host, + port, + transport: CmTransport::WebSocket, + ..Default::default() + } + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/cmsg_protobuf_header.rs b/app/src/main/cpp/wn-steam-client/rust/src/cmsg_protobuf_header.rs new file mode 100644 index 000000000..0c40fcbcc --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/cmsg_protobuf_header.rs @@ -0,0 +1,151 @@ +use crate::proto_wire::{Reader, WireType, Writer}; + +pub const INVALID_JOB_ID: u64 = u64::MAX; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct CMsgProtoBufHeader { + pub steamid: u64, + pub client_sessionid: i32, + pub routing_appid: u32, + pub jobid_source: u64, + pub jobid_target: u64, + pub target_job_name: String, + pub eresult: i32, + pub error_message: String, + pub realm: u32, + pub messageid: u64, + pub token_id: u64, +} + +impl Default for CMsgProtoBufHeader { + fn default() -> Self { + Self { + steamid: 0, + client_sessionid: 0, + routing_appid: 0, + jobid_source: INVALID_JOB_ID, + jobid_target: INVALID_JOB_ID, + target_job_name: String::new(), + eresult: -1, + error_message: String::new(), + realm: 0, + messageid: 0, + token_id: 0, + } + } +} + +impl CMsgProtoBufHeader { + pub fn serialize(&self, out: &mut Vec) { + let mut writer = Writer::new(out); + + if self.steamid != 0 { + writer.tag(1, WireType::Fixed64); + writer.fixed64_field_force_body(self.steamid); + } + writer.int32_field(2, self.client_sessionid); + writer.uint32_field(3, self.routing_appid); + if self.jobid_source != INVALID_JOB_ID { + writer.tag(10, WireType::Fixed64); + writer.fixed64_field_force_body(self.jobid_source); + } + if self.jobid_target != INVALID_JOB_ID { + writer.tag(11, WireType::Fixed64); + writer.fixed64_field_force_body(self.jobid_target); + } + writer.string_field(12, &self.target_job_name); + if self.eresult >= 0 { + writer.tag(14, WireType::Varint); + writer.varint(self.eresult as i64 as u64); + } + writer.string_field(15, &self.error_message); + writer.uint32_field(29, self.realm); + writer.uint64_field(21, self.messageid); + writer.uint64_field(34, self.token_id); + } + + pub fn deserialize(bytes: &[u8]) -> Option { + let mut reader = Reader::new(bytes); + let mut header = Self::default(); + + while !reader.eof() { + let Some(tag) = reader.next_tag() else { + return reader.ok().then_some(header); + }; + match tag.field_number { + 1 => { + if tag.wire_type != WireType::Fixed64 { + return None; + } + header.steamid = reader.fixed64()?; + } + 2 => header.client_sessionid = reader.i32()?, + 3 => header.routing_appid = reader.u32()?, + 10 => { + if tag.wire_type != WireType::Fixed64 { + return None; + } + header.jobid_source = reader.fixed64()?; + } + 11 => { + if tag.wire_type != WireType::Fixed64 { + return None; + } + header.jobid_target = reader.fixed64()?; + } + 12 => header.target_job_name = reader.string()?, + 14 => header.eresult = reader.i32()?, + 15 => header.error_message = reader.string()?, + 21 => header.messageid = reader.u64()?, + 29 => header.realm = reader.u32()?, + 34 => header.token_id = reader.u64()?, + _ => { + if !reader.skip(tag.wire_type) { + return None; + } + } + } + } + + Some(header) + } +} + +trait WriterFixedBodies { + fn fixed64_field_force_body(&mut self, v: u64); +} + +impl WriterFixedBodies for Writer<'_> { + fn fixed64_field_force_body(&mut self, v: u64) { + self.raw_bytes(&v.to_le_bytes()); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn serializes_only_set_job_ids() { + let h = CMsgProtoBufHeader { + steamid: 0x1122_3344_5566_7788, + client_sessionid: 17, + routing_appid: 480, + target_job_name: "Authentication.BeginAuthSessionViaCredentials#1".to_string(), + ..Default::default() + }; + let mut bytes = Vec::new(); + h.serialize(&mut bytes); + let parsed = CMsgProtoBufHeader::deserialize(&bytes).unwrap(); + assert_eq!(parsed, h); + assert!(!bytes + .windows(2) + .any(|w| w == [0x51, 0xff] || w == [0x59, 0xff])); + } + + #[test] + fn rejects_wrong_fixed_wire_type() { + let bytes = [0x08, 0x01]; + assert_eq!(CMsgProtoBufHeader::deserialize(&bytes), None); + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/content_manifest.rs b/app/src/main/cpp/wn-steam-client/rust/src/content_manifest.rs new file mode 100644 index 000000000..05daacb5f --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/content_manifest.rs @@ -0,0 +1,353 @@ +use crate::base64; +use crate::crypto::{ + aes256_cbc_decrypt, aes256_ecb_decrypt_block, AesBlock, SessionKey, AES_BLOCK_BYTES, + SESSION_KEY_LENGTH, +}; +use crate::proto_wire::{Reader, WireType}; + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct ChunkData { + pub sha: Vec, + pub crc: u32, + pub offset: u64, + pub cb_original: u32, + pub cb_compressed: u32, +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct FileMapping { + pub filename: String, + pub size: u64, + pub flags: u32, + pub sha_filename: Vec, + pub sha_content: Vec, + pub chunks: Vec, + pub linktarget: String, +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct Metadata { + pub depot_id: u32, + pub gid_manifest: u64, + pub creation_time: u32, + pub filenames_encrypted: bool, + pub cb_disk_original: u64, + pub cb_disk_compressed: u64, + pub unique_chunks: u32, + pub crc_encrypted: u32, + pub crc_clear: u32, +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct ContentManifest { + pub metadata: Metadata, + pub files: Vec, + pub signature: Vec, +} + +pub const PAYLOAD_MAGIC: u32 = 0x71f6_17d0; +pub const METADATA_MAGIC: u32 = 0x1f48_12be; +pub const SIGNATURE_MAGIC: u32 = 0x1b81_b817; +pub const END_OF_MANIFEST_MAGIC: u32 = 0x32c4_15ab; + +impl ContentManifest { + pub fn parse(raw: &[u8]) -> Option { + let mut manifest = Self::default(); + let mut have_payload = false; + let mut have_metadata = false; + let mut pos = 0usize; + + loop { + let magic = read_u32_le(raw, &mut pos)?; + if magic == END_OF_MANIFEST_MAGIC { + break; + } + let len = read_u32_le(raw, &mut pos)? as usize; + if pos + len > raw.len() { + return None; + } + let section = &raw[pos..pos + len]; + pos += len; + + match magic { + PAYLOAD_MAGIC => { + parse_payload(section, &mut manifest.files)?; + have_payload = true; + } + METADATA_MAGIC => { + manifest.metadata = parse_metadata(section)?; + have_metadata = true; + } + SIGNATURE_MAGIC => { + manifest.signature = parse_signature(section)?; + } + _ => return None, + } + } + + (have_payload && have_metadata).then_some(manifest) + } + + pub fn decrypt_filenames(&mut self, depot_key: &[u8]) -> bool { + if self.metadata.filenames_encrypted { + if depot_key.len() != SESSION_KEY_LENGTH { + return false; + } + let mut key = [0u8; SESSION_KEY_LENGTH]; + key.copy_from_slice(depot_key); + + for file in &mut self.files { + let Some(clear) = decrypt_name(&key, &file.filename) else { + return false; + }; + file.filename = clear; + if !file.linktarget.is_empty() { + let Some(clear) = decrypt_name(&key, &file.linktarget) else { + return false; + }; + file.linktarget = clear; + } + } + self.metadata.filenames_encrypted = false; + } + + for file in &mut self.files { + file.filename = file.filename.replace('\\', "/"); + file.linktarget = file.linktarget.replace('\\', "/"); + } + + self.files.sort_by(|a, b| { + a.filename + .bytes() + .map(|c| c.to_ascii_lowercase()) + .cmp(b.filename.bytes().map(|c| c.to_ascii_lowercase())) + }); + true + } +} + +fn decrypt_name(key: &SessionKey, enc: &str) -> Option { + let blob = base64::decode(enc)?; + if blob.len() < AES_BLOCK_BYTES * 2 { + return None; + } + let mut wrapped = [0u8; AES_BLOCK_BYTES]; + wrapped.copy_from_slice(&blob[..AES_BLOCK_BYTES]); + let iv: AesBlock = aes256_ecb_decrypt_block(key, &wrapped)?; + let plain = aes256_cbc_decrypt(key, &iv, &blob[AES_BLOCK_BYTES..])?; + let mut out = String::from_utf8_lossy(&plain).into_owned(); + if out.ends_with('\0') { + out.pop(); + } + Some(out) +} + +fn read_u32_le(buf: &[u8], pos: &mut usize) -> Option { + if *pos + 4 > buf.len() { + return None; + } + let v = u32::from_le_bytes(buf[*pos..*pos + 4].try_into().ok()?); + *pos += 4; + Some(v) +} + +fn parse_chunk(body: &[u8]) -> Option { + let mut reader = Reader::new(body); + let mut chunk = ChunkData::default(); + while !reader.eof() { + let Some(tag) = reader.next_tag() else { + return reader.ok().then_some(chunk); + }; + match tag.field_number { + 1 => chunk.sha = reader.bytes()?.to_vec(), + 2 => { + if tag.wire_type != WireType::Fixed32 { + return None; + } + chunk.crc = reader.fixed32()?; + } + 3 => chunk.offset = reader.u64()?, + 4 => chunk.cb_original = reader.u32()?, + 5 => chunk.cb_compressed = reader.u32()?, + _ => { + if !reader.skip(tag.wire_type) { + return None; + } + } + } + } + Some(chunk) +} + +fn parse_file_mapping(body: &[u8]) -> Option { + let mut reader = Reader::new(body); + let mut file = FileMapping::default(); + while !reader.eof() { + let Some(tag) = reader.next_tag() else { + return reader.ok().then_some(file); + }; + match tag.field_number { + 1 => file.filename = reader.string()?, + 2 => file.size = reader.u64()?, + 3 => file.flags = reader.u32()?, + 4 => file.sha_filename = reader.bytes()?.to_vec(), + 5 => file.sha_content = reader.bytes()?.to_vec(), + 6 => file.chunks.push(parse_chunk(reader.bytes()?)?), + 7 => file.linktarget = reader.string()?, + _ => { + if !reader.skip(tag.wire_type) { + return None; + } + } + } + } + Some(file) +} + +fn parse_payload(body: &[u8], out: &mut Vec) -> Option<()> { + let mut reader = Reader::new(body); + while !reader.eof() { + let Some(tag) = reader.next_tag() else { + return reader.ok().then_some(()); + }; + if tag.field_number == 1 { + out.push(parse_file_mapping(reader.bytes()?)?); + } else if !reader.skip(tag.wire_type) { + return None; + } + } + Some(()) +} + +fn parse_metadata(body: &[u8]) -> Option { + let mut reader = Reader::new(body); + let mut metadata = Metadata::default(); + while !reader.eof() { + let Some(tag) = reader.next_tag() else { + return reader.ok().then_some(metadata); + }; + match tag.field_number { + 1 => metadata.depot_id = reader.u32()?, + 2 => metadata.gid_manifest = reader.u64()?, + 3 => metadata.creation_time = reader.u32()?, + 4 => metadata.filenames_encrypted = reader.boolean()?, + 5 => metadata.cb_disk_original = reader.u64()?, + 6 => metadata.cb_disk_compressed = reader.u64()?, + 7 => metadata.unique_chunks = reader.u32()?, + 8 => metadata.crc_encrypted = reader.u32()?, + 9 => metadata.crc_clear = reader.u32()?, + _ => { + if !reader.skip(tag.wire_type) { + return None; + } + } + } + } + Some(metadata) +} + +fn parse_signature(body: &[u8]) -> Option> { + let mut reader = Reader::new(body); + let mut signature = Vec::new(); + while !reader.eof() { + let Some(tag) = reader.next_tag() else { + return reader.ok().then_some(signature); + }; + if tag.field_number == 1 { + signature = reader.bytes()?.to_vec(); + } else if !reader.skip(tag.wire_type) { + return None; + } + } + Some(signature) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::crypto::{aes256_cbc_encrypt, aes256_ecb_encrypt_block}; + use crate::proto_wire::Writer; + + #[test] + fn parses_manifest_sections() { + let mut chunk_body = Vec::new(); + { + let mut w = Writer::new(&mut chunk_body); + w.bytes_field(1, &[1; 20]); + w.tag(2, WireType::Fixed32); + w.raw_bytes(&0x1234_5678u32.to_le_bytes()); + w.uint64_field(3, 99); + w.uint32_field(4, 11); + w.uint32_field(5, 22); + } + + let mut file_body = Vec::new(); + { + let mut w = Writer::new(&mut file_body); + w.string_field(1, "bin\\game.exe"); + w.uint64_field(2, 11); + w.uint32_field(3, 1); + w.submessage_field(6, &chunk_body); + } + + let mut payload = Vec::new(); + Writer::new(&mut payload).submessage_field(1, &file_body); + + let mut metadata = Vec::new(); + { + let mut w = Writer::new(&mut metadata); + w.uint32_field(1, 123); + w.uint64_field(2, 456); + w.uint32_field(3, 789); + w.bool_field_force(4, false); + } + + let mut raw = Vec::new(); + push_section(&mut raw, PAYLOAD_MAGIC, &payload); + push_section(&mut raw, METADATA_MAGIC, &metadata); + raw.extend_from_slice(&END_OF_MANIFEST_MAGIC.to_le_bytes()); + + let mut manifest = ContentManifest::parse(&raw).unwrap(); + assert_eq!(manifest.metadata.depot_id, 123); + assert_eq!(manifest.files[0].filename, "bin\\game.exe"); + assert!(manifest.decrypt_filenames(&[])); + assert_eq!(manifest.files[0].filename, "bin/game.exe"); + assert_eq!(manifest.files[0].chunks[0].crc, 0x1234_5678); + } + + #[test] + fn decrypts_encrypted_filenames() { + let key = [9u8; SESSION_KEY_LENGTH]; + let name = encrypt_name(&key, "dir\\file.txt\0"); + let mut manifest = ContentManifest { + metadata: Metadata { + filenames_encrypted: true, + ..Default::default() + }, + files: vec![FileMapping { + filename: name, + ..Default::default() + }], + signature: Vec::new(), + }; + + assert!(manifest.decrypt_filenames(&key)); + assert_eq!(manifest.files[0].filename, "dir/file.txt"); + assert!(!manifest.metadata.filenames_encrypted); + } + + fn push_section(out: &mut Vec, magic: u32, body: &[u8]) { + out.extend_from_slice(&magic.to_le_bytes()); + out.extend_from_slice(&(body.len() as u32).to_le_bytes()); + out.extend_from_slice(body); + } + + fn encrypt_name(key: &SessionKey, name: &str) -> String { + let iv = [5u8; AES_BLOCK_BYTES]; + let wrapped = aes256_ecb_encrypt_block(key, &iv).unwrap(); + let body = aes256_cbc_encrypt(key, &iv, name.as_bytes()).unwrap(); + let mut blob = wrapped.to_vec(); + blob.extend_from_slice(&body); + base64::encode(&blob) + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/crypto.rs b/app/src/main/cpp/wn-steam-client/rust/src/crypto.rs new file mode 100644 index 000000000..103608beb --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/crypto.rs @@ -0,0 +1,150 @@ +use aes::cipher::{ + block_padding::Pkcs7, BlockDecrypt, BlockDecryptMut, BlockEncrypt, BlockEncryptMut, KeyInit, + KeyIvInit, +}; +use aes::Aes256; +use hmac::{Hmac, Mac}; +use rand::{rngs::OsRng, RngCore}; +use rsa::pkcs8::DecodePublicKey; +use rsa::traits::PublicKeyParts; +use rsa::{Oaep, RsaPublicKey}; +use sha1::{Digest as Sha1DigestTrait, Sha1}; +use sha2::Sha256; +use zeroize::Zeroize; + +pub const SESSION_KEY_LENGTH: usize = 32; +pub const AES_BLOCK_BYTES: usize = 16; +pub const SHA1_BYTES: usize = 20; +pub const SHA256_BYTES: usize = 32; +pub const HMAC_KEY_LENGTH: usize = 16; + +pub type SessionKey = [u8; SESSION_KEY_LENGTH]; +pub type Sha1Digest = [u8; SHA1_BYTES]; +pub type Sha256Digest = [u8; SHA256_BYTES]; +pub type AesBlock = [u8; AES_BLOCK_BYTES]; + +#[derive(Clone)] +pub struct SecureSessionKey { + pub bytes: SessionKey, +} + +impl SecureSessionKey { + pub fn new(bytes: SessionKey) -> Self { + Self { bytes } + } +} + +impl Drop for SecureSessionKey { + fn drop(&mut self) { + self.bytes.zeroize(); + } +} + +pub fn secure_random_bytes(out: &mut [u8]) -> bool { + OsRng.try_fill_bytes(out).is_ok() +} + +pub fn generate_session_key() -> Option { + let mut bytes = [0u8; SESSION_KEY_LENGTH]; + secure_random_bytes(&mut bytes).then_some(SecureSessionKey::new(bytes)) +} + +pub fn sha1(data: &[u8]) -> Sha1Digest { + Sha1::digest(data).into() +} + +pub fn sha256(data: &[u8]) -> Sha256Digest { + Sha256::digest(data).into() +} + +pub fn crc32(data: &[u8]) -> u32 { + crc32fast::hash(data) +} + +pub fn hmac_sha1(key: &[u8], data: &[u8]) -> Option { + let mut mac = as Mac>::new_from_slice(key).ok()?; + mac.update(data); + Some(mac.finalize().into_bytes().into()) +} + +pub fn aes256_ecb_encrypt_block(key: &SessionKey, input: &AesBlock) -> Option { + let cipher = Aes256::new_from_slice(key).ok()?; + let mut block = (*input).into(); + cipher.encrypt_block(&mut block); + Some(block.into()) +} + +pub fn aes256_ecb_decrypt_block(key: &SessionKey, input: &AesBlock) -> Option { + let cipher = Aes256::new_from_slice(key).ok()?; + let mut block = (*input).into(); + cipher.decrypt_block(&mut block); + Some(block.into()) +} + +pub fn aes256_cbc_encrypt(key: &SessionKey, iv: &AesBlock, plaintext: &[u8]) -> Option> { + Some( + cbc::Encryptor::::new(key.into(), iv.into()) + .encrypt_padded_vec_mut::(plaintext), + ) +} + +pub fn aes256_cbc_decrypt(key: &SessionKey, iv: &AesBlock, ciphertext: &[u8]) -> Option> { + if ciphertext.is_empty() || !ciphertext.len().is_multiple_of(AES_BLOCK_BYTES) { + return None; + } + cbc::Decryptor::::new(key.into(), iv.into()) + .decrypt_padded_vec_mut::(ciphertext) + .ok() +} + +pub fn rsa_oaep_sha1_encrypt(spki_der: &[u8], plaintext: &[u8]) -> Option> { + let public_key = RsaPublicKey::from_public_key_der(spki_der).ok()?; + let modulus = public_key.size(); + if modulus < 2 + 2 * SHA1_BYTES || plaintext.len() > modulus - 2 - 2 * SHA1_BYTES { + return None; + } + public_key + .encrypt(&mut OsRng, Oaep::new::(), plaintext) + .ok() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn hashes_match_known_values() { + assert_eq!( + hex(&sha1(b"abc")), + "a9993e364706816aba3e25717850c26c9cd0d89d" + ); + assert_eq!( + hex(&sha256(b"abc")), + "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" + ); + assert_eq!(crc32(b"123456789"), 0xcbf4_3926); + } + + #[test] + fn aes_cbc_roundtrips_with_pkcs7() { + let key = [7u8; SESSION_KEY_LENGTH]; + let iv = [3u8; AES_BLOCK_BYTES]; + let plaintext = b"steam manifest filename"; + let encrypted = aes256_cbc_encrypt(&key, &iv, plaintext).unwrap(); + assert_ne!(encrypted, plaintext); + let decrypted = aes256_cbc_decrypt(&key, &iv, &encrypted).unwrap(); + assert_eq!(decrypted, plaintext); + } + + #[test] + fn hmac_sha1_known_value() { + assert_eq!( + hex(&hmac_sha1(b"key", b"The quick brown fox jumps over the lazy dog").unwrap()), + "de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9" + ); + } + + fn hex(bytes: &[u8]) -> String { + bytes.iter().map(|b| format!("{b:02x}")).collect() + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/depot_chunk.rs b/app/src/main/cpp/wn-steam-client/rust/src/depot_chunk.rs new file mode 100644 index 000000000..f5c55f959 --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/depot_chunk.rs @@ -0,0 +1,253 @@ +use crate::cdn_client; +use crate::crypto::{ + aes256_cbc_decrypt, aes256_ecb_decrypt_block, AesBlock, SessionKey, AES_BLOCK_BYTES, + SESSION_KEY_LENGTH, +}; +use lzma_rs::decompress::raw::{LzmaDecoder, LzmaParams, LzmaProperties}; +use std::io::{Cursor, Read}; + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct DepotChunkResult { + pub data: Vec, + pub error: String, +} + +impl DepotChunkResult { + pub fn ok(&self) -> bool { + self.error.is_empty() + } +} + +pub fn process_depot_chunk( + raw: &[u8], + depot_key: &[u8], + expected_crc: u32, + expected_size: u32, +) -> DepotChunkResult { + if depot_key.len() != SESSION_KEY_LENGTH { + return fail("chunk: bad depot key length"); + } + if raw.is_empty() { + return fail("chunk: empty"); + } + let mut key = [0u8; SESSION_KEY_LENGTH]; + key.copy_from_slice(depot_key); + + let Some(dec) = steam_symmetric_decrypt(&key, raw) else { + return fail("chunk: AES decrypt failed"); + }; + if dec.is_empty() { + return fail("chunk: AES decrypt failed"); + } + + let result = if dec.starts_with(b"VSZa") { + decompress_vzstd(&dec, expected_size) + } else if dec.starts_with(b"VZa") { + decompress_vzip(&dec, expected_size) + } else if dec.starts_with(b"PK\x03\x04") { + match cdn_client::unzip_first_entry(&dec) { + Some(data) => DepotChunkResult { + data, + error: String::new(), + }, + None => fail("chunk: PKZip decompress failed"), + } + } else { + fail("chunk: unrecognised compression header") + }; + if !result.ok() { + return result; + } + if result.data.len() != expected_size as usize { + return fail(format!( + "chunk: size mismatch ({} != {expected_size})", + result.data.len() + )); + } + if steam_adler_hash(&result.data) != expected_crc { + return fail("chunk: Adler32 mismatch"); + } + result +} + +pub fn steam_symmetric_decrypt(key: &SessionKey, enc: &[u8]) -> Option> { + if enc.len() < AES_BLOCK_BYTES * 2 { + return None; + } + let mut wrapped = [0u8; AES_BLOCK_BYTES]; + wrapped.copy_from_slice(&enc[..AES_BLOCK_BYTES]); + let iv: AesBlock = aes256_ecb_decrypt_block(key, &wrapped)?; + aes256_cbc_decrypt(key, &iv, &enc[AES_BLOCK_BYTES..]) +} + +fn decompress_vzstd(dec: &[u8], expected_size: u32) -> DepotChunkResult { + if dec.len() <= 8 { + return fail("vzstd: chunk too small"); + } + let payload = &dec[8..]; + let mut reader = match ruzstd::StreamingDecoder::new(Cursor::new(payload)) { + Ok(reader) => reader, + Err(_) => return fail("vzstd: bad zstd frame"), + }; + let mut data = Vec::with_capacity(expected_size as usize); + if reader.read_to_end(&mut data).is_err() { + return fail("vzstd: decode failed"); + } + DepotChunkResult { + data, + error: String::new(), + } +} + +fn decompress_vzip(dec: &[u8], expected_size: u32) -> DepotChunkResult { + const HEADER: usize = 3 + 4; + const PROPS: usize = 5; + const FOOTER: usize = 10; + if dec.len() < HEADER + PROPS + FOOTER { + return fail("vzip: chunk too small"); + } + if dec[dec.len() - 2] != b'z' || dec[dec.len() - 1] != b'v' { + return fail("vzip: bad footer"); + } + let props = &dec[HEADER..HEADER + PROPS]; + let comp = &dec[HEADER + PROPS..dec.len() - FOOTER]; + let Some(params) = lzma_params_from_steam_props(props, expected_size as u64) else { + return fail("vzip: bad LZMA properties"); + }; + let mut decoder = match LzmaDecoder::new(params, None) { + Ok(decoder) => decoder, + Err(_) => return fail("vzip: raw decoder init failed"), + }; + let mut output = Vec::with_capacity(expected_size as usize); + match decoder.decompress(&mut Cursor::new(comp), &mut output) { + Ok(()) => DepotChunkResult { + data: output, + error: String::new(), + }, + Err(_) => fail("vzip: LZMA decode failed"), + } +} + +fn lzma_params_from_steam_props(props: &[u8], expected_size: u64) -> Option { + if props.len() != 5 { + return None; + } + let mut packed = props[0] as u32; + if packed >= 225 { + return None; + } + let lc = packed % 9; + packed /= 9; + let lp = packed % 5; + let pb = packed / 5; + if lc > 8 || lp > 4 || pb > 4 { + return None; + } + let dict_size = u32::from_le_bytes(props[1..5].try_into().ok()?); + Some(LzmaParams::new( + LzmaProperties { lc, lp, pb }, + dict_size, + Some(expected_size), + )) +} + +pub fn steam_adler_hash(data: &[u8]) -> u32 { + let mut a = 0u32; + let mut b = 0u32; + for byte in data { + a = (a + *byte as u32) % 65_521; + b = (b + a) % 65_521; + } + a | (b << 16) +} + +fn fail(msg: impl Into) -> DepotChunkResult { + DepotChunkResult { + data: Vec::new(), + error: msg.into(), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::crypto::{aes256_cbc_encrypt, aes256_ecb_encrypt_block}; + + #[test] + fn steam_adler_hash_uses_zero_seed() { + assert_eq!(steam_adler_hash(b""), 0); + assert_eq!(steam_adler_hash(b"abc"), 0x024a_0126); + } + + #[test] + fn decrypts_and_processes_pkzip_chunk() { + let key = [4u8; SESSION_KEY_LENGTH]; + let payload = b"chunk bytes"; + let mut zip = Vec::new(); + zip.extend_from_slice(&0x0403_4b50u32.to_le_bytes()); + zip.extend_from_slice(&20u16.to_le_bytes()); + zip.extend_from_slice(&0u16.to_le_bytes()); + zip.extend_from_slice(&0u16.to_le_bytes()); + zip.extend_from_slice(&0u16.to_le_bytes()); + zip.extend_from_slice(&0u16.to_le_bytes()); + zip.extend_from_slice(&0u32.to_le_bytes()); + zip.extend_from_slice(&(payload.len() as u32).to_le_bytes()); + zip.extend_from_slice(&(payload.len() as u32).to_le_bytes()); + zip.extend_from_slice(&1u16.to_le_bytes()); + zip.extend_from_slice(&0u16.to_le_bytes()); + zip.extend_from_slice(b"x"); + zip.extend_from_slice(payload); + + let raw = encrypt_steam_symmetric(&key, &zip); + let result = + process_depot_chunk(&raw, &key, steam_adler_hash(payload), payload.len() as u32); + assert_eq!(result.data, payload); + assert!(result.ok()); + } + + #[test] + fn decrypts_and_processes_vzip_raw_lzma_chunk() { + let key = [8u8; SESSION_KEY_LENGTH]; + let payload = b"legacy lzma chunk bytes"; + let mut lzma_alone = Vec::new(); + lzma_rs::lzma_compress(&mut Cursor::new(payload), &mut lzma_alone).unwrap(); + assert!(lzma_alone.len() > 13); + + let mut vzip = Vec::new(); + vzip.extend_from_slice(b"VZa"); + vzip.extend_from_slice(&0u32.to_le_bytes()); + vzip.extend_from_slice(&lzma_alone[..5]); + vzip.extend_from_slice(&lzma_alone[13..]); + vzip.extend_from_slice(&0u32.to_le_bytes()); + vzip.extend_from_slice(&(payload.len() as u32).to_le_bytes()); + vzip.extend_from_slice(b"zv"); + + let raw = encrypt_steam_symmetric(&key, &vzip); + let result = + process_depot_chunk(&raw, &key, steam_adler_hash(payload), payload.len() as u32); + assert_eq!(result.data, payload); + assert!(result.ok()); + } + + #[test] + fn vzip_rejects_bad_properties_before_raw_decode() { + let mut dec = Vec::new(); + dec.extend_from_slice(b"VZa"); + dec.extend_from_slice(&0u32.to_le_bytes()); + dec.extend_from_slice(&[225, 0, 0, 0, 0]); + dec.extend_from_slice(b"payload"); + dec.extend_from_slice(&0u32.to_le_bytes()); + dec.extend_from_slice(&0u32.to_le_bytes()); + dec.extend_from_slice(b"zv"); + assert_eq!(decompress_vzip(&dec, 1).error, "vzip: bad LZMA properties"); + } + + fn encrypt_steam_symmetric(key: &SessionKey, plaintext: &[u8]) -> Vec { + let iv = [6u8; AES_BLOCK_BYTES]; + let wrapped = aes256_ecb_encrypt_block(key, &iv).unwrap(); + let body = aes256_cbc_encrypt(key, &iv, plaintext).unwrap(); + let mut out = wrapped.to_vec(); + out.extend_from_slice(&body); + out + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/depot_config.rs b/app/src/main/cpp/wn-steam-client/rust/src/depot_config.rs new file mode 100644 index 000000000..1968fb69e --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/depot_config.rs @@ -0,0 +1,294 @@ +use serde_json::{json, Value}; +use std::collections::{BTreeMap, BTreeSet}; +use std::fs::{self, File}; +use std::io::Write; +use std::path::{Path, PathBuf}; +use std::sync::Mutex; + +pub const INVALID_MANIFEST_ID: u64 = 0x7fff_ffff_ffff_ffff; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct DepotConfigStore { + config_dir: PathBuf, + installed: BTreeMap, +} + +impl DepotConfigStore { + pub fn load(config_dir: impl Into) -> Self { + let config_dir = config_dir.into(); + let mut store = Self { + config_dir, + installed: BTreeMap::new(), + }; + let Ok(bytes) = fs::read_to_string(store.config_path()) else { + return store; + }; + let Ok(value) = serde_json::from_str::(&bytes) else { + return store; + }; + let Some(obj) = value + .get("installedManifestIDs") + .and_then(|v| v.as_object()) + else { + return store; + }; + for (key, value) in obj { + if let (Ok(depot_id), Some(manifest_id)) = (key.parse::(), value.as_u64()) { + store.installed.insert(depot_id, manifest_id); + } + } + store + } + + pub fn config_dir(&self) -> &Path { + &self.config_dir + } + + pub fn manifest_cache_path(&self, depot_id: u32, manifest_id: u64) -> PathBuf { + self.config_dir + .join(format!("{depot_id}_{manifest_id}.manifest")) + } + + pub fn installed_manifest(&self, depot_id: u32) -> u64 { + self.installed.get(&depot_id).copied().unwrap_or(0) + } + + pub fn is_installed(&self, depot_id: u32, manifest_id: u64) -> bool { + self.installed + .get(&depot_id) + .is_some_and(|installed| *installed == manifest_id && *installed != INVALID_MANIFEST_ID) + } + + pub fn begin_depot(&mut self, depot_id: u32) -> bool { + self.installed.insert(depot_id, INVALID_MANIFEST_ID); + self.save() + } + + pub fn finish_depot(&mut self, depot_id: u32, manifest_id: u64) -> bool { + self.installed.insert(depot_id, manifest_id); + self.save() + } + + pub fn forget_depot(&mut self, depot_id: u32) -> bool { + self.installed.remove(&depot_id); + self.save() + } + + pub fn discard(&mut self) { + self.installed.clear(); + let _ = fs::remove_file(self.config_path()); + } + + fn config_path(&self) -> PathBuf { + self.config_dir.join("depot.config") + } + + fn save(&self) -> bool { + if fs::create_dir_all(&self.config_dir).is_err() { + return false; + } + let ids: serde_json::Map = self + .installed + .iter() + .map(|(depot, manifest)| (depot.to_string(), json!(manifest))) + .collect(); + let Ok(bytes) = serde_json::to_string_pretty(&json!({ "installedManifestIDs": ids })) + else { + return false; + }; + atomic_write_synced(&self.config_path(), bytes.as_bytes()) + } +} + +pub struct DepotProgressStore { + path: PathBuf, + done: Mutex>, + flushed_count: Mutex, +} + +impl DepotProgressStore { + pub fn new(config_dir: impl AsRef, depot_id: u32, manifest_id: u64) -> Self { + let path = Self::sidecar_path(config_dir, depot_id, manifest_id); + let mut done = BTreeSet::new(); + if let Ok(buf) = fs::read(&path) { + if let Some(parsed) = parse_progress_sidecar(&buf) { + done = parsed; + } + } + let flushed_count = done.len(); + Self { + path, + done: Mutex::new(done), + flushed_count: Mutex::new(flushed_count), + } + } + + pub fn is_file_done(&self, file_index: u32) -> bool { + self.done.lock().unwrap().contains(&file_index) + } + + pub fn mark_file_done(&self, file_index: u32) { + self.done.lock().unwrap().insert(file_index); + } + + pub fn done_count(&self) -> usize { + self.done.lock().unwrap().len() + } + + pub fn flush(&self) -> bool { + let done = self.done.lock().unwrap(); + let mut flushed = self.flushed_count.lock().unwrap(); + if done.len() == *flushed { + return true; + } + let blob = serialize_progress_sidecar(&done); + if !atomic_write_synced(&self.path, &blob) { + return false; + } + *flushed = done.len(); + true + } + + pub fn discard(&self) { + self.done.lock().unwrap().clear(); + *self.flushed_count.lock().unwrap() = 0; + let _ = fs::remove_file(&self.path); + } + + pub fn remove(config_dir: impl AsRef, depot_id: u32, manifest_id: u64) { + let _ = fs::remove_file(Self::sidecar_path(config_dir, depot_id, manifest_id)); + } + + pub fn sidecar_path(config_dir: impl AsRef, depot_id: u32, manifest_id: u64) -> PathBuf { + config_dir + .as_ref() + .join(format!("{depot_id}_{manifest_id}.progress")) + } +} + +const PROGRESS_MAGIC: &[u8; 4] = b"WNDP"; +const PROGRESS_VERSION: u32 = 1; + +fn parse_progress_sidecar(buf: &[u8]) -> Option> { + if buf.len() < 12 || &buf[0..4] != PROGRESS_MAGIC { + return None; + } + if get_u32(&buf[4..8])? != PROGRESS_VERSION { + return None; + } + let count = get_u32(&buf[8..12])? as usize; + if 12usize.checked_add(count.checked_mul(4)?)? != buf.len() { + return None; + } + let mut out = BTreeSet::new(); + for i in 0..count { + out.insert(get_u32(&buf[12 + i * 4..16 + i * 4])?); + } + Some(out) +} + +fn serialize_progress_sidecar(done: &BTreeSet) -> Vec { + let mut out = Vec::with_capacity(12 + done.len() * 4); + out.extend_from_slice(PROGRESS_MAGIC); + put_u32(&mut out, PROGRESS_VERSION); + put_u32(&mut out, done.len() as u32); + for idx in done { + put_u32(&mut out, *idx); + } + out +} + +fn put_u32(out: &mut Vec, v: u32) { + out.extend_from_slice(&v.to_le_bytes()); +} + +fn get_u32(buf: &[u8]) -> Option { + Some(u32::from_le_bytes(buf.get(..4)?.try_into().ok()?)) +} + +fn atomic_write_synced(final_path: &Path, bytes: &[u8]) -> bool { + let Some(parent) = final_path.parent() else { + return false; + }; + if fs::create_dir_all(parent).is_err() { + return false; + } + let tmp_path = + final_path.with_extension(match final_path.extension().and_then(|s| s.to_str()) { + Some(ext) => format!("{ext}.tmp"), + None => "tmp".to_string(), + }); + let mut file = match File::create(&tmp_path) { + Ok(file) => file, + Err(_) => return false, + }; + if file.write_all(bytes).is_err() || file.sync_all().is_err() { + let _ = fs::remove_file(&tmp_path); + return false; + } + drop(file); + if fs::rename(&tmp_path, final_path).is_err() { + let _ = fs::remove_file(&tmp_path); + return false; + } + if let Ok(dir) = File::open(parent) { + let _ = dir.sync_all(); + } + true +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn depot_config_roundtrips_and_marks_in_progress() { + let dir = temp_dir("depot_config_roundtrips"); + let mut store = DepotConfigStore::load(&dir); + assert_eq!(store.installed_manifest(100), 0); + assert!(!store.is_installed(100, 0)); + assert!(store.begin_depot(100)); + assert_eq!( + DepotConfigStore::load(&dir).installed_manifest(100), + INVALID_MANIFEST_ID + ); + assert!(store.finish_depot(100, 555)); + let loaded = DepotConfigStore::load(&dir); + assert!(loaded.is_installed(100, 555)); + assert_eq!( + loaded.manifest_cache_path(100, 555), + dir.join("100_555.manifest") + ); + let _ = fs::remove_dir_all(&dir); + } + + #[test] + fn progress_sidecar_roundtrips_sorted_indices() { + let dir = temp_dir("progress_sidecar_roundtrips"); + let store = DepotProgressStore::new(&dir, 1, 2); + store.mark_file_done(9); + store.mark_file_done(3); + assert_eq!(store.done_count(), 2); + assert!(store.flush()); + + let loaded = DepotProgressStore::new(&dir, 1, 2); + assert!(loaded.is_file_done(3)); + assert!(loaded.is_file_done(9)); + assert!(!loaded.is_file_done(4)); + loaded.discard(); + assert!(!DepotProgressStore::sidecar_path(&dir, 1, 2).exists()); + let _ = fs::remove_dir_all(&dir); + } + + fn temp_dir(name: &str) -> PathBuf { + let dir = std::env::temp_dir().join(format!( + "wnsteam_{name}_{}", + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_nanos() + )); + let _ = fs::remove_dir_all(&dir); + dir + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/depot_downloader.rs b/app/src/main/cpp/wn-steam-client/rust/src/depot_downloader.rs new file mode 100644 index 000000000..3bd132a0c --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/depot_downloader.rs @@ -0,0 +1,738 @@ +use crate::cdn_client::{CdnClient, CdnManifestResult}; +use crate::content_manifest::ContentManifest; +use crate::depot_config::{DepotConfigStore, DepotProgressStore, INVALID_MANIFEST_ID}; +use crate::depot_writer::{write_depot_sequential, DepotWriteOptions}; +use crate::pb::ccontentserverdirectory::CContentServerDirectoryServerInfo; +use std::fs; +use std::path::{Path, PathBuf}; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::thread; +use std::time::Duration; + +pub const MAX_MANIFEST_FETCH_ATTEMPTS: usize = 5; + +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct DepotSpec { + pub depot_id: u32, + pub manifest_id: u64, +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct ResolvedDepotSpec { + pub depot_id: u32, + pub manifest_id: u64, + pub depot_key: Vec, + pub manifest_request_code: u64, +} + +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct DepotDownloadProgress { + pub depot_id: u32, + pub depot_done: u64, + pub depot_total: u64, + pub depots_done: u32, + pub depots_total: u32, + pub verifying: bool, +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct DepotDownloadResult { + pub success: bool, + pub error: String, + pub bytes_written: u64, + pub depots_completed: u32, + pub depots_skipped: u32, +} + +impl DepotDownloadResult { + pub fn fail(error: impl Into) -> Self { + Self { + success: false, + error: error.into(), + ..Default::default() + } + } + + pub fn ok(bytes_written: u64, depots_completed: u32, depots_skipped: u32) -> Self { + Self { + success: true, + bytes_written, + depots_completed, + depots_skipped, + error: String::new(), + } + } +} + +pub fn clean_pause_marker_name(depot_id: u32, manifest_id: u64) -> String { + format!("{depot_id}_{manifest_id}.cleanpause") +} + +pub fn clean_pause_marker_path( + config_dir: impl AsRef, + depot_id: u32, + manifest_id: u64, +) -> PathBuf { + config_dir + .as_ref() + .join(clean_pause_marker_name(depot_id, manifest_id)) +} + +pub fn has_clean_pause_marker( + config_dir: impl AsRef, + depot_id: u32, + manifest_id: u64, +) -> bool { + clean_pause_marker_path(config_dir, depot_id, manifest_id).is_file() +} + +pub fn write_clean_pause_marker( + config_dir: impl AsRef, + depot_id: u32, + manifest_id: u64, +) -> bool { + let path = clean_pause_marker_path(config_dir, depot_id, manifest_id); + let Some(parent) = path.parent() else { + return false; + }; + if fs::create_dir_all(parent).is_err() { + return false; + } + fs::write(path, manifest_id.to_string()).is_ok() +} + +pub fn remove_clean_pause_marker(config_dir: impl AsRef, depot_id: u32, manifest_id: u64) { + let _ = fs::remove_file(clean_pause_marker_path(config_dir, depot_id, manifest_id)); +} + +pub fn validate_download_inputs( + install_dir: &str, + depots: &[DepotSpec], +) -> Result<(), DepotDownloadResult> { + if install_dir.is_empty() { + return Err(DepotDownloadResult::fail("download: empty install dir")); + } + if depots.is_empty() { + return Err(DepotDownloadResult::fail("download: no depots")); + } + Ok(()) +} + +pub fn validate_resolved_download_inputs( + install_dir: &str, + depots: &[ResolvedDepotSpec], + servers: &[CContentServerDirectoryServerInfo], +) -> Result<(), DepotDownloadResult> { + if install_dir.is_empty() { + return Err(DepotDownloadResult::fail("download: empty install dir")); + } + if depots.is_empty() { + return Err(DepotDownloadResult::fail("download: no depots")); + } + if servers.is_empty() { + return Err(DepotDownloadResult::fail( + "download: no CDN servers available", + )); + } + Ok(()) +} + +pub fn filter_usable_cdn_servers( + servers: impl IntoIterator, +) -> Vec { + servers + .into_iter() + .filter(|server| !server.steam_china_only && !server.host.is_empty()) + .collect() +} + +pub fn manifest_retry_server_indices(server_count: usize, attempts: usize) -> Vec { + if server_count == 0 { + return Vec::new(); + } + (0..attempts) + .map(|attempt| attempt % server_count) + .collect() +} + +pub fn retry_backoff_millis(attempt: u32) -> u64 { + if attempt == 0 { + 0 + } else { + (300u64 << (attempt - 1)).min(4000) + } +} + +pub fn fetch_manifest_with_retry( + cdn: &CdnClient, + servers: &[CContentServerDirectoryServerInfo], + depot_id: u32, + manifest_id: u64, + request_code: u64, + cdn_auth_token: &str, + timeout: Duration, +) -> CdnManifestResult { + if servers.is_empty() { + return CdnManifestResult { + error: "download: no CDN servers available".to_string(), + ..Default::default() + }; + } + let mut last = CdnManifestResult::default(); + for (attempt, server_idx) in + manifest_retry_server_indices(servers.len(), MAX_MANIFEST_FETCH_ATTEMPTS) + .into_iter() + .enumerate() + { + if attempt > 0 { + thread::sleep(Duration::from_millis(retry_backoff_millis(attempt as u32))); + } + last = cdn.fetch_manifest( + &servers[server_idx], + depot_id, + manifest_id, + request_code, + cdn_auth_token, + timeout, + ); + if last.ok() { + return last; + } + } + last +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum DepotResumeDecision { + SkipInstalled, + Download { trust_existing_chunks: bool }, +} + +pub fn decide_depot_resume( + fresh: bool, + cfg: &DepotConfigStore, + spec: DepotSpec, + clean_pause_marker_exists: bool, +) -> DepotResumeDecision { + if !fresh && cfg.is_installed(spec.depot_id, spec.manifest_id) { + DepotResumeDecision::SkipInstalled + } else { + DepotResumeDecision::Download { + trust_existing_chunks: !fresh && clean_pause_marker_exists, + } + } +} + +pub fn in_progress_manifest_id() -> u64 { + INVALID_MANIFEST_ID +} + +pub fn map_write_progress( + depot_id: u32, + depots_done: u32, + depots_total: u32, + done: u64, + total: u64, + verifying: bool, +) -> DepotDownloadProgress { + DepotDownloadProgress { + depot_id, + depot_done: done, + depot_total: total, + depots_done, + depots_total, + verifying, + } +} + +pub type DepotProgressCallback<'a> = &'a (dyn Fn(&DepotDownloadProgress) + Sync); + +/// Returns a fresh manifest request code for (depot_id, manifest_id); Steam rotates codes ~every 5 min. +pub type ManifestCodeRefresher<'a> = &'a (dyn Fn(u32, u64) -> Option + Sync); + +pub fn download_resolved_depots( + install_dir: &str, + depots: &[ResolvedDepotSpec], + servers: &[CContentServerDirectoryServerInfo], + ca_bundle_path: &str, + fresh: bool, + max_workers: u32, +) -> DepotDownloadResult { + download_resolved_depots_with_cancel_progress( + install_dir, + depots, + servers, + ca_bundle_path, + fresh, + max_workers, + None, + None, + None, + ) +} + +pub fn download_resolved_depots_with_cancel( + install_dir: &str, + depots: &[ResolvedDepotSpec], + servers: &[CContentServerDirectoryServerInfo], + ca_bundle_path: &str, + fresh: bool, + max_workers: u32, + cancel: Option<&AtomicBool>, +) -> DepotDownloadResult { + download_resolved_depots_with_cancel_progress( + install_dir, + depots, + servers, + ca_bundle_path, + fresh, + max_workers, + cancel, + None, + None, + ) +} + +pub fn download_resolved_depots_with_cancel_progress( + install_dir: &str, + depots: &[ResolvedDepotSpec], + servers: &[CContentServerDirectoryServerInfo], + ca_bundle_path: &str, + fresh: bool, + max_workers: u32, + cancel: Option<&AtomicBool>, + on_progress: Option>, + code_refresher: Option>, +) -> DepotDownloadResult { + if let Err(error) = validate_resolved_download_inputs(install_dir, depots, servers) { + return error; + } + let usable_servers = filter_usable_cdn_servers(servers.iter().cloned()); + if usable_servers.is_empty() { + return DepotDownloadResult::fail("download: no usable CDN server"); + } + + if let Err(error) = fs::create_dir_all(install_dir) { + return DepotDownloadResult::fail(format!("download: mkdir install dir: {error}")); + } + let config_dir = Path::new(install_dir).join(".DepotDownloader"); + if let Err(error) = fs::create_dir_all(&config_dir) { + return DepotDownloadResult::fail(format!("download: mkdir config dir: {error}")); + } + + let mut cfg = DepotConfigStore::load(&config_dir); + if fresh { + // Reset only this batch's depots; a global discard would wipe earlier batches' records. + for depot in depots { + cfg.forget_depot(depot.depot_id); + DepotProgressStore::remove(&config_dir, depot.depot_id, depot.manifest_id); + remove_clean_pause_marker(&config_dir, depot.depot_id, depot.manifest_id); + } + } + + let cdn = CdnClient::new(ca_bundle_path); + let mut result = DepotDownloadResult { + success: true, + ..Default::default() + }; + + let depots_total = depots.len() as u32; + for (depot_index, depot) in depots.iter().enumerate() { + if cancel.is_some_and(|cancel| cancel.load(Ordering::Relaxed)) { + return DepotDownloadResult::fail("cancelled"); + } + let spec = DepotSpec { + depot_id: depot.depot_id, + manifest_id: depot.manifest_id, + }; + let clean_pause = has_clean_pause_marker(&config_dir, depot.depot_id, depot.manifest_id); + if decide_depot_resume(fresh, &cfg, spec, clean_pause) == DepotResumeDecision::SkipInstalled + { + result.depots_skipped += 1; + continue; + } + if depot.depot_key.len() != 32 { + return DepotDownloadResult::fail(format!( + "download: depot key unavailable for depot {}", + depot.depot_id + )); + } + if !cfg.begin_depot(depot.depot_id) { + return DepotDownloadResult::fail(format!( + "download: depot.config begin failed for depot {}", + depot.depot_id + )); + } + + let cache_path = cfg.manifest_cache_path(depot.depot_id, depot.manifest_id); + let raw_manifest = match read_cached_manifest(&cache_path) { + Some(raw) => raw, + None => { + if cancel.is_some_and(|cancel| cancel.load(Ordering::Relaxed)) { + return DepotDownloadResult::fail("cancelled"); + } + // Prefer a code obtained now; the pre-resolved one may have expired. + let refreshed_code = code_refresher + .and_then(|refresh| refresh(depot.depot_id, depot.manifest_id)); + let request_code = refreshed_code.unwrap_or(depot.manifest_request_code); + let mut manifest = fetch_manifest_with_retry( + &cdn, + &usable_servers, + depot.depot_id, + depot.manifest_id, + request_code, + "", + CdnClient::default_timeout(), + ); + if !manifest.ok() { + // One more pass with a code obtained after the failed attempts. + if let Some(fresh) = code_refresher + .and_then(|refresh| refresh(depot.depot_id, depot.manifest_id)) + .filter(|fresh| *fresh != request_code) + { + manifest = fetch_manifest_with_retry( + &cdn, + &usable_servers, + depot.depot_id, + depot.manifest_id, + fresh, + "", + CdnClient::default_timeout(), + ); + } + } + if !manifest.ok() { + return DepotDownloadResult::fail(format!( + "download: manifest fetch failed for depot {}: {}", + depot.depot_id, manifest.error + )); + } + let _ = write_manifest_cache(&cache_path, &manifest.raw_manifest); + manifest.raw_manifest + } + }; + + let Some(mut manifest) = ContentManifest::parse(&raw_manifest) else { + return DepotDownloadResult::fail(format!( + "download: manifest parse failed for depot {}", + depot.depot_id + )); + }; + if !manifest.decrypt_filenames(&depot.depot_key) { + return DepotDownloadResult::fail(format!( + "download: filename decryption failed for depot {}", + depot.depot_id + )); + } + + let depot_id = depot.depot_id; + let depots_done = depot_index as u32; + let chunk_progress = |done: u64, total: u64, verifying: bool| { + if let Some(on_progress) = on_progress { + let progress = map_write_progress( + depot_id, + depots_done, + depots_total, + done, + total, + verifying, + ); + on_progress(&progress); + } + }; + let chunk_progress: crate::depot_writer::DepotChunkProgressCallback = + &chunk_progress; + let write_result = write_depot_sequential( + &manifest, + &depot.depot_key, + &cdn, + &usable_servers, + install_dir, + DepotWriteOptions { + max_workers, + cancel, + on_progress: Some(chunk_progress), + ..Default::default() + }, + ); + if !write_result.ok() { + if write_result.resume_trust_safe { + let _ = write_clean_pause_marker(&config_dir, depot.depot_id, depot.manifest_id); + } + return DepotDownloadResult::fail(format!( + "download: depot {} write failed: {}", + depot.depot_id, write_result.error + )); + } + + if !cfg.finish_depot(depot.depot_id, depot.manifest_id) { + return DepotDownloadResult::fail(format!( + "download: depot.config finish failed for depot {}", + depot.depot_id + )); + } + DepotProgressStore::new(&config_dir, depot.depot_id, depot.manifest_id).discard(); + remove_clean_pause_marker(&config_dir, depot.depot_id, depot.manifest_id); + result.bytes_written += write_result.bytes_written; + result.depots_completed += 1; + } + + result +} + +fn read_cached_manifest(path: &Path) -> Option> { + let bytes = fs::read(path).ok()?; + (!bytes.is_empty()).then_some(bytes) +} + +fn write_manifest_cache(path: &Path, raw_manifest: &[u8]) -> bool { + let Some(parent) = path.parent() else { + return false; + }; + if fs::create_dir_all(parent).is_err() { + return false; + } + fs::write(path, raw_manifest).is_ok() +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::content_manifest::{END_OF_MANIFEST_MAGIC, METADATA_MAGIC, PAYLOAD_MAGIC}; + use crate::proto_wire::Writer; + use std::time::{SystemTime, UNIX_EPOCH}; + + #[test] + fn marker_names_match_cpp_format() { + assert_eq!(clean_pause_marker_name(123, 456), "123_456.cleanpause"); + } + + #[test] + fn clean_pause_marker_files_roundtrip() { + let dir = temp_dir("clean_pause"); + assert!(!has_clean_pause_marker(&dir, 123, 456)); + assert!(write_clean_pause_marker(&dir, 123, 456)); + assert!(has_clean_pause_marker(&dir, 123, 456)); + assert_eq!( + fs::read_to_string(clean_pause_marker_path(&dir, 123, 456)).unwrap(), + "456" + ); + remove_clean_pause_marker(&dir, 123, 456); + assert!(!has_clean_pause_marker(&dir, 123, 456)); + let _ = fs::remove_dir_all(&dir); + } + + #[test] + fn filters_non_china_servers_with_hosts() { + let servers = filter_usable_cdn_servers([ + CContentServerDirectoryServerInfo { + host: "ok".into(), + ..Default::default() + }, + CContentServerDirectoryServerInfo { + host: "china".into(), + steam_china_only: true, + ..Default::default() + }, + CContentServerDirectoryServerInfo { + host: String::new(), + ..Default::default() + }, + ]); + assert_eq!(servers.len(), 1); + assert_eq!(servers[0].host, "ok"); + } + + #[test] + fn resume_decision_matches_cpp_fresh_and_installed_rules() { + let dir = temp_dir("resume_decision"); + let mut cfg = DepotConfigStore::load(&dir); + cfg.finish_depot(100, 555); + let spec = DepotSpec { + depot_id: 100, + manifest_id: 555, + }; + assert_eq!( + decide_depot_resume(false, &cfg, spec, false), + DepotResumeDecision::SkipInstalled + ); + assert_eq!( + decide_depot_resume(true, &cfg, spec, true), + DepotResumeDecision::Download { + trust_existing_chunks: false + } + ); + assert_eq!( + decide_depot_resume( + false, + &cfg, + DepotSpec { + depot_id: 100, + manifest_id: 777 + }, + true + ), + DepotResumeDecision::Download { + trust_existing_chunks: true + } + ); + let _ = fs::remove_dir_all(&dir); + } + + #[test] + fn retry_rotation_and_progress_mapping_match_cpp() { + assert_eq!(manifest_retry_server_indices(3, 5), [0, 1, 2, 0, 1]); + assert_eq!(manifest_retry_server_indices(0, 5), Vec::::new()); + assert_eq!(retry_backoff_millis(1), 300); + assert_eq!(retry_backoff_millis(5), 4000); + assert_eq!( + map_write_progress(100, 2, 4, 10, 20, true), + DepotDownloadProgress { + depot_id: 100, + depot_done: 10, + depot_total: 20, + depots_done: 2, + depots_total: 4, + verifying: true + } + ); + } + + #[test] + fn validates_download_inputs() { + assert_eq!( + validate_download_inputs("", &[DepotSpec::default()]) + .unwrap_err() + .error, + "download: empty install dir" + ); + assert_eq!( + validate_download_inputs("/tmp/app", &[]).unwrap_err().error, + "download: no depots" + ); + assert!(validate_download_inputs("/tmp/app", &[DepotSpec::default()]).is_ok()); + assert_eq!(in_progress_manifest_id(), INVALID_MANIFEST_ID); + } + + #[test] + fn validates_resolved_inputs_and_filters_servers() { + assert_eq!( + validate_resolved_download_inputs("", &[ResolvedDepotSpec::default()], &[]) + .unwrap_err() + .error, + "download: empty install dir" + ); + assert_eq!( + validate_resolved_download_inputs("/tmp/app", &[], &[]) + .unwrap_err() + .error, + "download: no depots" + ); + assert_eq!( + validate_resolved_download_inputs("/tmp/app", &[ResolvedDepotSpec::default()], &[]) + .unwrap_err() + .error, + "download: no CDN servers available" + ); + } + + #[test] + fn resolved_download_uses_cached_manifest_and_records_install() { + let dir = temp_dir("resolved_download_cached_manifest"); + let config_dir = dir.join(".DepotDownloader"); + fs::create_dir_all(&config_dir).unwrap(); + let raw_manifest = raw_layout_manifest(100, 555, "empty.bin", 5); + fs::write(config_dir.join("100_555.manifest"), raw_manifest).unwrap(); + + let result = download_resolved_depots( + dir.to_str().unwrap(), + &[ResolvedDepotSpec { + depot_id: 100, + manifest_id: 555, + depot_key: vec![1u8; 32], + manifest_request_code: 0, + }], + &[CContentServerDirectoryServerInfo { + host: "cdn.example".into(), + https_support: "mandatory".into(), + ..Default::default() + }], + "", + false, + 4, + ); + + assert!(result.success, "{}", result.error); + assert_eq!(result.depots_completed, 1); + assert_eq!(result.depots_skipped, 0); + assert_eq!(fs::metadata(dir.join("empty.bin")).unwrap().len(), 5); + let cfg = DepotConfigStore::load(&config_dir); + assert!(cfg.is_installed(100, 555)); + + let skipped = download_resolved_depots( + dir.to_str().unwrap(), + &[ResolvedDepotSpec { + depot_id: 100, + manifest_id: 555, + depot_key: vec![1u8; 32], + manifest_request_code: 0, + }], + &[CContentServerDirectoryServerInfo { + host: "cdn.example".into(), + https_support: "mandatory".into(), + ..Default::default() + }], + "", + false, + 4, + ); + assert!(skipped.success); + assert_eq!(skipped.depots_completed, 0); + assert_eq!(skipped.depots_skipped, 1); + let _ = fs::remove_dir_all(&dir); + } + + fn temp_dir(name: &str) -> PathBuf { + let dir = std::env::temp_dir().join(format!( + "wnsteam_downloader_{name}_{}", + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_nanos() + )); + let _ = fs::remove_dir_all(&dir); + dir + } + + fn raw_layout_manifest(depot_id: u32, manifest_id: u64, filename: &str, size: u64) -> Vec { + let mut file_body = Vec::new(); + { + let mut writer = Writer::new(&mut file_body); + writer.string_field(1, filename); + writer.uint64_field(2, size); + } + + let mut payload = Vec::new(); + Writer::new(&mut payload).submessage_field(1, &file_body); + + let mut metadata = Vec::new(); + { + let mut writer = Writer::new(&mut metadata); + writer.uint32_field(1, depot_id); + writer.uint64_field(2, manifest_id); + writer.bool_field_force(4, false); + } + + let mut raw = Vec::new(); + push_section(&mut raw, PAYLOAD_MAGIC, &payload); + push_section(&mut raw, METADATA_MAGIC, &metadata); + raw.extend_from_slice(&END_OF_MANIFEST_MAGIC.to_le_bytes()); + raw + } + + fn push_section(out: &mut Vec, magic: u32, body: &[u8]) { + out.extend_from_slice(&magic.to_le_bytes()); + out.extend_from_slice(&(body.len() as u32).to_le_bytes()); + out.extend_from_slice(body); + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/depot_writer.rs b/app/src/main/cpp/wn-steam-client/rust/src/depot_writer.rs new file mode 100644 index 000000000..1c3bf24fe --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/depot_writer.rs @@ -0,0 +1,1027 @@ +use crate::cdn_client::{CdnClient, CdnConnection}; +use crate::content_manifest::{ChunkData, ContentManifest}; +use crate::depot_chunk::process_depot_chunk; +use crate::pb::ccontentserverdirectory::CContentServerDirectoryServerInfo; +use std::fs::{self, File, OpenOptions}; +use std::io::{Read, Seek, SeekFrom, Write}; +use std::path::{Component, Path}; +use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; +use std::sync::{Arc, Mutex}; +use std::thread; +use std::time::{Duration, Instant}; + +pub const DEPOT_FILE_FLAG_EXECUTABLE: u32 = 32; +pub const DEPOT_FILE_FLAG_DIRECTORY: u32 = 64; +pub const DEPOT_FILE_FLAG_SYMLINK: u32 = 512; +pub const MAX_CHUNK_ATTEMPTS: u32 = 5; +pub const SLOW_CHUNK_ROTATE_THRESHOLD_SECS: u64 = 8; +pub const SLOW_CHUNK_ROTATE_CONSECUTIVE_LIMIT: u32 = 3; + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct DepotWriteResult { + pub files_written: u64, + pub bytes_written: u64, + pub resume_trust_safe: bool, + pub error: String, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum DepotFileAction { + Directory { path: String }, + Symlink { path: String, target: String }, + Regular { path: String, size: u64, mode: u32 }, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct ChunkWriteJob { + pub file_idx: u32, + pub chunk_idx: u32, +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct DepotWritePlan { + pub total_bytes: u64, + pub files_written: u64, + pub actions: Vec, + pub chunk_jobs: Vec, + pub worker_count: u32, +} + +pub type DepotChunkProgressCallback<'a> = &'a (dyn Fn(u64, u64, bool) + Sync); + +#[derive(Clone, Copy)] +pub struct DepotWriteOptions<'a> { + pub cdn_auth_token: &'a str, + pub timeout: Duration, + pub max_workers: u32, + pub cancel: Option<&'a AtomicBool>, + pub on_progress: Option>, +} + +impl Default for DepotWriteOptions<'_> { + fn default() -> Self { + Self { + cdn_auth_token: "", + timeout: CdnClient::default_timeout(), + max_workers: 8, + cancel: None, + on_progress: None, + } + } +} + +impl DepotWriteResult { + pub fn ok(&self) -> bool { + self.error.is_empty() + } + + pub fn success(files_written: u64, bytes_written: u64) -> Self { + Self { + files_written, + bytes_written, + resume_trust_safe: true, + error: String::new(), + } + } + + pub fn fail(error: impl Into, resume_trust_safe: bool) -> Self { + Self { + error: error.into(), + resume_trust_safe, + ..Default::default() + } + } +} + +pub fn retry_backoff_millis(attempt: u32) -> u64 { + if attempt == 0 { + return 0; + } + (300u64 << (attempt - 1)).min(4000) +} + +pub fn chunk_attempt_server_indices( + start_server_index: usize, + server_count: usize, + attempts: u32, +) -> Vec { + if server_count == 0 || attempts == 0 { + return Vec::new(); + } + let mut out = Vec::with_capacity(attempts as usize); + let mut server_index = start_server_index % server_count; + for attempt in 0..attempts { + if attempt > 0 && server_count > 1 { + server_index = (server_index + 1) % server_count; + } + out.push(server_index); + } + out +} + +pub fn should_rotate_after_slow_chunks(consecutive_slow_chunks: u32, server_count: usize) -> bool { + server_count > 1 && consecutive_slow_chunks >= SLOW_CHUNK_ROTATE_CONSECUTIVE_LIMIT +} + +pub fn depot_adler_hash(data: &[u8]) -> u32 { + const BLOCK: usize = 5552; + let mut a = 0u32; + let mut b = 0u32; + for chunk in data.chunks(BLOCK) { + for byte in chunk { + a += *byte as u32; + b += a; + } + a %= 65521; + b %= 65521; + } + a | (b << 16) +} + +pub fn path_is_safe(rel: &str) -> bool { + if rel.is_empty() || rel.starts_with('/') || rel.starts_with('\\') { + return false; + } + let path = Path::new(rel); + if path.is_absolute() { + return false; + } + path.components().all(|component| { + matches!(component, Component::Normal(_)) || matches!(component, Component::CurDir) + }) && !path + .components() + .any(|component| matches!(component, Component::ParentDir | Component::Prefix(_))) +} + +pub fn clamp_worker_count(max_workers: u32, outstanding_chunks: usize) -> u32 { + if outstanding_chunks == 0 { + return 0; + } + let requested = if max_workers == 0 { 1 } else { max_workers }; + requested.min(64).min(outstanding_chunks as u32) +} + +pub fn plan_depot_write( + manifest: &ContentManifest, + depot_key: &[u8], + server_count: usize, + target_dir: &str, + max_workers: u32, +) -> Result { + if manifest.metadata.filenames_encrypted { + return Err(DepotWriteResult::fail( + "write_depot: manifest filenames are still encrypted", + false, + )); + } + if depot_key.len() != 32 { + return Err(DepotWriteResult::fail( + "write_depot: bad depot key length", + false, + )); + } + if server_count == 0 { + return Err(DepotWriteResult::fail("write_depot: no CDN servers", false)); + } + + let mut plan = DepotWritePlan { + total_bytes: manifest.files.iter().map(|file| file.size).sum(), + ..Default::default() + }; + + for (file_idx, file) in manifest.files.iter().enumerate() { + if !path_is_safe(&file.filename) { + return Err(DepotWriteResult::fail( + format!("write_depot: unsafe path '{}'", file.filename), + false, + )); + } + let path = join_target_path(target_dir, &file.filename); + if !file.linktarget.is_empty() { + plan.actions.push(DepotFileAction::Symlink { + path, + target: file.linktarget.clone(), + }); + plan.files_written += 1; + continue; + } + if (file.flags & DEPOT_FILE_FLAG_DIRECTORY) != 0 { + plan.actions.push(DepotFileAction::Directory { path }); + continue; + } + let mode = if (file.flags & DEPOT_FILE_FLAG_EXECUTABLE) != 0 { + 0o755 + } else { + 0o644 + }; + plan.actions.push(DepotFileAction::Regular { + path, + size: file.size, + mode, + }); + plan.files_written += 1; + for chunk_idx in 0..file.chunks.len() { + plan.chunk_jobs.push(ChunkWriteJob { + file_idx: file_idx as u32, + chunk_idx: chunk_idx as u32, + }); + } + } + plan.worker_count = clamp_worker_count(max_workers, plan.chunk_jobs.len()); + Ok(plan) +} + +pub fn write_depot_sequential( + manifest: &ContentManifest, + depot_key: &[u8], + cdn: &CdnClient, + servers: &[CContentServerDirectoryServerInfo], + target_dir: &str, + options: DepotWriteOptions<'_>, +) -> DepotWriteResult { + let plan = match plan_depot_write( + manifest, + depot_key, + servers.len(), + target_dir, + options.max_workers, + ) { + Ok(plan) => plan, + Err(error) => return error, + }; + let layout = create_depot_layout(&plan); + if !layout.ok() { + return layout; + } + + if plan.worker_count > 1 && plan.chunk_jobs.len() > 1 && servers.len() > 0 { + return write_depot_parallel( + manifest, + depot_key, + cdn, + servers, + target_dir, + &plan, + &options, + ); + } + + let mut bytes_written = 0u64; + let total_bytes = plan.total_bytes; + let mut conn = cdn.open_connection(); + for (job_index, job) in plan.chunk_jobs.iter().enumerate() { + if options + .cancel + .is_some_and(|cancel| cancel.load(Ordering::Relaxed)) + { + return DepotWriteResult::fail("cancelled", true); + } + let file = match manifest.files.get(job.file_idx as usize) { + Some(file) => file, + None => return DepotWriteResult::fail("bad file index", true), + }; + let chunk = match file.chunks.get(job.chunk_idx as usize) { + Some(chunk) => chunk, + None => return DepotWriteResult::fail("bad chunk index", true), + }; + let path = join_target_path(target_dir, &file.filename); + if existing_chunk_matches(&path, chunk) { + bytes_written += chunk.cb_original as u64; + if let Some(on_progress) = options.on_progress { + on_progress(bytes_written, total_bytes, true); + } + continue; + } + match fetch_process_write_chunk( + cdn, + Some(&mut conn), + servers, + manifest, + job.file_idx as usize, + job.chunk_idx as usize, + depot_key, + target_dir, + options.cdn_auth_token, + job_index % servers.len(), + options.timeout, + ) { + Ok(bytes) => { + bytes_written += bytes; + if let Some(on_progress) = options.on_progress { + on_progress(bytes_written, total_bytes, false); + } + } + Err(error) => return DepotWriteResult::fail(error, true), + } + } + + for file in &manifest.files { + if options + .cancel + .is_some_and(|cancel| cancel.load(Ordering::Relaxed)) + { + return DepotWriteResult::fail("cancelled", true); + } + if !file.linktarget.is_empty() || (file.flags & DEPOT_FILE_FLAG_DIRECTORY) != 0 { + continue; + } + let path = join_target_path(target_dir, &file.filename); + if let Err(error) = finalize_regular_file(path, file.size) { + return DepotWriteResult::fail(error, true); + } + } + + DepotWriteResult { + files_written: plan.files_written, + bytes_written, + resume_trust_safe: true, + error: String::new(), + } +} + +fn write_depot_parallel( + manifest: &ContentManifest, + depot_key: &[u8], + cdn: &CdnClient, + servers: &[CContentServerDirectoryServerInfo], + target_dir: &str, + plan: &DepotWritePlan, + options: &DepotWriteOptions<'_>, +) -> DepotWriteResult { + let total_bytes = plan.total_bytes; + let bytes_written = Arc::new(AtomicU64::new(0)); + let error_slot: Arc>> = Arc::new(Mutex::new(None)); + let next_index = Arc::new(std::sync::atomic::AtomicUsize::new(0)); + let jobs = Arc::new(plan.chunk_jobs.clone()); + let worker_count = (plan.worker_count as usize).max(1).min(jobs.len()); + let cdn_auth_token = options.cdn_auth_token.to_string(); + let timeout = options.timeout; + let cancel_flag = options.cancel.map(|c| { + let raw = c as *const AtomicBool; + unsafe { raw.as_ref() }.unwrap() + }); + // SAFETY: we only spawn scoped threads so all `'a` references outlive joins. + let scope_result = thread::scope(|scope| -> DepotWriteResult { + let mut handles = Vec::with_capacity(worker_count); + for worker_id in 0..worker_count { + let bytes_written = Arc::clone(&bytes_written); + let error_slot = Arc::clone(&error_slot); + let next_index = Arc::clone(&next_index); + let jobs = Arc::clone(&jobs); + let cdn_auth_token = cdn_auth_token.clone(); + let manifest_ref = manifest; + let depot_key_ref = depot_key; + let target_dir_ref = target_dir; + let cdn_ref = cdn; + let servers_ref = servers; + let progress = options.on_progress; + handles.push(scope.spawn(move || { + let mut conn = cdn_ref.open_connection(); + let mut slow_chunks = 0u32; + let mut worker_server_bias = worker_id % servers_ref.len(); + loop { + if cancel_flag.is_some_and(|c| c.load(Ordering::Relaxed)) { + return; + } + if error_slot.lock().expect("err slot poisoned").is_some() { + return; + } + let idx = next_index.fetch_add(1, Ordering::Relaxed); + if idx >= jobs.len() { + return; + } + let job = jobs[idx]; + let file = match manifest_ref.files.get(job.file_idx as usize) { + Some(file) => file, + None => { + *error_slot.lock().expect("err slot poisoned") = + Some("bad file index".to_string()); + return; + } + }; + let chunk = match file.chunks.get(job.chunk_idx as usize) { + Some(chunk) => chunk, + None => { + *error_slot.lock().expect("err slot poisoned") = + Some("bad chunk index".to_string()); + return; + } + }; + let path = join_target_path(target_dir_ref, &file.filename); + if existing_chunk_matches(&path, chunk) { + let total = + bytes_written.fetch_add(chunk.cb_original as u64, Ordering::Relaxed) + + chunk.cb_original as u64; + if let Some(cb) = progress { + cb(total, total_bytes, true); + } + continue; + } + if should_rotate_after_slow_chunks(slow_chunks, servers_ref.len()) { + worker_server_bias = (worker_server_bias + 1) % servers_ref.len(); + conn = cdn_ref.open_connection(); + slow_chunks = 0; + } + let start_server = (idx + worker_server_bias) % servers_ref.len(); + let started = Instant::now(); + match fetch_process_write_chunk( + cdn_ref, + Some(&mut conn), + servers_ref, + manifest_ref, + job.file_idx as usize, + job.chunk_idx as usize, + depot_key_ref, + target_dir_ref, + &cdn_auth_token, + start_server, + timeout, + ) { + Ok(bytes) => { + if started.elapsed() + > Duration::from_secs(SLOW_CHUNK_ROTATE_THRESHOLD_SECS) + && servers_ref.len() > 1 + { + slow_chunks += 1; + } else { + slow_chunks = 0; + } + let total = bytes_written.fetch_add(bytes, Ordering::Relaxed) + bytes; + if let Some(cb) = progress { + cb(total, total_bytes, false); + } + } + Err(error) => { + *error_slot.lock().expect("err slot poisoned") = Some(error); + return; + } + } + } + })); + } + for handle in handles { + let _ = handle.join(); + } + if cancel_flag.is_some_and(|c| c.load(Ordering::Relaxed)) { + return DepotWriteResult::fail("cancelled", true); + } + if let Some(error) = error_slot.lock().expect("err slot poisoned").take() { + return DepotWriteResult::fail(error, true); + } + DepotWriteResult { + files_written: plan.files_written, + bytes_written: bytes_written.load(Ordering::Relaxed), + resume_trust_safe: true, + error: String::new(), + } + }); + if !scope_result.ok() { + return scope_result; + } + + for file in &manifest.files { + if options + .cancel + .is_some_and(|cancel| cancel.load(Ordering::Relaxed)) + { + return DepotWriteResult::fail("cancelled", true); + } + if !file.linktarget.is_empty() || (file.flags & DEPOT_FILE_FLAG_DIRECTORY) != 0 { + continue; + } + let path = join_target_path(target_dir, &file.filename); + if let Err(error) = finalize_regular_file(path, file.size) { + return DepotWriteResult::fail(error, true); + } + } + scope_result +} + +pub fn create_depot_layout(plan: &DepotWritePlan) -> DepotWriteResult { + for action in &plan.actions { + let result = match action { + DepotFileAction::Directory { path } => create_directory(path), + DepotFileAction::Symlink { path, target } => create_symlink(path, target), + DepotFileAction::Regular { path, mode, .. } => create_regular_file(path, *mode), + }; + if let Err(error) = result { + return DepotWriteResult::fail(error, false); + } + } + DepotWriteResult { + files_written: plan.files_written, + bytes_written: 0, + resume_trust_safe: true, + error: String::new(), + } +} + +pub fn write_chunk_at(path: impl AsRef, offset: u64, data: &[u8]) -> Result { + let path = path.as_ref(); + make_parent_dirs(path)?; + let mut file = OpenOptions::new() + .create(true) + .write(true) + .read(true) + .truncate(false) + .open(path) + .map_err(|err| format!("write_depot: open '{}': {err}", path.display()))?; + file.seek(SeekFrom::Start(offset)) + .map_err(|err| format!("write_depot: seek '{}': {err}", path.display()))?; + file.write_all(data) + .map_err(|err| format!("write_depot: write '{}': {err}", path.display()))?; + Ok(data.len() as u64) +} + +pub fn process_and_write_chunk( + path: impl AsRef, + chunk: &ChunkData, + raw_chunk: &[u8], + depot_key: &[u8], +) -> Result { + let processed = process_depot_chunk(raw_chunk, depot_key, chunk.crc, chunk.cb_original); + if !processed.ok() { + return Err(format!("decode: {}", processed.error)); + } + write_chunk_at(path, chunk.offset, &processed.data) +} + +#[allow(clippy::too_many_arguments)] +pub fn fetch_process_write_chunk( + cdn: &CdnClient, + mut conn: Option<&mut CdnConnection>, + servers: &[CContentServerDirectoryServerInfo], + manifest: &ContentManifest, + file_idx: usize, + chunk_idx: usize, + depot_key: &[u8], + target_dir: &str, + cdn_auth_token: &str, + start_server_index: usize, + timeout: Duration, +) -> Result { + if servers.is_empty() { + return Err("write_depot: no CDN servers".to_string()); + } + let file = manifest + .files + .get(file_idx) + .ok_or_else(|| "write_depot: bad file index".to_string())?; + let chunk = file + .chunks + .get(chunk_idx) + .ok_or_else(|| "write_depot: bad chunk index".to_string())?; + let path = join_target_path(target_dir, &file.filename); + let mut last_error = String::new(); + for (attempt, server_idx) in + chunk_attempt_server_indices(start_server_index, servers.len(), MAX_CHUNK_ATTEMPTS) + .into_iter() + .enumerate() + { + if attempt > 0 { + thread::sleep(Duration::from_millis(retry_backoff_millis(attempt as u32))); + if let Some(connection) = conn.as_deref_mut() { + *connection = cdn.open_connection(); + } + } + let fetched = match conn.as_deref_mut() { + Some(connection) => cdn.fetch_chunk_with_connection( + connection, + &servers[server_idx], + manifest.metadata.depot_id, + &chunk.sha, + cdn_auth_token, + timeout, + ), + None => cdn.fetch_chunk( + &servers[server_idx], + manifest.metadata.depot_id, + &chunk.sha, + cdn_auth_token, + timeout, + ), + }; + if !fetched.ok() { + last_error = fetched.error; + continue; + } + match process_and_write_chunk(&path, chunk, &fetched.data, depot_key) { + Ok(bytes) => return Ok(bytes), + Err(error) => last_error = error, + } + } + Err(format!( + "write_depot: chunk for '{}' failed after {} attempts: {}", + file.filename, MAX_CHUNK_ATTEMPTS, last_error + )) +} + +pub fn existing_chunk_matches(path: impl AsRef, chunk: &ChunkData) -> bool { + let path = path.as_ref(); + let Ok(mut file) = File::open(path) else { + return false; + }; + let Ok(metadata) = file.metadata() else { + return false; + }; + let end = chunk.offset.saturating_add(chunk.cb_original as u64); + if chunk.cb_original == 0 || metadata.len() < end { + return false; + } + let mut buf = vec![0u8; chunk.cb_original as usize]; + if file.seek(SeekFrom::Start(chunk.offset)).is_err() || file.read_exact(&mut buf).is_err() { + return false; + } + depot_adler_hash(&buf) == chunk.crc +} + +pub fn sync_file(path: impl AsRef) -> bool { + OpenOptions::new() + .read(true) + .write(true) + .open(path) + .and_then(|file| file.sync_all()) + .is_ok() +} + +pub fn finalize_regular_file(path: impl AsRef, size: u64) -> Result<(), String> { + let path = path.as_ref(); + let file = OpenOptions::new() + .write(true) + .open(path) + .map_err(|err| format!("write_depot: final open '{}': {err}", path.display()))?; + file.set_len(size) + .map_err(|err| format!("write_depot: final truncate '{}': {err}", path.display()))?; + file.sync_all() + .map_err(|err| format!("write_depot: final sync '{}': {err}", path.display())) +} + +fn join_target_path(target_dir: &str, rel: &str) -> String { + if target_dir.ends_with('/') || target_dir.ends_with('\\') { + format!("{target_dir}{rel}") + } else { + format!("{target_dir}/{rel}") + } +} + +fn create_directory(path: &str) -> Result<(), String> { + fs::create_dir_all(path).map_err(|err| format!("write_depot: mkdir '{path}': {err}")) +} + +fn create_regular_file(path: &str, mode: u32) -> Result<(), String> { + let path_ref = Path::new(path); + make_parent_dirs(path_ref)?; + OpenOptions::new() + .create(true) + .write(true) + .read(true) + .truncate(false) + .open(path_ref) + .map_err(|err| format!("write_depot: open '{path}': {err}"))?; + set_file_mode(path_ref, mode) +} + +fn create_symlink(path: &str, target: &str) -> Result<(), String> { + let path_ref = Path::new(path); + make_parent_dirs(path_ref)?; + if path_ref.exists() { + fs::remove_file(path_ref).map_err(|err| format!("write_depot: unlink '{path}': {err}"))?; + } + create_platform_symlink(target, path_ref) + .map_err(|err| format!("write_depot: symlink '{path}': {err}")) +} + +fn make_parent_dirs(path: &Path) -> Result<(), String> { + if let Some(parent) = path.parent() { + if !parent.as_os_str().is_empty() { + fs::create_dir_all(parent) + .map_err(|err| format!("write_depot: mkdir '{}': {err}", parent.display()))?; + } + } + Ok(()) +} + +#[cfg(unix)] +fn create_platform_symlink(target: &str, path: &Path) -> std::io::Result<()> { + std::os::unix::fs::symlink(target, path) +} + +#[cfg(windows)] +fn create_platform_symlink(target: &str, path: &Path) -> std::io::Result<()> { + std::os::windows::fs::symlink_file(target, path) +} + +#[cfg(unix)] +fn set_file_mode(path: &Path, mode: u32) -> Result<(), String> { + use std::os::unix::fs::PermissionsExt; + let permissions = fs::Permissions::from_mode(mode); + fs::set_permissions(path, permissions) + .map_err(|err| format!("write_depot: chmod '{}': {err}", path.display())) +} + +#[cfg(not(unix))] +fn set_file_mode(_path: &Path, _mode: u32) -> Result<(), String> { + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::crypto::{aes256_cbc_encrypt, aes256_ecb_encrypt_block, AES_BLOCK_BYTES}; + use std::path::PathBuf; + + #[test] + fn depot_adler_uses_steam_zero_seed() { + assert_eq!(depot_adler_hash(b""), 0); + assert_eq!(depot_adler_hash(b"abc"), 0x024a_0126); + } + + #[test] + fn rejects_paths_that_escape_target() { + assert!(path_is_safe("a/b/file.txt")); + assert!(path_is_safe("./a/file.txt")); + assert!(!path_is_safe("")); + assert!(!path_is_safe("../file.txt")); + assert!(!path_is_safe("a/../file.txt")); + assert!(!path_is_safe("/abs/file.txt")); + } + + #[test] + fn retry_backoff_matches_cpp_caps() { + assert_eq!(retry_backoff_millis(1), 300); + assert_eq!(retry_backoff_millis(2), 600); + assert_eq!(retry_backoff_millis(5), 4000); + assert_eq!(MAX_CHUNK_ATTEMPTS, 5); + assert_eq!(SLOW_CHUNK_ROTATE_THRESHOLD_SECS, 8); + } + + #[test] + fn chunk_retry_rotates_across_servers_like_cpp_worker() { + assert_eq!(chunk_attempt_server_indices(0, 3, 5), [0, 1, 2, 0, 1]); + assert_eq!(chunk_attempt_server_indices(2, 3, 5), [2, 0, 1, 2, 0]); + assert_eq!(chunk_attempt_server_indices(0, 1, 5), [0, 0, 0, 0, 0]); + assert!(chunk_attempt_server_indices(0, 0, 5).is_empty()); + assert!(!should_rotate_after_slow_chunks(2, 3)); + assert!(should_rotate_after_slow_chunks(3, 3)); + assert!(!should_rotate_after_slow_chunks(3, 1)); + } + + #[test] + fn depot_plan_validates_inputs_and_enumerates_actions() { + let manifest = ContentManifest { + metadata: crate::content_manifest::Metadata { + filenames_encrypted: false, + ..Default::default() + }, + files: vec![ + crate::content_manifest::FileMapping { + filename: "bin".into(), + flags: DEPOT_FILE_FLAG_DIRECTORY, + ..Default::default() + }, + crate::content_manifest::FileMapping { + filename: "bin/game".into(), + size: 10, + flags: DEPOT_FILE_FLAG_EXECUTABLE, + chunks: vec![crate::content_manifest::ChunkData::default()], + ..Default::default() + }, + crate::content_manifest::FileMapping { + filename: "link".into(), + linktarget: "bin/game".into(), + ..Default::default() + }, + ], + signature: Vec::new(), + }; + + let plan = plan_depot_write(&manifest, &[7u8; 32], 2, "/target", 99).unwrap(); + assert_eq!(plan.total_bytes, 10); + assert_eq!(plan.files_written, 2); + assert_eq!(plan.worker_count, 1); + assert_eq!( + plan.actions, + [ + DepotFileAction::Directory { + path: "/target/bin".into() + }, + DepotFileAction::Regular { + path: "/target/bin/game".into(), + size: 10, + mode: 0o755 + }, + DepotFileAction::Symlink { + path: "/target/link".into(), + target: "bin/game".into() + } + ] + ); + assert_eq!( + plan.chunk_jobs, + [ChunkWriteJob { + file_idx: 1, + chunk_idx: 0 + }] + ); + } + + #[test] + fn depot_plan_rejects_cpp_error_cases() { + let mut manifest = ContentManifest { + files: vec![crate::content_manifest::FileMapping { + filename: "../escape".into(), + ..Default::default() + }], + ..Default::default() + }; + assert_eq!( + plan_depot_write(&manifest, &[0u8; 32], 1, "/target", 8) + .unwrap_err() + .error, + "write_depot: unsafe path '../escape'" + ); + manifest.files[0].filename = "ok".into(); + manifest.metadata.filenames_encrypted = true; + assert_eq!( + plan_depot_write(&manifest, &[0u8; 32], 1, "/target", 8) + .unwrap_err() + .error, + "write_depot: manifest filenames are still encrypted" + ); + manifest.metadata.filenames_encrypted = false; + assert_eq!( + plan_depot_write(&manifest, &[0u8; 31], 1, "/target", 8) + .unwrap_err() + .error, + "write_depot: bad depot key length" + ); + assert_eq!( + plan_depot_write(&manifest, &[0u8; 32], 0, "/target", 8) + .unwrap_err() + .error, + "write_depot: no CDN servers" + ); + } + + #[test] + fn worker_clamping_matches_cpp_limits() { + assert_eq!(clamp_worker_count(0, 10), 1); + assert_eq!(clamp_worker_count(128, 100), 64); + assert_eq!(clamp_worker_count(8, 3), 3); + assert_eq!(clamp_worker_count(8, 0), 0); + } + + #[test] + fn creates_layout_and_writes_chunks_at_offsets() { + let dir = temp_dir("depot_writer_layout"); + let manifest = ContentManifest { + metadata: crate::content_manifest::Metadata { + filenames_encrypted: false, + ..Default::default() + }, + files: vec![ + crate::content_manifest::FileMapping { + filename: "bin".into(), + flags: DEPOT_FILE_FLAG_DIRECTORY, + ..Default::default() + }, + crate::content_manifest::FileMapping { + filename: "bin/game.dat".into(), + size: 6, + chunks: vec![ChunkData { + offset: 2, + cb_original: 3, + crc: depot_adler_hash(b"abc"), + ..Default::default() + }], + ..Default::default() + }, + ], + signature: Vec::new(), + }; + let plan = plan_depot_write(&manifest, &[1u8; 32], 1, dir.to_str().unwrap(), 4).unwrap(); + let result = create_depot_layout(&plan); + assert!(result.ok(), "{}", result.error); + let file = dir.join("bin/game.dat"); + assert!(file.exists()); + + assert_eq!(write_chunk_at(&file, 2, b"abc").unwrap(), 3); + assert!(existing_chunk_matches(&file, &manifest.files[1].chunks[0])); + assert!(!existing_chunk_matches( + &file, + &ChunkData { + offset: 2, + cb_original: 3, + crc: 1, + ..Default::default() + } + )); + assert!(sync_file(&file)); + finalize_regular_file(&file, 6).unwrap(); + assert_eq!(fs::metadata(&file).unwrap().len(), 6); + let _ = fs::remove_dir_all(&dir); + } + + #[test] + fn process_and_write_chunk_decrypts_and_materializes_bytes() { + let dir = temp_dir("depot_writer_process_chunk"); + let file = dir.join("content.bin"); + let key = [9u8; 32]; + let payload = b"materialized chunk"; + let raw = encrypted_stored_zip_chunk(&key, payload); + let chunk = ChunkData { + offset: 4, + cb_original: payload.len() as u32, + crc: depot_adler_hash(payload), + ..Default::default() + }; + + assert_eq!( + process_and_write_chunk(&file, &chunk, &raw, &key).unwrap(), + payload.len() as u64 + ); + assert!(existing_chunk_matches(&file, &chunk)); + let mut bytes = Vec::new(); + File::open(&file).unwrap().read_to_end(&mut bytes).unwrap(); + assert_eq!(&bytes[4..], payload); + let _ = fs::remove_dir_all(&dir); + } + + #[test] + fn sequential_write_handles_layout_only_manifest() { + let dir = temp_dir("depot_writer_sequential_layout"); + let manifest = ContentManifest { + metadata: crate::content_manifest::Metadata { + filenames_encrypted: false, + depot_id: 7, + ..Default::default() + }, + files: vec![ + crate::content_manifest::FileMapping { + filename: "empty.bin".into(), + size: 5, + ..Default::default() + }, + crate::content_manifest::FileMapping { + filename: "folder".into(), + flags: DEPOT_FILE_FLAG_DIRECTORY, + ..Default::default() + }, + ], + signature: Vec::new(), + }; + let server = CContentServerDirectoryServerInfo { + host: "cdn.example".into(), + https_support: "mandatory".into(), + ..Default::default() + }; + let result = write_depot_sequential( + &manifest, + &[3u8; 32], + &CdnClient::new(""), + &[server], + dir.to_str().unwrap(), + DepotWriteOptions::default(), + ); + assert!(result.ok(), "{}", result.error); + assert_eq!(result.files_written, 1); + assert_eq!(result.bytes_written, 0); + assert_eq!(fs::metadata(dir.join("empty.bin")).unwrap().len(), 5); + assert!(dir.join("folder").is_dir()); + let _ = fs::remove_dir_all(&dir); + } + + fn temp_dir(name: &str) -> PathBuf { + std::env::temp_dir().join(format!( + "wnsteam_{name}_{}", + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_nanos() + )) + } + + fn encrypted_stored_zip_chunk(key: &[u8; 32], payload: &[u8]) -> Vec { + let mut zip = Vec::new(); + zip.extend_from_slice(&0x0403_4b50u32.to_le_bytes()); + zip.extend_from_slice(&20u16.to_le_bytes()); + zip.extend_from_slice(&0u16.to_le_bytes()); + zip.extend_from_slice(&0u16.to_le_bytes()); + zip.extend_from_slice(&0u16.to_le_bytes()); + zip.extend_from_slice(&0u16.to_le_bytes()); + zip.extend_from_slice(&0u32.to_le_bytes()); + zip.extend_from_slice(&(payload.len() as u32).to_le_bytes()); + zip.extend_from_slice(&(payload.len() as u32).to_le_bytes()); + zip.extend_from_slice(&1u16.to_le_bytes()); + zip.extend_from_slice(&0u16.to_le_bytes()); + zip.extend_from_slice(b"x"); + zip.extend_from_slice(payload); + + let iv = [6u8; AES_BLOCK_BYTES]; + let wrapped = aes256_ecb_encrypt_block(key, &iv).unwrap(); + let body = aes256_cbc_encrypt(key, &iv, &zip).unwrap(); + let mut out = wrapped.to_vec(); + out.extend_from_slice(&body); + out + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/emsg.rs b/app/src/main/cpp/wn-steam-client/rust/src/emsg.rs new file mode 100644 index 000000000..62e5c5c90 --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/emsg.rs @@ -0,0 +1,133 @@ +#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] +#[repr(transparent)] +pub struct EMsg(pub u32); + +impl EMsg { + pub const INVALID: Self = Self(0); + pub const MULTI: Self = Self(1); + pub const CHANNEL_ENCRYPT_REQUEST: Self = Self(1303); + pub const CHANNEL_ENCRYPT_RESPONSE: Self = Self(1304); + pub const CHANNEL_ENCRYPT_RESULT: Self = Self(1305); + pub const CLIENT_HELLO: Self = Self(9805); + pub const CLIENT_LOGON: Self = Self(5514); + pub const CLIENT_LOGON_RESPONSE: Self = Self(751); + pub const CLIENT_LOG_OFF: Self = Self(706); + pub const CLIENT_LOGGED_OFF: Self = Self(757); + pub const CLIENT_HEART_BEAT: Self = Self(703); + pub const CLIENT_GAMES_PLAYED_WITH_DATA_BLOB: Self = Self(5410); + pub const CLIENT_KICK_PLAYING_SESSION: Self = Self(9601); + pub const CLIENT_CHANGE_STATUS: Self = Self(716); + pub const CLIENT_REQUEST_FRIEND_DATA: Self = Self(815); + pub const CLIENT_SESSION_TOKEN: Self = Self(850); + pub const CLIENT_SERVER_UNAVAILABLE: Self = Self(5500); + pub const CLIENT_PERSONA_STATE: Self = Self(766); + pub const CLIENT_FRIENDS_LIST: Self = Self(767); + pub const CLIENT_PLAYING_SESSION_STATE: Self = Self(9600); + pub const CLIENT_ACCOUNT_INFO: Self = Self(768); + pub const CLIENT_EMAIL_ADDR_INFO: Self = Self(779); + pub const CLIENT_LICENSE_LIST: Self = Self(780); + pub const CLIENT_PICS_CHANGES_SINCE_REQUEST: Self = Self(8901); + pub const CLIENT_PICS_CHANGES_SINCE_RESPONSE: Self = Self(8902); + pub const CLIENT_PICS_PRODUCT_INFO_REQUEST: Self = Self(8903); + pub const CLIENT_PICS_PRODUCT_INFO_RESPONSE: Self = Self(8904); + pub const CLIENT_PICS_ACCESS_TOKEN_REQUEST: Self = Self(8905); + pub const CLIENT_PICS_ACCESS_TOKEN_RESPONSE: Self = Self(8906); + pub const CLIENT_GET_APP_OWNERSHIP_TICKET: Self = Self(857); + pub const CLIENT_GET_APP_OWNERSHIP_TICKET_RESPONSE: Self = Self(858); + pub const CLIENT_REQUEST_ENCRYPTED_APP_TICKET: Self = Self(5526); + pub const CLIENT_REQUEST_ENCRYPTED_APP_TICKET_RESPONSE: Self = Self(5527); + pub const CLIENT_GET_DEPOT_DECRYPTION_KEY: Self = Self(5438); + pub const CLIENT_GET_DEPOT_DECRYPTION_KEY_RESPONSE: Self = Self(5439); + pub const CLIENT_GET_USER_STATS: Self = Self(818); + pub const CLIENT_GET_USER_STATS_RESPONSE: Self = Self(819); + pub const CLIENT_STORE_USER_STATS_2: Self = Self(5466); + pub const SERVICE_METHOD: Self = Self(146); + pub const SERVICE_METHOD_CALL_FROM_CLIENT: Self = Self(151); + pub const SERVICE_METHOD_RESPONSE: Self = Self(147); + pub const SERVICE_METHOD_SEND_TO_CLIENT: Self = Self(152); + pub const SERVICE_METHOD_CALL_FROM_CLIENT_NON_AUTHED: Self = Self(9804); + pub const CLIENT_MMS_CREATE_LOBBY: Self = Self(6601); + pub const CLIENT_MMS_CREATE_LOBBY_RESPONSE: Self = Self(6602); + pub const CLIENT_MMS_JOIN_LOBBY: Self = Self(6603); + pub const CLIENT_MMS_JOIN_LOBBY_RESPONSE: Self = Self(6604); + pub const CLIENT_MMS_LEAVE_LOBBY: Self = Self(6605); + pub const CLIENT_MMS_LEAVE_LOBBY_RESPONSE: Self = Self(6606); + pub const CLIENT_MMS_GET_LOBBY_LIST: Self = Self(6607); + pub const CLIENT_MMS_GET_LOBBY_LIST_RESPONSE: Self = Self(6608); + pub const CLIENT_MMS_SET_LOBBY_DATA: Self = Self(6609); + pub const CLIENT_MMS_SET_LOBBY_DATA_RESPONSE: Self = Self(6610); + pub const CLIENT_MMS_GET_LOBBY_DATA: Self = Self(6611); + pub const CLIENT_MMS_LOBBY_DATA: Self = Self(6612); + pub const CLIENT_MMS_SEND_LOBBY_CHAT_MSG: Self = Self(6613); + pub const CLIENT_MMS_LOBBY_CHAT_MSG: Self = Self(6614); + pub const CLIENT_MMS_SET_LOBBY_OWNER: Self = Self(6615); + pub const CLIENT_MMS_SET_LOBBY_OWNER_RESPONSE: Self = Self(6616); + pub const CLIENT_MMS_SET_LOBBY_GAME_SERVER: Self = Self(6617); + pub const CLIENT_MMS_LOBBY_GAME_SERVER_SET: Self = Self(6618); + pub const CLIENT_MMS_USER_JOINED_LOBBY: Self = Self(6619); + pub const CLIENT_MMS_USER_LEFT_LOBBY: Self = Self(6620); + pub const CLIENT_MMS_INVITE_TO_LOBBY: Self = Self(6621); + pub const CLIENT_MMS_GET_LOBBY_STATUS: Self = Self(6626); + pub const CLIENT_MMS_GET_LOBBY_STATUS_RESPONSE: Self = Self(6627); +} + +pub const EMSG_PROTO_FLAG: u32 = 0x8000_0000; +pub const EMSG_MASK: u32 = 0x7fff_ffff; + +pub const fn has_proto_flag(raw: u32) -> bool { + (raw & EMSG_PROTO_FLAG) != 0 +} + +pub const fn strip_proto_flag(raw: u32) -> EMsg { + EMsg(raw & EMSG_MASK) +} + +pub const fn with_proto_flag(msg: EMsg) -> u32 { + msg.0 | EMSG_PROTO_FLAG +} + +#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] +#[repr(u32)] +pub enum EUniverse { + #[default] + Invalid = 0, + Public = 1, + Beta = 2, + Internal = 3, + Dev = 4, + Max = 5, +} + +impl EUniverse { + pub fn from_u32(v: u32) -> Option { + match v { + 1 => Some(Self::Public), + 2 => Some(Self::Beta), + 3 => Some(Self::Internal), + 4 => Some(Self::Dev), + 5 => Some(Self::Max), + _ => None, + } + } + + pub fn is_valid_universe(self) -> bool { + matches!(self, Self::Public | Self::Beta | Self::Internal | Self::Dev) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn universe_values_match_cpp_enum() { + assert_eq!(EUniverse::Invalid as u32, 0); + assert_eq!(EUniverse::Public as u32, 1); + assert_eq!(EUniverse::Beta as u32, 2); + assert_eq!(EUniverse::Internal as u32, 3); + assert_eq!(EUniverse::Dev as u32, 4); + assert_eq!(EUniverse::Max as u32, 5); + assert!(!EUniverse::Max.is_valid_universe()); + assert!(EUniverse::Public.is_valid_universe()); + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/encrypted_channel.rs b/app/src/main/cpp/wn-steam-client/rust/src/encrypted_channel.rs new file mode 100644 index 000000000..253301ed3 --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/encrypted_channel.rs @@ -0,0 +1,466 @@ +use crate::crypto::{ + aes256_cbc_decrypt, aes256_cbc_encrypt, aes256_ecb_decrypt_block, aes256_ecb_encrypt_block, + hmac_sha1, secure_random_bytes, AesBlock, SecureSessionKey, SessionKey, AES_BLOCK_BYTES, + HMAC_KEY_LENGTH, +}; +use crate::transport::{Transport, TransportDisconnectReason}; +use std::sync::atomic::{AtomicU8, Ordering}; +use std::sync::{Arc, Mutex}; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[repr(u8)] +pub enum ChannelState { + Disconnected, + Connected, + Challenged, + Encrypted, + Closing, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[repr(u8)] +pub enum ChannelDisconnectReason { + UserInitiated, + TransportError, + HandshakeProtocolError, + HandshakeFailed, + EnvelopeDecryptFailed, + HmacMismatch, +} + +type MessageCallback = Box; +type ConnectedCallback = Box; +type DisconnectedCallback = Box; + +pub struct EncryptedChannel { + inner: Arc, + transport: Mutex>, +} + +struct EncryptedChannelInner { + state: AtomicU8, + on_message: Mutex>, + on_connected: Mutex>, + on_disconnected: Mutex>, +} + +impl Default for EncryptedChannelInner { + fn default() -> Self { + Self { + state: AtomicU8::new(ChannelState::Disconnected as u8), + on_message: Mutex::new(None), + on_connected: Mutex::new(None), + on_disconnected: Mutex::new(None), + } + } +} + +impl EncryptedChannel { + pub fn new(mut transport: Box) -> Self { + let inner = Arc::new(EncryptedChannelInner::default()); + + let weak = Arc::downgrade(&inner); + transport.set_on_connected(Box::new(move || { + if let Some(inner) = weak.upgrade() { + inner + .state + .store(ChannelState::Encrypted as u8, Ordering::Release); + if let Some(callback) = inner + .on_connected + .lock() + .expect("encrypted channel callback poisoned") + .as_ref() + { + callback(); + } + } + })); + + let weak = Arc::downgrade(&inner); + transport.set_on_disconnected(Box::new(move |reason, detail| { + if let Some(inner) = weak.upgrade() { + inner + .state + .store(ChannelState::Disconnected as u8, Ordering::Release); + let reason = match reason { + TransportDisconnectReason::UserInitiated => { + ChannelDisconnectReason::UserInitiated + } + _ => ChannelDisconnectReason::TransportError, + }; + if let Some(callback) = inner + .on_disconnected + .lock() + .expect("encrypted channel callback poisoned") + .as_ref() + { + callback(reason, detail); + } + } + })); + + let weak = Arc::downgrade(&inner); + transport.set_on_message(Box::new(move |bytes| { + if let Some(inner) = weak.upgrade() { + if inner.state() != ChannelState::Encrypted { + return; + } + if let Some(callback) = inner + .on_message + .lock() + .expect("encrypted channel callback poisoned") + .as_ref() + { + callback(bytes); + } + } + })); + + Self { + inner, + transport: Mutex::new(transport), + } + } + + pub fn connect(&self, url: &str) -> bool { + if self + .inner + .state + .compare_exchange( + ChannelState::Disconnected as u8, + ChannelState::Connected as u8, + Ordering::AcqRel, + Ordering::Acquire, + ) + .is_err() + { + return false; + } + if !self + .transport + .lock() + .expect("encrypted channel transport poisoned") + .connect(url) + { + self.inner + .state + .store(ChannelState::Disconnected as u8, Ordering::Release); + return false; + } + true + } + + pub fn send(&self, plaintext: &[u8]) -> bool { + if self.state() != ChannelState::Encrypted { + return false; + } + self.transport + .lock() + .expect("encrypted channel transport poisoned") + .send(plaintext) + } + + pub fn disconnect(&self) { + if self.state() == ChannelState::Disconnected { + return; + } + self.inner + .state + .store(ChannelState::Closing as u8, Ordering::Release); + self.transport + .lock() + .expect("encrypted channel transport poisoned") + .disconnect(); + } + + pub fn state(&self) -> ChannelState { + self.inner.state() + } + + pub fn set_ca_bundle_path(&self, path: &str) { + self.transport + .lock() + .expect("encrypted channel transport poisoned") + .set_ca_bundle_path(path); + } + + pub fn set_on_message(&self, callback: F) + where + F: Fn(&[u8]) + Send + Sync + 'static, + { + *self + .inner + .on_message + .lock() + .expect("encrypted channel callback poisoned") = Some(Box::new(callback)); + } + + pub fn set_on_connected(&self, callback: F) + where + F: Fn() + Send + Sync + 'static, + { + *self + .inner + .on_connected + .lock() + .expect("encrypted channel callback poisoned") = Some(Box::new(callback)); + } + + pub fn set_on_disconnected(&self, callback: F) + where + F: Fn(ChannelDisconnectReason, &str) + Send + Sync + 'static, + { + *self + .inner + .on_disconnected + .lock() + .expect("encrypted channel callback poisoned") = Some(Box::new(callback)); + } +} + +impl Drop for EncryptedChannel { + fn drop(&mut self) { + self.disconnect(); + } +} + +impl EncryptedChannelInner { + fn state(&self) -> ChannelState { + match self.state.load(Ordering::Acquire) { + 1 => ChannelState::Connected, + 2 => ChannelState::Challenged, + 3 => ChannelState::Encrypted, + 4 => ChannelState::Closing, + _ => ChannelState::Disconnected, + } + } +} + +#[derive(Clone)] +pub struct EncryptedEnvelope { + session_key: SecureSessionKey, + hmac_key: [u8; HMAC_KEY_LENGTH], +} + +impl EncryptedEnvelope { + pub fn new(session_key: SessionKey) -> Self { + let mut hmac_key = [0u8; HMAC_KEY_LENGTH]; + hmac_key.copy_from_slice(&session_key[..HMAC_KEY_LENGTH]); + Self { + session_key: SecureSessionKey::new(session_key), + hmac_key, + } + } + + pub fn encrypt(&self, plaintext: &[u8]) -> Option> { + let mut random_iv = [0u8; AES_BLOCK_BYTES - 4]; + if !secure_random_bytes(&mut random_iv) { + return None; + } + self.encrypt_with_random_iv(plaintext, random_iv) + } + + fn encrypt_with_random_iv( + &self, + plaintext: &[u8], + random_iv: [u8; AES_BLOCK_BYTES - 4], + ) -> Option> { + let mut hmac_input = Vec::with_capacity(random_iv.len() + plaintext.len()); + hmac_input.extend_from_slice(&random_iv); + hmac_input.extend_from_slice(plaintext); + let hmac = hmac_sha1(&self.hmac_key, &hmac_input)?; + + let mut iv_plaintext = [0u8; AES_BLOCK_BYTES]; + iv_plaintext[..4].copy_from_slice(&hmac[..4]); + iv_plaintext[4..].copy_from_slice(&random_iv); + + let iv_ciphertext = aes256_ecb_encrypt_block(&self.session_key.bytes, &iv_plaintext)?; + let body = aes256_cbc_encrypt(&self.session_key.bytes, &iv_plaintext, plaintext)?; + + let mut out = Vec::with_capacity(AES_BLOCK_BYTES + body.len()); + out.extend_from_slice(&iv_ciphertext); + out.extend_from_slice(&body); + Some(out) + } + + pub fn decrypt(&self, wire: &[u8]) -> Option> { + if wire.len() < AES_BLOCK_BYTES * 2 { + return None; + } + let iv_ciphertext: AesBlock = wire[..AES_BLOCK_BYTES].try_into().ok()?; + let iv_plaintext = aes256_ecb_decrypt_block(&self.session_key.bytes, &iv_ciphertext)?; + let plaintext = aes256_cbc_decrypt( + &self.session_key.bytes, + &iv_plaintext, + &wire[AES_BLOCK_BYTES..], + )?; + + let mut hmac_input = Vec::with_capacity((AES_BLOCK_BYTES - 4) + plaintext.len()); + hmac_input.extend_from_slice(&iv_plaintext[4..]); + hmac_input.extend_from_slice(&plaintext); + let expected = hmac_sha1(&self.hmac_key, &hmac_input)?; + ct_equal(&iv_plaintext[..4], &expected[..4]).then_some(plaintext) + } +} + +fn ct_equal(a: &[u8], b: &[u8]) -> bool { + if a.len() != b.len() { + return false; + } + let mut diff = 0u8; + for (x, y) in a.iter().zip(b.iter()) { + diff |= x ^ y; + } + diff == 0 +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::transport::TransportState; + use std::sync::atomic::{AtomicBool, Ordering}; + use std::sync::mpsc; + + #[test] + fn envelope_roundtrips_with_fixed_iv() { + let key = [7u8; 32]; + let env = EncryptedEnvelope::new(key); + let encrypted = env + .encrypt_with_random_iv(b"hello steam", [3u8; AES_BLOCK_BYTES - 4]) + .unwrap(); + assert_ne!(encrypted, b"hello steam"); + assert_eq!(env.decrypt(&encrypted).unwrap(), b"hello steam"); + } + + #[test] + fn envelope_rejects_hmac_mismatch() { + let env = EncryptedEnvelope::new([7u8; 32]); + let mut encrypted = env + .encrypt_with_random_iv(b"hello steam", [3u8; AES_BLOCK_BYTES - 4]) + .unwrap(); + let last = encrypted.last_mut().unwrap(); + *last ^= 0x55; + assert!(env.decrypt(&encrypted).is_none()); + } + + #[test] + fn websocket_channel_skips_app_layer_handshake() { + let shared = Arc::new(MockTransportState::default()); + let channel = EncryptedChannel::new(Box::new(MockTransport { + shared: Arc::clone(&shared), + })); + let connected = Arc::new(AtomicBool::new(false)); + let connected_cb = Arc::clone(&connected); + let (tx, rx) = mpsc::channel(); + channel.set_on_connected(move || { + connected_cb.store(true, Ordering::SeqCst); + }); + channel.set_on_message(move |bytes| { + tx.send(bytes.to_vec()).unwrap(); + }); + + assert!(channel.connect("wss://cm.example.com:443/cmsocket/")); + assert_eq!(channel.state(), ChannelState::Connected); + shared.fire_connected(); + assert_eq!(channel.state(), ChannelState::Encrypted); + assert!(connected.load(Ordering::SeqCst)); + + assert!(channel.send(b"client hello")); + assert_eq!(shared.sent.lock().unwrap()[0], b"client hello"); + + shared.fire_message(b"server frame"); + assert_eq!(rx.recv().unwrap(), b"server frame"); + } + + #[test] + fn channel_maps_transport_disconnect_reasons() { + let shared = Arc::new(MockTransportState::default()); + let channel = EncryptedChannel::new(Box::new(MockTransport { + shared: Arc::clone(&shared), + })); + let (tx, rx) = mpsc::channel(); + channel.set_on_disconnected(move |reason, detail| { + tx.send((reason, detail.to_string())).unwrap(); + }); + assert!(channel.connect("wss://cm.example.com:443/cmsocket/")); + shared.fire_connected(); + shared.fire_disconnected(TransportDisconnectReason::NetworkError, "net down"); + assert_eq!( + rx.recv().unwrap(), + ( + ChannelDisconnectReason::TransportError, + "net down".to_string() + ) + ); + assert_eq!(channel.state(), ChannelState::Disconnected); + } + + #[derive(Default)] + struct MockTransportState { + connected: Mutex>, + disconnected: Mutex>, + message: Mutex>, + sent: Mutex>>, + ca_bundle_path: Mutex, + } + + impl MockTransportState { + fn fire_connected(&self) { + if let Some(callback) = self.connected.lock().unwrap().as_ref() { + callback(); + } + } + + fn fire_disconnected(&self, reason: TransportDisconnectReason, detail: &str) { + if let Some(callback) = self.disconnected.lock().unwrap().as_ref() { + callback(reason, detail); + } + } + + fn fire_message(&self, bytes: &[u8]) { + if let Some(callback) = self.message.lock().unwrap().as_ref() { + callback(bytes); + } + } + } + + struct MockTransport { + shared: Arc, + } + + impl Transport for MockTransport { + fn connect(&mut self, _url: &str) -> bool { + true + } + + fn send(&mut self, data: &[u8]) -> bool { + self.shared.sent.lock().unwrap().push(data.to_vec()); + true + } + + fn disconnect(&mut self) {} + + fn state(&self) -> TransportState { + TransportState::Connected + } + + fn set_on_message(&mut self, cb: Box) { + *self.shared.message.lock().unwrap() = Some(cb); + } + + fn set_on_connected(&mut self, cb: Box) { + *self.shared.connected.lock().unwrap() = Some(cb); + } + + fn set_on_disconnected( + &mut self, + cb: Box, + ) { + *self.shared.disconnected.lock().unwrap() = Some(cb); + } + + fn set_ca_bundle_path(&mut self, path: &str) { + *self.shared.ca_bundle_path.lock().unwrap() = path.to_string(); + } + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/handshake_messages.rs b/app/src/main/cpp/wn-steam-client/rust/src/handshake_messages.rs new file mode 100644 index 000000000..9df1b0416 --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/handshake_messages.rs @@ -0,0 +1,192 @@ +use crate::emsg::{has_proto_flag, strip_proto_flag, EMsg, EUniverse}; +use crate::wire_format; + +pub const INVALID_JOB_ID: u64 = u64::MAX; +pub const MSG_HDR_BYTES: usize = 20; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct MsgHdr { + pub msg: EMsg, + pub target_job_id: u64, + pub source_job_id: u64, +} + +impl Default for MsgHdr { + fn default() -> Self { + Self { + msg: EMsg::INVALID, + target_job_id: INVALID_JOB_ID, + source_job_id: INVALID_JOB_ID, + } + } +} + +impl MsgHdr { + pub fn serialize(&self, out: &mut Vec) { + let mut writer = wire_format::Writer::new(out); + writer.u32(self.msg.0); + writer.u64(self.target_job_id); + writer.u64(self.source_job_id); + } + + pub fn deserialize(input: &[u8]) -> Option<(Self, usize)> { + if input.len() < MSG_HDR_BYTES { + return None; + } + let mut reader = wire_format::Reader::new(&input[..MSG_HDR_BYTES]); + let raw_msg = reader.u32(); + if has_proto_flag(raw_msg) { + return None; + } + let hdr = Self { + msg: strip_proto_flag(raw_msg), + target_job_id: reader.u64(), + source_job_id: reader.u64(), + }; + reader.ok().then_some((hdr, MSG_HDR_BYTES)) + } +} + +pub const CHANNEL_ENCRYPT_PROTOCOL_VERSION: u32 = 1; +pub const CHANNEL_ENCRYPT_REQUEST_FIXED_BODY: usize = 8; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct MsgChannelEncryptRequest { + pub protocol_version: u32, + pub universe: EUniverse, + pub challenge: Vec, +} + +impl Default for MsgChannelEncryptRequest { + fn default() -> Self { + Self { + protocol_version: CHANNEL_ENCRYPT_PROTOCOL_VERSION, + universe: EUniverse::Invalid, + challenge: Vec::new(), + } + } +} + +impl MsgChannelEncryptRequest { + pub fn deserialize_body(body: &[u8]) -> Option { + if body.len() < CHANNEL_ENCRYPT_REQUEST_FIXED_BODY { + return None; + } + let mut reader = wire_format::Reader::new(body); + let protocol_version = reader.u32(); + let universe = EUniverse::from_u32(reader.u32())?; + if !reader.ok() { + return None; + } + let challenge = body[CHANNEL_ENCRYPT_REQUEST_FIXED_BODY..].to_vec(); + Some(Self { + protocol_version, + universe, + challenge, + }) + } +} + +pub const RSA_1024_CIPHER_BYTES: usize = 128; +pub const CHANNEL_ENCRYPT_RESPONSE_BODY_BYTES: usize = 144; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct MsgChannelEncryptResponse { + pub protocol_version: u32, + pub key_size: u32, + pub encrypted_handshake_blob: [u8; RSA_1024_CIPHER_BYTES], + pub key_crc: u32, + pub unknown_zero: u32, +} + +impl Default for MsgChannelEncryptResponse { + fn default() -> Self { + Self { + protocol_version: CHANNEL_ENCRYPT_PROTOCOL_VERSION, + key_size: RSA_1024_CIPHER_BYTES as u32, + encrypted_handshake_blob: [0; RSA_1024_CIPHER_BYTES], + key_crc: 0, + unknown_zero: 0, + } + } +} + +impl MsgChannelEncryptResponse { + pub fn serialize_body(&self, out: &mut Vec) { + let mut writer = wire_format::Writer::new(out); + writer.u32(self.protocol_version); + writer.u32(self.key_size); + writer.bytes(&self.encrypted_handshake_blob); + writer.u32(self.key_crc); + writer.u32(self.unknown_zero); + } +} + +pub const CHANNEL_ENCRYPT_RESULT_BODY_BYTES: usize = 4; + +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct MsgChannelEncryptResult { + pub result: u32, +} + +impl MsgChannelEncryptResult { + pub fn deserialize_body(body: &[u8]) -> Option { + if body.len() < CHANNEL_ENCRYPT_RESULT_BODY_BYTES { + return None; + } + let mut reader = wire_format::Reader::new(body); + let result = reader.u32(); + reader.ok().then_some(Self { result }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::emsg::with_proto_flag; + + #[test] + fn msg_hdr_roundtrips_and_rejects_proto_flag() { + let hdr = MsgHdr { + msg: EMsg::CHANNEL_ENCRYPT_REQUEST, + target_job_id: 7, + source_job_id: 9, + }; + let mut bytes = Vec::new(); + hdr.serialize(&mut bytes); + assert_eq!(bytes.len(), MSG_HDR_BYTES); + assert_eq!(MsgHdr::deserialize(&bytes), Some((hdr, MSG_HDR_BYTES))); + + bytes[..4].copy_from_slice(&with_proto_flag(EMsg::CHANNEL_ENCRYPT_REQUEST).to_le_bytes()); + assert_eq!(MsgHdr::deserialize(&bytes), None); + } + + #[test] + fn channel_encrypt_request_parses_challenge() { + let mut body = Vec::new(); + let mut writer = wire_format::Writer::new(&mut body); + writer.u32(1); + writer.u32(EUniverse::Public as u32); + writer.bytes(&[0xaa; 16]); + let msg = MsgChannelEncryptRequest::deserialize_body(&body).unwrap(); + assert_eq!(msg.protocol_version, 1); + assert_eq!(msg.universe, EUniverse::Public); + assert_eq!(msg.challenge, vec![0xaa; 16]); + } + + #[test] + fn channel_encrypt_response_is_144_bytes() { + let msg = MsgChannelEncryptResponse { + key_crc: 0xfeed_beef, + ..Default::default() + }; + let mut body = Vec::new(); + msg.serialize_body(&mut body); + assert_eq!(body.len(), CHANNEL_ENCRYPT_RESPONSE_BODY_BYTES); + assert_eq!(&body[0..4], &1u32.to_le_bytes()); + assert_eq!( + &body[8 + RSA_1024_CIPHER_BYTES..12 + RSA_1024_CIPHER_BYTES], + &0xfeed_beefu32.to_le_bytes() + ); + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/heartbeat.rs b/app/src/main/cpp/wn-steam-client/rust/src/heartbeat.rs new file mode 100644 index 000000000..681d8adf8 --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/heartbeat.rs @@ -0,0 +1,126 @@ +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, Condvar, Mutex}; +use std::thread::{self, JoinHandle}; +use std::time::Duration; +use std::{panic, panic::AssertUnwindSafe}; + +#[derive(Default)] +pub struct Heartbeat { + running: Arc, + stop: Arc<(Mutex, Condvar)>, + worker: Option>, +} + +impl Heartbeat { + pub fn start(&mut self, interval: Duration, cb: F) -> bool + where + F: Fn() + Send + 'static, + { + if interval.is_zero() { + return false; + } + self.stop(); + self.running.store(true, Ordering::Relaxed); + { + let (mu, _) = &*self.stop; + *mu.lock().expect("heartbeat poisoned") = false; + } + let running = Arc::clone(&self.running); + let stop = Arc::clone(&self.stop); + self.worker = Some(thread::spawn(move || run_loop(interval, cb, running, stop))); + true + } + + pub fn stop(&mut self) { + if !self.running.load(Ordering::Relaxed) { + return; + } + let (mu, cv) = &*self.stop; + *mu.lock().expect("heartbeat poisoned") = true; + cv.notify_all(); + if let Some(worker) = self.worker.take() { + let _ = worker.join(); + } + self.running.store(false, Ordering::Relaxed); + } + + pub fn running(&self) -> bool { + self.running.load(Ordering::Relaxed) + } +} + +impl Drop for Heartbeat { + fn drop(&mut self) { + self.stop(); + } +} + +fn run_loop( + interval: Duration, + cb: F, + running: Arc, + stop: Arc<(Mutex, Condvar)>, +) where + F: Fn() + Send + 'static, +{ + let (mu, cv) = &*stop; + loop { + let guard = mu.lock().expect("heartbeat poisoned"); + let (guard, _) = cv + .wait_timeout_while(guard, interval, |stop_requested| !*stop_requested) + .expect("heartbeat poisoned"); + if *guard { + return; + } + drop(guard); + let _ = panic::catch_unwind(AssertUnwindSafe(&cb)); + if !running.load(Ordering::Relaxed) { + return; + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::atomic::{AtomicUsize, Ordering}; + + #[test] + fn rejects_zero_interval() { + let mut hb = Heartbeat::default(); + assert!(!hb.start(Duration::ZERO, || {})); + assert!(!hb.running()); + } + + #[test] + fn ticks_and_stops() { + let ticks = Arc::new(AtomicUsize::new(0)); + let tick_copy = Arc::clone(&ticks); + let mut hb = Heartbeat::default(); + assert!(hb.start(Duration::from_millis(10), move || { + tick_copy.fetch_add(1, Ordering::Relaxed); + })); + thread::sleep(Duration::from_millis(35)); + hb.stop(); + assert!(ticks.load(Ordering::Relaxed) > 0); + assert!(!hb.running()); + } + + #[test] + fn callback_panic_does_not_stop_worker() { + let ticks = Arc::new(AtomicUsize::new(0)); + let tick_copy = Arc::clone(&ticks); + let (tx, rx) = std::sync::mpsc::channel(); + let mut hb = Heartbeat::default(); + assert!(hb.start(Duration::from_millis(10), move || { + let n = tick_copy.fetch_add(1, Ordering::Relaxed); + if n == 0 { + panic!("first heartbeat panic"); + } + tx.send(()).unwrap(); + })); + rx.recv_timeout(Duration::from_secs(1)).unwrap(); + hb.stop(); + assert!(ticks.load(Ordering::Relaxed) > 1); + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/jni.rs b/app/src/main/cpp/wn-steam-client/rust/src/jni.rs new file mode 100644 index 000000000..e2e470140 --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/jni.rs @@ -0,0 +1,4549 @@ +use crate::auth_session::{ + apply_account_name_fallback, auth_result_from_poll, build_credentials_begin_request, + build_guard_code_request, build_password_rsa_request, build_poll_request, + build_qr_begin_request, choose_guard_confirmation, + pending_credentials_from_begin_response, pending_qr_from_begin_response, sleep_slices, + take_qr_remote_interaction, AuthSessionResult, CredentialsAuthConfig, QrAuthConfig, +}; +use crate::cm_bridge; +use crate::cm_client::{CMClientCore, ClientState, OutboundProtoMessage, OutboundServiceCall}; +use crate::cm_runtime::CMClientRuntime; +use crate::emsg::EMsg; +use crate::encrypted_channel::{ChannelDisconnectReason, EncryptedChannel}; +use crate::version; +use crate::wine_bridge::{WineBridge, WineBridgeConfig}; +use crate::ws_connection::WsConnection; +use jni::objects::{ + GlobalRef, JByteArray, JClass, JIntArray, JLongArray, JObject, JString, JValue, +}; +use jni::sys::{ + jboolean, jbyteArray, jint, jlong, jlongArray, jstring, JNI_FALSE, JNI_TRUE, JNI_VERSION_1_6, +}; +use jni::{JNIEnv, JavaVM}; +use serde_json::json; +#[cfg(target_os = "android")] +use std::ffi::CString; +use std::ptr; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::mpsc; +use std::sync::OnceLock; +use std::sync::{Arc, Mutex}; +use std::thread; +use std::time::Duration; + +#[cfg(target_os = "android")] +#[link(name = "log")] +unsafe extern "C" { + fn __android_log_write(prio: i32, tag: *const i8, text: *const i8) -> i32; +} + +fn android_log(tag: &str, message: &str) { + #[cfg(target_os = "android")] + { + let Ok(tag) = CString::new(tag) else { + return; + }; + let sanitized = message.replace('\0', " "); + let Ok(text) = CString::new(sanitized) else { + return; + }; + unsafe { + let _ = __android_log_write(4, tag.as_ptr().cast(), text.as_ptr().cast()); + } + } + #[cfg(not(target_os = "android"))] + { + let _ = (tag, message); + } +} + +fn qr_log(message: &str) { + android_log("WnSteamQr", message); +} + +struct WnConnectionHandle { + channel: EncryptedChannel, + observer: Mutex>, +} + +struct WnSteamSessionHandle { + core: Arc, + ca_bundle_path: String, + auto_populate_library: bool, + wine_bridge: WineBridge, + runtime: Mutex>>, + login_cancel: Mutex>>, + download_cancel: Arc, + state_observer: Arc>>, + library_observer: Arc>>, + library_observer_installed: Mutex, +} + +impl WnConnectionHandle { + fn new() -> Self { + Self { + channel: EncryptedChannel::new(Box::new(WsConnection::new())), + observer: Mutex::new(None), + } + } +} + +impl WnSteamSessionHandle { + fn new() -> Self { + let core = Arc::new(CMClientCore::default()); + cm_bridge::set_active_core(Arc::clone(&core)); + Self { + core, + ca_bundle_path: String::new(), + auto_populate_library: true, + wine_bridge: WineBridge::default(), + runtime: Mutex::new(None), + login_cancel: Mutex::new(None), + download_cancel: Arc::new(AtomicBool::new(false)), + state_observer: Arc::new(Mutex::new(None)), + library_observer: Arc::new(Mutex::new(None)), + library_observer_installed: Mutex::new(false), + } + } + + fn enqueue_proto(&self, message: Option) -> bool { + let Some(message) = message else { + return false; + }; + self.enqueue_wire(message.wire) + } + + fn enqueue_wire(&self, wire: Vec) -> bool { + if wire.is_empty() { + return false; + } + if !self.core.enqueue_wire(wire) { + return false; + } + if let Some(runtime) = self + .runtime + .lock() + .expect("session runtime poisoned") + .as_ref() + { + runtime.flush_outbound(); + } + true + } + + fn runtime(&self) -> Arc { + let mut slot = self.runtime.lock().expect("session runtime poisoned"); + if let Some(runtime) = slot.as_ref() { + return Arc::clone(runtime); + } + let runtime = CMClientRuntime::new(Arc::clone(&self.core), Box::new(WsConnection::new())); + if !self.ca_bundle_path.is_empty() { + runtime.set_ca_bundle_path(&self.ca_bundle_path); + } + let state_observer = Arc::clone(&self.state_observer); + runtime.set_on_state(move |state| { + dispatch_state_observer(&state_observer, state); + }); + let client_message_observer = Arc::clone(&self.state_observer); + runtime.set_on_client_message(move |emsg, header, body| { + dispatch_client_message_observer(&client_message_observer, emsg, header.eresult, body); + }); + self.install_library_observer(); + *slot = Some(Arc::clone(&runtime)); + runtime + } + + fn install_library_observer(&self) { + let mut installed = self + .library_observer_installed + .lock() + .expect("library observer install flag poisoned"); + if *installed { + return; + } + let observer = Arc::clone(&self.library_observer); + self.core.library().set_observer(move || { + dispatch_library_observer(&observer); + }); + *installed = true; + } + + fn connected_runtime(&self) -> Option> { + let runtime = self + .runtime + .lock() + .expect("session runtime poisoned") + .as_ref() + .cloned()?; + matches!( + self.core.state(), + ClientState::Connected | ClientState::LoggedOn + ) + .then_some(runtime) + } + + fn begin_login_cancel(&self) -> Arc { + let cancel = Arc::new(AtomicBool::new(false)); + let mut slot = self.login_cancel.lock().expect("session login poisoned"); + if let Some(previous) = slot.replace(Arc::clone(&cancel)) { + previous.store(true, Ordering::Relaxed); + } + cancel + } + + fn cancel_login(&self) { + if let Some(cancel) = self + .login_cancel + .lock() + .expect("session login poisoned") + .take() + { + cancel.store(true, Ordering::Relaxed); + } + } +} + +fn to_handle(handle: Box) -> jlong { + Box::into_raw(handle) as isize as jlong +} + +fn to_session_handle(handle: Box) -> jlong { + Box::into_raw(handle) as isize as jlong +} + +unsafe fn from_handle_mut(handle: jlong) -> Option<&'static mut WnConnectionHandle> { + if handle == 0 { + return None; + } + unsafe { (handle as *mut WnConnectionHandle).as_mut() } +} + +unsafe fn from_session_handle_mut(handle: jlong) -> Option<&'static mut WnSteamSessionHandle> { + if handle == 0 { + return None; + } + unsafe { (handle as *mut WnSteamSessionHandle).as_mut() } +} + +unsafe fn drop_handle(handle: jlong) { + if handle != 0 { + unsafe { + drop(Box::from_raw(handle as *mut WnConnectionHandle)); + } + } +} + +unsafe fn drop_session_handle(handle: jlong) { + if handle != 0 { + unsafe { + drop(Box::from_raw(handle as *mut WnSteamSessionHandle)); + } + cm_bridge::clear_active_core(); + } +} + +static JVM: OnceLock = OnceLock::new(); +static AUTH_RESULT_CLASS: OnceLock = OnceLock::new(); + +fn ensure_auth_result_class(env: &mut JNIEnv) -> Option<&'static GlobalRef> { + if let Some(class) = AUTH_RESULT_CLASS.get() { + return Some(class); + } + let Ok(class) = env.find_class("com/winlator/cmod/feature/stores/steam/wnsteam/WnAuthResult") + else { + clear_pending_exception(env); + return None; + }; + let Ok(class) = env.new_global_ref(class) else { + clear_pending_exception(env); + return None; + }; + let _ = AUTH_RESULT_CLASS.set(class); + AUTH_RESULT_CLASS.get() +} + +fn auth_result_jclass<'a>(class: &'a GlobalRef) -> JClass<'a> { + unsafe { JClass::from_raw(class.as_obj().as_raw() as _) } +} + +fn new_string_or_null(env: &mut JNIEnv, value: &str) -> jstring { + env.new_string(value) + .map(|s| s.into_raw()) + .unwrap_or(ptr::null_mut()) +} + +#[no_mangle] +pub extern "system" fn JNI_OnLoad(vm: JavaVM, _reserved: *mut std::ffi::c_void) -> jint { + let _ = JVM.set(vm); + JNI_VERSION_1_6 +} + +fn dispatch_connection_connected(observer: &GlobalRef) { + let Some(vm) = JVM.get() else { + return; + }; + let Ok(mut env) = vm.attach_current_thread_as_daemon() else { + return; + }; + let _ = env.call_method(observer.as_obj(), "onConnected", "()V", &[]); + clear_pending_exception(&mut env); +} + +fn dispatch_connection_disconnected( + observer: &GlobalRef, + reason: ChannelDisconnectReason, + detail: &str, +) { + let Some(vm) = JVM.get() else { + return; + }; + let Ok(mut env) = vm.attach_current_thread_as_daemon() else { + return; + }; + let Ok(detail) = env.new_string(detail) else { + clear_pending_exception(&mut env); + return; + }; + let detail_obj = JObject::from(detail); + let _ = env.call_method( + observer.as_obj(), + "onDisconnected", + "(ILjava/lang/String;)V", + &[JValue::Int(reason as jint), JValue::Object(&detail_obj)], + ); + clear_pending_exception(&mut env); +} + +fn dispatch_state_observer(observer: &Arc>>, state: ClientState) { + let cloned = observer + .lock() + .expect("session state observer poisoned") + .as_ref() + .cloned(); + let Some(observer) = cloned else { + return; + }; + let Some(vm) = JVM.get() else { + return; + }; + let Ok(mut env) = vm.attach_current_thread_as_daemon() else { + return; + }; + let _ = env.call_method( + observer.as_obj(), + "onStateChanged", + "(I)V", + &[JValue::Int(state as jint)], + ); + clear_pending_exception(&mut env); +} + +fn dispatch_client_message_observer( + observer: &Arc>>, + emsg: EMsg, + eresult: i32, + body: &[u8], +) { + let cloned = observer + .lock() + .expect("session state observer poisoned") + .as_ref() + .cloned(); + let Some(observer) = cloned else { + return; + }; + let Some(vm) = JVM.get() else { + return; + }; + let Ok(mut env) = vm.attach_current_thread_as_daemon() else { + return; + }; + let Ok(array) = env.byte_array_from_slice(body) else { + clear_pending_exception(&mut env); + return; + }; + let array_obj = JObject::from(array); + let _ = env.call_method( + observer.as_obj(), + "onClientMessage", + "(II[B)V", + &[ + JValue::Int(emsg.0 as jint), + JValue::Int(eresult), + JValue::Object(&array_obj), + ], + ); + clear_pending_exception(&mut env); +} + +fn dispatch_library_observer(observer: &Arc>>) { + let cloned = observer + .lock() + .expect("session library observer poisoned") + .as_ref() + .cloned(); + let Some(observer) = cloned else { + return; + }; + let Some(vm) = JVM.get() else { + return; + }; + let Ok(mut env) = vm.attach_current_thread_as_daemon() else { + return; + }; + let _ = env.call_method(observer.as_obj(), "onLibraryChanged", "()V", &[]); + clear_pending_exception(&mut env); +} + +fn dispatch_connection_message(observer: &GlobalRef, bytes: &[u8]) { + let Some(vm) = JVM.get() else { + return; + }; + let Ok(mut env) = vm.attach_current_thread_as_daemon() else { + return; + }; + let Ok(array) = env.byte_array_from_slice(bytes) else { + clear_pending_exception(&mut env); + return; + }; + let array_obj = JObject::from(array); + let _ = env.call_method( + observer.as_obj(), + "onMessage", + "([B)V", + &[JValue::Object(&array_obj)], + ); + clear_pending_exception(&mut env); +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamClient_nativeVersion( + mut env: JNIEnv, + _class: JClass, +) -> jstring { + new_string_or_null(&mut env, version::version().string) +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnConnection_nativeCreate( + _env: JNIEnv, + _class: JClass, +) -> jlong { + to_handle(Box::new(WnConnectionHandle::new())) +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnConnection_nativeDestroy( + _env: JNIEnv, + _class: JClass, + handle: jlong, +) { + unsafe { drop_handle(handle) }; +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnConnection_nativeSetCaBundlePath( + mut env: JNIEnv, + _class: JClass, + handle: jlong, + path: JString, +) { + let Some(handle) = (unsafe { from_handle_mut(handle) }) else { + return; + }; + let Ok(path) = env.get_string(&path) else { + return; + }; + handle.channel.set_ca_bundle_path(&path.to_string_lossy()); +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnConnection_nativeSetObserver( + env: JNIEnv, + _class: JClass, + handle: jlong, + observer: JObject, +) { + let Some(handle) = (unsafe { from_handle_mut(handle) }) else { + return; + }; + let observer = if observer.is_null() { + None + } else { + env.new_global_ref(observer).ok() + }; + *handle + .observer + .lock() + .expect("connection observer poisoned") = observer.clone(); + + let Some(observer) = observer else { + handle.channel.set_on_connected(|| {}); + handle.channel.set_on_disconnected(|_, _| {}); + handle.channel.set_on_message(|_| {}); + return; + }; + + let connected_observer = observer.clone(); + handle.channel.set_on_connected(move || { + dispatch_connection_connected(&connected_observer); + }); + let disconnected_observer = observer.clone(); + handle.channel.set_on_disconnected(move |reason, detail| { + dispatch_connection_disconnected(&disconnected_observer, reason, detail); + }); + handle.channel.set_on_message(move |bytes| { + dispatch_connection_message(&observer, bytes); + }); +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnConnection_nativeConnect( + mut env: JNIEnv, + _class: JClass, + handle: jlong, + url: JString, +) -> jboolean { + let Some(handle) = (unsafe { from_handle_mut(handle) }) else { + return JNI_FALSE; + }; + let Ok(url) = env.get_string(&url) else { + return JNI_FALSE; + }; + if handle.channel.connect(&url.to_string_lossy()) { + JNI_TRUE + } else { + JNI_FALSE + } +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnConnection_nativeDisconnect( + _env: JNIEnv, + _class: JClass, + handle: jlong, +) { + if let Some(handle) = unsafe { from_handle_mut(handle) } { + handle.channel.disconnect(); + } +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnConnection_nativeSend( + env: JNIEnv, + _class: JClass, + handle: jlong, + data: JByteArray, +) -> jboolean { + let Some(handle) = (unsafe { from_handle_mut(handle) }) else { + return JNI_FALSE; + }; + let Ok(bytes) = env.convert_byte_array(data) else { + return JNI_FALSE; + }; + if handle.channel.send(&bytes) { + JNI_TRUE + } else { + JNI_FALSE + } +} + +pub fn empty_byte_array(env: JNIEnv) -> jbyteArray { + env.new_byte_array(0) + .map(|arr| arr.into_raw()) + .unwrap_or(ptr::null_mut()) +} + +fn byte_array_or_null(env: &JNIEnv, bytes: &[u8]) -> jbyteArray { + env.byte_array_from_slice(bytes) + .map(|arr| arr.into_raw()) + .unwrap_or(ptr::null_mut()) +} + +fn jstring_to_string(env: &mut JNIEnv, value: &JString) -> Option { + env.get_string(value) + .ok() + .map(|s| s.to_string_lossy().into_owned()) +} + +fn int_array_to_u32_vec(env: &JNIEnv, array: &JIntArray) -> Vec { + let len = env.get_array_length(array).unwrap_or(0); + if len <= 0 { + return Vec::new(); + } + let mut values = vec![0i32; len as usize]; + if env.get_int_array_region(array, 0, &mut values).is_err() { + return Vec::new(); + } + values.into_iter().map(|value| value as u32).collect() +} + +fn long_array_to_u64_vec(env: &JNIEnv, array: &JLongArray) -> Vec { + let len = env.get_array_length(array).unwrap_or(0); + if len <= 0 { + return Vec::new(); + } + let mut values = vec![0i64; len as usize]; + if env.get_long_array_region(array, 0, &mut values).is_err() { + return Vec::new(); + } + values.into_iter().map(|value| value as u64).collect() +} + +// One value per line (0 for blank/garbage), so callers that pair these lists +// with another by index never have a line silently dropped and shifted. +fn parse_u32_lines(value: &str) -> Vec { + value + .lines() + .map(|line| parse_u32_token(line).unwrap_or(0)) + .collect() +} + +fn parse_u64_lines(value: &str) -> Vec { + value + .lines() + .map(|line| parse_u64_token(line).unwrap_or(0)) + .collect() +} + +// Kotlin renders u64/u32 ids+tokens from signed Long/Int, so high-bit values +// arrive negative (e.g. "-2956503589389641226"). Parse signed-or-unsigned and +// wrap (two's complement) like the C++ stoull this replaced; a strict +// parse::() rejects the minus and would lose the token. +fn parse_u32_token(line: &str) -> Option { + let token = line.trim(); + if token.is_empty() { + return None; + } + token + .parse::() + .ok() + .or_else(|| token.parse::().ok().map(|v| v as u32)) +} + +fn parse_u64_token(line: &str) -> Option { + let token = line.trim(); + if token.is_empty() { + return None; + } + token + .parse::() + .ok() + .or_else(|| token.parse::().ok().map(|v| v as u64)) +} + +fn split_nonempty_lines(value: &str) -> Vec { + value + .lines() + .filter(|line| !line.is_empty()) + .map(ToOwned::to_owned) + .collect() +} + +fn kvnode_to_json_value(node: &crate::vdf::KVNode) -> serde_json::Value { + if node.is_object() { + let mut object = serde_json::Map::new(); + for child in &node.children { + object.insert(child.name.clone(), kvnode_to_json_value(child)); + } + serde_json::Value::Object(object) + } else { + json!(node.as_string("")) + } +} + +fn decode_hex(value: &str) -> Option> { + let compact = value.trim(); + if !compact.len().is_multiple_of(2) { + return None; + } + let mut out = Vec::with_capacity(compact.len() / 2); + let bytes = compact.as_bytes(); + for pair in bytes.chunks_exact(2) { + let hi = (pair[0] as char).to_digit(16)?; + let lo = (pair[1] as char).to_digit(16)?; + out.push(((hi << 4) | lo) as u8); + } + Some(out) +} + +fn clear_pending_exception(env: &mut JNIEnv) { + if env.exception_check().unwrap_or(false) { + let _ = env.exception_clear(); + } +} + +fn call_auth_result_failure(env: &mut JNIEnv, callback: &JObject, code: jint, message: &str) { + if callback.is_null() { + return; + } + let Some(auth_result_class) = ensure_auth_result_class(env) else { + return; + }; + let Ok(error) = env.new_string(message) else { + clear_pending_exception(env); + return; + }; + let Ok(empty) = env.new_string("") else { + clear_pending_exception(env); + return; + }; + let error_obj = JObject::from(error); + let empty_obj = JObject::from(empty); + let auth_result_class = auth_result_jclass(auth_result_class); + let Ok(result) = env.new_object( + auth_result_class, + "(ZILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;JZLjava/lang/String;)V", + &[ + JValue::Bool(JNI_FALSE), + JValue::Int(code), + JValue::Object(&error_obj), + JValue::Object(&empty_obj), + JValue::Object(&empty_obj), + JValue::Object(&empty_obj), + JValue::Object(&empty_obj), + JValue::Long(0), + JValue::Bool(JNI_FALSE), + JValue::Object(&empty_obj), + ], + ) else { + clear_pending_exception(env); + return; + }; + let _ = env.call_method( + callback, + "onAuthResult", + "(Lcom/winlator/cmod/feature/stores/steam/wnsteam/WnAuthResult;)V", + &[JValue::Object(&result)], + ); + clear_pending_exception(env); +} + +fn dispatch_auth_result(callback: &GlobalRef, result: AuthSessionResult) { + let Some(vm) = JVM.get() else { + return; + }; + let Ok(mut env) = vm.attach_current_thread_as_daemon() else { + return; + }; + call_auth_result(&mut env, callback.as_obj(), &result); +} + +fn call_auth_result(env: &mut JNIEnv, callback: &JObject, result: &AuthSessionResult) { + if callback.is_null() { + return; + } + let Some(auth_result_class) = AUTH_RESULT_CLASS.get() else { + qr_log("auth result class missing; dropping auth callback"); + return; + }; + let Ok(error_message) = env.new_string(&result.error_message) else { + clear_pending_exception(env); + return; + }; + let Ok(account_name) = env.new_string(&result.account_name) else { + clear_pending_exception(env); + return; + }; + let Ok(refresh_token) = env.new_string(&result.refresh_token) else { + clear_pending_exception(env); + return; + }; + let Ok(access_token) = env.new_string(&result.access_token) else { + clear_pending_exception(env); + return; + }; + let Ok(new_guard_data) = env.new_string(&result.new_guard_data) else { + clear_pending_exception(env); + return; + }; + let Ok(agreement_session_url) = env.new_string(&result.agreement_session_url) else { + clear_pending_exception(env); + return; + }; + let error_message = JObject::from(error_message); + let account_name = JObject::from(account_name); + let refresh_token = JObject::from(refresh_token); + let access_token = JObject::from(access_token); + let new_guard_data = JObject::from(new_guard_data); + let agreement_session_url = JObject::from(agreement_session_url); + let auth_result_class = auth_result_jclass(auth_result_class); + let Ok(auth_result) = env.new_object( + auth_result_class, + "(ZILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;JZLjava/lang/String;)V", + &[ + JValue::Bool(if result.success { JNI_TRUE } else { JNI_FALSE }), + JValue::Int(result.eresult), + JValue::Object(&error_message), + JValue::Object(&account_name), + JValue::Object(&refresh_token), + JValue::Object(&access_token), + JValue::Object(&new_guard_data), + JValue::Long(result.steamid as jlong), + JValue::Bool(if result.had_remote_interaction { + JNI_TRUE + } else { + JNI_FALSE + }), + JValue::Object(&agreement_session_url), + ], + ) else { + clear_pending_exception(env); + return; + }; + let _ = env.call_method( + callback, + "onAuthResult", + "(Lcom/winlator/cmod/feature/stores/steam/wnsteam/WnAuthResult;)V", + &[JValue::Object(&auth_result)], + ); + clear_pending_exception(env); +} + +fn dispatch_qr_challenge(callback: &GlobalRef, url: &str) { + let Some(vm) = JVM.get() else { + return; + }; + let Ok(mut env) = vm.attach_current_thread_as_daemon() else { + return; + }; + let Ok(url) = env.new_string(url) else { + clear_pending_exception(&mut env); + return; + }; + let url = JObject::from(url); + let _ = env.call_method( + callback.as_obj(), + "onQrChallengeUrl", + "(Ljava/lang/String;)V", + &[JValue::Object(&url)], + ); + clear_pending_exception(&mut env); +} + +fn authenticator_accept_device_confirmation(authenticator: &GlobalRef) -> bool { + let Some(vm) = JVM.get() else { + return false; + }; + let Ok(mut env) = vm.attach_current_thread_as_daemon() else { + return false; + }; + let Ok(future) = env.call_method( + authenticator.as_obj(), + "acceptDeviceConfirmation", + "()Ljava/util/concurrent/CompletableFuture;", + &[], + ) else { + clear_pending_exception(&mut env); + return false; + }; + let Ok(future) = future.l() else { + clear_pending_exception(&mut env); + return false; + }; + if future.is_null() { + return false; + } + let Ok(result) = env.call_method(&future, "get", "()Ljava/lang/Object;", &[]) else { + clear_pending_exception(&mut env); + return false; + }; + let Ok(result) = result.l() else { + clear_pending_exception(&mut env); + return false; + }; + if result.is_null() { + return false; + } + let Ok(value) = env.call_method(&result, "booleanValue", "()Z", &[]) else { + clear_pending_exception(&mut env); + return false; + }; + value.z().unwrap_or(false) +} + +fn future_string_from_method( + env: &mut JNIEnv, + target: &JObject, + method: &str, + signature: &str, + args: &[JValue], +) -> Option { + let future = env + .call_method(target, method, signature, args) + .ok()? + .l() + .ok()?; + if future.is_null() { + return None; + } + let result = env + .call_method(&future, "get", "()Ljava/lang/Object;", &[]) + .ok()? + .l() + .ok()?; + if result.is_null() { + return None; + } + let result = JString::from(result); + jstring_to_string(env, &result) +} + +fn authenticator_get_device_code( + authenticator: &GlobalRef, + previous_code_was_incorrect: bool, +) -> Option { + let vm = JVM.get()?; + let mut env = vm.attach_current_thread_as_daemon().ok()?; + let value = future_string_from_method( + &mut env, + authenticator.as_obj(), + "getDeviceCode", + "(Z)Ljava/util/concurrent/CompletableFuture;", + &[JValue::Bool(if previous_code_was_incorrect { + JNI_TRUE + } else { + JNI_FALSE + })], + ); + clear_pending_exception(&mut env); + value +} + +fn authenticator_get_email_code( + authenticator: &GlobalRef, + email: &str, + previous_code_was_incorrect: bool, +) -> Option { + let vm = JVM.get()?; + let mut env = vm.attach_current_thread_as_daemon().ok()?; + let email = env.new_string(email).ok()?; + let email_obj = JObject::from(email); + let value = future_string_from_method( + &mut env, + authenticator.as_obj(), + "getEmailCode", + "(Ljava/lang/String;Z)Ljava/util/concurrent/CompletableFuture;", + &[ + JValue::Object(&email_obj), + JValue::Bool(if previous_code_was_incorrect { + JNI_TRUE + } else { + JNI_FALSE + }), + ], + ); + clear_pending_exception(&mut env); + value +} + +fn request_authed_service_body( + runtime: &Arc, + timeout: Duration, + build: F, +) -> Option> +where + F: FnOnce(&CMClientCore, u64) -> Option, +{ + if runtime.core().state() != ClientState::LoggedOn { + return None; + } + let job_id = runtime.next_job_id(); + let call = build(runtime.core(), job_id)?; + let (tx, rx) = mpsc::channel(); + runtime.track_job( + job_id, + move |job| { + let body = (!job.synthetic_failure && job.eresult == 1).then_some(job.body); + let _ = tx.send(body); + }, + Some(timeout), + ); + if !runtime.core().enqueue_wire(call.wire) { + return None; + } + runtime.flush_outbound(); + rx.recv_timeout(timeout).ok().flatten() +} + +fn request_authed_service_success( + runtime: &Arc, + timeout: Duration, + build: F, +) -> bool +where + F: FnOnce(&CMClientCore, u64) -> Option, +{ + if runtime.core().state() != ClientState::LoggedOn { + return false; + } + let job_id = runtime.next_job_id(); + let Some(call) = build(runtime.core(), job_id) else { + return false; + }; + let (tx, rx) = mpsc::channel(); + runtime.track_job( + job_id, + move |job| { + let _ = tx.send(!job.synthetic_failure && job.eresult == 1); + }, + Some(timeout), + ); + if !runtime.core().enqueue_wire(call.wire) { + return false; + } + runtime.flush_outbound(); + rx.recv_timeout(timeout).unwrap_or(false) +} + +fn request_service_method_job( + runtime: &Arc, + method: &str, + authed: bool, + body: Vec, + timeout: Duration, + cancel: Option<&AtomicBool>, +) -> Option { + let job_id = runtime.next_job_id(); + let (tx, rx) = mpsc::channel(); + runtime.track_job( + job_id, + move |job| { + let _ = tx.send(job); + }, + Some(timeout), + ); + let wire = runtime + .core() + .build_service_method_call(method, authed, job_id, &body); + if !runtime.core().enqueue_wire(wire) { + return None; + } + runtime.flush_outbound(); + recv_with_cancel(&rx, timeout, cancel) +} + +fn recv_with_cancel( + rx: &mpsc::Receiver, + timeout: Duration, + cancel: Option<&AtomicBool>, +) -> Option { + let tick = Duration::from_millis(100); + let mut remaining = timeout; + loop { + if cancel.is_some_and(|c| c.load(Ordering::Relaxed)) { + return None; + } + let wait = remaining.min(tick); + match rx.recv_timeout(wait) { + Ok(value) => return Some(value), + Err(mpsc::RecvTimeoutError::Timeout) => { + if remaining <= wait { + return None; + } + remaining -= wait; + } + Err(mpsc::RecvTimeoutError::Disconnected) => return None, + } + } +} + +fn auth_result_from_job(job: &crate::job_manager::JobResult, fallback_label: &str) -> AuthSessionResult { + let message = if !job.error_message.is_empty() { + job.error_message.clone() + } else if job.synthetic_failure { + format!("{fallback_label} timed out") + } else { + format!("{fallback_label} failed (eresult={})", job.eresult) + }; + AuthSessionResult { + eresult: job.eresult, + error_message: message, + ..Default::default() + } +} + +fn request_user_stats_response( + runtime: &Arc, + app_id: u32, + timeout: Duration, +) -> Option { + if app_id == 0 || runtime.core().state() != ClientState::LoggedOn { + return None; + } + let job_id = runtime.next_job_id(); + let message = runtime.core().build_job_proto_message( + EMsg::CLIENT_GET_USER_STATS, + job_id, + crate::pb::cmsg_client_get_user_stats::CMsgClientGetUserStats { + game_id: app_id as u64, + steam_id_for_user: runtime.core().steam_id(), + } + .serialize(), + 0, + )?; + let (tx, rx) = mpsc::channel(); + runtime.track_job( + job_id, + move |job| { + let response = if job.synthetic_failure { + None + } else { + crate::pb::cmsg_client_get_user_stats::CMsgClientGetUserStatsResponse::deserialize( + &job.body, + ) + }; + let _ = tx.send(response); + }, + Some(timeout), + ); + if !runtime.core().enqueue_wire(message.wire) { + return None; + } + runtime.flush_outbound(); + rx.recv_timeout(timeout).ok().flatten() +} + +fn request_proto_body( + runtime: &Arc, + timeout: Duration, + build: F, +) -> Option> +where + F: FnOnce(&CMClientCore, u64) -> Option, +{ + if runtime.core().state() != ClientState::LoggedOn { + return None; + } + let job_id = runtime.next_job_id(); + let message = build(runtime.core(), job_id)?; + let (tx, rx) = mpsc::channel(); + runtime.track_job( + job_id, + move |job| { + let body = (!job.synthetic_failure && job.eresult > 0).then_some(job.body); + let _ = tx.send(body); + }, + Some(timeout), + ); + if !runtime.core().enqueue_wire(message.wire) { + return None; + } + runtime.flush_outbound(); + rx.recv_timeout(timeout).ok().flatten() +} + +fn request_pics_product_info( + runtime: &Arc, + packages: Vec, + apps: Vec, + meta_data_only: bool, + timeout: Duration, +) -> Option { + let body = request_proto_body(runtime, timeout, |core, job_id| { + core.build_pics_product_info(packages, apps, meta_data_only, job_id) + })?; + let mut response = + crate::pb::cmsg_client_pics::CMsgClientPICSProductInfoResponse::deserialize(&body)?; + hydrate_http_delivered_appinfo(&mut response, timeout); + Some(response) +} + +/// Large appinfo the CM delivered over HTTP arrives as an empty `buffer` + a +/// `sha`/`http_host`; fetch it like the official client so the app isn't dropped. +fn hydrate_http_delivered_appinfo( + response: &mut crate::pb::cmsg_client_pics::CMsgClientPICSProductInfoResponse, + timeout: Duration, +) { + if response.http_host.is_empty() { + return; + } + let targets: Vec<(usize, u32, Vec)> = response + .apps + .iter() + .enumerate() + .filter(|(_, app)| !app.missing_token && app.buffer.is_empty() && !app.sha.is_empty()) + .map(|(index, app)| (index, app.appid, app.sha.clone())) + .collect(); + if targets.is_empty() { + return; + } + let host = response.http_host.clone(); + let cdn = crate::cdn_client::CdnClient::new(""); + let total = targets.len(); + let fetched = fetch_appinfo_buffers_parallel(&cdn, &host, targets, timeout); + let mut hydrated = 0usize; + for (index, buffer) in fetched { + if let (Some(buffer), Some(app)) = (buffer, response.apps.get_mut(index)) { + app.buffer = buffer; + hydrated += 1; + } + } + android_log( + "WnSteamPics", + &format!("hydrated {hydrated}/{total} HTTP-delivered appinfo buffer(s) from {host}"), + ); +} + +/// Concurrent appinfo fetch (bounded pool); returns `(index, buffer?)`. +fn fetch_appinfo_buffers_parallel( + cdn: &crate::cdn_client::CdnClient, + host: &str, + targets: Vec<(usize, u32, Vec)>, + timeout: Duration, +) -> Vec<(usize, Option>)> { + let worker_count = targets.len().clamp(1, 8); + let queue = Mutex::new(targets.into_iter()); + let results = Mutex::new(Vec::new()); + thread::scope(|scope| { + for _ in 0..worker_count { + scope.spawn(|| { + let mut conn = cdn.open_connection(); + loop { + let job = queue.lock().expect("appinfo fetch queue poisoned").next(); + let Some((index, app_id, sha)) = job else { + break; + }; + let buffer = + cdn.fetch_appinfo_with_connection(&mut conn, host, app_id, &sha, timeout); + results + .lock() + .expect("appinfo fetch results poisoned") + .push((index, buffer)); + } + }); + } + }); + results.into_inner().expect("appinfo fetch results poisoned") +} + +fn request_app_ownership_ticket( + runtime: &Arc, + app_id: u32, + timeout: Duration, +) -> Option +{ + let body = request_proto_body(runtime, timeout, |core, job_id| { + core.build_get_app_ownership_ticket(app_id, job_id) + })?; + let response = + crate::pb::cmsg_client_get_app_ownership_ticket::CMsgClientGetAppOwnershipTicketResponse::deserialize(&body)?; + if response.eresult == 1 && !response.ticket.is_empty() { + runtime + .core() + .tickets() + .store(app_id, response.eresult, response.ticket.clone()); + } + Some(response) +} + +fn request_cdn_servers( + runtime: &Arc, + timeout: Duration, +) -> Option> { + let body = request_authed_service_body(runtime, timeout, |core, job_id| { + core.build_get_cdn_servers_call(0, job_id) + })?; + crate::pb::ccontentserverdirectory::CContentServerDirectoryGetServersForSteamPipeResponse::deserialize(&body) + .map(|response| response.servers) +} + +const ERESULT_OK: i32 = 1; +const MAX_RESOLVE_ATTEMPTS: u32 = 4; + +enum DepotKeyOutcome { + Granted(Vec), + AccessDenied, + Transient, +} + +enum ManifestCodeOutcome { + Code(u64), + AccessDenied, + Transient, +} + +/// One CM proto request returning the header eresult and body; None only when no response arrived. +fn request_proto_response( + runtime: &Arc, + timeout: Duration, + build: F, +) -> Option<(i32, Vec)> +where + F: FnOnce(&CMClientCore, u64) -> Option, +{ + if runtime.core().state() != ClientState::LoggedOn { + return None; + } + let job_id = runtime.next_job_id(); + let message = build(runtime.core(), job_id)?; + let (tx, rx) = mpsc::channel(); + runtime.track_job( + job_id, + move |job| { + let result = (!job.synthetic_failure).then(|| (job.eresult, job.body)); + let _ = tx.send(result); + }, + Some(timeout), + ); + if !runtime.core().enqueue_wire(message.wire) { + return None; + } + runtime.flush_outbound(); + rx.recv_timeout(timeout).ok().flatten() +} + +/// One authed service-method request returning the header eresult and body; None only when no response arrived. +fn request_authed_service_response( + runtime: &Arc, + timeout: Duration, + build: F, +) -> Option<(i32, Vec)> +where + F: FnOnce(&CMClientCore, u64) -> Option, +{ + if runtime.core().state() != ClientState::LoggedOn { + return None; + } + let job_id = runtime.next_job_id(); + let call = build(runtime.core(), job_id)?; + let (tx, rx) = mpsc::channel(); + runtime.track_job( + job_id, + move |job| { + let result = (!job.synthetic_failure).then(|| (job.eresult, job.body)); + let _ = tx.send(result); + }, + Some(timeout), + ); + if !runtime.core().enqueue_wire(call.wire) { + return None; + } + runtime.flush_outbound(); + rx.recv_timeout(timeout).ok().flatten() +} + +fn request_manifest_request_code( + runtime: &Arc, + app_id: u32, + depot_id: u32, + manifest_id: u64, + branch: &str, + timeout: Duration, +) -> ManifestCodeOutcome { + let Some((eresult, body)) = request_authed_service_response(runtime, timeout, |core, job_id| { + core.build_manifest_request_code_call(app_id, depot_id, manifest_id, branch, job_id) + }) else { + return ManifestCodeOutcome::Transient; + }; + if eresult == ERESULT_OK { + if let Some(response) = + crate::pb::ccontentserverdirectory::CContentServerDirectoryGetManifestRequestCodeResponse::deserialize(&body) + { + return ManifestCodeOutcome::Code(response.manifest_request_code); + } + return ManifestCodeOutcome::Transient; + } + // A response arrived but the code was refused (branch/depot not available to this + // account); skip the depot rather than abort the whole download. + android_log( + "WnSteamDownload", + &format!("manifest code refused depot={depot_id} eresult={eresult}"), + ); + ManifestCodeOutcome::AccessDenied +} + +fn request_depot_key( + runtime: &Arc, + app_id: u32, + depot_id: u32, + timeout: Duration, +) -> DepotKeyOutcome { + let Some((header_eresult, body)) = request_proto_response(runtime, timeout, |core, job_id| { + core.build_get_depot_decryption_key(depot_id, app_id, job_id) + }) else { + return DepotKeyOutcome::Transient; + }; + if let Some(response) = + crate::pb::cmsg_client_get_depot_decryption_key::CMsgClientGetDepotDecryptionKeyResponse::deserialize(&body) + { + if response.eresult == ERESULT_OK as u32 && response.depot_encryption_key.len() == 32 { + return DepotKeyOutcome::Granted(response.depot_encryption_key); + } + android_log( + "WnSteamDownload", + &format!( + "depot key refused depot={depot_id} header_eresult={header_eresult} body_eresult={}", + response.eresult + ), + ); + } else { + android_log( + "WnSteamDownload", + &format!("depot key refused depot={depot_id} header_eresult={header_eresult} (unparsed)"), + ); + } + // A response arrived but no usable key: Steam will not grant this depot to this + // account. Skip it (DepotDownloader does the same) rather than abort the download. + DepotKeyOutcome::AccessDenied +} + +enum KeyResolution { + Granted(Vec), + AccessDenied, + Cancelled, + Unavailable, +} + +fn resolve_depot_key_with_retry( + runtime: &Arc, + app_id: u32, + depot_id: u32, + timeout: Duration, + cancel: &AtomicBool, +) -> KeyResolution { + for attempt in 0..MAX_RESOLVE_ATTEMPTS { + if cancel.load(Ordering::Relaxed) { + return KeyResolution::Cancelled; + } + if attempt > 0 { + thread::sleep(Duration::from_millis( + crate::depot_downloader::retry_backoff_millis(attempt), + )); + } + match request_depot_key(runtime, app_id, depot_id, timeout) { + DepotKeyOutcome::Granted(key) => return KeyResolution::Granted(key), + DepotKeyOutcome::AccessDenied => return KeyResolution::AccessDenied, + DepotKeyOutcome::Transient => continue, + } + } + KeyResolution::Unavailable +} + +enum CodeResolution { + Code(u64), + AccessDenied, + Cancelled, + Unavailable, +} + +fn resolve_manifest_code_with_retry( + runtime: &Arc, + app_id: u32, + depot_id: u32, + manifest_id: u64, + branch: &str, + timeout: Duration, + cancel: &AtomicBool, +) -> CodeResolution { + for attempt in 0..MAX_RESOLVE_ATTEMPTS { + if cancel.load(Ordering::Relaxed) { + return CodeResolution::Cancelled; + } + if attempt > 0 { + thread::sleep(Duration::from_millis( + crate::depot_downloader::retry_backoff_millis(attempt), + )); + } + match request_manifest_request_code(runtime, app_id, depot_id, manifest_id, branch, timeout) + { + ManifestCodeOutcome::Code(code) => return CodeResolution::Code(code), + ManifestCodeOutcome::AccessDenied => return CodeResolution::AccessDenied, + ManifestCodeOutcome::Transient => continue, + } + } + CodeResolution::Unavailable +} + +/// Records depots Steam denied a key for so the Kotlin completeness gate can exclude them. +fn write_denied_depots_marker(config_dir: &std::path::Path, denied: &[u32]) { + let path = config_dir.join("denied.depots"); + if denied.is_empty() { + let _ = std::fs::remove_file(&path); + return; + } + let _ = std::fs::create_dir_all(config_dir); + let body = denied + .iter() + .map(|id| id.to_string()) + .collect::>() + .join("\n"); + let _ = std::fs::write(&path, body); +} + +fn request_item_def_digest( + runtime: &Arc, + app_id: u32, + timeout: Duration, +) -> Option { + if app_id == 0 || runtime.core().state() != ClientState::LoggedOn { + return None; + } + let job_id = runtime.next_job_id(); + let call = runtime + .core() + .build_inventory_item_def_meta_call(app_id, job_id)?; + let (tx, rx) = mpsc::channel(); + runtime.track_job( + job_id, + move |job| { + let digest = if job.synthetic_failure || job.eresult != 1 { + None + } else { + crate::pb::cinventory::CInventoryGetItemDefMetaResponse::deserialize(&job.body) + .and_then(|response| (!response.digest.is_empty()).then_some(response.digest)) + }; + let _ = tx.send(digest); + }, + Some(timeout), + ); + if !runtime.core().enqueue_wire(call.wire) { + return None; + } + runtime.flush_outbound(); + rx.recv_timeout(timeout).ok().flatten() +} + +fn request_subscribed_workshop_page( + runtime: &Arc, + app_id: u32, + page: u32, + page_size: u32, + timeout: Duration, +) -> Option { + if app_id == 0 || page == 0 || page_size == 0 || runtime.core().state() != ClientState::LoggedOn + { + return None; + } + let job_id = runtime.next_job_id(); + let call = runtime + .core() + .build_published_file_subscribed_call(app_id, page, page_size, job_id)?; + let (tx, rx) = mpsc::channel(); + runtime.track_job( + job_id, + move |job| { + let response = if job.synthetic_failure || job.eresult != 1 { + None + } else { + crate::pb::cpublishedfile::CPublishedFileGetUserFilesResponse::deserialize( + &job.body, + ) + }; + let _ = tx.send(response); + }, + Some(timeout), + ); + if !runtime.core().enqueue_wire(call.wire) { + return None; + } + runtime.flush_outbound(); + rx.recv_timeout(timeout).ok().flatten() +} + +struct QrPollState { + runtime: Arc, + qr_callback: GlobalRef, + result_callback: GlobalRef, + cancel: Arc, + client_id: u64, + request_id: Vec, + poll_interval_seconds: f32, + last_challenge_url: Arc>, +} + +fn start_qr_poll_loop(state: QrPollState) { + thread::spawn(move || { + let QrPollState { + runtime, + qr_callback, + result_callback, + cancel, + mut client_id, + request_id, + poll_interval_seconds, + last_challenge_url, + } = state; + let poll_interval = Duration::from_secs_f32(poll_interval_seconds.max(0.25)); + let timeout = Duration::from_secs(30); + let mut reported_remote_interaction = false; + qr_log(&format!( + "poll loop start client_id={} request_id_len={} interval_s={:.2}", + client_id, + request_id.len(), + poll_interval_seconds + )); + loop { + for slice in sleep_slices(poll_interval, Duration::from_millis(100)) { + if cancel.load(Ordering::Relaxed) { + return; + } + thread::sleep(slice); + } + if cancel.load(Ordering::Relaxed) { + return; + } + + let request = build_poll_request(client_id, request_id.clone()).serialize(); + let poll_job = match request_service_method_job( + &runtime, + "Authentication.PollAuthSessionStatus#1", + false, + request, + timeout, + Some(cancel.as_ref()), + ) { + Some(job) => job, + None => { + if cancel.load(Ordering::Relaxed) { + qr_log("poll loop cancelled while waiting for status"); + return; + } + qr_log("PollAuthSessionStatus timed out"); + dispatch_auth_result( + &result_callback, + AuthSessionResult { + error_message: "PollAuthSessionStatus timed out".to_string(), + ..Default::default() + }, + ); + return; + } + }; + if poll_job.synthetic_failure || poll_job.eresult != 1 { + qr_log(&format!( + "PollAuthSessionStatus failed synthetic={} eresult={} body_len={}", + poll_job.synthetic_failure, + poll_job.eresult, + poll_job.body.len() + )); + dispatch_auth_result( + &result_callback, + auth_result_from_job(&poll_job, "PollAuthSessionStatus"), + ); + return; + } + let Some(resp) = crate::pb::cauthentication::PollAuthSessionStatusResponse::deserialize( + &poll_job.body, + ) else { + qr_log(&format!( + "Poll response parse failed body_len={}", + poll_job.body.len() + )); + dispatch_auth_result( + &result_callback, + AuthSessionResult { + error_message: "Poll response parse failed".to_string(), + ..Default::default() + }, + ); + return; + }; + qr_log(&format!( + "poll response new_client_id={} remote={} refresh_len={} access_len={} account_len={} agreement_len={} challenge_len={}", + resp.new_client_id, + resp.had_remote_interaction, + resp.refresh_token.len(), + resp.access_token.len(), + resp.account_name.len(), + resp.agreement_session_url.len(), + resp.new_challenge_url.len() + )); + if resp.new_client_id != 0 { + client_id = resp.new_client_id; + } + if let Some(challenge) = { + let mut last = last_challenge_url + .lock() + .expect("qr challenge url poisoned"); + crate::auth_session::take_qr_challenge_update(&mut last, &resp) + } { + dispatch_qr_challenge(&qr_callback, &challenge); + } + let remote_interaction = + take_qr_remote_interaction(&mut reported_remote_interaction, &resp); + let account_name = resp.account_name.clone(); + let agreement_session_url = resp.agreement_session_url.clone(); + if let Some(result) = auth_result_from_poll(resp, 0) { + qr_log(&format!( + "dispatching final QR auth result success={} refresh_len={} account_len={} remote={}", + result.success, + result.refresh_token.len(), + result.account_name.len(), + result.had_remote_interaction + )); + dispatch_auth_result(&result_callback, result); + return; + } + if remote_interaction { + qr_log("dispatching intermediate remote-interaction QR auth update"); + dispatch_auth_result( + &result_callback, + AuthSessionResult { + account_name, + agreement_session_url, + had_remote_interaction: true, + ..Default::default() + }, + ); + } + } + }); +} + +fn call_prepare_result(env: &mut JNIEnv, callback: &JObject, ok: bool, error: &str) { + if callback.is_null() { + return; + } + let Ok(error) = env.new_string(error) else { + clear_pending_exception(env); + return; + }; + let error_obj = JObject::from(error); + let _ = env.call_method( + callback, + "onPrepareResult", + "(ZLjava/lang/String;)V", + &[ + JValue::Bool(if ok { JNI_TRUE } else { JNI_FALSE }), + JValue::Object(&error_obj), + ], + ); + clear_pending_exception(env); +} + +fn call_download_complete_result( + env: &mut JNIEnv, + listener: &JObject, + success: bool, + error: &str, + bytes_written: u64, + depots_completed: u32, + depots_skipped: u32, +) { + if listener.is_null() { + return; + } + let Ok(error) = env.new_string(error) else { + clear_pending_exception(env); + return; + }; + let error_obj = JObject::from(error); + let _ = env.call_method( + listener, + "onComplete", + "(ZLjava/lang/String;JII)V", + &[ + JValue::Bool(if success { JNI_TRUE } else { JNI_FALSE }), + JValue::Object(&error_obj), + JValue::Long(bytes_written as jlong), + JValue::Int(depots_completed as jint), + JValue::Int(depots_skipped as jint), + ], + ); + clear_pending_exception(env); +} + +fn call_download_complete(env: &mut JNIEnv, listener: &JObject, error: &str) { + call_download_complete_result(env, listener, false, error, 0, 0, 0); +} + +fn dispatch_download_complete( + listener: GlobalRef, + result: crate::depot_downloader::DepotDownloadResult, +) { + let Some(vm) = JVM.get() else { + return; + }; + let Ok(mut env) = vm.attach_current_thread_as_daemon() else { + return; + }; + let listener_obj = listener.as_obj(); + call_download_complete_result( + &mut env, + listener_obj, + result.success, + &result.error, + result.bytes_written, + result.depots_completed, + result.depots_skipped, + ); +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativePickCmUrl( + mut env: JNIEnv, + _class: JClass, + ca_bundle_path: JString, +) -> jstring { + let ca_bundle_path = jstring_to_string(&mut env, &ca_bundle_path).unwrap_or_default(); + let directory = crate::steam_directory::SteamDirectoryClient::new(ca_bundle_path); + let result = directory.fetch(0, crate::steam_directory::DEFAULT_TIMEOUT); + let directory_url = if result.ok() { + result + .servers + .into_iter() + .find_map(|server| { + let url = server.websocket_url(); + (!url.is_empty()).then_some(url) + }) + .unwrap_or_default() + } else { + String::new() + }; + let url = if !directory_url.is_empty() { + directory_url + } else { + crate::cm_server_list::hardcoded_fallback_servers() + .into_iter() + .find_map(|server| { + let url = server.websocket_url(); + (!url.is_empty()).then_some(url) + }) + .unwrap_or_default() + }; + new_string_or_null(&mut env, &url) +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeCreate( + mut env: JNIEnv, + _class: JClass, +) -> jlong { + let _ = ensure_auth_result_class(&mut env); + to_session_handle(Box::new(WnSteamSessionHandle::new())) +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeDestroy( + _env: JNIEnv, + _class: JClass, + handle: jlong, +) { + unsafe { drop_session_handle(handle) }; +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeSetCaBundlePath( + mut env: JNIEnv, + _class: JClass, + handle: jlong, + path: JString, +) { + let Some(handle) = (unsafe { from_session_handle_mut(handle) }) else { + return; + }; + if let Ok(path) = env.get_string(&path) { + handle.ca_bundle_path = path.to_string_lossy().into_owned(); + } +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeSetAutoPopulateLibrary( + _env: JNIEnv, + _class: JClass, + handle: jlong, + enabled: jboolean, +) { + if let Some(handle) = unsafe { from_session_handle_mut(handle) } { + handle.auto_populate_library = enabled != JNI_FALSE; + } +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeConnect( + mut env: JNIEnv, + _class: JClass, + handle: jlong, + url: JString, +) -> jboolean { + let Some(handle) = (unsafe { from_session_handle_mut(handle) }) else { + return JNI_FALSE; + }; + let Some(url) = jstring_to_string(&mut env, &url) else { + return JNI_FALSE; + }; + if url.is_empty() { + return JNI_FALSE; + } + let runtime = handle.runtime(); + if !runtime.connect(&url) { + return JNI_FALSE; + } + JNI_TRUE +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeDisconnect( + _env: JNIEnv, + _class: JClass, + handle: jlong, +) { + if let Some(handle) = unsafe { from_session_handle_mut(handle) } { + if let Some(runtime) = handle + .runtime + .lock() + .expect("session runtime poisoned") + .take() + { + runtime.disconnect(); + } else { + handle.core.set_state(ClientState::Disconnected); + dispatch_state_observer(&handle.state_observer, ClientState::Disconnected); + } + } +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeLogOffAndDisconnect( + env: JNIEnv, + class: JClass, + handle: jlong, + flush_ms: jint, +) { + if let Some(handle_ref) = unsafe { from_session_handle_mut(handle) } { + if handle_ref.enqueue_proto(handle_ref.core.build_logoff()) { + let flush = if flush_ms <= 0 { + Duration::from_millis(500) + } else { + Duration::from_millis(flush_ms as u64) + }; + thread::sleep(flush); + } + } + Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeDisconnect( + env, class, handle, + ); +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeIsPlayingBlocked( + _env: JNIEnv, + _class: JClass, + handle: jlong, +) -> jboolean { + let Some(handle) = (unsafe { from_session_handle_mut(handle) }) else { + return JNI_FALSE; + }; + if handle.core.is_playing_blocked() { + JNI_TRUE + } else { + JNI_FALSE + } +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeMarkPlayingBlocked( + _env: JNIEnv, + _class: JClass, + handle: jlong, +) { + if let Some(handle) = unsafe { from_session_handle_mut(handle) } { + handle.core.mark_playing_blocked(); + } +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeState( + _env: JNIEnv, + _class: JClass, + handle: jlong, +) -> jint { + let Some(handle) = (unsafe { from_session_handle_mut(handle) }) else { + return ClientState::Disconnected as jint; + }; + handle.core.state() as jint +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeSteamId( + _env: JNIEnv, + _class: JClass, + handle: jlong, +) -> jlong { + let Some(handle) = (unsafe { from_session_handle_mut(handle) }) else { + return 0; + }; + handle.core.steam_id() as jlong +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeFamilyGroupId( + _env: JNIEnv, + _class: JClass, + handle: jlong, +) -> jlong { + let Some(handle) = (unsafe { from_session_handle_mut(handle) }) else { + return 0; + }; + handle.core.family_group_id() as jlong +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeGetLibrarySnapshot( + mut env: JNIEnv, + _class: JClass, + handle: jlong, +) -> jstring { + let Some(handle) = (unsafe { from_session_handle_mut(handle) }) else { + return new_string_or_null(&mut env, "{}"); + }; + new_string_or_null(&mut env, &handle.core.library().snapshot_json()) +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeStartWineBridge( + _env: JNIEnv, + _class: JClass, + handle: jlong, + steam3_port: jint, + client_service_port: jint, +) -> jboolean { + let Some(handle) = (unsafe { from_session_handle_mut(handle) }) else { + return JNI_FALSE; + }; + let mut config = WineBridgeConfig::default(); + if steam3_port > 0 { + config.steam3_port = steam3_port as u16; + } + if client_service_port > 0 { + config.client_svc_port = client_service_port as u16; + } + if handle.wine_bridge.start(config) { + JNI_TRUE + } else { + JNI_FALSE + } +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeStopWineBridge( + _env: JNIEnv, + _class: JClass, + handle: jlong, +) { + if let Some(handle) = unsafe { from_session_handle_mut(handle) } { + handle.wine_bridge.stop(); + } +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeWineBridgeLastError( + mut env: JNIEnv, + _class: JClass, + handle: jlong, +) -> jstring { + let Some(handle) = (unsafe { from_session_handle_mut(handle) }) else { + return new_string_or_null(&mut env, ""); + }; + new_string_or_null(&mut env, &handle.wine_bridge.last_error()) +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeGetAppOwnershipTicket( + env: JNIEnv, + _class: JClass, + handle: jlong, + app_id: jint, +) -> jbyteArray { + let Some(handle) = (unsafe { from_session_handle_mut(handle) }) else { + return ptr::null_mut(); + }; + let Some(ticket) = handle.core.tickets().get(app_id as u32) else { + return ptr::null_mut(); + }; + if ticket.eresult != 1 || ticket.ticket.is_empty() { + return ptr::null_mut(); + } + byte_array_or_null(&env, &ticket.ticket) +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeGetLicenseList( + mut env: JNIEnv, + _class: JClass, + handle: jlong, +) -> jstring { + let Some(handle) = (unsafe { from_session_handle_mut(handle) }) else { + return ptr::null_mut(); + }; + let licenses = handle.core.license_list(); + let value = json!(licenses + .iter() + .map(|license| json!({ + "packageId": license.package_id, + "changeNumber": license.change_number, + "timeCreated": license.time_created, + "timeNextProcess": license.time_next_process, + "minuteLimit": license.minute_limit, + "minutesUsed": license.minutes_used, + "paymentMethod": license.payment_method, + "flags": license.flags, + "purchaseCountryCode": license.purchase_country_code, + "licenseType": license.license_type, + "territoryCode": license.territory_code, + "accessToken": license.access_token as i64, + "ownerId": license.owner_id, + "masterPackageId": license.master_package_id, + })) + .collect::>()) + .to_string(); + new_string_or_null(&mut env, &value) +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeGetFriendsList( + env: JNIEnv, + _class: JClass, + handle: jlong, +) -> jlongArray { + let friends = match unsafe { from_session_handle_mut(handle) } { + Some(handle) => handle + .core + .friends_list() + .into_iter() + .map(|sid| sid as jlong) + .collect::>(), + None => Vec::new(), + }; + let Ok(array) = env.new_long_array(friends.len() as i32) else { + return ptr::null_mut(); + }; + if !friends.is_empty() && env.set_long_array_region(&array, 0, &friends).is_err() { + return ptr::null_mut(); + } + array.into_raw() +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeGetFriendPersonas( + mut env: JNIEnv, + _class: JClass, + handle: jlong, +) -> jstring { + let Some(handle) = (unsafe { from_session_handle_mut(handle) }) else { + return new_string_or_null(&mut env, "[]"); + }; + let value = json!(handle + .core + .friend_personas() + .iter() + .map(|persona| json!({ + "sid": persona.sid as i64, + "name": persona.player_name, + "state": persona.persona_state, + "app": persona.game_played_app_id, + "avatarHash": crate::cdn_client::hex_encode(&persona.avatar_hash), + "gameName": persona.game_name, + "gameId": persona.gameid as i64, + "connect": persona + .rich_presence + .iter() + .find(|(k, _)| k == "connect") + .map(|(_, v)| v.as_str()) + .unwrap_or(""), + })) + .collect::>()) + .to_string(); + new_string_or_null(&mut env, &value) +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeGetSelfPersona( + mut env: JNIEnv, + _class: JClass, + handle: jlong, +) -> jstring { + let Some(handle) = (unsafe { from_session_handle_mut(handle) }) else { + return ptr::null_mut(); + }; + let Some(persona) = handle.core.self_persona() else { + return ptr::null_mut(); + }; + let value = json!({ + "personaState": persona.persona_state, + "gameAppId": persona.game_played_app_id, + "playerName": persona.player_name, + "avatarHash": crate::cdn_client::hex_encode(&persona.avatar_hash), + "gameName": persona.game_name, + "gameId": persona.gameid as i64, + }) + .to_string(); + new_string_or_null(&mut env, &value) +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeSendFriendMessage( + mut env: JNIEnv, + _class: JClass, + handle: jlong, + steam_id: jlong, + message: JString, +) -> jstring { + let Some(handle) = (unsafe { from_session_handle_mut(handle) }) else { + return ptr::null_mut(); + }; + let Some(runtime) = handle.connected_runtime() else { + return ptr::null_mut(); + }; + let Some(message) = jstring_to_string(&mut env, &message) else { + return ptr::null_mut(); + }; + if message.is_empty() { + return ptr::null_mut(); + } + let contains_bbcode = message.contains("[img]"); + let Some(body) = + request_authed_service_body(&runtime, Duration::from_secs(15), |core, job_id| { + core.build_send_friend_message(steam_id as u64, &message, contains_bbcode, job_id) + }) + else { + return ptr::null_mut(); + }; + let Some(response) = + crate::pb::cfriendmessages::CFriendMessagesSendMessageResponse::deserialize(&body) + else { + return ptr::null_mut(); + }; + let value = json!({ + "serverTimestamp": response.server_timestamp, + "ordinal": response.ordinal, + }) + .to_string(); + new_string_or_null(&mut env, &value) +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeGetRecentMessages( + mut env: JNIEnv, + _class: JClass, + handle: jlong, + steam_id: jlong, + count: jint, +) -> jstring { + let Some(handle) = (unsafe { from_session_handle_mut(handle) }) else { + return new_string_or_null(&mut env, "[]"); + }; + let Some(runtime) = handle.connected_runtime() else { + return new_string_or_null(&mut env, "[]"); + }; + let self_accountid = (handle.core.steam_id() & 0xFFFF_FFFF) as u32; + let count = count.clamp(1, 200) as u32; + let Some(body) = + request_authed_service_body(&runtime, Duration::from_secs(15), |core, job_id| { + core.build_get_recent_messages(steam_id as u64, count, job_id) + }) + else { + return new_string_or_null(&mut env, "[]"); + }; + let Some(response) = + crate::pb::cfriendmessages::CFriendMessagesGetRecentMessagesResponse::deserialize(&body) + else { + return new_string_or_null(&mut env, "[]"); + }; + let value = json!(response + .messages + .iter() + .map(|m| json!({ + "fromSelf": m.accountid == self_accountid, + "message": m.message, + "timestamp": m.timestamp, + "ordinal": m.ordinal, + })) + .collect::>()) + .to_string(); + new_string_or_null(&mut env, &value) +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeDrainFriendMessages( + mut env: JNIEnv, + _class: JClass, + handle: jlong, +) -> jstring { + let Some(handle) = (unsafe { from_session_handle_mut(handle) }) else { + return new_string_or_null(&mut env, "[]"); + }; + let value = json!(handle + .core + .drain_incoming_messages() + .iter() + .map(|m| json!({ + "friendId": m.friend_id as i64, + "fromSelf": m.from_self, + "message": m.message, + "timestamp": m.timestamp, + "ordinal": m.ordinal, + })) + .collect::>()) + .to_string(); + new_string_or_null(&mut env, &value) +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeSendChatImage( + mut env: JNIEnv, + _class: JClass, + handle: jlong, + steam_id: jlong, + refresh_token: JString, + image: JByteArray, + file_name: JString, +) -> jstring { + let Some(handle) = (unsafe { from_session_handle_mut(handle) }) else { + return ptr::null_mut(); + }; + let Some(runtime) = handle.connected_runtime() else { + return ptr::null_mut(); + }; + let self_steamid = handle.core.steam_id(); + let ca_bundle_path = handle.ca_bundle_path.clone(); + let Some(refresh_token) = jstring_to_string(&mut env, &refresh_token) else { + return ptr::null_mut(); + }; + let file_name = jstring_to_string(&mut env, &file_name).unwrap_or_else(|| "image.png".into()); + let Ok(bytes) = env.convert_byte_array(image) else { + return ptr::null_mut(); + }; + if bytes.is_empty() || self_steamid == 0 || refresh_token.is_empty() { + return ptr::null_mut(); + } + + // Mint a short-lived web access token for the steamLoginSecure cookie. + let request = crate::pb::cauthentication::AccessTokenGenerateForAppRequest { + refresh_token, + steamid: self_steamid, + renewal_type: crate::pb::cauthentication::EAuthTokenRenewalType::None, + } + .serialize(); + let Some(token_body) = + request_authed_service_body(&runtime, Duration::from_secs(15), move |core, job_id| { + core.build_authed_service_call( + "Authentication.GenerateAccessTokenForApp#1", + job_id, + request, + ) + }) + else { + return ptr::null_mut(); + }; + let access_token = crate::pb::cauthentication::AccessTokenGenerateForAppResponse::deserialize( + &token_body, + ) + .map(|r| r.access_token) + .unwrap_or_default(); + if access_token.is_empty() { + android_log("WNIMG", "no web access token"); + return ptr::null_mut(); + } + + let url = match crate::chat_image::upload( + &ca_bundle_path, + self_steamid, + steam_id as u64, + &access_token, + &bytes, + &file_name, + ) { + Ok(url) => url, + Err(err) => { + android_log("WNIMG", &format!("upload failed: {err}")); + return ptr::null_mut(); + } + }; + new_string_or_null(&mut env, &url) +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeSetPersonaState( + _env: JNIEnv, + _class: JClass, + handle: jlong, + persona_state: jint, +) { + if let Some(handle) = unsafe { from_session_handle_mut(handle) } { + handle.enqueue_proto(handle.core.build_set_persona_state(persona_state as u32)); + } +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeSetPersonaName( + mut env: JNIEnv, + _class: JClass, + handle: jlong, + name: JString, + persona_state: jint, +) { + let Some(handle) = (unsafe { from_session_handle_mut(handle) }) else { + return; + }; + let Ok(name) = env.get_string(&name) else { + return; + }; + let name = name.to_string_lossy().into_owned(); + handle.enqueue_proto( + handle + .core + .build_set_persona_name(name, persona_state as u32), + ); +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeRequestUserPersona( + _env: JNIEnv, + _class: JClass, + handle: jlong, +) { + if let Some(handle) = unsafe { from_session_handle_mut(handle) } { + handle.enqueue_proto(handle.core.build_request_user_persona()); + } +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeRequestFriendPersonas( + env: JNIEnv, + _class: JClass, + handle: jlong, + steam_ids: JLongArray, + flags: jint, +) { + let Some(handle) = (unsafe { from_session_handle_mut(handle) }) else { + return; + }; + let len = env.get_array_length(&steam_ids).unwrap_or(0); + if len <= 0 { + return; + } + let mut ids = vec![0i64; len as usize]; + if env.get_long_array_region(&steam_ids, 0, &mut ids).is_err() { + return; + } + let ids = ids.into_iter().map(|id| id as u64).collect::>(); + handle.enqueue_proto( + handle + .core + .build_request_friend_personas(&ids, flags as u32), + ); +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeNotifyGamesPlayed( + mut env: JNIEnv, + _class: JClass, + handle: jlong, + games_json: JString, + client_os_type: jint, +) { + let Some(handle) = (unsafe { from_session_handle_mut(handle) }) else { + return; + }; + let Ok(games_json) = env.get_string(&games_json) else { + return; + }; + let raw = games_json.to_string_lossy().into_owned(); + let parsed: Option = serde_json::from_str(&raw).ok(); + let entry = parsed + .as_ref() + .and_then(|value| value.as_array().and_then(|arr| arr.first())) + .cloned() + .unwrap_or(serde_json::Value::Null); + let pick_u64 = |entry: &serde_json::Value, keys: &[&str]| -> u64 { + for key in keys { + if let Some(value) = entry.get(*key) { + if let Some(v) = value.as_u64() { + return v; + } + if let Some(s) = value.as_str() { + if let Ok(v) = s.parse::() { + return v; + } + } + } + } + 0 + }; + let pick_u32 = |entry: &serde_json::Value, keys: &[&str]| -> u32 { + pick_u64(entry, keys) as u32 + }; + let game_id = pick_u64(&entry, &["gameId", "game_id", "appid", "app_id"]); + let extras = crate::cm_client::GamesPlayedExtras { + process_id: pick_u32(&entry, &["processId", "process_id"]), + owner_id: pick_u32(&entry, &["ownerId", "owner_id"]), + launch_source: pick_u32(&entry, &["launchSource", "launch_source"]), + game_build_id: pick_u32(&entry, &["gameBuildId", "game_build_id"]), + }; + let processes = entry + .get("processes") + .and_then(|v| v.as_array()) + .map(|arr| { + arr.iter() + .map(|p| crate::pb::cmsg_client_games_played::GamePlayedProcessInfo { + process_id: pick_u32(p, &["pid", "processId", "process_id"]), + process_id_parent: pick_u32( + p, + &["ppid", "processIdParent", "process_id_parent"], + ), + parent_is_steam: p + .get("isSteam") + .or_else(|| p.get("parentIsSteam")) + .or_else(|| p.get("parent_is_steam")) + .and_then(|v| v.as_bool()) + .unwrap_or(false), + }) + .collect::>() + }) + .unwrap_or_default(); + let os_type = if client_os_type < 0 { + 0 + } else { + client_os_type as u32 + }; + handle.enqueue_proto(handle.core.build_notify_games_played_full( + game_id, &extras, &processes, os_type, + )); +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeKickPlayingSession( + _env: JNIEnv, + _class: JClass, + handle: jlong, + only_stop_game: jboolean, +) { + if let Some(handle) = unsafe { from_session_handle_mut(handle) } { + handle.enqueue_proto( + handle + .core + .build_kick_playing_session(only_stop_game != JNI_FALSE), + ); + } +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeStoreUserStats( + env: JNIEnv, + _class: JClass, + handle: jlong, + app_id: jint, + steam_id: jlong, + crc_stats: jint, + stat_ids: JIntArray, + stat_values: JIntArray, +) { + let Some(handle) = (unsafe { from_session_handle_mut(handle) }) else { + return; + }; + let len = env + .get_array_length(&stat_ids) + .unwrap_or(0) + .min(env.get_array_length(&stat_values).unwrap_or(0)); + if len < 0 { + return; + } + let mut ids = vec![0i32; len as usize]; + let mut values = vec![0i32; len as usize]; + if env.get_int_array_region(&stat_ids, 0, &mut ids).is_err() + || env + .get_int_array_region(&stat_values, 0, &mut values) + .is_err() + { + return; + } + let stats = ids + .into_iter() + .zip(values) + .map(|(id, value)| (id as u32, value as u32)) + .collect::>(); + handle.enqueue_proto(handle.core.build_store_user_stats( + app_id as u32, + steam_id as u64, + crc_stats as u32, + &stats, + )); +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeSetStateObserver( + env: JNIEnv, + _class: JClass, + handle: jlong, + observer: JObject, +) { + let Some(handle) = (unsafe { from_session_handle_mut(handle) }) else { + return; + }; + let mut slot = handle + .state_observer + .lock() + .expect("session state observer poisoned"); + *slot = if observer.is_null() { + None + } else { + env.new_global_ref(observer).ok() + }; +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeSetLibraryObserver( + env: JNIEnv, + _class: JClass, + handle: jlong, + observer: JObject, +) { + let Some(handle) = (unsafe { from_session_handle_mut(handle) }) else { + return; + }; + let mut slot = handle + .library_observer + .lock() + .expect("session library observer poisoned"); + *slot = if observer.is_null() { + None + } else { + env.new_global_ref(observer).ok() + }; +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeRenewRefreshToken( + mut env: JNIEnv, + _class: JClass, + handle: jlong, + current_token: JString, + steam_id64: jlong, + timeout_ms: jint, +) -> jstring { + let Some(handle) = (unsafe { from_session_handle_mut(handle) }) else { + return ptr::null_mut(); + }; + if handle.core.state() != ClientState::LoggedOn { + return ptr::null_mut(); + } + let Some(runtime) = handle.connected_runtime() else { + return ptr::null_mut(); + }; + let Some(current_token) = jstring_to_string(&mut env, ¤t_token) else { + return ptr::null_mut(); + }; + if current_token.is_empty() || steam_id64 == 0 { + return ptr::null_mut(); + } + + let request = crate::pb::cauthentication::AccessTokenGenerateForAppRequest { + refresh_token: current_token, + steamid: steam_id64 as u64, + renewal_type: crate::pb::cauthentication::EAuthTokenRenewalType::Allow, + } + .serialize(); + let job_id = runtime.next_job_id(); + let (tx, rx) = mpsc::channel(); + runtime.track_job( + job_id, + move |job| { + let token = if !job.synthetic_failure && job.eresult == 1 { + crate::pb::cauthentication::AccessTokenGenerateForAppResponse::deserialize( + &job.body, + ) + .map(|resp| resp.refresh_token) + .unwrap_or_default() + } else { + String::new() + }; + let _ = tx.send(token); + }, + Some(Duration::from_millis(timeout_ms.max(1) as u64)), + ); + let wire = runtime.core().build_service_method_call( + "Authentication.GenerateAccessTokenForApp#1", + true, + job_id, + &request, + ); + if !runtime.core().enqueue_wire(wire) { + return ptr::null_mut(); + } + runtime.flush_outbound(); + + let wait = Duration::from_millis(timeout_ms.max(0) as u64); + let Ok(token) = rx.recv_timeout(wait) else { + return ptr::null_mut(); + }; + if token.is_empty() { + return ptr::null_mut(); + } + new_string_or_null(&mut env, &token) +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeStartLoginWithCredentials( + mut env: JNIEnv, + _class: JClass, + handle: jlong, + username: JString, + password: JString, + persistent_session: jboolean, + authenticator: JObject, + callback: JObject, +) { + let Some(handle) = (unsafe { from_session_handle_mut(handle) }) else { + call_auth_result_failure(&mut env, &callback, 2, "session closed"); + return; + }; + let Some(runtime) = handle.connected_runtime() else { + call_auth_result_failure(&mut env, &callback, 2, "not connected"); + return; + }; + let Some(username) = jstring_to_string(&mut env, &username) else { + call_auth_result_failure(&mut env, &callback, 2, "missing username"); + return; + }; + let Some(password) = jstring_to_string(&mut env, &password) else { + call_auth_result_failure(&mut env, &callback, 2, "missing password"); + return; + }; + let Ok(callback) = env.new_global_ref(callback) else { + clear_pending_exception(&mut env); + return; + }; + let authenticator = if authenticator.is_null() { + None + } else { + env.new_global_ref(authenticator).ok() + }; + let cancel = handle.begin_login_cancel(); + thread::spawn(move || { + let timeout = Duration::from_secs(30); + let mut config = CredentialsAuthConfig { + username, + password, + persistent_session: persistent_session != JNI_FALSE, + ..Default::default() + }; + let key_job = match request_service_method_job( + &runtime, + "Authentication.GetPasswordRSAPublicKey#1", + false, + build_password_rsa_request(&config).serialize(), + timeout, + Some(cancel.as_ref()), + ) { + Some(job) => job, + None => { + if cancel.load(Ordering::Relaxed) { + return; + } + dispatch_auth_result( + &callback, + AuthSessionResult { + error_message: "GetPasswordRSAPublicKey timed out".to_string(), + ..Default::default() + }, + ); + return; + } + }; + if key_job.synthetic_failure || key_job.eresult != 1 { + dispatch_auth_result( + &callback, + auth_result_from_job(&key_job, "GetPasswordRSAPublicKey"), + ); + return; + } + let Some(key) = crate::pb::cauthentication::GetPasswordRsaPublicKeyResponse::deserialize( + &key_job.body, + ) else { + dispatch_auth_result( + &callback, + AuthSessionResult { + error_message: "GetPasswordRSAPublicKey parse failed".to_string(), + ..Default::default() + }, + ); + return; + }; + let begin_request = match build_credentials_begin_request(&mut config, &key) { + Ok(request) => request, + Err(result) => { + dispatch_auth_result(&callback, result); + return; + } + }; + let begin_job = match request_service_method_job( + &runtime, + "Authentication.BeginAuthSessionViaCredentials#1", + false, + begin_request.serialize(), + timeout, + Some(cancel.as_ref()), + ) { + Some(job) => job, + None => { + if cancel.load(Ordering::Relaxed) { + return; + } + dispatch_auth_result( + &callback, + AuthSessionResult { + error_message: "BeginAuthSessionViaCredentials timed out".to_string(), + ..Default::default() + }, + ); + return; + } + }; + if begin_job.synthetic_failure || begin_job.eresult != 1 { + dispatch_auth_result( + &callback, + auth_result_from_job(&begin_job, "BeginAuthSessionViaCredentials"), + ); + return; + } + let Some(begin_response) = + crate::pb::cauthentication::BeginAuthSessionViaCredentialsResponse::deserialize( + &begin_job.body, + ) + else { + dispatch_auth_result( + &callback, + AuthSessionResult { + error_message: "BeginAuthSessionViaCredentials parse failed".to_string(), + ..Default::default() + }, + ); + return; + }; + let pending = match pending_credentials_from_begin_response(begin_response) { + Ok(pending) => pending, + Err(result) => { + dispatch_auth_result(&callback, result); + return; + } + }; + let chosen_guard = choose_guard_confirmation(&pending.allowed_confirmations); + match chosen_guard { + crate::pb::cauthentication::EAuthSessionGuardType::DeviceConfirmation => { + if let Some(authenticator) = authenticator.as_ref() { + // Mirrors C++: the boolean is informational. The phone tap is + // observed through the poll loop regardless of whether the UI + // dismissed the prompt. + let _ = authenticator_accept_device_confirmation(authenticator); + } + } + crate::pb::cauthentication::EAuthSessionGuardType::DeviceCode => { + let Some(authenticator) = authenticator.as_ref() else { + dispatch_auth_result( + &callback, + AuthSessionResult { + error_message: "Steam Guard device code required".to_string(), + ..Default::default() + }, + ); + return; + }; + let Some(code) = authenticator_get_device_code(authenticator, false) else { + if cancel.load(Ordering::Relaxed) { + return; + } + dispatch_auth_result( + &callback, + AuthSessionResult { + error_message: "Steam Guard device code was not provided".to_string(), + ..Default::default() + }, + ); + return; + }; + let update = build_guard_code_request( + pending.client_id, + pending.steamid, + chosen_guard, + code, + ); + let update_job = match request_service_method_job( + &runtime, + "Authentication.UpdateAuthSessionWithSteamGuardCode#1", + false, + update.serialize(), + timeout, + Some(cancel.as_ref()), + ) { + Some(job) => job, + None => { + if cancel.load(Ordering::Relaxed) { + return; + } + dispatch_auth_result( + &callback, + AuthSessionResult { + error_message: "UpdateAuthSessionWithSteamGuardCode timed out" + .to_string(), + ..Default::default() + }, + ); + return; + } + }; + // C++ accepts eresult 1 (OK) or 29 (DuplicateRequest — same code + // resubmitted) as a non-fatal "proceed to poll". + if !crate::auth_session::guard_update_succeeded(&update_job) { + dispatch_auth_result( + &callback, + auth_result_from_job(&update_job, "UpdateAuthSessionWithSteamGuardCode"), + ); + return; + } + } + crate::pb::cauthentication::EAuthSessionGuardType::EmailCode => { + let Some(authenticator) = authenticator.as_ref() else { + dispatch_auth_result( + &callback, + AuthSessionResult { + error_message: "Steam Guard email code required".to_string(), + ..Default::default() + }, + ); + return; + }; + let email = pending + .allowed_confirmations + .iter() + .find(|confirmation| confirmation.confirmation_type == chosen_guard) + .map(|confirmation| confirmation.associated_message.as_str()) + .unwrap_or_default(); + let Some(code) = authenticator_get_email_code(authenticator, email, false) else { + if cancel.load(Ordering::Relaxed) { + return; + } + dispatch_auth_result( + &callback, + AuthSessionResult { + error_message: "Steam Guard email code was not provided".to_string(), + ..Default::default() + }, + ); + return; + }; + let update = build_guard_code_request( + pending.client_id, + pending.steamid, + chosen_guard, + code, + ); + let update_job = match request_service_method_job( + &runtime, + "Authentication.UpdateAuthSessionWithSteamGuardCode#1", + false, + update.serialize(), + timeout, + Some(cancel.as_ref()), + ) { + Some(job) => job, + None => { + if cancel.load(Ordering::Relaxed) { + return; + } + dispatch_auth_result( + &callback, + AuthSessionResult { + error_message: "UpdateAuthSessionWithSteamGuardCode timed out" + .to_string(), + ..Default::default() + }, + ); + return; + } + }; + if !crate::auth_session::guard_update_succeeded(&update_job) { + dispatch_auth_result( + &callback, + auth_result_from_job(&update_job, "UpdateAuthSessionWithSteamGuardCode"), + ); + return; + } + } + _ => {} + } + + let poll_interval = Duration::from_secs_f32(pending.poll_interval_seconds.max(0.25)); + let mut client_id = pending.client_id; + let fallback_account_name = config.username.clone(); + loop { + for slice in sleep_slices(poll_interval, Duration::from_millis(100)) { + if cancel.load(Ordering::Relaxed) { + return; + } + thread::sleep(slice); + } + if cancel.load(Ordering::Relaxed) { + return; + } + let poll_job = match request_service_method_job( + &runtime, + "Authentication.PollAuthSessionStatus#1", + false, + build_poll_request(client_id, pending.request_id.clone()).serialize(), + timeout, + Some(cancel.as_ref()), + ) { + Some(job) => job, + None => { + if cancel.load(Ordering::Relaxed) { + return; + } + dispatch_auth_result( + &callback, + AuthSessionResult { + error_message: "PollAuthSessionStatus timed out".to_string(), + ..Default::default() + }, + ); + return; + } + }; + if poll_job.synthetic_failure || poll_job.eresult != 1 { + dispatch_auth_result( + &callback, + auth_result_from_job(&poll_job, "PollAuthSessionStatus"), + ); + return; + } + let Some(resp) = crate::pb::cauthentication::PollAuthSessionStatusResponse::deserialize( + &poll_job.body, + ) else { + dispatch_auth_result( + &callback, + AuthSessionResult { + error_message: "Poll response parse failed".to_string(), + ..Default::default() + }, + ); + return; + }; + if resp.new_client_id != 0 { + client_id = resp.new_client_id; + } + if let Some(mut result) = auth_result_from_poll(resp, pending.steamid) { + apply_account_name_fallback(&mut result, &fallback_account_name); + dispatch_auth_result(&callback, result); + return; + } + } + }); +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeStartLoginWithQr( + mut env: JNIEnv, + _class: JClass, + handle: jlong, + qr_callback: JObject, + result_callback: JObject, +) { + let Some(handle) = (unsafe { from_session_handle_mut(handle) }) else { + call_auth_result_failure(&mut env, &result_callback, 2, "session closed"); + return; + }; + let Some(runtime) = handle.connected_runtime() else { + call_auth_result_failure( + &mut env, + &result_callback, + 2, + "Rust QR auth transport is not connected", + ); + return; + }; + let Ok(qr_callback) = env.new_global_ref(qr_callback) else { + clear_pending_exception(&mut env); + call_auth_result_failure(&mut env, &result_callback, 2, "QR callback unavailable"); + return; + }; + let Ok(result_callback) = env.new_global_ref(result_callback) else { + clear_pending_exception(&mut env); + return; + }; + let cancel = handle.begin_login_cancel(); + thread::spawn(move || { + let timeout = Duration::from_secs(30); + let request = build_qr_begin_request(&QrAuthConfig::default()).serialize(); + qr_log("BeginAuthSessionViaQR request queued"); + let begin_job = match request_service_method_job( + &runtime, + "Authentication.BeginAuthSessionViaQR#1", + false, + request, + timeout, + Some(cancel.as_ref()), + ) { + Some(job) => job, + None => { + if cancel.load(Ordering::Relaxed) { + qr_log("BeginAuthSessionViaQR cancelled before response"); + return; + } + qr_log("BeginAuthSessionViaQR timed out"); + dispatch_auth_result( + &result_callback, + AuthSessionResult { + error_message: "BeginAuthSessionViaQR timed out".to_string(), + ..Default::default() + }, + ); + return; + } + }; + if begin_job.synthetic_failure || begin_job.eresult != 1 { + qr_log(&format!( + "BeginAuthSessionViaQR failed synthetic={} eresult={} body_len={}", + begin_job.synthetic_failure, + begin_job.eresult, + begin_job.body.len() + )); + dispatch_auth_result( + &result_callback, + auth_result_from_job(&begin_job, "BeginAuthSessionViaQR"), + ); + return; + } + let Some(resp) = + crate::pb::cauthentication::BeginAuthSessionViaQrResponse::deserialize(&begin_job.body) + else { + qr_log(&format!( + "BeginAuthSessionViaQR parse failed body_len={}", + begin_job.body.len() + )); + dispatch_auth_result( + &result_callback, + AuthSessionResult { + error_message: "QR response parse failed".to_string(), + ..Default::default() + }, + ); + return; + }; + let pending = pending_qr_from_begin_response(resp); + qr_log(&format!( + "BeginAuthSessionViaQR response client_id={} request_id_len={} interval_s={:.2} challenge_len={}", + pending.client_id, + pending.request_id.len(), + pending.poll_interval_seconds, + pending.challenge_url.len() + )); + if pending.client_id == 0 || pending.request_id.is_empty() { + qr_log("Steam rejected QR auth session"); + dispatch_auth_result( + &result_callback, + AuthSessionResult { + eresult: 5, + error_message: "Steam rejected the QR auth session".to_string(), + ..Default::default() + }, + ); + return; + } + dispatch_qr_challenge(&qr_callback, &pending.challenge_url); + start_qr_poll_loop(QrPollState { + runtime, + qr_callback, + result_callback, + cancel, + client_id: pending.client_id, + request_id: pending.request_id, + poll_interval_seconds: pending.poll_interval_seconds, + last_challenge_url: Arc::new(Mutex::new(pending.challenge_url)), + }); + }); +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeCancelLogin( + _env: JNIEnv, + _class: JClass, + handle: jlong, +) { + if let Some(handle) = unsafe { from_session_handle_mut(handle) } { + handle.cancel_login(); + } +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeLogonWithRefreshToken( + mut env: JNIEnv, + _class: JClass, + handle: jlong, + refresh_token: JString, + account_name: JString, + steam_id: jlong, +) -> jboolean { + let Some(handle) = (unsafe { from_session_handle_mut(handle) }) else { + return JNI_FALSE; + }; + let Some(refresh_token) = jstring_to_string(&mut env, &refresh_token) else { + return JNI_FALSE; + }; + if refresh_token.is_empty() { + return JNI_FALSE; + } + let account_name = jstring_to_string(&mut env, &account_name).unwrap_or_default(); + if handle.enqueue_proto(handle.core.build_logon_with_refresh_token( + refresh_token, + account_name, + steam_id as u64, + )) { + JNI_TRUE + } else { + JNI_FALSE + } +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativePrepareApp( + mut env: JNIEnv, + _class: JClass, + handle: jlong, + app_id: jint, + dlc_app_ids: JIntArray, + callback: JObject, +) { + let Some(handle) = (unsafe { from_session_handle_mut(handle) }) else { + call_prepare_result(&mut env, &callback, false, "session closed"); + return; + }; + if callback.is_null() { + return; + } + let Some(runtime) = handle.connected_runtime() else { + call_prepare_result(&mut env, &callback, false, "not connected"); + return; + }; + let dlc_app_ids = int_array_to_u32_vec(&env, &dlc_app_ids); + let all_ids = CMClientCore::prepare_app_ids(app_id as u32, &dlc_app_ids); + if all_ids.is_empty() { + call_prepare_result(&mut env, &callback, true, ""); + return; + } + + let missing_tokens = handle.core.prepare_app_missing_token_ids(&all_ids); + if !missing_tokens.is_empty() { + if let Some(body) = request_proto_body(&runtime, Duration::from_secs(30), |core, job_id| { + core.build_pics_access_tokens(Vec::new(), missing_tokens, job_id) + }) { + if let Some(response) = + crate::pb::cmsg_client_pics::CMsgClientPICSAccessTokenResponse::deserialize(&body) + { + handle.core.library().ingest_app_access_tokens(&response); + } + } + } + + let apps = handle.core.prepare_app_pics_requests(&all_ids); + let Some(response) = + request_pics_product_info(&runtime, Vec::new(), apps, false, Duration::from_secs(30)) + else { + call_prepare_result(&mut env, &callback, false, "PICS product info failed"); + return; + }; + handle.core.library().ingest_app_pics_response(&response); + + for app_id in all_ids { + let _ = request_app_ownership_ticket(&runtime, app_id, Duration::from_secs(30)); + } + call_prepare_result(&mut env, &callback, true, ""); +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeDownloadApp( + mut env: JNIEnv, + _class: JClass, + handle: jlong, + app_id: jint, + depot_ids: JIntArray, + manifest_ids: JLongArray, + branch: JString, + install_dir: JString, + fresh: jboolean, + ca_bundle_path: JString, + max_workers: jint, + listener: JObject, +) { + let Some(handle) = (unsafe { from_session_handle_mut(handle) }) else { + call_download_complete(&mut env, &listener, "session closed"); + return; + }; + if listener.is_null() { + return; + } + let Some(runtime) = handle.connected_runtime() else { + call_download_complete(&mut env, &listener, "not connected"); + return; + }; + let depot_ids = int_array_to_u32_vec(&env, &depot_ids); + let manifest_ids = long_array_to_u64_vec(&env, &manifest_ids); + if depot_ids.len() != manifest_ids.len() { + call_download_complete(&mut env, &listener, "depot/manifest array length mismatch"); + return; + } + let specs = depot_ids + .into_iter() + .zip(manifest_ids) + .map( + |(depot_id, manifest_id)| crate::depot_downloader::DepotSpec { + depot_id, + manifest_id, + }, + ) + .collect::>(); + let install_dir = jstring_to_string(&mut env, &install_dir).unwrap_or_default(); + if crate::depot_downloader::validate_download_inputs(&install_dir, &specs).is_err() { + call_download_complete(&mut env, &listener, "invalid download request"); + return; + } + let branch = jstring_to_string(&mut env, &branch).unwrap_or_else(|| "public".to_string()); + let ca_bundle_path = jstring_to_string(&mut env, &ca_bundle_path).unwrap_or_default(); + let Ok(listener) = env.new_global_ref(&listener) else { + call_download_complete(&mut env, &listener, "listener ref failed"); + return; + }; + let app_id = app_id.max(0) as u32; + let fresh = fresh != JNI_FALSE; + let max_workers = max_workers.max(1) as u32; + handle.download_cancel.store(false, Ordering::Relaxed); + let download_cancel = Arc::clone(&handle.download_cancel); + + thread::spawn(move || { + let timeout = Duration::from_secs(30); + if download_cancel.load(Ordering::Relaxed) { + dispatch_download_complete( + listener, + crate::depot_downloader::DepotDownloadResult::fail("cancelled"), + ); + return; + } + let Some(servers) = request_cdn_servers(&runtime, timeout) else { + dispatch_download_complete( + listener.clone(), + crate::depot_downloader::DepotDownloadResult::fail( + "download: CDN server request failed", + ), + ); + return; + }; + let config_dir = std::path::Path::new(&install_dir).join(".DepotDownloader"); + let installed_cfg = crate::depot_config::DepotConfigStore::load(&config_dir); + let mut resolved = Vec::with_capacity(specs.len()); + let mut denied: Vec = Vec::new(); + for spec in specs { + if download_cancel.load(Ordering::Relaxed) { + dispatch_download_complete( + listener, + crate::depot_downloader::DepotDownloadResult::fail("cancelled"), + ); + return; + } + // Already-installed depots need no key/code; the downloader's resume-skip handles them. + if !fresh && installed_cfg.is_installed(spec.depot_id, spec.manifest_id) { + resolved.push(crate::depot_downloader::ResolvedDepotSpec { + depot_id: spec.depot_id, + manifest_id: spec.manifest_id, + depot_key: Vec::new(), + manifest_request_code: 0, + }); + continue; + } + let depot_key = match resolve_depot_key_with_retry( + &runtime, + app_id, + spec.depot_id, + timeout, + download_cancel.as_ref(), + ) { + KeyResolution::Granted(key) => key, + // Access-denied means this account is not entitled; skip that depot, keep the rest. + KeyResolution::AccessDenied => { + denied.push(spec.depot_id); + continue; + } + KeyResolution::Cancelled => { + dispatch_download_complete( + listener, + crate::depot_downloader::DepotDownloadResult::fail("cancelled"), + ); + return; + } + KeyResolution::Unavailable => { + dispatch_download_complete( + listener.clone(), + crate::depot_downloader::DepotDownloadResult::fail(format!( + "download: depot key unavailable for depot {}", + spec.depot_id + )), + ); + return; + } + }; + let manifest_request_code = match resolve_manifest_code_with_retry( + &runtime, + app_id, + spec.depot_id, + spec.manifest_id, + &branch, + timeout, + download_cancel.as_ref(), + ) { + CodeResolution::Code(code) => code, + CodeResolution::AccessDenied => { + denied.push(spec.depot_id); + continue; + } + CodeResolution::Cancelled => { + dispatch_download_complete( + listener, + crate::depot_downloader::DepotDownloadResult::fail("cancelled"), + ); + return; + } + CodeResolution::Unavailable => { + dispatch_download_complete( + listener.clone(), + crate::depot_downloader::DepotDownloadResult::fail(format!( + "download: manifest request code unavailable for depot {}", + spec.depot_id + )), + ); + return; + } + }; + resolved.push(crate::depot_downloader::ResolvedDepotSpec { + depot_id: spec.depot_id, + manifest_id: spec.manifest_id, + depot_key, + manifest_request_code, + }); + } + write_denied_depots_marker(&config_dir, &denied); + if resolved.is_empty() { + dispatch_download_complete( + listener, + crate::depot_downloader::DepotDownloadResult::fail(if denied.is_empty() { + "download: no depots".to_string() + } else { + "download: no entitled depots (all depot keys denied)".to_string() + }), + ); + return; + } + let progress_listener = listener.clone(); + let progress = |progress: &crate::depot_downloader::DepotDownloadProgress| { + dispatch_download_progress(&progress_listener, progress); + }; + let progress_cb: crate::depot_downloader::DepotProgressCallback = &progress; + // Re-resolve manifest request codes right before each depot's manifest fetch. + let code_refresher = |depot_id: u32, manifest_id: u64| -> Option { + match resolve_manifest_code_with_retry( + &runtime, + app_id, + depot_id, + manifest_id, + &branch, + timeout, + download_cancel.as_ref(), + ) { + CodeResolution::Code(code) => Some(code), + _ => None, + } + }; + let code_refresher_cb: crate::depot_downloader::ManifestCodeRefresher = &code_refresher; + let result = + crate::depot_downloader::download_resolved_depots_with_cancel_progress( + &install_dir, + &resolved, + &servers, + &ca_bundle_path, + fresh, + max_workers, + Some(download_cancel.as_ref()), + Some(progress_cb), + Some(code_refresher_cb), + ); + dispatch_download_complete(listener, result); + }); +} + +fn dispatch_download_progress( + listener: &GlobalRef, + progress: &crate::depot_downloader::DepotDownloadProgress, +) { + let Some(vm) = JVM.get() else { + return; + }; + let Ok(mut env) = vm.attach_current_thread_as_daemon() else { + return; + }; + let _ = env.call_method( + listener.as_obj(), + "onProgress", + "(IJJIIZ)V", + &[ + JValue::Int(progress.depot_id as jint), + JValue::Long(progress.depot_done as jlong), + JValue::Long(progress.depot_total as jlong), + JValue::Int(progress.depots_done as jint), + JValue::Int(progress.depots_total as jint), + JValue::Bool(if progress.verifying { JNI_TRUE } else { JNI_FALSE }), + ], + ); + clear_pending_exception(&mut env); +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeCancelDownload( + _env: JNIEnv, + _class: JClass, + handle: jlong, +) { + if let Some(handle) = unsafe { from_session_handle_mut(handle) } { + handle.download_cancel.store(true, Ordering::Relaxed); + } +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeRequestEncryptedAppTicket( + env: JNIEnv, + _class: JClass, + handle: jlong, + app_id: jint, +) -> jbyteArray { + let Some(handle) = (unsafe { from_session_handle_mut(handle) }) else { + return ptr::null_mut(); + }; + if app_id <= 0 || handle.core.state() != ClientState::LoggedOn { + return ptr::null_mut(); + } + let Some(runtime) = handle.connected_runtime() else { + return ptr::null_mut(); + }; + + let app_id = app_id as u32; + let job_id = runtime.next_job_id(); + let Some(message) = runtime + .core() + .build_request_encrypted_app_ticket(app_id, job_id) + else { + return ptr::null_mut(); + }; + + let (tx, rx) = mpsc::channel(); + runtime.track_job( + job_id, + move |job| { + let ticket = if job.synthetic_failure { + Vec::new() + } else { + crate::pb::cmsg_client_request_encrypted_app_ticket::CMsgClientRequestEncryptedAppTicketResponse::deserialize(&job.body) + .map(|response| response.encrypted_app_ticket) + .unwrap_or_default() + }; + let _ = tx.send(ticket); + }, + Some(Duration::from_secs(30)), + ); + if !runtime.core().enqueue_wire(message.wire) { + return ptr::null_mut(); + } + runtime.flush_outbound(); + + let Ok(ticket) = rx.recv_timeout(Duration::from_secs(30)) else { + return ptr::null_mut(); + }; + if ticket.is_empty() { + return ptr::null_mut(); + } + byte_array_or_null(&env, &ticket) +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeGetUserStatsSchema( + env: JNIEnv, + _class: JClass, + handle: jlong, + app_id: jint, +) -> jbyteArray { + let Some(handle) = (unsafe { from_session_handle_mut(handle) }) else { + return ptr::null_mut(); + }; + let Some(runtime) = handle.connected_runtime() else { + return ptr::null_mut(); + }; + let Some(response) = + request_user_stats_response(&runtime, app_id.max(0) as u32, Duration::from_secs(30)) + else { + return ptr::null_mut(); + }; + if response.schema.is_empty() { + return ptr::null_mut(); + } + byte_array_or_null(&env, &response.schema) +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeGetUserStatsFull( + mut env: JNIEnv, + _class: JClass, + handle: jlong, + app_id: jint, +) -> jstring { + let Some(handle) = (unsafe { from_session_handle_mut(handle) }) else { + return ptr::null_mut(); + }; + let Some(runtime) = handle.connected_runtime() else { + return ptr::null_mut(); + }; + let Some(response) = + request_user_stats_response(&runtime, app_id.max(0) as u32, Duration::from_secs(30)) + else { + return ptr::null_mut(); + }; + let value = json!({ + "eresult": response.eresult, + "crcStats": response.crc_stats, + "schema": crate::cdn_client::hex_encode(&response.schema), + "achievementBlocks": response.achievement_blocks.iter().map(|block| json!({ + "achievementId": block.achievement_id, + "unlockTimes": block.unlock_time, + })).collect::>(), + }) + .to_string(); + new_string_or_null(&mut env, &value) +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeGetItemDefArchive( + mut env: JNIEnv, + _class: JClass, + handle: jlong, + app_id: jint, + ca_bundle_path: JString, +) -> jbyteArray { + let Some(handle) = (unsafe { from_session_handle_mut(handle) }) else { + return ptr::null_mut(); + }; + let Some(runtime) = handle.connected_runtime() else { + return ptr::null_mut(); + }; + let app_id = app_id.max(0) as u32; + let Some(digest) = request_item_def_digest(&runtime, app_id, Duration::from_secs(30)) else { + return ptr::null_mut(); + }; + let ca_bundle_path = jstring_to_string(&mut env, &ca_bundle_path).unwrap_or_default(); + let Some(body) = crate::cdn_client::CdnClient::new(ca_bundle_path).fetch_item_def_archive( + app_id, + &digest, + Duration::from_secs(30), + ) else { + return ptr::null_mut(); + }; + byte_array_or_null(&env, &body) +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeGetSubscribedWorkshopItems( + mut env: JNIEnv, + _class: JClass, + handle: jlong, + app_id: jint, +) -> jstring { + let Some(handle) = (unsafe { from_session_handle_mut(handle) }) else { + return ptr::null_mut(); + }; + let Some(runtime) = handle.connected_runtime() else { + return ptr::null_mut(); + }; + let app_id = app_id.max(0) as u32; + const PAGE_SIZE: u32 = 100; + const MAX_PAGES: u32 = 50; + let mut total = 0u32; + let mut all = Vec::new(); + for page in 1..=MAX_PAGES { + let Some(response) = request_subscribed_workshop_page( + &runtime, + app_id, + page, + PAGE_SIZE, + Duration::from_secs(30), + ) else { + return ptr::null_mut(); + }; + if page == 1 { + total = response.total; + } + let count = response.publishedfiledetails.len(); + all.extend(response.publishedfiledetails); + if count == 0 || (total != 0 && all.len() >= total as usize) { + break; + } + } + let value = json!(all + .iter() + .map(|detail| json!({ + "publishedFileId": detail.publishedfileid, + "appId": if detail.consumer_appid != 0 { detail.consumer_appid } else { app_id }, + "title": detail.title, + "fileName": detail.filename, + "fileUrl": detail.file_url, + "previewUrl": detail.preview_url, + "fileSizeBytes": detail.file_size, + "hcontentFile": detail.hcontent_file, + "timeUpdated": detail.time_updated, + })) + .collect::>()) + .to_string(); + new_string_or_null(&mut env, &value) +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeDownloadWorkshopItem( + mut env: JNIEnv, + _class: JClass, + handle: jlong, + app_id: jint, + manifest_id: jlong, + install_dir: JString, + ca_bundle_path: JString, + max_workers: jint, +) -> jlong { + let Some(handle) = (unsafe { from_session_handle_mut(handle) }) else { + return -1; + }; + let Some(runtime) = handle.connected_runtime() else { + return -1; + }; + let install_dir = jstring_to_string(&mut env, &install_dir).unwrap_or_default(); + if install_dir.is_empty() || app_id <= 0 || manifest_id <= 0 { + return -1; + } + let ca_bundle_path = jstring_to_string(&mut env, &ca_bundle_path).unwrap_or_default(); + let app_id = app_id as u32; + let manifest_id = manifest_id as u64; + let timeout = Duration::from_secs(30); + let Some(servers) = request_cdn_servers(&runtime, timeout) else { + return -1; + }; + let DepotKeyOutcome::Granted(depot_key) = + request_depot_key(&runtime, app_id, app_id, timeout) + else { + return -1; + }; + let ManifestCodeOutcome::Code(manifest_request_code) = + request_manifest_request_code(&runtime, app_id, app_id, manifest_id, "public", timeout) + else { + return -1; + }; + let spec = crate::depot_downloader::ResolvedDepotSpec { + depot_id: app_id, + manifest_id, + depot_key, + manifest_request_code, + }; + let result = crate::depot_downloader::download_resolved_depots( + &install_dir, + &[spec], + &servers, + &ca_bundle_path, + true, + max_workers.max(1) as u32, + ); + if result.success { + result.bytes_written as jlong + } else { + -1 + } +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeGetCloudFileList( + mut env: JNIEnv, + _class: JClass, + handle: jlong, + app_id: jint, +) -> jstring { + let Some(handle) = (unsafe { from_session_handle_mut(handle) }) else { + return ptr::null_mut(); + }; + let Some(runtime) = handle.connected_runtime() else { + return ptr::null_mut(); + }; + let app_id = app_id.max(0) as u32; + let Some(body) = + request_authed_service_body(&runtime, Duration::from_secs(30), |core, job_id| { + core.build_cloud_app_file_changelist_call(app_id, 0, job_id) + }) + else { + return ptr::null_mut(); + }; + let Some(response) = crate::pb::ccloud::CCloudGetAppFileChangelistResponse::deserialize(&body) + else { + return ptr::null_mut(); + }; + let value = json!({ + "currentChangeNumber": response.current_change_number, + "pathPrefixes": response.path_prefixes, + "machineNames": response.machine_names, + "files": response.files.iter().map(|file| json!({ + "fileName": file.file_name, + "sha": crate::cdn_client::hex_encode(&file.sha_file), + "timestamp": file.time_stamp, + "size": file.raw_file_size, + "persistState": file.persist_state, + "pathPrefixIndex": file.path_prefix_index, + "machineNameIndex": file.machine_name_index, + })).collect::>(), + }) + .to_string(); + new_string_or_null(&mut env, &value) +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeGetCloudUserQuota( + env: JNIEnv, + _class: JClass, + handle: jlong, +) -> jlongArray { + let Some(handle) = (unsafe { from_session_handle_mut(handle) }) else { + return ptr::null_mut(); + }; + let Some(runtime) = handle.connected_runtime() else { + return ptr::null_mut(); + }; + let Some(body) = + request_authed_service_body(&runtime, Duration::from_secs(30), |core, job_id| { + core.build_cloud_user_quota_call(job_id) + }) + else { + return ptr::null_mut(); + }; + let Some(response) = crate::pb::ccloud::CCloudGetUserQuotaResponse::deserialize(&body) else { + return ptr::null_mut(); + }; + let Ok(array) = env.new_long_array(2) else { + return ptr::null_mut(); + }; + let values = [response.total_bytes as jlong, response.used_bytes as jlong]; + if env.set_long_array_region(&array, 0, &values).is_err() { + return ptr::null_mut(); + } + array.into_raw() +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeGetCloudDownloadInfo( + mut env: JNIEnv, + _class: JClass, + handle: jlong, + app_id: jint, + filename: JString, +) -> jstring { + let Some(handle) = (unsafe { from_session_handle_mut(handle) }) else { + return ptr::null_mut(); + }; + let Some(runtime) = handle.connected_runtime() else { + return ptr::null_mut(); + }; + let app_id = app_id.max(0) as u32; + let Some(filename) = jstring_to_string(&mut env, &filename) else { + return ptr::null_mut(); + }; + let Some(body) = + request_authed_service_body(&runtime, Duration::from_secs(30), |core, job_id| { + core.build_cloud_file_download_info_call(app_id, filename, job_id) + }) + else { + return ptr::null_mut(); + }; + let Some(response) = crate::pb::ccloud::CCloudClientFileDownloadResponse::deserialize(&body) + else { + return ptr::null_mut(); + }; + if response.url_host.is_empty() { + return ptr::null_mut(); + } + let value = json!({ + "fileSize": response.file_size, + "rawFileSize": response.raw_file_size, + "sha": crate::cdn_client::hex_encode(&response.sha_file), + "timestamp": response.time_stamp, + "urlHost": response.url_host, + "urlPath": response.url_path, + "useHttps": response.use_https, + "encrypted": response.encrypted, + "headers": response.request_headers.iter().map(|header| json!({ + "name": header.name, + "value": header.value, + })).collect::>(), + }) + .to_string(); + new_string_or_null(&mut env, &value) +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeCloudBeginUploadBatch( + mut env: JNIEnv, + _class: JClass, + handle: jlong, + app_id: jint, + files: JString, + files_to_delete: JString, + client_id: jlong, +) -> jstring { + let Some(handle) = (unsafe { from_session_handle_mut(handle) }) else { + return ptr::null_mut(); + }; + let Some(runtime) = handle.connected_runtime() else { + return ptr::null_mut(); + }; + let app_id = app_id.max(0) as u32; + let files = split_nonempty_lines(&jstring_to_string(&mut env, &files).unwrap_or_default()); + let files_to_delete = + split_nonempty_lines(&jstring_to_string(&mut env, &files_to_delete).unwrap_or_default()); + let Some(body) = + request_authed_service_body(&runtime, Duration::from_secs(30), |core, job_id| { + core.build_cloud_begin_app_upload_batch_call( + app_id, + String::new(), + files, + files_to_delete, + client_id as u64, + job_id, + ) + }) + else { + return ptr::null_mut(); + }; + let Some(response) = crate::pb::ccloud::CCloudBeginAppUploadBatchResponse::deserialize(&body) + else { + return ptr::null_mut(); + }; + let value = json!({ + "batchId": response.batch_id, + "appChangeNumber": response.app_change_number, + }) + .to_string(); + new_string_or_null(&mut env, &value) +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeCloudBeginFileUpload( + mut env: JNIEnv, + _class: JClass, + handle: jlong, + app_id: jint, + filename: JString, + file_size: jint, + raw_file_size: jint, + sha_hex: JString, + timestamp: jlong, + batch_id: jlong, +) -> jstring { + let Some(handle) = (unsafe { from_session_handle_mut(handle) }) else { + return ptr::null_mut(); + }; + let Some(runtime) = handle.connected_runtime() else { + return ptr::null_mut(); + }; + let Some(filename) = jstring_to_string(&mut env, &filename) else { + return ptr::null_mut(); + }; + let Some(sha_hex) = jstring_to_string(&mut env, &sha_hex) else { + return ptr::null_mut(); + }; + let Some(file_sha) = decode_hex(&sha_hex) else { + return ptr::null_mut(); + }; + let app_id = app_id.max(0) as u32; + let Some(body) = + request_authed_service_body(&runtime, Duration::from_secs(30), |core, job_id| { + core.build_cloud_begin_file_upload_call( + app_id, + filename, + file_size as u32, + raw_file_size as u32, + file_sha, + timestamp as u64, + batch_id as u64, + job_id, + ) + }) + else { + return ptr::null_mut(); + }; + let Some(response) = crate::pb::ccloud::CCloudClientBeginFileUploadResponse::deserialize(&body) + else { + return ptr::null_mut(); + }; + let value = json!({ + "encryptFile": response.encrypt_file, + "blocks": response.block_requests.iter().map(|block| json!({ + "urlHost": block.url_host, + "urlPath": block.url_path, + "useHttps": block.use_https, + "httpMethod": block.http_method, + "blockOffset": block.block_offset, + "blockLength": block.block_length, + "mayParallelize": block.may_parallelize, + "headers": block.request_headers.iter().map(|header| json!({ + "name": header.name, + "value": header.value, + })).collect::>(), + })).collect::>(), + }) + .to_string(); + new_string_or_null(&mut env, &value) +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeCloudCommitFileUpload( + mut env: JNIEnv, + _class: JClass, + handle: jlong, + transfer_succeeded: jboolean, + app_id: jint, + sha_hex: JString, + filename: JString, +) -> jboolean { + let Some(handle) = (unsafe { from_session_handle_mut(handle) }) else { + return JNI_FALSE; + }; + let Some(runtime) = handle.connected_runtime() else { + return JNI_FALSE; + }; + let Some(sha_hex) = jstring_to_string(&mut env, &sha_hex) else { + return JNI_FALSE; + }; + let Some(filename) = jstring_to_string(&mut env, &filename) else { + return JNI_FALSE; + }; + let Some(file_sha) = decode_hex(&sha_hex) else { + return JNI_FALSE; + }; + let app_id = app_id.max(0) as u32; + let Some(body) = + request_authed_service_body(&runtime, Duration::from_secs(30), |core, job_id| { + core.build_cloud_commit_file_upload_call( + transfer_succeeded != JNI_FALSE, + app_id, + file_sha, + filename, + job_id, + ) + }) + else { + return JNI_FALSE; + }; + if crate::pb::ccloud::CCloudClientCommitFileUploadResponse::deserialize(&body) + .map(|response| response.file_committed) + .unwrap_or(false) + { + JNI_TRUE + } else { + JNI_FALSE + } +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeCloudCompleteUploadBatch( + _env: JNIEnv, + _class: JClass, + handle: jlong, + app_id: jint, + batch_id: jlong, + batch_eresult: jint, +) -> jboolean { + let Some(handle) = (unsafe { from_session_handle_mut(handle) }) else { + return JNI_FALSE; + }; + let Some(runtime) = handle.connected_runtime() else { + return JNI_FALSE; + }; + if request_authed_service_success(&runtime, Duration::from_secs(30), |core, job_id| { + core.build_cloud_complete_app_upload_batch_call( + app_id.max(0) as u32, + batch_id as u64, + batch_eresult as u32, + job_id, + ) + }) { + JNI_TRUE + } else { + JNI_FALSE + } +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeGetPicsChangesSince( + mut env: JNIEnv, + _class: JClass, + handle: jlong, + since_change_number: jlong, +) -> jstring { + let Some(handle) = (unsafe { from_session_handle_mut(handle) }) else { + return ptr::null_mut(); + }; + let Some(runtime) = handle.connected_runtime() else { + return ptr::null_mut(); + }; + let Some(body) = request_proto_body(&runtime, Duration::from_secs(30), |core, job_id| { + core.build_pics_changes_since(since_change_number as u32, job_id) + }) else { + return ptr::null_mut(); + }; + let Some(response) = + crate::pb::cmsg_client_pics::CMsgClientPICSChangesSinceResponse::deserialize(&body) + else { + return ptr::null_mut(); + }; + let value = json!({ + "currentChangeNumber": response.current_change_number, + "forceFullUpdate": response.force_full_update, + "apps": response.app_changes.iter().map(|app| json!({ + "appid": app.appid, + "changeNumber": app.change_number, + "needsToken": app.needs_token, + })).collect::>(), + "packages": response.package_changes.iter().map(|package| json!({ + "packageid": package.packageid, + "changeNumber": package.change_number, + "needsToken": package.needs_token, + })).collect::>(), + }) + .to_string(); + new_string_or_null(&mut env, &value) +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeGetPicsAppInfo( + mut env: JNIEnv, + _class: JClass, + handle: jlong, + app_id: jint, + access_token: jlong, +) -> jstring { + let Some(handle) = (unsafe { from_session_handle_mut(handle) }) else { + return ptr::null_mut(); + }; + let Some(runtime) = handle.connected_runtime() else { + return ptr::null_mut(); + }; + let app_id = app_id.max(0) as u32; + let app = crate::pb::cmsg_client_pics::PicsAppInfoReq { + appid: app_id, + access_token: access_token as u64, + only_public_obsolete: false, + }; + let Some(response) = request_pics_product_info( + &runtime, + Vec::new(), + vec![app], + false, + Duration::from_secs(30), + ) else { + return ptr::null_mut(); + }; + for app in response.apps { + if app.appid != app_id || app.buffer.is_empty() { + continue; + } + let Some(root) = crate::vdf::parse_auto(&app.buffer) else { + return ptr::null_mut(); + }; + let appinfo = if root.name.eq_ignore_ascii_case("appinfo") { + &root + } else { + root.child("appinfo").unwrap_or(&root) + }; + let value = json!({ + "changeNumber": app.change_number, + "appinfo": kvnode_to_json_value(appinfo), + }) + .to_string(); + return new_string_or_null(&mut env, &value); + } + ptr::null_mut() +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeGetPicsAccessTokens( + mut env: JNIEnv, + _class: JClass, + handle: jlong, + app_ids: JString, + package_ids: JString, +) -> jstring { + let Some(handle) = (unsafe { from_session_handle_mut(handle) }) else { + return ptr::null_mut(); + }; + let Some(runtime) = handle.connected_runtime() else { + return ptr::null_mut(); + }; + let app_ids = parse_u32_lines(&jstring_to_string(&mut env, &app_ids).unwrap_or_default()); + let package_ids = + parse_u32_lines(&jstring_to_string(&mut env, &package_ids).unwrap_or_default()); + let Some(body) = request_proto_body(&runtime, Duration::from_secs(30), |core, job_id| { + core.build_pics_access_tokens(package_ids, app_ids, job_id) + }) else { + return ptr::null_mut(); + }; + let Some(response) = + crate::pb::cmsg_client_pics::CMsgClientPICSAccessTokenResponse::deserialize(&body) + else { + return ptr::null_mut(); + }; + let app_tokens = response + .app_access_tokens + .iter() + .map(|token| { + ( + token.appid.to_string(), + json!(token.access_token.to_string()), + ) + }) + .collect::>(); + let package_tokens = response + .package_access_tokens + .iter() + .map(|token| { + ( + token.packageid.to_string(), + json!(token.access_token.to_string()), + ) + }) + .collect::>(); + let value = json!({ + "appTokens": app_tokens, + "packageTokens": package_tokens, + }) + .to_string(); + new_string_or_null(&mut env, &value) +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeGetPicsAppProductInfo( + mut env: JNIEnv, + _class: JClass, + handle: jlong, + app_ids: JString, + tokens: JString, +) -> jstring { + let Some(handle) = (unsafe { from_session_handle_mut(handle) }) else { + return ptr::null_mut(); + }; + let Some(runtime) = handle.connected_runtime() else { + return ptr::null_mut(); + }; + let app_ids = parse_u32_lines(&jstring_to_string(&mut env, &app_ids).unwrap_or_default()); + let tokens = parse_u64_lines(&jstring_to_string(&mut env, &tokens).unwrap_or_default()); + let apps = app_ids + .into_iter() + .enumerate() + .map( + |(index, appid)| crate::pb::cmsg_client_pics::PicsAppInfoReq { + appid, + access_token: tokens.get(index).copied().unwrap_or_default(), + only_public_obsolete: false, + }, + ) + .collect::>(); + let Some(response) = + request_pics_product_info(&runtime, Vec::new(), apps, false, Duration::from_secs(30)) + else { + return ptr::null_mut(); + }; + let mut apps = Vec::new(); + for app in response.apps { + if app.buffer.is_empty() { + continue; + } + let Some(root) = crate::vdf::parse_auto(&app.buffer) else { + continue; + }; + let appinfo = if root.name.eq_ignore_ascii_case("appinfo") { + &root + } else { + root.child("appinfo").unwrap_or(&root) + }; + apps.push(json!({ + "appid": app.appid, + "changeNumber": app.change_number, + "appinfo": kvnode_to_json_value(appinfo), + })); + } + new_string_or_null(&mut env, &json!(apps).to_string()) +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeGetPicsPackageInfo( + mut env: JNIEnv, + _class: JClass, + handle: jlong, + package_ids: JString, + tokens: JString, +) -> jstring { + let Some(handle) = (unsafe { from_session_handle_mut(handle) }) else { + return ptr::null_mut(); + }; + let Some(runtime) = handle.connected_runtime() else { + return ptr::null_mut(); + }; + let package_ids = + parse_u32_lines(&jstring_to_string(&mut env, &package_ids).unwrap_or_default()); + let tokens = parse_u64_lines(&jstring_to_string(&mut env, &tokens).unwrap_or_default()); + let packages = package_ids + .into_iter() + .enumerate() + .map( + |(index, packageid)| crate::pb::cmsg_client_pics::PicsPackageInfoReq { + packageid, + access_token: tokens.get(index).copied().unwrap_or_default(), + }, + ) + .collect::>(); + let Some(response) = request_pics_product_info( + &runtime, + packages, + Vec::new(), + false, + Duration::from_secs(30), + ) else { + return ptr::null_mut(); + }; + let mut packages = Vec::new(); + for package in response.packages { + if package.buffer.is_empty() { + continue; + } + let Some((_package_id, root)) = crate::vdf::parse_binary_package(&package.buffer) else { + continue; + }; + let appids = root + .child("appids") + .map(|node| { + node.children + .iter() + .map(|child| child.as_uint(0)) + .collect::>() + }) + .unwrap_or_default(); + let depotids = root + .child("depotids") + .map(|node| { + node.children + .iter() + .map(|child| child.as_uint(0)) + .collect::>() + }) + .unwrap_or_default(); + packages.push(json!({ + "packageid": package.packageid, + "changeNumber": package.change_number, + "appids": appids, + "depotids": depotids, + })); + } + new_string_or_null(&mut env, &json!(packages).to_string()) +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeGetFamilyGroup( + mut env: JNIEnv, + _class: JClass, + handle: jlong, + family_group_id: jlong, +) -> jstring { + let Some(handle) = (unsafe { from_session_handle_mut(handle) }) else { + return ptr::null_mut(); + }; + let Some(runtime) = handle.connected_runtime() else { + return ptr::null_mut(); + }; + let Some(body) = + request_authed_service_body(&runtime, Duration::from_secs(30), |core, job_id| { + core.build_family_group_call(family_group_id as u64, job_id) + }) + else { + return ptr::null_mut(); + }; + let Some(response) = + crate::pb::cfamilygroups::CFamilyGroupsGetFamilyGroupResponse::deserialize(&body) + else { + return ptr::null_mut(); + }; + let value = json!({ + "name": response.name, + "members": response.members.iter().map(|member| member.steamid).collect::>(), + }) + .to_string(); + new_string_or_null(&mut env, &value) +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeGetOwnedGames( + mut env: JNIEnv, + _class: JClass, + handle: jlong, + steam_id: jlong, +) -> jstring { + let Some(handle) = (unsafe { from_session_handle_mut(handle) }) else { + return ptr::null_mut(); + }; + let Some(runtime) = handle.connected_runtime() else { + return ptr::null_mut(); + }; + let Some(body) = + request_authed_service_body(&runtime, Duration::from_secs(30), |core, job_id| { + core.build_owned_games_call(steam_id as u64, job_id) + }) + else { + return ptr::null_mut(); + }; + let Some(response) = crate::pb::cplayer::CPlayerGetOwnedGamesResponse::deserialize(&body) + else { + return ptr::null_mut(); + }; + let value = json!(response + .games + .iter() + .map(|game| json!({ + "appId": game.appid, + "name": game.name, + "playtimeTwoWeeks": game.playtime_2weeks, + "playtimeForever": game.playtime_forever, + "imgIconUrl": game.img_icon_url, + "sortAs": game.sort_as, + "rtimeLastPlayed": game.rtime_last_played, + })) + .collect::>()) + .to_string(); + new_string_or_null(&mut env, &value) +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeSignalAppLaunchIntent( + mut env: JNIEnv, + _class: JClass, + handle: jlong, + app_id: jint, + client_id: jlong, + machine_name: JString, + ignore_pending: jboolean, + os_type: jint, +) -> jstring { + let Some(handle) = (unsafe { from_session_handle_mut(handle) }) else { + return ptr::null_mut(); + }; + let Some(runtime) = handle.connected_runtime() else { + return ptr::null_mut(); + }; + let machine_name = jstring_to_string(&mut env, &machine_name).unwrap_or_default(); + let Some(body) = + request_authed_service_body(&runtime, Duration::from_secs(30), |core, job_id| { + core.build_cloud_launch_intent_call( + app_id.max(0) as u32, + client_id as u64, + machine_name, + ignore_pending != JNI_FALSE, + os_type, + job_id, + ) + }) + else { + return ptr::null_mut(); + }; + let Some(response) = crate::pb::ccloud::CCloudAppLaunchIntentResponse::deserialize(&body) + else { + return ptr::null_mut(); + }; + let value = json!({ + "pendingOps": response.pending_operation_codes, + }) + .to_string(); + new_string_or_null(&mut env, &value) +} + +#[no_mangle] +pub extern "system" fn Java_com_winlator_cmod_feature_stores_steam_wnsteam_WnSteamSession_nativeSignalAppExitSyncDone( + _env: JNIEnv, + _class: JClass, + handle: jlong, + app_id: jint, + client_id: jlong, + uploads_completed: jboolean, + uploads_required: jboolean, +) { + let Some(handle) = (unsafe { from_session_handle_mut(handle) }) else { + return; + }; + let Some(runtime) = handle.connected_runtime() else { + return; + }; + let _ = request_authed_service_success(&runtime, Duration::from_secs(5), |core, job_id| { + core.build_cloud_exit_sync_done_call( + app_id.max(0) as u32, + client_id as u64, + uploads_completed != JNI_FALSE, + uploads_required != JNI_FALSE, + job_id, + ) + }); +} + +#[cfg(test)] +mod tests { + use super::{parse_u32_lines, parse_u64_lines}; + + #[test] + fn parses_high_bit_token_from_signed_long_string() { + // High-bit u64 token arrives as a negative Long string; recover unsigned. + assert_eq!( + parse_u64_lines("-2956503589389641226"), + vec![15490240484319910390u64] + ); + // Plain unsigned strings still parse unchanged. + assert_eq!(parse_u64_lines("4984014265555654850"), vec![4984014265555654850u64]); + } + + #[test] + fn keeps_token_to_id_pairing_aligned_across_negatives() { + // A dropped negative line shifts later tokens onto the wrong package. + let ids = parse_u32_lines("305944\n322317\n304933"); + let tokens = parse_u64_lines("4984014265555654850\n-4562710670371372905\n3396749975682522332"); + assert_eq!(ids.len(), 3); + assert_eq!(tokens.len(), 3, "no line may be dropped or pairing misaligns"); + assert_eq!(tokens[1], (-4562710670371372905i64) as u64); + assert_eq!(tokens[2], 3396749975682522332u64); + } + + #[test] + fn parses_high_bit_u32_id_from_signed_int_string() { + assert_eq!(parse_u32_lines("-1"), vec![u32::MAX]); + assert_eq!(parse_u32_lines("601150"), vec![601150u32]); + } + + #[test] + fn blank_or_garbage_line_becomes_zero_without_shifting() { + // A blank/garbage line must keep its slot (0), not drop and misalign. + assert_eq!(parse_u64_lines("11\n\n22"), vec![11u64, 0, 22]); + assert_eq!(parse_u32_lines("11\nxx\n22"), vec![11u32, 0, 22]); + assert!(parse_u64_lines("").is_empty()); + } + + #[test] + fn appinfo_json_preserves_object_key_order() { + // The Kotlin appinfo decoder groups DLC depots positionally, so depot + // keys must stay in source order, not be sorted (BTreeMap default). + let mut depots = crate::vdf::KVNode::new("depots"); + for id in ["601151", "1432644", "601152"] { + depots.children.push(crate::vdf::KVNode::new(id)); + } + assert_eq!( + super::kvnode_to_json_value(&depots).to_string(), + r#"{"601151":{},"1432644":{},"601152":{}}"# + ); + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/job_manager.rs b/app/src/main/cpp/wn-steam-client/rust/src/job_manager.rs new file mode 100644 index 000000000..90c332833 --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/job_manager.rs @@ -0,0 +1,255 @@ +use std::collections::HashMap; +use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; +use std::sync::{Arc, Condvar, Mutex}; +use std::thread::{self, JoinHandle}; +use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; +use std::{panic, panic::AssertUnwindSafe}; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct JobResult { + pub eresult: i32, + pub error_message: String, + pub body: Vec, + pub synthetic_failure: bool, +} + +impl Default for JobResult { + fn default() -> Self { + Self { + eresult: 2, + error_message: String::new(), + body: Vec::new(), + synthetic_failure: false, + } + } +} + +type JobContinuation = Box; + +struct Entry { + cb: JobContinuation, + deadline: Instant, +} + +#[derive(Default)] +struct Pending { + jobs: HashMap, +} + +pub struct JobManager { + next_counter: AtomicU64, + process_epoch: u64, + default_timeout: Duration, + pending: Arc<(Mutex, Condvar)>, + stop: Arc, + timeout_thread: Option>, +} + +impl JobManager { + pub fn new(default_timeout: Duration) -> Self { + let pending = Arc::new((Mutex::new(Pending::default()), Condvar::new())); + let stop = Arc::new(AtomicBool::new(false)); + let thread_pending = Arc::clone(&pending); + let thread_stop = Arc::clone(&stop); + let timeout_thread = thread::spawn(move || timeout_loop(thread_pending, thread_stop)); + Self { + next_counter: AtomicU64::new(1), + process_epoch: make_process_epoch(), + default_timeout, + pending, + stop, + timeout_thread: Some(timeout_thread), + } + } + + pub fn next_job_id(&self) -> u64 { + let lo = self.next_counter.fetch_add(1, Ordering::Relaxed); + ((self.process_epoch & 0xFF_FFFF) << 40) | (lo & 0xFF_FFFF_FFFF) + } + + pub fn track(&self, job_id: u64, cb: F, timeout: Option) + where + F: FnOnce(JobResult) + Send + 'static, + { + let timeout = timeout.unwrap_or(self.default_timeout); + let (mu, cv) = &*self.pending; + let mut pending = mu.lock().expect("job manager poisoned"); + pending.jobs.insert( + job_id, + Entry { + cb: Box::new(cb), + deadline: Instant::now() + timeout, + }, + ); + cv.notify_all(); + } + + pub fn deliver( + &self, + job_id_target: u64, + mut eresult: i32, + error_message: String, + body: &[u8], + ) { + let cb = { + let (mu, _) = &*self.pending; + let mut pending = mu.lock().expect("job manager poisoned"); + pending.jobs.remove(&job_id_target).map(|entry| entry.cb) + }; + if let Some(cb) = cb { + if eresult == -1 { + eresult = 1; + } + invoke_continuation( + cb, + JobResult { + eresult, + error_message, + body: body.to_vec(), + synthetic_failure: false, + }, + ); + } + } + + pub fn fail_all(&self, reason: &str) { + let drained = { + let (mu, _) = &*self.pending; + let mut pending = mu.lock().expect("job manager poisoned"); + std::mem::take(&mut pending.jobs) + }; + for (_, entry) in drained { + invoke_continuation( + entry.cb, + JobResult { + eresult: -1, + error_message: reason.to_string(), + body: Vec::new(), + synthetic_failure: true, + }, + ); + } + } +} + +impl Default for JobManager { + fn default() -> Self { + Self::new(Duration::from_secs(30)) + } +} + +impl Drop for JobManager { + fn drop(&mut self) { + self.stop.store(true, Ordering::Relaxed); + self.pending.1.notify_all(); + if let Some(handle) = self.timeout_thread.take() { + let _ = handle.join(); + } + self.fail_all("JobManager shutting down"); + } +} + +fn timeout_loop(pending: Arc<(Mutex, Condvar)>, stop: Arc) { + let (mu, cv) = &*pending; + loop { + let mut guard = mu.lock().expect("job manager poisoned"); + while guard.jobs.is_empty() && !stop.load(Ordering::Relaxed) { + guard = cv.wait(guard).expect("job manager poisoned"); + } + if stop.load(Ordering::Relaxed) { + return; + } + let earliest = guard + .jobs + .values() + .map(|entry| entry.deadline) + .min() + .unwrap_or_else(Instant::now); + let now = Instant::now(); + if earliest > now { + let (g, _) = cv + .wait_timeout(guard, earliest - now) + .expect("job manager poisoned"); + guard = g; + } + if stop.load(Ordering::Relaxed) { + return; + } + let now = Instant::now(); + let expired_ids: Vec = guard + .jobs + .iter() + .filter_map(|(id, entry)| (entry.deadline <= now).then_some(*id)) + .collect(); + let expired: Vec = expired_ids + .into_iter() + .filter_map(|id| guard.jobs.remove(&id)) + .collect(); + drop(guard); + for entry in expired { + invoke_continuation( + entry.cb, + JobResult { + eresult: -1, + error_message: "job timeout".to_string(), + body: Vec::new(), + synthetic_failure: true, + }, + ); + } + } +} + +fn invoke_continuation(cb: JobContinuation, result: JobResult) { + let _ = panic::catch_unwind(AssertUnwindSafe(|| cb(result))); +} + +fn make_process_epoch() -> u64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_secs() +} + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::mpsc; + + #[test] + fn deliver_maps_missing_eresult_to_ok() { + let jm = JobManager::new(Duration::from_secs(30)); + let id = jm.next_job_id(); + let (tx, rx) = mpsc::channel(); + jm.track(id, move |result| tx.send(result).unwrap(), None); + jm.deliver(id, -1, String::new(), b"body"); + let result = rx.recv_timeout(Duration::from_secs(1)).unwrap(); + assert_eq!(result.eresult, 1); + assert_eq!(result.body, b"body"); + } + + #[test] + fn timeout_fails_jobs_synthetically() { + let jm = JobManager::new(Duration::from_millis(20)); + let id = jm.next_job_id(); + let (tx, rx) = mpsc::channel(); + jm.track(id, move |result| tx.send(result).unwrap(), None); + let result = rx.recv_timeout(Duration::from_secs(2)).unwrap(); + assert!(result.synthetic_failure); + assert_eq!(result.error_message, "job timeout"); + } + + #[test] + fn continuation_panic_is_caught() { + let jm = JobManager::new(Duration::from_secs(30)); + let id = jm.next_job_id(); + jm.track( + id, + |_| { + panic!("continuation panic"); + }, + None, + ); + jm.deliver(id, 1, String::new(), b"body"); + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/key_dictionary.rs b/app/src/main/cpp/wn-steam-client/rust/src/key_dictionary.rs new file mode 100644 index 000000000..f1ef73d5b --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/key_dictionary.rs @@ -0,0 +1,91 @@ +use crate::emsg::EUniverse; + +pub const PUBLIC_UNIVERSE_KEY: [u8; 160] = [ + 0x30, 0x81, 0x9D, 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, + 0x05, 0x00, 0x03, 0x81, 0x8B, 0x00, 0x30, 0x81, 0x87, 0x02, 0x81, 0x81, 0x00, 0xDF, 0xEC, 0x1A, + 0xD6, 0x2C, 0x10, 0x66, 0x2C, 0x17, 0x35, 0x3A, 0x14, 0xB0, 0x7C, 0x59, 0x11, 0x7F, 0x9D, 0xD3, + 0xD8, 0x2B, 0x7A, 0xE3, 0xE0, 0x15, 0xCD, 0x19, 0x1E, 0x46, 0xE8, 0x7B, 0x87, 0x74, 0xA2, 0x18, + 0x46, 0x31, 0xA9, 0x03, 0x14, 0x79, 0x82, 0x8E, 0xE9, 0x45, 0xA2, 0x49, 0x12, 0xA9, 0x23, 0x68, + 0x73, 0x89, 0xCF, 0x69, 0xA1, 0xB1, 0x61, 0x46, 0xBD, 0xC1, 0xBE, 0xBF, 0xD6, 0x01, 0x1B, 0xD8, + 0x81, 0xD4, 0xDC, 0x90, 0xFB, 0xFE, 0x4F, 0x52, 0x73, 0x66, 0xCB, 0x95, 0x70, 0xD7, 0xC5, 0x8E, + 0xBA, 0x1C, 0x7A, 0x33, 0x75, 0xA1, 0x62, 0x34, 0x46, 0xBB, 0x60, 0xB7, 0x80, 0x68, 0xFA, 0x13, + 0xA7, 0x7A, 0x8A, 0x37, 0x4B, 0x9E, 0xC6, 0xF4, 0x5D, 0x5F, 0x3A, 0x99, 0xF9, 0x9E, 0xC4, 0x3A, + 0xE9, 0x63, 0xA2, 0xBB, 0x88, 0x19, 0x28, 0xE0, 0xE7, 0x14, 0xC0, 0x42, 0x89, 0x02, 0x01, 0x11, +]; + +pub const BETA_UNIVERSE_KEY: [u8; 160] = [ + 0x30, 0x81, 0x9D, 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, + 0x05, 0x00, 0x03, 0x81, 0x8B, 0x00, 0x30, 0x81, 0x87, 0x02, 0x81, 0x81, 0x00, 0xAE, 0xD1, 0x4B, + 0xC0, 0xA3, 0x36, 0x8B, 0xA0, 0x39, 0x0B, 0x43, 0xDC, 0xED, 0x6A, 0xC8, 0xF2, 0xA3, 0xE4, 0x7E, + 0x09, 0x8C, 0x55, 0x2E, 0xE7, 0xE9, 0x3C, 0xBB, 0xE5, 0x5E, 0x0F, 0x18, 0x74, 0x54, 0x8F, 0xF3, + 0xBD, 0x56, 0x69, 0x5B, 0x13, 0x09, 0xAF, 0xC8, 0xBE, 0xB3, 0xA1, 0x48, 0x69, 0xE9, 0x83, 0x49, + 0x65, 0x8D, 0xD2, 0x93, 0x21, 0x2F, 0xB9, 0x1E, 0xFA, 0x74, 0x3B, 0x55, 0x22, 0x79, 0xBF, 0x85, + 0x18, 0xCB, 0x6D, 0x52, 0x44, 0x4E, 0x05, 0x92, 0x89, 0x6A, 0xA8, 0x99, 0xED, 0x44, 0xAE, 0xE2, + 0x66, 0x46, 0x42, 0x0C, 0xFB, 0x6E, 0x4C, 0x30, 0xC6, 0x6C, 0x5C, 0x16, 0xFF, 0xBA, 0x9C, 0xB9, + 0x78, 0x3F, 0x17, 0x4B, 0xCB, 0xC9, 0x01, 0x5D, 0x3E, 0x37, 0x70, 0xEC, 0x67, 0x5A, 0x33, 0x48, + 0xF7, 0x46, 0xCE, 0x58, 0xAA, 0xEC, 0xD9, 0xFF, 0x4A, 0x78, 0x6C, 0x83, 0x4B, 0x02, 0x01, 0x11, +]; + +pub const INTERNAL_UNIVERSE_KEY: [u8; 160] = [ + 0x30, 0x81, 0x9D, 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, + 0x05, 0x00, 0x03, 0x81, 0x8B, 0x00, 0x30, 0x81, 0x87, 0x02, 0x81, 0x81, 0x00, 0xA8, 0xFE, 0x01, + 0x3B, 0xB6, 0xD7, 0x21, 0x4B, 0x53, 0x23, 0x6F, 0xA1, 0xAB, 0x4E, 0xF1, 0x07, 0x30, 0xA7, 0xC6, + 0x7E, 0x6A, 0x2C, 0xC2, 0x5D, 0x3A, 0xB8, 0x40, 0xCA, 0x59, 0x4D, 0x16, 0x2D, 0x74, 0xEB, 0x0E, + 0x72, 0x46, 0x29, 0xF9, 0xDE, 0x9B, 0xCE, 0x4B, 0x8C, 0xD0, 0xCA, 0xF4, 0x08, 0x94, 0x46, 0xA5, + 0x11, 0xAF, 0x3A, 0xCB, 0xB8, 0x4E, 0xDE, 0xC6, 0xD8, 0x85, 0x0A, 0x7D, 0xAA, 0x96, 0x0A, 0xEA, + 0x7B, 0x51, 0xD6, 0x22, 0x62, 0x5C, 0x1E, 0x58, 0xD7, 0x46, 0x1E, 0x09, 0xAE, 0x43, 0xA7, 0xC4, + 0x34, 0x69, 0xA2, 0xA5, 0xE8, 0x44, 0x76, 0x18, 0xE2, 0x3D, 0xB7, 0xC5, 0xA8, 0x96, 0xFD, 0xE5, + 0xB4, 0x4B, 0xF8, 0x40, 0x12, 0xA6, 0x17, 0x4E, 0xC4, 0xC1, 0x60, 0x0E, 0xB0, 0xC2, 0xB8, 0x40, + 0x4D, 0x9E, 0x76, 0x4C, 0x44, 0xF4, 0xFC, 0x6F, 0x14, 0x89, 0x73, 0xB4, 0x13, 0x02, 0x01, 0x11, +]; + +pub const DEV_UNIVERSE_KEY: [u8; 160] = [ + 0x30, 0x81, 0x9D, 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, + 0x05, 0x00, 0x03, 0x81, 0x8B, 0x00, 0x30, 0x81, 0x87, 0x02, 0x81, 0x81, 0x00, 0xD0, 0x05, 0x2C, + 0xE9, 0x80, 0x95, 0xCD, 0x30, 0x83, 0xA8, 0xE9, 0x25, 0x96, 0x63, 0xCE, 0xCC, 0x48, 0x5D, 0x5C, + 0x52, 0x00, 0xDB, 0x1E, 0x78, 0xD7, 0x6A, 0x4C, 0x2C, 0xC8, 0x41, 0x8C, 0xCC, 0x87, 0x46, 0xFB, + 0x1B, 0xC9, 0xE8, 0x6E, 0x4F, 0x7A, 0x6B, 0xC3, 0xE7, 0x0F, 0xD5, 0xA9, 0x5D, 0x6C, 0xD4, 0xEE, + 0xA2, 0xCC, 0x80, 0x5A, 0xD3, 0xCE, 0x53, 0x59, 0xE6, 0x80, 0x91, 0xC4, 0xC0, 0xD5, 0xF0, 0x63, + 0x23, 0x91, 0x69, 0x70, 0xC5, 0xBB, 0xBD, 0x05, 0xE2, 0x4F, 0x7D, 0x90, 0x12, 0xED, 0xAC, 0x4F, + 0x86, 0x96, 0x3C, 0x89, 0xCC, 0x92, 0x15, 0x63, 0xCB, 0x57, 0x70, 0xB9, 0xC3, 0xAE, 0x08, 0x4F, + 0xC8, 0x56, 0x16, 0xB0, 0x0C, 0xC6, 0xC8, 0x8A, 0x80, 0xD2, 0x37, 0xF7, 0x7F, 0xAB, 0x93, 0xBB, + 0xE6, 0xDE, 0x95, 0x78, 0xB8, 0x11, 0xC9, 0xE5, 0x62, 0xAD, 0xBC, 0x0C, 0x87, 0x02, 0x01, 0x11, +]; + +pub fn get_universe_public_key(universe: EUniverse) -> &'static [u8] { + match universe { + EUniverse::Public => &PUBLIC_UNIVERSE_KEY, + EUniverse::Beta => &BETA_UNIVERSE_KEY, + EUniverse::Internal => &INTERNAL_UNIVERSE_KEY, + EUniverse::Dev => &DEV_UNIVERSE_KEY, + EUniverse::Invalid | EUniverse::Max => &[], + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn returns_keys_for_known_universes_only() { + assert_eq!(get_universe_public_key(EUniverse::Public).len(), 160); + assert_eq!(get_universe_public_key(EUniverse::Beta).len(), 160); + assert_eq!(get_universe_public_key(EUniverse::Internal).len(), 160); + assert_eq!(get_universe_public_key(EUniverse::Dev).len(), 160); + assert!(get_universe_public_key(EUniverse::Invalid).is_empty()); + } + + #[test] + fn keys_are_spki_rsa_with_exponent_17() { + for universe in [ + EUniverse::Public, + EUniverse::Beta, + EUniverse::Internal, + EUniverse::Dev, + ] { + let key = get_universe_public_key(universe); + assert_eq!(&key[..8], &[0x30, 0x81, 0x9D, 0x30, 0x0D, 0x06, 0x09, 0x2A]); + assert_eq!(&key[157..], &[0x02, 0x01, 0x11]); + } + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/lib.rs b/app/src/main/cpp/wn-steam-client/rust/src/lib.rs new file mode 100644 index 000000000..04df26dc0 --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/lib.rs @@ -0,0 +1,45 @@ +//! Rust implementation of the WinNative Steam client. +//! +//! The crate is intentionally split along the existing C++ module boundaries +//! so JNI and `wn_cm_*` C-ABI exports can migrate without changing the Kotlin +//! or `libsteamclient.so` contracts. + +#![allow(clippy::missing_safety_doc, clippy::result_large_err)] + +pub mod auth_session; +pub mod authenticator; +pub mod base64; +pub mod cdn_client; +pub mod chat_image; +pub mod cm_bridge; +pub mod cm_client; +pub mod cm_runtime; +pub mod cm_server; +pub mod cm_server_list; +pub mod cmsg_protobuf_header; +pub mod content_manifest; +pub mod crypto; +pub mod depot_chunk; +pub mod depot_config; +pub mod depot_downloader; +pub mod depot_writer; +pub mod emsg; +pub mod encrypted_channel; +pub mod handshake_messages; +pub mod heartbeat; +pub mod jni; +pub mod job_manager; +pub mod key_dictionary; +pub mod library_store; +pub mod pb; +pub mod proto_envelope; +pub mod proto_wire; +pub mod rsa_password; +pub mod steam_directory; +pub mod ticket_cache; +pub mod transport; +pub mod vdf; +pub mod version; +pub mod wine_bridge; +pub mod wire_format; +pub mod ws_connection; diff --git a/app/src/main/cpp/wn-steam-client/rust/src/library_store.rs b/app/src/main/cpp/wn-steam-client/rust/src/library_store.rs new file mode 100644 index 000000000..88943789d --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/library_store.rs @@ -0,0 +1,429 @@ +use crate::pb::cmsg_client_license_list::CMsgClientLicenseList; +use crate::pb::cmsg_client_pics::{ + CMsgClientPICSAccessTokenResponse, CMsgClientPICSProductInfoResponse, PicsAppInfoReq, + PicsPackageInfoReq, +}; +use crate::vdf::{self, KVNode}; +use serde_json::json; +use std::collections::HashMap; +use std::sync::{Arc, Mutex}; + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct OwnedPackage { + pub package_id: u32, + pub access_token: u64, + pub change_number: i32, + pub license_flags: u32, + pub license_type: u32, + pub pics_fetched: bool, + pub app_ids: Vec, + pub depot_ids: Vec, +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct OwnedApp { + pub app_id: u32, + pub change_number: u32, + pub name: String, + pub sort_as: String, + pub app_type: String, + pub os_list: String, + pub parent_app_id: u32, + pub dlc_app_ids: Vec, + pub build_id: u32, + pub source_package_ids: Vec, + pub pics_fetched: bool, + pub missing_token: bool, + pub access_token: u64, +} + +type SnapshotObserver = Arc; + +#[derive(Default)] +pub struct WnLibraryStore { + packages: Mutex>, + apps: Mutex>, + observer: Mutex>, +} + +impl WnLibraryStore { + pub fn ingest_license_list(&self, msg: &CMsgClientLicenseList) { + { + let mut packages = self.packages.lock().expect("library packages poisoned"); + for license in &msg.licenses { + let slot = packages.entry(license.package_id).or_default(); + slot.package_id = license.package_id; + if license.access_token != 0 { + slot.access_token = license.access_token; + } + if license.change_number > slot.change_number { + slot.change_number = license.change_number; + } + slot.license_flags = license.flags; + slot.license_type = license.license_type; + } + } + self.notify(); + } + + pub fn get_pending_package_pics_request(&self, max_count: usize) -> Vec { + let packages = self.packages.lock().expect("library packages poisoned"); + packages + .values() + .filter(|p| !p.pics_fetched) + .take(max_count) + .map(|p| PicsPackageInfoReq { + packageid: p.package_id, + access_token: p.access_token, + }) + .collect() + } + + pub fn get_pending_app_pics_request(&self, max_count: usize) -> Vec { + let apps = self.apps.lock().expect("library apps poisoned"); + apps.values() + .filter(|a| !a.pics_fetched && (!a.missing_token || a.access_token != 0)) + .take(max_count) + .map(|a| PicsAppInfoReq { + appid: a.app_id, + access_token: a.access_token, + only_public_obsolete: false, + }) + .collect() + } + + pub fn get_apps_needing_access_token(&self) -> Vec { + let apps = self.apps.lock().expect("library apps poisoned"); + apps.values() + .filter(|a| a.missing_token && a.access_token == 0) + .map(|a| a.app_id) + .collect() + } + + pub fn ingest_package_pics_response(&self, resp: &CMsgClientPICSProductInfoResponse) { + { + let mut packages = self.packages.lock().expect("library packages poisoned"); + let mut apps = self.apps.lock().expect("library apps poisoned"); + for package in &resp.packages { + let slot = packages.entry(package.packageid).or_default(); + slot.package_id = package.packageid; + slot.change_number = package.change_number as i32; + slot.pics_fetched = true; + if !package.buffer.is_empty() { + if let Some((_prefix, root)) = vdf::parse_binary_package(&package.buffer) { + extract_uint32_array(root.child("appids"), &mut slot.app_ids); + extract_uint32_array(root.child("depotids"), &mut slot.depot_ids); + for app_id in &slot.app_ids { + let app = apps.entry(*app_id).or_default(); + app.app_id = *app_id; + if !app.source_package_ids.contains(&package.packageid) { + app.source_package_ids.push(package.packageid); + } + } + } + } + } + for package_id in &resp.unknown_packageids { + if let Some(package) = packages.get_mut(package_id) { + package.pics_fetched = true; + } + } + } + self.notify(); + } + + pub fn ingest_app_pics_response(&self, resp: &CMsgClientPICSProductInfoResponse) { + { + let mut apps = self.apps.lock().expect("library apps poisoned"); + for app_resp in &resp.apps { + let app = apps.entry(app_resp.appid).or_default(); + app.app_id = app_resp.appid; + app.change_number = app_resp.change_number; + app.pics_fetched = true; + if app_resp.missing_token { + app.missing_token = true; + app.pics_fetched = false; + continue; + } + app.missing_token = false; + if app_resp.buffer.is_empty() { + continue; + } + let Some(root) = vdf::parse_auto(&app_resp.buffer) else { + continue; + }; + let appinfo = if root.name.eq_ignore_ascii_case("appinfo") { + &root + } else { + root.child("appinfo").unwrap_or(&root) + }; + if let Some(common) = appinfo.child("common") { + set_string(common, "name", &mut app.name); + set_string(common, "sortas", &mut app.sort_as); + set_string(common, "type", &mut app.app_type); + set_string(common, "oslist", &mut app.os_list); + if let Some(parent) = common.child("parent") { + app.parent_app_id = parent.as_uint(0) as u32; + } + } + if let Some(list) = appinfo + .child("extended") + .and_then(|extended| extended.child("listofdlc")) + { + app.dlc_app_ids.clear(); + parse_csv_appids(&list.as_string(""), &mut app.dlc_app_ids); + } + if let Some(buildid) = appinfo + .child("depots") + .and_then(|depots| depots.child("branches")) + .and_then(|branches| branches.child("public")) + .and_then(|public| public.child("buildid")) + { + app.build_id = buildid.as_uint(0) as u32; + } + let child_id = app.app_id; + let parent_id = app.parent_app_id; + if parent_id != 0 { + let parent = apps.entry(parent_id).or_default(); + parent.app_id = parent_id; + if !parent.dlc_app_ids.contains(&child_id) { + parent.dlc_app_ids.push(child_id); + } + } + } + for app_id in &resp.unknown_appids { + if let Some(app) = apps.get_mut(app_id) { + app.pics_fetched = true; + } + } + } + self.notify(); + } + + pub fn ingest_app_access_tokens(&self, resp: &CMsgClientPICSAccessTokenResponse) { + { + let mut apps = self.apps.lock().expect("library apps poisoned"); + for token in &resp.app_access_tokens { + let app = apps.entry(token.appid).or_default(); + app.app_id = token.appid; + app.access_token = token.access_token; + app.missing_token = false; + } + for app_id in &resp.app_denied_tokens { + if let Some(app) = apps.get_mut(app_id) { + app.pics_fetched = true; + app.missing_token = false; + } + } + } + self.notify(); + } + + pub fn packages(&self) -> Vec { + self.packages + .lock() + .expect("library packages poisoned") + .values() + .cloned() + .collect() + } + + pub fn apps(&self) -> Vec { + self.apps + .lock() + .expect("library apps poisoned") + .values() + .cloned() + .collect() + } + + pub fn owned_apps(&self) -> Vec { + self.apps() + .into_iter() + .filter(|app| !app.source_package_ids.is_empty()) + .collect() + } + + pub fn find_app(&self, app_id: u32) -> Option { + self.apps + .lock() + .expect("library apps poisoned") + .get(&app_id) + .cloned() + } + + pub fn package_count(&self) -> usize { + self.packages + .lock() + .expect("library packages poisoned") + .len() + } + + pub fn app_count(&self) -> usize { + self.apps.lock().expect("library apps poisoned").len() + } + + pub fn owned_app_count(&self) -> usize { + self.apps + .lock() + .expect("library apps poisoned") + .values() + .filter(|app| !app.source_package_ids.is_empty()) + .count() + } + + pub fn snapshot_json(&self) -> String { + let packages = self.packages(); + let apps = self.apps(); + let owned: Vec<_> = apps + .iter() + .filter(|app| !app.source_package_ids.is_empty()) + .collect(); + json!({ + "packages": packages.iter().map(|p| json!({ + "id": p.package_id, + "flags": p.license_flags, + "license_type": p.license_type, + "change_number": p.change_number, + "access_token": p.access_token.to_string(), + })).collect::>(), + "owned_apps": owned.iter().map(|a| json!({ + "id": a.app_id, + "change_number": a.change_number, + "name": a.name, + "type": a.app_type, + "sort_as": a.sort_as, + "os_list": a.os_list, + "parent": a.parent_app_id, + "access_token": a.access_token.to_string(), + "build_id": a.build_id, + "dlc": a.dlc_app_ids, + "src_packages": a.source_package_ids, + })).collect::>(), + "all_apps_count": apps.len(), + "owned_apps_count": owned.len(), + }) + .to_string() + } + + pub fn set_observer(&self, observer: F) + where + F: Fn() + Send + Sync + 'static, + { + *self.observer.lock().expect("library observer poisoned") = Some(Arc::new(observer)); + } + + fn notify(&self) { + let cb = self + .observer + .lock() + .expect("library observer poisoned") + .clone(); + if let Some(cb) = cb { + cb(); + } + } +} + +fn extract_uint32_array(parent: Option<&KVNode>, out: &mut Vec) { + if let Some(parent) = parent { + for child in &parent.children { + let value = child.as_uint(0); + if value != 0 { + out.push(value as u32); + } + } + } +} + +fn parse_csv_appids(csv: &str, out: &mut Vec) { + for part in csv.split(',') { + if let Ok(value) = part.trim().parse::() { + if value != 0 { + out.push(value); + } + } + } +} + +fn set_string(parent: &KVNode, key: &str, out: &mut String) { + if let Some(node) = parent.child(key) { + *out = node.as_string(out); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::pb::cmsg_client_license_list::{CMsgClientLicenseList, License}; + use crate::pb::cmsg_client_pics::{PicsAppInfoResp, PicsPackageInfoResp}; + + #[test] + fn ingests_license_and_emits_pending_package_request() { + let store = WnLibraryStore::default(); + store.ingest_license_list(&CMsgClientLicenseList { + eresult: 1, + licenses: vec![License { + package_id: 100, + access_token: 55, + change_number: 7, + ..Default::default() + }], + }); + let pending = store.get_pending_package_pics_request(10); + assert_eq!(pending[0].packageid, 100); + assert_eq!(pending[0].access_token, 55); + } + + #[test] + fn ingests_text_app_pics_and_links_parent_dlc() { + let store = WnLibraryStore::default(); + let text = br#""appinfo" { + "common" { "name" "DLC" "type" "DLC" "parent" "480" } + "extended" { "listofdlc" "481,482" } + "depots" { "branches" { "public" { "buildid" "99" } } } + }"#; + store.ingest_app_pics_response(&CMsgClientPICSProductInfoResponse { + apps: vec![PicsAppInfoResp { + appid: 481, + buffer: text.to_vec(), + ..Default::default() + }], + ..Default::default() + }); + let app = store.find_app(481).unwrap(); + assert_eq!(app.name, "DLC"); + assert_eq!(app.parent_app_id, 480); + assert_eq!(app.build_id, 99); + assert!(store.find_app(480).unwrap().dlc_app_ids.contains(&481)); + } + + #[test] + fn marks_unknown_packages_as_fetched() { + let store = WnLibraryStore::default(); + store.ingest_license_list(&CMsgClientLicenseList { + eresult: 1, + licenses: vec![License { + package_id: 100, + ..Default::default() + }], + }); + store.ingest_package_pics_response(&CMsgClientPICSProductInfoResponse { + packages: vec![PicsPackageInfoResp { + packageid: 101, + change_number: 1, + ..Default::default() + }], + unknown_packageids: vec![100], + ..Default::default() + }); + assert!( + store + .packages() + .into_iter() + .find(|p| p.package_id == 100) + .unwrap() + .pics_fetched + ); + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/pb/cauthentication.rs b/app/src/main/cpp/wn-steam-client/rust/src/pb/cauthentication.rs new file mode 100644 index 000000000..a72e6f77e --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/pb/cauthentication.rs @@ -0,0 +1,583 @@ +use crate::proto_wire::{Reader, WireType, Writer}; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[repr(i32)] +pub enum EAuthTokenPlatformType { + Unknown = 0, + SteamClient = 1, + WebBrowser = 2, + MobileApp = 3, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[repr(i32)] +pub enum EAuthSessionGuardType { + Unknown = 0, + None = 1, + EmailCode = 2, + DeviceCode = 3, + DeviceConfirmation = 4, + EmailConfirmation = 5, + MachineToken = 6, + LegacyMachineAuth = 7, +} + +impl From for EAuthSessionGuardType { + fn from(value: i32) -> Self { + match value { + 1 => Self::None, + 2 => Self::EmailCode, + 3 => Self::DeviceCode, + 4 => Self::DeviceConfirmation, + 5 => Self::EmailConfirmation, + 6 => Self::MachineToken, + 7 => Self::LegacyMachineAuth, + _ => Self::Unknown, + } + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[repr(i32)] +pub enum ESessionPersistence { + Invalid = -1, + Ephemeral = 0, + Persistent = 1, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct CAuthenticationDeviceDetails { + pub device_friendly_name: String, + pub platform_type: EAuthTokenPlatformType, + pub os_type: i32, + pub gaming_device_type: u32, + pub client_count: u32, + pub machine_id: Vec, +} + +impl Default for CAuthenticationDeviceDetails { + fn default() -> Self { + Self { + device_friendly_name: String::new(), + platform_type: EAuthTokenPlatformType::SteamClient, + os_type: 16, + gaming_device_type: 0, + client_count: 0, + machine_id: Vec::new(), + } + } +} + +impl CAuthenticationDeviceDetails { + pub fn serialize(&self) -> Vec { + let mut out = Vec::new(); + let mut w = Writer::new(&mut out); + w.string_field(1, &self.device_friendly_name); + w.int32_field(2, self.platform_type as i32); + w.int32_field(3, self.os_type); + w.uint32_field(4, self.gaming_device_type); + w.uint32_field(5, self.client_count); + w.bytes_field(6, &self.machine_id); + out + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct GetPasswordRsaPublicKeyRequest { + pub account_name: String, +} + +impl GetPasswordRsaPublicKeyRequest { + pub fn serialize(&self) -> Vec { + let mut out = Vec::new(); + Writer::new(&mut out).string_field(1, &self.account_name); + out + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct GetPasswordRsaPublicKeyResponse { + pub publickey_mod: String, + pub publickey_exp: String, + pub timestamp: u64, +} + +impl GetPasswordRsaPublicKeyResponse { + pub fn deserialize(body: &[u8]) -> Option { + let mut reader = Reader::new(body); + let mut msg = Self::default(); + while !reader.eof() { + let Some(tag) = reader.next_tag() else { + return reader.ok().then_some(msg); + }; + match tag.field_number { + 1 => msg.publickey_mod = reader.string()?, + 2 => msg.publickey_exp = reader.string()?, + 3 => msg.timestamp = reader.u64()?, + _ => { + if !reader.skip(tag.wire_type) { + return None; + } + } + } + } + Some(msg) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct BeginAuthSessionViaCredentialsRequest { + pub account_name: String, + pub encrypted_password: String, + pub encryption_timestamp: u64, + pub website_id: String, + pub persistence: ESessionPersistence, + pub device_details: CAuthenticationDeviceDetails, + pub guard_data: String, + pub language: u32, + pub qos_level: i32, +} + +impl Default for BeginAuthSessionViaCredentialsRequest { + fn default() -> Self { + Self { + account_name: String::new(), + encrypted_password: String::new(), + encryption_timestamp: 0, + website_id: "Client".to_string(), + persistence: ESessionPersistence::Persistent, + device_details: CAuthenticationDeviceDetails::default(), + guard_data: String::new(), + language: 0, + qos_level: 2, + } + } +} + +impl BeginAuthSessionViaCredentialsRequest { + pub fn serialize(&self) -> Vec { + let mut out = Vec::new(); + let mut w = Writer::new(&mut out); + w.string_field(2, &self.account_name); + w.string_field(3, &self.encrypted_password); + w.uint64_field(4, self.encryption_timestamp); + w.int32_field(7, self.persistence as i32); + w.string_field(8, &self.website_id); + let dd = self.device_details.serialize(); + if !dd.is_empty() { + w.submessage_field(9, &dd); + } + w.string_field(10, &self.guard_data); + w.uint32_field(11, self.language); + w.int32_field(12, self.qos_level); + out + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct AllowedConfirmation { + pub confirmation_type: EAuthSessionGuardType, + pub associated_message: String, +} + +impl Default for AllowedConfirmation { + fn default() -> Self { + Self { + confirmation_type: EAuthSessionGuardType::Unknown, + associated_message: String::new(), + } + } +} + +impl AllowedConfirmation { + pub fn deserialize(body: &[u8]) -> Option { + let mut reader = Reader::new(body); + let mut msg = Self::default(); + while !reader.eof() { + let Some(tag) = reader.next_tag() else { + return reader.ok().then_some(msg); + }; + match tag.field_number { + 1 => msg.confirmation_type = EAuthSessionGuardType::from(reader.i32()?), + 2 => msg.associated_message = reader.string()?, + _ => { + if !reader.skip(tag.wire_type) { + return None; + } + } + } + } + Some(msg) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct BeginAuthSessionViaCredentialsResponse { + pub client_id: u64, + pub request_id: Vec, + pub interval: f32, + pub allowed_confirmations: Vec, + pub steamid: u64, + pub weak_token: String, + pub agreement_session_url: String, + pub extended_error_message: String, +} + +impl Default for BeginAuthSessionViaCredentialsResponse { + fn default() -> Self { + Self { + client_id: 0, + request_id: Vec::new(), + interval: 5.0, + allowed_confirmations: Vec::new(), + steamid: 0, + weak_token: String::new(), + agreement_session_url: String::new(), + extended_error_message: String::new(), + } + } +} + +impl BeginAuthSessionViaCredentialsResponse { + pub fn deserialize(body: &[u8]) -> Option { + let mut reader = Reader::new(body); + let mut msg = Self::default(); + while !reader.eof() { + let Some(tag) = reader.next_tag() else { + return reader.ok().then_some(msg); + }; + match tag.field_number { + 1 => msg.client_id = reader.u64()?, + 2 => msg.request_id = reader.bytes()?.to_vec(), + 3 => { + if tag.wire_type != WireType::Fixed32 { + return None; + } + msg.interval = f32::from_bits(reader.fixed32()?); + } + 4 => msg + .allowed_confirmations + .push(AllowedConfirmation::deserialize(reader.bytes()?)?), + 5 => msg.steamid = reader.u64()?, + 6 => msg.weak_token = reader.string()?, + 7 => msg.agreement_session_url = reader.string()?, + 8 => msg.extended_error_message = reader.string()?, + _ => { + if !reader.skip(tag.wire_type) { + return None; + } + } + } + } + Some(msg) + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct PollAuthSessionStatusRequest { + pub client_id: u64, + pub request_id: Vec, + pub token_to_revoke: u64, +} + +impl PollAuthSessionStatusRequest { + pub fn serialize(&self) -> Vec { + let mut out = Vec::new(); + let mut w = Writer::new(&mut out); + w.uint64_field(1, self.client_id); + w.bytes_field(2, &self.request_id); + if self.token_to_revoke != 0 { + w.tag(3, WireType::Fixed64); + w.raw_bytes(&self.token_to_revoke.to_le_bytes()); + } + out + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct PollAuthSessionStatusResponse { + pub new_client_id: u64, + pub new_challenge_url: String, + pub refresh_token: String, + pub access_token: String, + pub had_remote_interaction: bool, + pub account_name: String, + pub new_guard_data: String, + pub agreement_session_url: String, +} + +impl PollAuthSessionStatusResponse { + pub fn deserialize(body: &[u8]) -> Option { + let mut reader = Reader::new(body); + let mut msg = Self::default(); + while !reader.eof() { + let Some(tag) = reader.next_tag() else { + return reader.ok().then_some(msg); + }; + match tag.field_number { + 1 => msg.new_client_id = reader.u64()?, + 2 => msg.new_challenge_url = reader.string()?, + 3 => msg.refresh_token = reader.string()?, + 4 => msg.access_token = reader.string()?, + 5 => msg.had_remote_interaction = reader.boolean()?, + 6 => msg.account_name = reader.string()?, + 7 => msg.new_guard_data = reader.string()?, + 8 => msg.agreement_session_url = reader.string()?, + _ => { + if !reader.skip(tag.wire_type) { + return None; + } + } + } + } + Some(msg) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct UpdateAuthSessionWithSteamGuardCodeRequest { + pub client_id: u64, + pub steamid: u64, + pub code: String, + pub code_type: EAuthSessionGuardType, +} + +impl Default for UpdateAuthSessionWithSteamGuardCodeRequest { + fn default() -> Self { + Self { + client_id: 0, + steamid: 0, + code: String::new(), + code_type: EAuthSessionGuardType::Unknown, + } + } +} + +impl UpdateAuthSessionWithSteamGuardCodeRequest { + pub fn serialize(&self) -> Vec { + let mut out = Vec::new(); + let mut w = Writer::new(&mut out); + w.uint64_field(1, self.client_id); + if self.steamid != 0 { + w.tag(2, WireType::Fixed64); + w.raw_bytes(&self.steamid.to_le_bytes()); + } + w.string_field(3, &self.code); + w.int32_field(4, self.code_type as i32); + out + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct BeginAuthSessionViaQrRequest { + pub device_friendly_name: String, + pub platform_type: EAuthTokenPlatformType, + pub device_details: CAuthenticationDeviceDetails, + pub website_id: String, +} + +impl Default for BeginAuthSessionViaQrRequest { + fn default() -> Self { + Self { + device_friendly_name: String::new(), + platform_type: EAuthTokenPlatformType::MobileApp, + device_details: CAuthenticationDeviceDetails::default(), + website_id: "Mobile".to_string(), + } + } +} + +impl BeginAuthSessionViaQrRequest { + pub fn serialize(&self) -> Vec { + let mut out = Vec::new(); + let mut w = Writer::new(&mut out); + w.string_field(1, &self.device_friendly_name); + w.int32_field(2, self.platform_type as i32); + let dd = self.device_details.serialize(); + if !dd.is_empty() { + w.submessage_field(3, &dd); + } + w.string_field(4, &self.website_id); + out + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct BeginAuthSessionViaQrResponse { + pub client_id: u64, + pub challenge_url: String, + pub request_id: Vec, + pub interval: f32, + pub allowed_confirmations: Vec, + pub version: i32, +} + +impl Default for BeginAuthSessionViaQrResponse { + fn default() -> Self { + Self { + client_id: 0, + challenge_url: String::new(), + request_id: Vec::new(), + interval: 5.0, + allowed_confirmations: Vec::new(), + version: 0, + } + } +} + +impl BeginAuthSessionViaQrResponse { + pub fn deserialize(body: &[u8]) -> Option { + let mut reader = Reader::new(body); + let mut msg = Self::default(); + while !reader.eof() { + let Some(tag) = reader.next_tag() else { + return reader.ok().then_some(msg); + }; + match tag.field_number { + 1 => msg.client_id = reader.u64()?, + 2 => msg.challenge_url = reader.string()?, + 3 => msg.request_id = reader.bytes()?.to_vec(), + 4 => { + if tag.wire_type != WireType::Fixed32 { + return None; + } + msg.interval = f32::from_bits(reader.fixed32()?); + } + 5 => msg + .allowed_confirmations + .push(AllowedConfirmation::deserialize(reader.bytes()?)?), + 6 => msg.version = reader.i32()?, + _ => { + if !reader.skip(tag.wire_type) { + return None; + } + } + } + } + Some(msg) + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[repr(i32)] +pub enum EAuthTokenRenewalType { + None = 0, + Allow = 1, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct AccessTokenGenerateForAppRequest { + pub refresh_token: String, + pub steamid: u64, + pub renewal_type: EAuthTokenRenewalType, +} + +impl Default for AccessTokenGenerateForAppRequest { + fn default() -> Self { + Self { + refresh_token: String::new(), + steamid: 0, + renewal_type: EAuthTokenRenewalType::None, + } + } +} + +impl AccessTokenGenerateForAppRequest { + pub fn serialize(&self) -> Vec { + let mut out = Vec::new(); + let mut w = Writer::new(&mut out); + w.string_field(1, &self.refresh_token); + w.fixed64_field(2, self.steamid); + w.int32_field(3, self.renewal_type as i32); + out + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct AccessTokenGenerateForAppResponse { + pub access_token: String, + pub refresh_token: String, +} + +impl AccessTokenGenerateForAppResponse { + pub fn deserialize(body: &[u8]) -> Option { + let mut reader = Reader::new(body); + let mut msg = Self::default(); + while !reader.eof() { + let Some(tag) = reader.next_tag() else { + return reader.ok().then_some(msg); + }; + match tag.field_number { + 1 => msg.access_token = reader.string()?, + 2 => msg.refresh_token = reader.string()?, + _ => { + if !reader.skip(tag.wire_type) { + return None; + } + } + } + } + Some(msg) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn credentials_request_uses_audited_field_numbers() { + let msg = BeginAuthSessionViaCredentialsRequest { + account_name: "user".to_string(), + encrypted_password: "enc".to_string(), + encryption_timestamp: 123, + ..Default::default() + }; + let bytes = msg.serialize(); + let fields = field_numbers(&bytes); + assert!(fields.contains(&2)); + assert!(fields.contains(&3)); + assert!(fields.contains(&4)); + assert!(fields.contains(&7)); + assert!(fields.contains(&8)); + assert!(fields.contains(&9)); + assert!(!fields.contains(&6)); + } + + #[test] + fn credentials_response_reads_float_interval_and_confirmations() { + let mut confirmation = Vec::new(); + { + let mut w = Writer::new(&mut confirmation); + w.int32_field(1, EAuthSessionGuardType::DeviceCode as i32); + w.string_field(2, "mobile"); + } + let mut bytes = Vec::new(); + { + let mut w = Writer::new(&mut bytes); + w.uint64_field(1, 55); + w.bytes_field(2, &[1, 2]); + w.tag(3, WireType::Fixed32); + w.raw_bytes(&2.5f32.to_bits().to_le_bytes()); + w.submessage_field(4, &confirmation); + w.uint64_field(5, 765); + } + let msg = BeginAuthSessionViaCredentialsResponse::deserialize(&bytes).unwrap(); + assert_eq!(msg.client_id, 55); + assert_eq!(msg.interval, 2.5); + assert_eq!( + msg.allowed_confirmations[0].confirmation_type, + EAuthSessionGuardType::DeviceCode + ); + } + + fn field_numbers(bytes: &[u8]) -> Vec { + let mut reader = Reader::new(bytes); + let mut out = Vec::new(); + while let Some(tag) = reader.next_tag() { + out.push(tag.field_number); + reader.skip(tag.wire_type); + } + out + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/pb/ccloud.rs b/app/src/main/cpp/wn-steam-client/rust/src/pb/ccloud.rs new file mode 100644 index 000000000..266d3fab4 --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/pb/ccloud.rs @@ -0,0 +1,636 @@ +use crate::proto_wire::{Reader, Writer}; + +pub struct CCloudGetUserQuotaRequest; + +impl CCloudGetUserQuotaRequest { + pub fn serialize(&self) -> Vec { + Vec::new() + } +} + +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct CCloudGetUserQuotaResponse { + pub total_bytes: u64, + pub used_bytes: u64, +} + +impl CCloudGetUserQuotaResponse { + pub fn deserialize(body: &[u8]) -> Option { + let mut r = Reader::new(body); + let mut m = Self::default(); + while !r.eof() { + let Some(t) = r.next_tag() else { + return r.ok().then_some(m); + }; + match t.field_number { + 1 => m.total_bytes = r.u64()?, + 2 => m.used_bytes = r.u64()?, + _ => { + if !r.skip(t.wire_type) { + return None; + } + } + } + } + Some(m) + } +} + +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct CCloudGetAppFileChangelistRequest { + pub appid: u32, + pub synced_change_number: u64, +} + +impl CCloudGetAppFileChangelistRequest { + pub fn serialize(&self) -> Vec { + let mut out = Vec::new(); + let mut w = Writer::new(&mut out); + w.uint32_field(1, self.appid); + w.uint64_field(2, self.synced_change_number); + out + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct CCloudAppFileInfo { + pub file_name: String, + pub sha_file: Vec, + pub time_stamp: u64, + pub raw_file_size: u32, + pub persist_state: i32, + pub platforms_to_sync: u32, + pub path_prefix_index: u32, + pub machine_name_index: u32, +} + +impl CCloudAppFileInfo { + pub fn deserialize(body: &[u8]) -> Option { + let mut r = Reader::new(body); + let mut m = Self::default(); + while !r.eof() { + let Some(t) = r.next_tag() else { + return r.ok().then_some(m); + }; + match t.field_number { + 1 => m.file_name = r.string()?, + 2 => m.sha_file = r.bytes()?.to_vec(), + 3 => m.time_stamp = r.u64()?, + 4 => m.raw_file_size = r.u32()?, + 5 => m.persist_state = r.u64()? as u32 as i32, + 6 => m.platforms_to_sync = r.u32()?, + 7 => m.path_prefix_index = r.u32()?, + 8 => m.machine_name_index = r.u32()?, + _ => { + if !r.skip(t.wire_type) { + return None; + } + } + } + } + Some(m) + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct CCloudGetAppFileChangelistResponse { + pub current_change_number: u64, + pub files: Vec, + pub is_only_delta: bool, + pub path_prefixes: Vec, + pub machine_names: Vec, + pub app_buildid_hwm: u64, +} + +impl CCloudGetAppFileChangelistResponse { + pub fn deserialize(body: &[u8]) -> Option { + let mut r = Reader::new(body); + let mut m = Self::default(); + while !r.eof() { + let Some(t) = r.next_tag() else { + return r.ok().then_some(m); + }; + match t.field_number { + 1 => m.current_change_number = r.u64()?, + 2 => m.files.push(CCloudAppFileInfo::deserialize(r.bytes()?)?), + 3 => m.is_only_delta = r.boolean()?, + 4 => m.path_prefixes.push(r.string()?), + 5 => m.machine_names.push(r.string()?), + 6 => m.app_buildid_hwm = r.u64()?, + _ => { + if !r.skip(t.wire_type) { + return None; + } + } + } + } + Some(m) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct CCloudClientFileDownloadRequest { + pub appid: u32, + pub filename: String, + pub realm: u32, +} + +impl Default for CCloudClientFileDownloadRequest { + fn default() -> Self { + Self { + appid: 0, + filename: String::new(), + realm: 1, + } + } +} + +impl CCloudClientFileDownloadRequest { + pub fn serialize(&self) -> Vec { + let mut out = Vec::new(); + let mut w = Writer::new(&mut out); + w.uint32_field(1, self.appid); + w.string_field(2, &self.filename); + w.uint32_field(3, self.realm); + out + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct CCloudHTTPHeader { + pub name: String, + pub value: String, +} + +impl CCloudHTTPHeader { + pub fn deserialize(body: &[u8]) -> Option { + let mut r = Reader::new(body); + let mut m = Self::default(); + while !r.eof() { + let Some(t) = r.next_tag() else { + return r.ok().then_some(m); + }; + match t.field_number { + 1 => m.name = r.string()?, + 2 => m.value = r.string()?, + _ => { + if !r.skip(t.wire_type) { + return None; + } + } + } + } + Some(m) + } + + pub fn serialize(&self) -> Vec { + let mut out = Vec::new(); + let mut w = Writer::new(&mut out); + w.string_field(1, &self.name); + w.string_field(2, &self.value); + out + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct CCloudClientFileDownloadResponse { + pub file_size: u32, + pub raw_file_size: u32, + pub sha_file: Vec, + pub time_stamp: u64, + pub is_explicit_delete: bool, + pub url_host: String, + pub url_path: String, + pub use_https: bool, + pub request_headers: Vec, + pub encrypted: bool, +} + +impl CCloudClientFileDownloadResponse { + pub fn deserialize(body: &[u8]) -> Option { + let mut r = Reader::new(body); + let mut m = Self::default(); + while !r.eof() { + let Some(t) = r.next_tag() else { + return r.ok().then_some(m); + }; + match t.field_number { + 1 => { + if !r.skip(t.wire_type) { + return None; + } + } + 2 => m.file_size = r.u32()?, + 3 => m.raw_file_size = r.u32()?, + 4 => m.sha_file = r.bytes()?.to_vec(), + 5 => m.time_stamp = r.u64()?, + 6 => m.is_explicit_delete = r.boolean()?, + 7 => m.url_host = r.string()?, + 8 => m.url_path = r.string()?, + 9 => m.use_https = r.boolean()?, + 10 => m + .request_headers + .push(CCloudHTTPHeader::deserialize(r.bytes()?)?), + 11 => m.encrypted = r.boolean()?, + _ => { + if !r.skip(t.wire_type) { + return None; + } + } + } + } + Some(m) + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct CCloudBeginAppUploadBatchRequest { + pub appid: u32, + pub machine_name: String, + pub files_to_upload: Vec, + pub files_to_delete: Vec, + pub client_id: u64, + pub app_build_id: u64, +} + +impl CCloudBeginAppUploadBatchRequest { + pub fn serialize(&self) -> Vec { + let mut out = Vec::new(); + let mut w = Writer::new(&mut out); + w.uint32_field(1, self.appid); + w.string_field(2, &self.machine_name); + for file in &self.files_to_upload { + w.string_field(3, file); + } + for file in &self.files_to_delete { + w.string_field(4, file); + } + w.uint64_field(5, self.client_id); + w.uint64_field(6, self.app_build_id); + out + } +} + +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct CCloudBeginAppUploadBatchResponse { + pub batch_id: u64, + pub app_change_number: u64, +} + +impl CCloudBeginAppUploadBatchResponse { + pub fn deserialize(body: &[u8]) -> Option { + let mut r = Reader::new(body); + let mut m = Self::default(); + while !r.eof() { + let Some(t) = r.next_tag() else { + return r.ok().then_some(m); + }; + match t.field_number { + 1 => m.batch_id = r.u64()?, + 4 => m.app_change_number = r.u64()?, + _ => { + if !r.skip(t.wire_type) { + return None; + } + } + } + } + Some(m) + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct CCloudClientBeginFileUploadRequest { + pub appid: u32, + pub file_size: u32, + pub raw_file_size: u32, + pub file_sha: Vec, + pub time_stamp: u64, + pub filename: String, + pub upload_batch_id: u64, +} + +impl CCloudClientBeginFileUploadRequest { + pub fn serialize(&self) -> Vec { + let mut out = Vec::new(); + let mut w = Writer::new(&mut out); + w.uint32_field(1, self.appid); + w.uint32_field(2, self.file_size); + w.uint32_field(3, self.raw_file_size); + w.bytes_field(4, &self.file_sha); + w.uint64_field(5, self.time_stamp); + w.string_field(6, &self.filename); + w.uint64_field(13, self.upload_batch_id); + out + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct CCloudUploadBlockDetails { + pub url_host: String, + pub url_path: String, + pub use_https: bool, + pub http_method: i32, + pub request_headers: Vec, + pub block_offset: u64, + pub block_length: u32, + pub explicit_body_data: Vec, + pub may_parallelize: bool, +} + +impl CCloudUploadBlockDetails { + pub fn deserialize(body: &[u8]) -> Option { + let mut r = Reader::new(body); + let mut m = Self::default(); + while !r.eof() { + let Some(t) = r.next_tag() else { + return r.ok().then_some(m); + }; + match t.field_number { + 1 => m.url_host = r.string()?, + 2 => m.url_path = r.string()?, + 3 => m.use_https = r.boolean()?, + 4 => m.http_method = r.u64()? as u32 as i32, + 5 => m + .request_headers + .push(CCloudHTTPHeader::deserialize(r.bytes()?)?), + 6 => m.block_offset = r.u64()?, + 7 => m.block_length = r.u32()?, + 8 => m.explicit_body_data = r.bytes()?.to_vec(), + 9 => m.may_parallelize = r.boolean()?, + _ => { + if !r.skip(t.wire_type) { + return None; + } + } + } + } + Some(m) + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct CCloudClientBeginFileUploadResponse { + pub encrypt_file: bool, + pub block_requests: Vec, +} + +impl CCloudClientBeginFileUploadResponse { + pub fn deserialize(body: &[u8]) -> Option { + let mut r = Reader::new(body); + let mut m = Self::default(); + while !r.eof() { + let Some(t) = r.next_tag() else { + return r.ok().then_some(m); + }; + match t.field_number { + 1 => m.encrypt_file = r.boolean()?, + 2 => m + .block_requests + .push(CCloudUploadBlockDetails::deserialize(r.bytes()?)?), + _ => { + if !r.skip(t.wire_type) { + return None; + } + } + } + } + Some(m) + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct CCloudClientCommitFileUploadRequest { + pub transfer_succeeded: bool, + pub appid: u32, + pub file_sha: Vec, + pub filename: String, +} + +impl CCloudClientCommitFileUploadRequest { + pub fn serialize(&self) -> Vec { + let mut out = Vec::new(); + let mut w = Writer::new(&mut out); + w.bool_field_force(1, self.transfer_succeeded); + w.uint32_field(2, self.appid); + w.bytes_field(3, &self.file_sha); + w.string_field(4, &self.filename); + out + } +} + +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct CCloudClientCommitFileUploadResponse { + pub file_committed: bool, +} + +impl CCloudClientCommitFileUploadResponse { + pub fn deserialize(body: &[u8]) -> Option { + let mut r = Reader::new(body); + let mut m = Self::default(); + while !r.eof() { + let Some(t) = r.next_tag() else { + return r.ok().then_some(m); + }; + match t.field_number { + 1 => m.file_committed = r.boolean()?, + _ => { + if !r.skip(t.wire_type) { + return None; + } + } + } + } + Some(m) + } +} + +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct CCloudCompleteAppUploadBatchRequest { + pub appid: u32, + pub batch_id: u64, + pub batch_eresult: u32, +} + +impl CCloudCompleteAppUploadBatchRequest { + pub fn serialize(&self) -> Vec { + let mut out = Vec::new(); + let mut w = Writer::new(&mut out); + w.uint32_field(1, self.appid); + w.uint64_field(2, self.batch_id); + w.uint32_field(3, self.batch_eresult); + out + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct CCloudAppLaunchIntentRequest { + pub appid: u32, + pub client_id: u64, + pub machine_name: String, + pub ignore_pending_operations: bool, + pub os_type: i32, +} + +impl CCloudAppLaunchIntentRequest { + pub fn serialize(&self) -> Vec { + let mut out = Vec::new(); + let mut w = Writer::new(&mut out); + w.uint32_field(1, self.appid); + w.uint64_field(2, self.client_id); + w.string_field(3, &self.machine_name); + w.bool_field(4, self.ignore_pending_operations); + w.int32_field(5, self.os_type); + out + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct CCloudAppLaunchIntentResponse { + pub pending_operation_codes: Vec, +} + +impl CCloudAppLaunchIntentResponse { + pub fn deserialize(body: &[u8]) -> Option { + let mut r = Reader::new(body); + let mut m = Self::default(); + while !r.eof() { + let Some(t) = r.next_tag() else { + return r.ok().then_some(m); + }; + if t.field_number == 1 { + m.pending_operation_codes + .push(parse_pending_operation(r.bytes()?)?); + } else if !r.skip(t.wire_type) { + return None; + } + } + Some(m) + } +} + +fn parse_pending_operation(body: &[u8]) -> Option { + let mut r = Reader::new(body); + let mut op = 0; + while !r.eof() { + let Some(t) = r.next_tag() else { + return r.ok().then_some(op); + }; + if t.field_number == 1 { + op = r.u64()? as u32 as i32; + } else if !r.skip(t.wire_type) { + return None; + } + } + Some(op) +} + +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct CCloudAppExitSyncDoneNotification { + pub appid: u32, + pub client_id: u64, + pub uploads_completed: bool, + pub uploads_required: bool, +} + +impl CCloudAppExitSyncDoneNotification { + pub fn serialize(&self) -> Vec { + let mut out = Vec::new(); + let mut w = Writer::new(&mut out); + w.uint32_field(1, self.appid); + w.uint64_field(2, self.client_id); + w.bool_field(3, self.uploads_completed); + w.bool_field(4, self.uploads_required); + out + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parses_changelist_file_info() { + let mut file = Vec::new(); + { + let mut w = Writer::new(&mut file); + w.string_field(1, "save.dat"); + w.bytes_field(2, &[1; 20]); + w.uint64_field(3, 100); + w.uint32_field(4, 12); + w.uint32_field(5, 2); + w.uint32_field(6, 0xffff); + w.uint32_field(7, 1); + w.uint32_field(8, 2); + } + + let mut body = Vec::new(); + { + let mut w = Writer::new(&mut body); + w.uint64_field(1, 55); + w.submessage_field(2, &file); + w.bool_field(3, true); + w.string_field(4, "remote/"); + w.string_field(5, "machine"); + w.uint64_field(6, 777); + } + + let parsed = CCloudGetAppFileChangelistResponse::deserialize(&body).unwrap(); + assert_eq!(parsed.current_change_number, 55); + assert_eq!(parsed.files[0].file_name, "save.dat"); + assert_eq!(parsed.files[0].persist_state, 2); + assert_eq!(parsed.path_prefixes, ["remote/"]); + assert_eq!(parsed.machine_names, ["machine"]); + } + + #[test] + fn parses_download_and_upload_http_headers() { + let header = CCloudHTTPHeader { + name: "Auth".into(), + value: "token".into(), + } + .serialize(); + + let mut body = Vec::new(); + { + let mut w = Writer::new(&mut body); + w.uint32_field(2, 10); + w.uint32_field(3, 20); + w.bytes_field(4, &[2; 20]); + w.uint64_field(5, 1000); + w.string_field(7, "host"); + w.string_field(8, "/path"); + w.bool_field(9, true); + w.submessage_field(10, &header); + w.bool_field(11, true); + } + + let parsed = CCloudClientFileDownloadResponse::deserialize(&body).unwrap(); + assert_eq!(parsed.file_size, 10); + assert_eq!(parsed.request_headers[0].name, "Auth"); + assert!(parsed.use_https); + assert!(parsed.encrypted); + } + + #[test] + fn commit_upload_force_emits_false_transfer_status() { + let body = CCloudClientCommitFileUploadRequest { + transfer_succeeded: false, + appid: 480, + file_sha: vec![1, 2], + filename: "save.dat".into(), + } + .serialize(); + assert_eq!(&body[..4], &[8, 0, 16, 224]); + } + + #[test] + fn parses_launch_pending_operations() { + let mut op = Vec::new(); + Writer::new(&mut op).uint32_field(1, 4); + let mut body = Vec::new(); + Writer::new(&mut body).submessage_field(1, &op); + let parsed = CCloudAppLaunchIntentResponse::deserialize(&body).unwrap(); + assert_eq!(parsed.pending_operation_codes, [4]); + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/pb/ccontentserverdirectory.rs b/app/src/main/cpp/wn-steam-client/rust/src/pb/ccontentserverdirectory.rs new file mode 100644 index 000000000..ba6e9d16c --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/pb/ccontentserverdirectory.rs @@ -0,0 +1,212 @@ +use crate::proto_wire::{Reader, WireType, Writer}; + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct CContentServerDirectoryGetManifestRequestCodeRequest { + pub app_id: u32, + pub depot_id: u32, + pub manifest_id: u64, + pub app_branch: String, + pub branch_password_hash: String, +} + +impl CContentServerDirectoryGetManifestRequestCodeRequest { + pub fn serialize(&self) -> Vec { + let mut out = Vec::new(); + let mut w = Writer::new(&mut out); + w.uint32_field(1, self.app_id); + w.uint32_field(2, self.depot_id); + w.uint64_field(3, self.manifest_id); + w.string_field(4, &self.app_branch); + w.string_field(5, &self.branch_password_hash); + out + } +} + +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct CContentServerDirectoryGetManifestRequestCodeResponse { + pub manifest_request_code: u64, +} + +impl CContentServerDirectoryGetManifestRequestCodeResponse { + pub fn deserialize(body: &[u8]) -> Option { + let mut reader = Reader::new(body); + let mut msg = Self::default(); + while !reader.eof() { + let Some(tag) = reader.next_tag() else { + return reader.ok().then_some(msg); + }; + match tag.field_number { + 1 => msg.manifest_request_code = reader.u64()?, + _ => { + if !reader.skip(tag.wire_type) { + return None; + } + } + } + } + Some(msg) + } +} + +#[derive(Clone, Debug, Default, PartialEq)] +pub struct CContentServerDirectoryServerInfo { + pub server_type: String, + pub source_id: i32, + pub cell_id: i32, + pub load: i32, + pub weighted_load: f32, + pub num_entries_in_client_list: i32, + pub steam_china_only: bool, + pub host: String, + pub vhost: String, + pub use_as_proxy: bool, + pub proxy_request_path_template: String, + pub https_support: String, + pub allowed_app_ids: Vec, + pub priority_class: u32, +} + +impl CContentServerDirectoryServerInfo { + pub fn use_https(&self) -> bool { + self.https_support.eq_ignore_ascii_case("mandatory") + } + + pub fn port(&self) -> u16 { + if self.use_https() { + 443 + } else { + 80 + } + } + + pub fn deserialize(body: &[u8]) -> Option { + let mut reader = Reader::new(body); + let mut msg = Self::default(); + while !reader.eof() { + let Some(tag) = reader.next_tag() else { + return reader.ok().then_some(msg); + }; + match tag.field_number { + 1 => msg.server_type = reader.string()?, + 2 => msg.source_id = reader.i32()?, + 3 => msg.cell_id = reader.i32()?, + 4 => msg.load = reader.i32()?, + 5 => msg.weighted_load = f32::from_bits(reader.fixed32()?), + 6 => msg.num_entries_in_client_list = reader.i32()?, + 7 => msg.steam_china_only = reader.boolean()?, + 8 => msg.host = reader.string()?, + 9 => msg.vhost = reader.string()?, + 10 => msg.use_as_proxy = reader.boolean()?, + 11 => msg.proxy_request_path_template = reader.string()?, + 12 => msg.https_support = reader.string()?, + 13 => match tag.wire_type { + WireType::LengthDelimited => { + let mut packed = Reader::new(reader.bytes()?); + while !packed.eof() { + msg.allowed_app_ids.push(packed.varint()? as u32); + } + } + _ => msg.allowed_app_ids.push(reader.u32()?), + }, + 15 => msg.priority_class = reader.u32()?, + _ => { + if !reader.skip(tag.wire_type) { + return None; + } + } + } + } + Some(msg) + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct CContentServerDirectoryGetServersForSteamPipeRequest { + pub cell_id: u32, + pub max_servers: u32, +} + +impl Default for CContentServerDirectoryGetServersForSteamPipeRequest { + fn default() -> Self { + Self { + cell_id: 0, + max_servers: 20, + } + } +} + +impl CContentServerDirectoryGetServersForSteamPipeRequest { + pub fn serialize(&self) -> Vec { + let mut out = Vec::new(); + let mut w = Writer::new(&mut out); + w.uint32_field(1, self.cell_id); + w.uint32_field(2, self.max_servers); + out + } +} + +#[derive(Clone, Debug, Default, PartialEq)] +pub struct CContentServerDirectoryGetServersForSteamPipeResponse { + pub servers: Vec, + pub no_change: bool, +} + +impl CContentServerDirectoryGetServersForSteamPipeResponse { + pub fn deserialize(body: &[u8]) -> Option { + let mut reader = Reader::new(body); + let mut msg = Self::default(); + while !reader.eof() { + let Some(tag) = reader.next_tag() else { + return reader.ok().then_some(msg); + }; + match tag.field_number { + 1 => msg + .servers + .push(CContentServerDirectoryServerInfo::deserialize( + reader.bytes()?, + )?), + 2 => msg.no_change = reader.boolean()?, + _ => { + if !reader.skip(tag.wire_type) { + return None; + } + } + } + } + Some(msg) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parses_server_info_with_packed_and_unpacked_app_ids() { + let mut server = Vec::new(); + { + let mut w = Writer::new(&mut server); + w.string_field(1, "SteamCache"); + w.int32_field(2, 5); + w.int32_field(3, 6); + w.int32_field(4, 7); + w.tag(5, WireType::Fixed32); + w.raw_bytes(&1.25f32.to_bits().to_le_bytes()); + w.bool_field(7, true); + w.string_field(8, "cache.example"); + w.string_field(12, "MANDATORY"); + w.uint32_field(13, 480); + let mut packed = Vec::new(); + Writer::new(&mut packed).varint(730); + w.bytes_field(13, &packed); + w.uint32_field(15, 2); + } + + let parsed = CContentServerDirectoryServerInfo::deserialize(&server).unwrap(); + assert_eq!(parsed.server_type, "SteamCache"); + assert_eq!(parsed.weighted_load, 1.25); + assert_eq!(parsed.allowed_app_ids, [480, 730]); + assert!(parsed.use_https()); + assert_eq!(parsed.port(), 443); + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/pb/cfamilygroups.rs b/app/src/main/cpp/wn-steam-client/rust/src/pb/cfamilygroups.rs new file mode 100644 index 000000000..11bae4fd4 --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/pb/cfamilygroups.rs @@ -0,0 +1,92 @@ +use crate::proto_wire::{Reader, Writer}; + +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct CFamilyGroupsGetFamilyGroupRequest { + pub family_groupid: u64, +} + +impl CFamilyGroupsGetFamilyGroupRequest { + pub fn serialize(&self) -> Vec { + let mut out = Vec::new(); + Writer::new(&mut out).uint64_field(1, self.family_groupid); + out + } +} + +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct FamilyGroupMember { + pub steamid: u64, +} + +impl FamilyGroupMember { + pub fn deserialize(body: &[u8]) -> Option { + let mut reader = Reader::new(body); + let mut msg = Self::default(); + while !reader.eof() { + let Some(tag) = reader.next_tag() else { + return reader.ok().then_some(msg); + }; + match tag.field_number { + 1 => msg.steamid = reader.fixed64()?, + _ => { + if !reader.skip(tag.wire_type) { + return None; + } + } + } + } + Some(msg) + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct CFamilyGroupsGetFamilyGroupResponse { + pub name: String, + pub members: Vec, +} + +impl CFamilyGroupsGetFamilyGroupResponse { + pub fn deserialize(body: &[u8]) -> Option { + let mut reader = Reader::new(body); + let mut msg = Self::default(); + while !reader.eof() { + let Some(tag) = reader.next_tag() else { + return reader.ok().then_some(msg); + }; + match tag.field_number { + 1 => msg.name = reader.string()?, + 2 => msg + .members + .push(FamilyGroupMember::deserialize(reader.bytes()?)?), + _ => { + if !reader.skip(tag.wire_type) { + return None; + } + } + } + } + Some(msg) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parses_family_group_members() { + let mut member = Vec::new(); + Writer::new(&mut member).fixed64_field(1, 123); + + let mut body = Vec::new(); + { + let mut w = Writer::new(&mut body); + w.string_field(1, "Family"); + w.submessage_field(2, &member); + } + + let parsed = CFamilyGroupsGetFamilyGroupResponse::deserialize(&body).unwrap(); + assert_eq!(parsed.name, "Family"); + assert_eq!(parsed.members[0].steamid, 123); + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/pb/cfriendmessages.rs b/app/src/main/cpp/wn-steam-client/rust/src/pb/cfriendmessages.rs new file mode 100644 index 000000000..ef7f6a61d --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/pb/cfriendmessages.rs @@ -0,0 +1,245 @@ +use crate::proto_wire::{Reader, WireType, Writer}; + +pub const CHAT_ENTRY_TYPE_TEXT: i32 = 1; + +#[derive(Clone, Debug, Default)] +pub struct CFriendMessagesSendMessageRequest { + pub steamid: u64, + pub chat_entry_type: i32, + pub message: String, + pub contains_bbcode: bool, + pub echo_to_sender: bool, + pub low_priority: bool, +} + +impl CFriendMessagesSendMessageRequest { + pub fn serialize(&self) -> Vec { + let mut out = Vec::new(); + let mut w = Writer::new(&mut out); + w.fixed64_field(1, self.steamid); + w.int32_field(2, self.chat_entry_type); + w.string_field(3, &self.message); + if self.contains_bbcode { + w.bool_field(4, true); + } + if self.echo_to_sender { + w.bool_field(5, true); + } + if self.low_priority { + w.bool_field(6, true); + } + out + } +} + +#[derive(Clone, Debug, Default)] +pub struct CFriendMessagesSendMessageResponse { + pub modified_message: String, + pub server_timestamp: u32, + pub ordinal: i32, + pub message_without_bb_code: String, +} + +impl CFriendMessagesSendMessageResponse { + pub fn deserialize(body: &[u8]) -> Option { + let mut reader = Reader::new(body); + let mut msg = Self::default(); + while !reader.eof() { + let Some(tag) = reader.next_tag() else { + return reader.ok().then_some(msg); + }; + match (tag.field_number, tag.wire_type) { + (1, WireType::LengthDelimited) => msg.modified_message = reader.string()?, + (2, WireType::Varint) => msg.server_timestamp = reader.u32()?, + (3, WireType::Varint) => msg.ordinal = reader.i32()?, + (4, WireType::LengthDelimited) => { + msg.message_without_bb_code = reader.string()? + } + _ => { + if !reader.skip(tag.wire_type) { + return None; + } + } + } + } + Some(msg) + } +} + +#[derive(Clone, Debug, Default)] +pub struct CFriendMessagesGetRecentMessagesRequest { + pub steamid1: u64, + pub steamid2: u64, + pub count: u32, + pub most_recent_conversation: bool, +} + +impl CFriendMessagesGetRecentMessagesRequest { + pub fn serialize(&self) -> Vec { + let mut out = Vec::new(); + let mut w = Writer::new(&mut out); + w.fixed64_field(1, self.steamid1); + w.fixed64_field(2, self.steamid2); + w.uint32_field(3, self.count); + if self.most_recent_conversation { + w.bool_field(4, true); + } + out + } +} + +#[derive(Clone, Debug, Default)] +pub struct FriendMessage { + pub accountid: u32, + pub timestamp: u32, + pub message: String, + pub ordinal: i32, +} + +impl FriendMessage { + fn deserialize(body: &[u8]) -> Option { + let mut reader = Reader::new(body); + let mut msg = Self::default(); + while !reader.eof() { + let Some(tag) = reader.next_tag() else { + return reader.ok().then_some(msg); + }; + match (tag.field_number, tag.wire_type) { + (1, WireType::Varint) => msg.accountid = reader.u32()?, + (2, WireType::Varint) => msg.timestamp = reader.u32()?, + (3, WireType::LengthDelimited) => msg.message = reader.string()?, + (4, WireType::Varint) => msg.ordinal = reader.i32()?, + _ => { + if !reader.skip(tag.wire_type) { + return None; + } + } + } + } + Some(msg) + } +} + +#[derive(Clone, Debug, Default)] +pub struct CFriendMessagesGetRecentMessagesResponse { + pub messages: Vec, + pub more_available: bool, +} + +impl CFriendMessagesGetRecentMessagesResponse { + pub fn deserialize(body: &[u8]) -> Option { + let mut reader = Reader::new(body); + let mut msg = Self::default(); + while !reader.eof() { + let Some(tag) = reader.next_tag() else { + return reader.ok().then_some(msg); + }; + match (tag.field_number, tag.wire_type) { + (1, WireType::LengthDelimited) => { + msg.messages.push(FriendMessage::deserialize(reader.bytes()?)?) + } + (4, WireType::Varint) => msg.more_available = reader.u32()? != 0, + _ => { + if !reader.skip(tag.wire_type) { + return None; + } + } + } + } + Some(msg) + } +} + +#[derive(Clone, Debug, Default)] +pub struct CFriendMessagesIncomingMessageNotification { + pub steamid_friend: u64, + pub chat_entry_type: i32, + pub message: String, + pub rtime32_server_timestamp: u32, + pub ordinal: i32, + pub local_echo: bool, + pub message_no_bbcode: String, +} + +impl CFriendMessagesIncomingMessageNotification { + pub fn deserialize(body: &[u8]) -> Option { + let mut reader = Reader::new(body); + let mut msg = Self::default(); + while !reader.eof() { + let Some(tag) = reader.next_tag() else { + return reader.ok().then_some(msg); + }; + match (tag.field_number, tag.wire_type) { + (1, WireType::Fixed64) => msg.steamid_friend = reader.fixed64()?, + (2, WireType::Varint) => msg.chat_entry_type = reader.i32()?, + (4, WireType::LengthDelimited) => msg.message = reader.string()?, + (5, WireType::Fixed32) => msg.rtime32_server_timestamp = reader.fixed32()?, + (6, WireType::Varint) => msg.ordinal = reader.i32()?, + (7, WireType::Varint) => msg.local_echo = reader.u32()? != 0, + (8, WireType::LengthDelimited) => msg.message_no_bbcode = reader.string()?, + _ => { + if !reader.skip(tag.wire_type) { + return None; + } + } + } + } + Some(msg) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn send_request_roundtrips_fields() { + let req = CFriendMessagesSendMessageRequest { + steamid: 76561198000000000, + chat_entry_type: CHAT_ENTRY_TYPE_TEXT, + message: "hello".into(), + echo_to_sender: true, + ..Default::default() + }; + let bytes = req.serialize(); + let mut reader = Reader::new(&bytes); + let tag = reader.next_tag().unwrap(); + assert_eq!(tag.field_number, 1); + assert_eq!(reader.fixed64().unwrap(), 76561198000000000); + } + + #[test] + fn incoming_notification_parses() { + let mut body = Vec::new(); + { + let mut w = Writer::new(&mut body); + w.fixed64_field(1, 123); + w.int32_field(2, CHAT_ENTRY_TYPE_TEXT); + w.string_field(4, "hi there"); + w.fixed32_field(5, 1700000000); + w.int32_field(6, 0); + } + let parsed = CFriendMessagesIncomingMessageNotification::deserialize(&body).unwrap(); + assert_eq!(parsed.steamid_friend, 123); + assert_eq!(parsed.message, "hi there"); + assert_eq!(parsed.rtime32_server_timestamp, 1700000000); + } + + #[test] + fn recent_message_parses_ordinal_from_field4_and_skips_reactions() { + let mut body = Vec::new(); + { + let mut w = Writer::new(&mut body); + w.uint32_field(1, 42); + w.uint32_field(2, 1700000000); + w.string_field(3, "yo"); + w.int32_field(4, 7); + w.string_field(5, "reactions-submessage"); + } + let parsed = FriendMessage::deserialize(&body).unwrap(); + assert_eq!(parsed.accountid, 42); + assert_eq!(parsed.timestamp, 1700000000); + assert_eq!(parsed.message, "yo"); + assert_eq!(parsed.ordinal, 7); + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/pb/cinventory.rs b/app/src/main/cpp/wn-steam-client/rust/src/pb/cinventory.rs new file mode 100644 index 000000000..558542521 --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/pb/cinventory.rs @@ -0,0 +1,59 @@ +use crate::proto_wire::{Reader, Writer}; + +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct CInventoryGetItemDefMetaRequest { + pub appid: u32, +} + +impl CInventoryGetItemDefMetaRequest { + pub fn serialize(&self) -> Vec { + let mut out = Vec::new(); + Writer::new(&mut out).uint32_field(1, self.appid); + out + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct CInventoryGetItemDefMetaResponse { + pub modified: u32, + pub digest: String, +} + +impl CInventoryGetItemDefMetaResponse { + pub fn deserialize(body: &[u8]) -> Option { + let mut reader = Reader::new(body); + let mut msg = Self::default(); + while !reader.eof() { + let Some(tag) = reader.next_tag() else { + return reader.ok().then_some(msg); + }; + match tag.field_number { + 1 => msg.modified = reader.u32()?, + 2 => msg.digest = reader.string()?, + _ => { + if !reader.skip(tag.wire_type) { + return None; + } + } + } + } + Some(msg) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parses_item_def_meta_response() { + let mut body = Vec::new(); + let mut w = Writer::new(&mut body); + w.uint32_field(1, 12345); + w.string_field(2, "digest"); + + let parsed = CInventoryGetItemDefMetaResponse::deserialize(&body).unwrap(); + assert_eq!(parsed.modified, 12345); + assert_eq!(parsed.digest, "digest"); + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/pb/cmsg_client_change_status.rs b/app/src/main/cpp/wn-steam-client/rust/src/pb/cmsg_client_change_status.rs new file mode 100644 index 000000000..2a7716375 --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/pb/cmsg_client_change_status.rs @@ -0,0 +1,25 @@ +use crate::proto_wire::Writer; + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct CMsgClientChangeStatus { + pub persona_state: u32, + pub player_name: String, + pub persona_set_by_user: bool, + pub need_persona_response: bool, +} + +impl CMsgClientChangeStatus { + pub fn serialize(&self) -> Vec { + let mut out = Vec::new(); + let mut w = Writer::new(&mut out); + w.uint32_field_force(1, self.persona_state); + w.string_field(2, &self.player_name); + if self.persona_set_by_user { + w.bool_field(5, true); + } + if self.need_persona_response { + w.bool_field(7, true); + } + out + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/pb/cmsg_client_friends_list.rs b/app/src/main/cpp/wn-steam-client/rust/src/pb/cmsg_client_friends_list.rs new file mode 100644 index 000000000..c36fab0ac --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/pb/cmsg_client_friends_list.rs @@ -0,0 +1,100 @@ +use crate::proto_wire::{Reader, WireType}; + +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct ClientFriendsListEntry { + pub ulfriendid: u64, + pub efriendrelationship: u32, +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct CMsgClientFriendsList { + pub bincremental: bool, + pub friends: Vec, + pub max_friend_count: u32, + pub active_friend_count: u32, + pub friends_limit_hit: bool, +} + +impl CMsgClientFriendsList { + pub fn deserialize(body: &[u8]) -> Option { + let mut reader = Reader::new(body); + let mut msg = Self::default(); + while !reader.eof() { + let Some(tag) = reader.next_tag() else { + return reader.ok().then_some(msg); + }; + match tag.field_number { + 1 => msg.bincremental = reader.boolean()?, + 2 => msg.friends.push(parse_entry(reader.bytes()?)?), + 3 => msg.max_friend_count = reader.u32()?, + 4 => msg.active_friend_count = reader.u32()?, + 5 => msg.friends_limit_hit = reader.boolean()?, + _ => { + if !reader.skip(tag.wire_type) { + return None; + } + } + } + } + Some(msg) + } +} + +fn parse_entry(body: &[u8]) -> Option { + let mut reader = Reader::new(body); + let mut entry = ClientFriendsListEntry::default(); + while !reader.eof() { + let Some(tag) = reader.next_tag() else { + return reader.ok().then_some(entry); + }; + match tag.field_number { + 1 => { + if tag.wire_type != WireType::Fixed64 { + return None; + } + entry.ulfriendid = reader.fixed64()?; + } + 2 => entry.efriendrelationship = reader.u32()?, + _ => { + if !reader.skip(tag.wire_type) { + return None; + } + } + } + } + Some(entry) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::proto_wire::Writer; + + #[test] + fn parses_friends_list_entries() { + let mut entry = Vec::new(); + { + let mut w = Writer::new(&mut entry); + w.fixed64_field(1, 123); + w.uint32_field(2, 3); + } + + let mut body = Vec::new(); + { + let mut w = Writer::new(&mut body); + w.bool_field(1, true); + w.submessage_field(2, &entry); + w.uint32_field(3, 250); + w.uint32_field(4, 1); + w.bool_field(5, true); + } + + let parsed = CMsgClientFriendsList::deserialize(&body).unwrap(); + assert!(parsed.bincremental); + assert_eq!(parsed.friends[0].ulfriendid, 123); + assert_eq!(parsed.friends[0].efriendrelationship, 3); + assert_eq!(parsed.max_friend_count, 250); + assert_eq!(parsed.active_friend_count, 1); + assert!(parsed.friends_limit_hit); + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/pb/cmsg_client_games_played.rs b/app/src/main/cpp/wn-steam-client/rust/src/pb/cmsg_client_games_played.rs new file mode 100644 index 000000000..3b2052824 --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/pb/cmsg_client_games_played.rs @@ -0,0 +1,90 @@ +use crate::proto_wire::{WireType, Writer}; + +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct GamePlayedProcessInfo { + pub process_id: u32, + pub process_id_parent: u32, + pub parent_is_steam: bool, +} + +impl GamePlayedProcessInfo { + fn serialize(&self) -> Vec { + let mut out = Vec::new(); + let mut w = Writer::new(&mut out); + w.uint32_field_force(1, self.process_id); + w.uint32_field_force(2, self.process_id_parent); + w.bool_field_force(3, self.parent_is_steam); + out + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct GamePlayedEntry { + pub game_id: u64, + pub process_id: u32, + pub owner_id: u32, + pub launch_source: u32, + pub game_build_id: u32, + pub process_id_list: Vec, +} + +impl GamePlayedEntry { + fn serialize(&self) -> Vec { + let mut out = Vec::new(); + let mut w = Writer::new(&mut out); + if self.game_id != 0 { + w.tag(2, WireType::Fixed64); + w.raw_bytes(&self.game_id.to_le_bytes()); + } + w.uint32_field_force(9, self.process_id); + w.uint32_field_force(12, self.owner_id); + w.uint32_field_force(21, self.launch_source); + w.uint32_field_force(26, self.game_build_id); + for process in &self.process_id_list { + w.submessage_field(32, &process.serialize()); + } + out + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct CMsgClientGamesPlayed { + pub games_played: Vec, + pub client_os_type: u32, +} + +impl CMsgClientGamesPlayed { + pub fn serialize(&self) -> Vec { + let mut out = Vec::new(); + let mut w = Writer::new(&mut out); + for game in &self.games_played { + w.submessage_field(1, &game.serialize()); + } + w.uint32_field(2, self.client_os_type); + out + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::proto_wire::Reader; + + #[test] + fn games_played_force_emits_zero_process_fields() { + let msg = CMsgClientGamesPlayed { + games_played: vec![GamePlayedEntry { + game_id: 480, + process_id_list: vec![GamePlayedProcessInfo::default()], + ..Default::default() + }], + client_os_type: 16, + }; + let body = msg.serialize(); + let mut reader = Reader::new(&body); + let tag = reader.next_tag().unwrap(); + assert_eq!(tag.field_number, 1); + let game = reader.bytes().unwrap(); + assert!(game.windows(2).any(|w| w == [0x48, 0x00])); // field 9 process_id = 0 + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/pb/cmsg_client_get_app_ownership_ticket.rs b/app/src/main/cpp/wn-steam-client/rust/src/pb/cmsg_client_get_app_ownership_ticket.rs new file mode 100644 index 000000000..d11d5bffa --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/pb/cmsg_client_get_app_ownership_ticket.rs @@ -0,0 +1,74 @@ +use crate::proto_wire::{Reader, Writer}; + +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct CMsgClientGetAppOwnershipTicket { + pub app_id: u32, +} + +impl CMsgClientGetAppOwnershipTicket { + pub fn serialize(&self) -> Vec { + let mut out = Vec::new(); + Writer::new(&mut out).uint32_field(1, self.app_id); + out + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct CMsgClientGetAppOwnershipTicketResponse { + pub eresult: u32, + pub app_id: u32, + pub ticket: Vec, +} + +impl Default for CMsgClientGetAppOwnershipTicketResponse { + fn default() -> Self { + Self { + eresult: 2, + app_id: 0, + ticket: Vec::new(), + } + } +} + +impl CMsgClientGetAppOwnershipTicketResponse { + pub fn deserialize(body: &[u8]) -> Option { + let mut reader = Reader::new(body); + let mut msg = Self::default(); + while !reader.eof() { + let Some(tag) = reader.next_tag() else { + return reader.ok().then_some(msg); + }; + match tag.field_number { + 1 => msg.eresult = reader.u32()?, + 2 => msg.app_id = reader.u32()?, + 3 => msg.ticket = reader.bytes()?.to_vec(), + _ => { + if !reader.skip(tag.wire_type) { + return None; + } + } + } + } + Some(msg) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn ownership_ticket_roundtrip_fields() { + let req = CMsgClientGetAppOwnershipTicket { app_id: 480 }.serialize(); + assert_eq!(req, vec![0x08, 0xe0, 0x03]); + + let mut resp = Vec::new(); + let mut w = Writer::new(&mut resp); + w.uint32_field(1, 1); + w.uint32_field(2, 480); + w.bytes_field(3, &[1, 2, 3]); + let parsed = CMsgClientGetAppOwnershipTicketResponse::deserialize(&resp).unwrap(); + assert_eq!(parsed.eresult, 1); + assert_eq!(parsed.ticket, vec![1, 2, 3]); + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/pb/cmsg_client_get_depot_decryption_key.rs b/app/src/main/cpp/wn-steam-client/rust/src/pb/cmsg_client_get_depot_decryption_key.rs new file mode 100644 index 000000000..db45fb385 --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/pb/cmsg_client_get_depot_decryption_key.rs @@ -0,0 +1,57 @@ +use crate::proto_wire::{Reader, Writer}; + +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct CMsgClientGetDepotDecryptionKey { + pub depot_id: u32, + pub app_id: u32, +} + +impl CMsgClientGetDepotDecryptionKey { + pub fn serialize(&self) -> Vec { + let mut out = Vec::new(); + let mut w = Writer::new(&mut out); + w.uint32_field(1, self.depot_id); + w.uint32_field(2, self.app_id); + out + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct CMsgClientGetDepotDecryptionKeyResponse { + pub eresult: u32, + pub depot_id: u32, + pub depot_encryption_key: Vec, +} + +impl Default for CMsgClientGetDepotDecryptionKeyResponse { + fn default() -> Self { + Self { + eresult: 2, + depot_id: 0, + depot_encryption_key: Vec::new(), + } + } +} + +impl CMsgClientGetDepotDecryptionKeyResponse { + pub fn deserialize(body: &[u8]) -> Option { + let mut reader = Reader::new(body); + let mut msg = Self::default(); + while !reader.eof() { + let Some(tag) = reader.next_tag() else { + return reader.ok().then_some(msg); + }; + match tag.field_number { + 1 => msg.eresult = reader.u32()?, + 2 => msg.depot_id = reader.u32()?, + 3 => msg.depot_encryption_key = reader.bytes()?.to_vec(), + _ => { + if !reader.skip(tag.wire_type) { + return None; + } + } + } + } + Some(msg) + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/pb/cmsg_client_get_user_stats.rs b/app/src/main/cpp/wn-steam-client/rust/src/pb/cmsg_client_get_user_stats.rs new file mode 100644 index 000000000..c492e8a1c --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/pb/cmsg_client_get_user_stats.rs @@ -0,0 +1,129 @@ +use crate::proto_wire::{Reader, WireType, Writer}; + +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct CMsgClientGetUserStats { + pub game_id: u64, + pub steam_id_for_user: u64, +} + +impl CMsgClientGetUserStats { + pub fn serialize(&self) -> Vec { + let mut out = Vec::new(); + let mut w = Writer::new(&mut out); + w.fixed64_field(1, self.game_id); + w.fixed64_field(4, self.steam_id_for_user); + out + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct UserStatsAchievementBlock { + pub achievement_id: u32, + pub unlock_time: Vec, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct CMsgClientGetUserStatsResponse { + pub eresult: i32, + pub crc_stats: u32, + pub schema: Vec, + pub achievement_blocks: Vec, +} + +impl Default for CMsgClientGetUserStatsResponse { + fn default() -> Self { + Self { + eresult: 2, + crc_stats: 0, + schema: Vec::new(), + achievement_blocks: Vec::new(), + } + } +} + +impl CMsgClientGetUserStatsResponse { + pub fn deserialize(body: &[u8]) -> Option { + let mut reader = Reader::new(body); + let mut msg = Self::default(); + while !reader.eof() { + let Some(tag) = reader.next_tag() else { + return reader.ok().then_some(msg); + }; + match tag.field_number { + 2 => msg.eresult = reader.u64()? as u32 as i32, + 3 => msg.crc_stats = reader.u32()?, + 4 => msg.schema = reader.bytes()?.to_vec(), + 6 => msg + .achievement_blocks + .push(parse_achievement_block(reader.bytes()?)?), + _ => { + if !reader.skip(tag.wire_type) { + return None; + } + } + } + } + Some(msg) + } +} + +fn parse_achievement_block(body: &[u8]) -> Option { + let mut reader = Reader::new(body); + let mut block = UserStatsAchievementBlock::default(); + while !reader.eof() { + let Some(tag) = reader.next_tag() else { + return reader.ok().then_some(block); + }; + match (tag.field_number, tag.wire_type) { + (1, _) => block.achievement_id = reader.u32()?, + (2, WireType::Fixed32) => block.unlock_time.push(reader.fixed32()?), + (2, WireType::LengthDelimited) => { + let mut packed = Reader::new(reader.bytes()?); + while !packed.eof() { + block.unlock_time.push(packed.fixed32()?); + } + } + _ => { + if !reader.skip(tag.wire_type) { + return None; + } + } + } + } + Some(block) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parses_unpacked_and_packed_achievement_unlocks() { + let mut block = Vec::new(); + { + let mut w = Writer::new(&mut block); + w.uint32_field(1, 32); + w.tag(2, WireType::Fixed32); + w.raw_bytes(&10u32.to_le_bytes()); + let mut packed = Vec::new(); + packed.extend_from_slice(&20u32.to_le_bytes()); + packed.extend_from_slice(&30u32.to_le_bytes()); + w.bytes_field(2, &packed); + } + + let mut body = Vec::new(); + { + let mut w = Writer::new(&mut body); + w.int32_field(2, 1); + w.uint32_field(3, 1234); + w.bytes_field(4, b"schema"); + w.submessage_field(6, &block); + } + + let parsed = CMsgClientGetUserStatsResponse::deserialize(&body).unwrap(); + assert_eq!(parsed.eresult, 1); + assert_eq!(parsed.crc_stats, 1234); + assert_eq!(parsed.schema, b"schema"); + assert_eq!(parsed.achievement_blocks[0].unlock_time, vec![10, 20, 30]); + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/pb/cmsg_client_kick_playing_session.rs b/app/src/main/cpp/wn-steam-client/rust/src/pb/cmsg_client_kick_playing_session.rs new file mode 100644 index 000000000..de72ad499 --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/pb/cmsg_client_kick_playing_session.rs @@ -0,0 +1,14 @@ +use crate::proto_wire::Writer; + +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct CMsgClientKickPlayingSession { + pub only_stop_game: bool, +} + +impl CMsgClientKickPlayingSession { + pub fn serialize(&self) -> Vec { + let mut out = Vec::new(); + Writer::new(&mut out).bool_field_force(1, self.only_stop_game); + out + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/pb/cmsg_client_license_list.rs b/app/src/main/cpp/wn-steam-client/rust/src/pb/cmsg_client_license_list.rs new file mode 100644 index 000000000..0c3f065a5 --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/pb/cmsg_client_license_list.rs @@ -0,0 +1,143 @@ +use crate::proto_wire::{Reader, WireType}; + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct License { + pub package_id: u32, + pub time_created: u32, + pub time_next_process: u32, + pub minute_limit: i32, + pub minutes_used: i32, + pub payment_method: u32, + pub flags: u32, + pub purchase_country_code: String, + pub license_type: u32, + pub territory_code: i32, + pub change_number: i32, + pub owner_id: u32, + pub initial_period: u32, + pub initial_time_unit: u32, + pub renewal_period: u32, + pub renewal_time_unit: u32, + pub access_token: u64, + pub master_package_id: u32, +} + +impl License { + pub fn deserialize(body: &[u8]) -> Option { + let mut reader = Reader::new(body); + let mut msg = Self::default(); + while !reader.eof() { + let Some(tag) = reader.next_tag() else { + return reader.ok().then_some(msg); + }; + match tag.field_number { + 1 => msg.package_id = reader.u32()?, + 2 => { + if tag.wire_type != WireType::Fixed32 { + return None; + } + msg.time_created = reader.fixed32()?; + } + 3 => { + if tag.wire_type != WireType::Fixed32 { + return None; + } + msg.time_next_process = reader.fixed32()?; + } + 4 => msg.minute_limit = reader.i32()?, + 5 => msg.minutes_used = reader.i32()?, + 6 => msg.payment_method = reader.u32()?, + 7 => msg.flags = reader.u32()?, + 8 => msg.purchase_country_code = reader.string()?, + 9 => msg.license_type = reader.u32()?, + 10 => msg.territory_code = reader.i32()?, + 11 => msg.change_number = reader.i32()?, + 12 => msg.owner_id = reader.u32()?, + 13 => msg.initial_period = reader.u32()?, + 14 => msg.initial_time_unit = reader.u32()?, + 15 => msg.renewal_period = reader.u32()?, + 16 => msg.renewal_time_unit = reader.u32()?, + 17 => msg.access_token = reader.u64()?, + 18 => msg.master_package_id = reader.u32()?, + _ => { + if !reader.skip(tag.wire_type) { + return None; + } + } + } + } + Some(msg) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct CMsgClientLicenseList { + pub eresult: i32, + pub licenses: Vec, +} + +impl Default for CMsgClientLicenseList { + fn default() -> Self { + Self { + eresult: 2, + licenses: Vec::new(), + } + } +} + +impl CMsgClientLicenseList { + pub fn deserialize(body: &[u8]) -> Option { + let mut reader = Reader::new(body); + let mut msg = Self::default(); + while !reader.eof() { + let Some(tag) = reader.next_tag() else { + return reader.ok().then_some(msg); + }; + match tag.field_number { + 1 => msg.eresult = reader.i32()?, + 2 => msg.licenses.push(License::deserialize(reader.bytes()?)?), + _ => { + if !reader.skip(tag.wire_type) { + return None; + } + } + } + } + Some(msg) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::proto_wire::Writer; + + #[test] + fn parses_license_with_fixed_times_and_two_byte_tags() { + let mut lic = Vec::new(); + { + let mut w = Writer::new(&mut lic); + w.uint32_field(1, 123); + w.tag(2, WireType::Fixed32); + w.raw_bytes(&1000u32.to_le_bytes()); + w.tag(3, WireType::Fixed32); + w.raw_bytes(&2000u32.to_le_bytes()); + w.string_field(8, "US"); + w.uint32_field(16, 7); + w.uint64_field(17, 0x1234); + w.uint32_field(18, 456); + } + let mut body = Vec::new(); + { + let mut w = Writer::new(&mut body); + w.int32_field(1, 1); + w.submessage_field(2, &lic); + } + let parsed = CMsgClientLicenseList::deserialize(&body).unwrap(); + assert_eq!(parsed.eresult, 1); + assert_eq!(parsed.licenses[0].package_id, 123); + assert_eq!(parsed.licenses[0].time_created, 1000); + assert_eq!(parsed.licenses[0].renewal_time_unit, 7); + assert_eq!(parsed.licenses[0].master_package_id, 456); + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/pb/cmsg_client_mms_get_lobby_list.rs b/app/src/main/cpp/wn-steam-client/rust/src/pb/cmsg_client_mms_get_lobby_list.rs new file mode 100644 index 000000000..265c8d4b5 --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/pb/cmsg_client_mms_get_lobby_list.rs @@ -0,0 +1,166 @@ +use crate::proto_wire::{Reader, WireType, Writer}; + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct CMsgClientMMSGetLobbyListFilter { + pub key: String, + pub value: String, + pub comparision: i32, + pub filter_type: i32, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct CMsgClientMMSGetLobbyList { + pub app_id: u32, + pub num_lobbies_requested: i32, + pub cell_id: u32, + pub filters: Vec, +} + +impl Default for CMsgClientMMSGetLobbyList { + fn default() -> Self { + Self { + app_id: 0, + num_lobbies_requested: 50, + cell_id: 0, + filters: Vec::new(), + } + } +} + +impl CMsgClientMMSGetLobbyList { + pub fn serialize(&self) -> Vec { + let mut out = Vec::new(); + let mut w = Writer::new(&mut out); + w.uint32_field(1, self.app_id); + if self.num_lobbies_requested > 0 { + w.int32_field(3, self.num_lobbies_requested); + } + w.uint32_field(4, self.cell_id); + for filter in &self.filters { + let mut sub = Vec::new(); + let mut fw = Writer::new(&mut sub); + fw.string_field(1, &filter.key); + fw.string_field(2, &filter.value); + fw.int32_field(3, filter.comparision); + fw.int32_field(4, filter.filter_type); + w.submessage_field(6, &sub); + } + out + } +} + +#[derive(Clone, Debug, Default, PartialEq)] +pub struct MMSLobbyListEntry { + pub steam_id: u64, + pub max_members: i32, + pub lobby_type: i32, + pub lobby_flags: i32, + pub metadata: Vec, + pub num_members: i32, + pub distance: f32, + pub weight: i64, + pub ping: i32, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct CMsgClientMMSGetLobbyListResponse { + pub app_id: u32, + pub eresult: i32, + pub lobbies: Vec, +} + +impl Default for CMsgClientMMSGetLobbyListResponse { + fn default() -> Self { + Self { + app_id: 0, + eresult: 2, + lobbies: Vec::new(), + } + } +} + +impl CMsgClientMMSGetLobbyListResponse { + pub fn deserialize(body: &[u8]) -> Option { + let mut r = Reader::new(body); + let mut m = Self::default(); + while !r.eof() { + let Some(t) = r.next_tag() else { + return r.ok().then_some(m); + }; + match t.field_number { + 1 => m.app_id = r.u32()?, + 3 => m.eresult = r.u64()? as u32 as i32, + 4 => m.lobbies.push(parse_lobby_entry(r.bytes()?)?), + _ => { + if !r.skip(t.wire_type) { + return None; + } + } + } + } + Some(m) + } +} + +fn parse_lobby_entry(body: &[u8]) -> Option { + let mut r = Reader::new(body); + let mut e = MMSLobbyListEntry::default(); + while !r.eof() { + let Some(t) = r.next_tag() else { + return r.ok().then_some(e); + }; + match t.field_number { + 1 => e.steam_id = r.fixed64()?, + 2 => e.max_members = r.u64()? as u32 as i32, + 3 => e.lobby_type = r.u64()? as u32 as i32, + 4 => e.lobby_flags = r.u64()? as u32 as i32, + 5 => e.metadata = r.bytes()?.to_vec(), + 6 => e.num_members = r.u64()? as u32 as i32, + 7 => { + if t.wire_type != WireType::Fixed32 { + return None; + } + e.distance = f32::from_bits(r.fixed32()?); + } + 8 => e.weight = r.u64()? as i64, + 9 => e.ping = r.u64()? as u32 as i32, + _ => { + if !r.skip(t.wire_type) { + return None; + } + } + } + } + Some(e) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parses_lobby_list_entry_with_float_distance() { + let mut lobby = Vec::new(); + { + let mut w = Writer::new(&mut lobby); + w.fixed64_field(1, 100); + w.int32_field(2, 4); + w.int32_field(3, 2); + w.bytes_field(5, b"kv"); + w.tag(7, WireType::Fixed32); + w.raw_bytes(&1.5f32.to_bits().to_le_bytes()); + w.int64_field(8, -5); + } + let mut body = Vec::new(); + { + let mut w = Writer::new(&mut body); + w.uint32_field(1, 480); + w.submessage_field(4, &lobby); + } + let parsed = CMsgClientMMSGetLobbyListResponse::deserialize(&body).unwrap(); + assert_eq!(parsed.app_id, 480); + assert_eq!(parsed.eresult, 2); + assert_eq!(parsed.lobbies[0].distance, 1.5); + assert_eq!(parsed.lobbies[0].weight, -5); + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/pb/cmsg_client_mms_lobby_data.rs b/app/src/main/cpp/wn-steam-client/rust/src/pb/cmsg_client_mms_lobby_data.rs new file mode 100644 index 000000000..4dae4f316 --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/pb/cmsg_client_mms_lobby_data.rs @@ -0,0 +1,102 @@ +use crate::proto_wire::Reader; + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct MMSLobbyDataMember { + pub steam_id: u64, + pub persona_name: String, + pub metadata: Vec, +} + +impl MMSLobbyDataMember { + pub fn deserialize(body: &[u8]) -> Option { + let mut r = Reader::new(body); + let mut m = Self::default(); + while !r.eof() { + let Some(t) = r.next_tag() else { + return r.ok().then_some(m); + }; + match t.field_number { + 1 => m.steam_id = r.fixed64()?, + 2 => m.persona_name = r.string()?, + 3 => m.metadata = r.bytes()?.to_vec(), + _ => { + if !r.skip(t.wire_type) { + return None; + } + } + } + } + Some(m) + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct CMsgClientMMSLobbyData { + pub app_id: u32, + pub steam_id_lobby: u64, + pub num_members: i32, + pub max_members: i32, + pub lobby_type: i32, + pub lobby_flags: i32, + pub steam_id_owner: u64, + pub metadata: Vec, + pub members: Vec, +} + +impl CMsgClientMMSLobbyData { + pub fn deserialize(body: &[u8]) -> Option { + let mut r = Reader::new(body); + let mut m = Self::default(); + while !r.eof() { + let Some(t) = r.next_tag() else { + return r.ok().then_some(m); + }; + match t.field_number { + 1 => m.app_id = r.u32()?, + 2 => m.steam_id_lobby = r.fixed64()?, + 3 => m.num_members = r.u64()? as u32 as i32, + 4 => m.max_members = r.u64()? as u32 as i32, + 5 => m.lobby_type = r.u64()? as u32 as i32, + 6 => m.lobby_flags = r.u64()? as u32 as i32, + 7 => m.steam_id_owner = r.fixed64()?, + 8 => m.metadata = r.bytes()?.to_vec(), + 9 => m.members.push(MMSLobbyDataMember::deserialize(r.bytes()?)?), + _ => { + if !r.skip(t.wire_type) { + return None; + } + } + } + } + Some(m) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::proto_wire::Writer; + + #[test] + fn parses_lobby_data_members() { + let mut member = Vec::new(); + { + let mut w = Writer::new(&mut member); + w.fixed64_field(1, 200); + w.string_field(2, "Ada"); + w.bytes_field(3, b"meta"); + } + let mut body = Vec::new(); + { + let mut w = Writer::new(&mut body); + w.uint32_field(1, 480); + w.fixed64_field(2, 100); + w.uint32_field(3, 1); + w.fixed64_field(7, 200); + w.submessage_field(9, &member); + } + let parsed = CMsgClientMMSLobbyData::deserialize(&body).unwrap(); + assert_eq!(parsed.members[0].persona_name, "Ada"); + assert_eq!(parsed.steam_id_owner, 200); + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/pb/cmsg_client_mms_lobby_ops.rs b/app/src/main/cpp/wn-steam-client/rust/src/pb/cmsg_client_mms_lobby_ops.rs new file mode 100644 index 000000000..a793805b1 --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/pb/cmsg_client_mms_lobby_ops.rs @@ -0,0 +1,379 @@ +use crate::proto_wire::{Reader, Writer}; + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct CMsgClientMMSCreateLobby { + pub app_id: u32, + pub max_members: i32, + pub lobby_type: i32, + pub lobby_flags: i32, + pub metadata: Vec, + pub persona_name_owner: String, +} + +impl CMsgClientMMSCreateLobby { + pub fn serialize(&self) -> Vec { + let mut out = Vec::new(); + let mut w = Writer::new(&mut out); + w.uint32_field(1, self.app_id); + w.int32_field(2, self.max_members); + w.int32_field(3, self.lobby_type); + w.int32_field(4, self.lobby_flags); + w.bytes_field(7, &self.metadata); + w.string_field(8, &self.persona_name_owner); + out + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct CMsgClientMMSCreateLobbyResponse { + pub app_id: u32, + pub steam_id_lobby: u64, + pub eresult: i32, +} + +impl Default for CMsgClientMMSCreateLobbyResponse { + fn default() -> Self { + Self { + app_id: 0, + steam_id_lobby: 0, + eresult: 2, + } + } +} + +impl CMsgClientMMSCreateLobbyResponse { + pub fn deserialize(body: &[u8]) -> Option { + parse_simple_lobby_response(body) + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct CMsgClientMMSJoinLobby { + pub app_id: u32, + pub steam_id_lobby: u64, + pub persona_name: String, +} + +impl CMsgClientMMSJoinLobby { + pub fn serialize(&self) -> Vec { + let mut out = Vec::new(); + let mut w = Writer::new(&mut out); + w.uint32_field(1, self.app_id); + w.fixed64_field(2, self.steam_id_lobby); + w.string_field(3, &self.persona_name); + out + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct CMsgClientMMSJoinLobbyResponseMember { + pub steam_id: u64, + pub persona_name: String, + pub metadata: Vec, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct CMsgClientMMSJoinLobbyResponse { + pub app_id: u32, + pub steam_id_lobby: u64, + pub chat_room_enter_response: i32, + pub max_members: i32, + pub lobby_type: i32, + pub lobby_flags: i32, + pub steam_id_owner: u64, + pub metadata: Vec, + pub members: Vec, +} + +impl Default for CMsgClientMMSJoinLobbyResponse { + fn default() -> Self { + Self { + app_id: 0, + steam_id_lobby: 0, + chat_room_enter_response: 2, + max_members: 0, + lobby_type: 0, + lobby_flags: 0, + steam_id_owner: 0, + metadata: Vec::new(), + members: Vec::new(), + } + } +} + +impl CMsgClientMMSJoinLobbyResponse { + pub fn deserialize(body: &[u8]) -> Option { + let mut r = Reader::new(body); + let mut m = Self::default(); + while !r.eof() { + let Some(t) = r.next_tag() else { + return r.ok().then_some(m); + }; + match t.field_number { + 1 => m.app_id = r.u32()?, + 2 => m.steam_id_lobby = r.fixed64()?, + 3 => m.chat_room_enter_response = r.u64()? as u32 as i32, + 4 => m.max_members = r.u64()? as u32 as i32, + 5 => m.lobby_type = r.u64()? as u32 as i32, + 6 => m.lobby_flags = r.u64()? as u32 as i32, + 7 => m.steam_id_owner = r.fixed64()?, + 8 => m.metadata = r.bytes()?.to_vec(), + 9 => m.members.push(parse_join_member(r.bytes()?)?), + _ => { + if !r.skip(t.wire_type) { + return None; + } + } + } + } + Some(m) + } +} + +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct CMsgClientMMSLeaveLobby { + pub app_id: u32, + pub steam_id_lobby: u64, +} + +impl CMsgClientMMSLeaveLobby { + pub fn serialize(&self) -> Vec { + let mut out = Vec::new(); + let mut w = Writer::new(&mut out); + w.uint32_field(1, self.app_id); + w.fixed64_field(2, self.steam_id_lobby); + out + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct CMsgClientMMSSetLobbyData { + pub app_id: u32, + pub steam_id_lobby: u64, + pub steam_id_member: u64, + pub max_members: i32, + pub lobby_type: i32, + pub lobby_flags: i32, + pub metadata: Vec, +} + +impl CMsgClientMMSSetLobbyData { + pub fn serialize(&self) -> Vec { + let mut out = Vec::new(); + let mut w = Writer::new(&mut out); + w.uint32_field(1, self.app_id); + w.fixed64_field(2, self.steam_id_lobby); + w.fixed64_field(3, self.steam_id_member); + w.int32_field(4, self.max_members); + w.int32_field(5, self.lobby_type); + w.int32_field(6, self.lobby_flags); + w.bytes_field(7, &self.metadata); + out + } +} + +pub type CMsgClientMMSSetLobbyDataResponse = CMsgClientMMSCreateLobbyResponse; + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct CMsgClientMMSSendLobbyChatMsg { + pub app_id: u32, + pub steam_id_lobby: u64, + pub lobby_message: Vec, +} + +impl CMsgClientMMSSendLobbyChatMsg { + pub fn serialize(&self) -> Vec { + let mut out = Vec::new(); + let mut w = Writer::new(&mut out); + w.uint32_field(1, self.app_id); + w.fixed64_field(2, self.steam_id_lobby); + w.bytes_field(4, &self.lobby_message); + out + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct CMsgClientMMSLobbyChatMsg { + pub app_id: u32, + pub steam_id_lobby: u64, + pub steam_id_sender: u64, + pub lobby_message: Vec, +} + +impl CMsgClientMMSLobbyChatMsg { + pub fn deserialize(body: &[u8]) -> Option { + let mut r = Reader::new(body); + let mut m = Self::default(); + while !r.eof() { + let Some(t) = r.next_tag() else { + return r.ok().then_some(m); + }; + match t.field_number { + 1 => m.app_id = r.u32()?, + 2 => m.steam_id_lobby = r.fixed64()?, + 3 => m.steam_id_sender = r.fixed64()?, + 4 => m.lobby_message = r.bytes()?.to_vec(), + _ => { + if !r.skip(t.wire_type) { + return None; + } + } + } + } + Some(m) + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct CMsgClientMMSUserJoinedOrLeftLobby { + pub app_id: u32, + pub steam_id_lobby: u64, + pub steam_id_user: u64, + pub persona_name: String, +} + +impl CMsgClientMMSUserJoinedOrLeftLobby { + pub fn deserialize(body: &[u8]) -> Option { + let mut r = Reader::new(body); + let mut m = Self::default(); + while !r.eof() { + let Some(t) = r.next_tag() else { + return r.ok().then_some(m); + }; + match t.field_number { + 1 => m.app_id = r.u32()?, + 2 => m.steam_id_lobby = r.fixed64()?, + 3 => m.steam_id_user = r.fixed64()?, + 4 => m.persona_name = r.string()?, + _ => { + if !r.skip(t.wire_type) { + return None; + } + } + } + } + Some(m) + } +} + +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct CMsgClientMMSInviteToLobby { + pub app_id: u32, + pub steam_id_lobby: u64, + pub steam_id_user_invited: u64, +} + +impl CMsgClientMMSInviteToLobby { + pub fn serialize(&self) -> Vec { + let mut out = Vec::new(); + let mut w = Writer::new(&mut out); + w.uint32_field(1, self.app_id); + w.fixed64_field(2, self.steam_id_lobby); + w.fixed64_field(3, self.steam_id_user_invited); + out + } +} + +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct CMsgClientMMSSetLobbyOwner { + pub app_id: u32, + pub steam_id_lobby: u64, + pub steam_id_new_owner: u64, +} + +impl CMsgClientMMSSetLobbyOwner { + pub fn serialize(&self) -> Vec { + let mut out = Vec::new(); + let mut w = Writer::new(&mut out); + w.uint32_field(1, self.app_id); + w.fixed64_field(2, self.steam_id_lobby); + w.fixed64_field(3, self.steam_id_new_owner); + out + } +} + +pub type CMsgClientMMSSetLobbyOwnerResponse = CMsgClientMMSCreateLobbyResponse; + +fn parse_simple_lobby_response(body: &[u8]) -> Option { + let mut r = Reader::new(body); + let mut m = CMsgClientMMSCreateLobbyResponse::default(); + while !r.eof() { + let Some(t) = r.next_tag() else { + return r.ok().then_some(m); + }; + match t.field_number { + 1 => m.app_id = r.u32()?, + 2 => m.steam_id_lobby = r.fixed64()?, + 3 => m.eresult = r.u64()? as u32 as i32, + _ => { + if !r.skip(t.wire_type) { + return None; + } + } + } + } + Some(m) +} + +fn parse_join_member(body: &[u8]) -> Option { + let mut r = Reader::new(body); + let mut m = CMsgClientMMSJoinLobbyResponseMember::default(); + while !r.eof() { + let Some(t) = r.next_tag() else { + return r.ok().then_some(m); + }; + match t.field_number { + 1 => m.steam_id = r.fixed64()?, + 2 => m.persona_name = r.string()?, + 3 => m.metadata = r.bytes()?.to_vec(), + _ => { + if !r.skip(t.wire_type) { + return None; + } + } + } + } + Some(m) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parses_join_response_members() { + let mut member = Vec::new(); + { + let mut w = Writer::new(&mut member); + w.fixed64_field(1, 9); + w.string_field(2, "Ada"); + w.bytes_field(3, b"m"); + } + let mut body = Vec::new(); + { + let mut w = Writer::new(&mut body); + w.uint32_field(1, 480); + w.fixed64_field(2, 100); + w.uint32_field(3, 1); + w.uint32_field(4, 4); + w.fixed64_field(7, 9); + w.bytes_field(8, b"lobby"); + w.submessage_field(9, &member); + } + let parsed = CMsgClientMMSJoinLobbyResponse::deserialize(&body).unwrap(); + assert_eq!(parsed.chat_room_enter_response, 1); + assert_eq!(parsed.members[0].persona_name, "Ada"); + } + + #[test] + fn serializes_lobby_chat_message() { + let body = CMsgClientMMSSendLobbyChatMsg { + app_id: 480, + steam_id_lobby: 100, + lobby_message: b"hello".to_vec(), + } + .serialize(); + assert_eq!(body[0], 8); + assert!(body.ends_with(b"hello")); + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/pb/cmsg_client_persona.rs b/app/src/main/cpp/wn-steam-client/rust/src/pb/cmsg_client_persona.rs new file mode 100644 index 000000000..8e3f38951 --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/pb/cmsg_client_persona.rs @@ -0,0 +1,184 @@ +use crate::proto_wire::{Reader, WireType, Writer}; + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct CMsgClientRequestFriendData { + pub persona_state_requested: u32, + pub friends: Vec, +} + +impl CMsgClientRequestFriendData { + pub fn serialize(&self) -> Vec { + let mut out = Vec::new(); + let mut w = Writer::new(&mut out); + w.uint32_field(1, self.persona_state_requested); + for id in &self.friends { + w.fixed64_field(2, *id); + } + out + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct PersonaStateFriend { + pub friendid: u64, + pub persona_state: u32, + pub game_played_app_id: u32, + pub player_name: String, + pub avatar_hash: Vec, + pub game_name: String, + pub gameid: u64, + pub rich_presence: Vec<(String, String)>, + pub has_persona_state: bool, + pub has_game: bool, +} + +impl PersonaStateFriend { + pub fn deserialize(body: &[u8]) -> Option { + let mut reader = Reader::new(body); + let mut msg = Self::default(); + while !reader.eof() { + let Some(tag) = reader.next_tag() else { + return reader.ok().then_some(msg); + }; + match (tag.field_number, tag.wire_type) { + (1, WireType::Fixed64) => msg.friendid = reader.fixed64()?, + (2, WireType::Varint) => { + msg.persona_state = reader.u32()?; + msg.has_persona_state = true; + } + (3, WireType::Varint) => { + msg.game_played_app_id = reader.u32()?; + msg.has_game = true; + } + (15, WireType::LengthDelimited) => msg.player_name = reader.string()?, + (25, WireType::LengthDelimited) => msg + .rich_presence + .push(parse_kv_submessage(reader.bytes()?)?), + (31, WireType::LengthDelimited) => msg.avatar_hash = reader.bytes()?.to_vec(), + (55, WireType::LengthDelimited) => msg.game_name = reader.string()?, + (56, WireType::Fixed64) => msg.gameid = reader.fixed64()?, + _ => { + if !reader.skip(tag.wire_type) { + return None; + } + } + } + } + Some(msg) + } +} + +fn parse_kv_submessage(body: &[u8]) -> Option<(String, String)> { + let mut reader = Reader::new(body); + let mut key = String::new(); + let mut value = String::new(); + while !reader.eof() { + let Some(tag) = reader.next_tag() else { + return reader.ok().then_some((key, value)); + }; + match tag.field_number { + 1 => key = reader.string()?, + 2 => value = reader.string()?, + _ => { + if !reader.skip(tag.wire_type) { + return None; + } + } + } + } + Some((key, value)) +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct CMsgClientPersonaState { + pub status_flags: u32, + pub friends: Vec, +} + +impl CMsgClientPersonaState { + pub fn deserialize(body: &[u8]) -> Option { + let mut reader = Reader::new(body); + let mut msg = Self::default(); + while !reader.eof() { + let Some(tag) = reader.next_tag() else { + return reader.ok().then_some(msg); + }; + match tag.field_number { + 1 => msg.status_flags = reader.u32()?, + 2 => msg + .friends + .push(PersonaStateFriend::deserialize(reader.bytes()?)?), + _ => { + if !reader.skip(tag.wire_type) { + return None; + } + } + } + } + Some(msg) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::proto_wire::Writer; + + #[test] + fn parses_persona_state_friend_with_rich_presence() { + let mut kv = Vec::new(); + { + let mut w = Writer::new(&mut kv); + w.string_field(1, "status"); + w.string_field(2, "Playing"); + } + + let mut friend = Vec::new(); + { + let mut w = Writer::new(&mut friend); + w.fixed64_field(1, 123); + w.uint32_field(2, 1); + w.uint32_field(3, 440); + w.string_field(15, "Ada"); + w.submessage_field(25, &kv); + w.bytes_field(31, &[1, 2, 3]); + w.string_field(55, "Team Fortress 2"); + w.fixed64_field(56, 440); + } + + let mut body = Vec::new(); + Writer::new(&mut body).submessage_field(2, &friend); + + let parsed = CMsgClientPersonaState::deserialize(&body).unwrap(); + let friend = &parsed.friends[0]; + assert_eq!(friend.friendid, 123); + assert_eq!(friend.player_name, "Ada"); + assert_eq!(friend.rich_presence[0], ("status".into(), "Playing".into())); + assert_eq!(friend.avatar_hash, [1, 2, 3]); + assert_eq!(friend.game_name, "Team Fortress 2"); + assert!(friend.has_persona_state); + assert!(friend.has_game); + } + + #[test] + fn stateful_push_with_field25_as_fixed64_still_parses() { + // Live persona pushes carry field 25 as a fixed64, not the rich-presence submessage. + let mut friend = Vec::new(); + { + let mut w = Writer::new(&mut friend); + w.fixed64_field(1, 77); + w.uint32_field(2, 1); + w.fixed64_field(25, 0); + w.string_field(15, "Online Friend"); + } + let mut body = Vec::new(); + Writer::new(&mut body).submessage_field(2, &friend); + + let parsed = CMsgClientPersonaState::deserialize(&body).unwrap(); + let friend = &parsed.friends[0]; + assert_eq!(friend.friendid, 77); + assert_eq!(friend.persona_state, 1); + assert!(friend.has_persona_state); + assert_eq!(friend.player_name, "Online Friend"); + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/pb/cmsg_client_pics.rs b/app/src/main/cpp/wn-steam-client/rust/src/pb/cmsg_client_pics.rs new file mode 100644 index 000000000..4cafe488d --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/pb/cmsg_client_pics.rs @@ -0,0 +1,562 @@ +use crate::proto_wire::{Reader, WireType, Writer}; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct CMsgClientPICSChangesSinceRequest { + pub since_change_number: u32, + pub send_app_info_changes: bool, + pub send_package_info_changes: bool, +} + +impl Default for CMsgClientPICSChangesSinceRequest { + fn default() -> Self { + Self { + since_change_number: 0, + send_app_info_changes: true, + send_package_info_changes: true, + } + } +} + +impl CMsgClientPICSChangesSinceRequest { + pub fn serialize(&self) -> Vec { + let mut out = Vec::new(); + let mut w = Writer::new(&mut out); + w.uint32_field_force(1, self.since_change_number); + w.bool_field_force(2, self.send_app_info_changes); + w.bool_field_force(3, self.send_package_info_changes); + out + } +} + +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct PicsAppChange { + pub appid: u32, + pub change_number: u32, + pub needs_token: bool, +} + +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct PicsPackageChange { + pub packageid: u32, + pub change_number: u32, + pub needs_token: bool, +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct CMsgClientPICSChangesSinceResponse { + pub current_change_number: u32, + pub since_change_number: u32, + pub force_full_update: bool, + pub package_changes: Vec, + pub app_changes: Vec, + pub force_full_app_update: bool, + pub force_full_package_update: bool, +} + +impl CMsgClientPICSChangesSinceResponse { + pub fn deserialize(body: &[u8]) -> Option { + let mut reader = Reader::new(body); + let mut msg = Self::default(); + while !reader.eof() { + let Some(tag) = reader.next_tag() else { + return reader.ok().then_some(msg); + }; + match tag.field_number { + 1 => msg.current_change_number = reader.u32()?, + 2 => msg.since_change_number = reader.u32()?, + 3 => msg.force_full_update = reader.boolean()?, + 4 => { + let c = parse_pics_change(reader.bytes()?)?; + msg.package_changes.push(PicsPackageChange { + packageid: c.id, + change_number: c.change_number, + needs_token: c.needs_token, + }); + } + 5 => { + let c = parse_pics_change(reader.bytes()?)?; + msg.app_changes.push(PicsAppChange { + appid: c.id, + change_number: c.change_number, + needs_token: c.needs_token, + }); + } + 6 => msg.force_full_app_update = reader.boolean()?, + 7 => msg.force_full_package_update = reader.boolean()?, + _ => { + if !reader.skip(tag.wire_type) { + return None; + } + } + } + } + Some(msg) + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct CMsgClientPICSAccessTokenRequest { + pub packageids: Vec, + pub appids: Vec, +} + +impl CMsgClientPICSAccessTokenRequest { + pub fn serialize(&self) -> Vec { + let mut out = Vec::new(); + let mut w = Writer::new(&mut out); + for id in &self.packageids { + w.tag(1, WireType::Varint); + w.varint(*id as u64); + } + for id in &self.appids { + w.tag(2, WireType::Varint); + w.varint(*id as u64); + } + out + } +} + +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct PicsPackageToken { + pub packageid: u32, + pub access_token: u64, +} + +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct PicsAppToken { + pub appid: u32, + pub access_token: u64, +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct CMsgClientPICSAccessTokenResponse { + pub package_access_tokens: Vec, + pub package_denied_tokens: Vec, + pub app_access_tokens: Vec, + pub app_denied_tokens: Vec, +} + +impl CMsgClientPICSAccessTokenResponse { + pub fn deserialize(body: &[u8]) -> Option { + let mut reader = Reader::new(body); + let mut msg = Self::default(); + while !reader.eof() { + let Some(tag) = reader.next_tag() else { + return reader.ok().then_some(msg); + }; + match tag.field_number { + 1 => msg + .package_access_tokens + .push(parse_package_token(reader.bytes()?)?), + 2 => read_repeated_uint32( + &mut reader, + tag.wire_type, + &mut msg.package_denied_tokens, + )?, + 3 => msg + .app_access_tokens + .push(parse_app_token(reader.bytes()?)?), + 4 => read_repeated_uint32(&mut reader, tag.wire_type, &mut msg.app_denied_tokens)?, + _ => { + if !reader.skip(tag.wire_type) { + return None; + } + } + } + } + Some(msg) + } +} + +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct PicsAppInfoReq { + pub appid: u32, + pub access_token: u64, + pub only_public_obsolete: bool, +} + +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct PicsPackageInfoReq { + pub packageid: u32, + pub access_token: u64, +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct CMsgClientPICSProductInfoRequest { + pub packages: Vec, + pub apps: Vec, + pub meta_data_only: bool, + pub num_prev_failed: u32, + pub sequence_number: u32, + pub single_response: bool, +} + +impl CMsgClientPICSProductInfoRequest { + pub fn serialize(&self) -> Vec { + let mut out = Vec::new(); + let mut w = Writer::new(&mut out); + for package in &self.packages { + let mut sub = Vec::new(); + let mut sw = Writer::new(&mut sub); + sw.uint32_field(1, package.packageid); + sw.uint64_field(2, package.access_token); + w.submessage_field(1, &sub); + } + for app in &self.apps { + let mut sub = Vec::new(); + let mut sw = Writer::new(&mut sub); + sw.uint32_field(1, app.appid); + sw.uint64_field(2, app.access_token); + sw.bool_field(3, app.only_public_obsolete); + w.submessage_field(2, &sub); + } + w.bool_field(3, self.meta_data_only); + w.uint32_field(4, self.num_prev_failed); + w.uint32_field(6, self.sequence_number); + w.bool_field(7, self.single_response); + out + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct PicsAppInfoResp { + pub appid: u32, + pub change_number: u32, + pub missing_token: bool, + pub sha: Vec, + pub buffer: Vec, + pub only_public: bool, + pub size: u32, +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct PicsPackageInfoResp { + pub packageid: u32, + pub change_number: u32, + pub missing_token: bool, + pub sha: Vec, + pub buffer: Vec, + pub size: u32, +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct CMsgClientPICSProductInfoResponse { + pub apps: Vec, + pub unknown_appids: Vec, + pub packages: Vec, + pub unknown_packageids: Vec, + pub meta_data_only: bool, + pub response_pending: bool, + pub http_min_size: u32, + pub http_host: String, +} + +impl CMsgClientPICSProductInfoResponse { + pub fn deserialize(body: &[u8]) -> Option { + let mut reader = Reader::new(body); + let mut msg = Self::default(); + while !reader.eof() { + let Some(tag) = reader.next_tag() else { + return reader.ok().then_some(msg); + }; + match tag.field_number { + 1 => msg.apps.push(parse_app_info_resp(reader.bytes()?)?), + 2 => read_repeated_uint32(&mut reader, tag.wire_type, &mut msg.unknown_appids)?, + 3 => msg.packages.push(parse_package_info_resp(reader.bytes()?)?), + 4 => read_repeated_uint32(&mut reader, tag.wire_type, &mut msg.unknown_packageids)?, + 5 => msg.meta_data_only = reader.boolean()?, + 6 => msg.response_pending = reader.boolean()?, + 7 => msg.http_min_size = reader.u32()?, + 8 => msg.http_host = reader.string()?, + _ => { + if !reader.skip(tag.wire_type) { + return None; + } + } + } + } + Some(msg) + } + + pub fn serialize(&self) -> Vec { + let mut out = Vec::new(); + let mut w = Writer::new(&mut out); + for app in &self.apps { + w.submessage_field(1, &app.serialize()); + } + for appid in &self.unknown_appids { + w.uint32_field(2, *appid); + } + for package in &self.packages { + w.submessage_field(3, &package.serialize()); + } + for packageid in &self.unknown_packageids { + w.uint32_field(4, *packageid); + } + w.bool_field(5, self.meta_data_only); + w.bool_field(6, self.response_pending); + w.uint32_field(7, self.http_min_size); + w.string_field(8, &self.http_host); + out + } +} + +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +struct PicsChangeRaw { + id: u32, + change_number: u32, + needs_token: bool, +} + +fn parse_pics_change(body: &[u8]) -> Option { + let mut reader = Reader::new(body); + let mut msg = PicsChangeRaw::default(); + while !reader.eof() { + let Some(tag) = reader.next_tag() else { + return reader.ok().then_some(msg); + }; + match tag.field_number { + 1 => msg.id = reader.u32()?, + 2 => msg.change_number = reader.u32()?, + 3 => msg.needs_token = reader.boolean()?, + _ => { + if !reader.skip(tag.wire_type) { + return None; + } + } + } + } + Some(msg) +} + +fn parse_package_token(body: &[u8]) -> Option { + let raw = parse_token(body)?; + Some(PicsPackageToken { + packageid: raw.id, + access_token: raw.access_token, + }) +} + +fn parse_app_token(body: &[u8]) -> Option { + let raw = parse_token(body)?; + Some(PicsAppToken { + appid: raw.id, + access_token: raw.access_token, + }) +} + +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +struct TokenRaw { + id: u32, + access_token: u64, +} + +fn parse_token(body: &[u8]) -> Option { + let mut reader = Reader::new(body); + let mut msg = TokenRaw::default(); + while !reader.eof() { + let Some(tag) = reader.next_tag() else { + return reader.ok().then_some(msg); + }; + match tag.field_number { + 1 => msg.id = reader.u32()?, + 2 => msg.access_token = reader.u64()?, + _ => { + if !reader.skip(tag.wire_type) { + return None; + } + } + } + } + Some(msg) +} + +fn parse_app_info_resp(body: &[u8]) -> Option { + let mut reader = Reader::new(body); + let mut msg = PicsAppInfoResp::default(); + while !reader.eof() { + let Some(tag) = reader.next_tag() else { + return reader.ok().then_some(msg); + }; + match tag.field_number { + 1 => msg.appid = reader.u32()?, + 2 => msg.change_number = reader.u32()?, + 3 => msg.missing_token = reader.boolean()?, + 4 => msg.sha = reader.bytes()?.to_vec(), + 5 => msg.buffer = reader.bytes()?.to_vec(), + 6 => msg.only_public = reader.boolean()?, + 7 => msg.size = reader.u32()?, + _ => { + if !reader.skip(tag.wire_type) { + return None; + } + } + } + } + Some(msg) +} + +impl PicsAppInfoResp { + fn serialize(&self) -> Vec { + let mut out = Vec::new(); + let mut w = Writer::new(&mut out); + w.uint32_field(1, self.appid); + w.uint32_field(2, self.change_number); + w.bool_field(3, self.missing_token); + w.bytes_field(4, &self.sha); + w.bytes_field(5, &self.buffer); + w.bool_field(6, self.only_public); + w.uint32_field(7, self.size); + out + } +} + +fn parse_package_info_resp(body: &[u8]) -> Option { + let mut reader = Reader::new(body); + let mut msg = PicsPackageInfoResp::default(); + while !reader.eof() { + let Some(tag) = reader.next_tag() else { + return reader.ok().then_some(msg); + }; + match tag.field_number { + 1 => msg.packageid = reader.u32()?, + 2 => msg.change_number = reader.u32()?, + 3 => msg.missing_token = reader.boolean()?, + 4 => msg.sha = reader.bytes()?.to_vec(), + 5 => msg.buffer = reader.bytes()?.to_vec(), + 6 => msg.size = reader.u32()?, + _ => { + if !reader.skip(tag.wire_type) { + return None; + } + } + } + } + Some(msg) +} + +impl PicsPackageInfoResp { + fn serialize(&self) -> Vec { + let mut out = Vec::new(); + let mut w = Writer::new(&mut out); + w.uint32_field(1, self.packageid); + w.uint32_field(2, self.change_number); + w.bool_field(3, self.missing_token); + w.bytes_field(4, &self.sha); + w.bytes_field(5, &self.buffer); + w.uint32_field(6, self.size); + out + } +} + +fn read_repeated_uint32( + reader: &mut Reader<'_>, + wire_type: WireType, + out: &mut Vec, +) -> Option<()> { + match wire_type { + WireType::Varint => out.push(reader.u32()?), + WireType::LengthDelimited => { + let mut packed = Reader::new(reader.bytes()?); + while !packed.eof() { + out.push(packed.varint()? as u32); + } + } + _ => return None, + } + Some(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn token_body(id: u32, token: u64) -> Vec { + let mut body = Vec::new(); + let mut w = Writer::new(&mut body); + w.uint32_field(1, id); + w.uint64_field(2, token); + body + } + + #[test] + fn changes_since_request_force_emits_zero_and_bools() { + assert_eq!( + CMsgClientPICSChangesSinceRequest::default().serialize(), + [8, 0, 16, 1, 24, 1] + ); + } + + #[test] + fn parses_access_tokens_and_packed_denied_lists() { + let mut packed = Vec::new(); + { + let mut w = Writer::new(&mut packed); + w.varint(10); + w.varint(11); + } + + let mut body = Vec::new(); + { + let mut w = Writer::new(&mut body); + w.submessage_field(1, &token_body(100, 555)); + w.uint32_field(2, 9); + w.bytes_field(2, &packed); + w.submessage_field(3, &token_body(480, 777)); + w.uint32_field(4, 12); + } + + let parsed = CMsgClientPICSAccessTokenResponse::deserialize(&body).unwrap(); + assert_eq!(parsed.package_access_tokens[0].packageid, 100); + assert_eq!(parsed.package_denied_tokens, [9, 10, 11]); + assert_eq!(parsed.app_access_tokens[0].appid, 480); + assert_eq!(parsed.app_denied_tokens, [12]); + } + + #[test] + fn parses_product_info_response_without_stripping_package_prefix() { + let mut app = Vec::new(); + { + let mut w = Writer::new(&mut app); + w.uint32_field(1, 480); + w.uint32_field(2, 22); + w.bool_field(3, true); + w.bytes_field(4, &[1; 20]); + w.bytes_field(5, b"appvdf"); + w.bool_field(6, true); + w.uint32_field(7, 6); + } + + let mut package = Vec::new(); + { + let mut w = Writer::new(&mut package); + w.uint32_field(1, 100); + w.uint32_field(2, 33); + w.bytes_field(5, &[100, 0, 0, 0, b'v', b'd', b'f']); + w.uint32_field(6, 7); + } + + let mut packed_unknown = Vec::new(); + Writer::new(&mut packed_unknown).varint(888); + + let mut body = Vec::new(); + { + let mut w = Writer::new(&mut body); + w.submessage_field(1, &app); + w.uint32_field(2, 404); + w.submessage_field(3, &package); + w.bytes_field(4, &packed_unknown); + w.bool_field(6, true); + w.uint32_field(7, 1024); + w.string_field(8, "cdn.example"); + } + + let parsed = CMsgClientPICSProductInfoResponse::deserialize(&body).unwrap(); + assert_eq!(parsed.apps[0].appid, 480); + assert_eq!(parsed.unknown_appids, [404]); + assert_eq!(parsed.unknown_packageids, [888]); + assert_eq!(parsed.packages[0].buffer, [100, 0, 0, 0, b'v', b'd', b'f']); + assert!(parsed.response_pending); + assert_eq!(parsed.http_host, "cdn.example"); + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/pb/cmsg_client_playing_session_state.rs b/app/src/main/cpp/wn-steam-client/rust/src/pb/cmsg_client_playing_session_state.rs new file mode 100644 index 000000000..a91054f8a --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/pb/cmsg_client_playing_session_state.rs @@ -0,0 +1,63 @@ +use crate::proto_wire::Reader; + +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct CMsgClientPlayingSessionState { + pub playing_blocked: bool, + pub playing_app: u32, +} + +impl CMsgClientPlayingSessionState { + pub fn deserialize(body: &[u8]) -> Option { + let mut reader = Reader::new(body); + let mut msg = Self::default(); + while !reader.eof() { + let Some(tag) = reader.next_tag() else { + return reader.ok().then_some(msg); + }; + match tag.field_number { + 2 => msg.playing_blocked = reader.boolean()?, + 3 => msg.playing_app = reader.u32()?, + _ => { + if !reader.skip(tag.wire_type) { + return None; + } + } + } + } + Some(msg) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::pb::cmsg_client_change_status::CMsgClientChangeStatus; + use crate::pb::cmsg_client_kick_playing_session::CMsgClientKickPlayingSession; + use crate::proto_wire::Writer; + + #[test] + fn force_emits_zero_and_false_values() { + assert_eq!( + CMsgClientChangeStatus::default().serialize(), + vec![0x08, 0x00] + ); + assert_eq!( + CMsgClientKickPlayingSession { + only_stop_game: false + } + .serialize(), + vec![0x08, 0x00] + ); + } + + #[test] + fn parses_playing_session_state() { + let mut body = Vec::new(); + let mut w = Writer::new(&mut body); + w.bool_field(2, true); + w.uint32_field(3, 480); + let parsed = CMsgClientPlayingSessionState::deserialize(&body).unwrap(); + assert!(parsed.playing_blocked); + assert_eq!(parsed.playing_app, 480); + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/pb/cmsg_client_request_encrypted_app_ticket.rs b/app/src/main/cpp/wn-steam-client/rust/src/pb/cmsg_client_request_encrypted_app_ticket.rs new file mode 100644 index 000000000..9179804bd --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/pb/cmsg_client_request_encrypted_app_ticket.rs @@ -0,0 +1,54 @@ +use crate::proto_wire::{Reader, Writer}; + +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct CMsgClientRequestEncryptedAppTicket { + pub app_id: u32, +} + +impl CMsgClientRequestEncryptedAppTicket { + pub fn serialize(&self) -> Vec { + let mut out = Vec::new(); + Writer::new(&mut out).uint32_field(1, self.app_id); + out + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct CMsgClientRequestEncryptedAppTicketResponse { + pub app_id: u32, + pub eresult: i32, + pub encrypted_app_ticket: Vec, +} + +impl Default for CMsgClientRequestEncryptedAppTicketResponse { + fn default() -> Self { + Self { + app_id: 0, + eresult: 2, + encrypted_app_ticket: Vec::new(), + } + } +} + +impl CMsgClientRequestEncryptedAppTicketResponse { + pub fn deserialize(body: &[u8]) -> Option { + let mut reader = Reader::new(body); + let mut msg = Self::default(); + while !reader.eof() { + let Some(tag) = reader.next_tag() else { + return reader.ok().then_some(msg); + }; + match tag.field_number { + 1 => msg.app_id = reader.u32()?, + 2 => msg.eresult = reader.i32()?, + 3 => msg.encrypted_app_ticket = reader.bytes()?.to_vec(), + _ => { + if !reader.skip(tag.wire_type) { + return None; + } + } + } + } + Some(msg) + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/pb/cmsg_client_store_user_stats.rs b/app/src/main/cpp/wn-steam-client/rust/src/pb/cmsg_client_store_user_stats.rs new file mode 100644 index 000000000..99a4e456d --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/pb/cmsg_client_store_user_stats.rs @@ -0,0 +1,76 @@ +use crate::proto_wire::Writer; + +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct Stat { + pub stat_id: u32, + pub stat_value: u32, +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct CMsgClientStoreUserStats2 { + pub game_id: u64, + pub settor_steam_id: u64, + pub settee_steam_id: u64, + pub crc_stats: u32, + pub stats: Vec, +} + +impl CMsgClientStoreUserStats2 { + pub fn serialize(&self) -> Vec { + let mut out = Vec::new(); + let mut w = Writer::new(&mut out); + w.fixed64_field(1, self.game_id); + w.fixed64_field(2, self.settor_steam_id); + w.fixed64_field(3, self.settee_steam_id); + w.uint32_field_force(4, self.crc_stats); + for stat in &self.stats { + let mut sub = Vec::new(); + let mut sw = Writer::new(&mut sub); + sw.uint32_field_force(1, stat.stat_id); + sw.uint32_field_force(2, stat.stat_value); + w.submessage_field(6, &sub); + } + out + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::proto_wire::{Reader, WireType}; + + #[test] + fn keeps_zero_crc_and_zero_stat_values_present() { + let msg = CMsgClientStoreUserStats2 { + game_id: 7, + settor_steam_id: 8, + settee_steam_id: 9, + crc_stats: 0, + stats: vec![Stat { + stat_id: 0, + stat_value: 0, + }], + }; + let body = msg.serialize(); + let mut reader = Reader::new(&body); + let mut saw_crc = false; + let mut saw_stat = false; + while !reader.eof() { + let tag = reader.next_tag().unwrap(); + match (tag.field_number, tag.wire_type) { + (4, WireType::Varint) => { + assert_eq!(reader.u32().unwrap(), 0); + saw_crc = true; + } + (6, WireType::LengthDelimited) => { + let sub = reader.bytes().unwrap(); + assert_eq!(sub, &[8, 0, 16, 0]); + saw_stat = true; + } + _ => assert!(reader.skip(tag.wire_type)), + } + } + assert!(saw_crc); + assert!(saw_stat); + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/pb/cmsg_clientserver_login.rs b/app/src/main/cpp/wn-steam-client/rust/src/pb/cmsg_clientserver_login.rs new file mode 100644 index 000000000..a56f9ccd3 --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/pb/cmsg_clientserver_login.rs @@ -0,0 +1,275 @@ +use crate::proto_wire::{Reader, WireType, Writer}; + +pub const MSG_CLIENT_CURRENT_PROTOCOL: u32 = 65_581; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct CMsgClientHello { + pub protocol_version: u32, +} + +impl Default for CMsgClientHello { + fn default() -> Self { + Self { + protocol_version: MSG_CLIENT_CURRENT_PROTOCOL, + } + } +} + +impl CMsgClientHello { + pub fn serialize(&self) -> Vec { + let mut out = Vec::new(); + Writer::new(&mut out).uint32_field(1, self.protocol_version); + out + } +} + +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct CMsgClientHeartBeat { + pub send_reply: bool, +} + +impl CMsgClientHeartBeat { + pub fn serialize(&self) -> Vec { + let mut out = Vec::new(); + Writer::new(&mut out).bool_field(1, self.send_reply); + out + } +} + +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct CMsgClientLogOff; + +impl CMsgClientLogOff { + pub fn serialize(&self) -> Vec { + Vec::new() + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct CMsgClientLoggedOff { + pub eresult: i32, +} + +impl Default for CMsgClientLoggedOff { + fn default() -> Self { + Self { eresult: 2 } + } +} + +impl CMsgClientLoggedOff { + pub fn deserialize(body: &[u8]) -> Option { + let mut reader = Reader::new(body); + let mut msg = Self::default(); + while !reader.eof() { + let Some(tag) = reader.next_tag() else { + return reader.ok().then_some(msg); + }; + match tag.field_number { + 1 => msg.eresult = reader.i32()?, + _ => { + if !reader.skip(tag.wire_type) { + return None; + } + } + } + } + Some(msg) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct CMsgClientLogon { + pub protocol_version: u32, + pub cell_id: u32, + pub client_package_version: u32, + pub client_language: String, + pub client_os_type: u32, + pub should_remember_password: bool, + pub qos_level: u32, + pub client_supplied_steam_id: u64, + pub machine_id: Vec, + pub ui_mode: u32, + pub chat_mode: u32, + pub account_name: String, + pub machine_name: String, + pub client_instance_id: u64, + pub supports_rate_limit_response: bool, + pub access_token: String, + pub obfuscated_private_ip: u32, +} + +impl Default for CMsgClientLogon { + fn default() -> Self { + Self { + protocol_version: MSG_CLIENT_CURRENT_PROTOCOL, + cell_id: 0, + client_package_version: 1771, + client_language: "english".to_string(), + client_os_type: 16, + should_remember_password: true, + qos_level: 2, + client_supplied_steam_id: 0, + machine_id: Vec::new(), + ui_mode: 7, + chat_mode: 2, + account_name: String::new(), + machine_name: "WN-Steam-Client".to_string(), + client_instance_id: 0, + supports_rate_limit_response: true, + access_token: String::new(), + obfuscated_private_ip: 0, + } + } +} + +impl CMsgClientLogon { + pub fn serialize(&self) -> Vec { + let mut out = Vec::new(); + let mut w = Writer::new(&mut out); + w.uint32_field(1, self.protocol_version); + w.uint32_field(3, self.cell_id); + w.uint32_field(5, self.client_package_version); + w.string_field(6, &self.client_language); + w.uint32_field(7, self.client_os_type); + w.bool_field(8, self.should_remember_password); + w.uint32_field(21, self.qos_level); + if self.client_supplied_steam_id != 0 { + w.tag(22, WireType::Fixed64); + w.raw_bytes(&self.client_supplied_steam_id.to_le_bytes()); + } + w.bytes_field(30, &self.machine_id); + if self.obfuscated_private_ip != 0 { + w.uint32_field(31, self.obfuscated_private_ip); + let mut ip_msg = Vec::new(); + Writer::new(&mut ip_msg).uint32_field(1, self.obfuscated_private_ip); + w.bytes_field(95, &ip_msg); + } + w.uint32_field(32, self.ui_mode); + w.uint32_field(33, self.chat_mode); + w.string_field(50, &self.account_name); + w.string_field(96, &self.machine_name); + w.uint64_field(100, self.client_instance_id); + w.bool_field(102, self.supports_rate_limit_response); + w.string_field(108, &self.access_token); + out + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct CMsgClientLogonResponse { + pub eresult: i32, + pub heartbeat_seconds: i32, + pub rtime32_server_time: u32, + pub cell_id: u32, + pub eresult_extended: i32, + pub vanity_url: String, + pub client_supplied_steamid: u64, + pub client_instance_id: u64, + pub force_client_update_check: bool, + pub agreement_session_url: String, + pub token_id: u64, + pub family_group_id: u64, +} + +impl Default for CMsgClientLogonResponse { + fn default() -> Self { + Self { + eresult: 2, + heartbeat_seconds: 0, + rtime32_server_time: 0, + cell_id: 0, + eresult_extended: 0, + vanity_url: String::new(), + client_supplied_steamid: 0, + client_instance_id: 0, + force_client_update_check: false, + agreement_session_url: String::new(), + token_id: 0, + family_group_id: 0, + } + } +} + +impl CMsgClientLogonResponse { + pub fn deserialize(body: &[u8]) -> Option { + let mut reader = Reader::new(body); + let mut msg = Self::default(); + while !reader.eof() { + let Some(tag) = reader.next_tag() else { + return reader.ok().then_some(msg); + }; + match tag.field_number { + 1 => msg.eresult = reader.i32()?, + 3 => msg.heartbeat_seconds = reader.i32()?, + 5 => { + if tag.wire_type != WireType::Fixed32 { + return None; + } + msg.rtime32_server_time = reader.fixed32()?; + } + 7 => msg.cell_id = reader.u32()?, + 10 => msg.eresult_extended = reader.i32()?, + 14 => msg.vanity_url = reader.string()?, + 20 => { + if tag.wire_type != WireType::Fixed64 { + return None; + } + msg.client_supplied_steamid = reader.fixed64()?; + } + 27 => msg.client_instance_id = reader.u64()?, + 28 => msg.force_client_update_check = reader.boolean()?, + 29 => msg.agreement_session_url = reader.string()?, + 30 => msg.token_id = reader.u64()?, + 31 => msg.family_group_id = reader.u64()?, + _ => { + if !reader.skip(tag.wire_type) { + return None; + } + } + } + } + Some(msg) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn logon_serializes_canonical_fields() { + let msg = CMsgClientLogon { + account_name: "user".to_string(), + access_token: "refresh.jwt".to_string(), + client_instance_id: 99, + obfuscated_private_ip: 0x0102_0304, + ..Default::default() + }; + let bytes = msg.serialize(); + let mut reader = Reader::new(&bytes); + let mut fields = Vec::new(); + while let Some(tag) = reader.next_tag() { + fields.push(tag.field_number); + reader.skip(tag.wire_type); + } + assert!(fields.contains(&50)); + assert!(fields.contains(&95)); + assert!(fields.contains(&108)); + assert!(!fields.contains(&10)); + } + + #[test] + fn logon_response_reads_token_id_as_varint() { + let mut bytes = Vec::new(); + let mut w = Writer::new(&mut bytes); + w.int32_field(1, 1); + w.int32_field(3, 10); + w.tag(5, WireType::Fixed32); + w.raw_bytes(&123u32.to_le_bytes()); + w.uint64_field(30, 987); + let msg = CMsgClientLogonResponse::deserialize(&bytes).unwrap(); + assert_eq!(msg.eresult, 1); + assert_eq!(msg.rtime32_server_time, 123); + assert_eq!(msg.token_id, 987); + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/pb/cplayer.rs b/app/src/main/cpp/wn-steam-client/rust/src/pb/cplayer.rs new file mode 100644 index 000000000..9509b3cbe --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/pb/cplayer.rs @@ -0,0 +1,171 @@ +use crate::proto_wire::{Reader, Writer}; + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct CPlayerSetRichPresenceKv { + pub key: String, + pub value: String, +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct CPlayerSetRichPresenceRequest { + pub appid: u32, + pub rich_presence: Vec, +} + +impl CPlayerSetRichPresenceRequest { + pub fn serialize(&self) -> Vec { + let mut out = Vec::new(); + let mut w = Writer::new(&mut out); + w.uint32_field_force(1, self.appid); + for kv in &self.rich_presence { + let mut sub = Vec::new(); + let mut sw = Writer::new(&mut sub); + sw.string_field(1, &kv.key); + sw.string_field(2, &kv.value); + w.submessage_field(2, &sub); + } + out + } +} + +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct CPlayerGetOwnedGamesRequest { + pub steamid: u64, + pub include_appinfo: bool, + pub include_played_free_games: bool, + pub include_free_sub: bool, + pub include_extended_appinfo: bool, +} + +impl CPlayerGetOwnedGamesRequest { + pub fn serialize(&self) -> Vec { + let mut out = Vec::new(); + let mut w = Writer::new(&mut out); + w.uint64_field(1, self.steamid); + w.bool_field(2, self.include_appinfo); + w.bool_field(3, self.include_played_free_games); + w.bool_field(5, self.include_free_sub); + w.bool_field(8, self.include_extended_appinfo); + out + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct CPlayerOwnedGame { + pub appid: i32, + pub name: String, + pub playtime_2weeks: i32, + pub playtime_forever: i32, + pub img_icon_url: String, + pub rtime_last_played: u32, + pub sort_as: String, +} + +impl CPlayerOwnedGame { + pub fn deserialize(body: &[u8]) -> Option { + let mut reader = Reader::new(body); + let mut msg = Self::default(); + while !reader.eof() { + let Some(tag) = reader.next_tag() else { + return reader.ok().then_some(msg); + }; + match tag.field_number { + 1 => msg.appid = reader.u32()? as i32, + 2 => msg.name = reader.string()?, + 3 => msg.playtime_2weeks = reader.u32()? as i32, + 4 => msg.playtime_forever = reader.u32()? as i32, + 5 => msg.img_icon_url = reader.string()?, + 11 => msg.rtime_last_played = reader.u32()?, + 13 => msg.sort_as = reader.string()?, + _ => { + if !reader.skip(tag.wire_type) { + return None; + } + } + } + } + Some(msg) + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct CPlayerGetOwnedGamesResponse { + pub game_count: u32, + pub games: Vec, +} + +impl CPlayerGetOwnedGamesResponse { + pub fn deserialize(body: &[u8]) -> Option { + let mut reader = Reader::new(body); + let mut msg = Self::default(); + while !reader.eof() { + let Some(tag) = reader.next_tag() else { + return reader.ok().then_some(msg); + }; + match tag.field_number { + 1 => msg.game_count = reader.u32()?, + 2 => msg + .games + .push(CPlayerOwnedGame::deserialize(reader.bytes()?)?), + _ => { + if !reader.skip(tag.wire_type) { + return None; + } + } + } + } + Some(msg) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn rich_presence_force_emits_zero_appid() { + let body = CPlayerSetRichPresenceRequest { + appid: 0, + rich_presence: vec![CPlayerSetRichPresenceKv { + key: "status".to_string(), + value: "Playing".to_string(), + }], + } + .serialize(); + assert_eq!( + body, + [ + 8, 0, 18, 17, 10, 6, b's', b't', b'a', b't', b'u', b's', 18, 7, b'P', b'l', b'a', + b'y', b'i', b'n', b'g' + ] + ); + } + + #[test] + fn parses_owned_games_response() { + let mut game = Vec::new(); + { + let mut w = Writer::new(&mut game); + w.uint32_field(1, 42); + w.string_field(2, "Half-Life"); + w.uint32_field(3, 10); + w.uint32_field(4, 200); + w.string_field(5, "icon"); + w.uint32_field(11, 12345); + w.string_field(13, "Half Life"); + } + + let mut body = Vec::new(); + { + let mut w = Writer::new(&mut body); + w.uint32_field(1, 1); + w.submessage_field(2, &game); + } + + let parsed = CPlayerGetOwnedGamesResponse::deserialize(&body).unwrap(); + assert_eq!(parsed.game_count, 1); + assert_eq!(parsed.games[0].appid, 42); + assert_eq!(parsed.games[0].name, "Half-Life"); + assert_eq!(parsed.games[0].sort_as, "Half Life"); + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/pb/cpublishedfile.rs b/app/src/main/cpp/wn-steam-client/rust/src/pb/cpublishedfile.rs new file mode 100644 index 000000000..62b13ee75 --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/pb/cpublishedfile.rs @@ -0,0 +1,190 @@ +use crate::proto_wire::{Reader, WireType, Writer}; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct CPublishedFileGetUserFilesRequest { + pub steamid: u64, + pub appid: u32, + pub page: u32, + pub numperpage: u32, + pub request_type: String, + pub filetype: u32, +} + +impl Default for CPublishedFileGetUserFilesRequest { + fn default() -> Self { + Self { + steamid: 0, + appid: 0, + page: 1, + numperpage: 1, + request_type: String::new(), + filetype: 0, + } + } +} + +impl CPublishedFileGetUserFilesRequest { + pub fn serialize(&self) -> Vec { + let mut out = Vec::new(); + let mut w = Writer::new(&mut out); + w.fixed64_field(1, self.steamid); + w.uint32_field(2, self.appid); + w.uint32_field(4, self.page); + w.uint32_field(5, self.numperpage); + w.string_field(6, &self.request_type); + w.uint32_field(14, self.filetype); + out + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct PublishedFileDetails { + pub result: u32, + pub publishedfileid: u64, + pub consumer_appid: u32, + pub filename: String, + pub file_size: u64, + pub file_url: String, + pub preview_url: String, + pub hcontent_file: u64, + pub title: String, + pub time_updated: u32, +} + +impl PublishedFileDetails { + pub fn parse(body: &[u8]) -> Option { + let mut reader = Reader::new(body); + let mut msg = Self::default(); + while !reader.eof() { + let Some(tag) = reader.next_tag() else { + return reader.ok().then_some(msg); + }; + let handled = match (tag.field_number, tag.wire_type) { + (1, WireType::Varint) => { + msg.result = reader.u32()?; + true + } + (2, WireType::Varint) => { + msg.publishedfileid = reader.u64()?; + true + } + (5, WireType::Varint) => { + msg.consumer_appid = reader.u32()?; + true + } + (7, WireType::LengthDelimited) => { + msg.filename = reader.string()?; + true + } + (8, WireType::Varint) => { + msg.file_size = reader.u64()?; + true + } + (10, WireType::LengthDelimited) => { + msg.file_url = reader.string()?; + true + } + (11, WireType::LengthDelimited) => { + msg.preview_url = reader.string()?; + true + } + (14, WireType::Fixed64) => { + msg.hcontent_file = reader.fixed64()?; + true + } + (16, WireType::LengthDelimited) => { + msg.title = reader.string()?; + true + } + (20, WireType::Varint) => { + msg.time_updated = reader.u32()?; + true + } + _ => false, + }; + if !handled && !reader.skip(tag.wire_type) { + return None; + } + } + Some(msg) + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct CPublishedFileGetUserFilesResponse { + pub total: u32, + pub startindex: u32, + pub publishedfiledetails: Vec, +} + +impl CPublishedFileGetUserFilesResponse { + pub fn deserialize(body: &[u8]) -> Option { + let mut reader = Reader::new(body); + let mut msg = Self::default(); + while !reader.eof() { + let Some(tag) = reader.next_tag() else { + return reader.ok().then_some(msg); + }; + let handled = match (tag.field_number, tag.wire_type) { + (1, WireType::Varint) => { + msg.total = reader.u32()?; + true + } + (2, WireType::Varint) => { + msg.startindex = reader.u32()?; + true + } + (3, WireType::LengthDelimited) => { + msg.publishedfiledetails + .push(PublishedFileDetails::parse(reader.bytes()?)?); + true + } + _ => false, + }; + if !handled && !reader.skip(tag.wire_type) { + return None; + } + } + Some(msg) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parses_user_files_response_and_skips_schema_drift() { + let mut detail = Vec::new(); + { + let mut w = Writer::new(&mut detail); + w.uint32_field(1, 1); + w.uint64_field(2, 99); + w.uint32_field(5, 480); + w.string_field(7, "file.bin"); + w.uint64_field(8, 1024); + w.string_field(10, "https://example/file"); + w.string_field(11, "https://example/preview"); + w.fixed64_field(14, 777); + w.string_field(16, "Workshop Item"); + w.uint32_field(20, 123456); + w.fixed64_field(5, 0xfeed); + } + + let mut body = Vec::new(); + { + let mut w = Writer::new(&mut body); + w.uint32_field(1, 1); + w.uint32_field(2, 0); + w.submessage_field(3, &detail); + } + + let parsed = CPublishedFileGetUserFilesResponse::deserialize(&body).unwrap(); + let detail = &parsed.publishedfiledetails[0]; + assert_eq!(parsed.total, 1); + assert_eq!(detail.publishedfileid, 99); + assert_eq!(detail.consumer_appid, 480); + assert_eq!(detail.hcontent_file, 777); + assert_eq!(detail.title, "Workshop Item"); + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/pb/mod.rs b/app/src/main/cpp/wn-steam-client/rust/src/pb/mod.rs new file mode 100644 index 000000000..27a82e1b4 --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/pb/mod.rs @@ -0,0 +1,25 @@ +pub mod cauthentication; +pub mod ccloud; +pub mod ccontentserverdirectory; +pub mod cfamilygroups; +pub mod cfriendmessages; +pub mod cinventory; +pub mod cmsg_client_change_status; +pub mod cmsg_client_friends_list; +pub mod cmsg_client_games_played; +pub mod cmsg_client_get_app_ownership_ticket; +pub mod cmsg_client_get_depot_decryption_key; +pub mod cmsg_client_get_user_stats; +pub mod cmsg_client_kick_playing_session; +pub mod cmsg_client_license_list; +pub mod cmsg_client_mms_get_lobby_list; +pub mod cmsg_client_mms_lobby_data; +pub mod cmsg_client_mms_lobby_ops; +pub mod cmsg_client_persona; +pub mod cmsg_client_pics; +pub mod cmsg_client_playing_session_state; +pub mod cmsg_client_request_encrypted_app_ticket; +pub mod cmsg_client_store_user_stats; +pub mod cmsg_clientserver_login; +pub mod cplayer; +pub mod cpublishedfile; diff --git a/app/src/main/cpp/wn-steam-client/rust/src/proto_envelope.rs b/app/src/main/cpp/wn-steam-client/rust/src/proto_envelope.rs new file mode 100644 index 000000000..f428dc0a2 --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/proto_envelope.rs @@ -0,0 +1,87 @@ +use crate::cmsg_protobuf_header::CMsgProtoBufHeader; +use crate::emsg::{has_proto_flag, strip_proto_flag, with_proto_flag, EMsg}; +use crate::wire_format; + +pub const PROTO_ENVELOPE_PREFIX_BYTES: usize = 8; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct ProtoEnvelope { + pub emsg: EMsg, + pub header: CMsgProtoBufHeader, + pub body: Vec, +} + +impl Default for ProtoEnvelope { + fn default() -> Self { + Self { + emsg: EMsg::INVALID, + header: CMsgProtoBufHeader::default(), + body: Vec::new(), + } + } +} + +pub fn encode_proto_envelope(emsg: EMsg, header: &CMsgProtoBufHeader, body: &[u8]) -> Vec { + let mut hdr_bytes = Vec::with_capacity(64); + header.serialize(&mut hdr_bytes); + + let mut out = Vec::with_capacity(PROTO_ENVELOPE_PREFIX_BYTES + hdr_bytes.len() + body.len()); + let mut writer = wire_format::Writer::new(&mut out); + writer.u32(with_proto_flag(emsg)); + writer.u32(hdr_bytes.len() as u32); + writer.bytes(&hdr_bytes); + writer.bytes(body); + out +} + +pub fn decode_proto_envelope(wire: &[u8]) -> Option { + if wire.len() < PROTO_ENVELOPE_PREFIX_BYTES { + return None; + } + let mut reader = wire_format::Reader::new(wire); + let raw_emsg = reader.u32(); + let hdr_len = reader.u32() as usize; + if !reader.ok() || !has_proto_flag(raw_emsg) || reader.remaining() < hdr_len { + return None; + } + let header = CMsgProtoBufHeader::deserialize(reader.bytes(hdr_len))?; + if !reader.ok() { + return None; + } + let body_off = reader.position(); + Some(ProtoEnvelope { + emsg: strip_proto_flag(raw_emsg), + header, + body: wire[body_off..].to_vec(), + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn proto_envelope_roundtrips() { + let header = CMsgProtoBufHeader { + steamid: 76561197960287930, + jobid_source: 42, + target_job_name: "Player.GetOwnedGames#1".to_string(), + ..Default::default() + }; + let body = [1, 2, 3, 4, 5]; + let bytes = encode_proto_envelope(EMsg::SERVICE_METHOD_CALL_FROM_CLIENT, &header, &body); + let decoded = decode_proto_envelope(&bytes).unwrap(); + assert_eq!(decoded.emsg, EMsg::SERVICE_METHOD_CALL_FROM_CLIENT); + assert_eq!(decoded.header, header); + assert_eq!(decoded.body, body); + } + + #[test] + fn decode_rejects_non_proto_messages() { + let mut bytes = Vec::new(); + let mut writer = wire_format::Writer::new(&mut bytes); + writer.u32(EMsg::MULTI.0); + writer.u32(0); + assert_eq!(decode_proto_envelope(&bytes), None); + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/proto_wire.rs b/app/src/main/cpp/wn-steam-client/rust/src/proto_wire.rs new file mode 100644 index 000000000..bdb10f5a4 --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/proto_wire.rs @@ -0,0 +1,459 @@ +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[repr(u8)] +pub enum WireType { + Varint = 0, + Fixed64 = 1, + LengthDelimited = 2, + StartGroup = 3, + EndGroup = 4, + Fixed32 = 5, +} + +impl TryFrom for WireType { + type Error = (); + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(Self::Varint), + 1 => Ok(Self::Fixed64), + 2 => Ok(Self::LengthDelimited), + 3 => Ok(Self::StartGroup), + 4 => Ok(Self::EndGroup), + 5 => Ok(Self::Fixed32), + _ => Err(()), + } + } +} + +pub const MAX_VARINT_BYTES: usize = 10; + +pub const fn zigzag_encode_i64(v: i64) -> u64 { + ((v as u64) << 1) ^ ((v >> 63) as u64) +} + +pub const fn zigzag_decode_i64(v: u64) -> i64 { + ((v >> 1) as i64) ^ -((v & 1) as i64) +} + +pub const fn zigzag_encode_i32(v: i32) -> u32 { + ((v as u32) << 1) ^ ((v >> 31) as u32) +} + +pub const fn zigzag_decode_i32(v: u32) -> i32 { + ((v >> 1) as i32) ^ -((v & 1) as i32) +} + +pub const fn make_tag(field_number: i32, wire_type: WireType) -> u32 { + ((field_number as u32) << 3) | wire_type as u32 +} + +pub struct Writer<'a> { + out: &'a mut Vec, +} + +impl<'a> Writer<'a> { + pub fn new(out: &'a mut Vec) -> Self { + Self { out } + } + + pub fn varint(&mut self, mut v: u64) { + while v >= 0x80 { + self.out.push((v as u8) | 0x80); + v >>= 7; + } + self.out.push(v as u8); + } + + pub fn tag(&mut self, field_number: i32, wire_type: WireType) { + self.varint(make_tag(field_number, wire_type) as u64); + } + + pub fn uint32_field(&mut self, field_number: i32, v: u32) { + if v == 0 { + return; + } + self.tag(field_number, WireType::Varint); + self.varint(v as u64); + } + + pub fn uint64_field(&mut self, field_number: i32, v: u64) { + if v == 0 { + return; + } + self.tag(field_number, WireType::Varint); + self.varint(v); + } + + pub fn int32_field(&mut self, field_number: i32, v: i32) { + if v == 0 { + return; + } + self.tag(field_number, WireType::Varint); + self.varint(v as i64 as u64); + } + + pub fn int64_field(&mut self, field_number: i32, v: i64) { + if v == 0 { + return; + } + self.tag(field_number, WireType::Varint); + self.varint(v as u64); + } + + pub fn bool_field(&mut self, field_number: i32, v: bool) { + if !v { + return; + } + self.tag(field_number, WireType::Varint); + self.varint(1); + } + + pub fn fixed32_field(&mut self, field_number: i32, v: u32) { + if v == 0 { + return; + } + self.tag(field_number, WireType::Fixed32); + self.out.extend_from_slice(&v.to_le_bytes()); + } + + pub fn fixed64_field(&mut self, field_number: i32, v: u64) { + if v == 0 { + return; + } + self.tag(field_number, WireType::Fixed64); + self.out.extend_from_slice(&v.to_le_bytes()); + } + + pub fn string_field(&mut self, field_number: i32, s: &str) { + if s.is_empty() { + return; + } + self.tag(field_number, WireType::LengthDelimited); + self.varint(s.len() as u64); + self.out.extend_from_slice(s.as_bytes()); + } + + pub fn bytes_field(&mut self, field_number: i32, bytes: &[u8]) { + if bytes.is_empty() { + return; + } + self.tag(field_number, WireType::LengthDelimited); + self.varint(bytes.len() as u64); + self.out.extend_from_slice(bytes); + } + + pub fn submessage_field(&mut self, field_number: i32, body: &[u8]) { + self.tag(field_number, WireType::LengthDelimited); + self.varint(body.len() as u64); + self.out.extend_from_slice(body); + } + + pub fn raw_bytes(&mut self, bytes: &[u8]) { + self.out.extend_from_slice(bytes); + } + + pub fn uint32_field_force(&mut self, field_number: i32, v: u32) { + self.tag(field_number, WireType::Varint); + self.varint(v as u64); + } + + pub fn bool_field_force(&mut self, field_number: i32, v: bool) { + self.tag(field_number, WireType::Varint); + self.varint(u64::from(v)); + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct Tag { + pub field_number: i32, + pub wire_type: WireType, +} + +pub struct Reader<'a> { + buf: &'a [u8], + pos: usize, + ok: bool, +} + +impl<'a> Reader<'a> { + pub fn new(buf: &'a [u8]) -> Self { + Self { + buf, + pos: 0, + ok: true, + } + } + + pub fn ok(&self) -> bool { + self.ok + } + + pub fn eof(&self) -> bool { + self.pos >= self.buf.len() + } + + pub fn position(&self) -> usize { + self.pos + } + + pub fn next_tag(&mut self) -> Option { + if self.eof() { + return None; + } + let raw = self.varint()? as u32; + let wire_type = match WireType::try_from((raw & 0x07) as u8) { + Ok(wire_type) => wire_type, + Err(_) => { + self.ok = false; + return None; + } + }; + let field_number = (raw >> 3) as i32; + if matches!(wire_type, WireType::StartGroup | WireType::EndGroup) || field_number <= 0 { + self.ok = false; + return None; + } + Some(Tag { + field_number, + wire_type, + }) + } + + pub fn varint(&mut self) -> Option { + let mut result = 0u64; + for i in 0..MAX_VARINT_BYTES { + if self.pos >= self.buf.len() { + self.ok = false; + return None; + } + let b = self.buf[self.pos]; + self.pos += 1; + result |= ((b & 0x7f) as u64) << (i * 7); + if (b & 0x80) == 0 { + return Some(result); + } + } + self.ok = false; + None + } + + pub fn skip(&mut self, wire_type: WireType) -> bool { + match wire_type { + WireType::Varint => self.varint().is_some(), + WireType::Fixed64 => self.advance(8), + WireType::LengthDelimited => { + let Some(len) = self.varint() else { + return false; + }; + if len > usize::MAX as u64 { + self.ok = false; + return false; + } + self.advance(len as usize) + } + WireType::Fixed32 => self.advance(4), + WireType::StartGroup | WireType::EndGroup => { + self.ok = false; + false + } + } + } + + pub fn u32(&mut self) -> Option { + self.varint().map(|v| v as u32) + } + + pub fn u64(&mut self) -> Option { + self.varint() + } + + pub fn i32(&mut self) -> Option { + self.varint().map(|v| v as i32) + } + + pub fn i64(&mut self) -> Option { + self.varint().map(|v| v as i64) + } + + pub fn boolean(&mut self) -> Option { + self.varint().map(|v| v != 0) + } + + pub fn fixed32(&mut self) -> Option { + let Some(end) = self.pos.checked_add(4) else { + self.ok = false; + return None; + }; + if end > self.buf.len() { + self.ok = false; + return None; + } + let bytes: [u8; 4] = self.buf[self.pos..end].try_into().ok()?; + self.pos = end; + Some(u32::from_le_bytes(bytes)) + } + + pub fn fixed64(&mut self) -> Option { + let Some(end) = self.pos.checked_add(8) else { + self.ok = false; + return None; + }; + if end > self.buf.len() { + self.ok = false; + return None; + } + let bytes: [u8; 8] = self.buf[self.pos..end].try_into().ok()?; + self.pos = end; + Some(u64::from_le_bytes(bytes)) + } + + pub fn bytes(&mut self) -> Option<&'a [u8]> { + let len = self.varint()?; + if len > usize::MAX as u64 { + self.ok = false; + return None; + } + let len = len as usize; + let Some(end) = self.pos.checked_add(len) else { + self.ok = false; + return None; + }; + if end > self.buf.len() { + self.ok = false; + return None; + } + let out = &self.buf[self.pos..end]; + self.pos = end; + Some(out) + } + + pub fn string(&mut self) -> Option { + Some(String::from_utf8_lossy(self.bytes()?).into_owned()) + } + + fn advance(&mut self, amount: usize) -> bool { + let Some(end) = self.pos.checked_add(amount) else { + self.ok = false; + return false; + }; + if end > self.buf.len() { + self.ok = false; + return false; + } + self.pos = end; + true + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn varint_roundtrips_common_edges() { + let cases = [0, 1, 127, 128, 300, u32::MAX as u64, u64::MAX]; + for case in cases { + let mut buf = Vec::new(); + Writer::new(&mut buf).varint(case); + let mut reader = Reader::new(&buf); + assert_eq!(reader.varint(), Some(case)); + assert!(reader.ok()); + assert!(reader.eof()); + } + } + + #[test] + fn writes_and_reads_typed_fields() { + let mut buf = Vec::new(); + let mut writer = Writer::new(&mut buf); + writer.uint32_field(1, 42); + writer.fixed32_field(2, 0x1234_5678); + writer.string_field(3, "steam"); + writer.bool_field_force(4, false); + + let mut reader = Reader::new(&buf); + assert_eq!( + reader.next_tag(), + Some(Tag { + field_number: 1, + wire_type: WireType::Varint + }) + ); + assert_eq!(reader.u32(), Some(42)); + assert_eq!( + reader.next_tag(), + Some(Tag { + field_number: 2, + wire_type: WireType::Fixed32 + }) + ); + assert_eq!(reader.fixed32(), Some(0x1234_5678)); + assert_eq!( + reader.next_tag(), + Some(Tag { + field_number: 3, + wire_type: WireType::LengthDelimited + }) + ); + assert_eq!(reader.string(), Some("steam".to_string())); + assert_eq!( + reader.next_tag(), + Some(Tag { + field_number: 4, + wire_type: WireType::Varint + }) + ); + assert_eq!(reader.boolean(), Some(false)); + assert!(reader.eof()); + } + + #[test] + fn rejects_deprecated_groups_and_truncated_data() { + let mut group = Vec::new(); + Writer::new(&mut group).tag(1, WireType::StartGroup); + let mut reader = Reader::new(&group); + assert_eq!(reader.next_tag(), None); + assert!(!reader.ok()); + + let mut truncated = Reader::new(&[0x0a, 0x05, b'a']); + assert_eq!( + truncated.next_tag(), + Some(Tag { + field_number: 1, + wire_type: WireType::LengthDelimited + }) + ); + assert_eq!(truncated.bytes(), None); + assert!(!truncated.ok()); + } + + #[test] + fn rejects_invalid_wire_type_and_oversized_length() { + let mut invalid = Reader::new(&[0x0e]); + assert_eq!(invalid.next_tag(), None); + assert!(!invalid.ok()); + + let mut oversized = Vec::new(); + Writer::new(&mut oversized).varint(make_tag(1, WireType::LengthDelimited) as u64); + Writer::new(&mut oversized).varint(u64::MAX); + let mut reader = Reader::new(&oversized); + assert_eq!( + reader.next_tag(), + Some(Tag { + field_number: 1, + wire_type: WireType::LengthDelimited + }) + ); + assert_eq!(reader.bytes(), None); + assert!(!reader.ok()); + } + + #[test] + fn zigzag_matches_known_values() { + assert_eq!(zigzag_encode_i32(0), 0); + assert_eq!(zigzag_encode_i32(-1), 1); + assert_eq!(zigzag_encode_i32(1), 2); + assert_eq!(zigzag_decode_i32(1), -1); + assert_eq!(zigzag_decode_i64(3), -2); + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/rsa_password.rs b/app/src/main/cpp/wn-steam-client/rust/src/rsa_password.rs new file mode 100644 index 000000000..6987ac6e2 --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/rsa_password.rs @@ -0,0 +1,70 @@ +use rand::rngs::OsRng; +use rsa::{BigUint, Pkcs1v15Encrypt, RsaPublicKey}; + +pub fn rsa_pkcs1v15_encrypt_password_with_hex_key( + password: &str, + publickey_mod_hex: &str, + publickey_exp_hex: &str, +) -> Option> { + let modulus = BigUint::from_bytes_be(&hex_decode(publickey_mod_hex)?); + let exponent = BigUint::from_bytes_be(&hex_decode(publickey_exp_hex)?); + let key = RsaPublicKey::new(modulus, exponent).ok()?; + key.encrypt(&mut OsRng, Pkcs1v15Encrypt, password.as_bytes()) + .ok() +} + +fn hex_decode(hex: &str) -> Option> { + if !hex.len().is_multiple_of(2) { + return None; + } + let mut out = Vec::with_capacity(hex.len() / 2); + let bytes = hex.as_bytes(); + for pair in bytes.chunks_exact(2) { + let hi = nibble(pair[0])?; + let lo = nibble(pair[1])?; + out.push((hi << 4) | lo); + } + Some(out) +} + +fn nibble(c: u8) -> Option { + match c { + b'0'..=b'9' => Some(c - b'0'), + b'a'..=b'f' => Some(10 + c - b'a'), + b'A'..=b'F' => Some(10 + c - b'A'), + _ => None, + } +} + +#[cfg(test)] +mod tests { + use super::*; + use rsa::traits::PublicKeyParts; + + #[test] + fn rejects_bad_hex_keys() { + assert!(rsa_pkcs1v15_encrypt_password_with_hex_key("pw", "abc", "010001").is_none()); + assert!(rsa_pkcs1v15_encrypt_password_with_hex_key("pw", "zz", "010001").is_none()); + } + + #[test] + fn encrypts_with_generated_key_material() { + let private = rsa::RsaPrivateKey::new(&mut OsRng, 1024).unwrap(); + let public = private.to_public_key(); + let mod_hex = hex_encode(&public.n().to_bytes_be()); + let exp_hex = hex_encode(&public.e().to_bytes_be()); + let encrypted = + rsa_pkcs1v15_encrypt_password_with_hex_key("password", &mod_hex, &exp_hex).unwrap(); + assert_eq!(encrypted.len(), 128); + } + + fn hex_encode(bytes: &[u8]) -> String { + const HEX: &[u8; 16] = b"0123456789abcdef"; + let mut out = String::with_capacity(bytes.len() * 2); + for b in bytes { + out.push(HEX[(b >> 4) as usize] as char); + out.push(HEX[(b & 0x0f) as usize] as char); + } + out + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/steam_directory.rs b/app/src/main/cpp/wn-steam-client/rust/src/steam_directory.rs new file mode 100644 index 000000000..c346fef14 --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/steam_directory.rs @@ -0,0 +1,263 @@ +use crate::cm_server::{parse_endpoint, CmServer, CmTransport}; +use serde_json::Value; +use std::fs; +use std::time::Duration; + +pub const DEFAULT_USER_AGENT: &str = "Valve/Steam HTTP Client 1.0"; +pub const DIRECTORY_ENDPOINT: &str = + "https://api.steampowered.com/ISteamDirectory/GetCMListForConnect/v1/"; +pub const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10); + +#[derive(Clone, Debug, Default, PartialEq)] +pub struct SteamDirectoryResult { + pub servers: Vec, + pub error: String, + pub http_status: i32, +} + +impl SteamDirectoryResult { + pub fn ok(&self) -> bool { + self.error.is_empty() && !self.servers.is_empty() + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct SteamDirectoryClient { + ca_bundle_path: String, +} + +impl SteamDirectoryClient { + pub fn new(ca_bundle_path: impl Into) -> Self { + Self { + ca_bundle_path: ca_bundle_path.into(), + } + } + + pub fn ca_bundle_path(&self) -> &str { + &self.ca_bundle_path + } + + pub fn build_url(cell_id: u32) -> String { + format!( + "{DIRECTORY_ENDPOINT}?cellid={cell_id}&cmtype=websockets&maxcount=20&realm=steamglobal" + ) + } + + pub fn fetch(&self, cell_id: u32, timeout: Duration) -> SteamDirectoryResult { + self.fetch_with_user_agent(cell_id, timeout, DEFAULT_USER_AGENT) + } + + pub fn fetch_with_user_agent( + &self, + cell_id: u32, + timeout: Duration, + user_agent: &str, + ) -> SteamDirectoryResult { + let url = SteamDirectoryClient::build_url(cell_id); + match self.http_get_text(&url, timeout, user_agent) { + Ok(response) => { + SteamDirectoryClient::validate_response(response.http_status, &response.body) + } + Err(error) => SteamDirectoryResult { + error, + ..Default::default() + }, + } + } + + pub fn validate_response(http_status: i32, body: &str) -> SteamDirectoryResult { + if http_status != 200 { + return SteamDirectoryResult { + error: "non-200 HTTP status".to_string(), + http_status, + ..Default::default() + }; + } + let mut result = parse_directory_response(body); + result.http_status = http_status; + result + } + + pub fn default_timeout() -> Duration { + DEFAULT_TIMEOUT + } + + fn http_get_text( + &self, + url: &str, + timeout: Duration, + user_agent: &str, + ) -> Result { + let client = self.http_client(timeout, user_agent)?; + let response = client + .get(url) + .send() + .map_err(|err| format!("http get: {err}"))?; + let http_status = response.status().as_u16() as i32; + let body = response.text().map_err(|err| format!("http body: {err}"))?; + Ok(HttpTextResponse { http_status, body }) + } + + fn http_client( + &self, + timeout: Duration, + user_agent: &str, + ) -> Result { + let mut builder = reqwest::blocking::Client::builder() + .user_agent(user_agent) + .timeout(timeout) + .connect_timeout(timeout); + if !self.ca_bundle_path.is_empty() { + let pem = + fs::read(&self.ca_bundle_path).map_err(|err| format!("read CA bundle: {err}"))?; + let certs = reqwest::Certificate::from_pem_bundle(&pem) + .map_err(|err| format!("parse CA bundle: {err}"))?; + for cert in certs { + builder = builder.add_root_certificate(cert); + } + } + builder.build().map_err(|err| format!("http client: {err}")) + } +} + +struct HttpTextResponse { + http_status: i32, + body: String, +} + +pub fn parse_directory_response(body: &str) -> SteamDirectoryResult { + let mut result = SteamDirectoryResult::default(); + let root: Value = match serde_json::from_str(body) { + Ok(v) => v, + Err(e) => { + result.error = format!("json parse error: {e}"); + return result; + } + }; + let Some(response) = root.get("response") else { + result.error = "directory response: missing response".to_string(); + return result; + }; + let ok = response + .get("success") + .map(|s| { + s.as_bool() + .unwrap_or_else(|| s.as_i64().is_some_and(|n| n == 1)) + }) + .unwrap_or(false); + if !ok { + result.error = "directory response: success=false".to_string(); + return result; + } + let Some(list) = response.get("serverlist").and_then(|v| v.as_array()) else { + result.error = "directory response: missing serverlist".to_string(); + return result; + }; + + for entry in list { + let Some(endpoint) = entry.get("endpoint").and_then(|v| v.as_str()) else { + continue; + }; + let Some((host, port)) = parse_endpoint(endpoint) else { + continue; + }; + let transport = entry + .get("type") + .and_then(|v| v.as_str()) + .map(parse_transport) + .unwrap_or(CmTransport::Unknown); + if transport != CmTransport::WebSocket { + continue; + } + result.servers.push(CmServer { + endpoint: endpoint.to_string(), + host, + port, + transport, + realm: entry + .get("realm") + .and_then(|v| v.as_str()) + .unwrap_or("") + .to_string(), + datacenter: entry + .get("dc") + .and_then(|v| v.as_str()) + .unwrap_or("") + .to_string(), + load: entry.get("load").and_then(|v| v.as_i64()).unwrap_or(0) as i32, + weighted_load: entry + .get("wtd_load") + .and_then(|v| v.as_f64()) + .unwrap_or(0.0) as f32, + }); + } + result +} + +pub fn parse_transport(s: &str) -> CmTransport { + match s { + "websockets" | "websocket" => CmTransport::WebSocket, + "netfilter" => CmTransport::Tcp, + _ => CmTransport::Unknown, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parses_websocket_entries_and_filters_tcp() { + let json = r#"{ + "response": { + "success": 1, + "serverlist": [ + {"endpoint":"ext1-sea1.steamserver.net:443","type":"websockets","realm":"steamglobal","dc":"sea1","load":1,"wtd_load":2.5}, + {"endpoint":"tcp.example.com:27017","type":"netfilter"}, + {"endpoint":"bad","type":"websockets"} + ] + } + }"#; + let result = parse_directory_response(json); + assert!(result.error.is_empty()); + assert_eq!(result.servers.len(), 1); + assert_eq!(result.servers[0].host, "ext1-sea1.steamserver.net"); + assert_eq!(result.servers[0].weighted_load, 2.5); + } + + #[test] + fn accepts_boolean_success() { + let json = r#"{"response":{"success":true,"serverlist":[]}}"#; + let result = parse_directory_response(json); + assert!(result.error.is_empty()); + assert!(result.servers.is_empty()); + } + + #[test] + fn builds_directory_url_and_tracks_ca_bundle() { + let client = SteamDirectoryClient::new("/cacert.pem"); + assert_eq!(client.ca_bundle_path(), "/cacert.pem"); + assert_eq!( + SteamDirectoryClient::default_timeout(), + Duration::from_secs(10) + ); + assert_eq!( + SteamDirectoryClient::build_url(123), + "https://api.steampowered.com/ISteamDirectory/GetCMListForConnect/v1/?cellid=123&cmtype=websockets&maxcount=20&realm=steamglobal" + ); + } + + #[test] + fn validates_http_status_before_json() { + let result = SteamDirectoryClient::validate_response(503, "{}"); + assert_eq!(result.http_status, 503); + assert_eq!(result.error, "non-200 HTTP status"); + + let ok = SteamDirectoryClient::validate_response( + 200, + r#"{"response":{"success":1,"serverlist":[{"endpoint":"ext1-sea1.steamserver.net:443","type":"websockets"}]}}"#, + ); + assert_eq!(ok.http_status, 200); + assert!(ok.ok()); + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/ticket_cache.rs b/app/src/main/cpp/wn-steam-client/rust/src/ticket_cache.rs new file mode 100644 index 000000000..4fff5640d --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/ticket_cache.rs @@ -0,0 +1,70 @@ +use std::collections::HashMap; +use std::sync::Mutex; +use std::time::Instant; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct OwnedAppTicket { + pub app_id: u32, + pub ticket: Vec, + pub eresult: u32, + pub fetched_at: Instant, +} + +#[derive(Debug, Default)] +pub struct WnTicketCache { + cache: Mutex>, +} + +impl WnTicketCache { + pub fn store(&self, app_id: u32, eresult: u32, ticket: Vec) { + let mut cache = self.cache.lock().expect("ticket cache poisoned"); + cache.insert( + app_id, + OwnedAppTicket { + app_id, + ticket, + eresult, + fetched_at: Instant::now(), + }, + ); + } + + pub fn get(&self, app_id: u32) -> Option { + self.cache + .lock() + .expect("ticket cache poisoned") + .get(&app_id) + .cloned() + } + + pub fn has(&self, app_id: u32) -> bool { + self.cache + .lock() + .expect("ticket cache poisoned") + .contains_key(&app_id) + } + + pub fn size(&self) -> usize { + self.cache.lock().expect("ticket cache poisoned").len() + } + + pub fn clear(&self) { + self.cache.lock().expect("ticket cache poisoned").clear(); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn stores_gets_and_clears_tickets() { + let cache = WnTicketCache::default(); + cache.store(480, 1, vec![1, 2, 3]); + assert!(cache.has(480)); + assert_eq!(cache.size(), 1); + assert_eq!(cache.get(480).unwrap().ticket, [1, 2, 3]); + cache.clear(); + assert!(!cache.has(480)); + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/transport.rs b/app/src/main/cpp/wn-steam-client/rust/src/transport.rs new file mode 100644 index 000000000..12456d4bc --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/transport.rs @@ -0,0 +1,36 @@ +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[repr(u8)] +pub enum TransportState { + Disconnected, + Connecting, + Connected, + Disconnecting, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[repr(u8)] +pub enum TransportDisconnectReason { + UserInitiated, + RemoteClose, + TlsHandshakeFailed, + NetworkError, + HandshakeTimeout, + Unknown, +} + +pub type MessageCallback = Box; +pub type ConnectedCallback = Box; +pub type DisconnectedCallback = Box; + +pub trait Transport: Send { + fn connect(&mut self, url: &str) -> bool; + fn send(&mut self, data: &[u8]) -> bool; + fn disconnect(&mut self); + fn state(&self) -> TransportState; + + fn set_on_message(&mut self, cb: MessageCallback); + fn set_on_connected(&mut self, cb: ConnectedCallback); + fn set_on_disconnected(&mut self, cb: DisconnectedCallback); + + fn set_ca_bundle_path(&mut self, _path: &str) {} +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/vdf.rs b/app/src/main/cpp/wn-steam-client/rust/src/vdf.rs new file mode 100644 index 000000000..db91fd073 --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/vdf.rs @@ -0,0 +1,514 @@ +#[derive(Clone, Debug, PartialEq)] +pub enum KVValue { + Object, + String(String), + Int32(i32), + Float32(f32), + UInt32(u32), + WideString(Vec), + UInt64(u64), + Int64(i64), +} + +#[derive(Clone, Debug, PartialEq)] +pub struct KVNode { + pub name: String, + pub value: KVValue, + pub children: Vec, +} + +impl KVNode { + pub fn new(name: impl Into) -> Self { + Self { + name: name.into(), + value: KVValue::Object, + children: Vec::new(), + } + } + + pub fn is_object(&self) -> bool { + matches!(self.value, KVValue::Object) + } + + pub fn child(&self, key: &str) -> Option<&KVNode> { + self.children + .iter() + .find(|c| c.name.eq_ignore_ascii_case(key)) + } + + pub fn as_string(&self, fallback: &str) -> String { + match &self.value { + KVValue::String(v) => v.clone(), + KVValue::Int32(v) => v.to_string(), + KVValue::Float32(v) => v.to_string(), + KVValue::UInt32(v) => v.to_string(), + KVValue::UInt64(v) => v.to_string(), + KVValue::Int64(v) => v.to_string(), + _ => fallback.to_string(), + } + } + + pub fn as_int(&self, fallback: i64) -> i64 { + match &self.value { + KVValue::Int32(v) => *v as i64, + KVValue::Int64(v) => *v, + KVValue::UInt32(v) => *v as i64, + KVValue::UInt64(v) => *v as i64, + KVValue::String(v) => v.parse().unwrap_or(fallback), + _ => fallback, + } + } + + pub fn as_uint(&self, fallback: u64) -> u64 { + match &self.value { + KVValue::UInt32(v) => *v as u64, + KVValue::UInt64(v) => *v, + KVValue::Int32(v) => *v as u64, + KVValue::Int64(v) => *v as u64, + KVValue::String(v) => v.parse().unwrap_or(fallback), + _ => fallback, + } + } + + pub fn as_bool(&self, fallback: bool) -> bool { + match &self.value { + KVValue::Int32(v) => *v != 0, + KVValue::Int64(v) => *v != 0, + KVValue::UInt32(v) => *v != 0, + KVValue::UInt64(v) => *v != 0, + KVValue::String(v) + if v == "1" || v.eq_ignore_ascii_case("true") || v.eq_ignore_ascii_case("yes") => + { + true + } + KVValue::String(v) + if v == "0" || v.eq_ignore_ascii_case("false") || v.eq_ignore_ascii_case("no") => + { + false + } + _ => fallback, + } + } +} + +const TYPE_NONE: u8 = 0x00; +const TYPE_STRING: u8 = 0x01; +const TYPE_INT32: u8 = 0x02; +const TYPE_FLOAT32: u8 = 0x03; +const TYPE_POINTER: u8 = 0x04; +const TYPE_WIDE_STRING: u8 = 0x05; +const TYPE_COLOR: u8 = 0x06; +const TYPE_UINT64: u8 = 0x07; +const TYPE_END: u8 = 0x08; +const TYPE_INT64: u8 = 0x09; +const TYPE_END_ALT: u8 = 0x0b; + +struct Cursor<'a> { + buf: &'a [u8], + pos: usize, + ok: bool, +} + +impl<'a> Cursor<'a> { + fn new(buf: &'a [u8]) -> Self { + Self { + buf, + pos: 0, + ok: true, + } + } + + fn read_u8(&mut self) -> u8 { + if self.pos + 1 > self.buf.len() { + self.ok = false; + return 0; + } + let v = self.buf[self.pos]; + self.pos += 1; + v + } + + fn read_u32_le(&mut self) -> u32 { + if self.pos + 4 > self.buf.len() { + self.ok = false; + return 0; + } + let v = u32::from_le_bytes(self.buf[self.pos..self.pos + 4].try_into().unwrap()); + self.pos += 4; + v + } + + fn read_u64_le(&mut self) -> u64 { + let lo = self.read_u32_le() as u64; + let hi = self.read_u32_le() as u64; + lo | (hi << 32) + } + + fn read_f32_le(&mut self) -> f32 { + f32::from_bits(self.read_u32_le()) + } + + fn read_cstring(&mut self) -> String { + let mut out = Vec::new(); + while self.pos < self.buf.len() { + let b = self.buf[self.pos]; + self.pos += 1; + if b == 0 { + return String::from_utf8_lossy(&out).into_owned(); + } + out.push(b); + } + self.ok = false; + String::from_utf8_lossy(&out).into_owned() + } + + fn read_wide_cstring(&mut self) -> Vec { + let mut out = Vec::new(); + while self.pos + 1 < self.buf.len() { + let u = u16::from_le_bytes([self.buf[self.pos], self.buf[self.pos + 1]]); + self.pos += 2; + if u == 0 { + return out; + } + out.push(u); + } + self.ok = false; + out + } +} + +fn parse_value(cursor: &mut Cursor<'_>, ty: u8, node: &mut KVNode) -> bool { + match ty { + TYPE_NONE => { + while cursor.ok { + let inner_type = cursor.read_u8(); + if !cursor.ok { + return false; + } + if inner_type == TYPE_END || inner_type == TYPE_END_ALT { + return true; + } + let name = cursor.read_cstring(); + if !cursor.ok { + return false; + } + let mut child = KVNode::new(name); + if !parse_value(cursor, inner_type, &mut child) { + return false; + } + node.children.push(child); + } + false + } + TYPE_STRING => { + node.value = KVValue::String(cursor.read_cstring()); + cursor.ok + } + TYPE_INT32 => { + node.value = KVValue::Int32(cursor.read_u32_le() as i32); + cursor.ok + } + TYPE_FLOAT32 => { + node.value = KVValue::Float32(cursor.read_f32_le()); + cursor.ok + } + TYPE_POINTER | TYPE_COLOR => { + node.value = KVValue::UInt32(cursor.read_u32_le()); + cursor.ok + } + TYPE_WIDE_STRING => { + node.value = KVValue::WideString(cursor.read_wide_cstring()); + cursor.ok + } + TYPE_UINT64 => { + node.value = KVValue::UInt64(cursor.read_u64_le()); + cursor.ok + } + TYPE_INT64 => { + node.value = KVValue::Int64(cursor.read_u64_le() as i64); + cursor.ok + } + _ => false, + } +} + +pub fn parse_binary(body: &[u8]) -> Option { + if body.is_empty() { + return None; + } + let mut cursor = Cursor::new(body); + let ty = cursor.read_u8(); + if !cursor.ok { + return None; + } + let mut root = KVNode::new(cursor.read_cstring()); + if !cursor.ok || !parse_value(&mut cursor, ty, &mut root) { + return None; + } + Some(root) +} + +pub fn parse_binary_package(body: &[u8]) -> Option<(u32, KVNode)> { + if body.len() < 4 { + return None; + } + let package_id = u32::from_le_bytes(body[..4].try_into().unwrap()); + Some((package_id, parse_binary(&body[4..])?)) +} + +pub fn parse_auto(body: &[u8]) -> Option { + let first = body + .iter() + .copied() + .find(|b| !matches!(b, b' ' | b'\t' | b'\n' | b'\r'))?; + if first == b'"' { + parse_text(body) + } else { + parse_binary(body) + } +} + +struct TextCursor<'a> { + buf: &'a [u8], + pos: usize, + ok: bool, +} + +impl<'a> TextCursor<'a> { + fn new(buf: &'a [u8]) -> Self { + Self { + buf, + pos: 0, + ok: true, + } + } + + fn eof(&self) -> bool { + self.pos >= self.buf.len() + } + + fn peek(&self) -> u8 { + self.buf.get(self.pos).copied().unwrap_or(0) + } + + fn next(&mut self) -> u8 { + let b = self.peek(); + if !self.eof() { + self.pos += 1; + } + b + } + + fn skip_ws_and_comments(&mut self) { + while self.pos < self.buf.len() { + match self.buf[self.pos] { + b' ' | b'\t' | b'\n' | b'\r' => self.pos += 1, + b'/' if self.buf.get(self.pos + 1) == Some(&b'/') => { + self.pos += 2; + while self.pos < self.buf.len() && self.buf[self.pos] != b'\n' { + self.pos += 1; + } + } + b'/' if self.buf.get(self.pos + 1) == Some(&b'*') => { + self.pos += 2; + while self.pos + 1 < self.buf.len() + && !(self.buf[self.pos] == b'*' && self.buf[self.pos + 1] == b'/') + { + self.pos += 1; + } + self.pos = (self.pos + 2).min(self.buf.len()); + } + _ => break, + } + } + } + + fn read_token(&mut self) -> Option { + self.skip_ws_and_comments(); + if self.eof() { + return None; + } + if self.peek() == b'"' { + self.pos += 1; + let mut out = Vec::new(); + while !self.eof() { + let c = self.next(); + if c == b'"' { + return Some(String::from_utf8_lossy(&out).into_owned()); + } + if c == b'\\' && !self.eof() { + out.push(match self.next() { + b'n' => b'\n', + b't' => b'\t', + b'r' => b'\r', + b'\\' => b'\\', + b'"' => b'"', + other => other, + }); + } else { + out.push(c); + } + } + self.ok = false; + return None; + } + + let start = self.pos; + while !self.eof() { + let c = self.peek(); + if matches!(c, b' ' | b'\t' | b'\n' | b'\r' | b'{' | b'}' | b'"') { + break; + } + self.pos += 1; + } + (self.pos > start).then(|| String::from_utf8_lossy(&self.buf[start..self.pos]).into_owned()) + } +} + +fn parse_text_object(cursor: &mut TextCursor<'_>, parent: &mut KVNode) -> bool { + loop { + cursor.skip_ws_and_comments(); + if cursor.eof() { + return true; + } + if cursor.peek() == b'}' { + cursor.pos += 1; + return true; + } + let Some(key) = cursor.read_token() else { + return cursor.ok; + }; + cursor.skip_ws_and_comments(); + if cursor.eof() { + return false; + } + let mut child = KVNode::new(key); + if cursor.peek() == b'{' { + cursor.pos += 1; + if !parse_text_object(cursor, &mut child) { + return false; + } + } else { + let Some(value) = cursor.read_token() else { + return false; + }; + child.value = KVValue::String(value); + } + parent.children.push(child); + } +} + +pub fn parse_text(body: &[u8]) -> Option { + let mut cursor = TextCursor::new(body); + cursor.skip_ws_and_comments(); + let key = cursor.read_token()?; + cursor.skip_ws_and_comments(); + if cursor.peek() != b'{' { + return None; + } + cursor.pos += 1; + let mut root = KVNode::new(key); + parse_text_object(&mut cursor, &mut root).then_some(root) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parses_text_vdf_with_comments_and_case_insensitive_lookup() { + let text = br#" + // comment + "appinfo" { + "appid" "480" + "Common" { "name" "Spacewar" "type" "Game" } + /* block */ "enabled" "true" + } + "#; + let root = parse_text(text).unwrap(); + assert_eq!(root.name, "appinfo"); + assert_eq!(root.child("APPID").unwrap().as_int(0), 480); + assert_eq!( + root.child("common") + .unwrap() + .child("NAME") + .unwrap() + .as_string(""), + "Spacewar" + ); + assert!(root.child("enabled").unwrap().as_bool(false)); + } + + #[test] + fn parses_binary_tree() { + let mut bytes = Vec::new(); + bytes.extend_from_slice(&[TYPE_NONE]); + bytes.extend_from_slice(b"root\0"); + bytes.extend_from_slice(&[TYPE_STRING]); + bytes.extend_from_slice(b"name\0Spacewar\0"); + bytes.extend_from_slice(&[TYPE_INT32]); + bytes.extend_from_slice(b"appid\0"); + bytes.extend_from_slice(&480i32.to_le_bytes()); + bytes.extend_from_slice(&[TYPE_END]); + + let root = parse_binary(&bytes).unwrap(); + assert!(root.is_object()); + assert_eq!(root.child("name").unwrap().as_string(""), "Spacewar"); + assert_eq!(root.child("appid").unwrap().as_int(0), 480); + } + + #[test] + fn parses_package_prefix() { + let mut bytes = 123u32.to_le_bytes().to_vec(); + bytes.extend_from_slice(&[TYPE_NONE]); + bytes.extend_from_slice(b"package\0"); + bytes.extend_from_slice(&[TYPE_END]); + let (package_id, root) = parse_binary_package(&bytes).unwrap(); + assert_eq!(package_id, 123); + assert_eq!(root.name, "package"); + } + + // Manifest gid/size/download: proves the parser reads download (the 3rd value) + // correctly, ruling out a parser cause for the stale corrupt download sizes. + #[test] + fn parses_manifest_uint64_gid_size_download() { + let gid: u64 = 8072044898226043193; + let size: u64 = 30738676601; + let download: u64 = 15000000000; + let mut b = vec![TYPE_NONE]; + b.extend_from_slice(b"public\0"); + b.push(TYPE_UINT64); + b.extend_from_slice(b"gid\0"); + b.extend_from_slice(&gid.to_le_bytes()); + b.push(TYPE_UINT64); + b.extend_from_slice(b"size\0"); + b.extend_from_slice(&size.to_le_bytes()); + b.push(TYPE_UINT64); + b.extend_from_slice(b"download\0"); + b.extend_from_slice(&download.to_le_bytes()); + b.push(TYPE_END); + + let root = parse_binary(&b).unwrap(); + assert_eq!(root.child("gid").unwrap().as_string(""), gid.to_string()); + assert_eq!(root.child("size").unwrap().as_string(""), size.to_string()); + assert_eq!( + root.child("download").unwrap().as_string(""), + download.to_string() + ); + } + + #[test] + fn parses_manifest_text_vdf() { + let text = br#" + "public" + { + "gid" "8072044898226043193" + "size" "30738676601" + "download" "15000000000" + } + "#; + let root = parse_text(text).unwrap(); + assert_eq!(root.child("size").unwrap().as_string(""), "30738676601"); + assert_eq!(root.child("download").unwrap().as_string(""), "15000000000"); + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/version.rs b/app/src/main/cpp/wn-steam-client/rust/src/version.rs new file mode 100644 index 000000000..e33a3b1af --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/version.rs @@ -0,0 +1,78 @@ +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct Version { + pub major: i32, + pub minor: i32, + pub patch: i32, + pub string: &'static str, +} + +pub const VERSION: Version = Version { + major: parse_i32_or(option_env!("WN_STEAM_CLIENT_VERSION_MAJOR"), 0), + minor: parse_i32_or(option_env!("WN_STEAM_CLIENT_VERSION_MINOR"), 1), + patch: parse_i32_or(option_env!("WN_STEAM_CLIENT_VERSION_PATCH"), 0), + string: str_or(option_env!("WN_STEAM_CLIENT_VERSION_STRING"), "0.1.0"), +}; + +pub const fn version() -> Version { + VERSION +} + +const fn parse_i32(s: &str) -> Option { + let bytes = s.as_bytes(); + if bytes.is_empty() { + return None; + } + let mut i = 0; + let mut out: i32 = 0; + while i < bytes.len() { + let b = bytes[i]; + if b < b'0' || b > b'9' { + return None; + } + out = match out.checked_mul(10) { + Some(v) => v, + None => return None, + }; + out = match out.checked_add((b - b'0') as i32) { + Some(v) => v, + None => return None, + }; + i += 1; + } + Some(out) +} + +const fn parse_i32_or(s: Option<&str>, default: i32) -> i32 { + match s { + Some(s) => match parse_i32(s) { + Some(v) => v, + None => default, + }, + None => default, + } +} + +const fn str_or(s: Option<&'static str>, default: &'static str) -> &'static str { + match s { + Some(s) => s, + None => default, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn default_version_matches_cmake_defaults() { + assert_eq!( + version(), + Version { + major: 0, + minor: 1, + patch: 0, + string: "0.1.0" + } + ); + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/wine_bridge.rs b/app/src/main/cpp/wn-steam-client/rust/src/wine_bridge.rs new file mode 100644 index 000000000..4dc80e539 --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/wine_bridge.rs @@ -0,0 +1,202 @@ +use std::io::Read; +use std::net::{Shutdown, TcpListener, TcpStream}; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, Mutex}; +use std::thread::{self, JoinHandle}; +use std::time::Duration; + +type ClientObserver = Arc) + Send + Sync + 'static>; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct WineBridgeConfig { + pub bind_host: String, + pub steam3_port: u16, + pub client_svc_port: u16, + pub snoop_bytes: usize, +} + +impl Default for WineBridgeConfig { + fn default() -> Self { + Self { + bind_host: "127.0.0.1".to_string(), + steam3_port: 57343, + client_svc_port: 57344, + snoop_bytes: 64, + } + } +} + +#[derive(Default)] +pub struct WineBridge { + running: Arc, + last_error: Arc>, + observer: Arc>>, + threads: Vec>, +} + +impl WineBridge { + pub fn start(&mut self, config: WineBridgeConfig) -> bool { + if self.running.load(Ordering::Relaxed) { + return true; + } + self.stop(); + let steam3 = match TcpListener::bind((config.bind_host.as_str(), config.steam3_port)) { + Ok(listener) => listener, + Err(err) => { + self.set_error(format!( + "bind({}:{}): {err}", + config.bind_host, config.steam3_port + )); + return false; + } + }; + let client = match TcpListener::bind((config.bind_host.as_str(), config.client_svc_port)) { + Ok(listener) => listener, + Err(err) => { + self.set_error(format!( + "bind({}:{}): {err}", + config.bind_host, config.client_svc_port + )); + return false; + } + }; + self.running.store(true, Ordering::Relaxed); + self.threads.push(spawn_listener( + steam3, + config.steam3_port, + config.snoop_bytes, + Arc::clone(&self.running), + Arc::clone(&self.observer), + )); + self.threads.push(spawn_listener( + client, + config.client_svc_port, + config.snoop_bytes, + Arc::clone(&self.running), + Arc::clone(&self.observer), + )); + true + } + + pub fn stop(&mut self) { + self.running.store(false, Ordering::Relaxed); + for handle in self.threads.drain(..) { + let _ = handle.join(); + } + } + + pub fn running(&self) -> bool { + self.running.load(Ordering::Relaxed) + } + + pub fn last_error(&self) -> String { + self.last_error + .lock() + .expect("wine bridge poisoned") + .clone() + } + + pub fn set_observer(&self, observer: F) + where + F: Fn(u16, String, Vec) + Send + Sync + 'static, + { + *self.observer.lock().expect("wine bridge poisoned") = Some(Arc::new(observer)); + } + + fn set_error(&self, error: String) { + *self.last_error.lock().expect("wine bridge poisoned") = error; + } +} + +impl Drop for WineBridge { + fn drop(&mut self) { + self.stop(); + } +} + +fn spawn_listener( + listener: TcpListener, + port: u16, + snoop_bytes: usize, + running: Arc, + observer: Arc>>, +) -> JoinHandle<()> { + thread::spawn(move || { + let _ = listener.set_nonblocking(true); + while running.load(Ordering::Relaxed) { + match listener.accept() { + Ok((stream, _)) => handle_connection(stream, port, snoop_bytes, &observer), + Err(err) if err.kind() == std::io::ErrorKind::WouldBlock => { + thread::sleep(Duration::from_millis(25)); + } + Err(_) => break, + } + } + }) +} + +fn handle_connection( + mut stream: TcpStream, + port: u16, + snoop_bytes: usize, + observer: &Arc>>, +) { + let peer = stream + .peer_addr() + .map(|addr| addr.to_string()) + .unwrap_or_default(); + let mut first = vec![0u8; snoop_bytes]; + let n = stream.read(&mut first).unwrap_or(0); + first.truncate(n); + let cb = observer.lock().expect("wine bridge poisoned").clone(); + if let Some(cb) = cb { + cb(port, peer, first); + } + let _ = stream.shutdown(Shutdown::Both); +} + +#[cfg(test)] +mod tests { + use super::*; + use std::io::Write; + use std::net::TcpListener; + use std::sync::mpsc; + + #[test] + fn default_ports_match_cpp_bridge() { + let cfg = WineBridgeConfig::default(); + assert_eq!(cfg.steam3_port, 57343); + assert_eq!(cfg.client_svc_port, 57344); + } + + #[test] + fn observes_first_bytes_on_connection() { + let port1 = free_port(); + let port2 = free_port(); + let mut bridge = WineBridge::default(); + let (tx, rx) = mpsc::channel(); + bridge.set_observer(move |port, peer, first| { + tx.send((port, peer, first)).unwrap(); + }); + assert!(bridge.start(WineBridgeConfig { + steam3_port: port1, + client_svc_port: port2, + ..Default::default() + })); + let mut stream = TcpStream::connect(("127.0.0.1", port1)).unwrap(); + stream.write_all(b"abcdef").unwrap(); + let (port, peer, first) = rx.recv_timeout(Duration::from_secs(2)).unwrap(); + assert_eq!(port, port1); + assert!(peer.starts_with("127.0.0.1:")); + assert_eq!(first, b"abcdef"); + bridge.stop(); + } + + fn free_port() -> u16 { + TcpListener::bind(("127.0.0.1", 0)) + .unwrap() + .local_addr() + .unwrap() + .port() + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/wire_format.rs b/app/src/main/cpp/wn-steam-client/rust/src/wire_format.rs new file mode 100644 index 000000000..6f7c3d732 --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/wire_format.rs @@ -0,0 +1,154 @@ +pub fn read_u16_le(p: &[u8]) -> u16 { + u16::from_le_bytes([p[0], p[1]]) +} + +pub fn read_u32_le(p: &[u8]) -> u32 { + u32::from_le_bytes([p[0], p[1], p[2], p[3]]) +} + +pub fn read_u64_le(p: &[u8]) -> u64 { + u64::from_le_bytes([p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]]) +} + +pub fn write_u16_le(out: &mut [u8], v: u16) { + out[..2].copy_from_slice(&v.to_le_bytes()); +} + +pub fn write_u32_le(out: &mut [u8], v: u32) { + out[..4].copy_from_slice(&v.to_le_bytes()); +} + +pub fn write_u64_le(out: &mut [u8], v: u64) { + out[..8].copy_from_slice(&v.to_le_bytes()); +} + +pub struct Reader<'a> { + buf: &'a [u8], + pos: usize, + ok: bool, +} + +impl<'a> Reader<'a> { + pub fn new(buf: &'a [u8]) -> Self { + Self { + buf, + pos: 0, + ok: true, + } + } + + pub fn ok(&self) -> bool { + self.ok + } + + pub fn position(&self) -> usize { + self.pos + } + + pub fn remaining(&self) -> usize { + self.buf.len().saturating_sub(self.pos) + } + + pub fn u16(&mut self) -> u16 { + if !self.check(2) { + return 0; + } + let v = read_u16_le(&self.buf[self.pos..]); + self.pos += 2; + v + } + + pub fn u32(&mut self) -> u32 { + if !self.check(4) { + return 0; + } + let v = read_u32_le(&self.buf[self.pos..]); + self.pos += 4; + v + } + + pub fn u64(&mut self) -> u64 { + if !self.check(8) { + return 0; + } + let v = read_u64_le(&self.buf[self.pos..]); + self.pos += 8; + v + } + + pub fn bytes(&mut self, n: usize) -> &'a [u8] { + if !self.check(n) { + return &[]; + } + let out = &self.buf[self.pos..self.pos + n]; + self.pos += n; + out + } + + fn check(&mut self, n: usize) -> bool { + if !self.ok || self.remaining() < n { + self.ok = false; + return false; + } + true + } +} + +pub struct Writer<'a> { + out: &'a mut Vec, +} + +impl<'a> Writer<'a> { + pub fn new(out: &'a mut Vec) -> Self { + Self { out } + } + + pub fn u16(&mut self, v: u16) { + self.out.extend_from_slice(&v.to_le_bytes()); + } + + pub fn u32(&mut self, v: u32) { + self.out.extend_from_slice(&v.to_le_bytes()); + } + + pub fn u64(&mut self, v: u64) { + self.out.extend_from_slice(&v.to_le_bytes()); + } + + pub fn bytes(&mut self, bytes: &[u8]) { + self.out.extend_from_slice(bytes); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn reader_writer_little_endian_roundtrip() { + let mut buf = Vec::new(); + let mut writer = Writer::new(&mut buf); + writer.u16(0x1234); + writer.u32(0x89ab_cdef); + writer.u64(0x0123_4567_89ab_cdef); + + assert_eq!( + buf, + [0x34, 0x12, 0xef, 0xcd, 0xab, 0x89, 0xef, 0xcd, 0xab, 0x89, 0x67, 0x45, 0x23, 0x01] + ); + + let mut reader = Reader::new(&buf); + assert_eq!(reader.u16(), 0x1234); + assert_eq!(reader.u32(), 0x89ab_cdef); + assert_eq!(reader.u64(), 0x0123_4567_89ab_cdef); + assert!(reader.ok()); + assert_eq!(reader.remaining(), 0); + } + + #[test] + fn reader_short_input_flips_ok() { + let mut reader = Reader::new(&[1, 2, 3]); + assert_eq!(reader.u32(), 0); + assert!(!reader.ok()); + } +} diff --git a/app/src/main/cpp/wn-steam-client/rust/src/ws_connection.rs b/app/src/main/cpp/wn-steam-client/rust/src/ws_connection.rs new file mode 100644 index 000000000..b70d6a748 --- /dev/null +++ b/app/src/main/cpp/wn-steam-client/rust/src/ws_connection.rs @@ -0,0 +1,446 @@ +use crate::transport::{Transport, TransportDisconnectReason, TransportState}; +use std::sync::atomic::{AtomicBool, AtomicU8, Ordering}; +use std::sync::{mpsc, Arc, Mutex}; +use std::thread::{self, JoinHandle}; +use std::time::Duration; +use tungstenite::error::Error as WsError; +use tungstenite::stream::MaybeTlsStream; +use tungstenite::{connect, Message}; + +const NORMAL_CLOSE_CODE: u16 = 1000; +const READ_POLL_TIMEOUT: Duration = Duration::from_millis(100); + +type MessageCallback = Arc; +type ConnectedCallback = Arc; +type DisconnectedCallback = Arc; + +pub struct WsConnection { + shared: Arc, + sender: Mutex>>, + worker: Mutex>>, +} + +#[derive(Default)] +struct WsConnectionShared { + state: AtomicU8, + user_initiated_close: AtomicBool, + ca_bundle_path: Mutex, + on_message: Mutex>, + on_connected: Mutex>, + on_disconnected: Mutex>, +} + +enum WsCommand { + Send(Vec), + Disconnect, +} + +impl WsConnection { + pub fn new() -> Self { + Self { + shared: Arc::new(WsConnectionShared { + state: AtomicU8::new(TransportState::Disconnected as u8), + user_initiated_close: AtomicBool::new(false), + ca_bundle_path: Mutex::new(String::new()), + on_message: Mutex::new(None), + on_connected: Mutex::new(None), + on_disconnected: Mutex::new(None), + }), + sender: Mutex::new(None), + worker: Mutex::new(None), + } + } + + pub fn set_ca_bundle_path(&self, path: &str) { + *self + .shared + .ca_bundle_path + .lock() + .expect("ws connection poisoned") = path.to_string(); + } + + pub fn ca_bundle_path(&self) -> String { + self.shared + .ca_bundle_path + .lock() + .expect("ws connection poisoned") + .clone() + } + + pub fn state(&self) -> TransportState { + match self.shared.state.load(Ordering::Relaxed) { + 1 => TransportState::Connecting, + 2 => TransportState::Connected, + 3 => TransportState::Disconnecting, + _ => TransportState::Disconnected, + } + } + + pub fn transition_connecting(&self) -> bool { + self.shared + .user_initiated_close + .store(false, Ordering::Relaxed); + self.shared + .state + .compare_exchange( + TransportState::Disconnected as u8, + TransportState::Connecting as u8, + Ordering::AcqRel, + Ordering::Acquire, + ) + .is_ok() + } + + pub fn mark_connected(&self) { + self.shared.mark_connected(); + } + + pub fn mark_disconnected(&self, close_code: u16, detail: &str, tls_failed: bool) { + self.shared + .mark_disconnected(close_code, detail, tls_failed); + } + + pub fn mark_user_disconnect(&self) { + self.shared + .user_initiated_close + .store(true, Ordering::Relaxed); + self.shared + .state + .store(TransportState::Disconnecting as u8, Ordering::Release); + } + + pub fn deliver_binary(&self, data: &[u8]) { + self.shared.deliver_binary(data); + } + + pub fn set_on_message(&self, cb: F) + where + F: Fn(&[u8]) + Send + Sync + 'static, + { + *self + .shared + .on_message + .lock() + .expect("ws connection poisoned") = Some(Arc::new(cb)); + } + + pub fn set_on_connected(&self, cb: F) + where + F: Fn() + Send + Sync + 'static, + { + *self + .shared + .on_connected + .lock() + .expect("ws connection poisoned") = Some(Arc::new(cb)); + } + + pub fn set_on_disconnected(&self, cb: F) + where + F: Fn(TransportDisconnectReason, &str) + Send + Sync + 'static, + { + *self + .shared + .on_disconnected + .lock() + .expect("ws connection poisoned") = Some(Arc::new(cb)); + } + + fn connect_worker(&self, url: &str) -> bool { + if !self.transition_connecting() { + return false; + } + let (tx, rx) = mpsc::channel(); + *self.sender.lock().expect("ws connection poisoned") = Some(tx); + let shared = Arc::clone(&self.shared); + let url = url.to_string(); + let worker = thread::spawn(move || run_ws_worker(shared, url, rx)); + *self.worker.lock().expect("ws connection poisoned") = Some(worker); + true + } +} + +impl Transport for WsConnection { + fn connect(&mut self, url: &str) -> bool { + self.connect_worker(url) + } + + fn send(&mut self, data: &[u8]) -> bool { + if self.state() != TransportState::Connected { + return false; + } + self.sender + .lock() + .expect("ws connection poisoned") + .as_ref() + .is_some_and(|tx| tx.send(WsCommand::Send(data.to_vec())).is_ok()) + } + + fn disconnect(&mut self) { + if self.state() == TransportState::Disconnected { + return; + } + self.mark_user_disconnect(); + let sent = self + .sender + .lock() + .expect("ws connection poisoned") + .as_ref() + .is_some_and(|tx| tx.send(WsCommand::Disconnect).is_ok()); + if !sent { + self.mark_disconnected(NORMAL_CLOSE_CODE, "client disconnect", false); + } + } + + fn state(&self) -> TransportState { + WsConnection::state(self) + } + + fn set_on_message(&mut self, cb: Box) { + *self + .shared + .on_message + .lock() + .expect("ws connection poisoned") = Some(Arc::from(cb)); + } + + fn set_on_connected(&mut self, cb: Box) { + *self + .shared + .on_connected + .lock() + .expect("ws connection poisoned") = Some(Arc::from(cb)); + } + + fn set_on_disconnected( + &mut self, + cb: Box, + ) { + *self + .shared + .on_disconnected + .lock() + .expect("ws connection poisoned") = Some(Arc::from(cb)); + } + + fn set_ca_bundle_path(&mut self, path: &str) { + WsConnection::set_ca_bundle_path(self, path); + } +} + +impl Default for WsConnection { + fn default() -> Self { + Self::new() + } +} + +impl Drop for WsConnection { + fn drop(&mut self) { + self.disconnect(); + } +} + +impl WsConnectionShared { + fn mark_connected(&self) { + self.state + .store(TransportState::Connected as u8, Ordering::Release); + if let Some(cb) = self + .on_connected + .lock() + .expect("ws connection poisoned") + .clone() + { + cb(); + } + } + + fn mark_disconnected(&self, close_code: u16, detail: &str, tls_failed: bool) { + self.state + .store(TransportState::Disconnected as u8, Ordering::Release); + let reason = if self.user_initiated_close.load(Ordering::Relaxed) { + TransportDisconnectReason::UserInitiated + } else { + map_close_reason(close_code, tls_failed) + }; + if let Some(cb) = self + .on_disconnected + .lock() + .expect("ws connection poisoned") + .clone() + { + cb(reason, detail); + } + } + + fn deliver_binary(&self, data: &[u8]) { + if let Some(cb) = self + .on_message + .lock() + .expect("ws connection poisoned") + .clone() + { + cb(data); + } + } +} + +fn run_ws_worker(shared: Arc, url: String, rx: mpsc::Receiver) { + let (mut socket, _) = match connect(url.as_str()) { + Ok(connection) => connection, + Err(err) => { + let detail = err.to_string(); + let reason = map_error_reason(&detail, false); + shared.mark_disconnected(reason_to_close_code(reason), &detail, false); + return; + } + }; + let _ = set_read_timeout(socket.get_mut(), Some(READ_POLL_TIMEOUT)); + shared.mark_connected(); + + loop { + while let Ok(cmd) = rx.try_recv() { + match cmd { + WsCommand::Send(data) => { + if let Err(err) = socket.send(Message::Binary(data.into())) { + let detail = err.to_string(); + let reason = map_error_reason(&detail, false); + shared.mark_disconnected(reason_to_close_code(reason), &detail, false); + return; + } + } + WsCommand::Disconnect => { + let _ = socket.close(None); + shared.mark_disconnected(NORMAL_CLOSE_CODE, "client disconnect", false); + return; + } + } + } + + match socket.read() { + Ok(Message::Binary(bytes)) => shared.deliver_binary(&bytes), + Ok(Message::Close(frame)) => { + let detail = frame + .as_ref() + .map(|frame| frame.reason.to_string()) + .unwrap_or_else(|| "remote close".to_string()); + let code = frame + .map(|frame| u16::from(frame.code)) + .unwrap_or(NORMAL_CLOSE_CODE); + shared.mark_disconnected(code, &detail, false); + return; + } + Ok(Message::Ping(_) | Message::Pong(_) | Message::Text(_) | Message::Frame(_)) => {} + Err(err) if is_timeout_error(&err) => {} + Err(WsError::ConnectionClosed | WsError::AlreadyClosed) => { + shared.mark_disconnected(NORMAL_CLOSE_CODE, "remote close", false); + return; + } + Err(err) => { + let detail = err.to_string(); + let reason = map_error_reason(&detail, false); + shared.mark_disconnected(reason_to_close_code(reason), &detail, false); + return; + } + } + } +} + +fn set_read_timeout( + stream: &mut MaybeTlsStream, + timeout: Option, +) -> std::io::Result<()> { + match stream { + MaybeTlsStream::Plain(stream) => stream.set_read_timeout(timeout), + MaybeTlsStream::Rustls(stream) => stream.sock.set_read_timeout(timeout), + #[allow(unreachable_patterns)] + _ => Ok(()), + } +} + +fn is_timeout_error(err: &WsError) -> bool { + matches!(err, WsError::Io(io) if matches!( + io.kind(), + std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut + )) +} + +fn reason_to_close_code(reason: TransportDisconnectReason) -> u16 { + match reason { + TransportDisconnectReason::RemoteClose | TransportDisconnectReason::UserInitiated => { + NORMAL_CLOSE_CODE + } + _ => 0, + } +} + +pub fn map_close_reason(close_code: u16, tls_handshake_failed: bool) -> TransportDisconnectReason { + if tls_handshake_failed { + TransportDisconnectReason::TlsHandshakeFailed + } else if (1000..1016).contains(&close_code) { + TransportDisconnectReason::RemoteClose + } else { + TransportDisconnectReason::Unknown + } +} + +pub fn map_error_reason(reason: &str, user_initiated: bool) -> TransportDisconnectReason { + if user_initiated { + return TransportDisconnectReason::UserInitiated; + } + let tls_failed = reason.contains("tls") + || reason.contains("TLS") + || reason.contains("SSL") + || reason.contains("certificate"); + map_close_reason(0, tls_failed) +} + +pub fn normal_close_code() -> u16 { + NORMAL_CLOSE_CODE +} + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::mpsc; + + #[test] + fn maps_close_reasons_like_cpp() { + assert_eq!( + map_close_reason(0, true), + TransportDisconnectReason::TlsHandshakeFailed + ); + assert_eq!( + map_close_reason(1000, false), + TransportDisconnectReason::RemoteClose + ); + assert_eq!( + map_close_reason(2000, false), + TransportDisconnectReason::Unknown + ); + assert_eq!(normal_close_code(), 1000); + assert_eq!( + map_error_reason("TLS certificate verify failed", false), + TransportDisconnectReason::TlsHandshakeFailed + ); + assert_eq!( + map_error_reason("connection reset", false), + TransportDisconnectReason::Unknown + ); + assert_eq!( + map_error_reason("connection reset", true), + TransportDisconnectReason::UserInitiated + ); + } + + #[test] + fn transitions_and_callbacks_fire() { + let ws = WsConnection::new(); + let (tx, rx) = mpsc::channel(); + ws.set_on_connected(move || tx.send("connected").unwrap()); + assert!(ws.transition_connecting()); + assert!(!ws.transition_connecting()); + ws.mark_connected(); + assert_eq!(ws.state(), TransportState::Connected); + assert_eq!(rx.recv().unwrap(), "connected"); + } +} diff --git a/app/src/main/cpp/wn-steam-launcher/build.sh b/app/src/main/cpp/wn-steam-launcher/build.sh new file mode 100755 index 000000000..884fb3011 --- /dev/null +++ b/app/src/main/cpp/wn-steam-launcher/build.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +# Cross-compile steam.exe (the Steam Launcher in-Wine host) for Wine (x86_64 / +# 64-bit PE) and stage it into the APK assets. The Android Gradle build does NOT +# compile this — it only packages the prebuilt binary — so run this after editing +# any wn-steam-launcher source, then rebuild the APK. +# +# Built 64-bit on purpose: it hosts Valve's real steamclient64.dll so +# IClientAppManager::LaunchApp drives the game through steamclient's own +# app-launch path. Named "steam.exe" because steamclient's CGameLauncher path +# requires its host process to look like real Steam. +# +# Usage: ./build.sh +# Output: ../../assets/wnsteam/bionic/steam.exe +# +# Requires the POSIX-threads mingw-w64 cross compiler (clean_shutdown.cpp uses +# std::thread, which the default win32-threads variant does not provide). +set -euo pipefail +cd "$(dirname "$0")" + +CXX="${CXX:-x86_64-w64-mingw32-g++-posix}" +STRIP="${STRIP:-x86_64-w64-mingw32-strip}" +OUT_FILE="../../assets/wnsteam/bionic/steam.exe" + +# -Wl,--subsystem,windows: no console, so Wine doesn't map a transient console +# X11 window at startup (which raced the X server and cut the preloader short). +# Static link the runtime so no MinGW DLLs are dragged into the wine prefix. +"$CXX" -std=c++17 -O2 -Wall -Wextra -Wno-unused-parameter \ + -static -static-libgcc -static-libstdc++ \ + -Wl,--subsystem,windows \ + -I. \ + -o "$OUT_FILE" \ + src/main.cpp clean_shutdown.cpp \ + -ladvapi32 -lkernel32 -luser32 + +"$STRIP" "$OUT_FILE" + +echo "Built: $OUT_FILE ($(stat -c '%s' "$OUT_FILE") bytes)" +file "$OUT_FILE" diff --git a/app/src/main/cpp/wn-steam-launcher/clean_shutdown.cpp b/app/src/main/cpp/wn-steam-launcher/clean_shutdown.cpp new file mode 100644 index 000000000..f14a02d88 --- /dev/null +++ b/app/src/main/cpp/wn-steam-launcher/clean_shutdown.cpp @@ -0,0 +1,407 @@ +#include "clean_shutdown.h" + +#include +#include + +#include +#include +#include +#include +#include + +namespace { + +using Steam_LogOff_fn = void (*)(int, int); +using Steam_ReleaseUser_fn = void (*)(int, int); +using Steam_BReleaseSteamPipe_fn = bool (*)(int); +using Steam_BLoggedOn_fn = bool (*)(int, int); +using Steam_BGetCallback_fn = bool (*)(int, void*); +using Steam_FreeLastCallback_fn = void (*)(int); + +Steam_LogOff_fn g_logoff = nullptr; +Steam_ReleaseUser_fn g_release_user = nullptr; +Steam_BReleaseSteamPipe_fn g_release_pipe = nullptr; +Steam_BLoggedOn_fn g_bloggedon = nullptr; +Steam_BGetCallback_fn g_bgetcallback = nullptr; +Steam_FreeLastCallback_fn g_freelastcallback = nullptr; + +int g_pipe = 0; +int g_user = 0; +char g_log_path[MAX_PATH] = {0}; +char g_game_exe[260] = {0}; + +void* g_cs_engine = nullptr; +int g_cs_hUser = 0; +int g_cs_hPipe = 0; +unsigned int g_cs_appId = 0; + +constexpr int kVtEngine_GetIClientRemoteStorage = 0xC0; +constexpr int kVtRS_GetSyncState = 0x240; +constexpr int kVtRS_BeginAppSync = 0x270; +constexpr int kVtRS_IsAppSyncInProgress = 0x278; + +bool cs_is_exec_ptr(void* p) { + if (!p) return false; + MEMORY_BASIC_INFORMATION mbi; + if (VirtualQuery(p, &mbi, sizeof(mbi)) == 0) return false; + if (mbi.State != MEM_COMMIT) return false; + DWORD x = mbi.Protect & 0xFF; + return x == PAGE_EXECUTE || x == PAGE_EXECUTE_READ || + x == PAGE_EXECUTE_READWRITE || x == PAGE_EXECUTE_WRITECOPY; +} + +int kill_processes_by_name(const char* exeName) { + if (!exeName || !exeName[0]) return 0; + HANDLE snap = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (snap == INVALID_HANDLE_VALUE) return 0; + PROCESSENTRY32 pe; + pe.dwSize = sizeof(pe); + int killed = 0; + if (::Process32First(snap, &pe)) { + do { + if (wn_game_image_matches(pe.szExeFile, exeName)) { + HANDLE h = ::OpenProcess(PROCESS_TERMINATE, FALSE, pe.th32ProcessID); + if (h) { + if (::TerminateProcess(h, 0)) killed++; + ::CloseHandle(h); + } + } + } while (::Process32Next(snap, &pe)); + } + ::CloseHandle(snap); + return killed; +} + +int count_processes_by_name(const char* exeName) { + if (!exeName || !exeName[0]) return 0; + HANDLE snap = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (snap == INVALID_HANDLE_VALUE) return 0; + PROCESSENTRY32 pe; + pe.dwSize = sizeof(pe); + int n = 0; + if (::Process32First(snap, &pe)) { + do { + if (wn_game_image_matches(pe.szExeFile, exeName)) n++; + } while (::Process32Next(snap, &pe)); + } + ::CloseHandle(snap); + return n; +} + +std::vector g_close_pids; + +BOOL CALLBACK close_enum_proc(HWND hwnd, LPARAM lp) { + DWORD pid = 0; + ::GetWindowThreadProcessId(hwnd, &pid); + for (DWORD p : g_close_pids) { + if (p == pid) { + ::PostMessageA(hwnd, WM_CLOSE, 0, 0); + break; + } + } + return TRUE; +} + +int graceful_close_game(const char* exeName) { + g_close_pids.clear(); + HANDLE snap = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (snap != INVALID_HANDLE_VALUE) { + PROCESSENTRY32 pe; + pe.dwSize = sizeof(pe); + if (::Process32First(snap, &pe)) { + do { + if (wn_game_image_matches(pe.szExeFile, exeName)) { + g_close_pids.push_back(pe.th32ProcessID); + } + } while (::Process32Next(snap, &pe)); + } + ::CloseHandle(snap); + } + if (!g_close_pids.empty()) { + ::EnumWindows(close_enum_proc, 0); + } + return (int) g_close_pids.size(); +} + +void (*g_log_fn)(const char* line) = nullptr; + +std::atomic g_armed{false}; +std::atomic g_done{false}; +std::atomic g_teardown_complete{false}; +std::atomic g_watch_run{false}; + +constexpr const char* kSentinelPath = "C:\\wn-launcher.shutdown"; + +void wn_log(const char* msg) { + char line[512]; + std::snprintf(line, sizeof(line), "[wn-launcher] %s", msg); + + if (g_log_fn) { + g_log_fn(line); + return; + } + if (g_log_path[0] == '\0') return; + FILE* f = std::fopen(g_log_path, "a"); + if (!f) return; + std::fprintf(f, "%s\n", line); + std::fclose(f); +} + +void teardown(const char* reason) { + bool expected = false; + if (!g_done.compare_exchange_strong(expected, true)) return; + + char buf[256]; + std::snprintf(buf, sizeof(buf), + "clean-shutdown teardown begin (reason=%s pipe=%d user=%d)", + reason ? reason : "?", g_pipe, g_user); + wn_log(buf); + + if (g_game_exe[0] && g_pipe != 0) { + int targeted = graceful_close_game(g_game_exe); + if (targeted == 0) { + wn_log("game already exited — skipping graceful-close wait"); + } else { + std::snprintf(buf, sizeof(buf), + "graceful close \"%s\" (WM_CLOSE to %d game process(es)); " + "waiting for clean SteamAPI_Shutdown", g_game_exe, targeted); + wn_log(buf); + + const int kMaxWaitMs = 3000; + int waited = 0; + while (waited < kMaxWaitMs && count_processes_by_name(g_game_exe) > 0) { + if (g_bgetcallback && g_freelastcallback) { + char cb[64]; + while (g_bgetcallback(g_pipe, cb)) g_freelastcallback(g_pipe); + } + ::Sleep(100); + waited += 100; + } + bool gone = count_processes_by_name(g_game_exe) == 0; + if (gone) { + std::snprintf(buf, sizeof(buf), + "game \"%s\" exited gracefully after %dms — steamclient " + "should have emitted games-played([])", g_game_exe, waited); + wn_log(buf); + } else { + int killed = kill_processes_by_name(g_game_exe); + std::snprintf(buf, sizeof(buf), + "game \"%s\" ignored WM_CLOSE for %dms — hard-killed %d " + "(games-played reap may be delayed)", + g_game_exe, waited, killed); + wn_log(buf); + } + if (g_bgetcallback && g_freelastcallback) { + char cb[64]; + for (int i = 0; i < 4; ++i) { + while (g_bgetcallback(g_pipe, cb)) g_freelastcallback(g_pipe); + ::Sleep(100); + } + } else { + ::Sleep(400); + } + wn_log("games-played reap window elapsed"); + } + } + + if (g_cs_engine && g_cs_appId != 0) { + wn_launcher_cloud_sync(g_cs_engine, g_cs_hUser, g_cs_hPipe, g_cs_appId, 2, 4, 15000); + } + + if (g_logoff && g_user != 0 && g_pipe != 0) { + g_logoff(g_pipe, g_user); + wn_log("Steam_LogOff sent"); + + const int kMinMs = 300, kMaxMs = 700, kStepMs = 100; + int waited = 0; + bool loggedOff = false; + while (waited < kMaxMs) { + ::Sleep(kStepMs); + waited += kStepMs; + if (g_bloggedon && !g_bloggedon(g_pipe, g_user)) { + loggedOff = true; + if (waited >= kMinMs) break; + } + } + std::snprintf(buf, sizeof(buf), + "logoff flush wait done (%dms, BLoggedOn=%s)", + waited, loggedOff ? "false(logged-off)" : "true/unknown"); + wn_log(buf); + } + if (g_release_user && g_user != 0 && g_pipe != 0) { + g_release_user(g_pipe, g_user); + wn_log("Steam_ReleaseUser done"); + } + if (g_release_pipe && g_pipe != 0) { + bool ok = g_release_pipe(g_pipe); + std::snprintf(buf, sizeof(buf), "Steam_BReleaseSteamPipe -> %d", ok ? 1 : 0); + wn_log(buf); + } + + wn_log("clean logoff complete"); + + ::DeleteFileA(kSentinelPath); + + g_teardown_complete.store(true); +} + +BOOL WINAPI ctrl_handler(DWORD type) { + switch (type) { + case CTRL_CLOSE_EVENT: + case CTRL_LOGOFF_EVENT: + case CTRL_SHUTDOWN_EVENT: + case CTRL_C_EVENT: + case CTRL_BREAK_EVENT: + teardown("console-ctrl"); + return TRUE; + default: + return FALSE; + } +} + +void watch_loop() { + while (g_watch_run.load()) { + if (::GetFileAttributesA(kSentinelPath) != INVALID_FILE_ATTRIBUTES) { + teardown("sentinel"); + g_watch_run.store(false); + ::ExitProcess(0); + return; + } + ::Sleep(150); + } +} + +} + +extern "C" void wn_launcher_set_log_sink(void (*log_fn)(const char* line)) { + g_log_fn = log_fn; +} + +extern "C" void wn_launcher_set_game_exe(const char* exeName) { + if (exeName && exeName[0]) { + std::snprintf(g_game_exe, sizeof(g_game_exe), "%s", exeName); + } else { + g_game_exe[0] = '\0'; + } +} + +extern "C" void wn_launcher_arm_clean_shutdown(void* hSteamClient, int pipe, + int user, const char* logPath) { + bool expected = false; + if (!g_armed.compare_exchange_strong(expected, true)) return; + + g_pipe = pipe; + g_user = user; + if (logPath && logPath[0]) { + std::snprintf(g_log_path, sizeof(g_log_path), "%s", logPath); + } + + HMODULE h = reinterpret_cast(hSteamClient); + if (h) { + g_logoff = reinterpret_cast( + ::GetProcAddress(h, "Steam_LogOff")); + g_release_user = reinterpret_cast( + ::GetProcAddress(h, "Steam_ReleaseUser")); + g_release_pipe = reinterpret_cast( + ::GetProcAddress(h, "Steam_BReleaseSteamPipe")); + g_bloggedon = reinterpret_cast( + ::GetProcAddress(h, "Steam_BLoggedOn")); + g_bgetcallback = reinterpret_cast( + ::GetProcAddress(h, "Steam_BGetCallback")); + g_freelastcallback = reinterpret_cast( + ::GetProcAddress(h, "Steam_FreeLastCallback")); + } + + char buf[256]; + std::snprintf(buf, sizeof(buf), + "clean-shutdown armed (pipe=%d user=%d logoff=%p releaseUser=%p " + "releasePipe=%p bLoggedOn=%p sentinel=%s)", + pipe, user, reinterpret_cast(g_logoff), + reinterpret_cast(g_release_user), + reinterpret_cast(g_release_pipe), + reinterpret_cast(g_bloggedon), kSentinelPath); + wn_log(buf); + + ::SetConsoleCtrlHandler(ctrl_handler, TRUE); + + ::DeleteFileA(kSentinelPath); + + g_watch_run.store(true); + std::thread(watch_loop).detach(); +} + +extern "C" void wn_launcher_set_cloud_context(void* engine, int hUser, int hPipe, + unsigned int appId) { + g_cs_engine = engine; + g_cs_hUser = hUser; + g_cs_hPipe = hPipe; + g_cs_appId = appId; +} + +extern "C" int wn_launcher_cloud_sync(void* engine, int hUser, int hPipe, + unsigned int appId, int cmd, int flags, int timeoutMs) { + if (!engine || appId == 0) return -1; + void** engine_vt = *reinterpret_cast(engine); + void* getRsP = engine_vt[kVtEngine_GetIClientRemoteStorage / 8]; + if (!cs_is_exec_ptr(getRsP)) { + wn_log("[wn-launcher] cloud: GetIClientRemoteStorage slot not executable — skipping sync"); + return -1; + } + using GetRsFn = void* (*)(void*, int, int); + void* rs = reinterpret_cast(getRsP)(engine, hUser, hPipe); + if (!rs) { + wn_log("[wn-launcher] cloud: IClientRemoteStorage null — skipping sync"); + return -1; + } + void** rs_vt = *reinterpret_cast(rs); + void* beginP = rs_vt[kVtRS_BeginAppSync / 8]; + void* inProgP = rs_vt[kVtRS_IsAppSyncInProgress / 8]; + void* stateP = rs_vt[kVtRS_GetSyncState / 8]; + if (!cs_is_exec_ptr(beginP) || !cs_is_exec_ptr(inProgP) || !cs_is_exec_ptr(stateP)) { + wn_log("[wn-launcher] cloud: RemoteStorage slot(s) not executable — skipping sync"); + return -1; + } + using BeginFn = bool (*)(void*, unsigned int, int, int); + using InProgFn = bool (*)(void*, unsigned int); + using StateFn = int (*)(void*, unsigned int); + + char buf[176]; + int finalState = -1; + for (int attempt = 1; attempt <= 3; ++attempt) { + bool started = reinterpret_cast(beginP)(rs, appId, cmd, flags); + std::snprintf(buf, sizeof(buf), + "[wn-launcher] cloud: BeginAppSync(app=%u cmd=%d flags=%d) attempt %d -> %d", + appId, cmd, flags, attempt, started ? 1 : 0); + wn_log(buf); + int waited = 0; + while (reinterpret_cast(inProgP)(rs, appId) && waited < timeoutMs) { + if (g_bgetcallback && g_freelastcallback) { + char cb[64]; + while (g_bgetcallback(g_pipe, cb)) g_freelastcallback(g_pipe); + } + ::Sleep(10); + waited += 10; + } + finalState = reinterpret_cast(stateP)(rs, appId); + std::snprintf(buf, sizeof(buf), + "[wn-launcher] cloud: sync settled (state=%d after %dms)", finalState, waited); + wn_log(buf); + if (finalState == 1 || finalState == 0 || finalState == 6) break; + } + if (finalState == 6) { + wn_log("[wn-launcher] cloud: CONFLICT (state 6) — not auto-resolving; leaving saves intact"); + } + return finalState; +} + +extern "C" void wn_launcher_clean_shutdown_now(const char* reason) { + teardown(reason ? reason : "explicit"); +} + +extern "C" void wn_launcher_wait_clean_shutdown(int maxMs) { + int waited = 0; + while (g_done.load() && !g_teardown_complete.load() && waited < maxMs) { + ::Sleep(50); + waited += 50; + } +} diff --git a/app/src/main/cpp/wn-steam-launcher/clean_shutdown.h b/app/src/main/cpp/wn-steam-launcher/clean_shutdown.h new file mode 100644 index 000000000..b1c76d4f4 --- /dev/null +++ b/app/src/main/cpp/wn-steam-launcher/clean_shutdown.h @@ -0,0 +1,46 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +void wn_launcher_set_log_sink(void (*log_fn)(const char* line)); + +void wn_launcher_set_game_exe(const char* exeName); + +void wn_launcher_arm_clean_shutdown(void* hSteamClient, int pipe, int user, + const char* logPath); + +void wn_launcher_set_cloud_context(void* engine, int hUser, int hPipe, unsigned int appId); + +int wn_launcher_cloud_sync(void* engine, int hUser, int hPipe, + unsigned int appId, int cmd, int flags, int timeoutMs); + +void wn_launcher_clean_shutdown_now(const char* reason); + +void wn_launcher_wait_clean_shutdown(int maxMs); + +#ifdef __cplusplus +} + +#include + +inline bool wn_game_image_matches(const char* procName, const char* gameExe) { + if (!procName || !gameExe || !gameExe[0]) return false; + if (_stricmp(procName, gameExe) == 0) return true; + static const char* const kSteamlessSuffixes[] = { ".original.exe", ".unpacked.exe" }; + size_t glen = strlen(gameExe); + for (const char* suf : kSteamlessSuffixes) { + size_t slen = strlen(suf); + if (glen > slen && _stricmp(gameExe + (glen - slen), suf) == 0) { + char base[260]; + size_t blen = glen - slen; + if (blen >= sizeof(base)) blen = sizeof(base) - 1; + memcpy(base, gameExe, blen); + base[blen] = '\0'; + return _stricmp(procName, base) == 0; + } + } + return false; +} +#endif diff --git a/app/src/main/cpp/wn-steam-launcher/src/main.cpp b/app/src/main/cpp/wn-steam-launcher/src/main.cpp new file mode 100644 index 000000000..2b6445701 --- /dev/null +++ b/app/src/main/cpp/wn-steam-launcher/src/main.cpp @@ -0,0 +1,1560 @@ + +#define WIN32_LEAN_AND_MEAN +#include +#include "clean_shutdown.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef LOAD_LIBRARY_SEARCH_SYSTEM32 +#define LOAD_LIBRARY_SEARCH_SYSTEM32 0x00000800 +#endif +#ifndef LOAD_LIBRARY_SEARCH_DEFAULT_DIRS +#define LOAD_LIBRARY_SEARCH_DEFAULT_DIRS 0x00001000 +#endif +#ifndef LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR +#define LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR 0x00000100 +#endif +#ifndef LOAD_IGNORE_CODE_AUTHZ_LEVEL +#define LOAD_IGNORE_CODE_AUTHZ_LEVEL 0x00000010 +#endif + +#ifdef __i386__ +#define WN_THISCALL __thiscall +#else +#define WN_THISCALL +#endif + +static const int kVtEngine_GetIClientUser = 0x40; // IClientEngine slot 8 +static const int kVtUser_LogOn = 0x08; // slot 1: EResult LogOn(uint64 steamID) +static const int kVtUser_BLoggedOn = 0x20; // slot 4: bool BLoggedOn() +static const int kVtUser_GetSteamID = 0x50; // slot 10: CSteamID& GetSteamID(CSteamID& out) +static const int kVtUser_BHasCachedCreds = 0x188; // slot 49: bool BHasCachedCredentials(const char*) +static const int kVtUser_SetLoginToken = 0x1C0; // slot 56: EResult SetLoginToken(const char* token, const char* account) + +static const int kVtEngine_GetIClientAppManager = 0x158; // IClientEngine slot 43 +static const int kVtAppMgr_LaunchApp = 0x10; // IClientAppManager slot 2 +static const int kVtAppMgr_RefreshAppInfo = 0x298; // void RefreshAppInfo() +static const int kVtAppMgr_GetAppInstallState = 0x20; // int GetAppInstallState(AppId_t) + +static const int kVtEngine_GetIClientApps = 0x88; // slot 17: IClientApps*(hUser, hPipe) +static const int kVtApps_RequestAppInfoUpdate = 0x38; // slot 7: bool(AppId_t* ids, int n) + +static const int kVtEngine_GetIClientUtils = 0x70; // slot 14: IClientUtils*(HSteamPipe) +static const int kVtUtils_IsAPICallCompleted = 0xB0; // slot 22: bool(apiCall, *pbFailed) +static const int kVtUtils_GetAPICallFailureReason = 0xB8; // slot 23: int(apiCall) ESteamAPICallFailure +static const int kVtUtils_GetAPICallResult = 0xC0; // slot 24: bool(apiCall, pCb, cubCb, iCbExpected, *pbFailed) + +static const int kLaunchAppResultCallbackId = 0x13610B; +static const int kLaunchAppResultSize = 0x20C; +static const int kLaunchResultErrorOffset = 0x8; // int32 EAppUpdateError + +typedef void* (*CreateInterfaceFn)(const char* version, int* returnCode); +typedef int (*Steam_CreateGlobalUser_fn)(int* pipe_out); +typedef bool (*Steam_BLoggedOn_fn)(int pipe, int user); +typedef bool (*Steam_BGetCallback_fn)(int pipe, void* cb); +typedef void (*Steam_FreeLastCallback_fn)(int pipe); +typedef void (*Breakpad_SteamSetAppID_fn)(unsigned app_id); + +static FILE* g_logFile = NULL; + +static void open_log(void) { + if (g_logFile) return; + g_logFile = fopen("C:\\wn-launcher.log", "w"); + if (g_logFile) setvbuf(g_logFile, NULL, _IONBF, 0); +} + +static void log_line(const char* fmt, ...) { + char buf[1024]; + va_list ap; + va_start(ap, fmt); + int n = vsnprintf(buf, sizeof(buf) - 2, fmt, ap); + va_end(ap); + if (n < 0) n = 0; + if (n > (int)sizeof(buf) - 2) n = (int)sizeof(buf) - 2; + buf[n] = '\n'; + buf[n + 1] = '\0'; + fputs(buf, stderr); + OutputDebugStringA(buf); + if (g_logFile) { + fputs(buf, g_logFile); + } else { + FILE* lf = fopen("C:\\wn-launcher.log", "a"); + if (lf) { fputs(buf, lf); fclose(lf); } + } +} + +// Route clean_shutdown.cpp's [wn-launcher] markers through our single log handle; +// a separate fopen() there gets clobbered by our next write, dropping the markers +// the Android close path keys off. +static void clean_shutdown_log_sink(const char* line) { + if (line) log_line("%s", line); +} + +static uint64_t env_u64(const char* name) { + const char* v = getenv(name); + if (!v || !*v) return 0; + return (uint64_t) _strtoui64(v, NULL, 10); +} + +static int b64url_val(unsigned char c) { + if (c >= 'A' && c <= 'Z') return c - 'A'; + if (c >= 'a' && c <= 'z') return c - 'a' + 26; + if (c >= '0' && c <= '9') return c - '0' + 52; + if (c == '-') return 62; + if (c == '_') return 63; + return -1; +} + +static void log_token_claims(const char* token) { + if (!token || !*token) { log_line("[wn-launcher] token: (empty)"); return; } + const char* dot1 = strchr(token, '.'); + if (!dot1) { log_line("[wn-launcher] token: not a JWT (no '.')"); return; } + const char* dot2 = strchr(dot1 + 1, '.'); + if (!dot2) { log_line("[wn-launcher] token: not a JWT (one '.')"); return; } + size_t seglen = (size_t)(dot2 - (dot1 + 1)); + if (seglen == 0 || seglen > 2000) { + log_line("[wn-launcher] token: payload segment size unusable (%zu)", seglen); + return; + } + char out[1536]; + size_t op = 0; + uint32_t acc = 0; + int bits = 0; + for (size_t i = 0; i < seglen && op < sizeof(out) - 1; ++i) { + unsigned char c = (unsigned char) (dot1 + 1)[i]; + int v = b64url_val(c); + if (v < 0) continue; + acc = (acc << 6) | (uint32_t) v; + bits += 6; + if (bits >= 8) { + bits -= 8; + out[op++] = (char)((acc >> bits) & 0xFF); + } + } + out[op] = '\0'; + log_line("[wn-launcher] token JWT payload: %s", out); +} + +static void seed_active_process_registry(uint32_t our_pid, uint32_t steam_account_id) { + HKEY h = NULL; + LONG rc = RegCreateKeyExA(HKEY_CURRENT_USER, + "Software\\Valve\\Steam\\ActiveProcess", + 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &h, NULL); + if (rc != ERROR_SUCCESS) { + log_line("[wn-launcher] RegCreateKeyEx(ActiveProcess) failed rc=%ld", rc); + return; + } + const char* clientDll = "C:\\Program Files (x86)\\Steam\\steamclient.dll"; + const char* clientDll64 = "C:\\Program Files (x86)\\Steam\\steamclient64.dll"; + const char* installPath = "C:\\Program Files (x86)\\Steam"; + DWORD universe = 1; // k_EUniversePublic + DWORD pid_dw = (DWORD) our_pid; + DWORD active_user = (DWORD) steam_account_id; + RegSetValueExA(h, "SteamClientDll", 0, REG_SZ, (const BYTE*) clientDll, (DWORD) strlen(clientDll) + 1); + RegSetValueExA(h, "SteamClientDll64", 0, REG_SZ, (const BYTE*) clientDll64, (DWORD) strlen(clientDll64) + 1); + RegSetValueExA(h, "Universe", 0, REG_DWORD, (const BYTE*) &universe, sizeof(universe)); + RegSetValueExA(h, "pid", 0, REG_DWORD, (const BYTE*) &pid_dw, sizeof(pid_dw)); + RegSetValueExA(h, "ActiveUser", 0, REG_DWORD, (const BYTE*) &active_user, sizeof(active_user)); + RegCloseKey(h); + + const char* appIdStr = getenv("WN_STEAM_APPID"); + if (appIdStr && *appIdStr) { + char keyPath[256]; + snprintf(keyPath, sizeof(keyPath), + "Software\\Valve\\Steam\\Apps\\%s", appIdStr); + HKEY h2 = NULL; + if (RegCreateKeyExA(HKEY_CURRENT_USER, keyPath, 0, NULL, + REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &h2, NULL) == ERROR_SUCCESS) { + DWORD one = 1; + DWORD zero = 0; + RegSetValueExA(h2, "Installed", 0, REG_DWORD, (const BYTE*) &one, sizeof(one)); + RegSetValueExA(h2, "Running", 0, REG_DWORD, (const BYTE*) &one, sizeof(one)); + RegSetValueExA(h2, "Updating", 0, REG_DWORD, (const BYTE*) &zero, sizeof(zero)); + RegCloseKey(h2); + } + } + { + const char* steamFwd = "c:/program files (x86)/steam"; + const char* steamExe = "c:/program files (x86)/steam/steam.exe"; + const char* steamBack = "C:\\Program Files (x86)\\Steam"; + HKEY hk = NULL; + if (RegCreateKeyExA(HKEY_CURRENT_USER, "Software\\Valve\\Steam", 0, NULL, + REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hk, NULL) == ERROR_SUCCESS) { + RegSetValueExA(hk, "SteamPath", 0, REG_SZ, + (const BYTE*) steamFwd, (DWORD) strlen(steamFwd) + 1); + RegSetValueExA(hk, "SteamExe", 0, REG_SZ, + (const BYTE*) steamExe, (DWORD) strlen(steamExe) + 1); + RegCloseKey(hk); + } + HKEY hm = NULL; + if (RegCreateKeyExA(HKEY_LOCAL_MACHINE, "Software\\Valve\\Steam", 0, NULL, + REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hm, NULL) == ERROR_SUCCESS) { + RegSetValueExA(hm, "InstallPath", 0, REG_SZ, + (const BYTE*) steamBack, (DWORD) strlen(steamBack) + 1); + RegSetValueExA(hm, "SteamPath", 0, REG_SZ, + (const BYTE*) steamFwd, (DWORD) strlen(steamFwd) + 1); + RegCloseKey(hm); + } + SetEnvironmentVariableA("SteamPath", steamBack); + } + + log_line("[wn-launcher] HKCU ActiveProcess + Steam install registry seeded " + "(pid=%u, activeUser=%u, SteamPath set)", + our_pid, steam_account_id); +} + +static void stage_steam_config(void) { + const char* cfgDir = "C:\\Program Files (x86)\\Steam\\config"; + CreateDirectoryA(cfgDir, NULL); + const char* files[2] = { + "C:\\Program Files (x86)\\Steam\\config\\config.vdf", + "C:\\Program Files (x86)\\Steam\\config\\local.vdf", + }; + for (int i = 0; i < 2; ++i) { + DWORD attr = GetFileAttributesA(files[i]); + if (attr == INVALID_FILE_ATTRIBUTES) { + HANDLE h = CreateFileA(files[i], GENERIC_WRITE, 0, NULL, + CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL); + if (h != INVALID_HANDLE_VALUE) { + CloseHandle(h); + log_line("[wn-launcher] staged empty %s", files[i]); + } + } + } +} + +// Escape a free-text value for a VDF/ACF quoted field: double backslashes, then +// escape quotes and newlines. Mirrors the Kotlin escapeString() so the C++ and +// Kotlin manifest paths produce identical, well-formed output. +static std::string vdf_escape(const char* s) { + std::string out; + if (!s) return out; + for (const char* p = s; *p; ++p) { + switch (*p) { + case '\\': out += "\\\\"; break; + case '"': out += "\\\""; break; + case '\n': out += "\\n"; break; + case '\r': out += "\\r"; break; + default: out += *p; break; + } + } + return out; +} + +static void stage_app_manifest(uint32_t appId, const char* gameExe) { + if (appId == 0 || !gameExe) return; + const char* marker = "\\steamapps\\common\\"; + size_t mlen = strlen(marker); + const char* hit = NULL; + for (const char* s = gameExe; *s; ++s) { + if (_strnicmp(s, marker, mlen) == 0) { hit = s; break; } + } + if (!hit) { + log_line("[wn-launcher] app manifest: game not under steamapps\\common " + "— skipping (LaunchApp may report not-installed)"); + return; + } + const char* dirStart = hit + mlen; + const char* dirEnd = strchr(dirStart, '\\'); + if (!dirEnd || dirEnd == dirStart) return; + char installdir[260]; + size_t n = (size_t)(dirEnd - dirStart); + if (n >= sizeof(installdir)) return; + memcpy(installdir, dirStart, n); + installdir[n] = '\0'; + + CreateDirectoryA("C:\\Program Files (x86)\\Steam\\steamapps", NULL); + char acf[MAX_PATH]; + snprintf(acf, sizeof(acf), + "C:\\Program Files (x86)\\Steam\\steamapps\\appmanifest_%u.acf", + appId); + const char* owner = getenv("WN_STEAM_STEAMID"); + const char* depotsEnv = getenv("WN_STEAM_DEPOTS"); + const char* sharedEnv = getenv("WN_STEAM_SHARED_DEPOTS"); + const char* appName = getenv("WN_STEAM_APP_NAME"); + const char* installScriptsEnv = getenv("WN_STEAM_INSTALL_SCRIPTS"); + const char* language = getenv("WN_STEAM_LANGUAGE"); + const char* buildIdStr = getenv("WN_STEAM_BUILD_ID"); + const char* sizeOnDiskStr = getenv("WN_STEAM_SIZE_ON_DISK"); + const char* bytesToDownloadStr = getenv("WN_STEAM_BYTES_TO_DOWNLOAD"); + const char* bytesToStageStr = getenv("WN_STEAM_BYTES_TO_STAGE"); + if (!appName || !*appName) appName = installdir; + if (!language || !*language) language = "english"; + unsigned long long buildId = (buildIdStr && *buildIdStr) ? strtoull(buildIdStr, NULL, 10) : 0ULL; + unsigned long long sizeOnDisk = (sizeOnDiskStr && *sizeOnDiskStr) ? strtoull(sizeOnDiskStr, NULL, 10) : 0ULL; + unsigned long long bytesToDownload = (bytesToDownloadStr && *bytesToDownloadStr) ? strtoull(bytesToDownloadStr, NULL, 10) : 0ULL; + unsigned long long bytesToStage = (bytesToStageStr && *bytesToStageStr) ? strtoull(bytesToStageStr, NULL, 10) : 0ULL; + FILE* f = fopen(acf, "w"); + if (!f) { + log_line("[wn-launcher] app manifest: fopen(%s) failed", acf); + return; + } + std::string nameEsc = vdf_escape(appName); + std::string installdirEsc = vdf_escape(installdir); + std::string languageEsc = vdf_escape(language); + fprintf(f, + "\"AppState\"\n" + "{\n" + "\t\"appid\"\t\t\"%u\"\n" + "\t\"universe\"\t\t\"1\"\n" + "\t\"LauncherPath\"\t\t\"C:\\\\Program Files (x86)\\\\Steam\\\\steam.exe\"\n" + "\t\"name\"\t\t\"%s\"\n" + "\t\"StateFlags\"\t\t\"4\"\n" + "\t\"installdir\"\t\t\"%s\"\n" + "\t\"LastUpdated\"\t\t\"%llu\"\n" + "\t\"LastPlayed\"\t\t\"0\"\n" + "\t\"SizeOnDisk\"\t\t\"%llu\"\n" + "\t\"StagingSize\"\t\t\"0\"\n" + "\t\"buildid\"\t\t\"%llu\"\n" + "\t\"LastOwner\"\t\t\"%s\"\n" + "\t\"DownloadType\"\t\t\"1\"\n" + "\t\"UpdateResult\"\t\t\"0\"\n" + "\t\"BytesToDownload\"\t\t\"%llu\"\n" + "\t\"BytesDownloaded\"\t\t\"%llu\"\n" + "\t\"BytesToStage\"\t\t\"%llu\"\n" + "\t\"BytesStaged\"\t\t\"%llu\"\n" + "\t\"TargetBuildID\"\t\t\"%llu\"\n" + "\t\"AutoUpdateBehavior\"\t\t\"0\"\n" + "\t\"AllowOtherDownloadsWhileRunning\"\t\t\"0\"\n" + "\t\"ScheduledAutoUpdate\"\t\t\"0\"\n", + appId, nameEsc.c_str(), installdirEsc.c_str(), + (unsigned long long)time(NULL), + sizeOnDisk, buildId, + (owner && *owner) ? owner : "0", + bytesToDownload, bytesToDownload, + bytesToStage, bytesToStage, buildId); + // Write InstalledDepots with depot data from WN_STEAM_DEPOTS env var. + // Format: depotId:manifestGid:size[:dlcAppId],... + if (depotsEnv && *depotsEnv) { + fprintf(f, "\t\"InstalledDepots\"\n\t{\n"); + std::vector buf(strlen(depotsEnv) + 1); + memcpy(buf.data(), depotsEnv, buf.size()); + char* token = strtok(buf.data(), ","); + while (token) { + // Parse depotId:manifestGid:size[:dlcAppId] + char* colon1 = strchr(token, ':'); + if (!colon1) { token = strtok(NULL, ","); continue; } + *colon1 = '\0'; + const char* depotIdStr = token; + char* manifestStart = colon1 + 1; + char* colon2 = strchr(manifestStart, ':'); + if (!colon2) { token = strtok(NULL, ","); continue; } + *colon2 = '\0'; + const char* manifestStr = manifestStart; + char* sizeStart = colon2 + 1; + char* colon3 = strchr(sizeStart, ':'); + const char* sizeStr, *dlcAppIdStr; + if (colon3) { + *colon3 = '\0'; + sizeStr = sizeStart; + dlcAppIdStr = colon3 + 1; + } else { + sizeStr = sizeStart; + dlcAppIdStr = NULL; + } + fprintf(f, "\t\t\"%s\"\n\t\t{\n" + "\t\t\t\"manifest\"\t\t\"%s\"\n" + "\t\t\t\"size\"\t\t\"%s\"\n", + depotIdStr, manifestStr, sizeStr); + if (dlcAppIdStr && *dlcAppIdStr) { + fprintf(f, "\t\t\t\"dlcappid\"\t\t\"%s\"\n", dlcAppIdStr); + } + fprintf(f, "\t\t}\n"); + token = strtok(NULL, ","); + } + fprintf(f, "\t}\n"); + } else { + fprintf(f, "\t\"InstalledDepots\"\n\t{\n\t}\n"); + } + // Write InstallScripts from WN_STEAM_INSTALL_SCRIPTS env var. + // Format: depotId:scriptFilename,... + if (installScriptsEnv && *installScriptsEnv) { + fprintf(f, "\t\"InstallScripts\"\n\t{\n"); + std::vector isbuf(strlen(installScriptsEnv) + 1); + memcpy(isbuf.data(), installScriptsEnv, isbuf.size()); + char* istoken = strtok(isbuf.data(), ","); + while (istoken) { + char* iscolon = strchr(istoken, ':'); + if (!iscolon) { istoken = strtok(NULL, ","); continue; } + *iscolon = '\0'; + std::string scriptEsc = vdf_escape(iscolon + 1); + fprintf(f, "\t\t\"%s\"\t\t\"%s\"\n", istoken, scriptEsc.c_str()); + istoken = strtok(NULL, ","); + } + fprintf(f, "\t}\n"); + } + // Write SharedDepots from WN_STEAM_SHARED_DEPOTS env var. + // Format: sourceDepotId:targetAppId,... + if (sharedEnv && *sharedEnv) { + fprintf(f, "\t\"SharedDepots\"\n\t{\n"); + std::vector sbuf(strlen(sharedEnv) + 1); + memcpy(sbuf.data(), sharedEnv, sbuf.size()); + char* stoken = strtok(sbuf.data(), ","); + while (stoken) { + char* scolon = strchr(stoken, ':'); + if (!scolon) { stoken = strtok(NULL, ","); continue; } + *scolon = '\0'; + fprintf(f, "\t\t\"%s\"\t\t\"%s\"\n", stoken, scolon + 1); + stoken = strtok(NULL, ","); + } + fprintf(f, "\t}\n"); + } + fprintf(f, + "\t\"UserConfig\"\n" + "\t{\n" + "\t\t\"language\"\t\t\"%s\"\n" + "\t}\n" + "\t\"MountedConfig\"\n" + "\t{\n" + "\t\t\"language\"\t\t\"%s\"\n" + "\t}\n" + "}\n", + languageEsc.c_str(), languageEsc.c_str()); + fclose(f); + log_line("[wn-launcher] app manifest staged: %s (installdir=\"%s\", " + "depots=%s shared=%s scripts=%s)", + acf, installdir, + depotsEnv && *depotsEnv ? depotsEnv : "(none)", + sharedEnv && *sharedEnv ? sharedEnv : "(none)", + installScriptsEnv && *installScriptsEnv ? installScriptsEnv : "(none)"); +} + +// Counts running game processes (matches LaunchApp's canonical name or the literal +// fallback name via wn_game_image_matches). +static int count_game_processes(const char* exeName) { + HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (snap == INVALID_HANDLE_VALUE) return -1; + PROCESSENTRY32 pe; + pe.dwSize = sizeof(pe); + int count = 0; + if (Process32First(snap, &pe)) { + do { + if (wn_game_image_matches(pe.szExeFile, exeName)) count++; + } while (Process32Next(snap, &pe)); + } + CloseHandle(snap); + return count; +} + +// Direct launch when LaunchApp dispatches cleanly but never spawns the game (no +// real Steam UI/reaper under Wine to consume the request). Safe vs AlreadyRunning +// because the clean-shutdown arm reaps the CM session on exit. Logs the "game +// process started pid=" marker WnLauncherStatusTailer treats as launch-complete. +static bool create_process_game(const char* gameExe, const char* exeName) { + char cwd[MAX_PATH]; + snprintf(cwd, sizeof(cwd), "%s", gameExe); + char* slash = strrchr(cwd, '\\'); + if (slash) *slash = '\0'; else cwd[0] = '\0'; + + char cmd[MAX_PATH + 8]; + snprintf(cmd, sizeof(cmd), "\"%s\"", gameExe); + + STARTUPINFOA si; + memset(&si, 0, sizeof(si)); + si.cb = sizeof(si); + PROCESS_INFORMATION pi; + memset(&pi, 0, sizeof(pi)); + + // Inherit our env (SteamAppId etc.) so the game's SteamAPI_Init attaches to + // our logged-on steamclient session. + BOOL ok = CreateProcessA(gameExe, cmd, NULL, NULL, FALSE, + 0, NULL, cwd[0] ? cwd : NULL, &si, &pi); + if (!ok) { + log_line("[wn-launcher] CreateProcess fallback FAILED for \"%s\" (GLE=%lu)", + exeName, GetLastError()); + return false; + } + log_line("[wn-launcher] game process started pid=%lu via CreateProcess " + "fallback (\"%s\")", (unsigned long) pi.dwProcessId, exeName); + if (pi.hThread) CloseHandle(pi.hThread); + if (pi.hProcess) CloseHandle(pi.hProcess); + return true; +} + +static void dump_loaded_modules(const char* when) { + HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, + GetCurrentProcessId()); + if (snap == INVALID_HANDLE_VALUE) { + log_line("[wn-launcher] modules(%s): CreateToolhelp32Snapshot failed GLE=%lu", + when, GetLastError()); + return; + } + MODULEENTRY32 me; + me.dwSize = sizeof(me); + int n = 0; + if (Module32First(snap, &me)) { + do { + log_line("[wn-launcher] modules(%s): base=%p size=0x%lx name=%s path=%s", + when, me.modBaseAddr, (unsigned long) me.modBaseSize, + me.szModule, me.szExePath); + n++; + } while (Module32Next(snap, &me)); + } + log_line("[wn-launcher] modules(%s): total=%d", when, n); + CloseHandle(snap); +} + +static LONG WINAPI launcher_unhandled_filter(EXCEPTION_POINTERS* info) { + if (!info || !info->ExceptionRecord) return EXCEPTION_EXECUTE_HANDLER; + const EXCEPTION_RECORD* er = info->ExceptionRecord; + void* ip = er->ExceptionAddress; + + char modName[MAX_PATH] = {0}; + HMODULE faultMod = NULL; + if (GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS + | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + (LPCSTR)ip, &faultMod)) { + GetModuleFileNameA(faultMod, modName, sizeof(modName)); + } + + char bytes[3 * 16 + 1] = {0}; + { + MEMORY_BASIC_INFORMATION mbi; + if (VirtualQuery(ip, &mbi, sizeof(mbi)) && mbi.State == MEM_COMMIT) { + const unsigned char* p = (const unsigned char*)ip; + int hp = 0; + for (int i = 0; i < 16 && hp + 3 < (int)sizeof(bytes); ++i) { + hp += snprintf(bytes + hp, sizeof(bytes) - hp, "%02x ", p[i]); + } + } + } + + log_line("[wn-launcher] UEF: tid=%lu pid=%lu exc=0x%lx at %p mod='%s' bytes=%s", + (unsigned long) GetCurrentThreadId(), + (unsigned long) GetCurrentProcessId(), + er->ExceptionCode, ip, modName[0] ? modName : "(unknown)", bytes); + if (er->ExceptionCode == EXCEPTION_ACCESS_VIOLATION && er->NumberParameters >= 2) { + const char* op = (er->ExceptionInformation[0] == 0) ? "read" + : (er->ExceptionInformation[0] == 1) ? "write" + : (er->ExceptionInformation[0] == 8) ? "DEP" : "?"; + log_line("[wn-launcher] UEF: AV %s fault_addr=0x%llx", + op, (unsigned long long) er->ExceptionInformation[1]); + } + + { + MEMORY_BASIC_INFORMATION mbi; + if (VirtualQuery(ip, &mbi, sizeof(mbi))) { + log_line("[wn-launcher] UEF: page base=%p size=0x%llx state=0x%lx " + "protect=0x%lx alloc_protect=0x%lx type=0x%lx", + mbi.BaseAddress, (unsigned long long) mbi.RegionSize, + mbi.State, mbi.Protect, mbi.AllocationProtect, mbi.Type); + } + } + + if (info->ContextRecord) { + const CONTEXT* c = info->ContextRecord; + log_line("[wn-launcher] UEF: ctx Rip=%llx Rsp=%llx Rbp=%llx", + (unsigned long long) c->Rip, + (unsigned long long) c->Rsp, + (unsigned long long) c->Rbp); + log_line("[wn-launcher] UEF: ctx Rax=%llx Rcx=%llx Rdx=%llx Rbx=%llx", + (unsigned long long) c->Rax, (unsigned long long) c->Rcx, + (unsigned long long) c->Rdx, (unsigned long long) c->Rbx); + log_line("[wn-launcher] UEF: ctx Rsi=%llx Rdi=%llx R8=%llx R9=%llx", + (unsigned long long) c->Rsi, (unsigned long long) c->Rdi, + (unsigned long long) c->R8, (unsigned long long) c->R9); + const uint64_t* sp = (const uint64_t*) c->Rsp; + MEMORY_BASIC_INFORMATION smbi; + if (sp && VirtualQuery((LPCVOID) sp, &smbi, sizeof(smbi)) + && smbi.State == MEM_COMMIT) { + char chain[256]; int p = 0; + for (int i = 0; i < 8; ++i) { + p += snprintf(chain + p, sizeof(chain) - p, "%llx ", + (unsigned long long) sp[i]); + } + log_line("[wn-launcher] UEF: stack[0..7]=%s", chain); + } + } + + dump_loaded_modules("UEF"); + return EXCEPTION_EXECUTE_HANDLER; +} + +static bool start_steam_client_service(void) { + const char* kSvcName = "Steam Client Service"; + const char* kSvcExe = "C:\\Program Files (x86)\\Steam\\bin\\steamservice.exe"; + const char* kSvcBinPath = "\"C:\\Program Files (x86)\\Steam\\bin\\steamservice.exe\" /RunAsService"; + + DWORD attr = GetFileAttributesA(kSvcExe); + if (attr == INVALID_FILE_ATTRIBUTES || (attr & FILE_ATTRIBUTE_DIRECTORY)) { + log_line("[wn-launcher] steamservice: binary not present at %s — " + "LaunchApp's IPC queue will have no peer; will use " + "CreateProcess fallback", kSvcExe); + return false; + } + log_line("[wn-launcher] steamservice: found %s", kSvcExe); + + SC_HANDLE scm = OpenSCManagerA(NULL, NULL, SC_MANAGER_ALL_ACCESS); + if (!scm) { + log_line("[wn-launcher] steamservice: OpenSCManager failed GLE=%lu", + GetLastError()); + return false; + } + + SC_HANDLE svc = OpenServiceA(scm, kSvcName, SERVICE_ALL_ACCESS); + if (!svc) { + DWORD err = GetLastError(); + if (err == ERROR_SERVICE_DOES_NOT_EXIST) { + log_line("[wn-launcher] steamservice: service missing — " + "installing as \"%s\"", kSvcName); + svc = CreateServiceA( + scm, kSvcName, kSvcName, + SERVICE_ALL_ACCESS, + SERVICE_WIN32_OWN_PROCESS, + SERVICE_DEMAND_START, + SERVICE_ERROR_NORMAL, + kSvcBinPath, + NULL, NULL, NULL, NULL, NULL); + if (!svc) { + log_line("[wn-launcher] steamservice: CreateService failed GLE=%lu", + GetLastError()); + CloseServiceHandle(scm); + return false; + } + log_line("[wn-launcher] steamservice: service installed"); + } else { + log_line("[wn-launcher] steamservice: OpenService failed GLE=%lu", err); + CloseServiceHandle(scm); + return false; + } + } + + SERVICE_STATUS status; + memset(&status, 0, sizeof(status)); + QueryServiceStatus(svc, &status); + log_line("[wn-launcher] steamservice: pre-start state=%lu", status.dwCurrentState); + + if (status.dwCurrentState != SERVICE_RUNNING) { + if (!StartServiceA(svc, 0, NULL)) { + DWORD err = GetLastError(); + if (err != ERROR_SERVICE_ALREADY_RUNNING) { + log_line("[wn-launcher] steamservice: StartService failed GLE=%lu", + err); + CloseServiceHandle(svc); + CloseServiceHandle(scm); + return false; + } + } + int waited = 0; + while (waited < 30000) { + if (!QueryServiceStatus(svc, &status)) break; + if (status.dwCurrentState == SERVICE_RUNNING || + status.dwCurrentState == SERVICE_STOPPED) break; + Sleep(200); + waited += 200; + } + log_line("[wn-launcher] steamservice: post-start state=%lu after %dms", + status.dwCurrentState, waited); + } + + bool running = (status.dwCurrentState == SERVICE_RUNNING); + CloseServiceHandle(svc); + CloseServiceHandle(scm); + return running; +} + +static bool is_exec_ptr(void* p) { + if (!p) return false; + MEMORY_BASIC_INFORMATION mbi; + if (VirtualQuery(p, &mbi, sizeof(mbi)) == 0) return false; + if (mbi.State != MEM_COMMIT) return false; + DWORD x = mbi.Protect & 0xFF; + return x == PAGE_EXECUTE || x == PAGE_EXECUTE_READ || + x == PAGE_EXECUTE_READWRITE || x == PAGE_EXECUTE_WRITECOPY; +} + +static const char* kRedistsMarkerPath = "C:\\wn-installed-redists.txt"; + +enum class RedistInstallResult { + SKIPPED = 0, + INSTALLED = 1, + FAILED = 2, + TIMED_OUT = 3, +}; + +static bool is_known_redist_installer(const std::filesystem::path& p) { + if (!std::filesystem::is_regular_file(p)) return false; + std::string name = p.filename().string(); + std::string ext = p.extension().string(); + for (char& c : name) c = (char) std::tolower((unsigned char) c); + for (char& c : ext) c = (char) std::tolower((unsigned char) c); + if (ext != ".exe" && ext != ".msi") return false; + return name.find("vcredist") != std::string::npos || + name.find("vc_redist") != std::string::npos || + name.find("dxsetup") != std::string::npos || + name.find("directx") != std::string::npos || + name.find("physx") != std::string::npos || + name.find("oalinst") != std::string::npos || + name.find("openal") != std::string::npos || + name.find("dotnet") != std::string::npos || + name.find("ndp") != std::string::npos || + name.find("xna") != std::string::npos || + name.find("ue4prereq") != std::string::npos || + name.find("prereq") != std::string::npos || + name.find("redist") != std::string::npos; +} + +static std::vector collect_redist_installers(const std::filesystem::path& gameExePath) { + std::vector out; + try { + auto root = gameExePath.parent_path(); + if (root.empty()) return out; + const std::vector hotDirs = { + "redist", "redists", "_redist", "redistributables", "installer", + "installers", "support", "prereq", "prereqs", "commonredist", + }; + for (auto it = std::filesystem::recursive_directory_iterator(root, + std::filesystem::directory_options::skip_permission_denied); + it != std::filesystem::recursive_directory_iterator(); ++it) { + const auto& p = it->path(); + if (it->is_directory()) { + std::string lower = p.filename().string(); + for (char& c : lower) c = (char) std::tolower((unsigned char) c); + bool keep = false; + for (const auto& needle : hotDirs) { + if (lower.find(needle) != std::string::npos) { keep = true; break; } + } + if (!keep && p.parent_path() != root) { + it.disable_recursion_pending(); + } + continue; + } + if (is_known_redist_installer(p)) out.push_back(p); + } + } catch (...) {} + return out; +} + +static bool marker_has_path(const std::string& line) { + DWORD attr = GetFileAttributesA(line.c_str()); + return attr != INVALID_FILE_ATTRIBUTES; +} + +static bool load_installed_redists(std::vector& lines) { + FILE* f = fopen(kRedistsMarkerPath, "r"); + if (!f) return false; + char buf[MAX_PATH * 4]; + while (fgets(buf, sizeof(buf), f)) { + size_t n = strlen(buf); + while (n && (buf[n - 1] == '\n' || buf[n - 1] == '\r')) buf[--n] = '\0'; + if (n) lines.emplace_back(buf); + } + fclose(f); + return true; +} + +static bool save_installed_redists(const std::vector& lines) { + FILE* f = fopen(kRedistsMarkerPath, "w"); + if (!f) return false; + for (const auto& line : lines) fprintf(f, "%s\n", line.c_str()); + fclose(f); + return true; +} + +static bool marker_contains(const std::vector& lines, const std::string& path) { + for (const auto& line : lines) { + if (_stricmp(line.c_str(), path.c_str()) == 0) return true; + } + return false; +} + +static std::string redist_silent_args(const std::filesystem::path& installer) { + std::string name = installer.filename().string(); + std::string ext = installer.extension().string(); + for (char& c : name) c = (char) std::tolower((unsigned char) c); + for (char& c : ext) c = (char) std::tolower((unsigned char) c); + if (ext == ".msi") return " /qn /norestart"; + if (name.find("dxsetup") != std::string::npos) return " /silent"; + if (name.find("ue4prereq") != std::string::npos) return " /quiet /norestart"; + if (name.find("physx") != std::string::npos) return " /quiet /norestart"; + return " /quiet /norestart"; +} + +static RedistInstallResult run_redist_installer(const std::filesystem::path& installer, + DWORD* outExitCode) { + std::string cmd = "\"" + installer.string() + "\"" + redist_silent_args(installer); + std::vector cmdVec(cmd.begin(), cmd.end()); + cmdVec.push_back('\0'); + + STARTUPINFOA si = {}; + si.cb = sizeof(si); + si.dwFlags = STARTF_USESHOWWINDOW; + si.wShowWindow = SW_HIDE; + PROCESS_INFORMATION pi = {}; + std::string cwdStr = installer.parent_path().string(); + if (!CreateProcessA( + installer.string().c_str(), + cmdVec.data(), + nullptr, nullptr, FALSE, + CREATE_NO_WINDOW, + nullptr, + cwdStr.empty() ? nullptr : cwdStr.c_str(), + &si, &pi)) { + log_line("[wn-launcher] redist install: CreateProcess failed for %s " + "(GLE=%lu)", + installer.string().c_str(), GetLastError()); + if (outExitCode) *outExitCode = 0xFFFFFFFFu; + return RedistInstallResult::FAILED; + } + + constexpr DWORD kPerInstallerTimeoutMs = 90 * 1000; + DWORD waitResult = WaitForSingleObject(pi.hProcess, kPerInstallerTimeoutMs); + DWORD exitCode = ~0u; + bool timedOut = false; + if (waitResult == WAIT_OBJECT_0) { + GetExitCodeProcess(pi.hProcess, &exitCode); + } else { + log_line("[wn-launcher] redist install: %s — 90s timeout (silent " + "installer hung?)", + installer.filename().string().c_str()); + TerminateProcess(pi.hProcess, 1); + WaitForSingleObject(pi.hProcess, 5000); + timedOut = true; + exitCode = 0xFFFFFFFEu; + } + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + if (outExitCode) *outExitCode = exitCode; + if (timedOut) return RedistInstallResult::TIMED_OUT; + return (exitCode == 0 || exitCode == 3010) ? RedistInstallResult::INSTALLED + : RedistInstallResult::FAILED; +} + +static void scan_and_install_redists(const char* gameExe) { + if (!gameExe || !*gameExe) return; + std::filesystem::path gamePath(gameExe); + auto installers = collect_redist_installers(gamePath); + if (installers.empty()) { + log_line("[wn-launcher] redist scan: none found"); + return; + } + + std::vector marker; + load_installed_redists(marker); + + int installed = 0, skipped = 0, failedMarked = 0, timedOut = 0; + for (const auto& installer : installers) { + std::string abs = installer.string(); + if (marker_contains(marker, abs)) { + skipped++; + continue; + } + DWORD exitCode = 0; + log_line("[wn-launcher] redist install: %s%s", + installer.filename().string().c_str(), + redist_silent_args(installer).c_str()); + RedistInstallResult rc = run_redist_installer(installer, &exitCode); + if (rc == RedistInstallResult::INSTALLED) { + marker.push_back(abs); + installed++; + log_line("[wn-launcher] redist install: %s OK exit=%lu", + installer.filename().string().c_str(), + (unsigned long) exitCode); + } else if (rc == RedistInstallResult::TIMED_OUT) { + marker.push_back(abs); + timedOut++; + log_line("[wn-launcher] redist install: %s timed out — marking done " + "to avoid repeat hangs", installer.filename().string().c_str()); + } else { + if (exitCode == 1638 || exitCode == 1603 || exitCode == 5100) { + marker.push_back(abs); + failedMarked++; + log_line("[wn-launcher] redist install: %s exit=%lu — marking " + "done (already installed / not applicable)", + installer.filename().string().c_str(), + (unsigned long) exitCode); + } else { + log_line("[wn-launcher] redist install: %s FAILED exit=%lu", + installer.filename().string().c_str(), + (unsigned long) exitCode); + } + } + } + save_installed_redists(marker); + log_line("[wn-launcher] redist scan done: installed %d, skipped %d, " + "failed-marked %d, timed-out-unmarked %d (of %zu total)", + installed, skipped, failedMarked, timedOut, installers.size()); +} + +int main(int argc, char** argv) { + setbuf(stderr, NULL); + setbuf(stdout, NULL); + open_log(); + wn_launcher_set_log_sink(clean_shutdown_log_sink); + log_line("[wn-launcher] Steam Launcher in-process Steam launcher starting (pid=%lu tid=%lu)", + (unsigned long) GetCurrentProcessId(), + (unsigned long) GetCurrentThreadId()); + + const char* appIdStr = getenv("WN_STEAM_APPID"); + const char* user = getenv("WN_STEAM_USERNAME"); + const char* token = getenv("WN_STEAM_TOKEN"); + uint64_t steamId = env_u64("WN_STEAM_STEAMID"); + const char* gameExe = (argc > 1) ? argv[1] : NULL; + uint32_t appId = appIdStr ? (uint32_t) strtoul(appIdStr, NULL, 10) : 0; + + log_line("[wn-launcher] env appId=%u steamId=%llu user=%s exe=%s", + appId, + (unsigned long long) steamId, + user ? user : "(null)", + gameExe ? gameExe : "(null)"); + if (token && *token) { + size_t tokenLen = strlen(token); + log_line("[wn-launcher] token len=%zu prefix=%.*s suffix=%.*s", + tokenLen, tokenLen > 16 ? 16 : (int) tokenLen, token, + tokenLen > 12 ? 12 : (int) tokenLen, + tokenLen > 12 ? token + tokenLen - 12 : token); + log_token_claims(token); + } else { + log_line("[wn-launcher] token missing"); + } + if (argc <= 1 || !gameExe || !*gameExe) { + log_line("[wn-launcher] no game exe passed on argv[1]"); + return 1; + } + + const char* kSteamDir = "C:\\Program Files (x86)\\Steam"; + SetDllDirectoryA(kSteamDir); + SetCurrentDirectoryA(kSteamDir); + SetEnvironmentVariableA("SteamPath", kSteamDir); + SetEnvironmentVariableA("SteamGameId", appIdStr ? appIdStr : "0"); + SetEnvironmentVariableA("SteamAppId", appIdStr ? appIdStr : "0"); + SetEnvironmentVariableA("SteamUser", user ? user : ""); + SetEnvironmentVariableA("Steam3Master", "127.0.0.1:27036"); + SetEnvironmentVariableA("SteamClientLaunch", "1"); + SetEnvironmentVariableA("SteamNoOverlayUIDrawing", "1"); + + CreateDirectoryA("C:\\Program Files (x86)", NULL); + CreateDirectoryA(kSteamDir, NULL); + + stage_steam_config(); + seed_active_process_registry(GetCurrentProcessId(), (uint32_t)(steamId & 0xFFFFFFFFu)); + stage_app_manifest(appId, gameExe); + + const char* preloadDlls[] = { + "tier0_s64.dll", + "vstdlib_s64.dll", + "crashhandler64.dll", + "steamservice.dll", + }; + for (const char* dll : preloadDlls) { + char path[MAX_PATH]; + snprintf(path, sizeof(path), "%s\\%s", kSteamDir, dll); + HMODULE dm = LoadLibraryExA(path, NULL, LOAD_WITH_ALTERED_SEARCH_PATH); + if (dm) { + log_line("[wn-launcher] preload %s: ok (%p)", dll, dm); + } else { + log_line("[wn-launcher] preload %s: FAIL GLE=%lu", dll, GetLastError()); + } + } + + log_line("[wn-launcher] preloads done; installing unhandled-exception filter"); + LPTOP_LEVEL_EXCEPTION_FILTER prevFilter = + SetUnhandledExceptionFilter(launcher_unhandled_filter); + log_line("[wn-launcher] UEF installed (prev=%p)", prevFilter); + dump_loaded_modules("pre-LoadLibrary"); + + char steamclientPath[MAX_PATH]; + snprintf(steamclientPath, sizeof(steamclientPath), + "%s\\steamclient64.dll", kSteamDir); + + struct LoadAttempt { DWORD flags; const char* desc; }; + const LoadAttempt attempts[] = { + { LOAD_WITH_ALTERED_SEARCH_PATH, "LOAD_WITH_ALTERED_SEARCH_PATH" }, + { LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS, + "DLL_LOAD_DIR|DEFAULT_DIRS" }, + { LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_SYSTEM32, + "DLL_LOAD_DIR|SYSTEM32" }, + { LOAD_IGNORE_CODE_AUTHZ_LEVEL | LOAD_WITH_ALTERED_SEARCH_PATH, + "IGNORE_CODE_AUTHZ|ALTERED_SEARCH_PATH" }, + }; + + const int kAttempts = (int)(sizeof(attempts) / sizeof(attempts[0])); + HMODULE lsc = NULL; + DWORD lastErr = 0; + for (int i = 0; i < kAttempts && !lsc; i++) { + lsc = LoadLibraryExA(steamclientPath, NULL, attempts[i].flags); + if (lsc) { + log_line("[wn-launcher] steamclient64.dll loaded at %p " + "(strategy %d/%d: %s)", + lsc, i + 1, kAttempts, attempts[i].desc); + break; + } + lastErr = GetLastError(); + log_line("[wn-launcher] steamclient64.dll load fail strategy %d/%d (%s) " + "GLE=%lu", + i + 1, kAttempts, attempts[i].desc, lastErr); + Sleep(50); + } + for (int round = 0; round < 3 && !lsc; round++) { + log_line("[wn-launcher] steamclient64.dll cold-start retry " + "round %d/3 after 500ms", round + 1); + Sleep(500); + for (int i = 0; i < kAttempts && !lsc; i++) { + lsc = LoadLibraryExA(steamclientPath, NULL, attempts[i].flags); + if (!lsc) lastErr = GetLastError(); + } + if (lsc) { + log_line("[wn-launcher] steamclient64.dll loaded at %p " + "(retry round %d)", lsc, round + 1); + } + } + if (!lsc) { + lsc = LoadLibraryA(steamclientPath); + if (lsc) { + log_line("[wn-launcher] steamclient64.dll loaded at %p " + "(plain LoadLibraryA)", lsc); + } else { + lastErr = GetLastError(); + } + } + if (!lsc) { + HMODULE probe = LoadLibraryExA(steamclientPath, NULL, + LOAD_LIBRARY_AS_DATAFILE); + if (probe) { + log_line("[wn-launcher] diag: DATAFILE load OK — file is " + "well-formed; failure is in DllMain/runtime init"); + } else { + log_line("[wn-launcher] diag: DATAFILE load also FAILED, GLE=%lu", + GetLastError()); + } + log_line("[wn-launcher] LoadLibrary(%s) FAILED after all strategies, " + "last GLE=%lu", steamclientPath, lastErr); + return 2; + } + + CreateInterfaceFn createInterface = + (CreateInterfaceFn) GetProcAddress(lsc, "CreateInterface"); + Steam_CreateGlobalUser_fn createGlobalUser = + (Steam_CreateGlobalUser_fn) GetProcAddress(lsc, "Steam_CreateGlobalUser"); + Steam_BLoggedOn_fn bLoggedOn = + (Steam_BLoggedOn_fn) GetProcAddress(lsc, "Steam_BLoggedOn"); + Steam_BGetCallback_fn bGetCallback = + (Steam_BGetCallback_fn) GetProcAddress(lsc, "Steam_BGetCallback"); + Steam_FreeLastCallback_fn freeLastCallback = + (Steam_FreeLastCallback_fn) GetProcAddress(lsc, "Steam_FreeLastCallback"); + Breakpad_SteamSetAppID_fn breakpadSetAppId = + (Breakpad_SteamSetAppID_fn) GetProcAddress(lsc, "Breakpad_SteamSetAppID"); + + log_line("[wn-launcher] exports CreateInterface=%p CreateGlobalUser=%p " + "BLoggedOn=%p BGetCallback=%p FreeLastCallback=%p Breakpad=%p", + (void*) createInterface, (void*) createGlobalUser, (void*) bLoggedOn, + (void*) bGetCallback, (void*) freeLastCallback, (void*) breakpadSetAppId); + + if (!createInterface || !createGlobalUser) { + log_line("[wn-launcher] required steamclient exports missing"); + return 3; + } + + if (breakpadSetAppId && appId != 0) { + breakpadSetAppId(appId); + log_line("[wn-launcher] Breakpad_SteamSetAppID(%u)", appId); + } + + int retCode = 0; + void* engine = createInterface("CLIENTENGINE_INTERFACE_VERSION005", &retCode); + log_line("[wn-launcher] CreateInterface(CLIENTENGINE_INTERFACE_VERSION005) -> %p rc=%d", + engine, retCode); + if (!engine) { + engine = createInterface("CLIENTENGINE_INTERFACE_VERSION004", &retCode); + log_line("[wn-launcher] CreateInterface(CLIENTENGINE_INTERFACE_VERSION004) -> %p rc=%d", + engine, retCode); + } + if (!engine) { + log_line("[wn-launcher] failed to acquire IClientEngine"); + return 4; + } + + int pipe = 0; + int hUser = createGlobalUser(&pipe); + log_line("[wn-launcher] Steam_CreateGlobalUser -> pipe=%d user=%d", + pipe, hUser); + if (pipe == 0 || hUser == 0) { + log_line("[wn-launcher] invalid pipe/user from Steam_CreateGlobalUser"); + return 5; + } + + if (user && *user && token && *token && steamId != 0) { + void** engine_vt = *(void***) engine; + typedef void* (WN_THISCALL *GetIClientUserFn)(void* self, int hUser, int hPipe, const char*); + GetIClientUserFn getIClientUser = (GetIClientUserFn) + engine_vt[kVtEngine_GetIClientUser / 8]; + void* iuser = getIClientUser(engine, hUser, pipe, "CLIENTUSER_INTERFACE_VERSION001"); + log_line("[wn-launcher] IClientEngine.GetIClientUser -> %p", iuser); + if (iuser) { + void** iuser_vt = *(void***) iuser; + if (is_exec_ptr(iuser_vt[kVtUser_BHasCachedCreds / 8])) { + typedef bool (WN_THISCALL *HasCachedCredsFn)(void* self, const char*); + HasCachedCredsFn hasCachedCreds = (HasCachedCredsFn) + iuser_vt[kVtUser_BHasCachedCreds / 8]; + bool cached = hasCachedCreds(iuser, user); + log_line("[wn-launcher] BHasCachedCredentials(%s) -> %d", user, cached ? 1 : 0); + } + if (is_exec_ptr(iuser_vt[kVtUser_SetLoginToken / 8])) { + typedef int (WN_THISCALL *SetLoginTokenFn)(void* self, const char* token, + const char* account); + SetLoginTokenFn setLoginToken = (SetLoginTokenFn) + iuser_vt[kVtUser_SetLoginToken / 8]; + int tokRc = setLoginToken(iuser, token, user); + log_line("[wn-launcher] SetLoginToken(tokenLen=%d, account=%s) -> %d", + (int) strlen(token), user, tokRc); + + typedef void* (WN_THISCALL *GetSteamIDFn)(void* self, void* outBuf); + GetSteamIDFn getSteamID = (GetSteamIDFn) + iuser_vt[kVtUser_GetSteamID / 8]; + uint64_t outSid = 0; + void* sidRet = getSteamID(iuser, &outSid); + uint64_t logonSid = outSid; + if (logonSid == 0 && sidRet) logonSid = *(uint64_t*) sidRet; + if (logonSid == 0) { + logonSid = steamId; // fall back to the env-supplied SteamID + log_line("[wn-launcher] GetSteamID returned 0 — falling back " + "to env steamId=%llu", (unsigned long long) steamId); + } else { + log_line("[wn-launcher] GetSteamID -> %llu (env steamId=%llu)", + (unsigned long long) logonSid, + (unsigned long long) steamId); + } + + typedef int (WN_THISCALL *LogOnFn)(void* self, uint64_t steamID); + LogOnFn logOn = (LogOnFn) iuser_vt[kVtUser_LogOn / 8]; + int logonRc = logOn(iuser, logonSid); + log_line("[wn-launcher] LogOn(%llu) -> EResult=%d " + "(1=OK 5=InvalidPassword 15=AccessDenied 16=Timeout 84=RateLimit)", + (unsigned long long) logonSid, logonRc); + if (logonRc == 15) { + log_line("[wn-launcher] WARNING: LogOn returned AccessDenied " + "synchronously — credentials rejected pre-network"); + } + } + } + } else { + log_line("[wn-launcher] no creds — skipping refresh-token logon " + "(game may run in offline / no-auth mode)"); + } + + bool loggedOn = false; + bool cleanShutdownArmed = false; + bool sawConnected = false, sawConnFail = false; + int connFailEResult = 0; + int polls = 0; + if (bLoggedOn) { + const int kMaxPolls = 600; // 600 * 100ms = 60s + char cbBuf[64] = {0}; + for (; polls < kMaxPolls; ++polls) { + if (bGetCallback && freeLastCallback) { + while (bGetCallback(pipe, cbBuf)) { + int cbId = *(int*)(cbBuf + 4); + void* param = *(void**)(cbBuf + 8); + if (cbId == 101) { + sawConnected = true; + log_line("[wn-launcher] callback 101 SteamServersConnected"); + } else if (cbId == 102) { + sawConnFail = true; + int er = param ? *(int*)param : -1; + connFailEResult = er; + log_line("[wn-launcher] callback 102 SteamServerConnectFailure " + "EResult=%d (3=NoConnection 5=InvalidPassword " + "15=AccessDenied 16=Timeout 84=RateLimit)", er); + } else if (cbId == 103) { + int er = param ? *(int*)param : -1; + log_line("[wn-launcher] callback 103 SteamServersDisconnected " + "EResult=%d", er); + } else { + log_line("[wn-launcher] callback id=%d drained", cbId); + } + freeLastCallback(pipe); + } + } + if (bLoggedOn(pipe, hUser)) { + loggedOn = true; + log_line("[wn-launcher] Steam_BLoggedOn=true after %dx100ms", + polls + 1); + wn_launcher_arm_clean_shutdown(lsc, pipe, hUser, "C:\\wn-launcher.log"); + cleanShutdownArmed = true; + break; + } + if (sawConnFail && (connFailEResult == 5 || + connFailEResult == 15 || + connFailEResult == 84)) { + log_line("[wn-launcher] hard auth failure (EResult=%d) — " + "skipping remaining logon wait", connFailEResult); + break; + } + Sleep(100); + } + } + if (!loggedOn) { + log_line("[wn-launcher] WARNING: Steam_BLoggedOn not true after %dx100ms " + "(sawConnected=%d sawConnFail=%d) — proceeding with game launch " + "anyway (game may end up in offline mode)", + polls, sawConnected ? 1 : 0, sawConnFail ? 1 : 0); + } + + if (loggedOn && engine && appId != 0) { + void** engine_vt = *(void***) engine; + typedef void* (WN_THISCALL *GetIClientAppsFn)(void* self, int hUser, int hPipe); + GetIClientAppsFn getApps = (GetIClientAppsFn) + engine_vt[kVtEngine_GetIClientApps / 8]; + void* iApps = getApps(engine, hUser, pipe); + log_line("[wn-launcher] IClientEngine.GetIClientApps -> %p", iApps); + if (iApps) { + void** apps_vt = *(void***) iApps; + void* reqP = apps_vt[kVtApps_RequestAppInfoUpdate / 8]; + if (!is_exec_ptr(reqP)) { + log_line("[wn-launcher] RequestAppInfoUpdate slot not executable — " + "skipping appinfo refresh"); + } else { + typedef bool (WN_THISCALL *RequestAppInfoUpdateFn)(void* self, + uint32_t* appIds, int count); + RequestAppInfoUpdateFn reqInfo = (RequestAppInfoUpdateFn) reqP; + uint32_t appIds[1] = { appId }; + bool reqRc = reqInfo(iApps, appIds, 1); + log_line("[wn-launcher] RequestAppInfoUpdate(appId=%u) -> %d", + appId, reqRc ? 1 : 0); + // 1.5s for PICS appinfo to land (else LaunchApp -> MissingConfig); + // short is safe — the dispatch below retries on MissingConfig. + bool appInfoDone = false; + int waited = 0; + for (int i = 0; i < 15 && !appInfoDone; ++i) { + if (bGetCallback && freeLastCallback) { + char cb[64]; + while (bGetCallback(pipe, cb)) { + if (*(int*)(cb + 4) == 1003) appInfoDone = true; + freeLastCallback(pipe); + } + } + if (!appInfoDone) { Sleep(100); waited += 100; } + } + log_line("[wn-launcher] AppInfoUpdateComplete_t %s after %dms", + appInfoDone ? "received" : "NOT received", waited); + } + } + } + + if (loggedOn && engine && appId != 0) { + void** engine_vt = *(void***) engine; + typedef void* (WN_THISCALL *GetIfaceFn)(void* self, int hUser, int hPipe); + void* appMgr = ((GetIfaceFn) engine_vt[kVtEngine_GetIClientAppManager / 8]) + (engine, hUser, pipe); + log_line("[wn-launcher] readiness: IClientAppManager=%p", appMgr); + + if (appMgr) { + void** am_vt = *(void***) appMgr; + void* refreshP = am_vt[kVtAppMgr_RefreshAppInfo / 8]; + void* stateP = am_vt[kVtAppMgr_GetAppInstallState / 8]; + if (is_exec_ptr(refreshP)) { + typedef void (WN_THISCALL *RefreshAppInfoFn)(void* self); + ((RefreshAppInfoFn) refreshP)(appMgr); + log_line("[wn-launcher] RefreshAppInfo() called"); + } + if (is_exec_ptr(stateP)) { + typedef int (WN_THISCALL *GetAppInstallStateFn)(void* self, uint32_t app); + GetAppInstallStateFn getInstallState = (GetAppInstallStateFn) stateP; + // 2s — stage_app_manifest already wrote StateFlags=4, so this + // usually returns FullyInstalled at once; loop absorbs a slow re-parse. + int st = 0; + for (int i = 0; i < 20; ++i) { + st = getInstallState(appMgr, appId); + if (st & 4) break; + if (bGetCallback && freeLastCallback) { + char cb[64]; + while (bGetCallback(pipe, cb)) freeLastCallback(pipe); + } + Sleep(100); + } + log_line("[wn-launcher] GetAppInstallState(appId=%u) = 0x%x (%s)", + appId, st, + (st & 4) ? "FullyInstalled" + : "NOT installed — LaunchApp may no-op"); + } + } + } + + scan_and_install_redists(gameExe); + + bool svcRunning = start_steam_client_service(); + log_line("[wn-launcher] steamservice running: %d", svcRunning ? 1 : 0); + + const char* exeName = strrchr(gameExe, '\\'); + exeName = exeName ? exeName + 1 : gameExe; + + // Teardown stops the game before logoff — that exit emits games-played([]), + // which reaps the session and prevents AlreadyRunning next launch (logoff + // alone doesn't clear it). + wn_launcher_set_game_exe(exeName); + + // Pull cloud saves + set the teardown cloud context now, so the exit upload + // has a baseline to diff. + if (loggedOn && engine && appId != 0) { + wn_launcher_set_cloud_context(engine, hUser, pipe, appId); + wn_launcher_cloud_sync(engine, hUser, pipe, appId, 1, 0, 15000); + } + + bool launchedViaApp = false; + bool launchedViaFallback = false; + const char* launchFailureReason = "LaunchApp path unavailable"; + + // User override: skip LaunchApp (it would spawn the app's configured entry, not the chosen exe) and CreateProcess the selected exe directly; the Steam session is already up. + const char* directExeEnv = getenv("WN_STEAM_DIRECT_EXE"); + const bool directExe = directExeEnv && directExeEnv[0] != '\0'; + + if (directExe) { + log_line("[wn-launcher] WN_STEAM_DIRECT_EXE set — user-selected exe \"%s\"; " + "skipping Steam LaunchApp, launching directly via CreateProcess", + exeName); + launchFailureReason = "direct-exe mode (LaunchApp skipped by override)"; + } else if (engine && appId != 0) { + void** engine_vt = *(void***) engine; + typedef void* (WN_THISCALL *GetIClientAppManagerFn)(void* self, int hUser, int hPipe); + GetIClientAppManagerFn getAppMgr = (GetIClientAppManagerFn) + engine_vt[kVtEngine_GetIClientAppManager / 8]; + void* appMgr = getAppMgr(engine, hUser, pipe); + log_line("[wn-launcher] IClientEngine.GetIClientAppManager -> %p", appMgr); + if (appMgr) { + void** appMgr_vt = *(void***) appMgr; + typedef uint64_t (WN_THISCALL *LaunchAppFn)(void* self, void* pGameId, + uint32_t uLaunchOption, + uint32_t eLaunchSource, + const char* pszUserArgs); + LaunchAppFn launchApp = (LaunchAppFn) + appMgr_vt[kVtAppMgr_LaunchApp / 8]; + uint64_t gameId = (uint64_t)(appId & 0xFFFFFFu); + + // RefreshAppInfo() slot — re-primes appinfo between MissingConfig retries. + void* refreshAppInfoP = appMgr_vt[kVtAppMgr_RefreshAppInfo / 8]; + + // Cold launch may see 1-2 fast MissingConfig(9) retries; 5 stays inside + // the 35s watchdog. + const int kMaxLaunchAttempts = 5; + for (int attempt = 1; attempt <= kMaxLaunchAttempts && !launchedViaApp; ++attempt) { + uint64_t apiCall = launchApp(appMgr, &gameId, 0, 300, ""); + log_line("[wn-launcher] IClientAppManager.LaunchApp(appId=%u) " + "attempt=%d/%d -> HSteamAPICall=0x%llx", appId, + attempt, kMaxLaunchAttempts, + (unsigned long long) apiCall); + + int eAppError = -1; // -1 = not polled / unknown + if (apiCall != 0) { + typedef void* (WN_THISCALL *GetIClientUtilsFn)(void* self, int hPipe); + GetIClientUtilsFn getUtils = (GetIClientUtilsFn) + engine_vt[kVtEngine_GetIClientUtils / 8]; + void* utils = getUtils(engine, pipe); + log_line("[wn-launcher] IClientEngine.GetIClientUtils -> %p", utils); + if (utils) { + void** utils_vt = *(void***) utils; + void* isCompletedP = utils_vt[kVtUtils_IsAPICallCompleted / 8]; + void* getResultP = utils_vt[kVtUtils_GetAPICallResult / 8]; + void* getReasonP = utils_vt[kVtUtils_GetAPICallFailureReason / 8]; + log_line("[wn-launcher] utils vt IsAPICallCompleted=%p " + "GetAPICallFailureReason=%p GetAPICallResult=%p", + isCompletedP, getReasonP, getResultP); + if (is_exec_ptr(isCompletedP) && is_exec_ptr(getResultP)) { + typedef bool (WN_THISCALL *IsAPICallCompletedFn)(void* self, + uint64_t apiCall, bool* pbFailed); + typedef int (WN_THISCALL *GetFailureReasonFn)(void* self, + uint64_t apiCall); + typedef bool (WN_THISCALL *GetAPICallResultFn)(void* self, + uint64_t apiCall, void* pCb, + int cubCb, int iCbExpected, + bool* pbFailed); + IsAPICallCompletedFn isCompleted = (IsAPICallCompletedFn) isCompletedP; + GetFailureReasonFn getReason = (GetFailureReasonFn) getReasonP; + GetAPICallResultFn getResult = (GetAPICallResultFn) getResultP; + + const int kPollMaxMs = 10000; + int waited = 0; + bool failed = false; + bool completed = false; + while (waited < kPollMaxMs) { + failed = false; + completed = isCompleted(utils, apiCall, &failed); + if (completed) break; + if (bGetCallback && freeLastCallback) { + char cb[64]; + while (bGetCallback(pipe, cb)) freeLastCallback(pipe); + } + Sleep(100); + waited += 100; + } + if (!completed) { + log_line("[wn-launcher] LaunchApp poll: TIMED OUT " + "after %dms — job still pending", waited); + } else if (failed) { + int reason = is_exec_ptr(getReasonP) ? getReason(utils, apiCall) : -99; + log_line("[wn-launcher] LaunchApp poll: API CALL FAILED " + "after %dms, reason=%d " + "(-1=NoFailure 0=SteamGone 1=NetworkFailure " + "2=InvalidHandle 3=MismatchedCallback)", + waited, reason); + } else { + unsigned char buf[kLaunchAppResultSize]; + memset(buf, 0, sizeof(buf)); + bool resFailed = false; + bool got = getResult(utils, apiCall, buf, + kLaunchAppResultSize, + kLaunchAppResultCallbackId, + &resFailed); + eAppError = *(int*)(buf + kLaunchResultErrorOffset); + log_line("[wn-launcher] LaunchApp poll: COMPLETED in %dms " + "got=%d resFailed=%d EAppUpdateError=%d " + "(0=NoError 1=Unspecified 2=Paused 3=Cancelled " + "4=Suspended 5=NoSubscription 6=NoConnection " + "7=Timeout 8=MissingKey 9=MissingConfig " + "0xE=AppLocked 0xF=OtherSessionPlaying " + "0x10=AlreadyRunning 0x21=33 0x23=35 0x2D=45)", + waited, got ? 1 : 0, resFailed ? 1 : 0, eAppError); + char hex[3 * 32 + 1]; + int hp = 0; + for (int i = 0; i < 32; ++i) { + hp += snprintf(hex + hp, sizeof(hex) - hp, "%02x ", buf[i]); + } + log_line("[wn-launcher] LaunchApp result hex+0..32: %s", hex); + } + } else { + log_line("[wn-launcher] LaunchApp poll: IClientUtils vtable " + "slots not executable — skipping poll"); + } + } + } + + if (apiCall == 0) { + if (attempt < kMaxLaunchAttempts) { + log_line("[wn-launcher] LaunchApp attempt %d/%d: \"%s\" never " + "appeared — null call handle, retrying LaunchApp", + attempt, kMaxLaunchAttempts, exeName); + Sleep(500); + } else { + log_line("[wn-launcher] LaunchApp returned a null call handle " + "after %d attempts", kMaxLaunchAttempts); + launchFailureReason = "LaunchApp returned a null call handle"; + } + continue; + } + + if (eAppError == 9 /* MissingConfig */) { + // appinfo not landed — re-prime, settle, retry fast (nothing launched). + // "never appeared … retrying" wording disarms the Android watchdog. + if (is_exec_ptr(refreshAppInfoP)) { + typedef void (WN_THISCALL *RefreshAppInfoFn)(void* self); + ((RefreshAppInfoFn) refreshAppInfoP)(appMgr); + } + log_line("[wn-launcher] LaunchApp attempt %d/%d: \"%s\" never " + "appeared — MissingConfig (appinfo not ready); refreshed " + "appinfo, retrying LaunchApp", attempt, + kMaxLaunchAttempts, exeName); + for (int w = 0; w < 30; ++w) { // ~3s of callback pumping + if (bGetCallback && freeLastCallback) { + char cb[64]; + while (bGetCallback(pipe, cb)) freeLastCallback(pipe); + } + Sleep(100); + } + } else if (eAppError > 0 /* a real error, e.g. AlreadyRunning(0x10) */) { + // Not retryable in-process (AlreadyRunning = prior session's + // games-played still live server-side) — go straight to fallback. + log_line("[wn-launcher] LaunchApp attempt %d/%d: \"%s\" never " + "appeared — EAppUpdateError=%d%s; not retryable in-process " + "— falling back", attempt, kMaxLaunchAttempts, exeName, + eAppError, + eAppError == 0x10 + ? " (AlreadyRunning — prior session's games-played " + "registration still live server-side)" + : ""); + launchFailureReason = (eAppError == 0x10) + ? "LaunchApp returned AlreadyRunning (stale server session)" + : "LaunchApp returned a non-NoError EAppUpdateError"; + break; + } else { + // NoError(0)/indeterminate(-1): accepted. Wait WITHOUT re-dispatching + // — a second LaunchApp while one is pending cancels the spawn (Wine). + const int kGameAppearLoops = 40; // 40 * 500ms = 20s + log_line("[wn-launcher] LaunchApp dispatched (attempt %d/%d, " + "EAppUpdateError=%d); waiting up to %ds for \"%s\" to " + "appear (committed — no re-dispatch)", + attempt, kMaxLaunchAttempts, eAppError, + kGameAppearLoops / 2, exeName); + for (int w = 0; w < kGameAppearLoops && !launchedViaApp; ++w) { + if (count_game_processes(exeName) > 0) { + launchedViaApp = true; + break; + } + if (bGetCallback && freeLastCallback) { + char cb[64]; + while (bGetCallback(pipe, cb)) freeLastCallback(pipe); + } + Sleep(500); + } + if (launchedViaApp) { + log_line("[wn-launcher] LaunchApp: \"%s\" is running " + "(attempt %d/%d)", exeName, attempt, + kMaxLaunchAttempts); + } else { + log_line("[wn-launcher] LaunchApp attempt %d/%d: \"%s\" " + "accepted (EAppUpdateError=%d) but never spawned in " + "%ds — not re-dispatching (would cancel the pending " + "launch) — falling back", attempt, kMaxLaunchAttempts, + exeName, eAppError, kGameAppearLoops / 2); + launchFailureReason = + "LaunchApp accepted but the game never spawned"; + break; + } + } + } + } else { + launchFailureReason = "IClientAppManager was null"; + } + } else { + launchFailureReason = engine ? "appId was 0" : "IClientEngine was null"; + } + + // LaunchApp didn't bring the game up — start it directly; the "dispatched/never appeared/falling back" log markers disarm WnLauncherStatusTailer's post-dispatch watchdog. + if (!launchedViaApp) { + if (directExe) { + log_line("[wn-launcher] direct-exe mode: launching user-selected \"%s\" via " + "CreateProcess (Steam LaunchApp skipped)", exeName); + } else { + log_line("[wn-launcher] LaunchApp dispatched but \"%s\" never appeared " + "— falling back to CreateProcess (%s)", + exeName, launchFailureReason); + } + launchedViaFallback = create_process_game(gameExe, exeName); + } + + if (launchedViaApp || launchedViaFallback) { + const char* path = launchedViaApp ? "LaunchApp path" + : "CreateProcess fallback"; + log_line("[wn-launcher] watching \"%s\" for exit (%s)", exeName, path); + // Declare exit after 2 consecutive absent polls (~2s) — tolerates a brief gap. + int absent = 0; + while (absent < 2) { + Sleep(1000); + if (bGetCallback && freeLastCallback) { + char cb[64]; + while (bGetCallback(pipe, cb)) freeLastCallback(pipe); + } + absent = (count_game_processes(exeName) != 0) ? 0 : absent + 1; + } + log_line("[wn-launcher] game \"%s\" exited (%s)", exeName, path); + if (cleanShutdownArmed) { + wn_launcher_clean_shutdown_now("game-exit"); + // Block until teardown finishes so returning from main() doesn't kill + // the process mid-reap (cutting the logoff flush → AlreadyRunning). + wn_launcher_wait_clean_shutdown(12000); + } + log_line("[wn-launcher] Steam Launcher shutdown"); + return 0; + } + + log_line("[wn-launcher] could not start \"%s\" via LaunchApp or CreateProcess " + "(%s)", exeName, launchFailureReason); + if (cleanShutdownArmed) wn_launcher_clean_shutdown_now("launch-failed"); + return 9; +} diff --git a/app/src/main/cpp/wn-steamapi-bridge/.gitignore b/app/src/main/cpp/wn-steamapi-bridge/.gitignore new file mode 100644 index 000000000..567609b12 --- /dev/null +++ b/app/src/main/cpp/wn-steamapi-bridge/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/app/src/main/cpp/wn-steamapi-bridge/CMakeLists.txt b/app/src/main/cpp/wn-steamapi-bridge/CMakeLists.txt new file mode 100644 index 000000000..72774e8a5 --- /dev/null +++ b/app/src/main/cpp/wn-steamapi-bridge/CMakeLists.txt @@ -0,0 +1,42 @@ +## wn-steamapi-bridge — PE32+ x86_64 replacement steam_api64.dll. +## +## Built with the MinGW cross toolchain — NOT the Android NDK. The +## main app CMakeLists builds Linux .so artifacts (libwnsteam.so, +## libwn-libsteamclient.so) via the NDK. This DLL is consumed by Wine +## inside box64 at runtime, so it must be a Windows PE binary. Driving +## it from gradle would require dragging the MinGW toolchain into the +## NDK build, which we explicitly don't want. +## +## Build manually with build.sh in this directory; the script outputs +## the PE under build/ and copies it into +## app/src/main/assets/wnsteam/steampipe/steam_api64.dll for APK +## packaging. +## +## Keep this source list aligned with build.sh. The generated .def and +## override C sources are checked in so standalone CMake users can build +## the bridge without the legacy steam_api_bridge.c shim. + +cmake_minimum_required(VERSION 3.18) +project(wn_steamapi_bridge C) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_SHARED_LIBRARY_PREFIX "") + +if(NOT CMAKE_SYSTEM_NAME STREQUAL "Windows") + message(FATAL_ERROR + "wn-steamapi-bridge must be cross-compiled with a MinGW toolchain.\n" + "Run cmake with -DCMAKE_TOOLCHAIN_FILE=mingw64.cmake or use build.sh.") +endif() + +add_library(steam_api64 SHARED + steam_api_bridge_callbacks.c + steam_api_bridge_overrides.c + steam_api_bridge_steamclient.c + steam_api_bridge_lifecycle.c + steam_api_bridge.def +) +target_link_libraries(steam_api64 PRIVATE kernel32 user32) +target_link_options(steam_api64 PRIVATE -static-libgcc -Wl,--kill-at) +set_target_properties(steam_api64 PROPERTIES + OUTPUT_NAME "steam_api64" + SUFFIX ".dll") diff --git a/app/src/main/cpp/wn-steamapi-bridge/build.sh b/app/src/main/cpp/wn-steamapi-bridge/build.sh new file mode 100755 index 000000000..dbd44d2e2 --- /dev/null +++ b/app/src/main/cpp/wn-steamapi-bridge/build.sh @@ -0,0 +1,49 @@ +#!/bin/bash +# build.sh — cross-compile the bridge DLL and stage it into assets/. +# Standalone (no gradle/ndk); needs x86_64-w64-mingw32-gcc on PATH. +set -euo pipefail + +cd "$(dirname "$0")" +OUT="build/steam_api64.dll" +ASSET_DIR="../../assets/wnsteam/steampipe" +ASSET="$ASSET_DIR/steam_api64.dll" +GBE_SOURCE="../../../../../References/WinNative/app/src/main/assets/steampipe/steam_api64.dll" + +# Refresh gbe_fork export list → /tmp/gbe_real.txt (input to gen_forward_def.py). +if [ -f "$GBE_SOURCE" ]; then + x86_64-w64-mingw32-objdump -p "$GBE_SOURCE" 2>/dev/null \ + | awk '/\[Ordinal\/Name Pointer\] Table/,/^$/' \ + | awk 'NR>1 && /\[/{print $NF}' \ + > /tmp/gbe_real.txt +fi + +# Generate forwarders, .def forwards, and matchmaking override stubs. +python3 gen_forwarders.py +python3 gen_forward_def.py +python3 gen_overrides.py + +mkdir -p build + +# Hybrid bridge: our ~55 matchmaking overrides + .def forwards (~1200 exports) +# → original_steam_api64.dll. Omit steam_api_bridge_flat.c — gbe_fork covers the +# flat-C path and compiling it would duplicate the .def export definitions. +x86_64-w64-mingw32-gcc -shared -O2 -fvisibility=hidden \ + -o "$OUT" \ + steam_api_bridge_overrides.c \ + steam_api_bridge_callbacks.c \ + steam_api_bridge_steamclient.c \ + steam_api_bridge_lifecycle.c \ + steam_api_bridge.def \ + -static-libgcc -lkernel32 -luser32 \ + -Wl,--enable-stdcall-fixup \ + -Wl,--kill-at + +echo "[build.sh] PE built: $(ls -la "$OUT" | awk '{print $5}') bytes" + +mkdir -p "$ASSET_DIR" +cp "$OUT" "$ASSET" +echo "[build.sh] Staged: $ASSET" + +x86_64-w64-mingw32-objdump -p "$OUT" \ + | awk '/^\[Ordinal\/Name Pointer\] Table/{f=1;next} f && /^$/{exit} f{print}' \ + | head -40 diff --git a/app/src/main/cpp/wn-steamapi-bridge/gen_forward_def.py b/app/src/main/cpp/wn-steamapi-bridge/gen_forward_def.py new file mode 100755 index 000000000..07bf415a4 --- /dev/null +++ b/app/src/main/cpp/wn-steamapi-bridge/gen_forward_def.py @@ -0,0 +1,189 @@ +#!/usr/bin/env python3 +""" +gen_forward_def.py — emit a MinGW .def file that PE-export-forwards +the entire gbe_fork (Goldberg Emulator Fork) SteamAPI surface to a +sibling `original_steam_api64.dll`, EXCEPT for the matchmaking calls +that we want to handle ourselves. + +How PE forwards work: + An export entry in .edata can be a "forwarder" — a string of the + form `ModuleName.ExportName` instead of an RVA. When LoadLibrary + + GetProcAddress hits a forwarder, the Windows loader auto-loads the + named module + resolves the named export, returning that pointer + transparently. The caller doesn't see the indirection. + + MinGW .def syntax: `NewName = TargetModule.TargetExport`. The + resulting PE has its export-table entries pointing at the + forwarder string, not at any code in our bridge. + +The hybrid plan: + - We are `steam_api64.dll` in the game's install dir. + - We forward ~99% of exports to `original_steam_api64.dll` (gbe_fork + renamed at install time). + - We provide our OWN implementations for the OVERRIDE_NAMES set + below — matchmaking-family entry points that route through our + libsteamclient.so (via lsteamclient.dll → real CMClient state). + - Result: gbe_fork handles SteamAPI_Init, every non-matchmaking + flat-C call (which is most of them); we handle lobby list / + create / join → real Steam lobbies. + +Inputs: + /tmp/gbe_real.txt — line-per-export list extracted earlier from + the gbe_fork DLL via objdump (see header in build.sh). + +Output: + steam_api_bridge.def — MinGW .def file consumed by build.sh. +""" + +from __future__ import annotations +import sys +from pathlib import Path + +EXPORTS_LIST = Path("/tmp/gbe_real.txt") +OUT = Path(__file__).resolve().parent / "steam_api_bridge.def" + +# Exports our bridge implements itself (NOT forwarded to gbe_fork). +# Start narrow: only matchmaking + the lifecycle hooks that need to +# discover our overrides. Expand carefully — every name here needs a +# corresponding C implementation in steam_api_bridge.c or a code-gen +# pass. Anything in this set but missing a C impl produces a link +# error. +# +# Callback lifecycle hooks (task #163) — we own these so we can +# dual-dispatch: gbe_fork's CCallback queue + our libsteamclient.so's +# pending-callback queue (where matchmaking responses land via +# push_call_result). Initial impl just passes through to gbe_fork; +# the libsteamclient.so drain comes incrementally. +OVERRIDE_NAMES = { + "SteamAPI_RegisterCallback", + "SteamAPI_UnregisterCallback", + "SteamAPI_RegisterCallResult", + "SteamAPI_UnregisterCallResult", + "SteamAPI_RunCallbacks", + # Flat-C ISteamClient accessors for matchmaking. Steamworks.NET + # (Unity P/Invoke) calls these directly. Owning these two exports + # lets P/Invoke callers reach our libsteamclient.so matchmaking + # pointer without touching any vtable. Forest's C++ inline path + # (SteamClient()->GetISteamMatchmaking) does NOT come through + # here, so these are dead code on Forest's path — see note below. + "SteamAPI_ISteamClient_GetISteamMatchmaking", + "SteamAPI_ISteamClient_GetISteamMatchmakingServers", + # Lifecycle + SteamClient overrides — route the C++ inline path + # SteamClient()->GetISteamMatchmaking() through the wine PE bridge + # (steamclient64.dll) instead of gbe_fork. Our impl dual-inits gbe + # (for non-matchmaking forwarded exports) AND the wine bridge (for + # matchmaking / P2P). See steam_api_bridge_lifecycle.c. + "SteamClient", + "SteamAPI_Init", + "SteamAPI_InitSafe", + "SteamAPI_InitFlat", + "SteamAPI_Shutdown", + "SteamAPI_IsSteamRunning", + "SteamAPI_GetHSteamPipe", + "SteamAPI_GetHSteamUser", + "SteamAPI_RestartAppIfNecessary", + # Steam Launcher: bare global matchmaking accessors. The Steamworks SDK + # C++ header `isteammatchmaking.h` defines `inline SteamMatchmaking()` + # as a `STEAM_DEFINE_USER_INTERFACE_ACCESSOR` macro that compiles + # to an extern "C" call to the steam_api64.dll!SteamMatchmaking + # export. Forest's C++ inline path (and most Steamworks games') + # `SteamMatchmaking()->CreateLobby(...)` lands here. Forwarding + # this export to gbe_fork sent every Forest matchmaking call into + # gbe's LAN-broadcast emulator — explaining the "lobby visible on + # LAN, invisible on Internet" diagnostic. Owning the bare globals + # so they route to Valve's real in-process steamclient64.dll (see + # steam_api_bridge_steamclient.c). + "SteamMatchmaking", + "SteamMatchmakingServers", + "SteamAPI_SteamMatchmaking_v009", + "SteamAPI_SteamMatchmakingServers_v002", + # NOTE: SteamClient + SteamAPI_Init wrapper attempt (commits + # 7ce950c and following) crashed Forest. Forest takes the C++ + # inline SteamMatchmaking() path which compiles to + # SteamClient()->vtable[10], so to redirect it we'd have to own + # SteamClient and either patch gbe's vtable (corrupted gbe's + # bootstrap) or return a wrapper (Forest's downstream consumer + # hit kernelbase unwind even with an Init gate that deferred + # wrapper activation past gbe's init). Both attempts crashed with + # the same kernelbase epilogue AV. Likely root cause: our + # ISteamMatchmakingStub v009 vtable doesn't exactly match what + # Forest expects — needs verification against the real SDK header + # before another attempt. +} + +# Matchmaking surface — the whole ISteamMatchmaking flat-C family. +# These are the entry points Forest hits when the user clicks +# MULTIPLAYER → "loading lobbies". +MATCHMAKING_PREFIXES = ( + "SteamAPI_ISteamMatchmaking_", + # MatchmakingServers (server browser) — same story. + "SteamAPI_ISteamMatchmakingServers_", +) + + +def main() -> int: + if not EXPORTS_LIST.exists(): + print(f"ERROR: {EXPORTS_LIST} missing — run: " + f"x86_64-w64-mingw32-objdump -p $GBE_FORK | " + f"awk '/Name Pointer.*Table/,/^$/' | awk '/\\[/{{print $NF}}' " + f"> /tmp/gbe_real.txt", file=sys.stderr) + return 1 + + exports = [ + ln.strip() for ln in EXPORTS_LIST.read_text().splitlines() + if ln.strip() + ] + + # Compute the override set: matchmaking-family exports. + overrides = set(OVERRIDE_NAMES) + for e in exports: + for p in MATCHMAKING_PREFIXES: + if e.startswith(p): + overrides.add(e) + break + + # Sanity-check: at least the lobby-list call should be in there. + must_override = { + "SteamAPI_ISteamMatchmaking_RequestLobbyList", + "SteamAPI_ISteamMatchmaking_CreateLobby", + "SteamAPI_ISteamMatchmaking_JoinLobby", + "SteamAPI_ISteamMatchmaking_LeaveLobby", + "SteamAPI_ISteamMatchmaking_GetLobbyByIndex", + } + missing = must_override - set(exports) + if missing: + print(f"WARNING: gbe_fork doesn't export {missing} — bridge " + f"may need additional plumbing", file=sys.stderr) + + forwards = [e for e in exports if e not in overrides] + + lines = [ + "; AUTO-GENERATED by gen_forward_def.py — DO NOT EDIT.", + ";", + "; PE export forwarders → original_steam_api64.dll. The companion", + "; gbe_fork (renamed at install time) provides every non-matchmaking", + "; SteamAPI function. Our bridge's .text section only defines the", + "; matchmaking overrides listed under EXPORTS without an `=`.", + ";", + "LIBRARY steam_api64", + "EXPORTS", + ] + # Forwards first (alphabetical for readability) + for e in sorted(forwards): + lines.append(f" {e} = original_steam_api64.{e}") + # Then the overrides + lines.append(" ; --- overrides (our own impls in steam_api_bridge_overrides.c) ---") + for e in sorted(overrides): + lines.append(f" {e}") + + OUT.write_text("\n".join(lines) + "\n", encoding="utf-8") + print(f"[gen_forward_def] {len(forwards)} forwards + {len(overrides)} overrides → {OUT}", + file=sys.stderr) + print(f"[gen_forward_def] override names:", file=sys.stderr) + for e in sorted(overrides): + print(f" {e}", file=sys.stderr) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/app/src/main/cpp/wn-steamapi-bridge/gen_forwarders.py b/app/src/main/cpp/wn-steamapi-bridge/gen_forwarders.py new file mode 100755 index 000000000..ff5ded055 --- /dev/null +++ b/app/src/main/cpp/wn-steamapi-bridge/gen_forwarders.py @@ -0,0 +1,397 @@ +#!/usr/bin/env python3 +""" +gen_forwarders.py — emit SteamAPI_ISteam*_ flat-C forwarders. + +Inputs: + app/src/main/cpp/wn-libsteamclient/src/isteam_stubs.cpp + +Output: + steam_api_bridge_flat.c — one C function per virtual method per + ISteam*Stub class. Each forwarder takes `void* self` as its first + arg, casts vtable slot N to the right signature, and tail-calls. + +Approach: + 1. Locate each `class ISteamStub {` block and its matching `};`. + 2. Walk the body line-by-line. Accumulate text into a buffer until + we hit `(...)` matched parens — that's one virtual declaration. + Reset buffer at every `;` or `}` at depth 0 to keep multi-line + bodies from confusing the accumulator. + 3. Each `virtual ret name(args)` we see increments the slot counter + (sequential, matches SDK ABI ordering). +""" + +from __future__ import annotations +import os +import re +import sys +from pathlib import Path + +LSC_SRC_DIR = Path(__file__).resolve().parent.parent / "wn-libsteamclient" / "src" +# Sources walked for `virtual` decls. Each file's classes get parsed +# in source order; class-name → forwarder-prefix mapping below. +SOURCES = [ + LSC_SRC_DIR / "isteam_stubs.cpp", + LSC_SRC_DIR / "isteam_client.cpp", +] +# Map source class name (with or without Stub suffix) to the +# SteamAPI__ prefix that the SDK exports the flat-C +# under. Classes not in the map use the class name as-is (stripping +# `Stub` and `Impl` suffixes). +CLASS_NAME_OVERRIDE = { + "ISteamClientImpl": "ISteamClient", +} +OUT = Path(__file__).resolve().parent / "steam_api_bridge_flat.c" + + +def find_classes(text: str) -> list[tuple[str, int, int]]: + """Return [(class_name, body_start, body_end), ...]. Bodies span + the open `{` (exclusive) to the matching `}` (exclusive). + + Matches `class ISteamStub`, `class ISteamImpl`, and bare + `class ISteam` — different files use different suffix + conventions, but they all represent the same SDK interfaces.""" + result = [] + pattern = r"^class\s+(ISteam[A-Za-z0-9]*?)(?:Stub|Impl)?\s*\{" + seen_classes = set() + for m in re.finditer(pattern, text, re.MULTILINE): + cls = m.group(1) + cls = CLASS_NAME_OVERRIDE.get(cls + "Impl", CLASS_NAME_OVERRIDE.get(cls + "Stub", cls)) + if cls in seen_classes: + continue + seen_classes.add(cls) + start = m.end() # right after the opening { + depth = 1 + i = start + while i < len(text) and depth > 0: + ch = text[i] + if ch == "{": + depth += 1 + elif ch == "}": + depth -= 1 + if depth == 0: + break + i += 1 + result.append((cls, start, i)) + return result + + +def parens_balanced(s: str) -> int: + """Return depth at end. 0 = balanced, >0 = unclosed, <0 = extra ).""" + depth = 0 + for ch in s: + if ch == "(": + depth += 1 + elif ch == ")": + depth -= 1 + return depth + + +VIRTUAL_PROBE = re.compile(r"\bvirtual\s+") +# Capture: returnType (possibly multi-token), methodName, args (the +# substring between balanced parens). We require `virtual` to start +# the declaration; everything up to the first `(` is the return-type + +# method-name; everything between the first balanced `(...)` is args. +VIRTUAL_HEAD_RE = re.compile( + r"""^virtual\s+ + (?P[A-Za-z_][^()]*?[A-Za-z0-9_*&\s])\s*\( + """, + re.VERBOSE, +) + + +def parse_virtuals(body: str) -> list[dict]: + """Walk class body, emit one entry per virtual declaration in + source order. Bodies of inline-defined methods (`{ ... }`) are + skipped — we only care about the declaration line(s) up to the + closing `)` of the arg list, then jump past the body.""" + out = [] + i = 0 + n = len(body) + slot = 0 + while i < n: + # Find next `virtual\s+` + m = VIRTUAL_PROBE.search(body, i) + if m is None: + break + v_start = m.start() + # Sometimes "virtual" appears in a comment — check by looking + # backwards to the previous newline for `//`. + line_start = body.rfind("\n", 0, v_start) + 1 + prefix = body[line_start:v_start] + if "//" in prefix or "/*" in prefix: + i = m.end() + continue + # Find the opening `(` of the arg list — must be at depth 0 + # (no preceding `<` < ... yet, declaration text only). + j = v_start + paren_open = body.find("(", j) + if paren_open == -1: + break + # The head is body[v_start:paren_open] + head = body[v_start:paren_open] + # Now find the matching `)` for this `(`. + depth = 0 + k = paren_open + while k < n: + ch = body[k] + if ch == "(": + depth += 1 + elif ch == ")": + depth -= 1 + if depth == 0: + break + k += 1 + if k >= n: + break + args_str = body[paren_open + 1:k] + # Parse head: split off the trailing identifier as method name + head = head.strip() + # Expect head to start with `virtual\s+` + head = re.sub(r"^virtual\s+", "", head, count=1) + head = head.strip() + # Last identifier token is the method name; everything else is the return type + name_match = re.search(r"([A-Za-z_][A-Za-z0-9_]*)$", head) + if not name_match: + i = k + 1 + continue + method = name_match.group(1) + ret = head[:name_match.start()].strip() + # Filter out things that look like keywords / non-types + if not ret or ret in {"static", "explicit"}: + i = k + 1 + continue + args = split_args(args_str) + out.append({ + "slot": slot, + "ret": ret, + "name": method, + "args": [normalize_arg(a, j) for j, a in enumerate(args)], + }) + slot += 1 + # Skip past the closing `)` of the args. + i = k + 1 + # If the next non-space char is `{`, skip the entire body + # (matching braces). Otherwise (decl ends with `;`), no skip + # needed. + while i < n and body[i] in " \t\r\n": + i += 1 + if i < n and body[i] == "{": + depth = 1 + i += 1 + while i < n and depth > 0: + ch = body[i] + if ch == "{": + depth += 1 + elif ch == "}": + depth -= 1 + i += 1 + return out + + +def split_args(raw: str) -> list[str]: + out, depth, buf = [], 0, [] + for ch in raw: + if ch in "<(": + depth += 1 + elif ch in ">)": + depth -= 1 + if ch == "," and depth == 0: + out.append("".join(buf).strip()) + buf = [] + else: + buf.append(ch) + if buf: + out.append("".join(buf).strip()) + return [a for a in out if a] + + +def normalize_arg(arg: str, idx: int) -> tuple[str, str]: + arg = arg.strip() + eq = arg.find("=") + if eq != -1: + arg = arg[:eq].strip() + if not arg: + return ("void", f"_a{idx}") + # If arg ends with `*` or `&`, no name. If arg contains only + # type tokens (no trailing identifier after a space), no name. + # Heuristic: split on whitespace; if last token is identifier and + # ALSO appears in the SDK as a type, treat as type — else name. + m = re.match(r"^(.*?)([A-Za-z_][A-Za-z0-9_]*)\s*$", arg) + if not m: + return (arg, f"_a{idx}") + prefix, tail = m.group(1).rstrip(), m.group(2) + primitives = { + "void", "bool", "char", "short", "int", "long", "float", + "double", "size_t", "uint", "ulong", "ushort", + "uint8_t", "uint16_t", "uint32_t", "uint64_t", + "int8_t", "int16_t", "int32_t", "int64_t", + } + sdk_types = { + "HSteamPipe", "HSteamUser", "EResult", "AppId_t", + "CSteamID", "ISteamFriends", "HAuthTicket", "RTime32", + "HServerListRequest", "HServerQuery", + "AccountID_t", "CGameID", "DepotId_t", "SteamAPICall_t", + "PublishedFileId_t", "UGCFileWriteStreamHandle_t", + "UGCHandle_t", "UGCQueryHandle_t", "UGCUpdateHandle_t", + "FriendsGroupID_t", "SteamLeaderboard_t", + "SteamLeaderboardEntries_t", "ScreenshotHandle", + "PingLocation_t", "SteamNetConnection_t", + "SteamNetworkingMessage_t", "HSteamNetConnection", + "HSteamListenSocket", "HSteamNetPollGroup", + "SteamNetworkingPOPID", "SteamNetworkingMicroseconds", + "InputHandle_t", "ControllerHandle_t", + "ParticipantID_t", "PartyBeaconID_t", + "SteamItemDef_t", "SteamItemInstanceID_t", + "SteamAPIWarningMessageHook_t", + "ManifestId_t", "AccountType_t", "ClientUnifiedMessageHandle", + "BREAKPAD_HANDLE", "intptr_t", "ptrdiff_t", + "PartyBeaconID_t", "CCallResult", "CCallback", + "GameSearchErrorCode_t", "SteamErrMsg", "SteamAPIWarningMessageHook_t", + } + # Anything starting with E (enum) or T_t (typedef) heuristic: + is_type_word = ( + tail in primitives + or tail in sdk_types + or tail.endswith("_t") + or (len(tail) >= 2 and tail[0] == "E" and tail[1].isupper()) + or (len(tail) >= 2 and tail[0] == "C" and tail[1].isupper() and "Steam" in tail) + or (len(tail) >= 2 and tail[0] == "I" and tail[1].isupper() and "Steam" in tail) + ) + if not prefix: + # Just one token: must be the type + return (tail, f"_a{idx}") + if is_type_word: + # tail is type; full string IS the type + return (arg, f"_a{idx}") + return (prefix, tail) + + +# Map C++ types onto C-compatible types for the bridge PE. +def c_type(t: str) -> str: + t = t.strip() + # strip leading const, but keep const* as const-pointer + t = re.sub(r"\bconst\b", "", t).strip() + t = re.sub(r"\s+", " ", t) + if not t or t == "void": + return "void" + if t == "bool": + return "int" # MSVC bool ABI 1 byte; the cast preserves real layout + if "*" in t or "&" in t: + return "void*" + primitive_map = { + "char": "char", + "short": "short", + "int": "int", + "long": "long", + "size_t": "size_t", + "uint8_t": "uint8_t", "uint16_t": "uint16_t", + "uint32_t": "uint32_t", "uint64_t": "uint64_t", + "int8_t": "int8_t", "int16_t": "int16_t", + "int32_t": "int32_t", "int64_t": "int64_t", + "float": "float", "double": "double", + "unsigned": "unsigned", + "unsigned int": "unsigned int", + "unsigned long": "unsigned long", + "unsigned short": "unsigned short", + "uint": "unsigned int", + } + if t in primitive_map: + return primitive_map[t] + # All other types (enums, Steam handle typedefs, CSteamID, etc.) + # are integer-sized on x86_64 Windows ABI per Steamworks conventions. + return "uint64_t" + + +def emit_forwarder(cls: str, m: dict, emitted_names: set) -> str | None: + slot = m["slot"] + ret = c_type(m["ret"]) + name = m["name"] + fwd_name = f"SteamAPI_{cls}_{name}" + # Dedup overloads — SDK exports one symbol per overload by appending + # _0, _1, ... but our stubs collapse overloads. We emit the first + # only; symbols beyond the first get name-suffix. + base = fwd_name + suffix = 0 + while fwd_name in emitted_names: + suffix += 1 + fwd_name = f"{base}_{suffix}" + emitted_names.add(fwd_name) + cargs = [(c_type(t), n) for (t, n) in m["args"]] + # Sanitize parameter names — avoid C keywords + collisions. + seen = {"self"} + fixed = [] + for (ct, an) in cargs: + an2 = an + if an2 in seen or an2 in {"int", "long", "char", "register", "auto", "default", "new"}: + an2 = f"_p{len(fixed)}" + seen.add(an2) + fixed.append((ct, an2)) + cargs = fixed + if cargs: + params = ", ".join(f"{t} {n}" for (t, n) in cargs) + sig_params = ", ".join(t for (t, _) in cargs) + call_args = "self, " + ", ".join(n for (_, n) in cargs) + else: + params = "" + sig_params = "" + call_args = "self" + body = [] + body.append(f"WN_STEAMAPI_EXPORT {ret} {fwd_name}(void* self{', ' + params if params else ''}) {{") + if ret == "void": + body.append(f" if (self == NULL) return;") + elif ret == "void*": + body.append(f" if (self == NULL) return NULL;") + else: + body.append(f" if (self == NULL) return 0;") + body.append(f" void** vt = *(void***)self;") + body.append(f" typedef {ret} (*Fn)(void*{', ' + sig_params if cargs else ''});") + body.append(f" {'return ' if ret != 'void' else ''}((Fn)vt[{slot}])({call_args});") + body.append("}") + return "\n".join(body) + + +def main() -> int: + parsed: dict[str, list[dict]] = {} + for src in SOURCES: + if not src.exists(): + print(f"WARNING: {src} not found, skipping", file=sys.stderr) + continue + text = src.read_text(encoding="utf-8", errors="replace") + classes = find_classes(text) + for cls, s, e in classes: + if cls in parsed: + continue # first file wins + body = text[s:e] + ms = parse_virtuals(body) + parsed[cls] = ms + + total = sum(len(v) for v in parsed.values()) + print(f"[gen_forwarders] parsed {len(parsed)} classes, {total} virtual methods", file=sys.stderr) + for cls in sorted(parsed): + print(f" {cls}: {len(parsed[cls])}", file=sys.stderr) + + emitted = set() + lines = [ + "/* AUTO-GENERATED by gen_forwarders.py — DO NOT EDIT. */", + "", + "#include ", + "#include ", + "#include ", + "", + "#define WN_STEAMAPI_EXPORT __declspec(dllexport)", + "", + ] + for cls in sorted(parsed): + lines.append(f"/* === {cls} === {len(parsed[cls])} method(s) === */") + for m in parsed[cls]: + fwd = emit_forwarder(cls, m, emitted) + if fwd is not None: + lines.append(fwd) + lines.append("") + OUT.write_text("\n".join(lines), encoding="utf-8") + print(f"[gen_forwarders] wrote {OUT} ({OUT.stat().st_size} bytes, {len(emitted)} exports)", file=sys.stderr) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/app/src/main/cpp/wn-steamapi-bridge/gen_overrides.py b/app/src/main/cpp/wn-steamapi-bridge/gen_overrides.py new file mode 100755 index 000000000..719cda965 --- /dev/null +++ b/app/src/main/cpp/wn-steamapi-bridge/gen_overrides.py @@ -0,0 +1,399 @@ +#!/usr/bin/env python3 +""" +gen_overrides.py — emit C implementations for the matchmaking-family +flat-C exports that route through OUR libsteamclient.so instead of +gbe_fork's emulator state. + +The hybrid bridge forwards ~1200 SteamAPI calls to gbe_fork +(via PE export forwards in steam_api_bridge.def), but matchmaking +exports are carved out as direct exports of our bridge. Game-side +calls to e.g. SteamAPI_ISteamMatchmaking_RequestLobbyList land in +this file's implementations. + +Routing strategy: + 1. Each override lazily acquires OUR ISteamMatchmaking pointer by + LoadLibrary("steamclient64.dll") + GetProcAddress("CreateInterface") + + CreateInterface("SteamClient020") to get ISteamClient*, then + calling its vtable[10] (GetISteamMatchmaking) to get the + matchmaking interface. lsteamclient.dll wraps our libsteamclient.so's + ISteamMatchmakingStub, so the returned pointer's vtable layout + matches the SDK (and matches our isteam_stubs.cpp's virtual + declaration order). + 2. The override IGNORES the `self` arg (which would be gbe_fork's + matchmaking pointer if the game even passed it). It dispatches + via OUR cached pointer's vtable at slot N — slot N is extracted + from the existing steam_api_bridge_flat.c forwarder body, so + gen_overrides + gen_forwarders stay in sync as the source class + evolves. + 3. Per-call diagnostic logging to C:\\wnb.log via wnb_log_once stays + in place — first invocation logged once per export. + +If a specific call's vtable[N] dispatch crashes (the failure mode we +hit with the full-vtable bridge), the crash is ISOLATED to that +matchmaking method — the game's other Steam API calls continue +working via gbe_fork forwards. + +Input: + steam_api_bridge_flat.c — source of the vtable-slot numbers + the + exact signatures. + +Output: + steam_api_bridge_overrides.c — one C function per matchmaking + export, routing through OUR libsteamclient.so via cached + ISteamMatchmaking pointer. +""" + +from __future__ import annotations +import re +import sys +from pathlib import Path + +SRC = Path(__file__).resolve().parent / "steam_api_bridge_flat.c" +OUT = Path(__file__).resolve().parent / "steam_api_bridge_overrides.c" + +OVERRIDE_PREFIXES = ( + "SteamAPI_ISteamMatchmaking_", + "SteamAPI_ISteamMatchmakingServers_", +) + +# Extract the full forwarder body so we can lift the slot number from +# the existing flat-C output. The flat.c shape is: +# WN_STEAMAPI_EXPORT (void* self[, params]) { +# if (self == NULL) return ; +# void** vt = *(void***)self; +# typedef (*Fn)(void*[, params]); +# (return ?)((Fn)vt[N])(self[, args]); +# } +FUNC_RE = re.compile( + r"WN_STEAMAPI_EXPORT\s+(?P[A-Za-z0-9_*\s]+?)\s+" + r"(?PSteamAPI_[A-Za-z0-9_]+)\s*" + r"\((?P[^)]*)\)\s*\{" + r"(?P.*?)" + r"\}\s*\n", + re.DOTALL, +) +SLOT_RE = re.compile(r"vt\[(\d+)\]") + + +def default_ret(ret: str) -> str: + ret = ret.strip() + if ret == "void": + return "" + if ret == "void*": + return " return NULL;" + return " return 0;" + + +# Map override-prefix to the SDK interface version we ask for via +# CreateInterface, plus the ISteamClient vtable slot that returns it. +# The slot numbers come from our isteam_client.cpp's ISteamClientImpl: +# slot 10 = GetISteamMatchmaking(int hUser, int hPipe, const char* ver) +# slot 11 = GetISteamMatchmakingServers(int hUser, int hPipe, const char* ver) +INTERFACE_CONFIG = { + "SteamAPI_ISteamMatchmaking_": { + "version": "SteamMatchMaking009", + "vtable_slot": 10, + "cache_var": "g_our_matchmaking", + }, + "SteamAPI_ISteamMatchmakingServers_": { + "version": "SteamMatchMakingServers002", + "vtable_slot": 11, + "cache_var": "g_our_matchmaking_servers", + }, +} + + +PROLOGUE = r"""/* AUTO-GENERATED by gen_overrides.py — DO NOT EDIT. */ + +#include +#include +#include +#include + +#define WN_STEAMAPI_EXPORT __declspec(dllexport) + +static void wnb_log_once(const char* name) { + static const char* once_names[64]; + static int once_count = 0; + for (int i = 0; i < once_count; ++i) { + if (once_names[i] == name) return; + } + if (once_count < 64) { + once_names[once_count++] = name; + } + FILE* f = fopen("C:\\wnb.log", "a"); + if (f) { + fputs(name, f); + fputc('\n', f); + fclose(f); + } +} + +typedef void* (*CreateInterface_fn)(const char* pchVersion, int* pCode); +static CreateInterface_fn g_create_interface = NULL; +static void* g_steam_client = NULL; +static HMODULE g_steamclient_module = NULL; + +typedef unsigned char (*Steam_BGetCallback_fn)(int hpipe, void* pmsg); +typedef void (*Steam_FreeLastCallback_fn)(int hpipe); +typedef unsigned char (*Steam_GetAPICallResult_fn)(int hpipe, + unsigned long long hcall, + void* pcb, int cb, + int icb_expected, + unsigned char* pbfailed); +static Steam_BGetCallback_fn g_steam_bgetcallback = NULL; +static Steam_FreeLastCallback_fn g_steam_freelastcallback = NULL; +static Steam_GetAPICallResult_fn g_steam_getapicallresult = NULL; + +extern void wnb_dispatch_callback(int iCallback, const void* data, size_t data_size); +extern void wnb_dispatch_call_result(unsigned long long hAPICall, int io_failure, + const void* data, size_t data_size); +static int g_steam_pipe = 0; +static int g_steam_user = 0; + +extern void wnb_publish_dispatch_pointers(void); + +static void wnb_resolver_log(const char* msg) { + FILE* f = fopen("C:\\wnb.log", "a"); + if (f) { fputs(msg, f); fputc('\n', f); fclose(f); } +} + +static void* resolve_steam_client(void) { + if (g_steam_client != NULL) return g_steam_client; + wnb_publish_dispatch_pointers(); + SetDllDirectoryA("C:\\Program Files (x86)\\Steam"); + HMODULE sc = LoadLibraryExA( + "C:\\Program Files (x86)\\Steam\\steamclient64.dll", + NULL, LOAD_WITH_ALTERED_SEARCH_PATH); + if (sc == NULL) { + wnb_resolver_log("[wnb] LoadLibraryEx(Valve steamclient64.dll) " + "failed — falling back to bare name (gbe stub)"); + sc = LoadLibraryA("steamclient64.dll"); + } + if (sc == NULL) { + wnb_resolver_log("[wnb] LoadLibrary(steamclient64.dll) failed"); + return NULL; + } + g_steamclient_module = sc; + if (g_create_interface == NULL) { + g_create_interface = (CreateInterface_fn)GetProcAddress(sc, "CreateInterface"); + if (g_create_interface == NULL) { + wnb_resolver_log("[wnb] steamclient64.dll missing CreateInterface"); + return NULL; + } + } + g_steam_bgetcallback = (Steam_BGetCallback_fn) + GetProcAddress(sc, "Steam_BGetCallback"); + g_steam_freelastcallback = (Steam_FreeLastCallback_fn) + GetProcAddress(sc, "Steam_FreeLastCallback"); + g_steam_getapicallresult = (Steam_GetAPICallResult_fn) + GetProcAddress(sc, "Steam_GetAPICallResult"); + { + char buf[160]; + snprintf(buf, sizeof(buf), + "[wnb] callback-pump exports: BGetCallback=%p " + "FreeLastCallback=%p GetAPICallResult=%p", + (void*)g_steam_bgetcallback, + (void*)g_steam_freelastcallback, + (void*)g_steam_getapicallresult); + wnb_resolver_log(buf); + } + int code = 0; + g_steam_client = g_create_interface("SteamClient020", &code); + if (g_steam_client == NULL) g_steam_client = g_create_interface("SteamClient019", &code); + if (g_steam_client == NULL) g_steam_client = g_create_interface("SteamClient017", &code); + if (g_steam_client == NULL) { + wnb_resolver_log("[wnb] CreateInterface(SteamClient0XX) returned NULL"); + return NULL; + } + + { + void** vt = *(void***)g_steam_client; + typedef int (*CreateSteamPipe_fn)(void*); + typedef int (*ConnectToGlobalUser_fn)(void*, int); + g_steam_pipe = ((CreateSteamPipe_fn)vt[0])(g_steam_client); + if (g_steam_pipe != 0) { + g_steam_user = ((ConnectToGlobalUser_fn)vt[2])( + g_steam_client, g_steam_pipe); + } + char buf[128]; + snprintf(buf, sizeof(buf), + "[wnb] Valve ISteamClient: pipe=%d user=%d", + g_steam_pipe, g_steam_user); + wnb_resolver_log(buf); + if (g_steam_pipe == 0 || g_steam_user == 0) { + wnb_resolver_log("[wnb] WARNING: pipe/user handshake failed; " + "falling back to 1,1 (matchmaking may be empty)"); + if (g_steam_pipe == 0) g_steam_pipe = 1; + if (g_steam_user == 0) g_steam_user = 1; + } + } + return g_steam_client; +} + +static void* resolve_interface(int slot, const char* version) { + void* client = resolve_steam_client(); + if (client == NULL) return NULL; + void** vt = *(void***)client; + typedef void* (*GetIface_fn)(void*, int, int, const char*); + void* iface = ((GetIface_fn)vt[slot])( + client, g_steam_user, g_steam_pipe, version); + char buf[128]; + snprintf(buf, sizeof(buf), + "[wnb] resolve_interface slot=%d ver=%s pipe=%d user=%d -> %p", + slot, version, g_steam_pipe, g_steam_user, iface); + wnb_resolver_log(buf); + return iface; +} + +void wnb_pump_valve_callbacks(void) { + if (g_steam_client == NULL) return; /* resolver not run yet */ + if (g_steam_bgetcallback == NULL || g_steam_freelastcallback == NULL) return; + + struct CallbackMsg { int hUser; int iCallback; void* pubParam; int cubParam; }; + struct CallbackMsg msg; + int guard = 0; + while (guard++ < 512 && g_steam_bgetcallback(g_steam_pipe, &msg)) { + if (msg.iCallback == 703 /* SteamAPICallCompleted_t */) { + struct ApiCallDone { + unsigned long long hAsyncCall; + int iCallback; + unsigned cubParam; + }; + if (msg.pubParam != NULL && g_steam_getapicallresult != NULL) { + struct ApiCallDone cc = *(struct ApiCallDone*)msg.pubParam; + unsigned char payload[2048]; + int sz = (int)(cc.cubParam < sizeof(payload) + ? cc.cubParam : sizeof(payload)); + unsigned char failed = 0; + if (g_steam_getapicallresult(g_steam_pipe, cc.hAsyncCall, + payload, sz, cc.iCallback, + &failed)) { + char b[160]; + snprintf(b, sizeof(b), + "[wnb] pump: call-result hCall=%llu cb=%d " + "sz=%d failed=%d -> dispatch", + cc.hAsyncCall, cc.iCallback, sz, failed); + wnb_resolver_log(b); + wnb_dispatch_call_result(cc.hAsyncCall, failed, + payload, (size_t)sz); + } + } + } else { + char b[128]; + snprintf(b, sizeof(b), + "[wnb] pump: callback id=%d sz=%d -> dispatch", + msg.iCallback, msg.cubParam); + wnb_resolver_log(b); + wnb_dispatch_callback(msg.iCallback, msg.pubParam, + (size_t)msg.cubParam); + } + g_steam_freelastcallback(g_steam_pipe); + } +} + +static void* g_our_matchmaking = NULL; +static void* g_our_matchmaking_servers = NULL; + +void* get_our_matchmaking(void) { + if (g_our_matchmaking == NULL) + g_our_matchmaking = resolve_interface(10, "SteamMatchMaking009"); + return g_our_matchmaking; +} + +void* get_our_matchmaking_servers(void) { + if (g_our_matchmaking_servers == NULL) + g_our_matchmaking_servers = resolve_interface(11, "SteamMatchMakingServers002"); + return g_our_matchmaking_servers; +} + +""" + + +def main() -> int: + if not SRC.exists(): + print(f"ERROR: {SRC} missing — run gen_forwarders.py first", file=sys.stderr) + return 1 + text = SRC.read_text(encoding="utf-8", errors="replace") + + out_lines = [PROLOGUE] + count = 0 + for m in FUNC_RE.finditer(text): + name = m.group("name") + prefix = None + for p in OVERRIDE_PREFIXES: + if name.startswith(p): + prefix = p + break + if prefix is None: + continue + ret = m.group("ret").strip() + params = m.group("params").strip() + body = m.group("body") + slot_match = SLOT_RE.search(body) + if not slot_match: + print(f"WARNING: no vt[N] in {name}, skipping", file=sys.stderr) + continue + slot = int(slot_match.group(1)) + + # Build the params-list-without-self for forwarding the args. + # params text is like "void* self" or "void* self, int eLobbyType, ..." + # Split by `,`, drop the first (self), strip names. Reassemble + # types-only for the typedef + arg-names for the call. + plist = [p.strip() for p in params.split(",")] + if not plist or not plist[0].startswith("void* self"): + print(f"WARNING: {name} first param isn't `void* self`, skipping", file=sys.stderr) + continue + extra = plist[1:] # everything after self + extra_types = [] + extra_names = [] + for i, p in enumerate(extra): + # parse " " — last whitespace splits + mm = re.match(r"(.*\S)\s+([A-Za-z_][A-Za-z0-9_]*)$", p) + if mm: + extra_types.append(mm.group(1).strip()) + extra_names.append(mm.group(2).strip()) + else: + # No name — synthesize + extra_types.append(p) + extra_names.append(f"_a{i}") + + cfg = INTERFACE_CONFIG[prefix] + getter = f"get_our_matchmaking()" if cfg["cache_var"] == "g_our_matchmaking" \ + else f"get_our_matchmaking_servers()" + + # Build typedef + if extra_types: + typedef = f"{ret} (*Fn)(void*, {', '.join(extra_types)})" + call_args = "mm, " + ", ".join(extra_names) + param_str = "void* self, " + ", ".join(extra) + else: + typedef = f"{ret} (*Fn)(void*)" + call_args = "mm" + param_str = "void* self" + + out_lines.append(f"WN_STEAMAPI_EXPORT {ret} {name}({param_str}) {{") + out_lines.append(f' wnb_log_once("{name}");') + out_lines.append(f" (void)self;") + out_lines.append(f" void* mm = {getter};") + if ret == "void": + out_lines.append(f" if (mm == NULL) return;") + elif ret == "void*": + out_lines.append(f" if (mm == NULL) return NULL;") + else: + out_lines.append(f" if (mm == NULL) return 0;") + out_lines.append(f" void** vt = *(void***)mm;") + out_lines.append(f" typedef {typedef};") + prefix_ret = "" if ret == "void" else "return " + out_lines.append(f" {prefix_ret}((Fn)vt[{slot}])({call_args});") + out_lines.append("}") + out_lines.append("") + count += 1 + + OUT.write_text("\n".join(out_lines), encoding="utf-8") + print(f"[gen_overrides] {count} routed-override stubs → {OUT}", file=sys.stderr) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/app/src/main/cpp/wn-steamapi-bridge/steam_api_bridge.def b/app/src/main/cpp/wn-steamapi-bridge/steam_api_bridge.def new file mode 100644 index 000000000..407f9acb2 --- /dev/null +++ b/app/src/main/cpp/wn-steamapi-bridge/steam_api_bridge.def @@ -0,0 +1,1264 @@ +; AUTO-GENERATED by gen_forward_def.py — DO NOT EDIT. +; +; PE export forwarders → original_steam_api64.dll. The companion +; gbe_fork (renamed at install time) provides every non-matchmaking +; SteamAPI function. Our bridge's .text section only defines the +; matchmaking overrides listed under EXPORTS without an `=`. +; +LIBRARY steam_api64 +EXPORTS + GetHSteamPipe = original_steam_api64.GetHSteamPipe + GetHSteamUser = original_steam_api64.GetHSteamUser + SteamAPI_GetSteamInstallPath = original_steam_api64.SteamAPI_GetSteamInstallPath + SteamAPI_ISteamAppList_GetAppBuildId = original_steam_api64.SteamAPI_ISteamAppList_GetAppBuildId + SteamAPI_ISteamAppList_GetAppInstallDir = original_steam_api64.SteamAPI_ISteamAppList_GetAppInstallDir + SteamAPI_ISteamAppList_GetAppName = original_steam_api64.SteamAPI_ISteamAppList_GetAppName + SteamAPI_ISteamAppList_GetInstalledApps = original_steam_api64.SteamAPI_ISteamAppList_GetInstalledApps + SteamAPI_ISteamAppList_GetNumInstalledApps = original_steam_api64.SteamAPI_ISteamAppList_GetNumInstalledApps + SteamAPI_ISteamApps_BGetDLCDataByIndex = original_steam_api64.SteamAPI_ISteamApps_BGetDLCDataByIndex + SteamAPI_ISteamApps_BIsAppInstalled = original_steam_api64.SteamAPI_ISteamApps_BIsAppInstalled + SteamAPI_ISteamApps_BIsCybercafe = original_steam_api64.SteamAPI_ISteamApps_BIsCybercafe + SteamAPI_ISteamApps_BIsDlcInstalled = original_steam_api64.SteamAPI_ISteamApps_BIsDlcInstalled + SteamAPI_ISteamApps_BIsLowViolence = original_steam_api64.SteamAPI_ISteamApps_BIsLowViolence + SteamAPI_ISteamApps_BIsSubscribed = original_steam_api64.SteamAPI_ISteamApps_BIsSubscribed + SteamAPI_ISteamApps_BIsSubscribedApp = original_steam_api64.SteamAPI_ISteamApps_BIsSubscribedApp + SteamAPI_ISteamApps_BIsSubscribedFromFamilySharing = original_steam_api64.SteamAPI_ISteamApps_BIsSubscribedFromFamilySharing + SteamAPI_ISteamApps_BIsSubscribedFromFreeWeekend = original_steam_api64.SteamAPI_ISteamApps_BIsSubscribedFromFreeWeekend + SteamAPI_ISteamApps_BIsTimedTrial = original_steam_api64.SteamAPI_ISteamApps_BIsTimedTrial + SteamAPI_ISteamApps_BIsVACBanned = original_steam_api64.SteamAPI_ISteamApps_BIsVACBanned + SteamAPI_ISteamApps_GetAppBuildId = original_steam_api64.SteamAPI_ISteamApps_GetAppBuildId + SteamAPI_ISteamApps_GetAppInstallDir = original_steam_api64.SteamAPI_ISteamApps_GetAppInstallDir + SteamAPI_ISteamApps_GetAppOwner = original_steam_api64.SteamAPI_ISteamApps_GetAppOwner + SteamAPI_ISteamApps_GetAvailableGameLanguages = original_steam_api64.SteamAPI_ISteamApps_GetAvailableGameLanguages + SteamAPI_ISteamApps_GetBetaInfo = original_steam_api64.SteamAPI_ISteamApps_GetBetaInfo + SteamAPI_ISteamApps_GetCurrentBetaName = original_steam_api64.SteamAPI_ISteamApps_GetCurrentBetaName + SteamAPI_ISteamApps_GetCurrentGameLanguage = original_steam_api64.SteamAPI_ISteamApps_GetCurrentGameLanguage + SteamAPI_ISteamApps_GetDLCCount = original_steam_api64.SteamAPI_ISteamApps_GetDLCCount + SteamAPI_ISteamApps_GetDlcDownloadProgress = original_steam_api64.SteamAPI_ISteamApps_GetDlcDownloadProgress + SteamAPI_ISteamApps_GetEarliestPurchaseUnixTime = original_steam_api64.SteamAPI_ISteamApps_GetEarliestPurchaseUnixTime + SteamAPI_ISteamApps_GetFileDetails = original_steam_api64.SteamAPI_ISteamApps_GetFileDetails + SteamAPI_ISteamApps_GetInstalledDepots = original_steam_api64.SteamAPI_ISteamApps_GetInstalledDepots + SteamAPI_ISteamApps_GetLaunchCommandLine = original_steam_api64.SteamAPI_ISteamApps_GetLaunchCommandLine + SteamAPI_ISteamApps_GetLaunchQueryParam = original_steam_api64.SteamAPI_ISteamApps_GetLaunchQueryParam + SteamAPI_ISteamApps_GetNumBetas = original_steam_api64.SteamAPI_ISteamApps_GetNumBetas + SteamAPI_ISteamApps_InstallDLC = original_steam_api64.SteamAPI_ISteamApps_InstallDLC + SteamAPI_ISteamApps_MarkContentCorrupt = original_steam_api64.SteamAPI_ISteamApps_MarkContentCorrupt + SteamAPI_ISteamApps_RequestAllProofOfPurchaseKeys = original_steam_api64.SteamAPI_ISteamApps_RequestAllProofOfPurchaseKeys + SteamAPI_ISteamApps_RequestAppProofOfPurchaseKey = original_steam_api64.SteamAPI_ISteamApps_RequestAppProofOfPurchaseKey + SteamAPI_ISteamApps_SetActiveBeta = original_steam_api64.SteamAPI_ISteamApps_SetActiveBeta + SteamAPI_ISteamApps_SetDlcContext = original_steam_api64.SteamAPI_ISteamApps_SetDlcContext + SteamAPI_ISteamApps_UninstallDLC = original_steam_api64.SteamAPI_ISteamApps_UninstallDLC + SteamAPI_ISteamClient_BReleaseSteamPipe = original_steam_api64.SteamAPI_ISteamClient_BReleaseSteamPipe + SteamAPI_ISteamClient_BShutdownIfAllPipesClosed = original_steam_api64.SteamAPI_ISteamClient_BShutdownIfAllPipesClosed + SteamAPI_ISteamClient_ConnectToGlobalUser = original_steam_api64.SteamAPI_ISteamClient_ConnectToGlobalUser + SteamAPI_ISteamClient_CreateLocalUser = original_steam_api64.SteamAPI_ISteamClient_CreateLocalUser + SteamAPI_ISteamClient_CreateSteamPipe = original_steam_api64.SteamAPI_ISteamClient_CreateSteamPipe + SteamAPI_ISteamClient_GetIPCCallCount = original_steam_api64.SteamAPI_ISteamClient_GetIPCCallCount + SteamAPI_ISteamClient_GetISteamAppList = original_steam_api64.SteamAPI_ISteamClient_GetISteamAppList + SteamAPI_ISteamClient_GetISteamApps = original_steam_api64.SteamAPI_ISteamClient_GetISteamApps + SteamAPI_ISteamClient_GetISteamController = original_steam_api64.SteamAPI_ISteamClient_GetISteamController + SteamAPI_ISteamClient_GetISteamFriends = original_steam_api64.SteamAPI_ISteamClient_GetISteamFriends + SteamAPI_ISteamClient_GetISteamGameSearch = original_steam_api64.SteamAPI_ISteamClient_GetISteamGameSearch + SteamAPI_ISteamClient_GetISteamGameServer = original_steam_api64.SteamAPI_ISteamClient_GetISteamGameServer + SteamAPI_ISteamClient_GetISteamGameServerStats = original_steam_api64.SteamAPI_ISteamClient_GetISteamGameServerStats + SteamAPI_ISteamClient_GetISteamGenericInterface = original_steam_api64.SteamAPI_ISteamClient_GetISteamGenericInterface + SteamAPI_ISteamClient_GetISteamHTMLSurface = original_steam_api64.SteamAPI_ISteamClient_GetISteamHTMLSurface + SteamAPI_ISteamClient_GetISteamHTTP = original_steam_api64.SteamAPI_ISteamClient_GetISteamHTTP + SteamAPI_ISteamClient_GetISteamInput = original_steam_api64.SteamAPI_ISteamClient_GetISteamInput + SteamAPI_ISteamClient_GetISteamInventory = original_steam_api64.SteamAPI_ISteamClient_GetISteamInventory + SteamAPI_ISteamClient_GetISteamMusic = original_steam_api64.SteamAPI_ISteamClient_GetISteamMusic + SteamAPI_ISteamClient_GetISteamMusicRemote = original_steam_api64.SteamAPI_ISteamClient_GetISteamMusicRemote + SteamAPI_ISteamClient_GetISteamNetworking = original_steam_api64.SteamAPI_ISteamClient_GetISteamNetworking + SteamAPI_ISteamClient_GetISteamParentalSettings = original_steam_api64.SteamAPI_ISteamClient_GetISteamParentalSettings + SteamAPI_ISteamClient_GetISteamParties = original_steam_api64.SteamAPI_ISteamClient_GetISteamParties + SteamAPI_ISteamClient_GetISteamRemotePlay = original_steam_api64.SteamAPI_ISteamClient_GetISteamRemotePlay + SteamAPI_ISteamClient_GetISteamRemoteStorage = original_steam_api64.SteamAPI_ISteamClient_GetISteamRemoteStorage + SteamAPI_ISteamClient_GetISteamScreenshots = original_steam_api64.SteamAPI_ISteamClient_GetISteamScreenshots + SteamAPI_ISteamClient_GetISteamUGC = original_steam_api64.SteamAPI_ISteamClient_GetISteamUGC + SteamAPI_ISteamClient_GetISteamUnifiedMessages = original_steam_api64.SteamAPI_ISteamClient_GetISteamUnifiedMessages + SteamAPI_ISteamClient_GetISteamUser = original_steam_api64.SteamAPI_ISteamClient_GetISteamUser + SteamAPI_ISteamClient_GetISteamUserStats = original_steam_api64.SteamAPI_ISteamClient_GetISteamUserStats + SteamAPI_ISteamClient_GetISteamUtils = original_steam_api64.SteamAPI_ISteamClient_GetISteamUtils + SteamAPI_ISteamClient_GetISteamVideo = original_steam_api64.SteamAPI_ISteamClient_GetISteamVideo + SteamAPI_ISteamClient_ReleaseUser = original_steam_api64.SteamAPI_ISteamClient_ReleaseUser + SteamAPI_ISteamClient_SetLocalIPBinding = original_steam_api64.SteamAPI_ISteamClient_SetLocalIPBinding + SteamAPI_ISteamClient_SetWarningMessageHook = original_steam_api64.SteamAPI_ISteamClient_SetWarningMessageHook + SteamAPI_ISteamController_ActivateActionSet = original_steam_api64.SteamAPI_ISteamController_ActivateActionSet + SteamAPI_ISteamController_ActivateActionSetLayer = original_steam_api64.SteamAPI_ISteamController_ActivateActionSetLayer + SteamAPI_ISteamController_DeactivateActionSetLayer = original_steam_api64.SteamAPI_ISteamController_DeactivateActionSetLayer + SteamAPI_ISteamController_DeactivateAllActionSetLayers = original_steam_api64.SteamAPI_ISteamController_DeactivateAllActionSetLayers + SteamAPI_ISteamController_GetActionOriginFromXboxOrigin = original_steam_api64.SteamAPI_ISteamController_GetActionOriginFromXboxOrigin + SteamAPI_ISteamController_GetActionSetHandle = original_steam_api64.SteamAPI_ISteamController_GetActionSetHandle + SteamAPI_ISteamController_GetActiveActionSetLayers = original_steam_api64.SteamAPI_ISteamController_GetActiveActionSetLayers + SteamAPI_ISteamController_GetAnalogActionData = original_steam_api64.SteamAPI_ISteamController_GetAnalogActionData + SteamAPI_ISteamController_GetAnalogActionHandle = original_steam_api64.SteamAPI_ISteamController_GetAnalogActionHandle + SteamAPI_ISteamController_GetAnalogActionOrigins = original_steam_api64.SteamAPI_ISteamController_GetAnalogActionOrigins + SteamAPI_ISteamController_GetConnectedControllers = original_steam_api64.SteamAPI_ISteamController_GetConnectedControllers + SteamAPI_ISteamController_GetControllerBindingRevision = original_steam_api64.SteamAPI_ISteamController_GetControllerBindingRevision + SteamAPI_ISteamController_GetControllerForGamepadIndex = original_steam_api64.SteamAPI_ISteamController_GetControllerForGamepadIndex + SteamAPI_ISteamController_GetCurrentActionSet = original_steam_api64.SteamAPI_ISteamController_GetCurrentActionSet + SteamAPI_ISteamController_GetDigitalActionData = original_steam_api64.SteamAPI_ISteamController_GetDigitalActionData + SteamAPI_ISteamController_GetDigitalActionHandle = original_steam_api64.SteamAPI_ISteamController_GetDigitalActionHandle + SteamAPI_ISteamController_GetDigitalActionOrigins = original_steam_api64.SteamAPI_ISteamController_GetDigitalActionOrigins + SteamAPI_ISteamController_GetGamepadIndexForController = original_steam_api64.SteamAPI_ISteamController_GetGamepadIndexForController + SteamAPI_ISteamController_GetGlyphForActionOrigin = original_steam_api64.SteamAPI_ISteamController_GetGlyphForActionOrigin + SteamAPI_ISteamController_GetGlyphForXboxOrigin = original_steam_api64.SteamAPI_ISteamController_GetGlyphForXboxOrigin + SteamAPI_ISteamController_GetInputTypeForHandle = original_steam_api64.SteamAPI_ISteamController_GetInputTypeForHandle + SteamAPI_ISteamController_GetMotionData = original_steam_api64.SteamAPI_ISteamController_GetMotionData + SteamAPI_ISteamController_GetStringForActionOrigin = original_steam_api64.SteamAPI_ISteamController_GetStringForActionOrigin + SteamAPI_ISteamController_GetStringForXboxOrigin = original_steam_api64.SteamAPI_ISteamController_GetStringForXboxOrigin + SteamAPI_ISteamController_Init = original_steam_api64.SteamAPI_ISteamController_Init + SteamAPI_ISteamController_RunFrame = original_steam_api64.SteamAPI_ISteamController_RunFrame + SteamAPI_ISteamController_SetLEDColor = original_steam_api64.SteamAPI_ISteamController_SetLEDColor + SteamAPI_ISteamController_ShowBindingPanel = original_steam_api64.SteamAPI_ISteamController_ShowBindingPanel + SteamAPI_ISteamController_Shutdown = original_steam_api64.SteamAPI_ISteamController_Shutdown + SteamAPI_ISteamController_StopAnalogActionMomentum = original_steam_api64.SteamAPI_ISteamController_StopAnalogActionMomentum + SteamAPI_ISteamController_TranslateActionOrigin = original_steam_api64.SteamAPI_ISteamController_TranslateActionOrigin + SteamAPI_ISteamController_TriggerHapticPulse = original_steam_api64.SteamAPI_ISteamController_TriggerHapticPulse + SteamAPI_ISteamController_TriggerRepeatedHapticPulse = original_steam_api64.SteamAPI_ISteamController_TriggerRepeatedHapticPulse + SteamAPI_ISteamController_TriggerVibration = original_steam_api64.SteamAPI_ISteamController_TriggerVibration + SteamAPI_ISteamFriends_ActivateGameOverlay = original_steam_api64.SteamAPI_ISteamFriends_ActivateGameOverlay + SteamAPI_ISteamFriends_ActivateGameOverlayInviteDialog = original_steam_api64.SteamAPI_ISteamFriends_ActivateGameOverlayInviteDialog + SteamAPI_ISteamFriends_ActivateGameOverlayInviteDialogConnectString = original_steam_api64.SteamAPI_ISteamFriends_ActivateGameOverlayInviteDialogConnectString + SteamAPI_ISteamFriends_ActivateGameOverlayRemotePlayTogetherInviteDialog = original_steam_api64.SteamAPI_ISteamFriends_ActivateGameOverlayRemotePlayTogetherInviteDialog + SteamAPI_ISteamFriends_ActivateGameOverlayToStore = original_steam_api64.SteamAPI_ISteamFriends_ActivateGameOverlayToStore + SteamAPI_ISteamFriends_ActivateGameOverlayToUser = original_steam_api64.SteamAPI_ISteamFriends_ActivateGameOverlayToUser + SteamAPI_ISteamFriends_ActivateGameOverlayToWebPage = original_steam_api64.SteamAPI_ISteamFriends_ActivateGameOverlayToWebPage + SteamAPI_ISteamFriends_BHasEquippedProfileItem = original_steam_api64.SteamAPI_ISteamFriends_BHasEquippedProfileItem + SteamAPI_ISteamFriends_ClearRichPresence = original_steam_api64.SteamAPI_ISteamFriends_ClearRichPresence + SteamAPI_ISteamFriends_CloseClanChatWindowInSteam = original_steam_api64.SteamAPI_ISteamFriends_CloseClanChatWindowInSteam + SteamAPI_ISteamFriends_DownloadClanActivityCounts = original_steam_api64.SteamAPI_ISteamFriends_DownloadClanActivityCounts + SteamAPI_ISteamFriends_EnumerateFollowingList = original_steam_api64.SteamAPI_ISteamFriends_EnumerateFollowingList + SteamAPI_ISteamFriends_GetChatMemberByIndex = original_steam_api64.SteamAPI_ISteamFriends_GetChatMemberByIndex + SteamAPI_ISteamFriends_GetClanActivityCounts = original_steam_api64.SteamAPI_ISteamFriends_GetClanActivityCounts + SteamAPI_ISteamFriends_GetClanByIndex = original_steam_api64.SteamAPI_ISteamFriends_GetClanByIndex + SteamAPI_ISteamFriends_GetClanChatMemberCount = original_steam_api64.SteamAPI_ISteamFriends_GetClanChatMemberCount + SteamAPI_ISteamFriends_GetClanChatMessage = original_steam_api64.SteamAPI_ISteamFriends_GetClanChatMessage + SteamAPI_ISteamFriends_GetClanCount = original_steam_api64.SteamAPI_ISteamFriends_GetClanCount + SteamAPI_ISteamFriends_GetClanName = original_steam_api64.SteamAPI_ISteamFriends_GetClanName + SteamAPI_ISteamFriends_GetClanOfficerByIndex = original_steam_api64.SteamAPI_ISteamFriends_GetClanOfficerByIndex + SteamAPI_ISteamFriends_GetClanOfficerCount = original_steam_api64.SteamAPI_ISteamFriends_GetClanOfficerCount + SteamAPI_ISteamFriends_GetClanOwner = original_steam_api64.SteamAPI_ISteamFriends_GetClanOwner + SteamAPI_ISteamFriends_GetClanTag = original_steam_api64.SteamAPI_ISteamFriends_GetClanTag + SteamAPI_ISteamFriends_GetCoplayFriend = original_steam_api64.SteamAPI_ISteamFriends_GetCoplayFriend + SteamAPI_ISteamFriends_GetCoplayFriendCount = original_steam_api64.SteamAPI_ISteamFriends_GetCoplayFriendCount + SteamAPI_ISteamFriends_GetFollowerCount = original_steam_api64.SteamAPI_ISteamFriends_GetFollowerCount + SteamAPI_ISteamFriends_GetFriendByIndex = original_steam_api64.SteamAPI_ISteamFriends_GetFriendByIndex + SteamAPI_ISteamFriends_GetFriendCoplayGame = original_steam_api64.SteamAPI_ISteamFriends_GetFriendCoplayGame + SteamAPI_ISteamFriends_GetFriendCoplayTime = original_steam_api64.SteamAPI_ISteamFriends_GetFriendCoplayTime + SteamAPI_ISteamFriends_GetFriendCount = original_steam_api64.SteamAPI_ISteamFriends_GetFriendCount + SteamAPI_ISteamFriends_GetFriendCountFromSource = original_steam_api64.SteamAPI_ISteamFriends_GetFriendCountFromSource + SteamAPI_ISteamFriends_GetFriendFromSourceByIndex = original_steam_api64.SteamAPI_ISteamFriends_GetFriendFromSourceByIndex + SteamAPI_ISteamFriends_GetFriendGamePlayed = original_steam_api64.SteamAPI_ISteamFriends_GetFriendGamePlayed + SteamAPI_ISteamFriends_GetFriendMessage = original_steam_api64.SteamAPI_ISteamFriends_GetFriendMessage + SteamAPI_ISteamFriends_GetFriendPersonaName = original_steam_api64.SteamAPI_ISteamFriends_GetFriendPersonaName + SteamAPI_ISteamFriends_GetFriendPersonaNameHistory = original_steam_api64.SteamAPI_ISteamFriends_GetFriendPersonaNameHistory + SteamAPI_ISteamFriends_GetFriendPersonaState = original_steam_api64.SteamAPI_ISteamFriends_GetFriendPersonaState + SteamAPI_ISteamFriends_GetFriendRelationship = original_steam_api64.SteamAPI_ISteamFriends_GetFriendRelationship + SteamAPI_ISteamFriends_GetFriendRichPresence = original_steam_api64.SteamAPI_ISteamFriends_GetFriendRichPresence + SteamAPI_ISteamFriends_GetFriendRichPresenceKeyByIndex = original_steam_api64.SteamAPI_ISteamFriends_GetFriendRichPresenceKeyByIndex + SteamAPI_ISteamFriends_GetFriendRichPresenceKeyCount = original_steam_api64.SteamAPI_ISteamFriends_GetFriendRichPresenceKeyCount + SteamAPI_ISteamFriends_GetFriendSteamLevel = original_steam_api64.SteamAPI_ISteamFriends_GetFriendSteamLevel + SteamAPI_ISteamFriends_GetFriendsGroupCount = original_steam_api64.SteamAPI_ISteamFriends_GetFriendsGroupCount + SteamAPI_ISteamFriends_GetFriendsGroupIDByIndex = original_steam_api64.SteamAPI_ISteamFriends_GetFriendsGroupIDByIndex + SteamAPI_ISteamFriends_GetFriendsGroupMembersCount = original_steam_api64.SteamAPI_ISteamFriends_GetFriendsGroupMembersCount + SteamAPI_ISteamFriends_GetFriendsGroupMembersList = original_steam_api64.SteamAPI_ISteamFriends_GetFriendsGroupMembersList + SteamAPI_ISteamFriends_GetFriendsGroupName = original_steam_api64.SteamAPI_ISteamFriends_GetFriendsGroupName + SteamAPI_ISteamFriends_GetLargeFriendAvatar = original_steam_api64.SteamAPI_ISteamFriends_GetLargeFriendAvatar + SteamAPI_ISteamFriends_GetMediumFriendAvatar = original_steam_api64.SteamAPI_ISteamFriends_GetMediumFriendAvatar + SteamAPI_ISteamFriends_GetNumChatsWithUnreadPriorityMessages = original_steam_api64.SteamAPI_ISteamFriends_GetNumChatsWithUnreadPriorityMessages + SteamAPI_ISteamFriends_GetPersonaName = original_steam_api64.SteamAPI_ISteamFriends_GetPersonaName + SteamAPI_ISteamFriends_GetPersonaState = original_steam_api64.SteamAPI_ISteamFriends_GetPersonaState + SteamAPI_ISteamFriends_GetPlayerNickname = original_steam_api64.SteamAPI_ISteamFriends_GetPlayerNickname + SteamAPI_ISteamFriends_GetProfileItemPropertyString = original_steam_api64.SteamAPI_ISteamFriends_GetProfileItemPropertyString + SteamAPI_ISteamFriends_GetProfileItemPropertyUint = original_steam_api64.SteamAPI_ISteamFriends_GetProfileItemPropertyUint + SteamAPI_ISteamFriends_GetSmallFriendAvatar = original_steam_api64.SteamAPI_ISteamFriends_GetSmallFriendAvatar + SteamAPI_ISteamFriends_GetUserRestrictions = original_steam_api64.SteamAPI_ISteamFriends_GetUserRestrictions + SteamAPI_ISteamFriends_HasFriend = original_steam_api64.SteamAPI_ISteamFriends_HasFriend + SteamAPI_ISteamFriends_InviteUserToGame = original_steam_api64.SteamAPI_ISteamFriends_InviteUserToGame + SteamAPI_ISteamFriends_IsClanChatAdmin = original_steam_api64.SteamAPI_ISteamFriends_IsClanChatAdmin + SteamAPI_ISteamFriends_IsClanChatWindowOpenInSteam = original_steam_api64.SteamAPI_ISteamFriends_IsClanChatWindowOpenInSteam + SteamAPI_ISteamFriends_IsClanOfficialGameGroup = original_steam_api64.SteamAPI_ISteamFriends_IsClanOfficialGameGroup + SteamAPI_ISteamFriends_IsClanPublic = original_steam_api64.SteamAPI_ISteamFriends_IsClanPublic + SteamAPI_ISteamFriends_IsFollowing = original_steam_api64.SteamAPI_ISteamFriends_IsFollowing + SteamAPI_ISteamFriends_IsUserInSource = original_steam_api64.SteamAPI_ISteamFriends_IsUserInSource + SteamAPI_ISteamFriends_JoinClanChatRoom = original_steam_api64.SteamAPI_ISteamFriends_JoinClanChatRoom + SteamAPI_ISteamFriends_LeaveClanChatRoom = original_steam_api64.SteamAPI_ISteamFriends_LeaveClanChatRoom + SteamAPI_ISteamFriends_OpenClanChatWindowInSteam = original_steam_api64.SteamAPI_ISteamFriends_OpenClanChatWindowInSteam + SteamAPI_ISteamFriends_RegisterProtocolInOverlayBrowser = original_steam_api64.SteamAPI_ISteamFriends_RegisterProtocolInOverlayBrowser + SteamAPI_ISteamFriends_ReplyToFriendMessage = original_steam_api64.SteamAPI_ISteamFriends_ReplyToFriendMessage + SteamAPI_ISteamFriends_RequestClanOfficerList = original_steam_api64.SteamAPI_ISteamFriends_RequestClanOfficerList + SteamAPI_ISteamFriends_RequestEquippedProfileItems = original_steam_api64.SteamAPI_ISteamFriends_RequestEquippedProfileItems + SteamAPI_ISteamFriends_RequestFriendRichPresence = original_steam_api64.SteamAPI_ISteamFriends_RequestFriendRichPresence + SteamAPI_ISteamFriends_RequestUserInformation = original_steam_api64.SteamAPI_ISteamFriends_RequestUserInformation + SteamAPI_ISteamFriends_SendClanChatMessage = original_steam_api64.SteamAPI_ISteamFriends_SendClanChatMessage + SteamAPI_ISteamFriends_SetInGameVoiceSpeaking = original_steam_api64.SteamAPI_ISteamFriends_SetInGameVoiceSpeaking + SteamAPI_ISteamFriends_SetListenForFriendsMessages = original_steam_api64.SteamAPI_ISteamFriends_SetListenForFriendsMessages + SteamAPI_ISteamFriends_SetPersonaName = original_steam_api64.SteamAPI_ISteamFriends_SetPersonaName + SteamAPI_ISteamFriends_SetPlayedWith = original_steam_api64.SteamAPI_ISteamFriends_SetPlayedWith + SteamAPI_ISteamFriends_SetRichPresence = original_steam_api64.SteamAPI_ISteamFriends_SetRichPresence + SteamAPI_ISteamGameSearch_AcceptGame = original_steam_api64.SteamAPI_ISteamGameSearch_AcceptGame + SteamAPI_ISteamGameSearch_AddGameSearchParams = original_steam_api64.SteamAPI_ISteamGameSearch_AddGameSearchParams + SteamAPI_ISteamGameSearch_CancelRequestPlayersForGame = original_steam_api64.SteamAPI_ISteamGameSearch_CancelRequestPlayersForGame + SteamAPI_ISteamGameSearch_DeclineGame = original_steam_api64.SteamAPI_ISteamGameSearch_DeclineGame + SteamAPI_ISteamGameSearch_EndGame = original_steam_api64.SteamAPI_ISteamGameSearch_EndGame + SteamAPI_ISteamGameSearch_EndGameSearch = original_steam_api64.SteamAPI_ISteamGameSearch_EndGameSearch + SteamAPI_ISteamGameSearch_HostConfirmGameStart = original_steam_api64.SteamAPI_ISteamGameSearch_HostConfirmGameStart + SteamAPI_ISteamGameSearch_RequestPlayersForGame = original_steam_api64.SteamAPI_ISteamGameSearch_RequestPlayersForGame + SteamAPI_ISteamGameSearch_RetrieveConnectionDetails = original_steam_api64.SteamAPI_ISteamGameSearch_RetrieveConnectionDetails + SteamAPI_ISteamGameSearch_SearchForGameSolo = original_steam_api64.SteamAPI_ISteamGameSearch_SearchForGameSolo + SteamAPI_ISteamGameSearch_SearchForGameWithLobby = original_steam_api64.SteamAPI_ISteamGameSearch_SearchForGameWithLobby + SteamAPI_ISteamGameSearch_SetConnectionDetails = original_steam_api64.SteamAPI_ISteamGameSearch_SetConnectionDetails + SteamAPI_ISteamGameSearch_SetGameHostParams = original_steam_api64.SteamAPI_ISteamGameSearch_SetGameHostParams + SteamAPI_ISteamGameSearch_SubmitPlayerResult = original_steam_api64.SteamAPI_ISteamGameSearch_SubmitPlayerResult + SteamAPI_ISteamGameServerStats_ClearUserAchievement = original_steam_api64.SteamAPI_ISteamGameServerStats_ClearUserAchievement + SteamAPI_ISteamGameServerStats_GetUserAchievement = original_steam_api64.SteamAPI_ISteamGameServerStats_GetUserAchievement + SteamAPI_ISteamGameServerStats_GetUserStat = original_steam_api64.SteamAPI_ISteamGameServerStats_GetUserStat + SteamAPI_ISteamGameServerStats_GetUserStat0 = original_steam_api64.SteamAPI_ISteamGameServerStats_GetUserStat0 + SteamAPI_ISteamGameServerStats_GetUserStatFloat = original_steam_api64.SteamAPI_ISteamGameServerStats_GetUserStatFloat + SteamAPI_ISteamGameServerStats_GetUserStatInt32 = original_steam_api64.SteamAPI_ISteamGameServerStats_GetUserStatInt32 + SteamAPI_ISteamGameServerStats_RequestUserStats = original_steam_api64.SteamAPI_ISteamGameServerStats_RequestUserStats + SteamAPI_ISteamGameServerStats_SetUserAchievement = original_steam_api64.SteamAPI_ISteamGameServerStats_SetUserAchievement + SteamAPI_ISteamGameServerStats_SetUserStat = original_steam_api64.SteamAPI_ISteamGameServerStats_SetUserStat + SteamAPI_ISteamGameServerStats_SetUserStat0 = original_steam_api64.SteamAPI_ISteamGameServerStats_SetUserStat0 + SteamAPI_ISteamGameServerStats_SetUserStatFloat = original_steam_api64.SteamAPI_ISteamGameServerStats_SetUserStatFloat + SteamAPI_ISteamGameServerStats_SetUserStatInt32 = original_steam_api64.SteamAPI_ISteamGameServerStats_SetUserStatInt32 + SteamAPI_ISteamGameServerStats_StoreUserStats = original_steam_api64.SteamAPI_ISteamGameServerStats_StoreUserStats + SteamAPI_ISteamGameServerStats_UpdateUserAvgRateStat = original_steam_api64.SteamAPI_ISteamGameServerStats_UpdateUserAvgRateStat + SteamAPI_ISteamGameServer_AssociateWithClan = original_steam_api64.SteamAPI_ISteamGameServer_AssociateWithClan + SteamAPI_ISteamGameServer_BLoggedOn = original_steam_api64.SteamAPI_ISteamGameServer_BLoggedOn + SteamAPI_ISteamGameServer_BSecure = original_steam_api64.SteamAPI_ISteamGameServer_BSecure + SteamAPI_ISteamGameServer_BUpdateUserData = original_steam_api64.SteamAPI_ISteamGameServer_BUpdateUserData + SteamAPI_ISteamGameServer_BeginAuthSession = original_steam_api64.SteamAPI_ISteamGameServer_BeginAuthSession + SteamAPI_ISteamGameServer_CancelAuthTicket = original_steam_api64.SteamAPI_ISteamGameServer_CancelAuthTicket + SteamAPI_ISteamGameServer_ClearAllKeyValues = original_steam_api64.SteamAPI_ISteamGameServer_ClearAllKeyValues + SteamAPI_ISteamGameServer_ComputeNewPlayerCompatibility = original_steam_api64.SteamAPI_ISteamGameServer_ComputeNewPlayerCompatibility + SteamAPI_ISteamGameServer_CreateUnauthenticatedUserConnection = original_steam_api64.SteamAPI_ISteamGameServer_CreateUnauthenticatedUserConnection + SteamAPI_ISteamGameServer_EnableHeartbeats = original_steam_api64.SteamAPI_ISteamGameServer_EnableHeartbeats + SteamAPI_ISteamGameServer_EndAuthSession = original_steam_api64.SteamAPI_ISteamGameServer_EndAuthSession + SteamAPI_ISteamGameServer_ForceHeartbeat = original_steam_api64.SteamAPI_ISteamGameServer_ForceHeartbeat + SteamAPI_ISteamGameServer_GetAuthSessionTicket = original_steam_api64.SteamAPI_ISteamGameServer_GetAuthSessionTicket + SteamAPI_ISteamGameServer_GetGameplayStats = original_steam_api64.SteamAPI_ISteamGameServer_GetGameplayStats + SteamAPI_ISteamGameServer_GetNextOutgoingPacket = original_steam_api64.SteamAPI_ISteamGameServer_GetNextOutgoingPacket + SteamAPI_ISteamGameServer_GetPublicIP = original_steam_api64.SteamAPI_ISteamGameServer_GetPublicIP + SteamAPI_ISteamGameServer_GetServerReputation = original_steam_api64.SteamAPI_ISteamGameServer_GetServerReputation + SteamAPI_ISteamGameServer_GetSteamID = original_steam_api64.SteamAPI_ISteamGameServer_GetSteamID + SteamAPI_ISteamGameServer_HandleIncomingPacket = original_steam_api64.SteamAPI_ISteamGameServer_HandleIncomingPacket + SteamAPI_ISteamGameServer_InitGameServer = original_steam_api64.SteamAPI_ISteamGameServer_InitGameServer + SteamAPI_ISteamGameServer_LogOff = original_steam_api64.SteamAPI_ISteamGameServer_LogOff + SteamAPI_ISteamGameServer_LogOn = original_steam_api64.SteamAPI_ISteamGameServer_LogOn + SteamAPI_ISteamGameServer_LogOnAnonymous = original_steam_api64.SteamAPI_ISteamGameServer_LogOnAnonymous + SteamAPI_ISteamGameServer_RequestUserGroupStatus = original_steam_api64.SteamAPI_ISteamGameServer_RequestUserGroupStatus + SteamAPI_ISteamGameServer_SendUserConnectAndAuthenticate = original_steam_api64.SteamAPI_ISteamGameServer_SendUserConnectAndAuthenticate + SteamAPI_ISteamGameServer_SendUserConnectAndAuthenticate_DEPRECATED = original_steam_api64.SteamAPI_ISteamGameServer_SendUserConnectAndAuthenticate_DEPRECATED + SteamAPI_ISteamGameServer_SendUserDisconnect = original_steam_api64.SteamAPI_ISteamGameServer_SendUserDisconnect + SteamAPI_ISteamGameServer_SendUserDisconnect_DEPRECATED = original_steam_api64.SteamAPI_ISteamGameServer_SendUserDisconnect_DEPRECATED + SteamAPI_ISteamGameServer_SetAdvertiseServerActive = original_steam_api64.SteamAPI_ISteamGameServer_SetAdvertiseServerActive + SteamAPI_ISteamGameServer_SetBotPlayerCount = original_steam_api64.SteamAPI_ISteamGameServer_SetBotPlayerCount + SteamAPI_ISteamGameServer_SetDedicatedServer = original_steam_api64.SteamAPI_ISteamGameServer_SetDedicatedServer + SteamAPI_ISteamGameServer_SetGameData = original_steam_api64.SteamAPI_ISteamGameServer_SetGameData + SteamAPI_ISteamGameServer_SetGameDescription = original_steam_api64.SteamAPI_ISteamGameServer_SetGameDescription + SteamAPI_ISteamGameServer_SetGameTags = original_steam_api64.SteamAPI_ISteamGameServer_SetGameTags + SteamAPI_ISteamGameServer_SetHeartbeatInterval = original_steam_api64.SteamAPI_ISteamGameServer_SetHeartbeatInterval + SteamAPI_ISteamGameServer_SetKeyValue = original_steam_api64.SteamAPI_ISteamGameServer_SetKeyValue + SteamAPI_ISteamGameServer_SetMapName = original_steam_api64.SteamAPI_ISteamGameServer_SetMapName + SteamAPI_ISteamGameServer_SetMaxPlayerCount = original_steam_api64.SteamAPI_ISteamGameServer_SetMaxPlayerCount + SteamAPI_ISteamGameServer_SetModDir = original_steam_api64.SteamAPI_ISteamGameServer_SetModDir + SteamAPI_ISteamGameServer_SetPasswordProtected = original_steam_api64.SteamAPI_ISteamGameServer_SetPasswordProtected + SteamAPI_ISteamGameServer_SetProduct = original_steam_api64.SteamAPI_ISteamGameServer_SetProduct + SteamAPI_ISteamGameServer_SetRegion = original_steam_api64.SteamAPI_ISteamGameServer_SetRegion + SteamAPI_ISteamGameServer_SetServerName = original_steam_api64.SteamAPI_ISteamGameServer_SetServerName + SteamAPI_ISteamGameServer_SetSpectatorPort = original_steam_api64.SteamAPI_ISteamGameServer_SetSpectatorPort + SteamAPI_ISteamGameServer_SetSpectatorServerName = original_steam_api64.SteamAPI_ISteamGameServer_SetSpectatorServerName + SteamAPI_ISteamGameServer_UserHasLicenseForApp = original_steam_api64.SteamAPI_ISteamGameServer_UserHasLicenseForApp + SteamAPI_ISteamGameServer_WasRestartRequested = original_steam_api64.SteamAPI_ISteamGameServer_WasRestartRequested + SteamAPI_ISteamHTMLSurface_AddHeader = original_steam_api64.SteamAPI_ISteamHTMLSurface_AddHeader + SteamAPI_ISteamHTMLSurface_AllowStartRequest = original_steam_api64.SteamAPI_ISteamHTMLSurface_AllowStartRequest + SteamAPI_ISteamHTMLSurface_CopyToClipboard = original_steam_api64.SteamAPI_ISteamHTMLSurface_CopyToClipboard + SteamAPI_ISteamHTMLSurface_CreateBrowser = original_steam_api64.SteamAPI_ISteamHTMLSurface_CreateBrowser + SteamAPI_ISteamHTMLSurface_DestructISteamHTMLSurface = original_steam_api64.SteamAPI_ISteamHTMLSurface_DestructISteamHTMLSurface + SteamAPI_ISteamHTMLSurface_ExecuteJavascript = original_steam_api64.SteamAPI_ISteamHTMLSurface_ExecuteJavascript + SteamAPI_ISteamHTMLSurface_FileLoadDialogResponse = original_steam_api64.SteamAPI_ISteamHTMLSurface_FileLoadDialogResponse + SteamAPI_ISteamHTMLSurface_Find = original_steam_api64.SteamAPI_ISteamHTMLSurface_Find + SteamAPI_ISteamHTMLSurface_GetLinkAtPosition = original_steam_api64.SteamAPI_ISteamHTMLSurface_GetLinkAtPosition + SteamAPI_ISteamHTMLSurface_GoBack = original_steam_api64.SteamAPI_ISteamHTMLSurface_GoBack + SteamAPI_ISteamHTMLSurface_GoForward = original_steam_api64.SteamAPI_ISteamHTMLSurface_GoForward + SteamAPI_ISteamHTMLSurface_Init = original_steam_api64.SteamAPI_ISteamHTMLSurface_Init + SteamAPI_ISteamHTMLSurface_JSDialogResponse = original_steam_api64.SteamAPI_ISteamHTMLSurface_JSDialogResponse + SteamAPI_ISteamHTMLSurface_KeyChar = original_steam_api64.SteamAPI_ISteamHTMLSurface_KeyChar + SteamAPI_ISteamHTMLSurface_KeyDown = original_steam_api64.SteamAPI_ISteamHTMLSurface_KeyDown + SteamAPI_ISteamHTMLSurface_KeyUp = original_steam_api64.SteamAPI_ISteamHTMLSurface_KeyUp + SteamAPI_ISteamHTMLSurface_LoadURL = original_steam_api64.SteamAPI_ISteamHTMLSurface_LoadURL + SteamAPI_ISteamHTMLSurface_MouseDoubleClick = original_steam_api64.SteamAPI_ISteamHTMLSurface_MouseDoubleClick + SteamAPI_ISteamHTMLSurface_MouseDown = original_steam_api64.SteamAPI_ISteamHTMLSurface_MouseDown + SteamAPI_ISteamHTMLSurface_MouseMove = original_steam_api64.SteamAPI_ISteamHTMLSurface_MouseMove + SteamAPI_ISteamHTMLSurface_MouseUp = original_steam_api64.SteamAPI_ISteamHTMLSurface_MouseUp + SteamAPI_ISteamHTMLSurface_MouseWheel = original_steam_api64.SteamAPI_ISteamHTMLSurface_MouseWheel + SteamAPI_ISteamHTMLSurface_OpenDeveloperTools = original_steam_api64.SteamAPI_ISteamHTMLSurface_OpenDeveloperTools + SteamAPI_ISteamHTMLSurface_PasteFromClipboard = original_steam_api64.SteamAPI_ISteamHTMLSurface_PasteFromClipboard + SteamAPI_ISteamHTMLSurface_Reload = original_steam_api64.SteamAPI_ISteamHTMLSurface_Reload + SteamAPI_ISteamHTMLSurface_RemoveBrowser = original_steam_api64.SteamAPI_ISteamHTMLSurface_RemoveBrowser + SteamAPI_ISteamHTMLSurface_SetBackgroundMode = original_steam_api64.SteamAPI_ISteamHTMLSurface_SetBackgroundMode + SteamAPI_ISteamHTMLSurface_SetCookie = original_steam_api64.SteamAPI_ISteamHTMLSurface_SetCookie + SteamAPI_ISteamHTMLSurface_SetDPIScalingFactor = original_steam_api64.SteamAPI_ISteamHTMLSurface_SetDPIScalingFactor + SteamAPI_ISteamHTMLSurface_SetHorizontalScroll = original_steam_api64.SteamAPI_ISteamHTMLSurface_SetHorizontalScroll + SteamAPI_ISteamHTMLSurface_SetKeyFocus = original_steam_api64.SteamAPI_ISteamHTMLSurface_SetKeyFocus + SteamAPI_ISteamHTMLSurface_SetPageScaleFactor = original_steam_api64.SteamAPI_ISteamHTMLSurface_SetPageScaleFactor + SteamAPI_ISteamHTMLSurface_SetSize = original_steam_api64.SteamAPI_ISteamHTMLSurface_SetSize + SteamAPI_ISteamHTMLSurface_SetVerticalScroll = original_steam_api64.SteamAPI_ISteamHTMLSurface_SetVerticalScroll + SteamAPI_ISteamHTMLSurface_Shutdown = original_steam_api64.SteamAPI_ISteamHTMLSurface_Shutdown + SteamAPI_ISteamHTMLSurface_StopFind = original_steam_api64.SteamAPI_ISteamHTMLSurface_StopFind + SteamAPI_ISteamHTMLSurface_StopLoad = original_steam_api64.SteamAPI_ISteamHTMLSurface_StopLoad + SteamAPI_ISteamHTMLSurface_ViewSource = original_steam_api64.SteamAPI_ISteamHTMLSurface_ViewSource + SteamAPI_ISteamHTTP_CreateCookieContainer = original_steam_api64.SteamAPI_ISteamHTTP_CreateCookieContainer + SteamAPI_ISteamHTTP_CreateHTTPRequest = original_steam_api64.SteamAPI_ISteamHTTP_CreateHTTPRequest + SteamAPI_ISteamHTTP_DeferHTTPRequest = original_steam_api64.SteamAPI_ISteamHTTP_DeferHTTPRequest + SteamAPI_ISteamHTTP_GetHTTPDownloadProgressPct = original_steam_api64.SteamAPI_ISteamHTTP_GetHTTPDownloadProgressPct + SteamAPI_ISteamHTTP_GetHTTPRequestWasTimedOut = original_steam_api64.SteamAPI_ISteamHTTP_GetHTTPRequestWasTimedOut + SteamAPI_ISteamHTTP_GetHTTPResponseBodyData = original_steam_api64.SteamAPI_ISteamHTTP_GetHTTPResponseBodyData + SteamAPI_ISteamHTTP_GetHTTPResponseBodySize = original_steam_api64.SteamAPI_ISteamHTTP_GetHTTPResponseBodySize + SteamAPI_ISteamHTTP_GetHTTPResponseHeaderSize = original_steam_api64.SteamAPI_ISteamHTTP_GetHTTPResponseHeaderSize + SteamAPI_ISteamHTTP_GetHTTPResponseHeaderValue = original_steam_api64.SteamAPI_ISteamHTTP_GetHTTPResponseHeaderValue + SteamAPI_ISteamHTTP_GetHTTPStreamingResponseBodyData = original_steam_api64.SteamAPI_ISteamHTTP_GetHTTPStreamingResponseBodyData + SteamAPI_ISteamHTTP_PrioritizeHTTPRequest = original_steam_api64.SteamAPI_ISteamHTTP_PrioritizeHTTPRequest + SteamAPI_ISteamHTTP_ReleaseCookieContainer = original_steam_api64.SteamAPI_ISteamHTTP_ReleaseCookieContainer + SteamAPI_ISteamHTTP_ReleaseHTTPRequest = original_steam_api64.SteamAPI_ISteamHTTP_ReleaseHTTPRequest + SteamAPI_ISteamHTTP_SendHTTPRequest = original_steam_api64.SteamAPI_ISteamHTTP_SendHTTPRequest + SteamAPI_ISteamHTTP_SendHTTPRequestAndStreamResponse = original_steam_api64.SteamAPI_ISteamHTTP_SendHTTPRequestAndStreamResponse + SteamAPI_ISteamHTTP_SetCookie = original_steam_api64.SteamAPI_ISteamHTTP_SetCookie + SteamAPI_ISteamHTTP_SetHTTPRequestAbsoluteTimeoutMS = original_steam_api64.SteamAPI_ISteamHTTP_SetHTTPRequestAbsoluteTimeoutMS + SteamAPI_ISteamHTTP_SetHTTPRequestContextValue = original_steam_api64.SteamAPI_ISteamHTTP_SetHTTPRequestContextValue + SteamAPI_ISteamHTTP_SetHTTPRequestCookieContainer = original_steam_api64.SteamAPI_ISteamHTTP_SetHTTPRequestCookieContainer + SteamAPI_ISteamHTTP_SetHTTPRequestGetOrPostParameter = original_steam_api64.SteamAPI_ISteamHTTP_SetHTTPRequestGetOrPostParameter + SteamAPI_ISteamHTTP_SetHTTPRequestHeaderValue = original_steam_api64.SteamAPI_ISteamHTTP_SetHTTPRequestHeaderValue + SteamAPI_ISteamHTTP_SetHTTPRequestNetworkActivityTimeout = original_steam_api64.SteamAPI_ISteamHTTP_SetHTTPRequestNetworkActivityTimeout + SteamAPI_ISteamHTTP_SetHTTPRequestRawPostBody = original_steam_api64.SteamAPI_ISteamHTTP_SetHTTPRequestRawPostBody + SteamAPI_ISteamHTTP_SetHTTPRequestRequiresVerifiedCertificate = original_steam_api64.SteamAPI_ISteamHTTP_SetHTTPRequestRequiresVerifiedCertificate + SteamAPI_ISteamHTTP_SetHTTPRequestUserAgentInfo = original_steam_api64.SteamAPI_ISteamHTTP_SetHTTPRequestUserAgentInfo + SteamAPI_ISteamInput_ActivateActionSet = original_steam_api64.SteamAPI_ISteamInput_ActivateActionSet + SteamAPI_ISteamInput_ActivateActionSetLayer = original_steam_api64.SteamAPI_ISteamInput_ActivateActionSetLayer + SteamAPI_ISteamInput_BNewDataAvailable = original_steam_api64.SteamAPI_ISteamInput_BNewDataAvailable + SteamAPI_ISteamInput_BWaitForData = original_steam_api64.SteamAPI_ISteamInput_BWaitForData + SteamAPI_ISteamInput_DeactivateActionSetLayer = original_steam_api64.SteamAPI_ISteamInput_DeactivateActionSetLayer + SteamAPI_ISteamInput_DeactivateAllActionSetLayers = original_steam_api64.SteamAPI_ISteamInput_DeactivateAllActionSetLayers + SteamAPI_ISteamInput_EnableActionEventCallbacks = original_steam_api64.SteamAPI_ISteamInput_EnableActionEventCallbacks + SteamAPI_ISteamInput_EnableDeviceCallbacks = original_steam_api64.SteamAPI_ISteamInput_EnableDeviceCallbacks + SteamAPI_ISteamInput_GetActionOriginFromXboxOrigin = original_steam_api64.SteamAPI_ISteamInput_GetActionOriginFromXboxOrigin + SteamAPI_ISteamInput_GetActionSetHandle = original_steam_api64.SteamAPI_ISteamInput_GetActionSetHandle + SteamAPI_ISteamInput_GetActiveActionSetLayers = original_steam_api64.SteamAPI_ISteamInput_GetActiveActionSetLayers + SteamAPI_ISteamInput_GetAnalogActionData = original_steam_api64.SteamAPI_ISteamInput_GetAnalogActionData + SteamAPI_ISteamInput_GetAnalogActionHandle = original_steam_api64.SteamAPI_ISteamInput_GetAnalogActionHandle + SteamAPI_ISteamInput_GetAnalogActionOrigins = original_steam_api64.SteamAPI_ISteamInput_GetAnalogActionOrigins + SteamAPI_ISteamInput_GetConnectedControllers = original_steam_api64.SteamAPI_ISteamInput_GetConnectedControllers + SteamAPI_ISteamInput_GetControllerForGamepadIndex = original_steam_api64.SteamAPI_ISteamInput_GetControllerForGamepadIndex + SteamAPI_ISteamInput_GetCurrentActionSet = original_steam_api64.SteamAPI_ISteamInput_GetCurrentActionSet + SteamAPI_ISteamInput_GetDeviceBindingRevision = original_steam_api64.SteamAPI_ISteamInput_GetDeviceBindingRevision + SteamAPI_ISteamInput_GetDigitalActionData = original_steam_api64.SteamAPI_ISteamInput_GetDigitalActionData + SteamAPI_ISteamInput_GetDigitalActionHandle = original_steam_api64.SteamAPI_ISteamInput_GetDigitalActionHandle + SteamAPI_ISteamInput_GetDigitalActionOrigins = original_steam_api64.SteamAPI_ISteamInput_GetDigitalActionOrigins + SteamAPI_ISteamInput_GetGamepadIndexForController = original_steam_api64.SteamAPI_ISteamInput_GetGamepadIndexForController + SteamAPI_ISteamInput_GetGlyphForActionOrigin = original_steam_api64.SteamAPI_ISteamInput_GetGlyphForActionOrigin + SteamAPI_ISteamInput_GetGlyphForActionOrigin_Legacy = original_steam_api64.SteamAPI_ISteamInput_GetGlyphForActionOrigin_Legacy + SteamAPI_ISteamInput_GetGlyphForXboxOrigin = original_steam_api64.SteamAPI_ISteamInput_GetGlyphForXboxOrigin + SteamAPI_ISteamInput_GetGlyphPNGForActionOrigin = original_steam_api64.SteamAPI_ISteamInput_GetGlyphPNGForActionOrigin + SteamAPI_ISteamInput_GetGlyphSVGForActionOrigin = original_steam_api64.SteamAPI_ISteamInput_GetGlyphSVGForActionOrigin + SteamAPI_ISteamInput_GetInputTypeForHandle = original_steam_api64.SteamAPI_ISteamInput_GetInputTypeForHandle + SteamAPI_ISteamInput_GetMotionData = original_steam_api64.SteamAPI_ISteamInput_GetMotionData + SteamAPI_ISteamInput_GetRemotePlaySessionID = original_steam_api64.SteamAPI_ISteamInput_GetRemotePlaySessionID + SteamAPI_ISteamInput_GetSessionInputConfigurationSettings = original_steam_api64.SteamAPI_ISteamInput_GetSessionInputConfigurationSettings + SteamAPI_ISteamInput_GetStringForActionOrigin = original_steam_api64.SteamAPI_ISteamInput_GetStringForActionOrigin + SteamAPI_ISteamInput_GetStringForAnalogActionName = original_steam_api64.SteamAPI_ISteamInput_GetStringForAnalogActionName + SteamAPI_ISteamInput_GetStringForDigitalActionName = original_steam_api64.SteamAPI_ISteamInput_GetStringForDigitalActionName + SteamAPI_ISteamInput_GetStringForXboxOrigin = original_steam_api64.SteamAPI_ISteamInput_GetStringForXboxOrigin + SteamAPI_ISteamInput_Init = original_steam_api64.SteamAPI_ISteamInput_Init + SteamAPI_ISteamInput_Legacy_TriggerHapticPulse = original_steam_api64.SteamAPI_ISteamInput_Legacy_TriggerHapticPulse + SteamAPI_ISteamInput_Legacy_TriggerRepeatedHapticPulse = original_steam_api64.SteamAPI_ISteamInput_Legacy_TriggerRepeatedHapticPulse + SteamAPI_ISteamInput_RunFrame = original_steam_api64.SteamAPI_ISteamInput_RunFrame + SteamAPI_ISteamInput_SetDualSenseTriggerEffect = original_steam_api64.SteamAPI_ISteamInput_SetDualSenseTriggerEffect + SteamAPI_ISteamInput_SetInputActionManifestFilePath = original_steam_api64.SteamAPI_ISteamInput_SetInputActionManifestFilePath + SteamAPI_ISteamInput_SetLEDColor = original_steam_api64.SteamAPI_ISteamInput_SetLEDColor + SteamAPI_ISteamInput_ShowBindingPanel = original_steam_api64.SteamAPI_ISteamInput_ShowBindingPanel + SteamAPI_ISteamInput_Shutdown = original_steam_api64.SteamAPI_ISteamInput_Shutdown + SteamAPI_ISteamInput_StopAnalogActionMomentum = original_steam_api64.SteamAPI_ISteamInput_StopAnalogActionMomentum + SteamAPI_ISteamInput_TranslateActionOrigin = original_steam_api64.SteamAPI_ISteamInput_TranslateActionOrigin + SteamAPI_ISteamInput_TriggerHapticPulse = original_steam_api64.SteamAPI_ISteamInput_TriggerHapticPulse + SteamAPI_ISteamInput_TriggerRepeatedHapticPulse = original_steam_api64.SteamAPI_ISteamInput_TriggerRepeatedHapticPulse + SteamAPI_ISteamInput_TriggerSimpleHapticEvent = original_steam_api64.SteamAPI_ISteamInput_TriggerSimpleHapticEvent + SteamAPI_ISteamInput_TriggerVibration = original_steam_api64.SteamAPI_ISteamInput_TriggerVibration + SteamAPI_ISteamInput_TriggerVibrationExtended = original_steam_api64.SteamAPI_ISteamInput_TriggerVibrationExtended + SteamAPI_ISteamInventory_AddPromoItem = original_steam_api64.SteamAPI_ISteamInventory_AddPromoItem + SteamAPI_ISteamInventory_AddPromoItems = original_steam_api64.SteamAPI_ISteamInventory_AddPromoItems + SteamAPI_ISteamInventory_CheckResultSteamID = original_steam_api64.SteamAPI_ISteamInventory_CheckResultSteamID + SteamAPI_ISteamInventory_ConsumeItem = original_steam_api64.SteamAPI_ISteamInventory_ConsumeItem + SteamAPI_ISteamInventory_DeserializeResult = original_steam_api64.SteamAPI_ISteamInventory_DeserializeResult + SteamAPI_ISteamInventory_DestroyResult = original_steam_api64.SteamAPI_ISteamInventory_DestroyResult + SteamAPI_ISteamInventory_ExchangeItems = original_steam_api64.SteamAPI_ISteamInventory_ExchangeItems + SteamAPI_ISteamInventory_GenerateItems = original_steam_api64.SteamAPI_ISteamInventory_GenerateItems + SteamAPI_ISteamInventory_GetAllItems = original_steam_api64.SteamAPI_ISteamInventory_GetAllItems + SteamAPI_ISteamInventory_GetEligiblePromoItemDefinitionIDs = original_steam_api64.SteamAPI_ISteamInventory_GetEligiblePromoItemDefinitionIDs + SteamAPI_ISteamInventory_GetItemDefinitionIDs = original_steam_api64.SteamAPI_ISteamInventory_GetItemDefinitionIDs + SteamAPI_ISteamInventory_GetItemDefinitionProperty = original_steam_api64.SteamAPI_ISteamInventory_GetItemDefinitionProperty + SteamAPI_ISteamInventory_GetItemPrice = original_steam_api64.SteamAPI_ISteamInventory_GetItemPrice + SteamAPI_ISteamInventory_GetItemsByID = original_steam_api64.SteamAPI_ISteamInventory_GetItemsByID + SteamAPI_ISteamInventory_GetItemsWithPrices = original_steam_api64.SteamAPI_ISteamInventory_GetItemsWithPrices + SteamAPI_ISteamInventory_GetNumItemsWithPrices = original_steam_api64.SteamAPI_ISteamInventory_GetNumItemsWithPrices + SteamAPI_ISteamInventory_GetResultItemProperty = original_steam_api64.SteamAPI_ISteamInventory_GetResultItemProperty + SteamAPI_ISteamInventory_GetResultItems = original_steam_api64.SteamAPI_ISteamInventory_GetResultItems + SteamAPI_ISteamInventory_GetResultStatus = original_steam_api64.SteamAPI_ISteamInventory_GetResultStatus + SteamAPI_ISteamInventory_GetResultTimestamp = original_steam_api64.SteamAPI_ISteamInventory_GetResultTimestamp + SteamAPI_ISteamInventory_GrantPromoItems = original_steam_api64.SteamAPI_ISteamInventory_GrantPromoItems + SteamAPI_ISteamInventory_InspectItem = original_steam_api64.SteamAPI_ISteamInventory_InspectItem + SteamAPI_ISteamInventory_LoadItemDefinitions = original_steam_api64.SteamAPI_ISteamInventory_LoadItemDefinitions + SteamAPI_ISteamInventory_RemoveProperty = original_steam_api64.SteamAPI_ISteamInventory_RemoveProperty + SteamAPI_ISteamInventory_RequestEligiblePromoItemDefinitionsIDs = original_steam_api64.SteamAPI_ISteamInventory_RequestEligiblePromoItemDefinitionsIDs + SteamAPI_ISteamInventory_RequestPrices = original_steam_api64.SteamAPI_ISteamInventory_RequestPrices + SteamAPI_ISteamInventory_SendItemDropHeartbeat = original_steam_api64.SteamAPI_ISteamInventory_SendItemDropHeartbeat + SteamAPI_ISteamInventory_SerializeResult = original_steam_api64.SteamAPI_ISteamInventory_SerializeResult + SteamAPI_ISteamInventory_SetProperty = original_steam_api64.SteamAPI_ISteamInventory_SetProperty + SteamAPI_ISteamInventory_SetProperty0 = original_steam_api64.SteamAPI_ISteamInventory_SetProperty0 + SteamAPI_ISteamInventory_SetProperty1 = original_steam_api64.SteamAPI_ISteamInventory_SetProperty1 + SteamAPI_ISteamInventory_SetProperty2 = original_steam_api64.SteamAPI_ISteamInventory_SetProperty2 + SteamAPI_ISteamInventory_SetPropertyBool = original_steam_api64.SteamAPI_ISteamInventory_SetPropertyBool + SteamAPI_ISteamInventory_SetPropertyFloat = original_steam_api64.SteamAPI_ISteamInventory_SetPropertyFloat + SteamAPI_ISteamInventory_SetPropertyInt64 = original_steam_api64.SteamAPI_ISteamInventory_SetPropertyInt64 + SteamAPI_ISteamInventory_SetPropertyString = original_steam_api64.SteamAPI_ISteamInventory_SetPropertyString + SteamAPI_ISteamInventory_StartPurchase = original_steam_api64.SteamAPI_ISteamInventory_StartPurchase + SteamAPI_ISteamInventory_StartUpdateProperties = original_steam_api64.SteamAPI_ISteamInventory_StartUpdateProperties + SteamAPI_ISteamInventory_SubmitUpdateProperties = original_steam_api64.SteamAPI_ISteamInventory_SubmitUpdateProperties + SteamAPI_ISteamInventory_TradeItems = original_steam_api64.SteamAPI_ISteamInventory_TradeItems + SteamAPI_ISteamInventory_TransferItemQuantity = original_steam_api64.SteamAPI_ISteamInventory_TransferItemQuantity + SteamAPI_ISteamInventory_TriggerItemDrop = original_steam_api64.SteamAPI_ISteamInventory_TriggerItemDrop + SteamAPI_ISteamMatchmakingPingResponse_ServerFailedToRespond = original_steam_api64.SteamAPI_ISteamMatchmakingPingResponse_ServerFailedToRespond + SteamAPI_ISteamMatchmakingPingResponse_ServerResponded = original_steam_api64.SteamAPI_ISteamMatchmakingPingResponse_ServerResponded + SteamAPI_ISteamMatchmakingPlayersResponse_AddPlayerToList = original_steam_api64.SteamAPI_ISteamMatchmakingPlayersResponse_AddPlayerToList + SteamAPI_ISteamMatchmakingPlayersResponse_PlayersFailedToRespond = original_steam_api64.SteamAPI_ISteamMatchmakingPlayersResponse_PlayersFailedToRespond + SteamAPI_ISteamMatchmakingPlayersResponse_PlayersRefreshComplete = original_steam_api64.SteamAPI_ISteamMatchmakingPlayersResponse_PlayersRefreshComplete + SteamAPI_ISteamMatchmakingRulesResponse_RulesFailedToRespond = original_steam_api64.SteamAPI_ISteamMatchmakingRulesResponse_RulesFailedToRespond + SteamAPI_ISteamMatchmakingRulesResponse_RulesRefreshComplete = original_steam_api64.SteamAPI_ISteamMatchmakingRulesResponse_RulesRefreshComplete + SteamAPI_ISteamMatchmakingRulesResponse_RulesResponded = original_steam_api64.SteamAPI_ISteamMatchmakingRulesResponse_RulesResponded + SteamAPI_ISteamMatchmakingServerListResponse_RefreshComplete = original_steam_api64.SteamAPI_ISteamMatchmakingServerListResponse_RefreshComplete + SteamAPI_ISteamMatchmakingServerListResponse_ServerFailedToRespond = original_steam_api64.SteamAPI_ISteamMatchmakingServerListResponse_ServerFailedToRespond + SteamAPI_ISteamMatchmakingServerListResponse_ServerResponded = original_steam_api64.SteamAPI_ISteamMatchmakingServerListResponse_ServerResponded + SteamAPI_ISteamMusicRemote_BActivationSuccess = original_steam_api64.SteamAPI_ISteamMusicRemote_BActivationSuccess + SteamAPI_ISteamMusicRemote_BIsCurrentMusicRemote = original_steam_api64.SteamAPI_ISteamMusicRemote_BIsCurrentMusicRemote + SteamAPI_ISteamMusicRemote_CurrentEntryDidChange = original_steam_api64.SteamAPI_ISteamMusicRemote_CurrentEntryDidChange + SteamAPI_ISteamMusicRemote_CurrentEntryIsAvailable = original_steam_api64.SteamAPI_ISteamMusicRemote_CurrentEntryIsAvailable + SteamAPI_ISteamMusicRemote_CurrentEntryWillChange = original_steam_api64.SteamAPI_ISteamMusicRemote_CurrentEntryWillChange + SteamAPI_ISteamMusicRemote_DeregisterSteamMusicRemote = original_steam_api64.SteamAPI_ISteamMusicRemote_DeregisterSteamMusicRemote + SteamAPI_ISteamMusicRemote_EnableLooped = original_steam_api64.SteamAPI_ISteamMusicRemote_EnableLooped + SteamAPI_ISteamMusicRemote_EnablePlayNext = original_steam_api64.SteamAPI_ISteamMusicRemote_EnablePlayNext + SteamAPI_ISteamMusicRemote_EnablePlayPrevious = original_steam_api64.SteamAPI_ISteamMusicRemote_EnablePlayPrevious + SteamAPI_ISteamMusicRemote_EnablePlaylists = original_steam_api64.SteamAPI_ISteamMusicRemote_EnablePlaylists + SteamAPI_ISteamMusicRemote_EnableQueue = original_steam_api64.SteamAPI_ISteamMusicRemote_EnableQueue + SteamAPI_ISteamMusicRemote_EnableShuffled = original_steam_api64.SteamAPI_ISteamMusicRemote_EnableShuffled + SteamAPI_ISteamMusicRemote_PlaylistDidChange = original_steam_api64.SteamAPI_ISteamMusicRemote_PlaylistDidChange + SteamAPI_ISteamMusicRemote_PlaylistWillChange = original_steam_api64.SteamAPI_ISteamMusicRemote_PlaylistWillChange + SteamAPI_ISteamMusicRemote_QueueDidChange = original_steam_api64.SteamAPI_ISteamMusicRemote_QueueDidChange + SteamAPI_ISteamMusicRemote_QueueWillChange = original_steam_api64.SteamAPI_ISteamMusicRemote_QueueWillChange + SteamAPI_ISteamMusicRemote_RegisterSteamMusicRemote = original_steam_api64.SteamAPI_ISteamMusicRemote_RegisterSteamMusicRemote + SteamAPI_ISteamMusicRemote_ResetPlaylistEntries = original_steam_api64.SteamAPI_ISteamMusicRemote_ResetPlaylistEntries + SteamAPI_ISteamMusicRemote_ResetQueueEntries = original_steam_api64.SteamAPI_ISteamMusicRemote_ResetQueueEntries + SteamAPI_ISteamMusicRemote_SetCurrentPlaylistEntry = original_steam_api64.SteamAPI_ISteamMusicRemote_SetCurrentPlaylistEntry + SteamAPI_ISteamMusicRemote_SetCurrentQueueEntry = original_steam_api64.SteamAPI_ISteamMusicRemote_SetCurrentQueueEntry + SteamAPI_ISteamMusicRemote_SetDisplayName = original_steam_api64.SteamAPI_ISteamMusicRemote_SetDisplayName + SteamAPI_ISteamMusicRemote_SetPNGIcon_64x64 = original_steam_api64.SteamAPI_ISteamMusicRemote_SetPNGIcon_64x64 + SteamAPI_ISteamMusicRemote_SetPlaylistEntry = original_steam_api64.SteamAPI_ISteamMusicRemote_SetPlaylistEntry + SteamAPI_ISteamMusicRemote_SetQueueEntry = original_steam_api64.SteamAPI_ISteamMusicRemote_SetQueueEntry + SteamAPI_ISteamMusicRemote_UpdateCurrentEntryCoverArt = original_steam_api64.SteamAPI_ISteamMusicRemote_UpdateCurrentEntryCoverArt + SteamAPI_ISteamMusicRemote_UpdateCurrentEntryElapsedSeconds = original_steam_api64.SteamAPI_ISteamMusicRemote_UpdateCurrentEntryElapsedSeconds + SteamAPI_ISteamMusicRemote_UpdateCurrentEntryText = original_steam_api64.SteamAPI_ISteamMusicRemote_UpdateCurrentEntryText + SteamAPI_ISteamMusicRemote_UpdateLooped = original_steam_api64.SteamAPI_ISteamMusicRemote_UpdateLooped + SteamAPI_ISteamMusicRemote_UpdatePlaybackStatus = original_steam_api64.SteamAPI_ISteamMusicRemote_UpdatePlaybackStatus + SteamAPI_ISteamMusicRemote_UpdateShuffled = original_steam_api64.SteamAPI_ISteamMusicRemote_UpdateShuffled + SteamAPI_ISteamMusicRemote_UpdateVolume = original_steam_api64.SteamAPI_ISteamMusicRemote_UpdateVolume + SteamAPI_ISteamMusic_BIsEnabled = original_steam_api64.SteamAPI_ISteamMusic_BIsEnabled + SteamAPI_ISteamMusic_BIsPlaying = original_steam_api64.SteamAPI_ISteamMusic_BIsPlaying + SteamAPI_ISteamMusic_GetPlaybackStatus = original_steam_api64.SteamAPI_ISteamMusic_GetPlaybackStatus + SteamAPI_ISteamMusic_GetVolume = original_steam_api64.SteamAPI_ISteamMusic_GetVolume + SteamAPI_ISteamMusic_Pause = original_steam_api64.SteamAPI_ISteamMusic_Pause + SteamAPI_ISteamMusic_Play = original_steam_api64.SteamAPI_ISteamMusic_Play + SteamAPI_ISteamMusic_PlayNext = original_steam_api64.SteamAPI_ISteamMusic_PlayNext + SteamAPI_ISteamMusic_PlayPrevious = original_steam_api64.SteamAPI_ISteamMusic_PlayPrevious + SteamAPI_ISteamMusic_SetVolume = original_steam_api64.SteamAPI_ISteamMusic_SetVolume + SteamAPI_ISteamNetworkingConnectionCustomSignaling_Release = original_steam_api64.SteamAPI_ISteamNetworkingConnectionCustomSignaling_Release + SteamAPI_ISteamNetworkingConnectionCustomSignaling_SendSignal = original_steam_api64.SteamAPI_ISteamNetworkingConnectionCustomSignaling_SendSignal + SteamAPI_ISteamNetworkingCustomSignalingRecvContext_OnConnectRequest = original_steam_api64.SteamAPI_ISteamNetworkingCustomSignalingRecvContext_OnConnectRequest + SteamAPI_ISteamNetworkingCustomSignalingRecvContext_SendRejectionSignal = original_steam_api64.SteamAPI_ISteamNetworkingCustomSignalingRecvContext_SendRejectionSignal + SteamAPI_ISteamNetworkingFakeUDPPort_DestroyFakeUDPPort = original_steam_api64.SteamAPI_ISteamNetworkingFakeUDPPort_DestroyFakeUDPPort + SteamAPI_ISteamNetworkingFakeUDPPort_ReceiveMessages = original_steam_api64.SteamAPI_ISteamNetworkingFakeUDPPort_ReceiveMessages + SteamAPI_ISteamNetworkingFakeUDPPort_ScheduleCleanup = original_steam_api64.SteamAPI_ISteamNetworkingFakeUDPPort_ScheduleCleanup + SteamAPI_ISteamNetworkingFakeUDPPort_SendMessageToFakeIP = original_steam_api64.SteamAPI_ISteamNetworkingFakeUDPPort_SendMessageToFakeIP + SteamAPI_ISteamNetworkingMessages_AcceptSessionWithUser = original_steam_api64.SteamAPI_ISteamNetworkingMessages_AcceptSessionWithUser + SteamAPI_ISteamNetworkingMessages_CloseChannelWithUser = original_steam_api64.SteamAPI_ISteamNetworkingMessages_CloseChannelWithUser + SteamAPI_ISteamNetworkingMessages_CloseSessionWithUser = original_steam_api64.SteamAPI_ISteamNetworkingMessages_CloseSessionWithUser + SteamAPI_ISteamNetworkingMessages_GetSessionConnectionInfo = original_steam_api64.SteamAPI_ISteamNetworkingMessages_GetSessionConnectionInfo + SteamAPI_ISteamNetworkingMessages_ReceiveMessagesOnChannel = original_steam_api64.SteamAPI_ISteamNetworkingMessages_ReceiveMessagesOnChannel + SteamAPI_ISteamNetworkingMessages_SendMessageToUser = original_steam_api64.SteamAPI_ISteamNetworkingMessages_SendMessageToUser + SteamAPI_ISteamNetworkingSockets_AcceptConnection = original_steam_api64.SteamAPI_ISteamNetworkingSockets_AcceptConnection + SteamAPI_ISteamNetworkingSockets_BeginAsyncRequestFakeIP = original_steam_api64.SteamAPI_ISteamNetworkingSockets_BeginAsyncRequestFakeIP + SteamAPI_ISteamNetworkingSockets_CloseConnection = original_steam_api64.SteamAPI_ISteamNetworkingSockets_CloseConnection + SteamAPI_ISteamNetworkingSockets_CloseListenSocket = original_steam_api64.SteamAPI_ISteamNetworkingSockets_CloseListenSocket + SteamAPI_ISteamNetworkingSockets_ConfigureConnectionLanes = original_steam_api64.SteamAPI_ISteamNetworkingSockets_ConfigureConnectionLanes + SteamAPI_ISteamNetworkingSockets_ConnectByIPAddress = original_steam_api64.SteamAPI_ISteamNetworkingSockets_ConnectByIPAddress + SteamAPI_ISteamNetworkingSockets_ConnectP2P = original_steam_api64.SteamAPI_ISteamNetworkingSockets_ConnectP2P + SteamAPI_ISteamNetworkingSockets_ConnectP2PCustomSignaling = original_steam_api64.SteamAPI_ISteamNetworkingSockets_ConnectP2PCustomSignaling + SteamAPI_ISteamNetworkingSockets_ConnectToHostedDedicatedServer = original_steam_api64.SteamAPI_ISteamNetworkingSockets_ConnectToHostedDedicatedServer + SteamAPI_ISteamNetworkingSockets_CreateFakeUDPPort = original_steam_api64.SteamAPI_ISteamNetworkingSockets_CreateFakeUDPPort + SteamAPI_ISteamNetworkingSockets_CreateHostedDedicatedServerListenSocket = original_steam_api64.SteamAPI_ISteamNetworkingSockets_CreateHostedDedicatedServerListenSocket + SteamAPI_ISteamNetworkingSockets_CreateListenSocketIP = original_steam_api64.SteamAPI_ISteamNetworkingSockets_CreateListenSocketIP + SteamAPI_ISteamNetworkingSockets_CreateListenSocketP2P = original_steam_api64.SteamAPI_ISteamNetworkingSockets_CreateListenSocketP2P + SteamAPI_ISteamNetworkingSockets_CreateListenSocketP2PFakeIP = original_steam_api64.SteamAPI_ISteamNetworkingSockets_CreateListenSocketP2PFakeIP + SteamAPI_ISteamNetworkingSockets_CreatePollGroup = original_steam_api64.SteamAPI_ISteamNetworkingSockets_CreatePollGroup + SteamAPI_ISteamNetworkingSockets_CreateSocketPair = original_steam_api64.SteamAPI_ISteamNetworkingSockets_CreateSocketPair + SteamAPI_ISteamNetworkingSockets_DestroyPollGroup = original_steam_api64.SteamAPI_ISteamNetworkingSockets_DestroyPollGroup + SteamAPI_ISteamNetworkingSockets_FindRelayAuthTicketForServer = original_steam_api64.SteamAPI_ISteamNetworkingSockets_FindRelayAuthTicketForServer + SteamAPI_ISteamNetworkingSockets_FlushMessagesOnConnection = original_steam_api64.SteamAPI_ISteamNetworkingSockets_FlushMessagesOnConnection + SteamAPI_ISteamNetworkingSockets_GetAuthenticationStatus = original_steam_api64.SteamAPI_ISteamNetworkingSockets_GetAuthenticationStatus + SteamAPI_ISteamNetworkingSockets_GetCertificateRequest = original_steam_api64.SteamAPI_ISteamNetworkingSockets_GetCertificateRequest + SteamAPI_ISteamNetworkingSockets_GetConnectionInfo = original_steam_api64.SteamAPI_ISteamNetworkingSockets_GetConnectionInfo + SteamAPI_ISteamNetworkingSockets_GetConnectionName = original_steam_api64.SteamAPI_ISteamNetworkingSockets_GetConnectionName + SteamAPI_ISteamNetworkingSockets_GetConnectionRealTimeStatus = original_steam_api64.SteamAPI_ISteamNetworkingSockets_GetConnectionRealTimeStatus + SteamAPI_ISteamNetworkingSockets_GetConnectionUserData = original_steam_api64.SteamAPI_ISteamNetworkingSockets_GetConnectionUserData + SteamAPI_ISteamNetworkingSockets_GetDetailedConnectionStatus = original_steam_api64.SteamAPI_ISteamNetworkingSockets_GetDetailedConnectionStatus + SteamAPI_ISteamNetworkingSockets_GetFakeIP = original_steam_api64.SteamAPI_ISteamNetworkingSockets_GetFakeIP + SteamAPI_ISteamNetworkingSockets_GetGameCoordinatorServerLogin = original_steam_api64.SteamAPI_ISteamNetworkingSockets_GetGameCoordinatorServerLogin + SteamAPI_ISteamNetworkingSockets_GetHostedDedicatedServerAddress = original_steam_api64.SteamAPI_ISteamNetworkingSockets_GetHostedDedicatedServerAddress + SteamAPI_ISteamNetworkingSockets_GetHostedDedicatedServerPOPID = original_steam_api64.SteamAPI_ISteamNetworkingSockets_GetHostedDedicatedServerPOPID + SteamAPI_ISteamNetworkingSockets_GetHostedDedicatedServerPort = original_steam_api64.SteamAPI_ISteamNetworkingSockets_GetHostedDedicatedServerPort + SteamAPI_ISteamNetworkingSockets_GetIdentity = original_steam_api64.SteamAPI_ISteamNetworkingSockets_GetIdentity + SteamAPI_ISteamNetworkingSockets_GetListenSocketAddress = original_steam_api64.SteamAPI_ISteamNetworkingSockets_GetListenSocketAddress + SteamAPI_ISteamNetworkingSockets_GetQuickConnectionStatus = original_steam_api64.SteamAPI_ISteamNetworkingSockets_GetQuickConnectionStatus + SteamAPI_ISteamNetworkingSockets_GetRemoteFakeIPForConnection = original_steam_api64.SteamAPI_ISteamNetworkingSockets_GetRemoteFakeIPForConnection + SteamAPI_ISteamNetworkingSockets_InitAuthentication = original_steam_api64.SteamAPI_ISteamNetworkingSockets_InitAuthentication + SteamAPI_ISteamNetworkingSockets_ReceiveMessagesOnConnection = original_steam_api64.SteamAPI_ISteamNetworkingSockets_ReceiveMessagesOnConnection + SteamAPI_ISteamNetworkingSockets_ReceiveMessagesOnPollGroup = original_steam_api64.SteamAPI_ISteamNetworkingSockets_ReceiveMessagesOnPollGroup + SteamAPI_ISteamNetworkingSockets_ReceivedP2PCustomSignal = original_steam_api64.SteamAPI_ISteamNetworkingSockets_ReceivedP2PCustomSignal + SteamAPI_ISteamNetworkingSockets_ReceivedRelayAuthTicket = original_steam_api64.SteamAPI_ISteamNetworkingSockets_ReceivedRelayAuthTicket + SteamAPI_ISteamNetworkingSockets_ResetIdentity = original_steam_api64.SteamAPI_ISteamNetworkingSockets_ResetIdentity + SteamAPI_ISteamNetworkingSockets_RunCallbacks = original_steam_api64.SteamAPI_ISteamNetworkingSockets_RunCallbacks + SteamAPI_ISteamNetworkingSockets_SendMessageToConnection = original_steam_api64.SteamAPI_ISteamNetworkingSockets_SendMessageToConnection + SteamAPI_ISteamNetworkingSockets_SendMessages = original_steam_api64.SteamAPI_ISteamNetworkingSockets_SendMessages + SteamAPI_ISteamNetworkingSockets_SetCertificate = original_steam_api64.SteamAPI_ISteamNetworkingSockets_SetCertificate + SteamAPI_ISteamNetworkingSockets_SetConnectionName = original_steam_api64.SteamAPI_ISteamNetworkingSockets_SetConnectionName + SteamAPI_ISteamNetworkingSockets_SetConnectionPollGroup = original_steam_api64.SteamAPI_ISteamNetworkingSockets_SetConnectionPollGroup + SteamAPI_ISteamNetworkingSockets_SetConnectionUserData = original_steam_api64.SteamAPI_ISteamNetworkingSockets_SetConnectionUserData + SteamAPI_ISteamNetworkingUtils_AllocateMessage = original_steam_api64.SteamAPI_ISteamNetworkingUtils_AllocateMessage + SteamAPI_ISteamNetworkingUtils_CheckPingDataUpToDate = original_steam_api64.SteamAPI_ISteamNetworkingUtils_CheckPingDataUpToDate + SteamAPI_ISteamNetworkingUtils_ConvertPingLocationToString = original_steam_api64.SteamAPI_ISteamNetworkingUtils_ConvertPingLocationToString + SteamAPI_ISteamNetworkingUtils_EstimatePingTimeBetweenTwoLocations = original_steam_api64.SteamAPI_ISteamNetworkingUtils_EstimatePingTimeBetweenTwoLocations + SteamAPI_ISteamNetworkingUtils_EstimatePingTimeFromLocalHost = original_steam_api64.SteamAPI_ISteamNetworkingUtils_EstimatePingTimeFromLocalHost + SteamAPI_ISteamNetworkingUtils_GetConfigValue = original_steam_api64.SteamAPI_ISteamNetworkingUtils_GetConfigValue + SteamAPI_ISteamNetworkingUtils_GetConfigValueInfo = original_steam_api64.SteamAPI_ISteamNetworkingUtils_GetConfigValueInfo + SteamAPI_ISteamNetworkingUtils_GetDirectPingToPOP = original_steam_api64.SteamAPI_ISteamNetworkingUtils_GetDirectPingToPOP + SteamAPI_ISteamNetworkingUtils_GetFirstConfigValue = original_steam_api64.SteamAPI_ISteamNetworkingUtils_GetFirstConfigValue + SteamAPI_ISteamNetworkingUtils_GetIPv4FakeIPType = original_steam_api64.SteamAPI_ISteamNetworkingUtils_GetIPv4FakeIPType + SteamAPI_ISteamNetworkingUtils_GetLocalPingLocation = original_steam_api64.SteamAPI_ISteamNetworkingUtils_GetLocalPingLocation + SteamAPI_ISteamNetworkingUtils_GetLocalTimestamp = original_steam_api64.SteamAPI_ISteamNetworkingUtils_GetLocalTimestamp + SteamAPI_ISteamNetworkingUtils_GetPOPCount = original_steam_api64.SteamAPI_ISteamNetworkingUtils_GetPOPCount + SteamAPI_ISteamNetworkingUtils_GetPOPList = original_steam_api64.SteamAPI_ISteamNetworkingUtils_GetPOPList + SteamAPI_ISteamNetworkingUtils_GetPingToDataCenter = original_steam_api64.SteamAPI_ISteamNetworkingUtils_GetPingToDataCenter + SteamAPI_ISteamNetworkingUtils_GetRealIdentityForFakeIP = original_steam_api64.SteamAPI_ISteamNetworkingUtils_GetRealIdentityForFakeIP + SteamAPI_ISteamNetworkingUtils_GetRelayNetworkStatus = original_steam_api64.SteamAPI_ISteamNetworkingUtils_GetRelayNetworkStatus + SteamAPI_ISteamNetworkingUtils_InitRelayNetworkAccess = original_steam_api64.SteamAPI_ISteamNetworkingUtils_InitRelayNetworkAccess + SteamAPI_ISteamNetworkingUtils_IsFakeIPv4 = original_steam_api64.SteamAPI_ISteamNetworkingUtils_IsFakeIPv4 + SteamAPI_ISteamNetworkingUtils_IterateGenericEditableConfigValues = original_steam_api64.SteamAPI_ISteamNetworkingUtils_IterateGenericEditableConfigValues + SteamAPI_ISteamNetworkingUtils_ParsePingLocationString = original_steam_api64.SteamAPI_ISteamNetworkingUtils_ParsePingLocationString + SteamAPI_ISteamNetworkingUtils_SetConfigValue = original_steam_api64.SteamAPI_ISteamNetworkingUtils_SetConfigValue + SteamAPI_ISteamNetworkingUtils_SetConfigValueStruct = original_steam_api64.SteamAPI_ISteamNetworkingUtils_SetConfigValueStruct + SteamAPI_ISteamNetworkingUtils_SetConnectionConfigValueFloat = original_steam_api64.SteamAPI_ISteamNetworkingUtils_SetConnectionConfigValueFloat + SteamAPI_ISteamNetworkingUtils_SetConnectionConfigValueInt32 = original_steam_api64.SteamAPI_ISteamNetworkingUtils_SetConnectionConfigValueInt32 + SteamAPI_ISteamNetworkingUtils_SetConnectionConfigValueString = original_steam_api64.SteamAPI_ISteamNetworkingUtils_SetConnectionConfigValueString + SteamAPI_ISteamNetworkingUtils_SetDebugOutputFunction = original_steam_api64.SteamAPI_ISteamNetworkingUtils_SetDebugOutputFunction + SteamAPI_ISteamNetworkingUtils_SetGlobalCallback_FakeIPResult = original_steam_api64.SteamAPI_ISteamNetworkingUtils_SetGlobalCallback_FakeIPResult + SteamAPI_ISteamNetworkingUtils_SetGlobalCallback_MessagesSessionFailed = original_steam_api64.SteamAPI_ISteamNetworkingUtils_SetGlobalCallback_MessagesSessionFailed + SteamAPI_ISteamNetworkingUtils_SetGlobalCallback_MessagesSessionRequest = original_steam_api64.SteamAPI_ISteamNetworkingUtils_SetGlobalCallback_MessagesSessionRequest + SteamAPI_ISteamNetworkingUtils_SetGlobalCallback_SteamNetAuthenticationStatusChanged = original_steam_api64.SteamAPI_ISteamNetworkingUtils_SetGlobalCallback_SteamNetAuthenticationStatusChanged + SteamAPI_ISteamNetworkingUtils_SetGlobalCallback_SteamNetConnectionStatusChanged = original_steam_api64.SteamAPI_ISteamNetworkingUtils_SetGlobalCallback_SteamNetConnectionStatusChanged + SteamAPI_ISteamNetworkingUtils_SetGlobalCallback_SteamRelayNetworkStatusChanged = original_steam_api64.SteamAPI_ISteamNetworkingUtils_SetGlobalCallback_SteamRelayNetworkStatusChanged + SteamAPI_ISteamNetworkingUtils_SetGlobalConfigValueFloat = original_steam_api64.SteamAPI_ISteamNetworkingUtils_SetGlobalConfigValueFloat + SteamAPI_ISteamNetworkingUtils_SetGlobalConfigValueInt32 = original_steam_api64.SteamAPI_ISteamNetworkingUtils_SetGlobalConfigValueInt32 + SteamAPI_ISteamNetworkingUtils_SetGlobalConfigValuePtr = original_steam_api64.SteamAPI_ISteamNetworkingUtils_SetGlobalConfigValuePtr + SteamAPI_ISteamNetworkingUtils_SetGlobalConfigValueString = original_steam_api64.SteamAPI_ISteamNetworkingUtils_SetGlobalConfigValueString + SteamAPI_ISteamNetworkingUtils_SteamNetworkingIPAddr_GetFakeIPType = original_steam_api64.SteamAPI_ISteamNetworkingUtils_SteamNetworkingIPAddr_GetFakeIPType + SteamAPI_ISteamNetworkingUtils_SteamNetworkingIPAddr_ParseString = original_steam_api64.SteamAPI_ISteamNetworkingUtils_SteamNetworkingIPAddr_ParseString + SteamAPI_ISteamNetworkingUtils_SteamNetworkingIPAddr_ToString = original_steam_api64.SteamAPI_ISteamNetworkingUtils_SteamNetworkingIPAddr_ToString + SteamAPI_ISteamNetworkingUtils_SteamNetworkingIdentity_ParseString = original_steam_api64.SteamAPI_ISteamNetworkingUtils_SteamNetworkingIdentity_ParseString + SteamAPI_ISteamNetworkingUtils_SteamNetworkingIdentity_ToString = original_steam_api64.SteamAPI_ISteamNetworkingUtils_SteamNetworkingIdentity_ToString + SteamAPI_ISteamNetworking_AcceptP2PSessionWithUser = original_steam_api64.SteamAPI_ISteamNetworking_AcceptP2PSessionWithUser + SteamAPI_ISteamNetworking_AllowP2PPacketRelay = original_steam_api64.SteamAPI_ISteamNetworking_AllowP2PPacketRelay + SteamAPI_ISteamNetworking_CloseP2PChannelWithUser = original_steam_api64.SteamAPI_ISteamNetworking_CloseP2PChannelWithUser + SteamAPI_ISteamNetworking_CloseP2PSessionWithUser = original_steam_api64.SteamAPI_ISteamNetworking_CloseP2PSessionWithUser + SteamAPI_ISteamNetworking_CreateConnectionSocket = original_steam_api64.SteamAPI_ISteamNetworking_CreateConnectionSocket + SteamAPI_ISteamNetworking_CreateListenSocket = original_steam_api64.SteamAPI_ISteamNetworking_CreateListenSocket + SteamAPI_ISteamNetworking_CreateP2PConnectionSocket = original_steam_api64.SteamAPI_ISteamNetworking_CreateP2PConnectionSocket + SteamAPI_ISteamNetworking_DestroyListenSocket = original_steam_api64.SteamAPI_ISteamNetworking_DestroyListenSocket + SteamAPI_ISteamNetworking_DestroySocket = original_steam_api64.SteamAPI_ISteamNetworking_DestroySocket + SteamAPI_ISteamNetworking_GetListenSocketInfo = original_steam_api64.SteamAPI_ISteamNetworking_GetListenSocketInfo + SteamAPI_ISteamNetworking_GetMaxPacketSize = original_steam_api64.SteamAPI_ISteamNetworking_GetMaxPacketSize + SteamAPI_ISteamNetworking_GetP2PSessionState = original_steam_api64.SteamAPI_ISteamNetworking_GetP2PSessionState + SteamAPI_ISteamNetworking_GetSocketConnectionType = original_steam_api64.SteamAPI_ISteamNetworking_GetSocketConnectionType + SteamAPI_ISteamNetworking_GetSocketInfo = original_steam_api64.SteamAPI_ISteamNetworking_GetSocketInfo + SteamAPI_ISteamNetworking_IsDataAvailable = original_steam_api64.SteamAPI_ISteamNetworking_IsDataAvailable + SteamAPI_ISteamNetworking_IsDataAvailableOnSocket = original_steam_api64.SteamAPI_ISteamNetworking_IsDataAvailableOnSocket + SteamAPI_ISteamNetworking_IsP2PPacketAvailable = original_steam_api64.SteamAPI_ISteamNetworking_IsP2PPacketAvailable + SteamAPI_ISteamNetworking_ReadP2PPacket = original_steam_api64.SteamAPI_ISteamNetworking_ReadP2PPacket + SteamAPI_ISteamNetworking_RetrieveData = original_steam_api64.SteamAPI_ISteamNetworking_RetrieveData + SteamAPI_ISteamNetworking_RetrieveDataFromSocket = original_steam_api64.SteamAPI_ISteamNetworking_RetrieveDataFromSocket + SteamAPI_ISteamNetworking_SendDataOnSocket = original_steam_api64.SteamAPI_ISteamNetworking_SendDataOnSocket + SteamAPI_ISteamNetworking_SendP2PPacket = original_steam_api64.SteamAPI_ISteamNetworking_SendP2PPacket + SteamAPI_ISteamParentalSettings_BIsAppBlocked = original_steam_api64.SteamAPI_ISteamParentalSettings_BIsAppBlocked + SteamAPI_ISteamParentalSettings_BIsAppInBlockList = original_steam_api64.SteamAPI_ISteamParentalSettings_BIsAppInBlockList + SteamAPI_ISteamParentalSettings_BIsFeatureBlocked = original_steam_api64.SteamAPI_ISteamParentalSettings_BIsFeatureBlocked + SteamAPI_ISteamParentalSettings_BIsFeatureInBlockList = original_steam_api64.SteamAPI_ISteamParentalSettings_BIsFeatureInBlockList + SteamAPI_ISteamParentalSettings_BIsParentalLockEnabled = original_steam_api64.SteamAPI_ISteamParentalSettings_BIsParentalLockEnabled + SteamAPI_ISteamParentalSettings_BIsParentalLockLocked = original_steam_api64.SteamAPI_ISteamParentalSettings_BIsParentalLockLocked + SteamAPI_ISteamParties_CancelReservation = original_steam_api64.SteamAPI_ISteamParties_CancelReservation + SteamAPI_ISteamParties_ChangeNumOpenSlots = original_steam_api64.SteamAPI_ISteamParties_ChangeNumOpenSlots + SteamAPI_ISteamParties_CreateBeacon = original_steam_api64.SteamAPI_ISteamParties_CreateBeacon + SteamAPI_ISteamParties_DestroyBeacon = original_steam_api64.SteamAPI_ISteamParties_DestroyBeacon + SteamAPI_ISteamParties_GetAvailableBeaconLocations = original_steam_api64.SteamAPI_ISteamParties_GetAvailableBeaconLocations + SteamAPI_ISteamParties_GetBeaconByIndex = original_steam_api64.SteamAPI_ISteamParties_GetBeaconByIndex + SteamAPI_ISteamParties_GetBeaconDetails = original_steam_api64.SteamAPI_ISteamParties_GetBeaconDetails + SteamAPI_ISteamParties_GetBeaconLocationData = original_steam_api64.SteamAPI_ISteamParties_GetBeaconLocationData + SteamAPI_ISteamParties_GetNumActiveBeacons = original_steam_api64.SteamAPI_ISteamParties_GetNumActiveBeacons + SteamAPI_ISteamParties_GetNumAvailableBeaconLocations = original_steam_api64.SteamAPI_ISteamParties_GetNumAvailableBeaconLocations + SteamAPI_ISteamParties_JoinParty = original_steam_api64.SteamAPI_ISteamParties_JoinParty + SteamAPI_ISteamParties_OnReservationCompleted = original_steam_api64.SteamAPI_ISteamParties_OnReservationCompleted + SteamAPI_ISteamRemotePlay_BEnableRemotePlayTogetherDirectInput = original_steam_api64.SteamAPI_ISteamRemotePlay_BEnableRemotePlayTogetherDirectInput + SteamAPI_ISteamRemotePlay_BGetSessionClientResolution = original_steam_api64.SteamAPI_ISteamRemotePlay_BGetSessionClientResolution + SteamAPI_ISteamRemotePlay_BSendRemotePlayTogetherInvite = original_steam_api64.SteamAPI_ISteamRemotePlay_BSendRemotePlayTogetherInvite + SteamAPI_ISteamRemotePlay_BStartRemotePlayTogether = original_steam_api64.SteamAPI_ISteamRemotePlay_BStartRemotePlayTogether + SteamAPI_ISteamRemotePlay_CreateMouseCursor = original_steam_api64.SteamAPI_ISteamRemotePlay_CreateMouseCursor + SteamAPI_ISteamRemotePlay_DisableRemotePlayTogetherDirectInput = original_steam_api64.SteamAPI_ISteamRemotePlay_DisableRemotePlayTogetherDirectInput + SteamAPI_ISteamRemotePlay_GetInput = original_steam_api64.SteamAPI_ISteamRemotePlay_GetInput + SteamAPI_ISteamRemotePlay_GetSessionClientFormFactor = original_steam_api64.SteamAPI_ISteamRemotePlay_GetSessionClientFormFactor + SteamAPI_ISteamRemotePlay_GetSessionClientName = original_steam_api64.SteamAPI_ISteamRemotePlay_GetSessionClientName + SteamAPI_ISteamRemotePlay_GetSessionCount = original_steam_api64.SteamAPI_ISteamRemotePlay_GetSessionCount + SteamAPI_ISteamRemotePlay_GetSessionID = original_steam_api64.SteamAPI_ISteamRemotePlay_GetSessionID + SteamAPI_ISteamRemotePlay_GetSessionSteamID = original_steam_api64.SteamAPI_ISteamRemotePlay_GetSessionSteamID + SteamAPI_ISteamRemotePlay_SetMouseCursor = original_steam_api64.SteamAPI_ISteamRemotePlay_SetMouseCursor + SteamAPI_ISteamRemotePlay_SetMousePosition = original_steam_api64.SteamAPI_ISteamRemotePlay_SetMousePosition + SteamAPI_ISteamRemotePlay_SetMouseVisibility = original_steam_api64.SteamAPI_ISteamRemotePlay_SetMouseVisibility + SteamAPI_ISteamRemotePlay_ShowRemotePlayTogetherUI = original_steam_api64.SteamAPI_ISteamRemotePlay_ShowRemotePlayTogetherUI + SteamAPI_ISteamRemoteStorage_BeginFileWriteBatch = original_steam_api64.SteamAPI_ISteamRemoteStorage_BeginFileWriteBatch + SteamAPI_ISteamRemoteStorage_CommitPublishedFileUpdate = original_steam_api64.SteamAPI_ISteamRemoteStorage_CommitPublishedFileUpdate + SteamAPI_ISteamRemoteStorage_CreatePublishedFileUpdateRequest = original_steam_api64.SteamAPI_ISteamRemoteStorage_CreatePublishedFileUpdateRequest + SteamAPI_ISteamRemoteStorage_DeletePublishedFile = original_steam_api64.SteamAPI_ISteamRemoteStorage_DeletePublishedFile + SteamAPI_ISteamRemoteStorage_EndFileWriteBatch = original_steam_api64.SteamAPI_ISteamRemoteStorage_EndFileWriteBatch + SteamAPI_ISteamRemoteStorage_EnumeratePublishedFilesByUserAction = original_steam_api64.SteamAPI_ISteamRemoteStorage_EnumeratePublishedFilesByUserAction + SteamAPI_ISteamRemoteStorage_EnumeratePublishedWorkshopFiles = original_steam_api64.SteamAPI_ISteamRemoteStorage_EnumeratePublishedWorkshopFiles + SteamAPI_ISteamRemoteStorage_EnumerateUserPublishedFiles = original_steam_api64.SteamAPI_ISteamRemoteStorage_EnumerateUserPublishedFiles + SteamAPI_ISteamRemoteStorage_EnumerateUserSharedWorkshopFiles = original_steam_api64.SteamAPI_ISteamRemoteStorage_EnumerateUserSharedWorkshopFiles + SteamAPI_ISteamRemoteStorage_EnumerateUserSubscribedFiles = original_steam_api64.SteamAPI_ISteamRemoteStorage_EnumerateUserSubscribedFiles + SteamAPI_ISteamRemoteStorage_FileDelete = original_steam_api64.SteamAPI_ISteamRemoteStorage_FileDelete + SteamAPI_ISteamRemoteStorage_FileExists = original_steam_api64.SteamAPI_ISteamRemoteStorage_FileExists + SteamAPI_ISteamRemoteStorage_FileForget = original_steam_api64.SteamAPI_ISteamRemoteStorage_FileForget + SteamAPI_ISteamRemoteStorage_FilePersisted = original_steam_api64.SteamAPI_ISteamRemoteStorage_FilePersisted + SteamAPI_ISteamRemoteStorage_FileRead = original_steam_api64.SteamAPI_ISteamRemoteStorage_FileRead + SteamAPI_ISteamRemoteStorage_FileReadAsync = original_steam_api64.SteamAPI_ISteamRemoteStorage_FileReadAsync + SteamAPI_ISteamRemoteStorage_FileReadAsyncComplete = original_steam_api64.SteamAPI_ISteamRemoteStorage_FileReadAsyncComplete + SteamAPI_ISteamRemoteStorage_FileShare = original_steam_api64.SteamAPI_ISteamRemoteStorage_FileShare + SteamAPI_ISteamRemoteStorage_FileWrite = original_steam_api64.SteamAPI_ISteamRemoteStorage_FileWrite + SteamAPI_ISteamRemoteStorage_FileWriteAsync = original_steam_api64.SteamAPI_ISteamRemoteStorage_FileWriteAsync + SteamAPI_ISteamRemoteStorage_FileWriteStreamCancel = original_steam_api64.SteamAPI_ISteamRemoteStorage_FileWriteStreamCancel + SteamAPI_ISteamRemoteStorage_FileWriteStreamClose = original_steam_api64.SteamAPI_ISteamRemoteStorage_FileWriteStreamClose + SteamAPI_ISteamRemoteStorage_FileWriteStreamOpen = original_steam_api64.SteamAPI_ISteamRemoteStorage_FileWriteStreamOpen + SteamAPI_ISteamRemoteStorage_FileWriteStreamWriteChunk = original_steam_api64.SteamAPI_ISteamRemoteStorage_FileWriteStreamWriteChunk + SteamAPI_ISteamRemoteStorage_GetCachedUGCCount = original_steam_api64.SteamAPI_ISteamRemoteStorage_GetCachedUGCCount + SteamAPI_ISteamRemoteStorage_GetCachedUGCHandle = original_steam_api64.SteamAPI_ISteamRemoteStorage_GetCachedUGCHandle + SteamAPI_ISteamRemoteStorage_GetFileCount = original_steam_api64.SteamAPI_ISteamRemoteStorage_GetFileCount + SteamAPI_ISteamRemoteStorage_GetFileNameAndSize = original_steam_api64.SteamAPI_ISteamRemoteStorage_GetFileNameAndSize + SteamAPI_ISteamRemoteStorage_GetFileSize = original_steam_api64.SteamAPI_ISteamRemoteStorage_GetFileSize + SteamAPI_ISteamRemoteStorage_GetFileTimestamp = original_steam_api64.SteamAPI_ISteamRemoteStorage_GetFileTimestamp + SteamAPI_ISteamRemoteStorage_GetLocalFileChange = original_steam_api64.SteamAPI_ISteamRemoteStorage_GetLocalFileChange + SteamAPI_ISteamRemoteStorage_GetLocalFileChangeCount = original_steam_api64.SteamAPI_ISteamRemoteStorage_GetLocalFileChangeCount + SteamAPI_ISteamRemoteStorage_GetPublishedFileDetails = original_steam_api64.SteamAPI_ISteamRemoteStorage_GetPublishedFileDetails + SteamAPI_ISteamRemoteStorage_GetPublishedItemVoteDetails = original_steam_api64.SteamAPI_ISteamRemoteStorage_GetPublishedItemVoteDetails + SteamAPI_ISteamRemoteStorage_GetQuota = original_steam_api64.SteamAPI_ISteamRemoteStorage_GetQuota + SteamAPI_ISteamRemoteStorage_GetSyncPlatforms = original_steam_api64.SteamAPI_ISteamRemoteStorage_GetSyncPlatforms + SteamAPI_ISteamRemoteStorage_GetUGCDetails = original_steam_api64.SteamAPI_ISteamRemoteStorage_GetUGCDetails + SteamAPI_ISteamRemoteStorage_GetUGCDownloadProgress = original_steam_api64.SteamAPI_ISteamRemoteStorage_GetUGCDownloadProgress + SteamAPI_ISteamRemoteStorage_GetUserPublishedItemVoteDetails = original_steam_api64.SteamAPI_ISteamRemoteStorage_GetUserPublishedItemVoteDetails + SteamAPI_ISteamRemoteStorage_IsCloudEnabledForAccount = original_steam_api64.SteamAPI_ISteamRemoteStorage_IsCloudEnabledForAccount + SteamAPI_ISteamRemoteStorage_IsCloudEnabledForApp = original_steam_api64.SteamAPI_ISteamRemoteStorage_IsCloudEnabledForApp + SteamAPI_ISteamRemoteStorage_PublishVideo = original_steam_api64.SteamAPI_ISteamRemoteStorage_PublishVideo + SteamAPI_ISteamRemoteStorage_PublishWorkshopFile = original_steam_api64.SteamAPI_ISteamRemoteStorage_PublishWorkshopFile + SteamAPI_ISteamRemoteStorage_SetCloudEnabledForApp = original_steam_api64.SteamAPI_ISteamRemoteStorage_SetCloudEnabledForApp + SteamAPI_ISteamRemoteStorage_SetSyncPlatforms = original_steam_api64.SteamAPI_ISteamRemoteStorage_SetSyncPlatforms + SteamAPI_ISteamRemoteStorage_SetUserPublishedFileAction = original_steam_api64.SteamAPI_ISteamRemoteStorage_SetUserPublishedFileAction + SteamAPI_ISteamRemoteStorage_SubscribePublishedFile = original_steam_api64.SteamAPI_ISteamRemoteStorage_SubscribePublishedFile + SteamAPI_ISteamRemoteStorage_UGCDownload = original_steam_api64.SteamAPI_ISteamRemoteStorage_UGCDownload + SteamAPI_ISteamRemoteStorage_UGCDownloadToLocation = original_steam_api64.SteamAPI_ISteamRemoteStorage_UGCDownloadToLocation + SteamAPI_ISteamRemoteStorage_UGCRead = original_steam_api64.SteamAPI_ISteamRemoteStorage_UGCRead + SteamAPI_ISteamRemoteStorage_UnsubscribePublishedFile = original_steam_api64.SteamAPI_ISteamRemoteStorage_UnsubscribePublishedFile + SteamAPI_ISteamRemoteStorage_UpdatePublishedFileDescription = original_steam_api64.SteamAPI_ISteamRemoteStorage_UpdatePublishedFileDescription + SteamAPI_ISteamRemoteStorage_UpdatePublishedFileFile = original_steam_api64.SteamAPI_ISteamRemoteStorage_UpdatePublishedFileFile + SteamAPI_ISteamRemoteStorage_UpdatePublishedFilePreviewFile = original_steam_api64.SteamAPI_ISteamRemoteStorage_UpdatePublishedFilePreviewFile + SteamAPI_ISteamRemoteStorage_UpdatePublishedFileSetChangeDescription = original_steam_api64.SteamAPI_ISteamRemoteStorage_UpdatePublishedFileSetChangeDescription + SteamAPI_ISteamRemoteStorage_UpdatePublishedFileTags = original_steam_api64.SteamAPI_ISteamRemoteStorage_UpdatePublishedFileTags + SteamAPI_ISteamRemoteStorage_UpdatePublishedFileTitle = original_steam_api64.SteamAPI_ISteamRemoteStorage_UpdatePublishedFileTitle + SteamAPI_ISteamRemoteStorage_UpdatePublishedFileVisibility = original_steam_api64.SteamAPI_ISteamRemoteStorage_UpdatePublishedFileVisibility + SteamAPI_ISteamRemoteStorage_UpdateUserPublishedItemVote = original_steam_api64.SteamAPI_ISteamRemoteStorage_UpdateUserPublishedItemVote + SteamAPI_ISteamScreenshots_AddScreenshotToLibrary = original_steam_api64.SteamAPI_ISteamScreenshots_AddScreenshotToLibrary + SteamAPI_ISteamScreenshots_AddVRScreenshotToLibrary = original_steam_api64.SteamAPI_ISteamScreenshots_AddVRScreenshotToLibrary + SteamAPI_ISteamScreenshots_HookScreenshots = original_steam_api64.SteamAPI_ISteamScreenshots_HookScreenshots + SteamAPI_ISteamScreenshots_IsScreenshotsHooked = original_steam_api64.SteamAPI_ISteamScreenshots_IsScreenshotsHooked + SteamAPI_ISteamScreenshots_SetLocation = original_steam_api64.SteamAPI_ISteamScreenshots_SetLocation + SteamAPI_ISteamScreenshots_TagPublishedFile = original_steam_api64.SteamAPI_ISteamScreenshots_TagPublishedFile + SteamAPI_ISteamScreenshots_TagUser = original_steam_api64.SteamAPI_ISteamScreenshots_TagUser + SteamAPI_ISteamScreenshots_TriggerScreenshot = original_steam_api64.SteamAPI_ISteamScreenshots_TriggerScreenshot + SteamAPI_ISteamScreenshots_WriteScreenshot = original_steam_api64.SteamAPI_ISteamScreenshots_WriteScreenshot + SteamAPI_ISteamTV_AddBroadcastGameData = original_steam_api64.SteamAPI_ISteamTV_AddBroadcastGameData + SteamAPI_ISteamTV_AddRegion = original_steam_api64.SteamAPI_ISteamTV_AddRegion + SteamAPI_ISteamTV_AddTimelineMarker = original_steam_api64.SteamAPI_ISteamTV_AddTimelineMarker + SteamAPI_ISteamTV_IsBroadcasting = original_steam_api64.SteamAPI_ISteamTV_IsBroadcasting + SteamAPI_ISteamTV_RemoveBroadcastGameData = original_steam_api64.SteamAPI_ISteamTV_RemoveBroadcastGameData + SteamAPI_ISteamTV_RemoveRegion = original_steam_api64.SteamAPI_ISteamTV_RemoveRegion + SteamAPI_ISteamTV_RemoveTimelineMarker = original_steam_api64.SteamAPI_ISteamTV_RemoveTimelineMarker + SteamAPI_ISteamTimeline_AddGamePhaseTag = original_steam_api64.SteamAPI_ISteamTimeline_AddGamePhaseTag + SteamAPI_ISteamTimeline_AddInstantaneousTimelineEvent = original_steam_api64.SteamAPI_ISteamTimeline_AddInstantaneousTimelineEvent + SteamAPI_ISteamTimeline_AddRangeTimelineEvent = original_steam_api64.SteamAPI_ISteamTimeline_AddRangeTimelineEvent + SteamAPI_ISteamTimeline_AddTimelineEvent = original_steam_api64.SteamAPI_ISteamTimeline_AddTimelineEvent + SteamAPI_ISteamTimeline_ClearTimelineStateDescription = original_steam_api64.SteamAPI_ISteamTimeline_ClearTimelineStateDescription + SteamAPI_ISteamTimeline_ClearTimelineTooltip = original_steam_api64.SteamAPI_ISteamTimeline_ClearTimelineTooltip + SteamAPI_ISteamTimeline_DoesEventRecordingExist = original_steam_api64.SteamAPI_ISteamTimeline_DoesEventRecordingExist + SteamAPI_ISteamTimeline_DoesGamePhaseRecordingExist = original_steam_api64.SteamAPI_ISteamTimeline_DoesGamePhaseRecordingExist + SteamAPI_ISteamTimeline_EndGamePhase = original_steam_api64.SteamAPI_ISteamTimeline_EndGamePhase + SteamAPI_ISteamTimeline_EndRangeTimelineEvent = original_steam_api64.SteamAPI_ISteamTimeline_EndRangeTimelineEvent + SteamAPI_ISteamTimeline_OpenOverlayToGamePhase = original_steam_api64.SteamAPI_ISteamTimeline_OpenOverlayToGamePhase + SteamAPI_ISteamTimeline_OpenOverlayToTimelineEvent = original_steam_api64.SteamAPI_ISteamTimeline_OpenOverlayToTimelineEvent + SteamAPI_ISteamTimeline_RemoveTimelineEvent = original_steam_api64.SteamAPI_ISteamTimeline_RemoveTimelineEvent + SteamAPI_ISteamTimeline_SetGamePhaseAttribute = original_steam_api64.SteamAPI_ISteamTimeline_SetGamePhaseAttribute + SteamAPI_ISteamTimeline_SetGamePhaseID = original_steam_api64.SteamAPI_ISteamTimeline_SetGamePhaseID + SteamAPI_ISteamTimeline_SetTimelineGameMode = original_steam_api64.SteamAPI_ISteamTimeline_SetTimelineGameMode + SteamAPI_ISteamTimeline_SetTimelineStateDescription = original_steam_api64.SteamAPI_ISteamTimeline_SetTimelineStateDescription + SteamAPI_ISteamTimeline_SetTimelineTooltip = original_steam_api64.SteamAPI_ISteamTimeline_SetTimelineTooltip + SteamAPI_ISteamTimeline_StartGamePhase = original_steam_api64.SteamAPI_ISteamTimeline_StartGamePhase + SteamAPI_ISteamTimeline_StartRangeTimelineEvent = original_steam_api64.SteamAPI_ISteamTimeline_StartRangeTimelineEvent + SteamAPI_ISteamTimeline_UpdateRangeTimelineEvent = original_steam_api64.SteamAPI_ISteamTimeline_UpdateRangeTimelineEvent + SteamAPI_ISteamUGC_AddAppDependency = original_steam_api64.SteamAPI_ISteamUGC_AddAppDependency + SteamAPI_ISteamUGC_AddContentDescriptor = original_steam_api64.SteamAPI_ISteamUGC_AddContentDescriptor + SteamAPI_ISteamUGC_AddDependency = original_steam_api64.SteamAPI_ISteamUGC_AddDependency + SteamAPI_ISteamUGC_AddExcludedTag = original_steam_api64.SteamAPI_ISteamUGC_AddExcludedTag + SteamAPI_ISteamUGC_AddItemKeyValueTag = original_steam_api64.SteamAPI_ISteamUGC_AddItemKeyValueTag + SteamAPI_ISteamUGC_AddItemPreviewFile = original_steam_api64.SteamAPI_ISteamUGC_AddItemPreviewFile + SteamAPI_ISteamUGC_AddItemPreviewVideo = original_steam_api64.SteamAPI_ISteamUGC_AddItemPreviewVideo + SteamAPI_ISteamUGC_AddItemToFavorites = original_steam_api64.SteamAPI_ISteamUGC_AddItemToFavorites + SteamAPI_ISteamUGC_AddRequiredKeyValueTag = original_steam_api64.SteamAPI_ISteamUGC_AddRequiredKeyValueTag + SteamAPI_ISteamUGC_AddRequiredTag = original_steam_api64.SteamAPI_ISteamUGC_AddRequiredTag + SteamAPI_ISteamUGC_AddRequiredTagGroup = original_steam_api64.SteamAPI_ISteamUGC_AddRequiredTagGroup + SteamAPI_ISteamUGC_BInitWorkshopForGameServer = original_steam_api64.SteamAPI_ISteamUGC_BInitWorkshopForGameServer + SteamAPI_ISteamUGC_CreateItem = original_steam_api64.SteamAPI_ISteamUGC_CreateItem + SteamAPI_ISteamUGC_CreateQueryAllUGCRequest = original_steam_api64.SteamAPI_ISteamUGC_CreateQueryAllUGCRequest + SteamAPI_ISteamUGC_CreateQueryAllUGCRequest0 = original_steam_api64.SteamAPI_ISteamUGC_CreateQueryAllUGCRequest0 + SteamAPI_ISteamUGC_CreateQueryAllUGCRequestCursor = original_steam_api64.SteamAPI_ISteamUGC_CreateQueryAllUGCRequestCursor + SteamAPI_ISteamUGC_CreateQueryAllUGCRequestPage = original_steam_api64.SteamAPI_ISteamUGC_CreateQueryAllUGCRequestPage + SteamAPI_ISteamUGC_CreateQueryUGCDetailsRequest = original_steam_api64.SteamAPI_ISteamUGC_CreateQueryUGCDetailsRequest + SteamAPI_ISteamUGC_CreateQueryUserUGCRequest = original_steam_api64.SteamAPI_ISteamUGC_CreateQueryUserUGCRequest + SteamAPI_ISteamUGC_DeleteItem = original_steam_api64.SteamAPI_ISteamUGC_DeleteItem + SteamAPI_ISteamUGC_DownloadItem = original_steam_api64.SteamAPI_ISteamUGC_DownloadItem + SteamAPI_ISteamUGC_GetAppDependencies = original_steam_api64.SteamAPI_ISteamUGC_GetAppDependencies + SteamAPI_ISteamUGC_GetItemDownloadInfo = original_steam_api64.SteamAPI_ISteamUGC_GetItemDownloadInfo + SteamAPI_ISteamUGC_GetItemInstallInfo = original_steam_api64.SteamAPI_ISteamUGC_GetItemInstallInfo + SteamAPI_ISteamUGC_GetItemState = original_steam_api64.SteamAPI_ISteamUGC_GetItemState + SteamAPI_ISteamUGC_GetItemUpdateProgress = original_steam_api64.SteamAPI_ISteamUGC_GetItemUpdateProgress + SteamAPI_ISteamUGC_GetNumSubscribedItems = original_steam_api64.SteamAPI_ISteamUGC_GetNumSubscribedItems + SteamAPI_ISteamUGC_GetNumSupportedGameVersions = original_steam_api64.SteamAPI_ISteamUGC_GetNumSupportedGameVersions + SteamAPI_ISteamUGC_GetQueryFirstUGCKeyValueTag = original_steam_api64.SteamAPI_ISteamUGC_GetQueryFirstUGCKeyValueTag + SteamAPI_ISteamUGC_GetQueryUGCAdditionalPreview = original_steam_api64.SteamAPI_ISteamUGC_GetQueryUGCAdditionalPreview + SteamAPI_ISteamUGC_GetQueryUGCChildren = original_steam_api64.SteamAPI_ISteamUGC_GetQueryUGCChildren + SteamAPI_ISteamUGC_GetQueryUGCContentDescriptors = original_steam_api64.SteamAPI_ISteamUGC_GetQueryUGCContentDescriptors + SteamAPI_ISteamUGC_GetQueryUGCKeyValueTag = original_steam_api64.SteamAPI_ISteamUGC_GetQueryUGCKeyValueTag + SteamAPI_ISteamUGC_GetQueryUGCKeyValueTag0 = original_steam_api64.SteamAPI_ISteamUGC_GetQueryUGCKeyValueTag0 + SteamAPI_ISteamUGC_GetQueryUGCMetadata = original_steam_api64.SteamAPI_ISteamUGC_GetQueryUGCMetadata + SteamAPI_ISteamUGC_GetQueryUGCNumAdditionalPreviews = original_steam_api64.SteamAPI_ISteamUGC_GetQueryUGCNumAdditionalPreviews + SteamAPI_ISteamUGC_GetQueryUGCNumKeyValueTags = original_steam_api64.SteamAPI_ISteamUGC_GetQueryUGCNumKeyValueTags + SteamAPI_ISteamUGC_GetQueryUGCNumTags = original_steam_api64.SteamAPI_ISteamUGC_GetQueryUGCNumTags + SteamAPI_ISteamUGC_GetQueryUGCPreviewURL = original_steam_api64.SteamAPI_ISteamUGC_GetQueryUGCPreviewURL + SteamAPI_ISteamUGC_GetQueryUGCResult = original_steam_api64.SteamAPI_ISteamUGC_GetQueryUGCResult + SteamAPI_ISteamUGC_GetQueryUGCStatistic = original_steam_api64.SteamAPI_ISteamUGC_GetQueryUGCStatistic + SteamAPI_ISteamUGC_GetQueryUGCTag = original_steam_api64.SteamAPI_ISteamUGC_GetQueryUGCTag + SteamAPI_ISteamUGC_GetQueryUGCTagDisplayName = original_steam_api64.SteamAPI_ISteamUGC_GetQueryUGCTagDisplayName + SteamAPI_ISteamUGC_GetSubscribedItems = original_steam_api64.SteamAPI_ISteamUGC_GetSubscribedItems + SteamAPI_ISteamUGC_GetSupportedGameVersionData = original_steam_api64.SteamAPI_ISteamUGC_GetSupportedGameVersionData + SteamAPI_ISteamUGC_GetUserContentDescriptorPreferences = original_steam_api64.SteamAPI_ISteamUGC_GetUserContentDescriptorPreferences + SteamAPI_ISteamUGC_GetUserItemVote = original_steam_api64.SteamAPI_ISteamUGC_GetUserItemVote + SteamAPI_ISteamUGC_GetWorkshopEULAStatus = original_steam_api64.SteamAPI_ISteamUGC_GetWorkshopEULAStatus + SteamAPI_ISteamUGC_ReleaseQueryUGCRequest = original_steam_api64.SteamAPI_ISteamUGC_ReleaseQueryUGCRequest + SteamAPI_ISteamUGC_RemoveAllItemKeyValueTags = original_steam_api64.SteamAPI_ISteamUGC_RemoveAllItemKeyValueTags + SteamAPI_ISteamUGC_RemoveAppDependency = original_steam_api64.SteamAPI_ISteamUGC_RemoveAppDependency + SteamAPI_ISteamUGC_RemoveContentDescriptor = original_steam_api64.SteamAPI_ISteamUGC_RemoveContentDescriptor + SteamAPI_ISteamUGC_RemoveDependency = original_steam_api64.SteamAPI_ISteamUGC_RemoveDependency + SteamAPI_ISteamUGC_RemoveItemFromFavorites = original_steam_api64.SteamAPI_ISteamUGC_RemoveItemFromFavorites + SteamAPI_ISteamUGC_RemoveItemKeyValueTags = original_steam_api64.SteamAPI_ISteamUGC_RemoveItemKeyValueTags + SteamAPI_ISteamUGC_RemoveItemPreview = original_steam_api64.SteamAPI_ISteamUGC_RemoveItemPreview + SteamAPI_ISteamUGC_RequestUGCDetails = original_steam_api64.SteamAPI_ISteamUGC_RequestUGCDetails + SteamAPI_ISteamUGC_SendQueryUGCRequest = original_steam_api64.SteamAPI_ISteamUGC_SendQueryUGCRequest + SteamAPI_ISteamUGC_SetAdminQuery = original_steam_api64.SteamAPI_ISteamUGC_SetAdminQuery + SteamAPI_ISteamUGC_SetAllowCachedResponse = original_steam_api64.SteamAPI_ISteamUGC_SetAllowCachedResponse + SteamAPI_ISteamUGC_SetAllowLegacyUpload = original_steam_api64.SteamAPI_ISteamUGC_SetAllowLegacyUpload + SteamAPI_ISteamUGC_SetCloudFileNameFilter = original_steam_api64.SteamAPI_ISteamUGC_SetCloudFileNameFilter + SteamAPI_ISteamUGC_SetItemContent = original_steam_api64.SteamAPI_ISteamUGC_SetItemContent + SteamAPI_ISteamUGC_SetItemDescription = original_steam_api64.SteamAPI_ISteamUGC_SetItemDescription + SteamAPI_ISteamUGC_SetItemMetadata = original_steam_api64.SteamAPI_ISteamUGC_SetItemMetadata + SteamAPI_ISteamUGC_SetItemPreview = original_steam_api64.SteamAPI_ISteamUGC_SetItemPreview + SteamAPI_ISteamUGC_SetItemTags = original_steam_api64.SteamAPI_ISteamUGC_SetItemTags + SteamAPI_ISteamUGC_SetItemTitle = original_steam_api64.SteamAPI_ISteamUGC_SetItemTitle + SteamAPI_ISteamUGC_SetItemUpdateLanguage = original_steam_api64.SteamAPI_ISteamUGC_SetItemUpdateLanguage + SteamAPI_ISteamUGC_SetItemVisibility = original_steam_api64.SteamAPI_ISteamUGC_SetItemVisibility + SteamAPI_ISteamUGC_SetItemsDisabledLocally = original_steam_api64.SteamAPI_ISteamUGC_SetItemsDisabledLocally + SteamAPI_ISteamUGC_SetLanguage = original_steam_api64.SteamAPI_ISteamUGC_SetLanguage + SteamAPI_ISteamUGC_SetMatchAnyTag = original_steam_api64.SteamAPI_ISteamUGC_SetMatchAnyTag + SteamAPI_ISteamUGC_SetRankedByTrendDays = original_steam_api64.SteamAPI_ISteamUGC_SetRankedByTrendDays + SteamAPI_ISteamUGC_SetRequiredGameVersions = original_steam_api64.SteamAPI_ISteamUGC_SetRequiredGameVersions + SteamAPI_ISteamUGC_SetReturnAdditionalPreviews = original_steam_api64.SteamAPI_ISteamUGC_SetReturnAdditionalPreviews + SteamAPI_ISteamUGC_SetReturnChildren = original_steam_api64.SteamAPI_ISteamUGC_SetReturnChildren + SteamAPI_ISteamUGC_SetReturnKeyValueTags = original_steam_api64.SteamAPI_ISteamUGC_SetReturnKeyValueTags + SteamAPI_ISteamUGC_SetReturnLongDescription = original_steam_api64.SteamAPI_ISteamUGC_SetReturnLongDescription + SteamAPI_ISteamUGC_SetReturnMetadata = original_steam_api64.SteamAPI_ISteamUGC_SetReturnMetadata + SteamAPI_ISteamUGC_SetReturnOnlyIDs = original_steam_api64.SteamAPI_ISteamUGC_SetReturnOnlyIDs + SteamAPI_ISteamUGC_SetReturnPlaytimeStats = original_steam_api64.SteamAPI_ISteamUGC_SetReturnPlaytimeStats + SteamAPI_ISteamUGC_SetReturnTotalOnly = original_steam_api64.SteamAPI_ISteamUGC_SetReturnTotalOnly + SteamAPI_ISteamUGC_SetSearchText = original_steam_api64.SteamAPI_ISteamUGC_SetSearchText + SteamAPI_ISteamUGC_SetSubscriptionsLoadOrder = original_steam_api64.SteamAPI_ISteamUGC_SetSubscriptionsLoadOrder + SteamAPI_ISteamUGC_SetTimeCreatedDateRange = original_steam_api64.SteamAPI_ISteamUGC_SetTimeCreatedDateRange + SteamAPI_ISteamUGC_SetTimeUpdatedDateRange = original_steam_api64.SteamAPI_ISteamUGC_SetTimeUpdatedDateRange + SteamAPI_ISteamUGC_SetUserItemVote = original_steam_api64.SteamAPI_ISteamUGC_SetUserItemVote + SteamAPI_ISteamUGC_ShowWorkshopEULA = original_steam_api64.SteamAPI_ISteamUGC_ShowWorkshopEULA + SteamAPI_ISteamUGC_StartItemUpdate = original_steam_api64.SteamAPI_ISteamUGC_StartItemUpdate + SteamAPI_ISteamUGC_StartPlaytimeTracking = original_steam_api64.SteamAPI_ISteamUGC_StartPlaytimeTracking + SteamAPI_ISteamUGC_StopPlaytimeTracking = original_steam_api64.SteamAPI_ISteamUGC_StopPlaytimeTracking + SteamAPI_ISteamUGC_StopPlaytimeTrackingForAllItems = original_steam_api64.SteamAPI_ISteamUGC_StopPlaytimeTrackingForAllItems + SteamAPI_ISteamUGC_SubmitItemUpdate = original_steam_api64.SteamAPI_ISteamUGC_SubmitItemUpdate + SteamAPI_ISteamUGC_SubscribeItem = original_steam_api64.SteamAPI_ISteamUGC_SubscribeItem + SteamAPI_ISteamUGC_SuspendDownloads = original_steam_api64.SteamAPI_ISteamUGC_SuspendDownloads + SteamAPI_ISteamUGC_UnsubscribeItem = original_steam_api64.SteamAPI_ISteamUGC_UnsubscribeItem + SteamAPI_ISteamUGC_UpdateItemPreviewFile = original_steam_api64.SteamAPI_ISteamUGC_UpdateItemPreviewFile + SteamAPI_ISteamUGC_UpdateItemPreviewVideo = original_steam_api64.SteamAPI_ISteamUGC_UpdateItemPreviewVideo + SteamAPI_ISteamUserStats_AttachLeaderboardUGC = original_steam_api64.SteamAPI_ISteamUserStats_AttachLeaderboardUGC + SteamAPI_ISteamUserStats_ClearAchievement = original_steam_api64.SteamAPI_ISteamUserStats_ClearAchievement + SteamAPI_ISteamUserStats_DownloadLeaderboardEntries = original_steam_api64.SteamAPI_ISteamUserStats_DownloadLeaderboardEntries + SteamAPI_ISteamUserStats_DownloadLeaderboardEntriesForUsers = original_steam_api64.SteamAPI_ISteamUserStats_DownloadLeaderboardEntriesForUsers + SteamAPI_ISteamUserStats_FindLeaderboard = original_steam_api64.SteamAPI_ISteamUserStats_FindLeaderboard + SteamAPI_ISteamUserStats_FindOrCreateLeaderboard = original_steam_api64.SteamAPI_ISteamUserStats_FindOrCreateLeaderboard + SteamAPI_ISteamUserStats_GetAchievement = original_steam_api64.SteamAPI_ISteamUserStats_GetAchievement + SteamAPI_ISteamUserStats_GetAchievementAchievedPercent = original_steam_api64.SteamAPI_ISteamUserStats_GetAchievementAchievedPercent + SteamAPI_ISteamUserStats_GetAchievementAndUnlockTime = original_steam_api64.SteamAPI_ISteamUserStats_GetAchievementAndUnlockTime + SteamAPI_ISteamUserStats_GetAchievementDisplayAttribute = original_steam_api64.SteamAPI_ISteamUserStats_GetAchievementDisplayAttribute + SteamAPI_ISteamUserStats_GetAchievementIcon = original_steam_api64.SteamAPI_ISteamUserStats_GetAchievementIcon + SteamAPI_ISteamUserStats_GetAchievementName = original_steam_api64.SteamAPI_ISteamUserStats_GetAchievementName + SteamAPI_ISteamUserStats_GetAchievementProgressLimitsFloat = original_steam_api64.SteamAPI_ISteamUserStats_GetAchievementProgressLimitsFloat + SteamAPI_ISteamUserStats_GetAchievementProgressLimitsInt32 = original_steam_api64.SteamAPI_ISteamUserStats_GetAchievementProgressLimitsInt32 + SteamAPI_ISteamUserStats_GetDownloadedLeaderboardEntry = original_steam_api64.SteamAPI_ISteamUserStats_GetDownloadedLeaderboardEntry + SteamAPI_ISteamUserStats_GetGlobalStat = original_steam_api64.SteamAPI_ISteamUserStats_GetGlobalStat + SteamAPI_ISteamUserStats_GetGlobalStat0 = original_steam_api64.SteamAPI_ISteamUserStats_GetGlobalStat0 + SteamAPI_ISteamUserStats_GetGlobalStatDouble = original_steam_api64.SteamAPI_ISteamUserStats_GetGlobalStatDouble + SteamAPI_ISteamUserStats_GetGlobalStatHistory = original_steam_api64.SteamAPI_ISteamUserStats_GetGlobalStatHistory + SteamAPI_ISteamUserStats_GetGlobalStatHistory0 = original_steam_api64.SteamAPI_ISteamUserStats_GetGlobalStatHistory0 + SteamAPI_ISteamUserStats_GetGlobalStatHistoryDouble = original_steam_api64.SteamAPI_ISteamUserStats_GetGlobalStatHistoryDouble + SteamAPI_ISteamUserStats_GetGlobalStatHistoryInt64 = original_steam_api64.SteamAPI_ISteamUserStats_GetGlobalStatHistoryInt64 + SteamAPI_ISteamUserStats_GetGlobalStatInt64 = original_steam_api64.SteamAPI_ISteamUserStats_GetGlobalStatInt64 + SteamAPI_ISteamUserStats_GetLeaderboardDisplayType = original_steam_api64.SteamAPI_ISteamUserStats_GetLeaderboardDisplayType + SteamAPI_ISteamUserStats_GetLeaderboardEntryCount = original_steam_api64.SteamAPI_ISteamUserStats_GetLeaderboardEntryCount + SteamAPI_ISteamUserStats_GetLeaderboardName = original_steam_api64.SteamAPI_ISteamUserStats_GetLeaderboardName + SteamAPI_ISteamUserStats_GetLeaderboardSortMethod = original_steam_api64.SteamAPI_ISteamUserStats_GetLeaderboardSortMethod + SteamAPI_ISteamUserStats_GetMostAchievedAchievementInfo = original_steam_api64.SteamAPI_ISteamUserStats_GetMostAchievedAchievementInfo + SteamAPI_ISteamUserStats_GetNextMostAchievedAchievementInfo = original_steam_api64.SteamAPI_ISteamUserStats_GetNextMostAchievedAchievementInfo + SteamAPI_ISteamUserStats_GetNumAchievements = original_steam_api64.SteamAPI_ISteamUserStats_GetNumAchievements + SteamAPI_ISteamUserStats_GetNumberOfCurrentPlayers = original_steam_api64.SteamAPI_ISteamUserStats_GetNumberOfCurrentPlayers + SteamAPI_ISteamUserStats_GetStat = original_steam_api64.SteamAPI_ISteamUserStats_GetStat + SteamAPI_ISteamUserStats_GetStat0 = original_steam_api64.SteamAPI_ISteamUserStats_GetStat0 + SteamAPI_ISteamUserStats_GetStatFloat = original_steam_api64.SteamAPI_ISteamUserStats_GetStatFloat + SteamAPI_ISteamUserStats_GetStatInt32 = original_steam_api64.SteamAPI_ISteamUserStats_GetStatInt32 + SteamAPI_ISteamUserStats_GetUserAchievement = original_steam_api64.SteamAPI_ISteamUserStats_GetUserAchievement + SteamAPI_ISteamUserStats_GetUserAchievementAndUnlockTime = original_steam_api64.SteamAPI_ISteamUserStats_GetUserAchievementAndUnlockTime + SteamAPI_ISteamUserStats_GetUserStat = original_steam_api64.SteamAPI_ISteamUserStats_GetUserStat + SteamAPI_ISteamUserStats_GetUserStat0 = original_steam_api64.SteamAPI_ISteamUserStats_GetUserStat0 + SteamAPI_ISteamUserStats_GetUserStatFloat = original_steam_api64.SteamAPI_ISteamUserStats_GetUserStatFloat + SteamAPI_ISteamUserStats_GetUserStatInt32 = original_steam_api64.SteamAPI_ISteamUserStats_GetUserStatInt32 + SteamAPI_ISteamUserStats_IndicateAchievementProgress = original_steam_api64.SteamAPI_ISteamUserStats_IndicateAchievementProgress + SteamAPI_ISteamUserStats_RequestCurrentStats = original_steam_api64.SteamAPI_ISteamUserStats_RequestCurrentStats + SteamAPI_ISteamUserStats_RequestGlobalAchievementPercentages = original_steam_api64.SteamAPI_ISteamUserStats_RequestGlobalAchievementPercentages + SteamAPI_ISteamUserStats_RequestGlobalStats = original_steam_api64.SteamAPI_ISteamUserStats_RequestGlobalStats + SteamAPI_ISteamUserStats_RequestUserStats = original_steam_api64.SteamAPI_ISteamUserStats_RequestUserStats + SteamAPI_ISteamUserStats_ResetAllStats = original_steam_api64.SteamAPI_ISteamUserStats_ResetAllStats + SteamAPI_ISteamUserStats_SetAchievement = original_steam_api64.SteamAPI_ISteamUserStats_SetAchievement + SteamAPI_ISteamUserStats_SetStat = original_steam_api64.SteamAPI_ISteamUserStats_SetStat + SteamAPI_ISteamUserStats_SetStat0 = original_steam_api64.SteamAPI_ISteamUserStats_SetStat0 + SteamAPI_ISteamUserStats_SetStatFloat = original_steam_api64.SteamAPI_ISteamUserStats_SetStatFloat + SteamAPI_ISteamUserStats_SetStatInt32 = original_steam_api64.SteamAPI_ISteamUserStats_SetStatInt32 + SteamAPI_ISteamUserStats_StoreStats = original_steam_api64.SteamAPI_ISteamUserStats_StoreStats + SteamAPI_ISteamUserStats_UpdateAvgRateStat = original_steam_api64.SteamAPI_ISteamUserStats_UpdateAvgRateStat + SteamAPI_ISteamUserStats_UploadLeaderboardScore = original_steam_api64.SteamAPI_ISteamUserStats_UploadLeaderboardScore + SteamAPI_ISteamUser_AdvertiseGame = original_steam_api64.SteamAPI_ISteamUser_AdvertiseGame + SteamAPI_ISteamUser_BIsBehindNAT = original_steam_api64.SteamAPI_ISteamUser_BIsBehindNAT + SteamAPI_ISteamUser_BIsPhoneIdentifying = original_steam_api64.SteamAPI_ISteamUser_BIsPhoneIdentifying + SteamAPI_ISteamUser_BIsPhoneRequiringVerification = original_steam_api64.SteamAPI_ISteamUser_BIsPhoneRequiringVerification + SteamAPI_ISteamUser_BIsPhoneVerified = original_steam_api64.SteamAPI_ISteamUser_BIsPhoneVerified + SteamAPI_ISteamUser_BIsTwoFactorEnabled = original_steam_api64.SteamAPI_ISteamUser_BIsTwoFactorEnabled + SteamAPI_ISteamUser_BLoggedOn = original_steam_api64.SteamAPI_ISteamUser_BLoggedOn + SteamAPI_ISteamUser_BSetDurationControlOnlineState = original_steam_api64.SteamAPI_ISteamUser_BSetDurationControlOnlineState + SteamAPI_ISteamUser_BeginAuthSession = original_steam_api64.SteamAPI_ISteamUser_BeginAuthSession + SteamAPI_ISteamUser_CancelAuthTicket = original_steam_api64.SteamAPI_ISteamUser_CancelAuthTicket + SteamAPI_ISteamUser_DecompressVoice = original_steam_api64.SteamAPI_ISteamUser_DecompressVoice + SteamAPI_ISteamUser_EndAuthSession = original_steam_api64.SteamAPI_ISteamUser_EndAuthSession + SteamAPI_ISteamUser_GetAuthSessionTicket = original_steam_api64.SteamAPI_ISteamUser_GetAuthSessionTicket + SteamAPI_ISteamUser_GetAuthTicketForWebApi = original_steam_api64.SteamAPI_ISteamUser_GetAuthTicketForWebApi + SteamAPI_ISteamUser_GetAvailableVoice = original_steam_api64.SteamAPI_ISteamUser_GetAvailableVoice + SteamAPI_ISteamUser_GetDurationControl = original_steam_api64.SteamAPI_ISteamUser_GetDurationControl + SteamAPI_ISteamUser_GetEncryptedAppTicket = original_steam_api64.SteamAPI_ISteamUser_GetEncryptedAppTicket + SteamAPI_ISteamUser_GetGameBadgeLevel = original_steam_api64.SteamAPI_ISteamUser_GetGameBadgeLevel + SteamAPI_ISteamUser_GetHSteamUser = original_steam_api64.SteamAPI_ISteamUser_GetHSteamUser + SteamAPI_ISteamUser_GetMarketEligibility = original_steam_api64.SteamAPI_ISteamUser_GetMarketEligibility + SteamAPI_ISteamUser_GetPlayerSteamLevel = original_steam_api64.SteamAPI_ISteamUser_GetPlayerSteamLevel + SteamAPI_ISteamUser_GetSteamID = original_steam_api64.SteamAPI_ISteamUser_GetSteamID + SteamAPI_ISteamUser_GetUserDataFolder = original_steam_api64.SteamAPI_ISteamUser_GetUserDataFolder + SteamAPI_ISteamUser_GetVoice = original_steam_api64.SteamAPI_ISteamUser_GetVoice + SteamAPI_ISteamUser_GetVoiceOptimalSampleRate = original_steam_api64.SteamAPI_ISteamUser_GetVoiceOptimalSampleRate + SteamAPI_ISteamUser_InitiateGameConnection = original_steam_api64.SteamAPI_ISteamUser_InitiateGameConnection + SteamAPI_ISteamUser_InitiateGameConnection_DEPRECATED = original_steam_api64.SteamAPI_ISteamUser_InitiateGameConnection_DEPRECATED + SteamAPI_ISteamUser_RequestEncryptedAppTicket = original_steam_api64.SteamAPI_ISteamUser_RequestEncryptedAppTicket + SteamAPI_ISteamUser_RequestStoreAuthURL = original_steam_api64.SteamAPI_ISteamUser_RequestStoreAuthURL + SteamAPI_ISteamUser_StartVoiceRecording = original_steam_api64.SteamAPI_ISteamUser_StartVoiceRecording + SteamAPI_ISteamUser_StopVoiceRecording = original_steam_api64.SteamAPI_ISteamUser_StopVoiceRecording + SteamAPI_ISteamUser_TerminateGameConnection = original_steam_api64.SteamAPI_ISteamUser_TerminateGameConnection + SteamAPI_ISteamUser_TerminateGameConnection_DEPRECATED = original_steam_api64.SteamAPI_ISteamUser_TerminateGameConnection_DEPRECATED + SteamAPI_ISteamUser_TrackAppUsageEvent = original_steam_api64.SteamAPI_ISteamUser_TrackAppUsageEvent + SteamAPI_ISteamUser_UserHasLicenseForApp = original_steam_api64.SteamAPI_ISteamUser_UserHasLicenseForApp + SteamAPI_ISteamUtils_BOverlayNeedsPresent = original_steam_api64.SteamAPI_ISteamUtils_BOverlayNeedsPresent + SteamAPI_ISteamUtils_CheckFileSignature = original_steam_api64.SteamAPI_ISteamUtils_CheckFileSignature + SteamAPI_ISteamUtils_DismissFloatingGamepadTextInput = original_steam_api64.SteamAPI_ISteamUtils_DismissFloatingGamepadTextInput + SteamAPI_ISteamUtils_DismissGamepadTextInput = original_steam_api64.SteamAPI_ISteamUtils_DismissGamepadTextInput + SteamAPI_ISteamUtils_FilterText = original_steam_api64.SteamAPI_ISteamUtils_FilterText + SteamAPI_ISteamUtils_GetAPICallFailureReason = original_steam_api64.SteamAPI_ISteamUtils_GetAPICallFailureReason + SteamAPI_ISteamUtils_GetAPICallResult = original_steam_api64.SteamAPI_ISteamUtils_GetAPICallResult + SteamAPI_ISteamUtils_GetAppID = original_steam_api64.SteamAPI_ISteamUtils_GetAppID + SteamAPI_ISteamUtils_GetCSERIPPort = original_steam_api64.SteamAPI_ISteamUtils_GetCSERIPPort + SteamAPI_ISteamUtils_GetConnectedUniverse = original_steam_api64.SteamAPI_ISteamUtils_GetConnectedUniverse + SteamAPI_ISteamUtils_GetCurrentBatteryPower = original_steam_api64.SteamAPI_ISteamUtils_GetCurrentBatteryPower + SteamAPI_ISteamUtils_GetEnteredGamepadTextInput = original_steam_api64.SteamAPI_ISteamUtils_GetEnteredGamepadTextInput + SteamAPI_ISteamUtils_GetEnteredGamepadTextLength = original_steam_api64.SteamAPI_ISteamUtils_GetEnteredGamepadTextLength + SteamAPI_ISteamUtils_GetIPCCallCount = original_steam_api64.SteamAPI_ISteamUtils_GetIPCCallCount + SteamAPI_ISteamUtils_GetIPCountry = original_steam_api64.SteamAPI_ISteamUtils_GetIPCountry + SteamAPI_ISteamUtils_GetIPv6ConnectivityState = original_steam_api64.SteamAPI_ISteamUtils_GetIPv6ConnectivityState + SteamAPI_ISteamUtils_GetImageRGBA = original_steam_api64.SteamAPI_ISteamUtils_GetImageRGBA + SteamAPI_ISteamUtils_GetImageSize = original_steam_api64.SteamAPI_ISteamUtils_GetImageSize + SteamAPI_ISteamUtils_GetSecondsSinceAppActive = original_steam_api64.SteamAPI_ISteamUtils_GetSecondsSinceAppActive + SteamAPI_ISteamUtils_GetSecondsSinceComputerActive = original_steam_api64.SteamAPI_ISteamUtils_GetSecondsSinceComputerActive + SteamAPI_ISteamUtils_GetServerRealTime = original_steam_api64.SteamAPI_ISteamUtils_GetServerRealTime + SteamAPI_ISteamUtils_GetSteamUILanguage = original_steam_api64.SteamAPI_ISteamUtils_GetSteamUILanguage + SteamAPI_ISteamUtils_InitFilterText = original_steam_api64.SteamAPI_ISteamUtils_InitFilterText + SteamAPI_ISteamUtils_IsAPICallCompleted = original_steam_api64.SteamAPI_ISteamUtils_IsAPICallCompleted + SteamAPI_ISteamUtils_IsOverlayEnabled = original_steam_api64.SteamAPI_ISteamUtils_IsOverlayEnabled + SteamAPI_ISteamUtils_IsSteamChinaLauncher = original_steam_api64.SteamAPI_ISteamUtils_IsSteamChinaLauncher + SteamAPI_ISteamUtils_IsSteamInBigPictureMode = original_steam_api64.SteamAPI_ISteamUtils_IsSteamInBigPictureMode + SteamAPI_ISteamUtils_IsSteamRunningInVR = original_steam_api64.SteamAPI_ISteamUtils_IsSteamRunningInVR + SteamAPI_ISteamUtils_IsSteamRunningOnSteamDeck = original_steam_api64.SteamAPI_ISteamUtils_IsSteamRunningOnSteamDeck + SteamAPI_ISteamUtils_IsVRHeadsetStreamingEnabled = original_steam_api64.SteamAPI_ISteamUtils_IsVRHeadsetStreamingEnabled + SteamAPI_ISteamUtils_SetGameLauncherMode = original_steam_api64.SteamAPI_ISteamUtils_SetGameLauncherMode + SteamAPI_ISteamUtils_SetOverlayNotificationInset = original_steam_api64.SteamAPI_ISteamUtils_SetOverlayNotificationInset + SteamAPI_ISteamUtils_SetOverlayNotificationPosition = original_steam_api64.SteamAPI_ISteamUtils_SetOverlayNotificationPosition + SteamAPI_ISteamUtils_SetVRHeadsetStreamingEnabled = original_steam_api64.SteamAPI_ISteamUtils_SetVRHeadsetStreamingEnabled + SteamAPI_ISteamUtils_SetWarningMessageHook = original_steam_api64.SteamAPI_ISteamUtils_SetWarningMessageHook + SteamAPI_ISteamUtils_ShowFloatingGamepadTextInput = original_steam_api64.SteamAPI_ISteamUtils_ShowFloatingGamepadTextInput + SteamAPI_ISteamUtils_ShowGamepadTextInput = original_steam_api64.SteamAPI_ISteamUtils_ShowGamepadTextInput + SteamAPI_ISteamUtils_ShowModalGamepadTextInput = original_steam_api64.SteamAPI_ISteamUtils_ShowModalGamepadTextInput + SteamAPI_ISteamUtils_StartVRDashboard = original_steam_api64.SteamAPI_ISteamUtils_StartVRDashboard + SteamAPI_ISteamVideo_GetOPFSettings = original_steam_api64.SteamAPI_ISteamVideo_GetOPFSettings + SteamAPI_ISteamVideo_GetOPFStringForApp = original_steam_api64.SteamAPI_ISteamVideo_GetOPFStringForApp + SteamAPI_ISteamVideo_GetVideoURL = original_steam_api64.SteamAPI_ISteamVideo_GetVideoURL + SteamAPI_ISteamVideo_IsBroadcasting = original_steam_api64.SteamAPI_ISteamVideo_IsBroadcasting + SteamAPI_InitAnonymousUser = original_steam_api64.SteamAPI_InitAnonymousUser + SteamAPI_ManualDispatch_FreeLastCallback = original_steam_api64.SteamAPI_ManualDispatch_FreeLastCallback + SteamAPI_ManualDispatch_GetAPICallResult = original_steam_api64.SteamAPI_ManualDispatch_GetAPICallResult + SteamAPI_ManualDispatch_GetNextCallback = original_steam_api64.SteamAPI_ManualDispatch_GetNextCallback + SteamAPI_ManualDispatch_Init = original_steam_api64.SteamAPI_ManualDispatch_Init + SteamAPI_ManualDispatch_RunFrame = original_steam_api64.SteamAPI_ManualDispatch_RunFrame + SteamAPI_MatchMakingKeyValuePair_t_Construct = original_steam_api64.SteamAPI_MatchMakingKeyValuePair_t_Construct + SteamAPI_ReleaseCurrentThreadMemory = original_steam_api64.SteamAPI_ReleaseCurrentThreadMemory + SteamAPI_RestartApp = original_steam_api64.SteamAPI_RestartApp + SteamAPI_SetBreakpadAppID = original_steam_api64.SteamAPI_SetBreakpadAppID + SteamAPI_SetMiniDumpComment = original_steam_api64.SteamAPI_SetMiniDumpComment + SteamAPI_SetTryCatchCallbacks = original_steam_api64.SteamAPI_SetTryCatchCallbacks + SteamAPI_SteamAppList_v001 = original_steam_api64.SteamAPI_SteamAppList_v001 + SteamAPI_SteamApps_v008 = original_steam_api64.SteamAPI_SteamApps_v008 + SteamAPI_SteamController_v007 = original_steam_api64.SteamAPI_SteamController_v007 + SteamAPI_SteamController_v008 = original_steam_api64.SteamAPI_SteamController_v008 + SteamAPI_SteamDatagramHostedAddress_Clear = original_steam_api64.SteamAPI_SteamDatagramHostedAddress_Clear + SteamAPI_SteamDatagramHostedAddress_GetPopID = original_steam_api64.SteamAPI_SteamDatagramHostedAddress_GetPopID + SteamAPI_SteamDatagramHostedAddress_SetDevAddress = original_steam_api64.SteamAPI_SteamDatagramHostedAddress_SetDevAddress + SteamAPI_SteamFriends_v017 = original_steam_api64.SteamAPI_SteamFriends_v017 + SteamAPI_SteamFriends_v018 = original_steam_api64.SteamAPI_SteamFriends_v018 + SteamAPI_SteamGameSearch_v001 = original_steam_api64.SteamAPI_SteamGameSearch_v001 + SteamAPI_SteamGameServerApps_v008 = original_steam_api64.SteamAPI_SteamGameServerApps_v008 + SteamAPI_SteamGameServerHTTP_v003 = original_steam_api64.SteamAPI_SteamGameServerHTTP_v003 + SteamAPI_SteamGameServerInventory_v003 = original_steam_api64.SteamAPI_SteamGameServerInventory_v003 + SteamAPI_SteamGameServerNetworkingMessages_SteamAPI_v002 = original_steam_api64.SteamAPI_SteamGameServerNetworkingMessages_SteamAPI_v002 + SteamAPI_SteamGameServerNetworkingMessages_v002 = original_steam_api64.SteamAPI_SteamGameServerNetworkingMessages_v002 + SteamAPI_SteamGameServerNetworkingSockets_SteamAPI_v009 = original_steam_api64.SteamAPI_SteamGameServerNetworkingSockets_SteamAPI_v009 + SteamAPI_SteamGameServerNetworkingSockets_SteamAPI_v011 = original_steam_api64.SteamAPI_SteamGameServerNetworkingSockets_SteamAPI_v011 + SteamAPI_SteamGameServerNetworkingSockets_SteamAPI_v012 = original_steam_api64.SteamAPI_SteamGameServerNetworkingSockets_SteamAPI_v012 + SteamAPI_SteamGameServerNetworkingSockets_v008 = original_steam_api64.SteamAPI_SteamGameServerNetworkingSockets_v008 + SteamAPI_SteamGameServerNetworkingSockets_v009 = original_steam_api64.SteamAPI_SteamGameServerNetworkingSockets_v009 + SteamAPI_SteamGameServerNetworking_v006 = original_steam_api64.SteamAPI_SteamGameServerNetworking_v006 + SteamAPI_SteamGameServerStats_v001 = original_steam_api64.SteamAPI_SteamGameServerStats_v001 + SteamAPI_SteamGameServerUGC_v014 = original_steam_api64.SteamAPI_SteamGameServerUGC_v014 + SteamAPI_SteamGameServerUGC_v015 = original_steam_api64.SteamAPI_SteamGameServerUGC_v015 + SteamAPI_SteamGameServerUGC_v016 = original_steam_api64.SteamAPI_SteamGameServerUGC_v016 + SteamAPI_SteamGameServerUGC_v017 = original_steam_api64.SteamAPI_SteamGameServerUGC_v017 + SteamAPI_SteamGameServerUGC_v018 = original_steam_api64.SteamAPI_SteamGameServerUGC_v018 + SteamAPI_SteamGameServerUGC_v020 = original_steam_api64.SteamAPI_SteamGameServerUGC_v020 + SteamAPI_SteamGameServerUGC_v021 = original_steam_api64.SteamAPI_SteamGameServerUGC_v021 + SteamAPI_SteamGameServerUtils_v009 = original_steam_api64.SteamAPI_SteamGameServerUtils_v009 + SteamAPI_SteamGameServerUtils_v010 = original_steam_api64.SteamAPI_SteamGameServerUtils_v010 + SteamAPI_SteamGameServer_v013 = original_steam_api64.SteamAPI_SteamGameServer_v013 + SteamAPI_SteamGameServer_v014 = original_steam_api64.SteamAPI_SteamGameServer_v014 + SteamAPI_SteamGameServer_v015 = original_steam_api64.SteamAPI_SteamGameServer_v015 + SteamAPI_SteamHTMLSurface_v005 = original_steam_api64.SteamAPI_SteamHTMLSurface_v005 + SteamAPI_SteamHTTP_v003 = original_steam_api64.SteamAPI_SteamHTTP_v003 + SteamAPI_SteamIPAddress_t_IsSet = original_steam_api64.SteamAPI_SteamIPAddress_t_IsSet + SteamAPI_SteamInput_v001 = original_steam_api64.SteamAPI_SteamInput_v001 + SteamAPI_SteamInput_v002 = original_steam_api64.SteamAPI_SteamInput_v002 + SteamAPI_SteamInput_v005 = original_steam_api64.SteamAPI_SteamInput_v005 + SteamAPI_SteamInput_v006 = original_steam_api64.SteamAPI_SteamInput_v006 + SteamAPI_SteamInventory_v003 = original_steam_api64.SteamAPI_SteamInventory_v003 + SteamAPI_SteamMusicRemote_v001 = original_steam_api64.SteamAPI_SteamMusicRemote_v001 + SteamAPI_SteamMusic_v001 = original_steam_api64.SteamAPI_SteamMusic_v001 + SteamAPI_SteamNetworkingConfigValue_t_SetFloat = original_steam_api64.SteamAPI_SteamNetworkingConfigValue_t_SetFloat + SteamAPI_SteamNetworkingConfigValue_t_SetInt32 = original_steam_api64.SteamAPI_SteamNetworkingConfigValue_t_SetInt32 + SteamAPI_SteamNetworkingConfigValue_t_SetInt64 = original_steam_api64.SteamAPI_SteamNetworkingConfigValue_t_SetInt64 + SteamAPI_SteamNetworkingConfigValue_t_SetPtr = original_steam_api64.SteamAPI_SteamNetworkingConfigValue_t_SetPtr + SteamAPI_SteamNetworkingConfigValue_t_SetString = original_steam_api64.SteamAPI_SteamNetworkingConfigValue_t_SetString + SteamAPI_SteamNetworkingIPAddrRender_c_str = original_steam_api64.SteamAPI_SteamNetworkingIPAddrRender_c_str + SteamAPI_SteamNetworkingIPAddr_Clear = original_steam_api64.SteamAPI_SteamNetworkingIPAddr_Clear + SteamAPI_SteamNetworkingIPAddr_GetFakeIPType = original_steam_api64.SteamAPI_SteamNetworkingIPAddr_GetFakeIPType + SteamAPI_SteamNetworkingIPAddr_GetIPv4 = original_steam_api64.SteamAPI_SteamNetworkingIPAddr_GetIPv4 + SteamAPI_SteamNetworkingIPAddr_IsEqualTo = original_steam_api64.SteamAPI_SteamNetworkingIPAddr_IsEqualTo + SteamAPI_SteamNetworkingIPAddr_IsFakeIP = original_steam_api64.SteamAPI_SteamNetworkingIPAddr_IsFakeIP + SteamAPI_SteamNetworkingIPAddr_IsIPv4 = original_steam_api64.SteamAPI_SteamNetworkingIPAddr_IsIPv4 + SteamAPI_SteamNetworkingIPAddr_IsIPv6AllZeros = original_steam_api64.SteamAPI_SteamNetworkingIPAddr_IsIPv6AllZeros + SteamAPI_SteamNetworkingIPAddr_IsLocalHost = original_steam_api64.SteamAPI_SteamNetworkingIPAddr_IsLocalHost + SteamAPI_SteamNetworkingIPAddr_ParseString = original_steam_api64.SteamAPI_SteamNetworkingIPAddr_ParseString + SteamAPI_SteamNetworkingIPAddr_SetIPv4 = original_steam_api64.SteamAPI_SteamNetworkingIPAddr_SetIPv4 + SteamAPI_SteamNetworkingIPAddr_SetIPv6 = original_steam_api64.SteamAPI_SteamNetworkingIPAddr_SetIPv6 + SteamAPI_SteamNetworkingIPAddr_SetIPv6LocalHost = original_steam_api64.SteamAPI_SteamNetworkingIPAddr_SetIPv6LocalHost + SteamAPI_SteamNetworkingIPAddr_ToString = original_steam_api64.SteamAPI_SteamNetworkingIPAddr_ToString + SteamAPI_SteamNetworkingIdentityRender_c_str = original_steam_api64.SteamAPI_SteamNetworkingIdentityRender_c_str + SteamAPI_SteamNetworkingIdentity_Clear = original_steam_api64.SteamAPI_SteamNetworkingIdentity_Clear + SteamAPI_SteamNetworkingIdentity_GetFakeIPType = original_steam_api64.SteamAPI_SteamNetworkingIdentity_GetFakeIPType + SteamAPI_SteamNetworkingIdentity_GetGenericBytes = original_steam_api64.SteamAPI_SteamNetworkingIdentity_GetGenericBytes + SteamAPI_SteamNetworkingIdentity_GetGenericString = original_steam_api64.SteamAPI_SteamNetworkingIdentity_GetGenericString + SteamAPI_SteamNetworkingIdentity_GetIPAddr = original_steam_api64.SteamAPI_SteamNetworkingIdentity_GetIPAddr + SteamAPI_SteamNetworkingIdentity_GetIPv4 = original_steam_api64.SteamAPI_SteamNetworkingIdentity_GetIPv4 + SteamAPI_SteamNetworkingIdentity_GetPSNID = original_steam_api64.SteamAPI_SteamNetworkingIdentity_GetPSNID + SteamAPI_SteamNetworkingIdentity_GetStadiaID = original_steam_api64.SteamAPI_SteamNetworkingIdentity_GetStadiaID + SteamAPI_SteamNetworkingIdentity_GetSteamID = original_steam_api64.SteamAPI_SteamNetworkingIdentity_GetSteamID + SteamAPI_SteamNetworkingIdentity_GetSteamID64 = original_steam_api64.SteamAPI_SteamNetworkingIdentity_GetSteamID64 + SteamAPI_SteamNetworkingIdentity_GetXboxPairwiseID = original_steam_api64.SteamAPI_SteamNetworkingIdentity_GetXboxPairwiseID + SteamAPI_SteamNetworkingIdentity_IsEqualTo = original_steam_api64.SteamAPI_SteamNetworkingIdentity_IsEqualTo + SteamAPI_SteamNetworkingIdentity_IsFakeIP = original_steam_api64.SteamAPI_SteamNetworkingIdentity_IsFakeIP + SteamAPI_SteamNetworkingIdentity_IsInvalid = original_steam_api64.SteamAPI_SteamNetworkingIdentity_IsInvalid + SteamAPI_SteamNetworkingIdentity_IsLocalHost = original_steam_api64.SteamAPI_SteamNetworkingIdentity_IsLocalHost + SteamAPI_SteamNetworkingIdentity_ParseString = original_steam_api64.SteamAPI_SteamNetworkingIdentity_ParseString + SteamAPI_SteamNetworkingIdentity_SetGenericBytes = original_steam_api64.SteamAPI_SteamNetworkingIdentity_SetGenericBytes + SteamAPI_SteamNetworkingIdentity_SetGenericString = original_steam_api64.SteamAPI_SteamNetworkingIdentity_SetGenericString + SteamAPI_SteamNetworkingIdentity_SetIPAddr = original_steam_api64.SteamAPI_SteamNetworkingIdentity_SetIPAddr + SteamAPI_SteamNetworkingIdentity_SetIPv4Addr = original_steam_api64.SteamAPI_SteamNetworkingIdentity_SetIPv4Addr + SteamAPI_SteamNetworkingIdentity_SetLocalHost = original_steam_api64.SteamAPI_SteamNetworkingIdentity_SetLocalHost + SteamAPI_SteamNetworkingIdentity_SetPSNID = original_steam_api64.SteamAPI_SteamNetworkingIdentity_SetPSNID + SteamAPI_SteamNetworkingIdentity_SetStadiaID = original_steam_api64.SteamAPI_SteamNetworkingIdentity_SetStadiaID + SteamAPI_SteamNetworkingIdentity_SetSteamID = original_steam_api64.SteamAPI_SteamNetworkingIdentity_SetSteamID + SteamAPI_SteamNetworkingIdentity_SetSteamID64 = original_steam_api64.SteamAPI_SteamNetworkingIdentity_SetSteamID64 + SteamAPI_SteamNetworkingIdentity_SetXboxPairwiseID = original_steam_api64.SteamAPI_SteamNetworkingIdentity_SetXboxPairwiseID + SteamAPI_SteamNetworkingIdentity_ToString = original_steam_api64.SteamAPI_SteamNetworkingIdentity_ToString + SteamAPI_SteamNetworkingMessage_t_Release = original_steam_api64.SteamAPI_SteamNetworkingMessage_t_Release + SteamAPI_SteamNetworkingMessages_SteamAPI_v002 = original_steam_api64.SteamAPI_SteamNetworkingMessages_SteamAPI_v002 + SteamAPI_SteamNetworkingMessages_v002 = original_steam_api64.SteamAPI_SteamNetworkingMessages_v002 + SteamAPI_SteamNetworkingPOPIDRender_c_str = original_steam_api64.SteamAPI_SteamNetworkingPOPIDRender_c_str + SteamAPI_SteamNetworkingSockets_SteamAPI_v009 = original_steam_api64.SteamAPI_SteamNetworkingSockets_SteamAPI_v009 + SteamAPI_SteamNetworkingSockets_SteamAPI_v011 = original_steam_api64.SteamAPI_SteamNetworkingSockets_SteamAPI_v011 + SteamAPI_SteamNetworkingSockets_SteamAPI_v012 = original_steam_api64.SteamAPI_SteamNetworkingSockets_SteamAPI_v012 + SteamAPI_SteamNetworkingSockets_v008 = original_steam_api64.SteamAPI_SteamNetworkingSockets_v008 + SteamAPI_SteamNetworkingSockets_v009 = original_steam_api64.SteamAPI_SteamNetworkingSockets_v009 + SteamAPI_SteamNetworkingUtils_SteamAPI_v003 = original_steam_api64.SteamAPI_SteamNetworkingUtils_SteamAPI_v003 + SteamAPI_SteamNetworkingUtils_SteamAPI_v004 = original_steam_api64.SteamAPI_SteamNetworkingUtils_SteamAPI_v004 + SteamAPI_SteamNetworkingUtils_v003 = original_steam_api64.SteamAPI_SteamNetworkingUtils_v003 + SteamAPI_SteamNetworking_v006 = original_steam_api64.SteamAPI_SteamNetworking_v006 + SteamAPI_SteamParentalSettings_v001 = original_steam_api64.SteamAPI_SteamParentalSettings_v001 + SteamAPI_SteamParties_v002 = original_steam_api64.SteamAPI_SteamParties_v002 + SteamAPI_SteamRemotePlay_v001 = original_steam_api64.SteamAPI_SteamRemotePlay_v001 + SteamAPI_SteamRemotePlay_v002 = original_steam_api64.SteamAPI_SteamRemotePlay_v002 + SteamAPI_SteamRemotePlay_v003 = original_steam_api64.SteamAPI_SteamRemotePlay_v003 + SteamAPI_SteamRemoteStorage_v014 = original_steam_api64.SteamAPI_SteamRemoteStorage_v014 + SteamAPI_SteamRemoteStorage_v016 = original_steam_api64.SteamAPI_SteamRemoteStorage_v016 + SteamAPI_SteamScreenshots_v003 = original_steam_api64.SteamAPI_SteamScreenshots_v003 + SteamAPI_SteamTV_v001 = original_steam_api64.SteamAPI_SteamTV_v001 + SteamAPI_SteamTimeline_v001 = original_steam_api64.SteamAPI_SteamTimeline_v001 + SteamAPI_SteamTimeline_v004 = original_steam_api64.SteamAPI_SteamTimeline_v004 + SteamAPI_SteamUGC_v014 = original_steam_api64.SteamAPI_SteamUGC_v014 + SteamAPI_SteamUGC_v015 = original_steam_api64.SteamAPI_SteamUGC_v015 + SteamAPI_SteamUGC_v016 = original_steam_api64.SteamAPI_SteamUGC_v016 + SteamAPI_SteamUGC_v017 = original_steam_api64.SteamAPI_SteamUGC_v017 + SteamAPI_SteamUGC_v018 = original_steam_api64.SteamAPI_SteamUGC_v018 + SteamAPI_SteamUGC_v020 = original_steam_api64.SteamAPI_SteamUGC_v020 + SteamAPI_SteamUGC_v021 = original_steam_api64.SteamAPI_SteamUGC_v021 + SteamAPI_SteamUserStats_v011 = original_steam_api64.SteamAPI_SteamUserStats_v011 + SteamAPI_SteamUserStats_v012 = original_steam_api64.SteamAPI_SteamUserStats_v012 + SteamAPI_SteamUserStats_v013 = original_steam_api64.SteamAPI_SteamUserStats_v013 + SteamAPI_SteamUser_v020 = original_steam_api64.SteamAPI_SteamUser_v020 + SteamAPI_SteamUser_v021 = original_steam_api64.SteamAPI_SteamUser_v021 + SteamAPI_SteamUser_v022 = original_steam_api64.SteamAPI_SteamUser_v022 + SteamAPI_SteamUser_v023 = original_steam_api64.SteamAPI_SteamUser_v023 + SteamAPI_SteamUtils_v009 = original_steam_api64.SteamAPI_SteamUtils_v009 + SteamAPI_SteamUtils_v010 = original_steam_api64.SteamAPI_SteamUtils_v010 + SteamAPI_SteamVideo_v001 = original_steam_api64.SteamAPI_SteamVideo_v001 + SteamAPI_SteamVideo_v002 = original_steam_api64.SteamAPI_SteamVideo_v002 + SteamAPI_SteamVideo_v007 = original_steam_api64.SteamAPI_SteamVideo_v007 + SteamAPI_UseBreakpadCrashHandler = original_steam_api64.SteamAPI_UseBreakpadCrashHandler + SteamAPI_WriteMiniDump = original_steam_api64.SteamAPI_WriteMiniDump + SteamAPI_gameserveritem_t_Construct = original_steam_api64.SteamAPI_gameserveritem_t_Construct + SteamAPI_gameserveritem_t_GetName = original_steam_api64.SteamAPI_gameserveritem_t_GetName + SteamAPI_gameserveritem_t_SetName = original_steam_api64.SteamAPI_gameserveritem_t_SetName + SteamAPI_servernetadr_t_Assign = original_steam_api64.SteamAPI_servernetadr_t_Assign + SteamAPI_servernetadr_t_Construct = original_steam_api64.SteamAPI_servernetadr_t_Construct + SteamAPI_servernetadr_t_GetConnectionAddressString = original_steam_api64.SteamAPI_servernetadr_t_GetConnectionAddressString + SteamAPI_servernetadr_t_GetConnectionPort = original_steam_api64.SteamAPI_servernetadr_t_GetConnectionPort + SteamAPI_servernetadr_t_GetIP = original_steam_api64.SteamAPI_servernetadr_t_GetIP + SteamAPI_servernetadr_t_GetQueryAddressString = original_steam_api64.SteamAPI_servernetadr_t_GetQueryAddressString + SteamAPI_servernetadr_t_GetQueryPort = original_steam_api64.SteamAPI_servernetadr_t_GetQueryPort + SteamAPI_servernetadr_t_Init = original_steam_api64.SteamAPI_servernetadr_t_Init + SteamAPI_servernetadr_t_IsLessThan = original_steam_api64.SteamAPI_servernetadr_t_IsLessThan + SteamAPI_servernetadr_t_SetConnectionPort = original_steam_api64.SteamAPI_servernetadr_t_SetConnectionPort + SteamAPI_servernetadr_t_SetIP = original_steam_api64.SteamAPI_servernetadr_t_SetIP + SteamAPI_servernetadr_t_SetQueryPort = original_steam_api64.SteamAPI_servernetadr_t_SetQueryPort + SteamAppList = original_steam_api64.SteamAppList + SteamApps = original_steam_api64.SteamApps + SteamController = original_steam_api64.SteamController + SteamFriends = original_steam_api64.SteamFriends + SteamGameServer = original_steam_api64.SteamGameServer + SteamGameServerApps = original_steam_api64.SteamGameServerApps + SteamGameServerClient = original_steam_api64.SteamGameServerClient + SteamGameServerHTTP = original_steam_api64.SteamGameServerHTTP + SteamGameServerInternal_CreateInterface = original_steam_api64.SteamGameServerInternal_CreateInterface + SteamGameServerInventory = original_steam_api64.SteamGameServerInventory + SteamGameServerNetworking = original_steam_api64.SteamGameServerNetworking + SteamGameServerStats = original_steam_api64.SteamGameServerStats + SteamGameServerUGC = original_steam_api64.SteamGameServerUGC + SteamGameServerUtils = original_steam_api64.SteamGameServerUtils + SteamGameServer_BSecure = original_steam_api64.SteamGameServer_BSecure + SteamGameServer_GetHSteamPipe = original_steam_api64.SteamGameServer_GetHSteamPipe + SteamGameServer_GetHSteamUser = original_steam_api64.SteamGameServer_GetHSteamUser + SteamGameServer_GetIPCCallCount = original_steam_api64.SteamGameServer_GetIPCCallCount + SteamGameServer_GetSteamID = original_steam_api64.SteamGameServer_GetSteamID + SteamGameServer_Init = original_steam_api64.SteamGameServer_Init + SteamGameServer_InitSafe = original_steam_api64.SteamGameServer_InitSafe + SteamGameServer_RunCallbacks = original_steam_api64.SteamGameServer_RunCallbacks + SteamGameServer_Shutdown = original_steam_api64.SteamGameServer_Shutdown + SteamHTMLSurface = original_steam_api64.SteamHTMLSurface + SteamHTTP = original_steam_api64.SteamHTTP + SteamInternal_ContextInit = original_steam_api64.SteamInternal_ContextInit + SteamInternal_CreateInterface = original_steam_api64.SteamInternal_CreateInterface + SteamInternal_FindOrCreateGameServerInterface = original_steam_api64.SteamInternal_FindOrCreateGameServerInterface + SteamInternal_FindOrCreateUserInterface = original_steam_api64.SteamInternal_FindOrCreateUserInterface + SteamInternal_GameServer_Init = original_steam_api64.SteamInternal_GameServer_Init + SteamInternal_GameServer_Init_V2 = original_steam_api64.SteamInternal_GameServer_Init_V2 + SteamInternal_SteamAPI_Init = original_steam_api64.SteamInternal_SteamAPI_Init + SteamInventory = original_steam_api64.SteamInventory + SteamMasterServerUpdater = original_steam_api64.SteamMasterServerUpdater + SteamMusic = original_steam_api64.SteamMusic + SteamMusicRemote = original_steam_api64.SteamMusicRemote + SteamNetworking = original_steam_api64.SteamNetworking + SteamParentalSettings = original_steam_api64.SteamParentalSettings + SteamRemoteStorage = original_steam_api64.SteamRemoteStorage + SteamScreenshots = original_steam_api64.SteamScreenshots + SteamUGC = original_steam_api64.SteamUGC + SteamUnifiedMessages = original_steam_api64.SteamUnifiedMessages + SteamUser = original_steam_api64.SteamUser + SteamUserStats = original_steam_api64.SteamUserStats + SteamUtils = original_steam_api64.SteamUtils + SteamVideo = original_steam_api64.SteamVideo + Steam_GetHSteamUserCurrent = original_steam_api64.Steam_GetHSteamUserCurrent + Steam_RegisterInterfaceFuncs = original_steam_api64.Steam_RegisterInterfaceFuncs + Steam_RunCallbacks = original_steam_api64.Steam_RunCallbacks + VR_GetGenericInterface = original_steam_api64.VR_GetGenericInterface + VR_GetStringForHmdError = original_steam_api64.VR_GetStringForHmdError + VR_Init = original_steam_api64.VR_Init + VR_IsHmdPresent = original_steam_api64.VR_IsHmdPresent + VR_Shutdown = original_steam_api64.VR_Shutdown + g_pSteamClientGameServer = original_steam_api64.g_pSteamClientGameServer + ; --- overrides (our own impls in steam_api_bridge_overrides.c) --- + SteamAPI_GetHSteamPipe + SteamAPI_GetHSteamUser + SteamAPI_ISteamClient_GetISteamMatchmaking + SteamAPI_ISteamClient_GetISteamMatchmakingServers + SteamAPI_ISteamMatchmakingServers_CancelQuery + SteamAPI_ISteamMatchmakingServers_CancelServerQuery + SteamAPI_ISteamMatchmakingServers_GetServerCount + SteamAPI_ISteamMatchmakingServers_GetServerDetails + SteamAPI_ISteamMatchmakingServers_IsRefreshing + SteamAPI_ISteamMatchmakingServers_PingServer + SteamAPI_ISteamMatchmakingServers_PlayerDetails + SteamAPI_ISteamMatchmakingServers_RefreshQuery + SteamAPI_ISteamMatchmakingServers_RefreshServer + SteamAPI_ISteamMatchmakingServers_ReleaseRequest + SteamAPI_ISteamMatchmakingServers_RequestFavoritesServerList + SteamAPI_ISteamMatchmakingServers_RequestFriendsServerList + SteamAPI_ISteamMatchmakingServers_RequestHistoryServerList + SteamAPI_ISteamMatchmakingServers_RequestInternetServerList + SteamAPI_ISteamMatchmakingServers_RequestLANServerList + SteamAPI_ISteamMatchmakingServers_RequestSpectatorServerList + SteamAPI_ISteamMatchmakingServers_ServerRules + SteamAPI_ISteamMatchmaking_AddFavoriteGame + SteamAPI_ISteamMatchmaking_AddRequestLobbyListCompatibleMembersFilter + SteamAPI_ISteamMatchmaking_AddRequestLobbyListDistanceFilter + SteamAPI_ISteamMatchmaking_AddRequestLobbyListFilterSlotsAvailable + SteamAPI_ISteamMatchmaking_AddRequestLobbyListNearValueFilter + SteamAPI_ISteamMatchmaking_AddRequestLobbyListNumericalFilter + SteamAPI_ISteamMatchmaking_AddRequestLobbyListResultCountFilter + SteamAPI_ISteamMatchmaking_AddRequestLobbyListStringFilter + SteamAPI_ISteamMatchmaking_CreateLobby + SteamAPI_ISteamMatchmaking_DeleteLobbyData + SteamAPI_ISteamMatchmaking_GetFavoriteGame + SteamAPI_ISteamMatchmaking_GetFavoriteGameCount + SteamAPI_ISteamMatchmaking_GetLobbyByIndex + SteamAPI_ISteamMatchmaking_GetLobbyChatEntry + SteamAPI_ISteamMatchmaking_GetLobbyData + SteamAPI_ISteamMatchmaking_GetLobbyDataByIndex + SteamAPI_ISteamMatchmaking_GetLobbyDataCount + SteamAPI_ISteamMatchmaking_GetLobbyGameServer + SteamAPI_ISteamMatchmaking_GetLobbyMemberByIndex + SteamAPI_ISteamMatchmaking_GetLobbyMemberData + SteamAPI_ISteamMatchmaking_GetLobbyMemberLimit + SteamAPI_ISteamMatchmaking_GetLobbyOwner + SteamAPI_ISteamMatchmaking_GetNumLobbyMembers + SteamAPI_ISteamMatchmaking_InviteUserToLobby + SteamAPI_ISteamMatchmaking_JoinLobby + SteamAPI_ISteamMatchmaking_LeaveLobby + SteamAPI_ISteamMatchmaking_RemoveFavoriteGame + SteamAPI_ISteamMatchmaking_RequestLobbyData + SteamAPI_ISteamMatchmaking_RequestLobbyList + SteamAPI_ISteamMatchmaking_SendLobbyChatMsg + SteamAPI_ISteamMatchmaking_SetLinkedLobby + SteamAPI_ISteamMatchmaking_SetLobbyData + SteamAPI_ISteamMatchmaking_SetLobbyGameServer + SteamAPI_ISteamMatchmaking_SetLobbyJoinable + SteamAPI_ISteamMatchmaking_SetLobbyMemberData + SteamAPI_ISteamMatchmaking_SetLobbyMemberLimit + SteamAPI_ISteamMatchmaking_SetLobbyOwner + SteamAPI_ISteamMatchmaking_SetLobbyType + SteamAPI_Init + SteamAPI_InitFlat + SteamAPI_InitSafe + SteamAPI_IsSteamRunning + SteamAPI_RegisterCallResult + SteamAPI_RegisterCallback + SteamAPI_RestartAppIfNecessary + SteamAPI_RunCallbacks + SteamAPI_Shutdown + SteamAPI_SteamMatchmakingServers_v002 + SteamAPI_SteamMatchmaking_v009 + SteamAPI_UnregisterCallResult + SteamAPI_UnregisterCallback + SteamClient + SteamMatchmaking + SteamMatchmakingServers diff --git a/app/src/main/cpp/wn-steamapi-bridge/steam_api_bridge_callbacks.c b/app/src/main/cpp/wn-steamapi-bridge/steam_api_bridge_callbacks.c new file mode 100644 index 000000000..b69a80ce6 --- /dev/null +++ b/app/src/main/cpp/wn-steamapi-bridge/steam_api_bridge_callbacks.c @@ -0,0 +1,252 @@ + +#include +#include +#include +#include +#include + +#define WN_STEAMAPI_EXPORT __declspec(dllexport) + +__attribute__((visibility("default"))) +void wnb_dispatch_callback(int iCallback, const void* data, size_t data_size); +__attribute__((visibility("default"))) +void wnb_dispatch_call_result(uint64_t hAPICall, int io_failure, + const void* data, size_t data_size); + +#define WNB_MAX_LISTENERS 64 + +typedef struct { + void* callback; /* CCallbackBase* */ + int iCallback; /* for CCallback path */ + uint64_t hAPICall; /* for CCallResult path (0 = CCallback) */ +} WnbListener; + +static WnbListener g_listeners[WNB_MAX_LISTENERS]; +static CRITICAL_SECTION g_listeners_cs; +static int g_listeners_inited = 0; + +#define WNB_MAX_PENDING 64 +#define WNB_MAX_PAYLOAD 256 + +typedef struct { + uint64_t hAPICall; /* 0 = empty slot */ + int io_failure; + size_t payload_size; + uint8_t payload[WNB_MAX_PAYLOAD]; +} WnbPendingResult; + +static WnbPendingResult g_pending[WNB_MAX_PENDING]; + +static void listeners_init(void) { + if (g_listeners_inited) return; + InitializeCriticalSection(&g_listeners_cs); + g_listeners_inited = 1; +} + +static void listeners_add(void* pCallback, int iCallback, uint64_t hAPICall) { + if (!pCallback) return; + listeners_init(); + EnterCriticalSection(&g_listeners_cs); + for (int i = 0; i < WNB_MAX_LISTENERS; ++i) { + if (g_listeners[i].callback == NULL) { + g_listeners[i].callback = pCallback; + g_listeners[i].iCallback = iCallback; + g_listeners[i].hAPICall = hAPICall; + break; + } + } + LeaveCriticalSection(&g_listeners_cs); +} + +static void listeners_remove(void* pCallback) { + if (!pCallback) return; + listeners_init(); + EnterCriticalSection(&g_listeners_cs); + for (int i = 0; i < WNB_MAX_LISTENERS; ++i) { + if (g_listeners[i].callback == pCallback) { + g_listeners[i].callback = NULL; + } + } + LeaveCriticalSection(&g_listeners_cs); +} + +typedef void (*VoidFn)(void); +typedef void (*RegisterFn)(void* pCallback, int iCallback); +typedef void (*UnregisterFn)(void* pCallback); +typedef void (*RegisterResultFn)(void* pCallback, uint64_t hAPICall); +typedef void (*UnregisterResultFn)(void* pCallback, uint64_t hAPICall); + +static HMODULE g_gbe_fork = NULL; +static RegisterFn g_gbe_register_callback = NULL; +static UnregisterFn g_gbe_unregister_callback = NULL; +static RegisterResultFn g_gbe_register_call_result = NULL; +static UnregisterResultFn g_gbe_unregister_call_result = NULL; +static VoidFn g_gbe_run_callbacks = NULL; + +static void resolve_gbe_fork(void) { + if (g_gbe_fork != NULL) return; + g_gbe_fork = LoadLibraryA("original_steam_api64.dll"); + if (g_gbe_fork == NULL) { + OutputDebugStringA( + "[wnb-callbacks] LoadLibrary(original_steam_api64.dll) failed"); + return; + } + g_gbe_register_callback = + (RegisterFn)GetProcAddress(g_gbe_fork, "SteamAPI_RegisterCallback"); + g_gbe_unregister_callback = + (UnregisterFn)GetProcAddress(g_gbe_fork, "SteamAPI_UnregisterCallback"); + g_gbe_register_call_result = + (RegisterResultFn)GetProcAddress(g_gbe_fork, "SteamAPI_RegisterCallResult"); + g_gbe_unregister_call_result = + (UnregisterResultFn)GetProcAddress(g_gbe_fork, "SteamAPI_UnregisterCallResult"); + g_gbe_run_callbacks = + (VoidFn)GetProcAddress(g_gbe_fork, "SteamAPI_RunCallbacks"); +} + +WN_STEAMAPI_EXPORT void SteamAPI_RegisterCallback(void* pCallback, int iCallback) { + resolve_gbe_fork(); + if (g_gbe_register_callback != NULL) { + g_gbe_register_callback(pCallback, iCallback); + } + listeners_add(pCallback, iCallback, /*hAPICall=*/0); +} + +WN_STEAMAPI_EXPORT void SteamAPI_UnregisterCallback(void* pCallback) { + resolve_gbe_fork(); + if (g_gbe_unregister_callback != NULL) { + g_gbe_unregister_callback(pCallback); + } + listeners_remove(pCallback); +} + +static void drain_pending_for(void* pCallback, uint64_t hAPICall) { + if (!pCallback || hAPICall == 0) return; + listeners_init(); + uint8_t payload[WNB_MAX_PAYLOAD]; + size_t payload_size = 0; + int io_failure = 0; + int found = 0; + EnterCriticalSection(&g_listeners_cs); + for (int i = 0; i < WNB_MAX_PENDING; ++i) { + if (g_pending[i].hAPICall == hAPICall) { + payload_size = g_pending[i].payload_size; + if (payload_size > WNB_MAX_PAYLOAD) payload_size = WNB_MAX_PAYLOAD; + memcpy(payload, g_pending[i].payload, payload_size); + io_failure = g_pending[i].io_failure; + g_pending[i].hAPICall = 0; + g_pending[i].payload_size = 0; + found = 1; + break; + } + } + LeaveCriticalSection(&g_listeners_cs); + if (!found) return; + listeners_remove(pCallback); + typedef void (*RunResultFn)(void* /*this*/, void* /*pvParam*/, int /*bIOFailure*/); + void** vtable = *(void***)pCallback; + RunResultFn run = (RunResultFn)vtable[1]; + run(pCallback, payload, io_failure); +} + +WN_STEAMAPI_EXPORT void SteamAPI_RegisterCallResult(void* pCallback, uint64_t hAPICall) { + resolve_gbe_fork(); + if (g_gbe_register_call_result != NULL) { + g_gbe_register_call_result(pCallback, hAPICall); + } + listeners_add(pCallback, /*iCallback=*/0, hAPICall); + drain_pending_for(pCallback, hAPICall); +} + +WN_STEAMAPI_EXPORT void SteamAPI_UnregisterCallResult(void* pCallback, uint64_t hAPICall) { + resolve_gbe_fork(); + if (g_gbe_unregister_call_result != NULL) { + g_gbe_unregister_call_result(pCallback, hAPICall); + } + listeners_remove(pCallback); +} + +__attribute__((visibility("default"))) +void wnb_publish_dispatch_pointers(void) { + const char* dir = getenv("WN_STATE_DIR"); + if (!dir || !*dir) dir = "/tmp"; + char path[512]; + snprintf(path, sizeof(path), "%s/wnb_ptrs.txt", dir); + FILE* f = fopen(path, "w"); + if (!f) { + OutputDebugStringA("[wnb-callbacks] publish_dispatch_pointers: fopen failed"); + return; + } + fprintf(f, "dispatch_callback %llu\n", + (unsigned long long)(uintptr_t)wnb_dispatch_callback); + fprintf(f, "dispatch_call_result %llu\n", + (unsigned long long)(uintptr_t)wnb_dispatch_call_result); + fclose(f); +} + +__attribute__((visibility("default"))) +void wnb_dispatch_callback(int iCallback, const void* data, size_t data_size) { + (void)data_size; /* CCallback fan-out doesn't need late-bind; the + listeners_init(); + EnterCriticalSection(&g_listeners_cs); + void* matches[WNB_MAX_LISTENERS]; + int n = 0; + for (int i = 0; i < WNB_MAX_LISTENERS; ++i) { + if (g_listeners[i].callback != NULL + && g_listeners[i].hAPICall == 0 + && g_listeners[i].iCallback == iCallback) { + matches[n++] = g_listeners[i].callback; + } + } + LeaveCriticalSection(&g_listeners_cs); + typedef void (*RunFn)(void* /*this*/, const void* /*pvParam*/); + for (int i = 0; i < n; ++i) { + void** vtable = *(void***)matches[i]; + RunFn run = (RunFn)vtable[0]; + run(matches[i], data); + } +} + +__attribute__((visibility("default"))) +void wnb_dispatch_call_result(uint64_t hAPICall, int io_failure, + const void* data, size_t data_size) { + listeners_init(); + EnterCriticalSection(&g_listeners_cs); + void* matches[WNB_MAX_LISTENERS]; + int n = 0; + for (int i = 0; i < WNB_MAX_LISTENERS; ++i) { + if (g_listeners[i].callback != NULL + && g_listeners[i].hAPICall == hAPICall) { + matches[n++] = g_listeners[i].callback; + g_listeners[i].callback = NULL; + } + } + if (n == 0) { + size_t copy = data_size > WNB_MAX_PAYLOAD ? WNB_MAX_PAYLOAD : data_size; + for (int i = 0; i < WNB_MAX_PENDING; ++i) { + if (g_pending[i].hAPICall == 0) { + g_pending[i].hAPICall = hAPICall; + g_pending[i].io_failure = io_failure; + g_pending[i].payload_size = copy; + if (data && copy) memcpy(g_pending[i].payload, data, copy); + break; + } + } + } + LeaveCriticalSection(&g_listeners_cs); + typedef void (*RunResultFn)(void* /*this*/, const void* /*pvParam*/, int /*bIOFailure*/); + for (int i = 0; i < n; ++i) { + void** vtable = *(void***)matches[i]; + RunResultFn run = (RunResultFn)vtable[1]; + run(matches[i], data, io_failure); + } +} + +extern void wnb_pump_valve_callbacks(void); + +WN_STEAMAPI_EXPORT void SteamAPI_RunCallbacks(void) { + resolve_gbe_fork(); + if (g_gbe_run_callbacks != NULL) { + g_gbe_run_callbacks(); + } + wnb_pump_valve_callbacks(); +} diff --git a/app/src/main/cpp/wn-steamapi-bridge/steam_api_bridge_flat.c b/app/src/main/cpp/wn-steamapi-bridge/steam_api_bridge_flat.c new file mode 100644 index 000000000..2aeba30cb --- /dev/null +++ b/app/src/main/cpp/wn-steamapi-bridge/steam_api_bridge_flat.c @@ -0,0 +1,4952 @@ + +#include +#include +#include + +#define WN_STEAMAPI_EXPORT __declspec(dllexport) + +WN_STEAMAPI_EXPORT uint32_t SteamAPI_ISteamAppList_GetNumInstalledApps(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint32_t (*Fn)(void*); + return ((Fn)vt[0])(self); +} +WN_STEAMAPI_EXPORT uint32_t SteamAPI_ISteamAppList_GetInstalledApps(void* self, void* pvecAppID, uint32_t cMax) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint32_t (*Fn)(void*, void*, uint32_t); + return ((Fn)vt[1])(self, pvecAppID, cMax); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamAppList_GetAppName(void* self, uint32_t appId, void* pName, int cMaxName) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint32_t, void*, int); + return ((Fn)vt[2])(self, appId, pName, cMaxName); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamAppList_GetAppInstallDir(void* self, uint32_t appId, void* pDir, int cMaxDir) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint32_t, void*, int); + return ((Fn)vt[3])(self, appId, pDir, cMaxDir); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamAppList_GetAppBuildId(void* self, uint32_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint32_t); + return ((Fn)vt[4])(self, _a0); +} + +WN_STEAMAPI_EXPORT int SteamAPI_ISteamApps_BIsSubscribed(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[0])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamApps_BIsLowViolence(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[1])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamApps_BIsCybercafe(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[2])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamApps_BIsVACBanned(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[3])(self); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamApps_GetCurrentGameLanguage(void* self) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*); + return ((Fn)vt[4])(self); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamApps_GetAvailableGameLanguages(void* self) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*); + return ((Fn)vt[5])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamApps_BIsSubscribedApp(void* self, uint32_t appId) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint32_t); + return ((Fn)vt[6])(self, appId); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamApps_BIsDlcInstalled(void* self, uint32_t appId) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint32_t); + return ((Fn)vt[7])(self, appId); +} +WN_STEAMAPI_EXPORT uint32_t SteamAPI_ISteamApps_GetEarliestPurchaseUnixTime(void* self, uint32_t app_id) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint32_t (*Fn)(void*, uint32_t); + return ((Fn)vt[8])(self, app_id); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamApps_BIsSubscribedFromFreeWeekend(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[9])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamApps_GetDLCCount(void* self, uint32_t appId) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint32_t); + return ((Fn)vt[10])(self, appId); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamApps_BGetDLCDataByIndex(void* self, uint32_t appId, int iDLC, void* pAppID, void* pbAvailable, void* pchName, int cchNameBufferSize) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint32_t, int, void*, void*, void*, int); + return ((Fn)vt[11])(self, appId, iDLC, pAppID, pbAvailable, pchName, cchNameBufferSize); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamApps_InstallDLC(void* self, uint32_t _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint32_t); + ((Fn)vt[12])(self, _a0); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamApps_UninstallDLC(void* self, uint32_t _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint32_t); + ((Fn)vt[13])(self, _a0); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamApps_RequestAppProofOfPurchaseKey(void* self, uint32_t _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint32_t); + ((Fn)vt[14])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamApps_GetCurrentBetaName(void* self, void* pchName, int cchNameBufferSize) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, int); + return ((Fn)vt[15])(self, pchName, cchNameBufferSize); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamApps_MarkContentCorrupt(void* self, void* _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*); + return ((Fn)vt[16])(self, _a0); +} +WN_STEAMAPI_EXPORT uint32_t SteamAPI_ISteamApps_GetInstalledDepots(void* self, uint32_t appID, void* pvecDepots, uint32_t cMaxDepots) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint32_t (*Fn)(void*, uint32_t, void*, uint32_t); + return ((Fn)vt[17])(self, appID, pvecDepots, cMaxDepots); +} +WN_STEAMAPI_EXPORT uint32_t SteamAPI_ISteamApps_GetAppInstallDir(void* self, uint32_t appId, void* buf, uint32_t cap) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint32_t (*Fn)(void*, uint32_t, void*, uint32_t); + return ((Fn)vt[18])(self, appId, buf, cap); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamApps_BIsAppInstalled(void* self, uint32_t appId) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint32_t); + return ((Fn)vt[19])(self, appId); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamApps_GetAppOwner(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*); + return ((Fn)vt[20])(self); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamApps_GetLaunchQueryParam(void* self, void* _a0) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, void*); + return ((Fn)vt[21])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamApps_GetDlcDownloadProgress(void* self, uint32_t appID, void* pBytesDownloaded, void* pBytesTotal) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint32_t, void*, void*); + return ((Fn)vt[22])(self, appID, pBytesDownloaded, pBytesTotal); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamApps_GetAppBuildId(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[23])(self); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamApps_RequestAllProofOfPurchaseKeys(void* self) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*); + ((Fn)vt[24])(self); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamApps_GetFileDetails(void* self, void* pchFile) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, void*); + return ((Fn)vt[25])(self, pchFile); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamApps_GetLaunchCommandLine(void* self, void* buf, int cubMax) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, int); + return ((Fn)vt[26])(self, buf, cubMax); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamApps_BIsSubscribedFromFamilySharing(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[27])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamApps_BIsTimedTrial(void* self, void* pcSecondsAllowed, void* pcSecondsPlayed) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, void*); + return ((Fn)vt[28])(self, pcSecondsAllowed, pcSecondsPlayed); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamApps_SetDlcContext(void* self, void* _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*); + return ((Fn)vt[29])(self, _a0); +} + +WN_STEAMAPI_EXPORT int SteamAPI_ISteamClient_CreateSteamPipe(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[0])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamClient_BReleaseSteamPipe(void* self, int pipe) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int); + return ((Fn)vt[1])(self, pipe); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamClient_ConnectToGlobalUser(void* self, int pipe) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int); + return ((Fn)vt[2])(self, pipe); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamClient_CreateLocalUser(void* self, void* pipe_inout, void* _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, void*); + return ((Fn)vt[3])(self, pipe_inout, _a1); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamClient_ReleaseUser(void* self, int pipe, int user) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, int, int); + ((Fn)vt[4])(self, pipe, user); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamClient_GetISteamUser(void* self, void* _a0, void* _a1, void* _a2) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, void*, void*, void*); + return ((Fn)vt[5])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamClient_GetISteamGameServer(void* self, int _a0, int _a1, void* _a2) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, int, int, void*); + return ((Fn)vt[6])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamClient_SetLocalIPBinding(void* self, uint32_t _a0, uint16_t _a1) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint32_t, uint16_t); + ((Fn)vt[7])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamClient_GetISteamFriends(void* self, void* _a0, void* _a1, void* _a2) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, void*, void*, void*); + return ((Fn)vt[8])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamClient_GetISteamUtils(void* self, void* _a0, void* _a1) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, void*, void*); + return ((Fn)vt[9])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamClient_GetISteamMatchmaking(void* self, int _a0, int _a1, void* _a2) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, int, int, void*); + return ((Fn)vt[10])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamClient_GetISteamMatchmakingServers(void* self, int _a0, int _a1, void* _a2) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, int, int, void*); + return ((Fn)vt[11])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamClient_GetISteamGenericInterface(void* self, int _a0, int _a1, void* version) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, int, int, void*); + return ((Fn)vt[12])(self, _a0, _a1, version); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamClient_GetISteamUserStats(void* self, void* _a0, void* _a1, void* _a2) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, void*, void*, void*); + return ((Fn)vt[13])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamClient_GetISteamApps(void* self, void* _a0, void* _a1, void* _a2) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, void*, void*, void*); + return ((Fn)vt[14])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamClient_GetISteamNetworking(void* self, int _a0, int _a1, void* _a2) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, int, int, void*); + return ((Fn)vt[15])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamClient_GetISteamRemoteStorage(void* self, void* _a0, void* _a1, void* _a2) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, void*, void*, void*); + return ((Fn)vt[16])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamClient_GetISteamScreenshots(void* self, int _a0, int _a1, void* _a2) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, int, int, void*); + return ((Fn)vt[17])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamClient_GetISteamUGC(void* self, int _a0, int _a1, void* _a2) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, int, int, void*); + return ((Fn)vt[18])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamClient_GetISteamAppList(void* self, int _a0, int _a1, void* _a2) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, int, int, void*); + return ((Fn)vt[19])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamClient_GetISteamMusic(void* self, int _a0, int _a1, void* _a2) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, int, int, void*); + return ((Fn)vt[20])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamClient_GetISteamMusicRemote(void* self, int _a0, int _a1, void* _a2) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, int, int, void*); + return ((Fn)vt[21])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamClient_GetISteamHTMLSurface(void* self, int _a0, int _a1, void* _a2) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, int, int, void*); + return ((Fn)vt[22])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamClient_Set_SteamAPI_CPostAPIResultInProcess(void* self, void* _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, void*); + ((Fn)vt[23])(self, _a0); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamClient_Remove_SteamAPI_CPostAPIResultInProcess(void* self, void* _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, void*); + ((Fn)vt[24])(self, _a0); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamClient_Set_SteamAPI_CCheckCallbackRegisteredInProcess(void* self, void* _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, void*); + ((Fn)vt[25])(self, _a0); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamClient_GetISteamInventory(void* self, int _a0, int _a1, void* _a2) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, int, int, void*); + return ((Fn)vt[26])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamClient_GetISteamVideo(void* self, int _a0, int _a1, void* _a2) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, int, int, void*); + return ((Fn)vt[27])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamClient_GetISteamParentalSettings(void* self, int _a0, int _a1, void* _a2) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, int, int, void*); + return ((Fn)vt[28])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamClient_GetISteamInput(void* self, int _a0, int _a1, void* _a2) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, int, int, void*); + return ((Fn)vt[29])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamClient_GetISteamParties(void* self, int _a0, int _a1, void* _a2) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, int, int, void*); + return ((Fn)vt[30])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamClient_GetISteamRemotePlay(void* self, int _a0, int _a1, void* _a2) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, int, int, void*); + return ((Fn)vt[31])(self, _a0, _a1, _a2); +} + +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamFriends_GetPersonaName(void* self) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*); + return ((Fn)vt[0])(self); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamFriends_SetPersonaName(void* self, void* pchPersonaName) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, void*); + return ((Fn)vt[1])(self, pchPersonaName); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamFriends_GetPersonaState(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[2])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamFriends_GetFriendCount(void* self, void* _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*); + return ((Fn)vt[3])(self, _a0); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamFriends_GetFriendByIndex(void* self, int idx, void* _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, int, void*); + return ((Fn)vt[4])(self, idx, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamFriends_GetFriendRelationship(void* self, uint64_t sid) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t); + return ((Fn)vt[5])(self, sid); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamFriends_GetFriendPersonaState(void* self, uint64_t sid) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t); + return ((Fn)vt[6])(self, sid); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamFriends_GetFriendPersonaName(void* self, uint64_t sid) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, uint64_t); + return ((Fn)vt[7])(self, sid); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamFriends_GetFriendGamePlayed(void* self, uint64_t sid, void* pFriendGameInfo) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*); + return ((Fn)vt[8])(self, sid, pFriendGameInfo); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamFriends_GetFriendPersonaNameHistory(void* self, uint64_t _a0, int _a1) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, uint64_t, int); + return ((Fn)vt[9])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamFriends_GetFriendSteamLevel(void* self, uint64_t sid) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t); + return ((Fn)vt[10])(self, sid); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamFriends_GetPlayerNickname(void* self, uint64_t sid) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, uint64_t); + return ((Fn)vt[11])(self, sid); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamFriends_GetFriendsGroupCount(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[12])(self); +} +WN_STEAMAPI_EXPORT int16_t SteamAPI_ISteamFriends_GetFriendsGroupIDByIndex(void* self, int _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int16_t (*Fn)(void*, int); + return ((Fn)vt[13])(self, _a0); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamFriends_GetFriendsGroupName(void* self, int16_t _a0) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, int16_t); + return ((Fn)vt[14])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamFriends_GetFriendsGroupMembersCount(void* self, int16_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int16_t); + return ((Fn)vt[15])(self, _a0); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamFriends_GetFriendsGroupMembersList(void* self, int16_t _a0, void* _a1, int _a2) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, int16_t, void*, int); + ((Fn)vt[16])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamFriends_HasFriend(void* self, uint64_t sid, int iFriendFlags) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, int); + return ((Fn)vt[17])(self, sid, iFriendFlags); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamFriends_GetClanCount(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[18])(self); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamFriends_GetClanByIndex(void* self, int _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, int); + return ((Fn)vt[19])(self, _a0); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamFriends_GetClanName(void* self, uint64_t _a0) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, uint64_t); + return ((Fn)vt[20])(self, _a0); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamFriends_GetClanTag(void* self, uint64_t _a0) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, uint64_t); + return ((Fn)vt[21])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamFriends_GetClanActivityCounts(void* self, uint64_t _a0, void* _a1, void* _a2, void* _a3) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*, void*, void*); + return ((Fn)vt[22])(self, _a0, _a1, _a2, _a3); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamFriends_DownloadClanActivityCounts(void* self, void* _a0, void* _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, void*, void*); + return ((Fn)vt[23])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamFriends_GetFriendCountFromSource(void* self, uint64_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t); + return ((Fn)vt[24])(self, _a0); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamFriends_GetFriendFromSourceByIndex(void* self, uint64_t _a0, int _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint64_t, int); + return ((Fn)vt[25])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamFriends_IsUserInSource(void* self, uint64_t _a0, uint64_t _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, uint64_t); + return ((Fn)vt[26])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamFriends_SetInGameVoiceSpeaking(void* self, uint64_t _a0, int _a1) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint64_t, int); + ((Fn)vt[27])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamFriends_ActivateGameOverlay(void* self, void* dialog) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, void*); + ((Fn)vt[28])(self, dialog); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamFriends_ActivateGameOverlayToUser(void* self, void* dialog, uint64_t sid) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, void*, uint64_t); + ((Fn)vt[29])(self, dialog, sid); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamFriends_ActivateGameOverlayToWebPage(void* self, void* url, void* _a1) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, void*, void*); + ((Fn)vt[30])(self, url, _a1); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamFriends_ActivateGameOverlayToStore(void* self, uint32_t appid, void* _a1) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint32_t, void*); + ((Fn)vt[31])(self, appid, _a1); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamFriends_SetPlayedWith(void* self, uint64_t _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint64_t); + ((Fn)vt[32])(self, _a0); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamFriends_ActivateGameOverlayInviteDialog(void* self, uint64_t lobby_sid) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint64_t); + ((Fn)vt[33])(self, lobby_sid); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamFriends_GetSmallFriendAvatar(void* self, uint64_t steamID) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t); + return ((Fn)vt[34])(self, steamID); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamFriends_GetMediumFriendAvatar(void* self, uint64_t steamID) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t); + return ((Fn)vt[35])(self, steamID); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamFriends_GetLargeFriendAvatar(void* self, uint64_t steamID) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t); + return ((Fn)vt[36])(self, steamID); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamFriends_RequestUserInformation(void* self, uint64_t steamID, int bRequireNameOnly) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, int); + return ((Fn)vt[37])(self, steamID, bRequireNameOnly); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamFriends_RequestClanOfficerList(void* self, uint64_t clanSid) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint64_t); + return ((Fn)vt[38])(self, clanSid); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamFriends_GetClanOwner(void* self, uint64_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint64_t); + return ((Fn)vt[39])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamFriends_GetClanOfficerCount(void* self, uint64_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t); + return ((Fn)vt[40])(self, _a0); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamFriends_GetClanOfficerByIndex(void* self, uint64_t _a0, int _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint64_t, int); + return ((Fn)vt[41])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT uint32_t SteamAPI_ISteamFriends_GetUserRestrictions(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint32_t (*Fn)(void*); + return ((Fn)vt[42])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamFriends_SetRichPresence(void* self, void* pchKey, void* pchValue) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, void*); + return ((Fn)vt[43])(self, pchKey, pchValue); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamFriends_ClearRichPresence(void* self) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*); + ((Fn)vt[44])(self); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamFriends_GetFriendRichPresence(void* self, uint64_t steamID, void* pchKey) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, uint64_t, void*); + return ((Fn)vt[45])(self, steamID, pchKey); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamFriends_GetFriendRichPresenceKeyCount(void* self, uint64_t steamID) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t); + return ((Fn)vt[46])(self, steamID); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamFriends_GetFriendRichPresenceKeyByIndex(void* self, uint64_t steamID, int idx) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, uint64_t, int); + return ((Fn)vt[47])(self, steamID, idx); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamFriends_RequestFriendRichPresence(void* self, uint64_t steamID) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint64_t); + ((Fn)vt[48])(self, steamID); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamFriends_InviteUserToGame(void* self, uint64_t _a0, void* _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*); + return ((Fn)vt[49])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamFriends_GetCoplayFriendCount(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[50])(self); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamFriends_GetCoplayFriend(void* self, int _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, int); + return ((Fn)vt[51])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamFriends_GetFriendCoplayTime(void* self, uint64_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t); + return ((Fn)vt[52])(self, _a0); +} +WN_STEAMAPI_EXPORT uint32_t SteamAPI_ISteamFriends_GetFriendCoplayGame(void* self, uint64_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint32_t (*Fn)(void*, uint64_t); + return ((Fn)vt[53])(self, _a0); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamFriends_JoinClanChatRoom(void* self, uint64_t clanSid) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint64_t); + return ((Fn)vt[54])(self, clanSid); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamFriends_LeaveClanChatRoom(void* self, uint64_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t); + return ((Fn)vt[55])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamFriends_GetClanChatMemberCount(void* self, uint64_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t); + return ((Fn)vt[56])(self, _a0); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamFriends_GetChatMemberByIndex(void* self, uint64_t _a0, int _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint64_t, int); + return ((Fn)vt[57])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamFriends_SendClanChatMessage(void* self, uint64_t _a0, void* _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*); + return ((Fn)vt[58])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamFriends_GetClanChatMessage(void* self, uint64_t _a0, int _a1, void* _a2, int _a3, void* _a4, void* _a5) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, int, void*, int, void*, void*); + return ((Fn)vt[59])(self, _a0, _a1, _a2, _a3, _a4, _a5); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamFriends_IsClanChatAdmin(void* self, uint64_t _a0, uint64_t _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, uint64_t); + return ((Fn)vt[60])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamFriends_IsClanChatWindowOpenInSteam(void* self, uint64_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t); + return ((Fn)vt[61])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamFriends_OpenClanChatWindowInSteam(void* self, uint64_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t); + return ((Fn)vt[62])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamFriends_CloseClanChatWindowInSteam(void* self, uint64_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t); + return ((Fn)vt[63])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamFriends_SetListenForFriendsMessages(void* self, int _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int); + return ((Fn)vt[64])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamFriends_ReplyToFriendMessage(void* self, uint64_t _a0, void* _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*); + return ((Fn)vt[65])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamFriends_GetFriendMessage(void* self, uint64_t _a0, int _a1, void* _a2, int _a3, void* _a4) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, int, void*, int, void*); + return ((Fn)vt[66])(self, _a0, _a1, _a2, _a3, _a4); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamFriends_GetFollowerCount(void* self, uint64_t sid) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint64_t); + return ((Fn)vt[67])(self, sid); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamFriends_IsFollowing(void* self, uint64_t sid) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint64_t); + return ((Fn)vt[68])(self, sid); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamFriends_EnumerateFollowingList(void* self, void* _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, void*); + return ((Fn)vt[69])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamFriends_IsClanPublic(void* self, uint64_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t); + return ((Fn)vt[70])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamFriends_IsClanOfficialGameGroup(void* self, uint64_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t); + return ((Fn)vt[71])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamFriends_GetNumChatsWithUnreadPriorityMessages(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[72])(self); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamFriends_ActivateGameOverlayRemotePlayTogetherInviteDialog(void* self, uint64_t _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint64_t); + ((Fn)vt[73])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamFriends_RegisterProtocolInOverlayBrowser(void* self, void* _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*); + return ((Fn)vt[74])(self, _a0); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamFriends_ActivateGameOverlayInviteDialogConnectString(void* self, void* _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, void*); + ((Fn)vt[75])(self, _a0); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamFriends_RequestEquippedProfileItems(void* self, uint64_t sid) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint64_t); + return ((Fn)vt[76])(self, sid); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamFriends_BHasEquippedProfileItem(void* self, uint64_t _a0, int _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, int); + return ((Fn)vt[77])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamFriends_GetProfileItemPropertyString(void* self, uint64_t _a0, int _a1, int _a2) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, uint64_t, int, int); + return ((Fn)vt[78])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT uint32_t SteamAPI_ISteamFriends_GetProfileItemPropertyUint(void* self, uint64_t _a0, int _a1, int _a2) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint32_t (*Fn)(void*, uint64_t, int, int); + return ((Fn)vt[79])(self, _a0, _a1, _a2); +} + +WN_STEAMAPI_EXPORT void SteamAPI_ISteamGameServer_SetProduct(void* self, void* _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, void*); + ((Fn)vt[0])(self, _a0); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamGameServer_SetGameDescription(void* self, void* _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, void*); + ((Fn)vt[1])(self, _a0); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamGameServer_SetModDir(void* self, void* _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, void*); + ((Fn)vt[2])(self, _a0); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamGameServer_SetDedicatedServer(void* self, int _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, int); + ((Fn)vt[3])(self, _a0); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamGameServer_LogOn(void* self, void* _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, void*); + ((Fn)vt[4])(self, _a0); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamGameServer_LogOnAnonymous(void* self) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*); + ((Fn)vt[5])(self); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamGameServer_LogOff(void* self) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*); + ((Fn)vt[6])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamGameServer_BLoggedOn(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[7])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamGameServer_BSecure(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[8])(self); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamGameServer_GetSteamID(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*); + return ((Fn)vt[9])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamGameServer_WasRestartRequested(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[10])(self); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamGameServer_SetMaxPlayerCount(void* self, int _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, int); + ((Fn)vt[11])(self, _a0); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamGameServer_SetBotPlayerCount(void* self, int _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, int); + ((Fn)vt[12])(self, _a0); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamGameServer_SetServerName(void* self, void* _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, void*); + ((Fn)vt[13])(self, _a0); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamGameServer_SetMapName(void* self, void* _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, void*); + ((Fn)vt[14])(self, _a0); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamGameServer_SetPasswordProtected(void* self, int _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, int); + ((Fn)vt[15])(self, _a0); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamGameServer_SetSpectatorPort(void* self, uint16_t _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint16_t); + ((Fn)vt[16])(self, _a0); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamGameServer_SetSpectatorServerName(void* self, void* _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, void*); + ((Fn)vt[17])(self, _a0); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamGameServer_ClearAllKeyValues(void* self) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*); + ((Fn)vt[18])(self); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamGameServer_SetKeyValue(void* self, void* _a0, void* _a1) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, void*, void*); + ((Fn)vt[19])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamGameServer_SetGameTags(void* self, void* _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, void*); + ((Fn)vt[20])(self, _a0); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamGameServer_SetGameData(void* self, void* _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, void*); + ((Fn)vt[21])(self, _a0); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamGameServer_SetRegion(void* self, void* _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, void*); + ((Fn)vt[22])(self, _a0); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamGameServer_SetAdvertiseServerActive(void* self, int _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, int); + ((Fn)vt[23])(self, _a0); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamGameServer_GetAuthSessionTicket(void* self, void* _a0, int _a1, void* pcb, void* _a3) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, void*, int, void*, void*); + return ((Fn)vt[24])(self, _a0, _a1, pcb, _a3); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamGameServer_BeginAuthSession(void* self, void* _a0, int _a1, uint64_t _a2) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, int, uint64_t); + return ((Fn)vt[25])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamGameServer_EndAuthSession(void* self, uint64_t _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint64_t); + ((Fn)vt[26])(self, _a0); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamGameServer_CancelAuthTicket(void* self, uint64_t _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint64_t); + ((Fn)vt[27])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamGameServer_UserHasLicenseForApp(void* self, uint64_t _a0, uint32_t _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, uint32_t); + return ((Fn)vt[28])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamGameServer_RequestUserGroupStatus(void* self, uint64_t _a0, uint64_t _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, uint64_t); + return ((Fn)vt[29])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamGameServer_GetGameplayStats(void* self) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*); + ((Fn)vt[30])(self); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamGameServer_GetServerReputation(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*); + return ((Fn)vt[31])(self); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamGameServer_GetPublicIP(void* self, void* out) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, void*); + ((Fn)vt[32])(self, out); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamGameServer_HandleIncomingPacket(void* self, void* _a0, int _a1, uint32_t _a2, uint16_t _a3) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, int, uint32_t, uint16_t); + return ((Fn)vt[33])(self, _a0, _a1, _a2, _a3); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamGameServer_GetNextOutgoingPacket(void* self, void* _a0, int _a1, void* _a2, void* _a3) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, int, void*, void*); + return ((Fn)vt[34])(self, _a0, _a1, _a2, _a3); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamGameServer_AssociateWithClan(void* self, uint64_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint64_t); + return ((Fn)vt[35])(self, _a0); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamGameServer_ComputeNewPlayerCompatibility(void* self, uint64_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint64_t); + return ((Fn)vt[36])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamGameServer_SendUserConnectAndAuthenticate_DEPRECATED(void* self, uint32_t _a0, void* _a1, uint32_t _a2, void* _a3) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint32_t, void*, uint32_t, void*); + return ((Fn)vt[37])(self, _a0, _a1, _a2, _a3); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamGameServer_CreateUnauthenticatedUserConnection(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*); + return ((Fn)vt[38])(self); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamGameServer_SendUserDisconnect_DEPRECATED(void* self, uint64_t _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint64_t); + ((Fn)vt[39])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamGameServer_BUpdateUserData(void* self, uint64_t _a0, void* _a1, uint32_t _a2) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*, uint32_t); + return ((Fn)vt[40])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamGameServer_GetAuthTicketForWebApi(void* self, void* _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, void*); + return ((Fn)vt[41])(self, _a0); +} + +WN_STEAMAPI_EXPORT int SteamAPI_ISteamHTMLSurface_Init(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[0])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamHTMLSurface_Shutdown(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[1])(self); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamHTMLSurface_CreateBrowser(void* self, void* _a0, void* _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, void*, void*); + return ((Fn)vt[2])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamHTMLSurface_RemoveBrowser(void* self, uint32_t _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint32_t); + ((Fn)vt[3])(self, _a0); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamHTMLSurface_LoadURL(void* self, uint32_t _a0, void* _a1, void* _a2) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint32_t, void*, void*); + ((Fn)vt[4])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamHTMLSurface_SetSize(void* self, uint32_t _a0, uint32_t _a1, uint32_t _a2) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint32_t, uint32_t, uint32_t); + ((Fn)vt[5])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamHTMLSurface_StopLoad(void* self, uint32_t _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint32_t); + ((Fn)vt[6])(self, _a0); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamHTMLSurface_Reload(void* self, uint32_t _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint32_t); + ((Fn)vt[7])(self, _a0); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamHTMLSurface_GoBack(void* self, uint32_t _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint32_t); + ((Fn)vt[8])(self, _a0); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamHTMLSurface_GoForward(void* self, uint32_t _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint32_t); + ((Fn)vt[9])(self, _a0); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamHTMLSurface_AddHeader(void* self, uint32_t _a0, void* _a1, void* _a2) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint32_t, void*, void*); + ((Fn)vt[10])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamHTMLSurface_ExecuteJavascript(void* self, uint32_t _a0, void* _a1) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint32_t, void*); + ((Fn)vt[11])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamHTMLSurface_MouseUp(void* self, uint32_t _a0, int _a1) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint32_t, int); + ((Fn)vt[12])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamHTMLSurface_MouseDown(void* self, uint32_t _a0, int _a1) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint32_t, int); + ((Fn)vt[13])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamHTMLSurface_MouseDoubleClick(void* self, uint32_t _a0, int _a1) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint32_t, int); + ((Fn)vt[14])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamHTMLSurface_MouseMove(void* self, uint32_t _a0, int _a1, int _a2) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint32_t, int, int); + ((Fn)vt[15])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamHTMLSurface_MouseWheel(void* self, uint32_t _a0, int32_t _a1) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint32_t, int32_t); + ((Fn)vt[16])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamHTMLSurface_KeyDown(void* self, uint32_t _a0, uint32_t _a1, int _a2) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint32_t, uint32_t, int); + ((Fn)vt[17])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamHTMLSurface_KeyUp(void* self, uint32_t _a0, uint32_t _a1, int _a2) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint32_t, uint32_t, int); + ((Fn)vt[18])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamHTMLSurface_KeyChar(void* self, uint32_t _a0, uint32_t _a1, int _a2) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint32_t, uint32_t, int); + ((Fn)vt[19])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamHTMLSurface_SetHorizontalScroll(void* self, uint32_t _a0, uint32_t _a1) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint32_t, uint32_t); + ((Fn)vt[20])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamHTMLSurface_SetVerticalScroll(void* self, uint32_t _a0, uint32_t _a1) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint32_t, uint32_t); + ((Fn)vt[21])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamHTMLSurface_SetKeyFocus(void* self, uint32_t _a0, int _a1) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint32_t, int); + ((Fn)vt[22])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamHTMLSurface_ViewSource(void* self, uint32_t _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint32_t); + ((Fn)vt[23])(self, _a0); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamHTMLSurface_CopyToClipboard(void* self, uint32_t _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint32_t); + ((Fn)vt[24])(self, _a0); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamHTMLSurface_PasteFromClipboard(void* self, uint32_t _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint32_t); + ((Fn)vt[25])(self, _a0); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamHTMLSurface_Find(void* self, uint32_t _a0, void* _a1, int _a2, int _a3) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint32_t, void*, int, int); + ((Fn)vt[26])(self, _a0, _a1, _a2, _a3); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamHTMLSurface_StopFind(void* self, uint32_t _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint32_t); + ((Fn)vt[27])(self, _a0); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamHTMLSurface_GetLinkAtPosition(void* self, uint32_t _a0, int _a1, int _a2) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint32_t, int, int); + ((Fn)vt[28])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamHTMLSurface_SetCookie(void* self, void* _a0, void* _a1, void* _a2, void* _a3, uint32_t _a4, int _a5, int _a6) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, void*, void*, void*, void*, uint32_t, int, int); + ((Fn)vt[29])(self, _a0, _a1, _a2, _a3, _a4, _a5, _a6); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamHTMLSurface_SetPageScaleFactor(void* self, uint32_t _a0, float _a1, int _a2, int _a3) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint32_t, float, int, int); + ((Fn)vt[30])(self, _a0, _a1, _a2, _a3); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamHTMLSurface_SetBackgroundMode(void* self, uint32_t _a0, int _a1) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint32_t, int); + ((Fn)vt[31])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamHTMLSurface_SetDPIScalingFactor(void* self, uint32_t _a0, float _a1) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint32_t, float); + ((Fn)vt[32])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamHTMLSurface_OpenDeveloperTools(void* self, uint32_t _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint32_t); + ((Fn)vt[33])(self, _a0); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamHTMLSurface_AllowStartRequest(void* self, uint32_t _a0, int _a1) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint32_t, int); + ((Fn)vt[34])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamHTMLSurface_JSDialogResponse(void* self, uint32_t _a0, int _a1) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint32_t, int); + ((Fn)vt[35])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamHTMLSurface_FileLoadDialogResponse(void* self, uint32_t _a0, void* _a1) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint32_t, void*); + ((Fn)vt[36])(self, _a0, _a1); +} + +WN_STEAMAPI_EXPORT int SteamAPI_ISteamInput_Init(void* self, int _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int); + return ((Fn)vt[0])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamInput_Shutdown(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[1])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamInput_SetInputActionManifestFilePath(void* self, void* _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*); + return ((Fn)vt[2])(self, _a0); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamInput_RunFrame(void* self, int _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, int); + ((Fn)vt[3])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamInput_BWaitForData(void* self, int _a0, uint32_t _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int, uint32_t); + return ((Fn)vt[4])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamInput_BNewDataAvailable(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[5])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamInput_GetConnectedControllers(void* self, void* _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*); + return ((Fn)vt[6])(self, _a0); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamInput_EnableDeviceCallbacks(void* self) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*); + ((Fn)vt[7])(self); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamInput_EnableActionEventCallbacks(void* self, void* _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, void*); + ((Fn)vt[8])(self, _a0); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamInput_GetActionSetHandle(void* self, void* _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, void*); + return ((Fn)vt[9])(self, _a0); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamInput_ActivateActionSet(void* self, uint64_t _a0, uint64_t _a1) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint64_t, uint64_t); + ((Fn)vt[10])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamInput_GetCurrentActionSet(void* self, uint64_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint64_t); + return ((Fn)vt[11])(self, _a0); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamInput_ActivateActionSetLayer(void* self, uint64_t _a0, uint64_t _a1) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint64_t, uint64_t); + ((Fn)vt[12])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamInput_DeactivateActionSetLayer(void* self, uint64_t _a0, uint64_t _a1) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint64_t, uint64_t); + ((Fn)vt[13])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamInput_DeactivateAllActionSetLayers(void* self, uint64_t _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint64_t); + ((Fn)vt[14])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamInput_GetActiveActionSetLayers(void* self, uint64_t _a0, void* _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*); + return ((Fn)vt[15])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamInput_GetDigitalActionHandle(void* self, void* _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, void*); + return ((Fn)vt[16])(self, _a0); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamInput_GetDigitalActionData(void* self, uint64_t _a0, uint64_t _a1, void* outData) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint64_t, uint64_t, void*); + ((Fn)vt[17])(self, _a0, _a1, outData); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamInput_GetDigitalActionOrigins(void* self, uint64_t _a0, uint64_t _a1, uint64_t _a2, void* _a3) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, uint64_t, uint64_t, void*); + return ((Fn)vt[18])(self, _a0, _a1, _a2, _a3); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamInput_GetStringForDigitalActionName(void* self, uint64_t _a0) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, uint64_t); + return ((Fn)vt[19])(self, _a0); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamInput_GetAnalogActionHandle(void* self, void* _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, void*); + return ((Fn)vt[20])(self, _a0); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamInput_GetAnalogActionData(void* self, uint64_t _a0, uint64_t _a1, void* outData) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint64_t, uint64_t, void*); + ((Fn)vt[21])(self, _a0, _a1, outData); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamInput_GetAnalogActionOrigins(void* self, uint64_t _a0, uint64_t _a1, uint64_t _a2, void* _a3) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, uint64_t, uint64_t, void*); + return ((Fn)vt[22])(self, _a0, _a1, _a2, _a3); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamInput_GetGlyphPNGForActionOrigin(void* self, int _a0, int _a1, uint32_t _a2) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, int, int, uint32_t); + return ((Fn)vt[23])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamInput_GetGlyphSVGForActionOrigin(void* self, int _a0, uint32_t _a1) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, int, uint32_t); + return ((Fn)vt[24])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamInput_GetGlyphForActionOrigin_Legacy(void* self, int _a0) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, int); + return ((Fn)vt[25])(self, _a0); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamInput_GetStringForActionOrigin(void* self, int _a0) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, int); + return ((Fn)vt[26])(self, _a0); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamInput_GetStringForAnalogActionName(void* self, uint64_t _a0) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, uint64_t); + return ((Fn)vt[27])(self, _a0); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamInput_StopAnalogActionMomentum(void* self, uint64_t _a0, uint64_t _a1) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint64_t, uint64_t); + ((Fn)vt[28])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamInput_GetMotionData(void* self, uint64_t _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint64_t); + ((Fn)vt[29])(self, _a0); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamInput_TriggerVibration(void* self, uint64_t _a0, uint16_t _a1, uint16_t _a2) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint64_t, uint16_t, uint16_t); + ((Fn)vt[30])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamInput_TriggerVibrationExtended(void* self, uint64_t _a0, uint16_t _a1, uint16_t _a2, uint16_t _a3, uint16_t _a4) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint64_t, uint16_t, uint16_t, uint16_t, uint16_t); + ((Fn)vt[31])(self, _a0, _a1, _a2, _a3, _a4); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamInput_TriggerSimpleHapticEvent(void* self, uint64_t _a0, int _a1, uint8_t _a2, char _a3, uint8_t _a4, char _a5) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint64_t, int, uint8_t, char, uint8_t, char); + ((Fn)vt[32])(self, _a0, _a1, _a2, _a3, _a4, _a5); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamInput_SetLEDColor(void* self, uint64_t _a0, uint8_t _a1, uint8_t _a2, uint8_t _a3, uint32_t _a4) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint64_t, uint8_t, uint8_t, uint8_t, uint32_t); + ((Fn)vt[33])(self, _a0, _a1, _a2, _a3, _a4); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamInput_Legacy_TriggerHapticPulse(void* self, uint64_t _a0, int _a1, uint16_t _a2) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint64_t, int, uint16_t); + ((Fn)vt[34])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamInput_Legacy_TriggerRepeatedHapticPulse(void* self, uint64_t _a0, int _a1, uint16_t _a2, uint16_t _a3, uint16_t _a4, uint32_t _a5) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint64_t, int, uint16_t, uint16_t, uint16_t, uint32_t); + ((Fn)vt[35])(self, _a0, _a1, _a2, _a3, _a4, _a5); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamInput_ShowBindingPanel(void* self, uint64_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t); + return ((Fn)vt[36])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamInput_GetInputTypeForHandle(void* self, uint64_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t); + return ((Fn)vt[37])(self, _a0); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamInput_GetControllerForGamepadIndex(void* self, int _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, int); + return ((Fn)vt[38])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamInput_GetGamepadIndexForController(void* self, uint64_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t); + return ((Fn)vt[39])(self, _a0); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamInput_GetStringForXboxOrigin(void* self, int _a0) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, int); + return ((Fn)vt[40])(self, _a0); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamInput_GetGlyphForXboxOrigin(void* self, int _a0) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, int); + return ((Fn)vt[41])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamInput_GetActionOriginFromXboxOrigin(void* self, uint64_t _a0, int _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, int); + return ((Fn)vt[42])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamInput_TranslateActionOrigin(void* self, int _a0, int _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int, int); + return ((Fn)vt[43])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamInput_GetDeviceBindingRevision(void* self, uint64_t _a0, void* _a1, void* _a2) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*, void*); + return ((Fn)vt[44])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT uint32_t SteamAPI_ISteamInput_GetRemotePlaySessionID(void* self, uint64_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint32_t (*Fn)(void*, uint64_t); + return ((Fn)vt[45])(self, _a0); +} +WN_STEAMAPI_EXPORT uint32_t SteamAPI_ISteamInput_GetSessionInputConfigurationSettings(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint32_t (*Fn)(void*); + return ((Fn)vt[46])(self); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamInput_SetDualSenseTriggerEffect(void* self, uint64_t _a0, void* _a1) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint64_t, void*); + ((Fn)vt[47])(self, _a0, _a1); +} + +WN_STEAMAPI_EXPORT int SteamAPI_ISteamInventory_GetResultStatus(void* self, void* _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*); + return ((Fn)vt[0])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamInventory_GetResultItems(void* self, int _a0, void* _a1, void* pcb) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int, void*, void*); + return ((Fn)vt[1])(self, _a0, _a1, pcb); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamInventory_GetResultItemProperty(void* self, int _a0, uint32_t _a1, void* _a2, void* buf, void* cb) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int, uint32_t, void*, void*, void*); + return ((Fn)vt[2])(self, _a0, _a1, _a2, buf, cb); +} +WN_STEAMAPI_EXPORT uint32_t SteamAPI_ISteamInventory_GetResultTimestamp(void* self, int _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint32_t (*Fn)(void*, int); + return ((Fn)vt[3])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamInventory_CheckResultSteamID(void* self, int _a0, uint64_t _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int, uint64_t); + return ((Fn)vt[4])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamInventory_DestroyResult(void* self, int _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, int); + ((Fn)vt[5])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamInventory_GetAllItems(void* self, void* phRes) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*); + return ((Fn)vt[6])(self, phRes); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamInventory_GetItemsByID(void* self, void* phRes, void* _a1, uint32_t _a2) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, void*, uint32_t); + return ((Fn)vt[7])(self, phRes, _a1, _a2); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamInventory_SerializeResult(void* self, int _a0, void* _a1, void* pcb) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int, void*, void*); + return ((Fn)vt[8])(self, _a0, _a1, pcb); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamInventory_DeserializeResult(void* self, void* phRes, void* _a1, uint32_t _a2, int _a3) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, void*, uint32_t, int); + return ((Fn)vt[9])(self, phRes, _a1, _a2, _a3); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamInventory_GenerateItems(void* self, void* phRes, void* _a1, void* _a2, uint32_t _a3) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, void*, void*, uint32_t); + return ((Fn)vt[10])(self, phRes, _a1, _a2, _a3); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamInventory_GrantPromoItems(void* self, void* phRes) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*); + return ((Fn)vt[11])(self, phRes); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamInventory_AddPromoItem(void* self, void* phRes, int32_t _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, int32_t); + return ((Fn)vt[12])(self, phRes, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamInventory_AddPromoItems(void* self, void* phRes, void* _a1, uint32_t _a2) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, void*, uint32_t); + return ((Fn)vt[13])(self, phRes, _a1, _a2); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamInventory_ConsumeItem(void* self, void* phRes, uint64_t _a1, uint32_t _a2) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, uint64_t, uint32_t); + return ((Fn)vt[14])(self, phRes, _a1, _a2); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamInventory_ExchangeItems(void* self, void* phRes, void* _a1, void* _a2, uint32_t _a3, void* _a4, void* _a5, uint32_t _a6) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, void*, void*, uint32_t, void*, void*, uint32_t); + return ((Fn)vt[15])(self, phRes, _a1, _a2, _a3, _a4, _a5, _a6); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamInventory_TransferItemQuantity(void* self, void* phRes, uint64_t _a1, uint32_t _a2, uint64_t _a3) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, uint64_t, uint32_t, uint64_t); + return ((Fn)vt[16])(self, phRes, _a1, _a2, _a3); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamInventory_SendItemDropHeartbeat(void* self) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*); + ((Fn)vt[17])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamInventory_TriggerItemDrop(void* self, void* phRes, int32_t _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, int32_t); + return ((Fn)vt[18])(self, phRes, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamInventory_TradeItems(void* self, void* phRes, uint64_t _a1, void* _a2, void* _a3, uint32_t _a4, void* _a5, void* _a6, uint32_t _a7) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, uint64_t, void*, void*, uint32_t, void*, void*, uint32_t); + return ((Fn)vt[19])(self, phRes, _a1, _a2, _a3, _a4, _a5, _a6, _a7); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamInventory_LoadItemDefinitions(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[20])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamInventory_GetItemDefinitionIDs(void* self, void* defs, void* pcb) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, void*); + return ((Fn)vt[21])(self, defs, pcb); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamInventory_GetItemDefinitionProperty(void* self, int32_t iDef, void* propName, void* buf, void* cb) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int32_t, void*, void*, void*); + return ((Fn)vt[22])(self, iDef, propName, buf, cb); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamInventory_RequestEligiblePromoItemDefinitionsIDs(void* self, uint64_t sid) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint64_t); + return ((Fn)vt[23])(self, sid); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamInventory_GetEligiblePromoItemDefinitionIDs(void* self, uint64_t _a0, void* _a1, void* pcb) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*, void*); + return ((Fn)vt[24])(self, _a0, _a1, pcb); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamInventory_StartPurchase(void* self, void* _a0, void* _a1, void* _a2) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, void*, void*, void*); + return ((Fn)vt[25])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamInventory_RequestPrices(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*); + return ((Fn)vt[26])(self); +} +WN_STEAMAPI_EXPORT uint32_t SteamAPI_ISteamInventory_GetNumItemsWithPrices(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint32_t (*Fn)(void*); + return ((Fn)vt[27])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamInventory_GetItemsWithPrices(void* self, void* _a0, void* _a1, void* _a2, uint32_t _a3) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, void*, void*, uint32_t); + return ((Fn)vt[28])(self, _a0, _a1, _a2, _a3); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamInventory_GetItemPrice(void* self, int32_t _a0, void* p, void* bp) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int32_t, void*, void*); + return ((Fn)vt[29])(self, _a0, p, bp); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamInventory_StartUpdateProperties(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*); + return ((Fn)vt[30])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamInventory_RemoveProperty(void* self, uint64_t _a0, uint64_t _a1, void* _a2) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, uint64_t, void*); + return ((Fn)vt[31])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamInventory_SetProperty_String(void* self, uint64_t _a0, uint64_t _a1, void* _a2, void* _a3) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, uint64_t, void*, void*); + return ((Fn)vt[32])(self, _a0, _a1, _a2, _a3); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamInventory_SetProperty_Bool(void* self, uint64_t _a0, uint64_t _a1, void* _a2, int _a3) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, uint64_t, void*, int); + return ((Fn)vt[33])(self, _a0, _a1, _a2, _a3); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamInventory_SetProperty_Int64(void* self, uint64_t _a0, uint64_t _a1, void* _a2, int64_t _a3) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, uint64_t, void*, int64_t); + return ((Fn)vt[34])(self, _a0, _a1, _a2, _a3); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamInventory_SetProperty_Float(void* self, uint64_t _a0, uint64_t _a1, void* _a2, float _a3) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, uint64_t, void*, float); + return ((Fn)vt[35])(self, _a0, _a1, _a2, _a3); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamInventory_SubmitUpdateProperties(void* self, uint64_t _a0, void* phRes) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*); + return ((Fn)vt[36])(self, _a0, phRes); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamInventory_InspectItem(void* self, void* phRes, void* _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, void*); + return ((Fn)vt[37])(self, phRes, _a1); +} + +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMatchmaking_GetFavoriteGameCount(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[0])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMatchmaking_GetFavoriteGame(void* self, int _a0, void* _a1, void* _a2, void* _a3, void* _a4, void* _a5, void* _a6) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int, void*, void*, void*, void*, void*, void*); + return ((Fn)vt[1])(self, _a0, _a1, _a2, _a3, _a4, _a5, _a6); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMatchmaking_AddFavoriteGame(void* self, uint32_t _a0, uint32_t _a1, uint16_t _a2, uint16_t _a3, uint32_t _a4, uint32_t _a5) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint32_t, uint32_t, uint16_t, uint16_t, uint32_t, uint32_t); + return ((Fn)vt[2])(self, _a0, _a1, _a2, _a3, _a4, _a5); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMatchmaking_RemoveFavoriteGame(void* self, uint32_t _a0, uint32_t _a1, uint16_t _a2, uint16_t _a3, uint32_t _a4) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint32_t, uint32_t, uint16_t, uint16_t, uint32_t); + return ((Fn)vt[3])(self, _a0, _a1, _a2, _a3, _a4); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamMatchmaking_RequestLobbyList(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*); + return ((Fn)vt[4])(self); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamMatchmaking_AddRequestLobbyListStringFilter(void* self, void* k, void* v, int cmp) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, void*, void*, int); + ((Fn)vt[5])(self, k, v, cmp); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamMatchmaking_AddRequestLobbyListNumericalFilter(void* self, void* k, int v, int cmp) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, void*, int, int); + ((Fn)vt[6])(self, k, v, cmp); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamMatchmaking_AddRequestLobbyListNearValueFilter(void* self, void* k, int v) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, void*, int); + ((Fn)vt[7])(self, k, v); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamMatchmaking_AddRequestLobbyListFilterSlotsAvailable(void* self, int slots) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, int); + ((Fn)vt[8])(self, slots); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamMatchmaking_AddRequestLobbyListDistanceFilter(void* self, int eDist) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, int); + ((Fn)vt[9])(self, eDist); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamMatchmaking_AddRequestLobbyListResultCountFilter(void* self, int n) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, int); + ((Fn)vt[10])(self, n); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamMatchmaking_AddRequestLobbyListCompatibleMembersFilter(void* self, void* _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, void*); + ((Fn)vt[11])(self, _a0); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamMatchmaking_GetLobbyByIndex(void* self, int idx) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, int); + return ((Fn)vt[12])(self, idx); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamMatchmaking_CreateLobby(void* self, int eLobbyType, int maxMembers) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, int, int); + return ((Fn)vt[13])(self, eLobbyType, maxMembers); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamMatchmaking_JoinLobby(void* self, uint64_t lobbySid) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint64_t); + return ((Fn)vt[14])(self, lobbySid); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamMatchmaking_LeaveLobby(void* self, uint64_t sid) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint64_t); + ((Fn)vt[15])(self, sid); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMatchmaking_InviteUserToLobby(void* self, uint64_t sid, uint64_t invitee) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, uint64_t); + return ((Fn)vt[16])(self, sid, invitee); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMatchmaking_GetNumLobbyMembers(void* self, uint64_t sid) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t); + return ((Fn)vt[17])(self, sid); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamMatchmaking_GetLobbyMemberByIndex(void* self, uint64_t sid, int idx) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint64_t, int); + return ((Fn)vt[18])(self, sid, idx); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamMatchmaking_GetLobbyData(void* self, uint64_t sid, void* key) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, uint64_t, void*); + return ((Fn)vt[19])(self, sid, key); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMatchmaking_SetLobbyData(void* self, uint64_t sid, void* key, void* val) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*, void*); + return ((Fn)vt[20])(self, sid, key, val); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMatchmaking_GetLobbyDataCount(void* self, uint64_t sid) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t); + return ((Fn)vt[21])(self, sid); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMatchmaking_GetLobbyDataByIndex(void* self, uint64_t sid, int idx, void* key, int kn, void* val, int vn) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, int, void*, int, void*, int); + return ((Fn)vt[22])(self, sid, idx, key, kn, val, vn); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMatchmaking_DeleteLobbyData(void* self, uint64_t sid, void* key) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*); + return ((Fn)vt[23])(self, sid, key); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamMatchmaking_GetLobbyMemberData(void* self, uint64_t sid, uint64_t member, void* key) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, uint64_t, uint64_t, void*); + return ((Fn)vt[24])(self, sid, member, key); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamMatchmaking_SetLobbyMemberData(void* self, uint64_t sid, void* key, void* val) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint64_t, void*, void*); + ((Fn)vt[25])(self, sid, key, val); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMatchmaking_SendLobbyChatMsg(void* self, uint64_t sid, void* body, int n) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*, int); + return ((Fn)vt[26])(self, sid, body, n); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMatchmaking_GetLobbyChatEntry(void* self, uint64_t sid, int idx, void* speaker_out, void* body_out, int body_cap, void* chat_type_out) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, int, void*, void*, int, void*); + return ((Fn)vt[27])(self, sid, idx, speaker_out, body_out, body_cap, chat_type_out); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMatchmaking_RequestLobbyData(void* self, uint64_t sid) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t); + return ((Fn)vt[28])(self, sid); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamMatchmaking_SetLobbyGameServer(void* self, uint64_t sid, uint32_t ip, uint16_t port, uint64_t gs) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint64_t, uint32_t, uint16_t, uint64_t); + ((Fn)vt[29])(self, sid, ip, port, gs); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMatchmaking_GetLobbyGameServer(void* self, uint64_t sid, void* ip, void* port, void* sid_out) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*, void*, void*); + return ((Fn)vt[30])(self, sid, ip, port, sid_out); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMatchmaking_SetLobbyMemberLimit(void* self, uint64_t sid, int max_members) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, int); + return ((Fn)vt[31])(self, sid, max_members); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMatchmaking_GetLobbyMemberLimit(void* self, uint64_t sid) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t); + return ((Fn)vt[32])(self, sid); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMatchmaking_SetLobbyType(void* self, uint64_t sid, int eLobbyType) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, int); + return ((Fn)vt[33])(self, sid, eLobbyType); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMatchmaking_SetLobbyJoinable(void* self, uint64_t sid, int joinable) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, int); + return ((Fn)vt[34])(self, sid, joinable); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamMatchmaking_GetLobbyOwner(void* self, uint64_t sid) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint64_t); + return ((Fn)vt[35])(self, sid); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMatchmaking_SetLobbyOwner(void* self, uint64_t sid, uint64_t new_owner) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, uint64_t); + return ((Fn)vt[36])(self, sid, new_owner); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMatchmaking_SetLinkedLobby(void* self, uint64_t _a0, uint64_t _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, uint64_t); + return ((Fn)vt[37])(self, _a0, _a1); +} + +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamMatchmakingServers_RequestInternetServerList(void* self, uint32_t app, void* _a1, uint32_t n, void* _a3) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, uint32_t, void*, uint32_t, void*); + return ((Fn)vt[0])(self, app, _a1, n, _a3); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamMatchmakingServers_RequestLANServerList(void* self, uint32_t app, void* _a1) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, uint32_t, void*); + return ((Fn)vt[1])(self, app, _a1); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamMatchmakingServers_RequestFriendsServerList(void* self, uint32_t app, void* _a1, uint32_t _a2, void* _a3) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, uint32_t, void*, uint32_t, void*); + return ((Fn)vt[2])(self, app, _a1, _a2, _a3); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamMatchmakingServers_RequestFavoritesServerList(void* self, uint32_t app, void* _a1, uint32_t _a2, void* _a3) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, uint32_t, void*, uint32_t, void*); + return ((Fn)vt[3])(self, app, _a1, _a2, _a3); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamMatchmakingServers_RequestHistoryServerList(void* self, uint32_t app, void* _a1, uint32_t _a2, void* _a3) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, uint32_t, void*, uint32_t, void*); + return ((Fn)vt[4])(self, app, _a1, _a2, _a3); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamMatchmakingServers_RequestSpectatorServerList(void* self, uint32_t app, void* _a1, uint32_t _a2, void* _a3) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, uint32_t, void*, uint32_t, void*); + return ((Fn)vt[5])(self, app, _a1, _a2, _a3); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamMatchmakingServers_ReleaseRequest(void* self, void* _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, void*); + ((Fn)vt[6])(self, _a0); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamMatchmakingServers_GetServerDetails(void* self, void* _a0, int _a1) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, void*, int); + return ((Fn)vt[7])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamMatchmakingServers_CancelQuery(void* self, void* _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, void*); + ((Fn)vt[8])(self, _a0); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamMatchmakingServers_RefreshQuery(void* self, void* _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, void*); + ((Fn)vt[9])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMatchmakingServers_IsRefreshing(void* self, void* _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*); + return ((Fn)vt[10])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMatchmakingServers_GetServerCount(void* self, void* _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*); + return ((Fn)vt[11])(self, _a0); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamMatchmakingServers_RefreshServer(void* self, void* _a0, int _a1) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, void*, int); + ((Fn)vt[12])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMatchmakingServers_PingServer(void* self, uint32_t _a0, uint16_t _a1, void* _a2) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint32_t, uint16_t, void*); + return ((Fn)vt[13])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMatchmakingServers_PlayerDetails(void* self, uint32_t _a0, uint16_t _a1, void* _a2) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint32_t, uint16_t, void*); + return ((Fn)vt[14])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMatchmakingServers_ServerRules(void* self, uint32_t _a0, uint16_t _a1, void* _a2) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint32_t, uint16_t, void*); + return ((Fn)vt[15])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamMatchmakingServers_CancelServerQuery(void* self, int _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, int); + ((Fn)vt[16])(self, _a0); +} + +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMusic_BIsEnabled(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[0])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMusic_BIsPlaying(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[1])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMusic_GetPlaybackStatus(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[2])(self); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamMusic_Play(void* self) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*); + ((Fn)vt[3])(self); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamMusic_Pause(void* self) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*); + ((Fn)vt[4])(self); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamMusic_PlayPrevious(void* self) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*); + ((Fn)vt[5])(self); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamMusic_PlayNext(void* self) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*); + ((Fn)vt[6])(self); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamMusic_SetVolume(void* self, float _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, float); + ((Fn)vt[7])(self, _a0); +} +WN_STEAMAPI_EXPORT float SteamAPI_ISteamMusic_GetVolume(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef float (*Fn)(void*); + return ((Fn)vt[8])(self); +} + +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMusicRemote_RegisterSteamMusicRemote(void* self, void* _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*); + return ((Fn)vt[0])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMusicRemote_DeregisterSteamMusicRemote(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[1])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMusicRemote_BIsCurrentMusicRemote(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[2])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMusicRemote_BActivationSuccess(void* self, int _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int); + return ((Fn)vt[3])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMusicRemote_SetDisplayName(void* self, void* _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*); + return ((Fn)vt[4])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMusicRemote_SetPNGIcon_64x64(void* self, void* _a0, uint32_t _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, uint32_t); + return ((Fn)vt[5])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMusicRemote_EnablePlayPrevious(void* self, int _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int); + return ((Fn)vt[6])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMusicRemote_EnablePlayNext(void* self, int _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int); + return ((Fn)vt[7])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMusicRemote_EnableShuffled(void* self, int _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int); + return ((Fn)vt[8])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMusicRemote_EnableLooped(void* self, int _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int); + return ((Fn)vt[9])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMusicRemote_EnableQueue(void* self, int _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int); + return ((Fn)vt[10])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMusicRemote_EnablePlaylists(void* self, int _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int); + return ((Fn)vt[11])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMusicRemote_UpdatePlaybackStatus(void* self, int _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int); + return ((Fn)vt[12])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMusicRemote_UpdateShuffled(void* self, int _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int); + return ((Fn)vt[13])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMusicRemote_UpdateLooped(void* self, int _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int); + return ((Fn)vt[14])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMusicRemote_UpdateVolume(void* self, float _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, float); + return ((Fn)vt[15])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMusicRemote_CurrentEntryWillChange(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[16])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMusicRemote_CurrentEntryIsAvailable(void* self, int _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int); + return ((Fn)vt[17])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMusicRemote_UpdateCurrentEntryText(void* self, void* _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*); + return ((Fn)vt[18])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMusicRemote_UpdateCurrentEntryElapsedSeconds(void* self, int _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int); + return ((Fn)vt[19])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMusicRemote_UpdateCurrentEntryCoverArt(void* self, void* _a0, uint32_t _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, uint32_t); + return ((Fn)vt[20])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMusicRemote_CurrentEntryDidChange(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[21])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMusicRemote_QueueWillChange(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[22])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMusicRemote_ResetQueueEntries(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[23])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMusicRemote_SetQueueEntry(void* self, int _a0, int _a1, void* _a2) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int, int, void*); + return ((Fn)vt[24])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMusicRemote_SetCurrentQueueEntry(void* self, int _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int); + return ((Fn)vt[25])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMusicRemote_QueueDidChange(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[26])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMusicRemote_PlaylistWillChange(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[27])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMusicRemote_ResetPlaylistEntries(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[28])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMusicRemote_SetPlaylistEntry(void* self, int _a0, int _a1, void* _a2) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int, int, void*); + return ((Fn)vt[29])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMusicRemote_SetCurrentPlaylistEntry(void* self, int _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int); + return ((Fn)vt[30])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMusicRemote_PlaylistDidChange(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[31])(self); +} + +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworking_SendP2PPacket(void* self, uint64_t sid, void* _a1, uint32_t n, void* _a3, void* _a4) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*, uint32_t, void*, void*); + return ((Fn)vt[0])(self, sid, _a1, n, _a3, _a4); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworking_IsP2PPacketAvailable(void* self, void* pcub, int nChannel) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, int); + return ((Fn)vt[1])(self, pcub, nChannel); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworking_ReadP2PPacket(void* self, void* dest, uint32_t cubDest, void* pcub, void* sidOut, int nChannel) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, uint32_t, void*, void*, int); + return ((Fn)vt[2])(self, dest, cubDest, pcub, sidOut, nChannel); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworking_AcceptP2PSessionWithUser(void* self, uint64_t sid) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t); + return ((Fn)vt[3])(self, sid); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworking_CloseP2PSessionWithUser(void* self, uint64_t sid) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t); + return ((Fn)vt[4])(self, sid); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworking_CloseP2PChannelWithUser(void* self, uint64_t sid, int nChannel) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, int); + return ((Fn)vt[5])(self, sid, nChannel); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworking_GetP2PSessionState(void* self, uint64_t sid, void* pState) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*); + return ((Fn)vt[6])(self, sid, pState); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworking_AllowP2PPacketRelay(void* self, int bAllow) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int); + return ((Fn)vt[7])(self, bAllow); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworking_CreateListenSocket(void* self, int _a0, uint32_t _a1, uint16_t _a2, int _a3) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int, uint32_t, uint16_t, int); + return ((Fn)vt[8])(self, _a0, _a1, _a2, _a3); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworking_CreateP2PConnectionSocket(void* self, uint64_t _a0, int _a1, int _a2, int _a3) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, int, int, int); + return ((Fn)vt[9])(self, _a0, _a1, _a2, _a3); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworking_CreateConnectionSocket(void* self, uint32_t _a0, uint16_t _a1, int _a2) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint32_t, uint16_t, int); + return ((Fn)vt[10])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworking_DestroySocket(void* self, int _a0, int _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int, int); + return ((Fn)vt[11])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworking_DestroyListenSocket(void* self, int _a0, int _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int, int); + return ((Fn)vt[12])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworking_SendDataOnSocket(void* self, int _a0, void* _a1, uint32_t _a2, int _a3) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int, void*, uint32_t, int); + return ((Fn)vt[13])(self, _a0, _a1, _a2, _a3); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworking_IsDataAvailableOnSocket(void* self, int _a0, void* pcb) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int, void*); + return ((Fn)vt[14])(self, _a0, pcb); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworking_RetrieveDataFromSocket(void* self, int _a0, void* _a1, uint32_t _a2, void* pcb) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int, void*, uint32_t, void*); + return ((Fn)vt[15])(self, _a0, _a1, _a2, pcb); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworking_IsDataAvailable(void* self, int _a0, void* pcb, void* _a2) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int, void*, void*); + return ((Fn)vt[16])(self, _a0, pcb, _a2); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworking_RetrieveData(void* self, int _a0, void* _a1, uint32_t _a2, void* pcb, void* _a4) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int, void*, uint32_t, void*, void*); + return ((Fn)vt[17])(self, _a0, _a1, _a2, pcb, _a4); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworking_GetSocketInfo(void* self, int _a0, void* sid, void* status, void* ip, void* port, void* lsock) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int, void*, void*, void*, void*, void*); + return ((Fn)vt[18])(self, _a0, sid, status, ip, port, lsock); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworking_GetListenSocketInfo(void* self, int _a0, void* ip, void* port) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int, void*, void*); + return ((Fn)vt[19])(self, _a0, ip, port); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworking_GetSocketConnectionType(void* self, int _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int); + return ((Fn)vt[20])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworking_GetMaxPacketSize(void* self, int _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int); + return ((Fn)vt[21])(self, _a0); +} + +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingMessages_SendMessageToUser(void* self, void* _a0, void* _a1, uint32_t _a2, int _a3, int _a4) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, void*, uint32_t, int, int); + return ((Fn)vt[0])(self, _a0, _a1, _a2, _a3, _a4); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingMessages_ReceiveMessagesOnChannel(void* self, int _a0, void* _a1, int _a2) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int, void*, int); + return ((Fn)vt[1])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingMessages_AcceptSessionWithUser(void* self, void* _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*); + return ((Fn)vt[2])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingMessages_CloseSessionWithUser(void* self, void* _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*); + return ((Fn)vt[3])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingMessages_CloseChannelWithUser(void* self, void* _a0, int _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, int); + return ((Fn)vt[4])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingMessages_GetSessionConnectionInfo(void* self, void* _a0, void* _a1, void* _a2) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, void*, void*); + return ((Fn)vt[5])(self, _a0, _a1, _a2); +} + +WN_STEAMAPI_EXPORT uint32_t SteamAPI_ISteamNetworkingSockets_CreateListenSocketIP(void* self, void* _a0, int _a1, void* _a2) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint32_t (*Fn)(void*, void*, int, void*); + return ((Fn)vt[0])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT uint32_t SteamAPI_ISteamNetworkingSockets_ConnectByIPAddress(void* self, void* _a0, int _a1, void* _a2) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint32_t (*Fn)(void*, void*, int, void*); + return ((Fn)vt[1])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT uint32_t SteamAPI_ISteamNetworkingSockets_CreateListenSocketP2P(void* self, int _a0, int _a1, void* _a2) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint32_t (*Fn)(void*, int, int, void*); + return ((Fn)vt[2])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT uint32_t SteamAPI_ISteamNetworkingSockets_ConnectP2P(void* self, void* _a0, int _a1, int _a2, void* _a3) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint32_t (*Fn)(void*, void*, int, int, void*); + return ((Fn)vt[3])(self, _a0, _a1, _a2, _a3); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingSockets_AcceptConnection(void* self, uint32_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint32_t); + return ((Fn)vt[4])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingSockets_CloseConnection(void* self, uint32_t _a0, int _a1, void* _a2, int _a3) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint32_t, int, void*, int); + return ((Fn)vt[5])(self, _a0, _a1, _a2, _a3); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingSockets_CloseListenSocket(void* self, uint32_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint32_t); + return ((Fn)vt[6])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingSockets_SetConnectionUserData(void* self, uint32_t _a0, int64_t _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint32_t, int64_t); + return ((Fn)vt[7])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int64_t SteamAPI_ISteamNetworkingSockets_GetConnectionUserData(void* self, uint32_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int64_t (*Fn)(void*, uint32_t); + return ((Fn)vt[8])(self, _a0); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamNetworkingSockets_SetConnectionName(void* self, uint32_t _a0, void* _a1) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint32_t, void*); + ((Fn)vt[9])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingSockets_GetConnectionName(void* self, uint32_t _a0, void* buf, int cap) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint32_t, void*, int); + return ((Fn)vt[10])(self, _a0, buf, cap); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingSockets_SendMessageToConnection(void* self, uint32_t _a0, void* _a1, uint32_t _a2, int _a3, void* _a4) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint32_t, void*, uint32_t, int, void*); + return ((Fn)vt[11])(self, _a0, _a1, _a2, _a3, _a4); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamNetworkingSockets_SendMessages(void* self, int _a0, void* _a1, void* _a2) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, int, void*, void*); + ((Fn)vt[12])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingSockets_FlushMessagesOnConnection(void* self, uint32_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint32_t); + return ((Fn)vt[13])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingSockets_ReceiveMessagesOnConnection(void* self, uint32_t _a0, void* _a1, int _a2) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint32_t, void*, int); + return ((Fn)vt[14])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT uint32_t SteamAPI_ISteamNetworkingSockets_CreatePollGroup(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint32_t (*Fn)(void*); + return ((Fn)vt[15])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingSockets_DestroyPollGroup(void* self, uint32_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint32_t); + return ((Fn)vt[16])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingSockets_SetConnectionPollGroup(void* self, uint32_t _a0, uint32_t _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint32_t, uint32_t); + return ((Fn)vt[17])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingSockets_ReceiveMessagesOnPollGroup(void* self, uint32_t _a0, void* _a1, int _a2) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint32_t, void*, int); + return ((Fn)vt[18])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingSockets_GetConnectionInfo(void* self, uint32_t _a0, void* _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint32_t, void*); + return ((Fn)vt[19])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingSockets_GetConnectionRealTimeStatus(void* self, uint32_t _a0, void* _a1, int _a2, void* _a3) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint32_t, void*, int, void*); + return ((Fn)vt[20])(self, _a0, _a1, _a2, _a3); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingSockets_GetDetailedConnectionStatus(void* self, uint32_t _a0, void* buf, int cap) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint32_t, void*, int); + return ((Fn)vt[21])(self, _a0, buf, cap); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingSockets_GetListenSocketAddress(void* self, uint32_t _a0, void* _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint32_t, void*); + return ((Fn)vt[22])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingSockets_CreateSocketPair(void* self, void* a, void* b, int _a2, void* _a3, void* _a4) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, void*, int, void*, void*); + return ((Fn)vt[23])(self, a, b, _a2, _a3, _a4); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingSockets_ConfigureConnectionLanes(void* self, uint32_t _a0, int _a1, void* _a2, void* _a3) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint32_t, int, void*, void*); + return ((Fn)vt[24])(self, _a0, _a1, _a2, _a3); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingSockets_GetIdentity(void* self, void* pIdentity) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*); + return ((Fn)vt[25])(self, pIdentity); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingSockets_InitAuthentication(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[26])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingSockets_GetAuthenticationStatus(void* self, void* _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*); + return ((Fn)vt[27])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingSockets_ReceivedRelayAuthTicket(void* self, void* _a0, int _a1, void* _a2) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, int, void*); + return ((Fn)vt[28])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingSockets_FindRelayAuthTicketForServer(void* self, void* _a0, int _a1, void* _a2) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, int, void*); + return ((Fn)vt[29])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT uint32_t SteamAPI_ISteamNetworkingSockets_ConnectToHostedDedicatedServer(void* self, void* _a0, int _a1, int _a2, void* _a3) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint32_t (*Fn)(void*, void*, int, int, void*); + return ((Fn)vt[30])(self, _a0, _a1, _a2, _a3); +} +WN_STEAMAPI_EXPORT uint16_t SteamAPI_ISteamNetworkingSockets_GetHostedDedicatedServerPort(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint16_t (*Fn)(void*); + return ((Fn)vt[31])(self); +} +WN_STEAMAPI_EXPORT uint32_t SteamAPI_ISteamNetworkingSockets_GetHostedDedicatedServerPOPID(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint32_t (*Fn)(void*); + return ((Fn)vt[32])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingSockets_GetHostedDedicatedServerAddress(void* self, void* _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*); + return ((Fn)vt[33])(self, _a0); +} +WN_STEAMAPI_EXPORT uint32_t SteamAPI_ISteamNetworkingSockets_CreateHostedDedicatedServerListenSocket(void* self, int _a0, int _a1, void* _a2) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint32_t (*Fn)(void*, int, int, void*); + return ((Fn)vt[34])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingSockets_GetGameCoordinatorServerLogin(void* self, void* _a0, void* _a1, void* _a2) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, void*, void*); + return ((Fn)vt[35])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT uint32_t SteamAPI_ISteamNetworkingSockets_ConnectP2PCustomSignaling(void* self, void* _a0, void* _a1, int _a2, int _a3, void* _a4) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint32_t (*Fn)(void*, void*, void*, int, int, void*); + return ((Fn)vt[36])(self, _a0, _a1, _a2, _a3, _a4); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingSockets_ReceivedP2PCustomSignal(void* self, void* _a0, int _a1, void* _a2) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, int, void*); + return ((Fn)vt[37])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingSockets_GetCertificateRequest(void* self, void* _a0, void* _a1, void* _a2) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, void*, void*); + return ((Fn)vt[38])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingSockets_SetCertificate(void* self, void* _a0, int _a1, void* _a2) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, int, void*); + return ((Fn)vt[39])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamNetworkingSockets_ResetIdentity(void* self, void* _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, void*); + ((Fn)vt[40])(self, _a0); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamNetworkingSockets_RunCallbacks(void* self) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*); + ((Fn)vt[41])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingSockets_BeginAsyncRequestFakeIP(void* self, int _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int); + return ((Fn)vt[42])(self, _a0); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamNetworkingSockets_GetFakeIP(void* self, int _a0, void* _a1) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, int, void*); + ((Fn)vt[43])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT uint32_t SteamAPI_ISteamNetworkingSockets_CreateListenSocketP2PFakeIP(void* self, int _a0, int _a1, void* _a2) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint32_t (*Fn)(void*, int, int, void*); + return ((Fn)vt[44])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingSockets_GetRemoteFakeIPForConnection(void* self, uint32_t _a0, void* _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint32_t, void*); + return ((Fn)vt[45])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamNetworkingSockets_CreateFakeUDPPort(void* self, int _a0) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, int); + return ((Fn)vt[46])(self, _a0); +} + +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamNetworkingUtils_AllocateMessage(void* self, int _a0) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, int); + return ((Fn)vt[0])(self, _a0); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamNetworkingUtils_InitRelayNetworkAccess(void* self) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*); + ((Fn)vt[1])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingUtils_GetRelayNetworkStatus(void* self, void* _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*); + return ((Fn)vt[2])(self, _a0); +} +WN_STEAMAPI_EXPORT float SteamAPI_ISteamNetworkingUtils_GetLocalPingLocation(void* self, void* _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef float (*Fn)(void*, void*); + return ((Fn)vt[3])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingUtils_EstimatePingTimeBetweenTwoLocations(void* self, void* _a0, void* _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, void*); + return ((Fn)vt[4])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingUtils_EstimatePingTimeFromLocalHost(void* self, void* _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*); + return ((Fn)vt[5])(self, _a0); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamNetworkingUtils_ConvertPingLocationToString(void* self, void* _a0, void* buf, int cap) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, void*, void*, int); + ((Fn)vt[6])(self, _a0, buf, cap); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingUtils_ParsePingLocationString(void* self, void* _a0, void* _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, void*); + return ((Fn)vt[7])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingUtils_CheckPingDataUpToDate(void* self, float _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, float); + return ((Fn)vt[8])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingUtils_GetPingToDataCenter(void* self, uint32_t _a0, void* _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint32_t, void*); + return ((Fn)vt[9])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingUtils_GetDirectPingToPOP(void* self, uint32_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint32_t); + return ((Fn)vt[10])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingUtils_GetPOPCount(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[11])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingUtils_GetPOPList(void* self, void* _a0, int _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, int); + return ((Fn)vt[12])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int64_t SteamAPI_ISteamNetworkingUtils_GetLocalTimestamp(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int64_t (*Fn)(void*); + return ((Fn)vt[13])(self); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamNetworkingUtils_SetDebugOutputFunction(void* self, int _a0, void* _a1) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, int, void*); + ((Fn)vt[14])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingUtils_IsFakeIPv4(void* self, uint32_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint32_t); + return ((Fn)vt[15])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingUtils_GetIPv4FakeIPType(void* self, uint32_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint32_t); + return ((Fn)vt[16])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingUtils_GetRealIdentityForFakeIP(void* self, void* _a0, void* _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, void*); + return ((Fn)vt[17])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingUtils_SetGlobalConfigValueInt32(void* self, int _a0, int _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int, int); + return ((Fn)vt[18])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingUtils_SetGlobalConfigValueFloat(void* self, int _a0, float _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int, float); + return ((Fn)vt[19])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingUtils_SetGlobalConfigValueString(void* self, int _a0, void* _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int, void*); + return ((Fn)vt[20])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingUtils_SetGlobalConfigValuePtr(void* self, int _a0, void* _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int, void*); + return ((Fn)vt[21])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingUtils_SetConnectionConfigValueInt32(void* self, uint32_t _a0, int _a1, int _a2) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint32_t, int, int); + return ((Fn)vt[22])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingUtils_SetConnectionConfigValueFloat(void* self, uint32_t _a0, int _a1, float _a2) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint32_t, int, float); + return ((Fn)vt[23])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingUtils_SetConnectionConfigValueString(void* self, uint32_t _a0, int _a1, void* _a2) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint32_t, int, void*); + return ((Fn)vt[24])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingUtils_SetConfigValue(void* self, int _a0, int _a1, uint64_t _a2, int _a3, void* _a4) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int, int, uint64_t, int, void*); + return ((Fn)vt[25])(self, _a0, _a1, _a2, _a3, _a4); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingUtils_SetConfigValueStruct(void* self, void* _a0, int _a1, uint64_t _a2) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, int, uint64_t); + return ((Fn)vt[26])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingUtils_GetConfigValue(void* self, int _a0, int _a1, uint64_t _a2, void* _a3, void* _a4, void* _a5) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int, int, uint64_t, void*, void*, void*); + return ((Fn)vt[27])(self, _a0, _a1, _a2, _a3, _a4, _a5); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamNetworkingUtils_GetConfigValueInfo(void* self, int _a0, void* _a1, void* _a2, void* _a3) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, int, void*, void*, void*); + return ((Fn)vt[28])(self, _a0, _a1, _a2, _a3); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingUtils_IterateGenericEditableConfigValues(void* self, int _a0, int _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int, int); + return ((Fn)vt[29])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamNetworkingUtils_SteamNetworkingIPAddr_ToString(void* self, void* pAddr, void* buf, uint32_t cap, int with_port) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, void*, void*, uint32_t, int); + ((Fn)vt[30])(self, pAddr, buf, cap, with_port); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingUtils_SteamNetworkingIPAddr_ParseString(void* self, void* pAddr, void* s) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, void*); + return ((Fn)vt[31])(self, pAddr, s); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingUtils_SteamNetworkingIPAddr_GetFakeIPType(void* self, void* _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*); + return ((Fn)vt[32])(self, _a0); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamNetworkingUtils_SteamNetworkingIdentity_ToString(void* self, void* pId, void* buf, uint32_t cap) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, void*, void*, uint32_t); + ((Fn)vt[33])(self, pId, buf, cap); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamNetworkingUtils_SteamNetworkingIdentity_ParseString(void* self, void* _a0, void* _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, void*); + return ((Fn)vt[34])(self, _a0, _a1); +} + +WN_STEAMAPI_EXPORT int SteamAPI_ISteamParentalSettings_BIsParentalLockEnabled(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[0])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamParentalSettings_BIsParentalLockLocked(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[1])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamParentalSettings_BIsAppBlocked(void* self, uint32_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint32_t); + return ((Fn)vt[2])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamParentalSettings_BIsAppInBlockList(void* self, uint32_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint32_t); + return ((Fn)vt[3])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamParentalSettings_BIsFeatureBlocked(void* self, int _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int); + return ((Fn)vt[4])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamParentalSettings_BIsFeatureInBlockList(void* self, int _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int); + return ((Fn)vt[5])(self, _a0); +} + +WN_STEAMAPI_EXPORT uint32_t SteamAPI_ISteamParties_GetNumActiveBeacons(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint32_t (*Fn)(void*); + return ((Fn)vt[0])(self); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamParties_GetBeaconByIndex(void* self, uint32_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint32_t); + return ((Fn)vt[1])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamParties_GetBeaconDetails(void* self, uint64_t _a0, void* _a1, void* _a2, void* meta, int mn) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*, void*, void*, int); + return ((Fn)vt[2])(self, _a0, _a1, _a2, meta, mn); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamParties_JoinParty(void* self, uint64_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint64_t); + return ((Fn)vt[3])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamParties_GetNumAvailableBeaconLocations(void* self, void* pNum) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*); + return ((Fn)vt[4])(self, pNum); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamParties_GetAvailableBeaconLocations(void* self, void* _a0, uint32_t _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, uint32_t); + return ((Fn)vt[5])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamParties_CreateBeacon(void* self, uint32_t _a0, void* _a1, int _a2, void* _a3, void* _a4) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint32_t, void*, int, void*, void*); + return ((Fn)vt[6])(self, _a0, _a1, _a2, _a3, _a4); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamParties_OnReservationCompleted(void* self, uint64_t _a0, uint64_t _a1) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint64_t, uint64_t); + ((Fn)vt[7])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamParties_CancelReservation(void* self, uint64_t _a0, uint64_t _a1) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint64_t, uint64_t); + ((Fn)vt[8])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamParties_ChangeNumOpenSlots(void* self, uint64_t _a0, uint32_t _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint64_t, uint32_t); + return ((Fn)vt[9])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamParties_DestroyBeacon(void* self, uint64_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t); + return ((Fn)vt[10])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamParties_GetBeaconLocationData(void* self, void* _a0, int _a1, void* str, int sn) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, int, void*, int); + return ((Fn)vt[11])(self, _a0, _a1, str, sn); +} + +WN_STEAMAPI_EXPORT uint32_t SteamAPI_ISteamRemotePlay_GetSessionCount(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint32_t (*Fn)(void*); + return ((Fn)vt[0])(self); +} +WN_STEAMAPI_EXPORT uint32_t SteamAPI_ISteamRemotePlay_GetSessionID(void* self, int _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint32_t (*Fn)(void*, int); + return ((Fn)vt[1])(self, _a0); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamRemotePlay_GetSessionSteamID(void* self, uint32_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint32_t); + return ((Fn)vt[2])(self, _a0); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamRemotePlay_GetSessionClientName(void* self, uint32_t _a0) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, uint32_t); + return ((Fn)vt[3])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamRemotePlay_GetSessionClientFormFactor(void* self, uint32_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint32_t); + return ((Fn)vt[4])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamRemotePlay_BGetSessionClientResolution(void* self, uint32_t _a0, void* w, void* h) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint32_t, void*, void*); + return ((Fn)vt[5])(self, _a0, w, h); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamRemotePlay_BStartRemotePlayTogether(void* self, int _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int); + return ((Fn)vt[6])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamRemotePlay_BSendRemotePlayTogetherInvite(void* self, uint64_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t); + return ((Fn)vt[7])(self, _a0); +} + +WN_STEAMAPI_EXPORT int SteamAPI_ISteamRemoteStorage_FileWrite(void* self, void* pchFile, void* pvData, int cubData) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, void*, int); + return ((Fn)vt[0])(self, pchFile, pvData, cubData); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamRemoteStorage_FileRead(void* self, void* pchFile, void* pvData, int cubDataToRead) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, void*, int); + return ((Fn)vt[1])(self, pchFile, pvData, cubDataToRead); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamRemoteStorage_FileWriteAsync(void* self, void* pchFile, void* pvData, uint32_t cubData) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, void*, void*, uint32_t); + return ((Fn)vt[2])(self, pchFile, pvData, cubData); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamRemoteStorage_FileReadAsync(void* self, void* pchFile, uint32_t nOffset, uint32_t cubToRead) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, void*, uint32_t, uint32_t); + return ((Fn)vt[3])(self, pchFile, nOffset, cubToRead); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamRemoteStorage_FileReadAsyncComplete(void* self, uint64_t hCall, void* pvBuffer, uint32_t cubToRead) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*, uint32_t); + return ((Fn)vt[4])(self, hCall, pvBuffer, cubToRead); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamRemoteStorage_FileForget(void* self, void* pchFile) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*); + return ((Fn)vt[5])(self, pchFile); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamRemoteStorage_FileDelete(void* self, void* pchFile) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*); + return ((Fn)vt[6])(self, pchFile); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamRemoteStorage_FileShare(void* self, void* pchFile) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, void*); + return ((Fn)vt[7])(self, pchFile); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamRemoteStorage_SetSyncPlatforms(void* self, void* pchFile, void* _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, void*); + return ((Fn)vt[8])(self, pchFile, _a1); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamRemoteStorage_FileWriteStreamOpen(void* self, void* pchFile) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, void*); + return ((Fn)vt[9])(self, pchFile); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamRemoteStorage_FileWriteStreamWriteChunk(void* self, uint64_t h, void* pvData, int cubData) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*, int); + return ((Fn)vt[10])(self, h, pvData, cubData); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamRemoteStorage_FileWriteStreamClose(void* self, uint64_t h) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t); + return ((Fn)vt[11])(self, h); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamRemoteStorage_FileWriteStreamCancel(void* self, uint64_t h) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t); + return ((Fn)vt[12])(self, h); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamRemoteStorage_FileExists(void* self, void* pchFile) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*); + return ((Fn)vt[13])(self, pchFile); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamRemoteStorage_FilePersisted(void* self, void* pchFile) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*); + return ((Fn)vt[14])(self, pchFile); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamRemoteStorage_GetFileSize(void* self, void* pchFile) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*); + return ((Fn)vt[15])(self, pchFile); +} +WN_STEAMAPI_EXPORT int64_t SteamAPI_ISteamRemoteStorage_GetFileTimestamp(void* self, void* pchFile) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int64_t (*Fn)(void*, void*); + return ((Fn)vt[16])(self, pchFile); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamRemoteStorage_GetSyncPlatforms(void* self, void* pchFile) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*); + return ((Fn)vt[17])(self, pchFile); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamRemoteStorage_GetFileCount(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[18])(self); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamRemoteStorage_GetFileNameAndSize(void* self, int iFile, void* pnFileSizeInBytes) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, int, void*); + return ((Fn)vt[19])(self, iFile, pnFileSizeInBytes); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamRemoteStorage_GetQuota(void* self, void* total, void* avail) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, void*, void*); + ((Fn)vt[20])(self, total, avail); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamRemoteStorage_IsCloudEnabledForAccount(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[21])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamRemoteStorage_IsCloudEnabledForApp(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[22])(self); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamRemoteStorage_SetCloudEnabledForApp(void* self, int enabled) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, int); + ((Fn)vt[23])(self, enabled); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamRemoteStorage_UGCDownload(void* self, uint64_t hContent, void* _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint64_t, void*); + return ((Fn)vt[24])(self, hContent, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamRemoteStorage_GetUGCDownloadProgress(void* self, uint64_t _a0, void* d, void* e) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*, void*); + return ((Fn)vt[25])(self, _a0, d, e); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamRemoteStorage_GetUGCDetails(void* self, void* _a0, void* appID, void* ppchName, void* pcbFile, void* steamIDOwner) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, void*, void*, void*, void*); + return ((Fn)vt[26])(self, _a0, appID, ppchName, pcbFile, steamIDOwner); +} +WN_STEAMAPI_EXPORT int32_t SteamAPI_ISteamRemoteStorage_UGCRead(void* self, void* _a0, void* _a1, void* _a2, void* _a3, void* _a4) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int32_t (*Fn)(void*, void*, void*, void*, void*, void*); + return ((Fn)vt[27])(self, _a0, _a1, _a2, _a3, _a4); +} +WN_STEAMAPI_EXPORT int32_t SteamAPI_ISteamRemoteStorage_GetCachedUGCCount(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int32_t (*Fn)(void*); + return ((Fn)vt[28])(self); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamRemoteStorage_GetCachedUGCHandle(void* self, void* _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, void*); + return ((Fn)vt[29])(self, _a0); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamRemoteStorage_PublishWorkshopFile_DEPRECATED(void* self, void* _a0, void* _a1, uint32_t _a2, void* _a3, void* _a4, int _a5, void* _a6, void* _a7, int _a8) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, void*, void*, uint32_t, void*, void*, int, void*, void*, int); + return ((Fn)vt[30])(self, _a0, _a1, _a2, _a3, _a4, _a5, _a6, _a7, _a8); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamRemoteStorage_CreatePublishedFileUpdateRequest_DEPRECATED(void* self, uint64_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint64_t); + return ((Fn)vt[31])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamRemoteStorage_UpdatePublishedFileFile_DEPRECATED(void* self, uint64_t _a0, void* _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*); + return ((Fn)vt[32])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamRemoteStorage_UpdatePublishedFilePreviewFile_DEPRECATED(void* self, uint64_t _a0, void* _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*); + return ((Fn)vt[33])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamRemoteStorage_UpdatePublishedFileTitle_DEPRECATED(void* self, uint64_t _a0, void* _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*); + return ((Fn)vt[34])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamRemoteStorage_UpdatePublishedFileDescription_DEPRECATED(void* self, uint64_t _a0, void* _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*); + return ((Fn)vt[35])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamRemoteStorage_UpdatePublishedFileVisibility_DEPRECATED(void* self, uint64_t _a0, int _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, int); + return ((Fn)vt[36])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamRemoteStorage_UpdatePublishedFileTags_DEPRECATED(void* self, uint64_t _a0, void* _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*); + return ((Fn)vt[37])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamRemoteStorage_CommitPublishedFileUpdate_DEPRECATED(void* self, uint64_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint64_t); + return ((Fn)vt[38])(self, _a0); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamRemoteStorage_GetPublishedFileDetails_DEPRECATED(void* self, uint64_t _a0, uint32_t _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint64_t, uint32_t); + return ((Fn)vt[39])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamRemoteStorage_DeletePublishedFile_DEPRECATED(void* self, uint64_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint64_t); + return ((Fn)vt[40])(self, _a0); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamRemoteStorage_EnumerateUserPublishedFiles_DEPRECATED(void* self, uint32_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint32_t); + return ((Fn)vt[41])(self, _a0); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamRemoteStorage_SubscribePublishedFile_DEPRECATED(void* self, uint64_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint64_t); + return ((Fn)vt[42])(self, _a0); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamRemoteStorage_EnumerateUserSubscribedFiles_DEPRECATED(void* self, uint32_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint32_t); + return ((Fn)vt[43])(self, _a0); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamRemoteStorage_UnsubscribePublishedFile_DEPRECATED(void* self, uint64_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint64_t); + return ((Fn)vt[44])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamRemoteStorage_UpdatePublishedFileSetChangeDescription_DEPRECATED(void* self, uint64_t _a0, void* _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*); + return ((Fn)vt[45])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamRemoteStorage_GetPublishedItemVoteDetails_DEPRECATED(void* self, uint64_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint64_t); + return ((Fn)vt[46])(self, _a0); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamRemoteStorage_UpdateUserPublishedItemVote_DEPRECATED(void* self, uint64_t _a0, int _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint64_t, int); + return ((Fn)vt[47])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamRemoteStorage_GetUserPublishedItemVoteDetails_DEPRECATED(void* self, uint64_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint64_t); + return ((Fn)vt[48])(self, _a0); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamRemoteStorage_EnumerateUserSharedWorkshopFiles_DEPRECATED(void* self, uint64_t _a0, uint32_t _a1, void* _a2, void* _a3) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint64_t, uint32_t, void*, void*); + return ((Fn)vt[49])(self, _a0, _a1, _a2, _a3); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamRemoteStorage_PublishVideo_DEPRECATED(void* self, int _a0, void* _a1, uint32_t _a2, void* _a3, void* _a4, uint32_t _a5, void* _a6) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, int, void*, uint32_t, void*, void*, uint32_t, void*); + return ((Fn)vt[50])(self, _a0, _a1, _a2, _a3, _a4, _a5, _a6); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamRemoteStorage_SetUserPublishedFileAction_DEPRECATED(void* self, uint64_t _a0, int _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint64_t, int); + return ((Fn)vt[51])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamRemoteStorage_EnumeratePublishedFilesByUserAction_DEPRECATED(void* self, int _a0, uint32_t _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, int, uint32_t); + return ((Fn)vt[52])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamRemoteStorage_EnumeratePublishedWorkshopFiles_DEPRECATED(void* self, int _a0, uint32_t _a1, uint32_t _a2, uint32_t _a3, void* _a4, void* _a5) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, int, uint32_t, uint32_t, uint32_t, void*, void*); + return ((Fn)vt[53])(self, _a0, _a1, _a2, _a3, _a4, _a5); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamRemoteStorage_UGCDownloadToLocation(void* self, uint64_t hContent, void* _a1, void* _a2) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint64_t, void*, void*); + return ((Fn)vt[54])(self, hContent, _a1, _a2); +} +WN_STEAMAPI_EXPORT int32_t SteamAPI_ISteamRemoteStorage_GetLocalFileChangeCount(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int32_t (*Fn)(void*); + return ((Fn)vt[55])(self); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamRemoteStorage_GetLocalFileChange(void* self, void* _a0, void* peChangeType, void* pePathType) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, void*, void*, void*); + return ((Fn)vt[56])(self, _a0, peChangeType, pePathType); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamRemoteStorage_BeginFileWriteBatch(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[57])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamRemoteStorage_EndFileWriteBatch(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[58])(self); +} + +WN_STEAMAPI_EXPORT uint32_t SteamAPI_ISteamScreenshots_WriteScreenshot(void* self, void* _a0, uint32_t _a1, int _a2, int _a3) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint32_t (*Fn)(void*, void*, uint32_t, int, int); + return ((Fn)vt[0])(self, _a0, _a1, _a2, _a3); +} +WN_STEAMAPI_EXPORT uint32_t SteamAPI_ISteamScreenshots_AddScreenshotToLibrary(void* self, void* _a0, void* _a1, int _a2, int _a3) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint32_t (*Fn)(void*, void*, void*, int, int); + return ((Fn)vt[1])(self, _a0, _a1, _a2, _a3); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamScreenshots_TriggerScreenshot(void* self) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*); + ((Fn)vt[2])(self); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamScreenshots_HookScreenshots(void* self, int hooked) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, int); + ((Fn)vt[3])(self, hooked); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamScreenshots_SetLocation(void* self, uint32_t _a0, void* _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint32_t, void*); + return ((Fn)vt[4])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamScreenshots_TagUser(void* self, uint32_t _a0, uint64_t _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint32_t, uint64_t); + return ((Fn)vt[5])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamScreenshots_TagPublishedFile(void* self, uint32_t _a0, uint64_t _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint32_t, uint64_t); + return ((Fn)vt[6])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamScreenshots_IsScreenshotsHooked(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[7])(self); +} +WN_STEAMAPI_EXPORT uint32_t SteamAPI_ISteamScreenshots_AddVRScreenshotToLibrary(void* self, int _a0, void* _a1, void* _a2) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint32_t (*Fn)(void*, int, void*, void*); + return ((Fn)vt[8])(self, _a0, _a1, _a2); +} + +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamUGC_CreateQueryUserUGCRequest(void* self, uint32_t _a0, int _a1, int _a2, int _a3, uint32_t _a4, uint32_t _a5, uint32_t _a6) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint32_t, int, int, int, uint32_t, uint32_t, uint32_t); + return ((Fn)vt[0])(self, _a0, _a1, _a2, _a3, _a4, _a5, _a6); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamUGC_CreateQueryAllUGCRequest_Page(void* self, int _a0, int _a1, uint32_t _a2, uint32_t _a3, uint32_t _a4) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, int, int, uint32_t, uint32_t, uint32_t); + return ((Fn)vt[1])(self, _a0, _a1, _a2, _a3, _a4); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamUGC_CreateQueryAllUGCRequest_Cursor(void* self, int _a0, int _a1, uint32_t _a2, uint32_t _a3, void* _a4) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, int, int, uint32_t, uint32_t, void*); + return ((Fn)vt[2])(self, _a0, _a1, _a2, _a3, _a4); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamUGC_CreateQueryUGCDetailsRequest(void* self, void* _a0, uint32_t _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, void*, uint32_t); + return ((Fn)vt[3])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamUGC_SendQueryUGCRequest(void* self, uint64_t handle) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint64_t); + return ((Fn)vt[4])(self, handle); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_GetQueryUGCResult(void* self, uint64_t _a0, uint32_t _a1, void* _a2) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, uint32_t, void*); + return ((Fn)vt[5])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT uint32_t SteamAPI_ISteamUGC_GetQueryUGCNumTags(void* self, uint64_t _a0, uint32_t _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint32_t (*Fn)(void*, uint64_t, uint32_t); + return ((Fn)vt[6])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_GetQueryUGCTag(void* self, uint64_t _a0, uint32_t _a1, uint32_t _a2, void* v, uint32_t vn) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, uint32_t, uint32_t, void*, uint32_t); + return ((Fn)vt[7])(self, _a0, _a1, _a2, v, vn); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_GetQueryUGCTagDisplayName(void* self, uint64_t _a0, uint32_t _a1, uint32_t _a2, void* v, uint32_t vn) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, uint32_t, uint32_t, void*, uint32_t); + return ((Fn)vt[8])(self, _a0, _a1, _a2, v, vn); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_GetQueryUGCPreviewURL(void* self, uint64_t _a0, uint32_t _a1, void* v, uint32_t vn) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, uint32_t, void*, uint32_t); + return ((Fn)vt[9])(self, _a0, _a1, v, vn); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_GetQueryUGCMetadata(void* self, uint64_t _a0, uint32_t _a1, void* v, uint32_t vn) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, uint32_t, void*, uint32_t); + return ((Fn)vt[10])(self, _a0, _a1, v, vn); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_GetQueryUGCChildren(void* self, uint64_t _a0, uint32_t _a1, void* _a2, uint32_t _a3) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, uint32_t, void*, uint32_t); + return ((Fn)vt[11])(self, _a0, _a1, _a2, _a3); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_GetQueryUGCStatistic(void* self, uint64_t _a0, uint32_t _a1, int _a2, void* out) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, uint32_t, int, void*); + return ((Fn)vt[12])(self, _a0, _a1, _a2, out); +} +WN_STEAMAPI_EXPORT uint32_t SteamAPI_ISteamUGC_GetQueryUGCNumAdditionalPreviews(void* self, uint64_t _a0, uint32_t _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint32_t (*Fn)(void*, uint64_t, uint32_t); + return ((Fn)vt[13])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_GetQueryUGCAdditionalPreview(void* self, uint64_t _a0, uint32_t _a1, uint32_t _a2, void* url, uint32_t uns, void* orig, uint32_t os, void* _a7) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, uint32_t, uint32_t, void*, uint32_t, void*, uint32_t, void*); + return ((Fn)vt[14])(self, _a0, _a1, _a2, url, uns, orig, os, _a7); +} +WN_STEAMAPI_EXPORT uint32_t SteamAPI_ISteamUGC_GetQueryUGCNumKeyValueTags(void* self, uint64_t _a0, uint32_t _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint32_t (*Fn)(void*, uint64_t, uint32_t); + return ((Fn)vt[15])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_GetQueryUGCKeyValueTagByIndex(void* self, uint64_t _a0, uint32_t _a1, uint32_t _a2, void* k, uint32_t kn, void* v, uint32_t vn) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, uint32_t, uint32_t, void*, uint32_t, void*, uint32_t); + return ((Fn)vt[16])(self, _a0, _a1, _a2, k, kn, v, vn); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_GetQueryUGCKeyValueTagByName(void* self, uint64_t _a0, uint32_t _a1, void* _a2, void* v, uint32_t vn) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, uint32_t, void*, void*, uint32_t); + return ((Fn)vt[17])(self, _a0, _a1, _a2, v, vn); +} +WN_STEAMAPI_EXPORT uint32_t SteamAPI_ISteamUGC_GetQueryUGCContentDescriptors(void* self, uint64_t _a0, uint32_t _a1, void* _a2, uint32_t _a3) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint32_t (*Fn)(void*, uint64_t, uint32_t, void*, uint32_t); + return ((Fn)vt[18])(self, _a0, _a1, _a2, _a3); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_ReleaseQueryUGCRequest(void* self, uint64_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t); + return ((Fn)vt[19])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_AddRequiredTag(void* self, uint64_t _a0, void* _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*); + return ((Fn)vt[20])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_AddRequiredTagGroup(void* self, uint64_t _a0, void* _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*); + return ((Fn)vt[21])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_AddExcludedTag(void* self, uint64_t _a0, void* _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*); + return ((Fn)vt[22])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_SetReturnOnlyIDs(void* self, uint64_t _a0, int _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, int); + return ((Fn)vt[23])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_SetReturnKeyValueTags(void* self, uint64_t _a0, int _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, int); + return ((Fn)vt[24])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_SetReturnLongDescription(void* self, uint64_t _a0, int _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, int); + return ((Fn)vt[25])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_SetReturnMetadata(void* self, uint64_t _a0, int _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, int); + return ((Fn)vt[26])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_SetReturnChildren(void* self, uint64_t _a0, int _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, int); + return ((Fn)vt[27])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_SetReturnAdditionalPreviews(void* self, uint64_t _a0, int _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, int); + return ((Fn)vt[28])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_SetReturnTotalOnly(void* self, uint64_t _a0, int _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, int); + return ((Fn)vt[29])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_SetReturnPlaytimeStats(void* self, uint64_t _a0, uint32_t _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, uint32_t); + return ((Fn)vt[30])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_SetLanguage(void* self, uint64_t _a0, void* _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*); + return ((Fn)vt[31])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_SetAllowCachedResponse(void* self, uint64_t _a0, uint32_t _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, uint32_t); + return ((Fn)vt[32])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_SetCloudFileNameFilter(void* self, uint64_t _a0, void* _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*); + return ((Fn)vt[33])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_SetMatchAnyTag(void* self, uint64_t _a0, int _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, int); + return ((Fn)vt[34])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_SetSearchText(void* self, uint64_t _a0, void* _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*); + return ((Fn)vt[35])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_SetRankedByTrendDays(void* self, uint64_t _a0, uint32_t _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, uint32_t); + return ((Fn)vt[36])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_SetTimeCreatedDateRange(void* self, uint64_t _a0, uint32_t _a1, uint32_t _a2) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, uint32_t, uint32_t); + return ((Fn)vt[37])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_SetTimeUpdatedDateRange(void* self, uint64_t _a0, uint32_t _a1, uint32_t _a2) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, uint32_t, uint32_t); + return ((Fn)vt[38])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_AddRequiredKeyValueTag(void* self, uint64_t _a0, void* _a1, void* _a2) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*, void*); + return ((Fn)vt[39])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamUGC_RequestUGCDetails(void* self, void* _a0, void* _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, void*, void*); + return ((Fn)vt[40])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamUGC_CreateItem(void* self, uint32_t _a0, int _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint32_t, int); + return ((Fn)vt[41])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamUGC_StartItemUpdate(void* self, uint32_t _a0, uint64_t _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint32_t, uint64_t); + return ((Fn)vt[42])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_SetItemTitle(void* self, uint64_t _a0, void* _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*); + return ((Fn)vt[43])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_SetItemDescription(void* self, uint64_t _a0, void* _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*); + return ((Fn)vt[44])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_SetItemUpdateLanguage(void* self, uint64_t _a0, void* _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*); + return ((Fn)vt[45])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_SetItemMetadata(void* self, uint64_t _a0, void* _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*); + return ((Fn)vt[46])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_SetItemVisibility(void* self, uint64_t _a0, int _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, int); + return ((Fn)vt[47])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_SetItemTags(void* self, uint64_t _a0, void* _a1, int _a2) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*, int); + return ((Fn)vt[48])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_SetItemContent(void* self, uint64_t _a0, void* _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*); + return ((Fn)vt[49])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_SetItemPreview(void* self, uint64_t _a0, void* _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*); + return ((Fn)vt[50])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_SetAllowLegacyUpload(void* self, uint64_t _a0, int _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, int); + return ((Fn)vt[51])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_RemoveAllItemKeyValueTags(void* self, uint64_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t); + return ((Fn)vt[52])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_RemoveItemKeyValueTags(void* self, uint64_t _a0, void* _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*); + return ((Fn)vt[53])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_AddItemKeyValueTag(void* self, uint64_t _a0, void* _a1, void* _a2) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*, void*); + return ((Fn)vt[54])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_AddItemPreviewFile(void* self, uint64_t _a0, void* _a1, int _a2) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*, int); + return ((Fn)vt[55])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_AddItemPreviewVideo(void* self, uint64_t _a0, void* _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*); + return ((Fn)vt[56])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_UpdateItemPreviewFile(void* self, uint64_t _a0, uint32_t _a1, void* _a2) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, uint32_t, void*); + return ((Fn)vt[57])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_UpdateItemPreviewVideo(void* self, uint64_t _a0, uint32_t _a1, void* _a2) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, uint32_t, void*); + return ((Fn)vt[58])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_RemoveItemPreview(void* self, uint64_t _a0, uint32_t _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, uint32_t); + return ((Fn)vt[59])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_AddContentDescriptor(void* self, uint64_t _a0, int _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, int); + return ((Fn)vt[60])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_RemoveContentDescriptor(void* self, uint64_t _a0, int _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, int); + return ((Fn)vt[61])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamUGC_SubmitItemUpdate(void* self, uint64_t _a0, void* _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint64_t, void*); + return ((Fn)vt[62])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_GetItemUpdateProgress(void* self, uint64_t _a0, void* bp, void* bt) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*, void*); + return ((Fn)vt[63])(self, _a0, bp, bt); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamUGC_SetUserItemVote(void* self, uint64_t _a0, int _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint64_t, int); + return ((Fn)vt[64])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamUGC_GetUserItemVote(void* self, uint64_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint64_t); + return ((Fn)vt[65])(self, _a0); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamUGC_AddItemToFavorites(void* self, uint32_t _a0, uint64_t _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint32_t, uint64_t); + return ((Fn)vt[66])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamUGC_RemoveItemFromFavorites(void* self, uint32_t _a0, uint64_t _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint32_t, uint64_t); + return ((Fn)vt[67])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamUGC_SubscribeItem(void* self, uint64_t publishedFileId) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint64_t); + return ((Fn)vt[68])(self, publishedFileId); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamUGC_UnsubscribeItem(void* self, uint64_t publishedFileId) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint64_t); + return ((Fn)vt[69])(self, publishedFileId); +} +WN_STEAMAPI_EXPORT uint32_t SteamAPI_ISteamUGC_GetNumSubscribedItems(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint32_t (*Fn)(void*); + return ((Fn)vt[70])(self); +} +WN_STEAMAPI_EXPORT uint32_t SteamAPI_ISteamUGC_GetSubscribedItems(void* self, void* pIds, uint32_t cMax) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint32_t (*Fn)(void*, void*, uint32_t); + return ((Fn)vt[71])(self, pIds, cMax); +} +WN_STEAMAPI_EXPORT uint32_t SteamAPI_ISteamUGC_GetItemState(void* self, uint64_t publishedFileId) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint32_t (*Fn)(void*, uint64_t); + return ((Fn)vt[72])(self, publishedFileId); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_GetItemInstallInfo(void* self, uint64_t publishedFileId, void* bytes, void* folder, uint32_t fn, void* timestamp) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*, void*, uint32_t, void*); + return ((Fn)vt[73])(self, publishedFileId, bytes, folder, fn, timestamp); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_GetItemDownloadInfo(void* self, uint64_t publishedFileId, void* bd, void* bt) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*, void*); + return ((Fn)vt[74])(self, publishedFileId, bd, bt); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_DownloadItem(void* self, uint64_t publishedFileId, void* _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*); + return ((Fn)vt[75])(self, publishedFileId, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_BInitWorkshopForGameServer(void* self, uint32_t _a0, void* _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint32_t, void*); + return ((Fn)vt[76])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamUGC_SuspendDownloads(void* self, int _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, int); + ((Fn)vt[77])(self, _a0); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamUGC_StartPlaytimeTracking(void* self, void* _a0, uint32_t _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, void*, uint32_t); + return ((Fn)vt[78])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamUGC_StopPlaytimeTracking(void* self, void* _a0, uint32_t _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, void*, uint32_t); + return ((Fn)vt[79])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamUGC_StopPlaytimeTrackingForAllItems(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*); + return ((Fn)vt[80])(self); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamUGC_AddDependency(void* self, uint64_t _a0, uint64_t _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint64_t, uint64_t); + return ((Fn)vt[81])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamUGC_RemoveDependency(void* self, uint64_t _a0, uint64_t _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint64_t, uint64_t); + return ((Fn)vt[82])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamUGC_AddAppDependency(void* self, uint64_t _a0, uint32_t _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint64_t, uint32_t); + return ((Fn)vt[83])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamUGC_RemoveAppDependency(void* self, uint64_t _a0, uint32_t _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint64_t, uint32_t); + return ((Fn)vt[84])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamUGC_GetAppDependencies(void* self, uint64_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint64_t); + return ((Fn)vt[85])(self, _a0); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamUGC_DeleteItem(void* self, uint64_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint64_t); + return ((Fn)vt[86])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUGC_ShowWorkshopEULA(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[87])(self); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamUGC_GetWorkshopEULAStatus(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*); + return ((Fn)vt[88])(self); +} +WN_STEAMAPI_EXPORT uint32_t SteamAPI_ISteamUGC_GetUserContentDescriptorPreferences(void* self, void* _a0, uint32_t _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint32_t (*Fn)(void*, void*, uint32_t); + return ((Fn)vt[89])(self, _a0, _a1); +} + +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUser_GetHSteamUser(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[0])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUser_BLoggedOn(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[1])(self); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamUser_GetSteamID(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*); + return ((Fn)vt[2])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUser_InitiateGameConnection_DEPRECATED(void* self, void* _a0, int _a1, uint64_t _a2, uint32_t _a3, uint16_t _a4, int _a5) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, int, uint64_t, uint32_t, uint16_t, int); + return ((Fn)vt[3])(self, _a0, _a1, _a2, _a3, _a4, _a5); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamUser_TerminateGameConnection_DEPRECATED(void* self, uint32_t _a0, uint16_t _a1) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint32_t, uint16_t); + ((Fn)vt[4])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamUser_TrackAppUsageEvent(void* self, uint64_t _a0, int _a1, void* _a2) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint64_t, int, void*); + ((Fn)vt[5])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUser_GetUserDataFolder(void* self, void* pchBuffer, int cubBuffer) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, int); + return ((Fn)vt[6])(self, pchBuffer, cubBuffer); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamUser_StartVoiceRecording(void* self) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*); + ((Fn)vt[7])(self); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamUser_StopVoiceRecording(void* self) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*); + ((Fn)vt[8])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUser_GetAvailableVoice(void* self, void* _a0, void* _a1, uint32_t _a2) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, void*, uint32_t); + return ((Fn)vt[9])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUser_GetVoice(void* self, int _a0, void* _a1, uint32_t _a2, void* _a3, int _a4, void* _a5, uint32_t _a6, void* _a7, uint32_t _a8) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int, void*, uint32_t, void*, int, void*, uint32_t, void*, uint32_t); + return ((Fn)vt[10])(self, _a0, _a1, _a2, _a3, _a4, _a5, _a6, _a7, _a8); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUser_DecompressVoice(void* self, void* _a0, uint32_t _a1, void* _a2, uint32_t _a3, void* _a4, uint32_t _a5) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, uint32_t, void*, uint32_t, void*, uint32_t); + return ((Fn)vt[11])(self, _a0, _a1, _a2, _a3, _a4, _a5); +} +WN_STEAMAPI_EXPORT uint32_t SteamAPI_ISteamUser_GetVoiceOptimalSampleRate(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint32_t (*Fn)(void*); + return ((Fn)vt[12])(self); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamUser_GetAuthSessionTicket(void* self, void* buf, int maxLen, void* pcbTicket, void* _a3) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, void*, int, void*, void*); + return ((Fn)vt[13])(self, buf, maxLen, pcbTicket, _a3); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamUser_GetAuthTicketForWebApi(void* self, void* pchIdentity) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, void*); + return ((Fn)vt[14])(self, pchIdentity); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUser_BeginAuthSession(void* self, void* _a0, int cbTicket, uint64_t steamID) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, int, uint64_t); + return ((Fn)vt[15])(self, _a0, cbTicket, steamID); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamUser_EndAuthSession(void* self, uint64_t _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint64_t); + ((Fn)vt[16])(self, _a0); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamUser_CancelAuthTicket(void* self, uint64_t hAuthTicket) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint64_t); + ((Fn)vt[17])(self, hAuthTicket); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUser_UserHasLicenseForApp(void* self, uint64_t steamID, uint32_t appID) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, uint32_t); + return ((Fn)vt[18])(self, steamID, appID); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUser_BIsBehindNAT(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[19])(self); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamUser_AdvertiseGame(void* self, uint64_t _a0, uint32_t _a1, uint16_t _a2) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, uint64_t, uint32_t, uint16_t); + ((Fn)vt[20])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamUser_RequestEncryptedAppTicket(void* self, void* rgubData, int cbData) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, void*, int); + return ((Fn)vt[21])(self, rgubData, cbData); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUser_GetEncryptedAppTicket(void* self, void* buf, int cbMax, void* pcbTicket) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, int, void*); + return ((Fn)vt[22])(self, buf, cbMax, pcbTicket); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUser_GetGameBadgeLevel(void* self, int nSeries, int bFoil) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int, int); + return ((Fn)vt[23])(self, nSeries, bFoil); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUser_GetPlayerSteamLevel(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[24])(self); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamUser_RequestStoreAuthURL(void* self, void* pchRedirectURL) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, void*); + return ((Fn)vt[25])(self, pchRedirectURL); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUser_BIsPhoneVerified(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[26])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUser_BIsTwoFactorEnabled(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[27])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUser_BIsPhoneIdentifying(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[28])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUser_BIsPhoneRequiringVerification(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[29])(self); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamUser_GetMarketEligibility(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*); + return ((Fn)vt[30])(self); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamUser_GetDurationControl(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*); + return ((Fn)vt[31])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUser_BSetDurationControlOnlineState(void* self, void* _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*); + return ((Fn)vt[32])(self, _a0); +} + +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUserStats_RequestCurrentStats(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[0])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUserStats_GetStatInt(void* self, void* pchName, void* pData) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, void*); + return ((Fn)vt[1])(self, pchName, pData); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUserStats_GetStatFloat(void* self, void* pchName, void* pData) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, void*); + return ((Fn)vt[2])(self, pchName, pData); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUserStats_SetStatInt(void* self, void* pchName, int32_t nData) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, int32_t); + return ((Fn)vt[3])(self, pchName, nData); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUserStats_SetStatFloat(void* self, void* pchName, float fData) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, float); + return ((Fn)vt[4])(self, pchName, fData); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUserStats_UpdateAvgRateStat(void* self, void* pchName, float flCountThisSession, double dSessionLength) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, float, double); + return ((Fn)vt[5])(self, pchName, flCountThisSession, dSessionLength); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUserStats_GetAchievement(void* self, void* pchName, void* pbAchieved) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, void*); + return ((Fn)vt[6])(self, pchName, pbAchieved); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUserStats_SetAchievement(void* self, void* pchName) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*); + return ((Fn)vt[7])(self, pchName); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUserStats_ClearAchievement(void* self, void* pchName) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*); + return ((Fn)vt[8])(self, pchName); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUserStats_GetAchievementAndUnlockTime(void* self, void* pchName, void* pbAchieved, void* punlockTime) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, void*, void*); + return ((Fn)vt[9])(self, pchName, pbAchieved, punlockTime); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUserStats_StoreStats(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[10])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUserStats_GetAchievementIcon(void* self, void* pchName) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*); + return ((Fn)vt[11])(self, pchName); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamUserStats_GetAchievementDisplayAttribute(void* self, void* pchName, void* pchKey) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, void*, void*); + return ((Fn)vt[12])(self, pchName, pchKey); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUserStats_IndicateAchievementProgress(void* self, void* pchName, uint32_t nCurProgress, uint32_t nMaxProgress) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, uint32_t, uint32_t); + return ((Fn)vt[13])(self, pchName, nCurProgress, nMaxProgress); +} +WN_STEAMAPI_EXPORT uint32_t SteamAPI_ISteamUserStats_GetNumAchievements(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint32_t (*Fn)(void*); + return ((Fn)vt[14])(self); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamUserStats_GetAchievementName(void* self, uint32_t idx) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, uint32_t); + return ((Fn)vt[15])(self, idx); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamUserStats_RequestUserStats(void* self, uint64_t steamID) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint64_t); + return ((Fn)vt[16])(self, steamID); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUserStats_GetUserStatInt(void* self, uint64_t steamID, void* pchName, void* pData) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*, void*); + return ((Fn)vt[17])(self, steamID, pchName, pData); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUserStats_GetUserStatFloat(void* self, uint64_t steamID, void* pchName, void* pData) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*, void*); + return ((Fn)vt[18])(self, steamID, pchName, pData); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUserStats_GetUserAchievement(void* self, uint64_t steamID, void* pchName, void* pbAchieved) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*, void*); + return ((Fn)vt[19])(self, steamID, pchName, pbAchieved); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUserStats_GetUserAchievementAndUnlockTime(void* self, uint64_t steamID, void* pchName, void* pbAchieved, void* punlockTime) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*, void*, void*); + return ((Fn)vt[20])(self, steamID, pchName, pbAchieved, punlockTime); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUserStats_ResetAllStats(void* self, int bAchievementsToo) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int); + return ((Fn)vt[21])(self, bAchievementsToo); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamUserStats_FindOrCreateLeaderboard(void* self, void* _a0, void* _a1, void* _a2) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, void*, void*, void*); + return ((Fn)vt[22])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamUserStats_FindLeaderboard(void* self, void* _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, void*); + return ((Fn)vt[23])(self, _a0); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamUserStats_GetLeaderboardName(void* self, uint64_t _a0) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*, uint64_t); + return ((Fn)vt[24])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUserStats_GetLeaderboardEntryCount(void* self, uint64_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t); + return ((Fn)vt[25])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUserStats_GetLeaderboardSortMethod(void* self, uint64_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t); + return ((Fn)vt[26])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUserStats_GetLeaderboardDisplayType(void* self, uint64_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t); + return ((Fn)vt[27])(self, _a0); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamUserStats_DownloadLeaderboardEntries(void* self, uint64_t hLeaderboard, void* _a1, void* _a2, void* _a3) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint64_t, void*, void*, void*); + return ((Fn)vt[28])(self, hLeaderboard, _a1, _a2, _a3); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamUserStats_DownloadLeaderboardEntriesForUsers(void* self, uint64_t hLeaderboard, void* _a1, void* _a2) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint64_t, void*, void*); + return ((Fn)vt[29])(self, hLeaderboard, _a1, _a2); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUserStats_GetDownloadedLeaderboardEntry(void* self, uint64_t _a0, int _a1, void* _a2, void* _a3, int _a4) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, int, void*, void*, int); + return ((Fn)vt[30])(self, _a0, _a1, _a2, _a3, _a4); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamUserStats_UploadLeaderboardScore(void* self, uint64_t hLeaderboard, void* _a1, int32_t score, void* _a3, void* _a4) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint64_t, void*, int32_t, void*, void*); + return ((Fn)vt[31])(self, hLeaderboard, _a1, score, _a3, _a4); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamUserStats_AttachLeaderboardUGC(void* self, uint64_t hLeaderboard, void* _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint64_t, void*); + return ((Fn)vt[32])(self, hLeaderboard, _a1); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamUserStats_GetNumberOfCurrentPlayers(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*); + return ((Fn)vt[33])(self); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamUserStats_RequestGlobalAchievementPercentages(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*); + return ((Fn)vt[34])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUserStats_GetMostAchievedAchievementInfo(void* self, void* _a0, uint32_t _a1, void* _a2, void* _a3) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, uint32_t, void*, void*); + return ((Fn)vt[35])(self, _a0, _a1, _a2, _a3); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUserStats_GetNextMostAchievedAchievementInfo(void* self, int _a0, void* _a1, uint32_t _a2, void* _a3, void* _a4) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int, void*, uint32_t, void*, void*); + return ((Fn)vt[36])(self, _a0, _a1, _a2, _a3, _a4); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUserStats_GetAchievementAchievedPercent(void* self, void* _a0, void* p) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, void*); + return ((Fn)vt[37])(self, _a0, p); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamUserStats_RequestGlobalStats(void* self, void* _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, void*); + return ((Fn)vt[38])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUserStats_GetGlobalStatInt64(void* self, void* _a0, void* p) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, void*); + return ((Fn)vt[39])(self, _a0, p); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUserStats_GetGlobalStatDouble(void* self, void* _a0, void* p) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, void*); + return ((Fn)vt[40])(self, _a0, p); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUserStats_GetGlobalStatHistoryInt64(void* self, void* _a0, void* _a1, uint32_t _a2) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, void*, uint32_t); + return ((Fn)vt[41])(self, _a0, _a1, _a2); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUserStats_GetGlobalStatHistoryDouble(void* self, void* _a0, void* _a1, uint32_t _a2) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, void*, uint32_t); + return ((Fn)vt[42])(self, _a0, _a1, _a2); +} + +WN_STEAMAPI_EXPORT uint32_t SteamAPI_ISteamUtils_GetSecondsSinceAppActive(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint32_t (*Fn)(void*); + return ((Fn)vt[0])(self); +} +WN_STEAMAPI_EXPORT uint32_t SteamAPI_ISteamUtils_GetSecondsSinceComputerActive(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint32_t (*Fn)(void*); + return ((Fn)vt[1])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUtils_GetConnectedUniverse(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[2])(self); +} +WN_STEAMAPI_EXPORT uint32_t SteamAPI_ISteamUtils_GetServerRealTime(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint32_t (*Fn)(void*); + return ((Fn)vt[3])(self); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamUtils_GetIPCountry(void* self) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*); + return ((Fn)vt[4])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUtils_GetImageSize(void* self, int iImage, void* pnWidth, void* pnHeight) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int, void*, void*); + return ((Fn)vt[5])(self, iImage, pnWidth, pnHeight); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUtils_GetImageRGBA(void* self, int iImage, void* pubDest, int nDestBufferSize) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int, void*, int); + return ((Fn)vt[6])(self, iImage, pubDest, nDestBufferSize); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUtils_GetCSERIPPort(void* self, void* _a0, void* _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, void*); + return ((Fn)vt[7])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT uint8_t SteamAPI_ISteamUtils_GetCurrentBatteryPower(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint8_t (*Fn)(void*); + return ((Fn)vt[8])(self); +} +WN_STEAMAPI_EXPORT uint32_t SteamAPI_ISteamUtils_GetAppID(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint32_t (*Fn)(void*); + return ((Fn)vt[9])(self); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamUtils_SetOverlayNotificationPosition(void* self, int _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, int); + ((Fn)vt[10])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUtils_IsAPICallCompleted(void* self, uint64_t hCall, void* pbFailed) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*); + return ((Fn)vt[11])(self, hCall, pbFailed); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUtils_GetAPICallFailureReason(void* self, void* _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*); + return ((Fn)vt[12])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUtils_GetAPICallResult(void* self, uint64_t hCall, void* pCallback, int cubCallback, int iCallbackExpected, void* pbFailed) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint64_t, void*, int, int, void*); + return ((Fn)vt[13])(self, hCall, pCallback, cubCallback, iCallbackExpected, pbFailed); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamUtils_RunFrame(void* self) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*); + ((Fn)vt[14])(self); +} +WN_STEAMAPI_EXPORT uint32_t SteamAPI_ISteamUtils_GetIPCCallCount(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint32_t (*Fn)(void*); + return ((Fn)vt[15])(self); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamUtils_SetWarningMessageHook(void* self, void* _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, void*); + ((Fn)vt[16])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUtils_IsOverlayEnabled(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[17])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUtils_BOverlayNeedsPresent(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[18])(self); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamUtils_CheckFileSignature(void* self, void* _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, void*); + return ((Fn)vt[19])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUtils_ShowGamepadTextInput(void* self, int _a0, int _a1, void* _a2, uint32_t _a3, void* _a4) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int, int, void*, uint32_t, void*); + return ((Fn)vt[20])(self, _a0, _a1, _a2, _a3, _a4); +} +WN_STEAMAPI_EXPORT uint32_t SteamAPI_ISteamUtils_GetEnteredGamepadTextLength(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint32_t (*Fn)(void*); + return ((Fn)vt[21])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUtils_GetEnteredGamepadTextInput(void* self, void* _a0, uint32_t _a1) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, uint32_t); + return ((Fn)vt[22])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamUtils_GetSteamUILanguage(void* self) { + if (self == NULL) return NULL; + void** vt = *(void***)self; + typedef void* (*Fn)(void*); + return ((Fn)vt[23])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUtils_IsSteamRunningInVR(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[24])(self); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamUtils_SetOverlayNotificationInset(void* self, int _a0, int _a1) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, int, int); + ((Fn)vt[25])(self, _a0, _a1); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUtils_IsSteamInBigPictureMode(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[26])(self); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamUtils_StartVRDashboard(void* self) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*); + ((Fn)vt[27])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUtils_IsVRHeadsetStreamingEnabled(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[28])(self); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamUtils_SetVRHeadsetStreamingEnabled(void* self, int _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, int); + ((Fn)vt[29])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUtils_IsSteamChinaLauncher(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[30])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUtils_InitFilterText(void* self, uint32_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint32_t); + return ((Fn)vt[31])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUtils_FilterText(void* self, void* _a0, void* _a1, void* in, void* out, uint32_t outSize) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*, void*, void*, void*, uint32_t); + return ((Fn)vt[32])(self, _a0, _a1, in, out, outSize); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUtils_GetIPv6ConnectivityState(void* self, int _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int); + return ((Fn)vt[33])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUtils_IsSteamRunningOnSteamDeck(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[34])(self); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUtils_ShowFloatingGamepadTextInput(void* self, int _a0, int _a1, int _a2, int _a3, int _a4) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, int, int, int, int, int); + return ((Fn)vt[35])(self, _a0, _a1, _a2, _a3, _a4); +} +WN_STEAMAPI_EXPORT void SteamAPI_ISteamUtils_SetGameLauncherMode(void* self, int _a0) { + if (self == NULL) return; + void** vt = *(void***)self; + typedef void (*Fn)(void*, int); + ((Fn)vt[36])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamUtils_DismissFloatingGamepadTextInput(void* self) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*); + return ((Fn)vt[37])(self); +} + +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamVideo_GetVideoURL_DEPRECATED(void* self, uint32_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint32_t); + return ((Fn)vt[0])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamVideo_IsBroadcasting(void* self, void* pnNumViewers) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, void*); + return ((Fn)vt[1])(self, pnNumViewers); +} +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamVideo_GetOPFSettings(void* self, uint32_t _a0) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef uint64_t (*Fn)(void*, uint32_t); + return ((Fn)vt[2])(self, _a0); +} +WN_STEAMAPI_EXPORT int SteamAPI_ISteamVideo_GetOPFStringForApp(void* self, uint32_t _a0, void* buf, void* pnBufSize) { + if (self == NULL) return 0; + void** vt = *(void***)self; + typedef int (*Fn)(void*, uint32_t, void*, void*); + return ((Fn)vt[3])(self, _a0, buf, pnBufSize); +} diff --git a/app/src/main/cpp/wn-steamapi-bridge/steam_api_bridge_lifecycle.c b/app/src/main/cpp/wn-steamapi-bridge/steam_api_bridge_lifecycle.c new file mode 100644 index 000000000..e5035a1e7 --- /dev/null +++ b/app/src/main/cpp/wn-steamapi-bridge/steam_api_bridge_lifecycle.c @@ -0,0 +1,243 @@ +#include +#include +#include +#include + +#define WN_STEAMAPI_EXPORT __declspec(dllexport) + +static HMODULE g_wine_bridge = NULL; +static HMODULE g_gbe = NULL; +static int32_t g_pipe = 0; +static int32_t g_user = 0; +static int g_inited = 0; +static void* g_steam_client = NULL; + +typedef void* (*CreateInterface_fn)(const char*, int*); +static CreateInterface_fn g_CreateInterface = NULL; + +static int wnb_logging_enabled(void) { + static volatile LONG cached = -1; + LONG v = cached; + if (v == -1) { + const char* e = getenv("WNB_LOG"); + v = (e && e[0] && e[0] != '0') ? 1 : 0; + cached = v; + } + return (int)v; +} + +static void wnb_log(const char* msg) { + if (!wnb_logging_enabled()) return; + FILE* f = fopen("C:\\wnb.log", "a"); + if (f) { fputs(msg, f); fputs("\n", f); fclose(f); } +} + +static CRITICAL_SECTION g_init_cs; +static volatile LONG g_init_cs_state = 0; + +static void init_lock(void) { + if (InterlockedCompareExchange(&g_init_cs_state, 1, 0) == 0) { + InitializeCriticalSection(&g_init_cs); + InterlockedExchange(&g_init_cs_state, 2); + } + while (g_init_cs_state != 2) Sleep(0); + EnterCriticalSection(&g_init_cs); +} + +static void init_unlock(void) { + LeaveCriticalSection(&g_init_cs); +} + +static int load_wine_bridge(void) { + if (g_wine_bridge != NULL) return g_steam_client != NULL; + wnb_log("[lifecycle] load_wine_bridge: LoadLibrary(steamclient64.dll)"); + g_wine_bridge = LoadLibraryA("steamclient64.dll"); + if (!g_wine_bridge) { + wnb_log("[lifecycle] LoadLibrary(steamclient64.dll) FAILED"); + return 0; + } + wnb_log("[lifecycle] load_wine_bridge: GetProcAddress(CreateInterface)"); + g_CreateInterface = (CreateInterface_fn)GetProcAddress(g_wine_bridge, "CreateInterface"); + if (!g_CreateInterface) { + wnb_log("[lifecycle] steamclient64.dll missing CreateInterface"); + return 0; + } + char buf[160]; + snprintf(buf, sizeof(buf), "[lifecycle] CreateInterface fn=%p — calling SteamClient020", + (void*)g_CreateInterface); + wnb_log(buf); + int code = 0; + g_steam_client = g_CreateInterface("SteamClient020", &code); + snprintf(buf, sizeof(buf), "[lifecycle] CreateInterface(SteamClient020) -> %p code=%d", + g_steam_client, code); + wnb_log(buf); + if (!g_steam_client) { + g_steam_client = g_CreateInterface("SteamClient019", &code); + snprintf(buf, sizeof(buf), "[lifecycle] CreateInterface(SteamClient019) -> %p", g_steam_client); + wnb_log(buf); + } + if (!g_steam_client) { + g_steam_client = g_CreateInterface("SteamClient017", &code); + snprintf(buf, sizeof(buf), "[lifecycle] CreateInterface(SteamClient017) -> %p", g_steam_client); + wnb_log(buf); + } + if (!g_steam_client) { + wnb_log("[lifecycle] all CreateInterface attempts NULL"); + return 0; + } + snprintf(buf, sizeof(buf), + "[lifecycle] wine bridge init OK: client=%p (skipping CreateSteamPipe/ConnectToGlobalUser)", + g_steam_client); + wnb_log(buf); + return 1; +} + +static int gbe_init(void) { + if (g_gbe != NULL) return 1; + g_gbe = LoadLibraryA("original_steam_api64.dll"); + if (!g_gbe) { + wnb_log("[lifecycle] LoadLibrary(original_steam_api64.dll) FAILED"); + return 0; + } + typedef int (*Init_fn)(void); + Init_fn p = (Init_fn)GetProcAddress(g_gbe, "SteamAPI_Init"); + if (p) { + int rc = p(); + char log[80]; + snprintf(log, sizeof(log), "[lifecycle] gbe SteamAPI_Init -> %d", rc); + wnb_log(log); + return rc; + } + return 0; +} + +WN_STEAMAPI_EXPORT int SteamAPI_Init(void) { + if (g_inited) return 1; + init_lock(); + if (!g_inited) { + wnb_log("[lifecycle] SteamAPI_Init called"); + gbe_init(); + g_inited = 1; + } + init_unlock(); + return 1; +} + +WN_STEAMAPI_EXPORT int SteamAPI_InitSafe(void) { return SteamAPI_Init(); } + +WN_STEAMAPI_EXPORT int SteamAPI_InitFlat(void* p_outErrMsg) { + (void)p_outErrMsg; + return SteamAPI_Init() ? 0 : 2; +} + +WN_STEAMAPI_EXPORT void SteamAPI_Shutdown(void) { + wnb_log("[lifecycle] SteamAPI_Shutdown"); + if (g_gbe) { + typedef void (*Sht_fn)(void); + Sht_fn p = (Sht_fn)GetProcAddress(g_gbe, "SteamAPI_Shutdown"); + if (p) p(); + } + g_inited = 0; +} + +WN_STEAMAPI_EXPORT int SteamAPI_IsSteamRunning(void) { return 1; } + +WN_STEAMAPI_EXPORT int SteamAPI_GetHSteamPipe(void) { + if (!g_inited) SteamAPI_Init(); + if (g_pipe != 0) return g_pipe; + if (g_gbe) { + typedef int (*P_fn)(void); + P_fn p = (P_fn)GetProcAddress(g_gbe, "SteamAPI_GetHSteamPipe"); + if (p) return p(); + } + return 0; +} + +WN_STEAMAPI_EXPORT int SteamAPI_GetHSteamUser(void) { + if (!g_inited) SteamAPI_Init(); + if (g_user != 0) return g_user; + if (g_gbe) { + typedef int (*P_fn)(void); + P_fn p = (P_fn)GetProcAddress(g_gbe, "SteamAPI_GetHSteamUser"); + if (p) return p(); + } + return 0; +} + +WN_STEAMAPI_EXPORT int SteamAPI_RestartAppIfNecessary(uint32_t unOwnAppID) { + (void)unOwnAppID; + return 0; +} + +extern void* get_our_matchmaking(void); +extern void* get_our_matchmaking_servers(void); + +static void* WINAPI thunk_GetISteamMatchmaking( + void* self, int hSteamUser, int hSteamPipe, const char* pchVersion) { + (void)self; (void)hSteamUser; (void)hSteamPipe; (void)pchVersion; + static int logged = 0; + if (!logged) { + wnb_log("[lifecycle] vtable thunk: GetISteamMatchmaking -> Valve client"); + logged = 1; + } + return get_our_matchmaking(); +} + +static void* WINAPI thunk_GetISteamMatchmakingServers( + void* self, int hSteamUser, int hSteamPipe, const char* pchVersion) { + (void)self; (void)hSteamUser; (void)hSteamPipe; (void)pchVersion; + static int logged = 0; + if (!logged) { + wnb_log("[lifecycle] vtable thunk: GetISteamMatchmakingServers -> Valve client"); + logged = 1; + } + return get_our_matchmaking_servers(); +} + +static void hook_gbe_matchmaking_slots(void* gbe_client) { + static int hooked = 0; + if (hooked || gbe_client == NULL) return; + + void** vt = *(void***)gbe_client; + void* orig10 = vt[10]; + void* orig11 = vt[11]; + + DWORD old_prot = 0; + if (!VirtualProtect(&vt[10], sizeof(void*) * 2, + PAGE_EXECUTE_READWRITE, &old_prot)) { + wnb_log("[lifecycle] hook: VirtualProtect WRITE failed; cannot redirect matchmaking"); + return; + } + vt[10] = (void*)thunk_GetISteamMatchmaking; + vt[11] = (void*)thunk_GetISteamMatchmakingServers; + DWORD restored_prot = 0; + VirtualProtect(&vt[10], sizeof(void*) * 2, old_prot, &restored_prot); + + char buf[160]; + snprintf(buf, sizeof(buf), + "[lifecycle] hook: patched gbe ISteamClient vt[10] (was %p -> %p) " + "vt[11] (was %p -> %p)", + orig10, vt[10], orig11, vt[11]); + wnb_log(buf); + hooked = 1; +} + +WN_STEAMAPI_EXPORT void* SteamClient(void) { + if (!g_inited) SteamAPI_Init(); + + if (g_gbe) { + typedef void* (*SC_fn)(void); + SC_fn p = (SC_fn)GetProcAddress(g_gbe, "SteamClient"); + if (p) { + void* gbe_client = p(); + hook_gbe_matchmaking_slots(gbe_client); /* one-shot */ + static int logged = 0; + if (!logged) { + wnb_log("[lifecycle] SteamClient() -> gbe (matchmaking slots patched to Valve)"); + logged = 1; + } + return gbe_client; + } + } + return NULL; +} diff --git a/app/src/main/cpp/wn-steamapi-bridge/steam_api_bridge_overrides.c b/app/src/main/cpp/wn-steamapi-bridge/steam_api_bridge_overrides.c new file mode 100644 index 000000000..3bda8e948 --- /dev/null +++ b/app/src/main/cpp/wn-steamapi-bridge/steam_api_bridge_overrides.c @@ -0,0 +1,795 @@ + +#include +#include +#include +#include +#include + +#define WN_STEAMAPI_EXPORT __declspec(dllexport) + +static int wnb_logging_enabled(void) { + static volatile LONG cached = -1; + LONG v = cached; + if (v == -1) { + const char* e = getenv("WNB_LOG"); + v = (e && e[0] && e[0] != '0') ? 1 : 0; + cached = v; + } + return (int)v; +} + +static CRITICAL_SECTION g_resolver_cs; +static volatile LONG g_resolver_cs_state = 0; + +static void resolver_lock(void) { + if (InterlockedCompareExchange(&g_resolver_cs_state, 1, 0) == 0) { + InitializeCriticalSection(&g_resolver_cs); + InterlockedExchange(&g_resolver_cs_state, 2); + } + while (g_resolver_cs_state != 2) Sleep(0); + EnterCriticalSection(&g_resolver_cs); +} + +static void resolver_unlock(void) { + LeaveCriticalSection(&g_resolver_cs); +} + +static void wnb_log_once(const char* name) { + if (!wnb_logging_enabled()) return; + static const char* once_names[64]; + static int once_count = 0; + for (int i = 0; i < once_count; ++i) { + if (once_names[i] == name) return; + } + if (once_count < 64) { + once_names[once_count++] = name; + } + FILE* f = fopen("C:\\wnb.log", "a"); + if (f) { + fputs(name, f); + fputc('\n', f); + fclose(f); + } +} + +typedef void* (*CreateInterface_fn)(const char* pchVersion, int* pCode); +static CreateInterface_fn g_create_interface = NULL; +static void* g_steam_client = NULL; +static HMODULE g_steamclient_module = NULL; + +typedef unsigned char (*Steam_BGetCallback_fn)(int hpipe, void* pmsg); +typedef void (*Steam_FreeLastCallback_fn)(int hpipe); +typedef unsigned char (*Steam_GetAPICallResult_fn)(int hpipe, + unsigned long long hcall, + void* pcb, int cb, + int icb_expected, + unsigned char* pbfailed); +static Steam_BGetCallback_fn g_steam_bgetcallback = NULL; +static Steam_FreeLastCallback_fn g_steam_freelastcallback = NULL; +static Steam_GetAPICallResult_fn g_steam_getapicallresult = NULL; + +extern void wnb_dispatch_callback(int iCallback, const void* data, size_t data_size); +extern void wnb_dispatch_call_result(unsigned long long hAPICall, int io_failure, + const void* data, size_t data_size); +static int g_steam_pipe = 0; +static int g_steam_user = 0; + +extern void wnb_publish_dispatch_pointers(void); + +static void wnb_resolver_log(const char* msg) { + if (!wnb_logging_enabled()) return; + FILE* f = fopen("C:\\wnb.log", "a"); + if (f) { fputs(msg, f); fputc('\n', f); fclose(f); } +} + +static void resolve_steam_client_locked(void) { + wnb_publish_dispatch_pointers(); + SetDllDirectoryA("C:\\Program Files (x86)\\Steam"); + HMODULE sc = LoadLibraryExA( + "C:\\Program Files (x86)\\Steam\\steamclient64.dll", + NULL, LOAD_WITH_ALTERED_SEARCH_PATH); + if (sc == NULL) { + wnb_resolver_log("[wnb] LoadLibraryEx(Valve steamclient64.dll) " + "failed — falling back to bare name (gbe stub)"); + sc = LoadLibraryA("steamclient64.dll"); + } + if (sc == NULL) { + wnb_resolver_log("[wnb] LoadLibrary(steamclient64.dll) failed"); + return; + } + g_steamclient_module = sc; + if (g_create_interface == NULL) { + g_create_interface = (CreateInterface_fn)GetProcAddress(sc, "CreateInterface"); + if (g_create_interface == NULL) { + wnb_resolver_log("[wnb] steamclient64.dll missing CreateInterface"); + return; + } + } + g_steam_bgetcallback = (Steam_BGetCallback_fn) + GetProcAddress(sc, "Steam_BGetCallback"); + g_steam_freelastcallback = (Steam_FreeLastCallback_fn) + GetProcAddress(sc, "Steam_FreeLastCallback"); + g_steam_getapicallresult = (Steam_GetAPICallResult_fn) + GetProcAddress(sc, "Steam_GetAPICallResult"); + { + char buf[160]; + snprintf(buf, sizeof(buf), + "[wnb] callback-pump exports: BGetCallback=%p " + "FreeLastCallback=%p GetAPICallResult=%p", + (void*)g_steam_bgetcallback, + (void*)g_steam_freelastcallback, + (void*)g_steam_getapicallresult); + wnb_resolver_log(buf); + } + int code = 0; + g_steam_client = g_create_interface("SteamClient020", &code); + if (g_steam_client == NULL) g_steam_client = g_create_interface("SteamClient019", &code); + if (g_steam_client == NULL) g_steam_client = g_create_interface("SteamClient017", &code); + if (g_steam_client == NULL) { + wnb_resolver_log("[wnb] CreateInterface(SteamClient0XX) returned NULL"); + return; + } + + { + void** vt = *(void***)g_steam_client; + typedef int (*CreateSteamPipe_fn)(void*); + typedef int (*ConnectToGlobalUser_fn)(void*, int); + g_steam_pipe = ((CreateSteamPipe_fn)vt[0])(g_steam_client); + if (g_steam_pipe != 0) { + g_steam_user = ((ConnectToGlobalUser_fn)vt[2])( + g_steam_client, g_steam_pipe); + } + char buf[128]; + snprintf(buf, sizeof(buf), + "[wnb] Valve ISteamClient: pipe=%d user=%d", + g_steam_pipe, g_steam_user); + wnb_resolver_log(buf); + if (g_steam_pipe == 0 || g_steam_user == 0) { + wnb_resolver_log("[wnb] WARNING: pipe/user handshake failed; " + "falling back to 1,1 (matchmaking may be empty)"); + if (g_steam_pipe == 0) g_steam_pipe = 1; + if (g_steam_user == 0) g_steam_user = 1; + } + } +} + +static void* resolve_steam_client(void) { + if (g_steam_client != NULL) return g_steam_client; + resolver_lock(); + if (g_steam_client == NULL) resolve_steam_client_locked(); + resolver_unlock(); + return g_steam_client; +} + +static void* resolve_interface(int slot, const char* version) { + void* client = resolve_steam_client(); + if (client == NULL) return NULL; + void** vt = *(void***)client; + typedef void* (*GetIface_fn)(void*, int, int, const char*); + void* iface = ((GetIface_fn)vt[slot])( + client, g_steam_user, g_steam_pipe, version); + char buf[128]; + snprintf(buf, sizeof(buf), + "[wnb] resolve_interface slot=%d ver=%s pipe=%d user=%d -> %p", + slot, version, g_steam_pipe, g_steam_user, iface); + wnb_resolver_log(buf); + return iface; +} + +void wnb_pump_valve_callbacks(void) { + if (g_steam_client == NULL) return; /* resolver not run yet */ + if (g_steam_bgetcallback == NULL || g_steam_freelastcallback == NULL) return; + + struct CallbackMsg { int hUser; int iCallback; void* pubParam; int cubParam; }; + struct CallbackMsg msg; + int guard = 0; + while (guard++ < 512 && g_steam_bgetcallback(g_steam_pipe, &msg)) { + if (msg.iCallback == 703 /* SteamAPICallCompleted_t */) { + struct ApiCallDone { + unsigned long long hAsyncCall; + int iCallback; + unsigned cubParam; + }; + if (msg.pubParam != NULL && g_steam_getapicallresult != NULL) { + struct ApiCallDone cc = *(struct ApiCallDone*)msg.pubParam; + unsigned char payload[2048]; + int sz = (int)(cc.cubParam < sizeof(payload) + ? cc.cubParam : sizeof(payload)); + unsigned char failed = 0; + if (g_steam_getapicallresult(g_steam_pipe, cc.hAsyncCall, + payload, sz, cc.iCallback, + &failed)) { + char b[160]; + snprintf(b, sizeof(b), + "[wnb] pump: call-result hCall=%llu cb=%d " + "sz=%d failed=%d -> dispatch", + cc.hAsyncCall, cc.iCallback, sz, failed); + wnb_resolver_log(b); + wnb_dispatch_call_result(cc.hAsyncCall, failed, + payload, (size_t)sz); + } + } + } else { + char b[128]; + snprintf(b, sizeof(b), + "[wnb] pump: callback id=%d sz=%d -> dispatch", + msg.iCallback, msg.cubParam); + wnb_resolver_log(b); + wnb_dispatch_callback(msg.iCallback, msg.pubParam, + (size_t)msg.cubParam); + } + g_steam_freelastcallback(g_steam_pipe); + } +} + +static void* g_our_matchmaking = NULL; +static void* g_our_matchmaking_servers = NULL; + +void* get_our_matchmaking(void) { + if (g_our_matchmaking != NULL) return g_our_matchmaking; + resolver_lock(); + if (g_our_matchmaking == NULL) + g_our_matchmaking = resolve_interface(10, "SteamMatchMaking009"); + resolver_unlock(); + return g_our_matchmaking; +} + +void* get_our_matchmaking_servers(void) { + if (g_our_matchmaking_servers != NULL) return g_our_matchmaking_servers; + resolver_lock(); + if (g_our_matchmaking_servers == NULL) + g_our_matchmaking_servers = resolve_interface(11, "SteamMatchMakingServers002"); + resolver_unlock(); + return g_our_matchmaking_servers; +} + + +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMatchmaking_GetFavoriteGameCount(void* self) { + wnb_log_once("SteamAPI_ISteamMatchmaking_GetFavoriteGameCount"); + (void)self; + void* mm = get_our_matchmaking(); + if (mm == NULL) return 0; + void** vt = *(void***)mm; + typedef int (*Fn)(void*); + return ((Fn)vt[0])(mm); +} + +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMatchmaking_GetFavoriteGame(void* self, int _a0, void* _a1, void* _a2, void* _a3, void* _a4, void* _a5, void* _a6) { + wnb_log_once("SteamAPI_ISteamMatchmaking_GetFavoriteGame"); + (void)self; + void* mm = get_our_matchmaking(); + if (mm == NULL) return 0; + void** vt = *(void***)mm; + typedef int (*Fn)(void*, int, void*, void*, void*, void*, void*, void*); + return ((Fn)vt[1])(mm, _a0, _a1, _a2, _a3, _a4, _a5, _a6); +} + +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMatchmaking_AddFavoriteGame(void* self, uint32_t _a0, uint32_t _a1, uint16_t _a2, uint16_t _a3, uint32_t _a4, uint32_t _a5) { + wnb_log_once("SteamAPI_ISteamMatchmaking_AddFavoriteGame"); + (void)self; + void* mm = get_our_matchmaking(); + if (mm == NULL) return 0; + void** vt = *(void***)mm; + typedef int (*Fn)(void*, uint32_t, uint32_t, uint16_t, uint16_t, uint32_t, uint32_t); + return ((Fn)vt[2])(mm, _a0, _a1, _a2, _a3, _a4, _a5); +} + +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMatchmaking_RemoveFavoriteGame(void* self, uint32_t _a0, uint32_t _a1, uint16_t _a2, uint16_t _a3, uint32_t _a4) { + wnb_log_once("SteamAPI_ISteamMatchmaking_RemoveFavoriteGame"); + (void)self; + void* mm = get_our_matchmaking(); + if (mm == NULL) return 0; + void** vt = *(void***)mm; + typedef int (*Fn)(void*, uint32_t, uint32_t, uint16_t, uint16_t, uint32_t); + return ((Fn)vt[3])(mm, _a0, _a1, _a2, _a3, _a4); +} + +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamMatchmaking_RequestLobbyList(void* self) { + wnb_log_once("SteamAPI_ISteamMatchmaking_RequestLobbyList"); + (void)self; + void* mm = get_our_matchmaking(); + if (mm == NULL) return 0; + void** vt = *(void***)mm; + typedef uint64_t (*Fn)(void*); + return ((Fn)vt[4])(mm); +} + +WN_STEAMAPI_EXPORT void SteamAPI_ISteamMatchmaking_AddRequestLobbyListStringFilter(void* self, void* k, void* v, int cmp) { + wnb_log_once("SteamAPI_ISteamMatchmaking_AddRequestLobbyListStringFilter"); + (void)self; + void* mm = get_our_matchmaking(); + if (mm == NULL) return; + void** vt = *(void***)mm; + typedef void (*Fn)(void*, void*, void*, int); + ((Fn)vt[5])(mm, k, v, cmp); +} + +WN_STEAMAPI_EXPORT void SteamAPI_ISteamMatchmaking_AddRequestLobbyListNumericalFilter(void* self, void* k, int v, int cmp) { + wnb_log_once("SteamAPI_ISteamMatchmaking_AddRequestLobbyListNumericalFilter"); + (void)self; + void* mm = get_our_matchmaking(); + if (mm == NULL) return; + void** vt = *(void***)mm; + typedef void (*Fn)(void*, void*, int, int); + ((Fn)vt[6])(mm, k, v, cmp); +} + +WN_STEAMAPI_EXPORT void SteamAPI_ISteamMatchmaking_AddRequestLobbyListNearValueFilter(void* self, void* k, int v) { + wnb_log_once("SteamAPI_ISteamMatchmaking_AddRequestLobbyListNearValueFilter"); + (void)self; + void* mm = get_our_matchmaking(); + if (mm == NULL) return; + void** vt = *(void***)mm; + typedef void (*Fn)(void*, void*, int); + ((Fn)vt[7])(mm, k, v); +} + +WN_STEAMAPI_EXPORT void SteamAPI_ISteamMatchmaking_AddRequestLobbyListFilterSlotsAvailable(void* self, int slots) { + wnb_log_once("SteamAPI_ISteamMatchmaking_AddRequestLobbyListFilterSlotsAvailable"); + (void)self; + void* mm = get_our_matchmaking(); + if (mm == NULL) return; + void** vt = *(void***)mm; + typedef void (*Fn)(void*, int); + ((Fn)vt[8])(mm, slots); +} + +WN_STEAMAPI_EXPORT void SteamAPI_ISteamMatchmaking_AddRequestLobbyListDistanceFilter(void* self, int eDist) { + wnb_log_once("SteamAPI_ISteamMatchmaking_AddRequestLobbyListDistanceFilter"); + (void)self; + void* mm = get_our_matchmaking(); + if (mm == NULL) return; + void** vt = *(void***)mm; + typedef void (*Fn)(void*, int); + ((Fn)vt[9])(mm, eDist); +} + +WN_STEAMAPI_EXPORT void SteamAPI_ISteamMatchmaking_AddRequestLobbyListResultCountFilter(void* self, int n) { + wnb_log_once("SteamAPI_ISteamMatchmaking_AddRequestLobbyListResultCountFilter"); + (void)self; + void* mm = get_our_matchmaking(); + if (mm == NULL) return; + void** vt = *(void***)mm; + typedef void (*Fn)(void*, int); + ((Fn)vt[10])(mm, n); +} + +WN_STEAMAPI_EXPORT void SteamAPI_ISteamMatchmaking_AddRequestLobbyListCompatibleMembersFilter(void* self, void* _a0) { + wnb_log_once("SteamAPI_ISteamMatchmaking_AddRequestLobbyListCompatibleMembersFilter"); + (void)self; + void* mm = get_our_matchmaking(); + if (mm == NULL) return; + void** vt = *(void***)mm; + typedef void (*Fn)(void*, void*); + ((Fn)vt[11])(mm, _a0); +} + +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamMatchmaking_GetLobbyByIndex(void* self, int idx) { + wnb_log_once("SteamAPI_ISteamMatchmaking_GetLobbyByIndex"); + (void)self; + void* mm = get_our_matchmaking(); + if (mm == NULL) return 0; + void** vt = *(void***)mm; + typedef uint64_t (*Fn)(void*, int); + return ((Fn)vt[12])(mm, idx); +} + +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamMatchmaking_CreateLobby(void* self, int eLobbyType, int maxMembers) { + wnb_log_once("SteamAPI_ISteamMatchmaking_CreateLobby"); + (void)self; + void* mm = get_our_matchmaking(); + if (mm == NULL) return 0; + void** vt = *(void***)mm; + typedef uint64_t (*Fn)(void*, int, int); + return ((Fn)vt[13])(mm, eLobbyType, maxMembers); +} + +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamMatchmaking_JoinLobby(void* self, uint64_t lobbySid) { + wnb_log_once("SteamAPI_ISteamMatchmaking_JoinLobby"); + (void)self; + void* mm = get_our_matchmaking(); + if (mm == NULL) return 0; + void** vt = *(void***)mm; + typedef uint64_t (*Fn)(void*, uint64_t); + return ((Fn)vt[14])(mm, lobbySid); +} + +WN_STEAMAPI_EXPORT void SteamAPI_ISteamMatchmaking_LeaveLobby(void* self, uint64_t sid) { + wnb_log_once("SteamAPI_ISteamMatchmaking_LeaveLobby"); + (void)self; + void* mm = get_our_matchmaking(); + if (mm == NULL) return; + void** vt = *(void***)mm; + typedef void (*Fn)(void*, uint64_t); + ((Fn)vt[15])(mm, sid); +} + +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMatchmaking_InviteUserToLobby(void* self, uint64_t sid, uint64_t invitee) { + wnb_log_once("SteamAPI_ISteamMatchmaking_InviteUserToLobby"); + (void)self; + void* mm = get_our_matchmaking(); + if (mm == NULL) return 0; + void** vt = *(void***)mm; + typedef int (*Fn)(void*, uint64_t, uint64_t); + return ((Fn)vt[16])(mm, sid, invitee); +} + +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMatchmaking_GetNumLobbyMembers(void* self, uint64_t sid) { + wnb_log_once("SteamAPI_ISteamMatchmaking_GetNumLobbyMembers"); + (void)self; + void* mm = get_our_matchmaking(); + if (mm == NULL) return 0; + void** vt = *(void***)mm; + typedef int (*Fn)(void*, uint64_t); + return ((Fn)vt[17])(mm, sid); +} + +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamMatchmaking_GetLobbyMemberByIndex(void* self, uint64_t sid, int idx) { + wnb_log_once("SteamAPI_ISteamMatchmaking_GetLobbyMemberByIndex"); + (void)self; + void* mm = get_our_matchmaking(); + if (mm == NULL) return 0; + void** vt = *(void***)mm; + typedef uint64_t (*Fn)(void*, uint64_t, int); + return ((Fn)vt[18])(mm, sid, idx); +} + +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamMatchmaking_GetLobbyData(void* self, uint64_t sid, void* key) { + wnb_log_once("SteamAPI_ISteamMatchmaking_GetLobbyData"); + (void)self; + void* mm = get_our_matchmaking(); + if (mm == NULL) return NULL; + void** vt = *(void***)mm; + typedef void* (*Fn)(void*, uint64_t, void*); + return ((Fn)vt[19])(mm, sid, key); +} + +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMatchmaking_SetLobbyData(void* self, uint64_t sid, void* key, void* val) { + wnb_log_once("SteamAPI_ISteamMatchmaking_SetLobbyData"); + (void)self; + void* mm = get_our_matchmaking(); + if (mm == NULL) return 0; + void** vt = *(void***)mm; + typedef int (*Fn)(void*, uint64_t, void*, void*); + return ((Fn)vt[20])(mm, sid, key, val); +} + +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMatchmaking_GetLobbyDataCount(void* self, uint64_t sid) { + wnb_log_once("SteamAPI_ISteamMatchmaking_GetLobbyDataCount"); + (void)self; + void* mm = get_our_matchmaking(); + if (mm == NULL) return 0; + void** vt = *(void***)mm; + typedef int (*Fn)(void*, uint64_t); + return ((Fn)vt[21])(mm, sid); +} + +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMatchmaking_GetLobbyDataByIndex(void* self, uint64_t sid, int idx, void* key, int kn, void* val, int vn) { + wnb_log_once("SteamAPI_ISteamMatchmaking_GetLobbyDataByIndex"); + (void)self; + void* mm = get_our_matchmaking(); + if (mm == NULL) return 0; + void** vt = *(void***)mm; + typedef int (*Fn)(void*, uint64_t, int, void*, int, void*, int); + return ((Fn)vt[22])(mm, sid, idx, key, kn, val, vn); +} + +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMatchmaking_DeleteLobbyData(void* self, uint64_t sid, void* key) { + wnb_log_once("SteamAPI_ISteamMatchmaking_DeleteLobbyData"); + (void)self; + void* mm = get_our_matchmaking(); + if (mm == NULL) return 0; + void** vt = *(void***)mm; + typedef int (*Fn)(void*, uint64_t, void*); + return ((Fn)vt[23])(mm, sid, key); +} + +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamMatchmaking_GetLobbyMemberData(void* self, uint64_t sid, uint64_t member, void* key) { + wnb_log_once("SteamAPI_ISteamMatchmaking_GetLobbyMemberData"); + (void)self; + void* mm = get_our_matchmaking(); + if (mm == NULL) return NULL; + void** vt = *(void***)mm; + typedef void* (*Fn)(void*, uint64_t, uint64_t, void*); + return ((Fn)vt[24])(mm, sid, member, key); +} + +WN_STEAMAPI_EXPORT void SteamAPI_ISteamMatchmaking_SetLobbyMemberData(void* self, uint64_t sid, void* key, void* val) { + wnb_log_once("SteamAPI_ISteamMatchmaking_SetLobbyMemberData"); + (void)self; + void* mm = get_our_matchmaking(); + if (mm == NULL) return; + void** vt = *(void***)mm; + typedef void (*Fn)(void*, uint64_t, void*, void*); + ((Fn)vt[25])(mm, sid, key, val); +} + +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMatchmaking_SendLobbyChatMsg(void* self, uint64_t sid, void* body, int n) { + wnb_log_once("SteamAPI_ISteamMatchmaking_SendLobbyChatMsg"); + (void)self; + void* mm = get_our_matchmaking(); + if (mm == NULL) return 0; + void** vt = *(void***)mm; + typedef int (*Fn)(void*, uint64_t, void*, int); + return ((Fn)vt[26])(mm, sid, body, n); +} + +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMatchmaking_GetLobbyChatEntry(void* self, uint64_t sid, int idx, void* speaker_out, void* body_out, int body_cap, void* chat_type_out) { + wnb_log_once("SteamAPI_ISteamMatchmaking_GetLobbyChatEntry"); + (void)self; + void* mm = get_our_matchmaking(); + if (mm == NULL) return 0; + void** vt = *(void***)mm; + typedef int (*Fn)(void*, uint64_t, int, void*, void*, int, void*); + return ((Fn)vt[27])(mm, sid, idx, speaker_out, body_out, body_cap, chat_type_out); +} + +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMatchmaking_RequestLobbyData(void* self, uint64_t sid) { + wnb_log_once("SteamAPI_ISteamMatchmaking_RequestLobbyData"); + (void)self; + void* mm = get_our_matchmaking(); + if (mm == NULL) return 0; + void** vt = *(void***)mm; + typedef int (*Fn)(void*, uint64_t); + return ((Fn)vt[28])(mm, sid); +} + +WN_STEAMAPI_EXPORT void SteamAPI_ISteamMatchmaking_SetLobbyGameServer(void* self, uint64_t sid, uint32_t ip, uint16_t port, uint64_t gs) { + wnb_log_once("SteamAPI_ISteamMatchmaking_SetLobbyGameServer"); + (void)self; + void* mm = get_our_matchmaking(); + if (mm == NULL) return; + void** vt = *(void***)mm; + typedef void (*Fn)(void*, uint64_t, uint32_t, uint16_t, uint64_t); + ((Fn)vt[29])(mm, sid, ip, port, gs); +} + +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMatchmaking_GetLobbyGameServer(void* self, uint64_t sid, void* ip, void* port, void* sid_out) { + wnb_log_once("SteamAPI_ISteamMatchmaking_GetLobbyGameServer"); + (void)self; + void* mm = get_our_matchmaking(); + if (mm == NULL) return 0; + void** vt = *(void***)mm; + typedef int (*Fn)(void*, uint64_t, void*, void*, void*); + return ((Fn)vt[30])(mm, sid, ip, port, sid_out); +} + +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMatchmaking_SetLobbyMemberLimit(void* self, uint64_t sid, int max_members) { + wnb_log_once("SteamAPI_ISteamMatchmaking_SetLobbyMemberLimit"); + (void)self; + void* mm = get_our_matchmaking(); + if (mm == NULL) return 0; + void** vt = *(void***)mm; + typedef int (*Fn)(void*, uint64_t, int); + return ((Fn)vt[31])(mm, sid, max_members); +} + +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMatchmaking_GetLobbyMemberLimit(void* self, uint64_t sid) { + wnb_log_once("SteamAPI_ISteamMatchmaking_GetLobbyMemberLimit"); + (void)self; + void* mm = get_our_matchmaking(); + if (mm == NULL) return 0; + void** vt = *(void***)mm; + typedef int (*Fn)(void*, uint64_t); + return ((Fn)vt[32])(mm, sid); +} + +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMatchmaking_SetLobbyType(void* self, uint64_t sid, int eLobbyType) { + wnb_log_once("SteamAPI_ISteamMatchmaking_SetLobbyType"); + (void)self; + void* mm = get_our_matchmaking(); + if (mm == NULL) return 0; + void** vt = *(void***)mm; + typedef int (*Fn)(void*, uint64_t, int); + return ((Fn)vt[33])(mm, sid, eLobbyType); +} + +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMatchmaking_SetLobbyJoinable(void* self, uint64_t sid, int joinable) { + wnb_log_once("SteamAPI_ISteamMatchmaking_SetLobbyJoinable"); + (void)self; + void* mm = get_our_matchmaking(); + if (mm == NULL) return 0; + void** vt = *(void***)mm; + typedef int (*Fn)(void*, uint64_t, int); + return ((Fn)vt[34])(mm, sid, joinable); +} + +WN_STEAMAPI_EXPORT uint64_t SteamAPI_ISteamMatchmaking_GetLobbyOwner(void* self, uint64_t sid) { + wnb_log_once("SteamAPI_ISteamMatchmaking_GetLobbyOwner"); + (void)self; + void* mm = get_our_matchmaking(); + if (mm == NULL) return 0; + void** vt = *(void***)mm; + typedef uint64_t (*Fn)(void*, uint64_t); + return ((Fn)vt[35])(mm, sid); +} + +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMatchmaking_SetLobbyOwner(void* self, uint64_t sid, uint64_t new_owner) { + wnb_log_once("SteamAPI_ISteamMatchmaking_SetLobbyOwner"); + (void)self; + void* mm = get_our_matchmaking(); + if (mm == NULL) return 0; + void** vt = *(void***)mm; + typedef int (*Fn)(void*, uint64_t, uint64_t); + return ((Fn)vt[36])(mm, sid, new_owner); +} + +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMatchmaking_SetLinkedLobby(void* self, uint64_t _a0, uint64_t _a1) { + wnb_log_once("SteamAPI_ISteamMatchmaking_SetLinkedLobby"); + (void)self; + void* mm = get_our_matchmaking(); + if (mm == NULL) return 0; + void** vt = *(void***)mm; + typedef int (*Fn)(void*, uint64_t, uint64_t); + return ((Fn)vt[37])(mm, _a0, _a1); +} + +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamMatchmakingServers_RequestInternetServerList(void* self, uint32_t app, void* _a1, uint32_t n, void* _a3) { + wnb_log_once("SteamAPI_ISteamMatchmakingServers_RequestInternetServerList"); + (void)self; + void* mm = get_our_matchmaking_servers(); + if (mm == NULL) return NULL; + void** vt = *(void***)mm; + typedef void* (*Fn)(void*, uint32_t, void*, uint32_t, void*); + return ((Fn)vt[0])(mm, app, _a1, n, _a3); +} + +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamMatchmakingServers_RequestLANServerList(void* self, uint32_t app, void* _a1) { + wnb_log_once("SteamAPI_ISteamMatchmakingServers_RequestLANServerList"); + (void)self; + void* mm = get_our_matchmaking_servers(); + if (mm == NULL) return NULL; + void** vt = *(void***)mm; + typedef void* (*Fn)(void*, uint32_t, void*); + return ((Fn)vt[1])(mm, app, _a1); +} + +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamMatchmakingServers_RequestFriendsServerList(void* self, uint32_t app, void* _a1, uint32_t _a2, void* _a3) { + wnb_log_once("SteamAPI_ISteamMatchmakingServers_RequestFriendsServerList"); + (void)self; + void* mm = get_our_matchmaking_servers(); + if (mm == NULL) return NULL; + void** vt = *(void***)mm; + typedef void* (*Fn)(void*, uint32_t, void*, uint32_t, void*); + return ((Fn)vt[2])(mm, app, _a1, _a2, _a3); +} + +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamMatchmakingServers_RequestFavoritesServerList(void* self, uint32_t app, void* _a1, uint32_t _a2, void* _a3) { + wnb_log_once("SteamAPI_ISteamMatchmakingServers_RequestFavoritesServerList"); + (void)self; + void* mm = get_our_matchmaking_servers(); + if (mm == NULL) return NULL; + void** vt = *(void***)mm; + typedef void* (*Fn)(void*, uint32_t, void*, uint32_t, void*); + return ((Fn)vt[3])(mm, app, _a1, _a2, _a3); +} + +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamMatchmakingServers_RequestHistoryServerList(void* self, uint32_t app, void* _a1, uint32_t _a2, void* _a3) { + wnb_log_once("SteamAPI_ISteamMatchmakingServers_RequestHistoryServerList"); + (void)self; + void* mm = get_our_matchmaking_servers(); + if (mm == NULL) return NULL; + void** vt = *(void***)mm; + typedef void* (*Fn)(void*, uint32_t, void*, uint32_t, void*); + return ((Fn)vt[4])(mm, app, _a1, _a2, _a3); +} + +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamMatchmakingServers_RequestSpectatorServerList(void* self, uint32_t app, void* _a1, uint32_t _a2, void* _a3) { + wnb_log_once("SteamAPI_ISteamMatchmakingServers_RequestSpectatorServerList"); + (void)self; + void* mm = get_our_matchmaking_servers(); + if (mm == NULL) return NULL; + void** vt = *(void***)mm; + typedef void* (*Fn)(void*, uint32_t, void*, uint32_t, void*); + return ((Fn)vt[5])(mm, app, _a1, _a2, _a3); +} + +WN_STEAMAPI_EXPORT void SteamAPI_ISteamMatchmakingServers_ReleaseRequest(void* self, void* _a0) { + wnb_log_once("SteamAPI_ISteamMatchmakingServers_ReleaseRequest"); + (void)self; + void* mm = get_our_matchmaking_servers(); + if (mm == NULL) return; + void** vt = *(void***)mm; + typedef void (*Fn)(void*, void*); + ((Fn)vt[6])(mm, _a0); +} + +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamMatchmakingServers_GetServerDetails(void* self, void* _a0, int _a1) { + wnb_log_once("SteamAPI_ISteamMatchmakingServers_GetServerDetails"); + (void)self; + void* mm = get_our_matchmaking_servers(); + if (mm == NULL) return NULL; + void** vt = *(void***)mm; + typedef void* (*Fn)(void*, void*, int); + return ((Fn)vt[7])(mm, _a0, _a1); +} + +WN_STEAMAPI_EXPORT void SteamAPI_ISteamMatchmakingServers_CancelQuery(void* self, void* _a0) { + wnb_log_once("SteamAPI_ISteamMatchmakingServers_CancelQuery"); + (void)self; + void* mm = get_our_matchmaking_servers(); + if (mm == NULL) return; + void** vt = *(void***)mm; + typedef void (*Fn)(void*, void*); + ((Fn)vt[8])(mm, _a0); +} + +WN_STEAMAPI_EXPORT void SteamAPI_ISteamMatchmakingServers_RefreshQuery(void* self, void* _a0) { + wnb_log_once("SteamAPI_ISteamMatchmakingServers_RefreshQuery"); + (void)self; + void* mm = get_our_matchmaking_servers(); + if (mm == NULL) return; + void** vt = *(void***)mm; + typedef void (*Fn)(void*, void*); + ((Fn)vt[9])(mm, _a0); +} + +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMatchmakingServers_IsRefreshing(void* self, void* _a0) { + wnb_log_once("SteamAPI_ISteamMatchmakingServers_IsRefreshing"); + (void)self; + void* mm = get_our_matchmaking_servers(); + if (mm == NULL) return 0; + void** vt = *(void***)mm; + typedef int (*Fn)(void*, void*); + return ((Fn)vt[10])(mm, _a0); +} + +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMatchmakingServers_GetServerCount(void* self, void* _a0) { + wnb_log_once("SteamAPI_ISteamMatchmakingServers_GetServerCount"); + (void)self; + void* mm = get_our_matchmaking_servers(); + if (mm == NULL) return 0; + void** vt = *(void***)mm; + typedef int (*Fn)(void*, void*); + return ((Fn)vt[11])(mm, _a0); +} + +WN_STEAMAPI_EXPORT void SteamAPI_ISteamMatchmakingServers_RefreshServer(void* self, void* _a0, int _a1) { + wnb_log_once("SteamAPI_ISteamMatchmakingServers_RefreshServer"); + (void)self; + void* mm = get_our_matchmaking_servers(); + if (mm == NULL) return; + void** vt = *(void***)mm; + typedef void (*Fn)(void*, void*, int); + ((Fn)vt[12])(mm, _a0, _a1); +} + +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMatchmakingServers_PingServer(void* self, uint32_t _a0, uint16_t _a1, void* _a2) { + wnb_log_once("SteamAPI_ISteamMatchmakingServers_PingServer"); + (void)self; + void* mm = get_our_matchmaking_servers(); + if (mm == NULL) return 0; + void** vt = *(void***)mm; + typedef int (*Fn)(void*, uint32_t, uint16_t, void*); + return ((Fn)vt[13])(mm, _a0, _a1, _a2); +} + +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMatchmakingServers_PlayerDetails(void* self, uint32_t _a0, uint16_t _a1, void* _a2) { + wnb_log_once("SteamAPI_ISteamMatchmakingServers_PlayerDetails"); + (void)self; + void* mm = get_our_matchmaking_servers(); + if (mm == NULL) return 0; + void** vt = *(void***)mm; + typedef int (*Fn)(void*, uint32_t, uint16_t, void*); + return ((Fn)vt[14])(mm, _a0, _a1, _a2); +} + +WN_STEAMAPI_EXPORT int SteamAPI_ISteamMatchmakingServers_ServerRules(void* self, uint32_t _a0, uint16_t _a1, void* _a2) { + wnb_log_once("SteamAPI_ISteamMatchmakingServers_ServerRules"); + (void)self; + void* mm = get_our_matchmaking_servers(); + if (mm == NULL) return 0; + void** vt = *(void***)mm; + typedef int (*Fn)(void*, uint32_t, uint16_t, void*); + return ((Fn)vt[15])(mm, _a0, _a1, _a2); +} + +WN_STEAMAPI_EXPORT void SteamAPI_ISteamMatchmakingServers_CancelServerQuery(void* self, int _a0) { + wnb_log_once("SteamAPI_ISteamMatchmakingServers_CancelServerQuery"); + (void)self; + void* mm = get_our_matchmaking_servers(); + if (mm == NULL) return; + void** vt = *(void***)mm; + typedef void (*Fn)(void*, int); + ((Fn)vt[16])(mm, _a0); +} diff --git a/app/src/main/cpp/wn-steamapi-bridge/steam_api_bridge_steamclient.c b/app/src/main/cpp/wn-steamapi-bridge/steam_api_bridge_steamclient.c new file mode 100644 index 000000000..3b5c72675 --- /dev/null +++ b/app/src/main/cpp/wn-steamapi-bridge/steam_api_bridge_steamclient.c @@ -0,0 +1,69 @@ + +#include +#include +#include +#include + +#define WN_STEAMAPI_EXPORT __declspec(dllexport) + +extern void* get_our_matchmaking(void); +extern void* get_our_matchmaking_servers(void); + +static int g_logged_matchmaking = 0; +static int g_logged_matchmaking_servers = 0; +static void wnb_marker(const char* msg) { + FILE* f = fopen("C:\\wnb.log", "a"); + if (f) { fputs(msg, f); fputs("\n", f); fclose(f); } +} + +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamClient_GetISteamMatchmaking( + void* instancePtr, + int hSteamUser, + int hSteamPipe, + const char* pchVersion) { + (void)instancePtr; (void)hSteamUser; (void)hSteamPipe; (void)pchVersion; + if (!g_logged_matchmaking) { + wnb_marker("SteamAPI_ISteamClient_GetISteamMatchmaking: flat-C hook -> libsteamclient.so"); + g_logged_matchmaking = 1; + } + return get_our_matchmaking(); +} + +WN_STEAMAPI_EXPORT void* SteamAPI_ISteamClient_GetISteamMatchmakingServers( + void* instancePtr, + int hSteamUser, + int hSteamPipe, + const char* pchVersion) { + (void)instancePtr; (void)hSteamUser; (void)hSteamPipe; (void)pchVersion; + if (!g_logged_matchmaking_servers) { + wnb_marker("SteamAPI_ISteamClient_GetISteamMatchmakingServers: flat-C hook -> libsteamclient.so"); + g_logged_matchmaking_servers = 1; + } + return get_our_matchmaking_servers(); +} + +WN_STEAMAPI_EXPORT void* SteamMatchmaking(void) { + static int logged = 0; + if (!logged) { + wnb_marker("SteamMatchmaking(): bare global -> Steam Launcher Valve client"); + logged = 1; + } + return get_our_matchmaking(); +} + +WN_STEAMAPI_EXPORT void* SteamMatchmakingServers(void) { + static int logged = 0; + if (!logged) { + wnb_marker("SteamMatchmakingServers(): bare global -> Steam Launcher Valve client"); + logged = 1; + } + return get_our_matchmaking_servers(); +} + +WN_STEAMAPI_EXPORT void* SteamAPI_SteamMatchmaking_v009(void) { + return get_our_matchmaking(); +} + +WN_STEAMAPI_EXPORT void* SteamAPI_SteamMatchmakingServers_v002(void) { + return get_our_matchmaking_servers(); +} diff --git a/app/src/main/cpp/xvfb-setxid-shim/build.sh b/app/src/main/cpp/xvfb-setxid-shim/build.sh new file mode 100644 index 000000000..329d9ad42 --- /dev/null +++ b/app/src/main/cpp/xvfb-setxid-shim/build.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")/../../rust/wn-steam-tools" +exec ./build-linux-glibc.sh diff --git a/app/src/main/cpp/xz/native_xz_stream.c b/app/src/main/cpp/xz/native_xz_stream.c deleted file mode 100644 index 18ae7b29e..000000000 --- a/app/src/main/cpp/xz/native_xz_stream.c +++ /dev/null @@ -1,230 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -#include "xz.h" - -#define XZ_INPUT_BUFFER_SIZE (64 * 1024) -#define XZ_DICT_SIZE_MAX (128U << 20) - -typedef struct native_xz_stream { - FILE *file; - struct xz_dec *decoder; - struct xz_buf buffer; - uint8_t input_buffer[XZ_INPUT_BUFFER_SIZE]; - bool input_finished; - bool stream_finished; -} native_xz_stream_t; - -static pthread_once_t xz_crc_once = PTHREAD_ONCE_INIT; - -static void native_xz_init_crc(void) { - xz_crc32_init(); - xz_crc64_init(); -} - -static void throw_io_exception(JNIEnv *env, const char *message) { - jclass io_exception_class = (*env)->FindClass(env, "java/io/IOException"); - if (io_exception_class != NULL) { - (*env)->ThrowNew(env, io_exception_class, message); - } -} - -static const char *to_xz_error(enum xz_ret ret) { - switch (ret) { - case XZ_STREAM_END: - return "Unexpected XZ end-of-stream state"; - case XZ_MEM_ERROR: - return "Native XZ decoder ran out of memory"; - case XZ_MEMLIMIT_ERROR: - return "XZ dictionary exceeds native decoder limit"; - case XZ_FORMAT_ERROR: - return "Not an XZ stream"; - case XZ_OPTIONS_ERROR: - return "Unsupported XZ options"; - case XZ_DATA_ERROR: - return "Corrupt XZ stream"; - case XZ_BUF_ERROR: - return "Truncated or stalled XZ stream"; - case XZ_UNSUPPORTED_CHECK: - return "Unsupported XZ integrity check"; - case XZ_OK: - default: - return "Unknown native XZ error"; - } -} - -static void close_native_xz_stream(native_xz_stream_t *stream) { - if (stream == NULL) - return; - - if (stream->decoder != NULL) { - xz_dec_end(stream->decoder); - stream->decoder = NULL; - } - - if (stream->file != NULL) { - fclose(stream->file); - stream->file = NULL; - } - - free(stream); -} - -JNIEXPORT jlong JNICALL -Java_com_winlator_cmod_shared_io_NativeXzInputStream_nativeOpen(JNIEnv *env, - jclass clazz, - jstring path) { - (void)clazz; - - if (path == NULL) { - throw_io_exception(env, "Missing XZ source path"); - return 0; - } - - if (pthread_once(&xz_crc_once, native_xz_init_crc) != 0) { - throw_io_exception(env, "Failed to initialize native XZ CRC tables"); - return 0; - } - - const char *path_chars = (*env)->GetStringUTFChars(env, path, NULL); - if (path_chars == NULL) { - return 0; - } - - native_xz_stream_t *stream = calloc(1, sizeof(native_xz_stream_t)); - if (stream == NULL) { - (*env)->ReleaseStringUTFChars(env, path, path_chars); - throw_io_exception(env, "Failed to allocate native XZ stream"); - return 0; - } - - stream->file = fopen(path_chars, "rb"); - (*env)->ReleaseStringUTFChars(env, path, path_chars); - if (stream->file == NULL) { - char error_message[160]; - snprintf(error_message, sizeof(error_message), - "Failed to open XZ source: %s", strerror(errno)); - close_native_xz_stream(stream); - throw_io_exception(env, error_message); - return 0; - } - - stream->decoder = xz_dec_init(XZ_DYNALLOC, XZ_DICT_SIZE_MAX); - if (stream->decoder == NULL) { - close_native_xz_stream(stream); - throw_io_exception(env, "Failed to initialize native XZ decoder"); - return 0; - } - - stream->buffer.in = stream->input_buffer; - stream->buffer.in_pos = 0; - stream->buffer.in_size = 0; - stream->buffer.out = NULL; - stream->buffer.out_pos = 0; - stream->buffer.out_size = 0; - return (jlong)(intptr_t)stream; -} - -JNIEXPORT jint JNICALL -Java_com_winlator_cmod_shared_io_NativeXzInputStream_nativeRead( - JNIEnv *env, jclass clazz, jlong handle, jbyteArray output, jint offset, - jint length) { - (void)clazz; - - if (length == 0) { - return 0; - } - - native_xz_stream_t *stream = (native_xz_stream_t *)(intptr_t)handle; - if (stream == NULL || stream->decoder == NULL) { - throw_io_exception(env, "Native XZ stream is closed"); - return -1; - } - - jbyte *output_bytes = (*env)->GetPrimitiveArrayCritical(env, output, NULL); - if (output_bytes == NULL) { - return -1; - } - - stream->buffer.out = (uint8_t *)(output_bytes + offset); - stream->buffer.out_pos = 0; - stream->buffer.out_size = (size_t)length; - - const char *error_msg = NULL; - - while (stream->buffer.out_pos < stream->buffer.out_size) { - if (stream->stream_finished) { - break; - } - - if (stream->buffer.in_pos == stream->buffer.in_size && - !stream->input_finished) { - size_t amount_read = fread(stream->input_buffer, 1, - sizeof(stream->input_buffer), stream->file); - if (amount_read == 0) { - if (ferror(stream->file)) { - error_msg = "Failed reading XZ source"; - break; - } - stream->input_finished = true; - } - - stream->buffer.in = stream->input_buffer; - stream->buffer.in_pos = 0; - stream->buffer.in_size = amount_read; - } - - size_t input_before = stream->buffer.in_pos; - size_t output_before = stream->buffer.out_pos; - enum xz_ret ret = xz_dec_catrun(stream->decoder, &stream->buffer, - stream->input_finished ? 1 : 0); - - if (ret == XZ_OK) { - if (stream->buffer.out_pos == stream->buffer.out_size) { - break; - } - - if (stream->buffer.in_pos == input_before && - stream->buffer.out_pos == output_before) { - error_msg = "Native XZ decoder stalled"; - break; - } - continue; - } - - if (ret == XZ_STREAM_END) { - stream->stream_finished = true; - break; - } - - error_msg = to_xz_error(ret); - break; - } - - int amount_decoded = (int)stream->buffer.out_pos; - (*env)->ReleasePrimitiveArrayCritical(env, output, output_bytes, 0); - - if (error_msg != NULL) { - throw_io_exception(env, error_msg); - return -1; - } - if (amount_decoded == 0 && stream->stream_finished) { - return -1; - } - return amount_decoded; -} - -JNIEXPORT void JNICALL -Java_com_winlator_cmod_shared_io_NativeXzInputStream_nativeClose(JNIEnv *env, - jclass clazz, - jlong handle) { - (void)env; - (void)clazz; - close_native_xz_stream((native_xz_stream_t *)(intptr_t)handle); -} diff --git a/app/src/main/cpp/xz/xz_embedded/COPYING b/app/src/main/cpp/xz/xz_embedded/COPYING deleted file mode 100644 index f0c316128..000000000 --- a/app/src/main/cpp/xz/xz_embedded/COPYING +++ /dev/null @@ -1,13 +0,0 @@ -Copyright (C) The XZ Embedded authors and contributors - -Permission to use, copy, modify, and/or distribute this -software for any purpose with or without fee is hereby granted. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL -WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL -THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR -CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, -NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN -CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/app/src/main/cpp/xz/xz_embedded/xz.h b/app/src/main/cpp/xz/xz_embedded/xz.h deleted file mode 100644 index 6a63172cb..000000000 --- a/app/src/main/cpp/xz/xz_embedded/xz.h +++ /dev/null @@ -1,444 +0,0 @@ -/* SPDX-License-Identifier: 0BSD */ - -/* - * XZ decompressor - * - * Authors: Lasse Collin - * Igor Pavlov - */ - -#ifndef XZ_H -#define XZ_H - -#ifdef __KERNEL__ -#include -#include -#else -#include -#include -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -/* "#define XZ_EXTERN static" can be used to make extern functions static. */ -#ifndef XZ_EXTERN -#define XZ_EXTERN extern -#endif - -/** - * enum xz_mode - Operation mode - * - * @XZ_SINGLE: Single-call mode. This uses less RAM than - * multi-call modes, because the LZMA2 - * dictionary doesn't need to be allocated as - * part of the decoder state. All required data - * structures are allocated at initialization, - * so xz_dec_run() cannot return XZ_MEM_ERROR. - * @XZ_PREALLOC: Multi-call mode with preallocated LZMA2 - * dictionary buffer. All data structures are - * allocated at initialization, so xz_dec_run() - * cannot return XZ_MEM_ERROR. - * @XZ_DYNALLOC: Multi-call mode. The LZMA2 dictionary is - * allocated once the required size has been - * parsed from the stream headers. If the - * allocation fails, xz_dec_run() will return - * XZ_MEM_ERROR. - * - * It is possible to enable support only for a subset of the above - * modes at compile time by defining XZ_DEC_SINGLE, XZ_DEC_PREALLOC, - * or XZ_DEC_DYNALLOC. The xz_dec kernel module is always compiled - * with support for all operation modes, but the preboot code may - * be built with fewer features to minimize code size. - */ -enum xz_mode { XZ_SINGLE, XZ_PREALLOC, XZ_DYNALLOC }; - -/** - * enum xz_ret - Return codes - * @XZ_OK: Everything is OK so far. More input or more - * output space is required to continue. This - * return code is possible only in multi-call mode - * (XZ_PREALLOC or XZ_DYNALLOC). - * @XZ_STREAM_END: Operation finished successfully. - * @XZ_UNSUPPORTED_CHECK: Integrity check type is not supported. Decoding - * is still possible in multi-call mode by simply - * calling xz_dec_run() again. - * Note that this return value is used only if - * XZ_DEC_ANY_CHECK was defined at build time, - * which is not used in the kernel. Unsupported - * check types return XZ_OPTIONS_ERROR if - * XZ_DEC_ANY_CHECK was not defined at build time. - * @XZ_MEM_ERROR: Allocating memory failed. This return code is - * possible only if the decoder was initialized - * with XZ_DYNALLOC. The amount of memory that was - * tried to be allocated was no more than the - * dict_max argument given to xz_dec_init(). - * @XZ_MEMLIMIT_ERROR: A bigger LZMA2 dictionary would be needed than - * allowed by the dict_max argument given to - * xz_dec_init(). This return value is possible - * only in multi-call mode (XZ_PREALLOC or - * XZ_DYNALLOC); the single-call mode (XZ_SINGLE) - * ignores the dict_max argument. - * @XZ_FORMAT_ERROR: File format was not recognized (wrong magic - * bytes). - * @XZ_OPTIONS_ERROR: This implementation doesn't support the requested - * compression options. In the decoder this means - * that the header CRC32 matches, but the header - * itself specifies something that we don't support. - * @XZ_DATA_ERROR: Compressed data is corrupt. - * @XZ_BUF_ERROR: Cannot make any progress. Details are slightly - * different between multi-call and single-call - * mode; more information below. - * - * In multi-call mode, XZ_BUF_ERROR is returned when two consecutive calls - * to XZ code cannot consume any input and cannot produce any new output. - * This happens when there is no new input available, or the output buffer - * is full while at least one output byte is still pending. Assuming your - * code is not buggy, you can get this error only when decoding a compressed - * stream that is truncated or otherwise corrupt. - * - * In single-call mode, XZ_BUF_ERROR is returned only when the output buffer - * is too small or the compressed input is corrupt in a way that makes the - * decoder produce more output than the caller expected. When it is - * (relatively) clear that the compressed input is truncated, XZ_DATA_ERROR - * is used instead of XZ_BUF_ERROR. - */ -enum xz_ret { - XZ_OK, - XZ_STREAM_END, - XZ_UNSUPPORTED_CHECK, - XZ_MEM_ERROR, - XZ_MEMLIMIT_ERROR, - XZ_FORMAT_ERROR, - XZ_OPTIONS_ERROR, - XZ_DATA_ERROR, - XZ_BUF_ERROR -}; - -/** - * struct xz_buf - Passing input and output buffers to XZ code - * @in: Beginning of the input buffer. This may be NULL if and only - * if in_pos is equal to in_size. - * @in_pos: Current position in the input buffer. This must not exceed - * in_size. - * @in_size: Size of the input buffer - * @out: Beginning of the output buffer. This may be NULL if and only - * if out_pos is equal to out_size. - * @out_pos: Current position in the output buffer. This must not exceed - * out_size. - * @out_size: Size of the output buffer - * - * Only the contents of the output buffer from out[out_pos] onward, and - * the variables in_pos and out_pos are modified by the XZ code. - */ -struct xz_buf { - const uint8_t *in; - size_t in_pos; - size_t in_size; - - uint8_t *out; - size_t out_pos; - size_t out_size; -}; - -/* - * struct xz_dec - Opaque type to hold the XZ decoder state - */ -struct xz_dec; - -/** - * xz_dec_init() - Allocate and initialize a XZ decoder state - * @mode: Operation mode - * @dict_max: Maximum size of the LZMA2 dictionary (history buffer) for - * multi-call decoding. This is ignored in single-call mode - * (mode == XZ_SINGLE). LZMA2 dictionary is always 2^n bytes - * or 2^n + 2^(n-1) bytes (the latter sizes are less common - * in practice), so other values for dict_max don't make sense. - * In the kernel, dictionary sizes of 64 KiB, 128 KiB, 256 KiB, - * 512 KiB, and 1 MiB are probably the only reasonable values, - * except for kernel and initramfs images where a bigger - * dictionary can be fine and useful. - * - * Single-call mode (XZ_SINGLE): xz_dec_run() decodes the whole stream at - * once. The caller must provide enough output space or the decoding will - * fail. The output space is used as the dictionary buffer, which is why - * there is no need to allocate the dictionary as part of the decoder's - * internal state. - * - * Because the output buffer is used as the workspace, streams encoded using - * a big dictionary are not a problem in single-call mode. It is enough that - * the output buffer is big enough to hold the actual uncompressed data; it - * can be smaller than the dictionary size stored in the stream headers. - * - * Multi-call mode with preallocated dictionary (XZ_PREALLOC): dict_max bytes - * of memory is preallocated for the LZMA2 dictionary. This way there is no - * risk that xz_dec_run() could run out of memory, since xz_dec_run() will - * never allocate any memory. Instead, if the preallocated dictionary is too - * small for decoding the given input stream, xz_dec_run() will return - * XZ_MEMLIMIT_ERROR. Thus, it is important to know what kind of data will be - * decoded to avoid allocating excessive amount of memory for the dictionary. - * - * Multi-call mode with dynamically allocated dictionary (XZ_DYNALLOC): - * dict_max specifies the maximum allowed dictionary size that xz_dec_run() - * may allocate once it has parsed the dictionary size from the stream - * headers. This way excessive allocations can be avoided while still - * limiting the maximum memory usage to a sane value to prevent running the - * system out of memory when decompressing streams from untrusted sources. - * - * On success, xz_dec_init() returns a pointer to struct xz_dec, which is - * ready to be used with xz_dec_run(). If memory allocation fails, - * xz_dec_init() returns NULL. - */ -XZ_EXTERN struct xz_dec *xz_dec_init(enum xz_mode mode, uint32_t dict_max); - -/** - * xz_dec_run() - Run the XZ decoder for a single XZ stream - * @s: Decoder state allocated using xz_dec_init() - * @b: Input and output buffers - * - * The possible return values depend on build options and operation mode. - * See enum xz_ret for details. - * - * Note that if an error occurs in single-call mode (return value is not - * XZ_STREAM_END), b->in_pos and b->out_pos are not modified and the - * contents of the output buffer from b->out[b->out_pos] onward are - * undefined. This is true even after XZ_BUF_ERROR, because with some filter - * chains, there may be a second pass over the output buffer, and this pass - * cannot be properly done if the output buffer is truncated. Thus, you - * cannot give the single-call decoder a too small buffer and then expect to - * get that amount valid data from the beginning of the stream. You must use - * the multi-call decoder if you don't want to uncompress the whole stream. - * - * Use xz_dec_run() when XZ data is stored inside some other file format. - * The decoding will stop after one XZ stream has been decompressed. To - * decompress regular .xz files which might have multiple concatenated - * streams, use xz_dec_catrun() instead. - */ -XZ_EXTERN enum xz_ret xz_dec_run(struct xz_dec *s, struct xz_buf *b); - -/** - * xz_dec_catrun() - Run the XZ decoder with support for concatenated streams - * @s: Decoder state allocated using xz_dec_init() - * @b: Input and output buffers - * @finish: This is an int instead of bool to avoid requiring stdbool.h. - * As long as more input might be coming, finish must be false. - * When the caller knows that it has provided all the input to - * the decoder (some possibly still in b->in), it must set finish - * to true. Only when finish is true can this function return - * XZ_STREAM_END to indicate successful decompression of the - * file. In single-call mode (XZ_SINGLE) finish is assumed to - * always be true; the caller-provided value is ignored. - * - * This is like xz_dec_run() except that this makes it easy to decode .xz - * files with multiple streams (multiple .xz files concatenated as is). - * The rarely-used Stream Padding feature is supported too, that is, there - * can be null bytes after or between the streams. The number of null bytes - * must be a multiple of four. - * - * When finish is false and b->in_pos == b->in_size, it is possible that - * XZ_BUF_ERROR isn't returned even when no progress is possible (XZ_OK is - * returned instead). This shouldn't matter because in this situation a - * reasonable caller will attempt to provide more input or set finish to - * true for the next xz_dec_catrun() call anyway. - * - * For any struct xz_dec that has been initialized for multi-call mode: - * Once decoding has been started with xz_dec_run() or xz_dec_catrun(), - * the same function must be used until xz_dec_reset() or xz_dec_end(). - * Switching between the two decoding functions without resetting results - * in undefined behavior. - * - * xz_dec_catrun() is only available if XZ_DEC_CONCATENATED was defined - * at compile time. - */ -XZ_EXTERN enum xz_ret xz_dec_catrun(struct xz_dec *s, struct xz_buf *b, - int finish); - -/** - * xz_dec_reset() - Reset an already allocated decoder state - * @s: Decoder state allocated using xz_dec_init() - * - * This function can be used to reset the multi-call decoder state without - * freeing and reallocating memory with xz_dec_end() and xz_dec_init(). - * - * In single-call mode, xz_dec_reset() is always called in the beginning of - * xz_dec_run(). Thus, explicit call to xz_dec_reset() is useful only in - * multi-call mode. - */ -XZ_EXTERN void xz_dec_reset(struct xz_dec *s); - -/** - * xz_dec_end() - Free the memory allocated for the decoder state - * @s: Decoder state allocated using xz_dec_init(). If s is NULL, - * this function does nothing. - */ -XZ_EXTERN void xz_dec_end(struct xz_dec *s); - -/** - * DOC: MicroLZMA decompressor - * - * This MicroLZMA header format was created for use in EROFS but may be used - * by others too. **In most cases one needs the XZ APIs above instead.** - * - * The compressed format supported by this decoder is a raw LZMA stream - * whose first byte (always 0x00) has been replaced with bitwise-negation - * of the LZMA properties (lc/lp/pb) byte. For example, if lc/lp/pb is - * 3/0/2, the first byte is 0xA2. This way the first byte can never be 0x00. - * Just like with LZMA2, lc + lp <= 4 must be true. The LZMA end-of-stream - * marker must not be used. The unused values are reserved for future use. - */ - -/* - * struct xz_dec_microlzma - Opaque type to hold the MicroLZMA decoder state - */ -struct xz_dec_microlzma; - -/** - * xz_dec_microlzma_alloc() - Allocate memory for the MicroLZMA decoder - * @mode: XZ_SINGLE or XZ_PREALLOC - * @dict_size: LZMA dictionary size. This must be at least 4 KiB and - * at most 3 GiB. - * - * In contrast to xz_dec_init(), this function only allocates the memory - * and remembers the dictionary size. xz_dec_microlzma_reset() must be used - * before calling xz_dec_microlzma_run(). - * - * The amount of allocated memory is a little less than 30 KiB with XZ_SINGLE. - * With XZ_PREALLOC also a dictionary buffer of dict_size bytes is allocated. - * - * On success, xz_dec_microlzma_alloc() returns a pointer to - * struct xz_dec_microlzma. If memory allocation fails or - * dict_size is invalid, NULL is returned. - */ -XZ_EXTERN struct xz_dec_microlzma *xz_dec_microlzma_alloc(enum xz_mode mode, - uint32_t dict_size); - -/** - * xz_dec_microlzma_reset() - Reset the MicroLZMA decoder state - * @s: Decoder state allocated using xz_dec_microlzma_alloc() - * @comp_size: Compressed size of the input stream - * @uncomp_size: Uncompressed size of the input stream. A value smaller - * than the real uncompressed size of the input stream can - * be specified if uncomp_size_is_exact is set to false. - * uncomp_size can never be set to a value larger than the - * expected real uncompressed size because it would eventually - * result in XZ_DATA_ERROR. - * @uncomp_size_is_exact: This is an int instead of bool to avoid - * requiring stdbool.h. This should normally be set to true. - * When this is set to false, error detection is weaker. - */ -XZ_EXTERN void xz_dec_microlzma_reset(struct xz_dec_microlzma *s, - uint32_t comp_size, uint32_t uncomp_size, - int uncomp_size_is_exact); - -/** - * xz_dec_microlzma_run() - Run the MicroLZMA decoder - * @s: Decoder state initialized using xz_dec_microlzma_reset() - * @b: Input and output buffers - * - * This works similarly to xz_dec_run() with a few important differences. - * Only the differences are documented here. - * - * The only possible return values are XZ_OK, XZ_STREAM_END, and - * XZ_DATA_ERROR. This function cannot return XZ_BUF_ERROR: if no progress - * is possible due to lack of input data or output space, this function will - * keep returning XZ_OK. Thus, the calling code must be written so that it - * will eventually provide input and output space matching (or exceeding) - * comp_size and uncomp_size arguments given to xz_dec_microlzma_reset(). - * If the caller cannot do this (for example, if the input file is truncated - * or otherwise corrupt), the caller must detect this error by itself to - * avoid an infinite loop. - * - * If the compressed data seems to be corrupt, XZ_DATA_ERROR is returned. - * This can happen also when incorrect dictionary, uncompressed, or - * compressed sizes have been specified. - * - * With XZ_PREALLOC only: As an extra feature, b->out may be NULL to skip over - * uncompressed data. This way the caller doesn't need to provide a temporary - * output buffer for the bytes that will be ignored. - * - * With XZ_SINGLE only: In contrast to xz_dec_run(), the return value XZ_OK - * is also possible and thus XZ_SINGLE is actually a limited multi-call mode. - * After XZ_OK the bytes decoded so far may be read from the output buffer. - * It is possible to continue decoding but the variables b->out and b->out_pos - * MUST NOT be changed by the caller. Increasing the value of b->out_size is - * allowed to make more output space available; one doesn't need to provide - * space for the whole uncompressed data on the first call. The input buffer - * may be changed normally like with XZ_PREALLOC. This way input data can be - * provided from non-contiguous memory. - */ -XZ_EXTERN enum xz_ret xz_dec_microlzma_run(struct xz_dec_microlzma *s, - struct xz_buf *b); - -/** - * xz_dec_microlzma_end() - Free the memory allocated for the decoder state - * @s: Decoder state allocated using xz_dec_microlzma_alloc(). - * If s is NULL, this function does nothing. - */ -XZ_EXTERN void xz_dec_microlzma_end(struct xz_dec_microlzma *s); - -/* - * Standalone build (userspace build or in-kernel build for boot time use) - * needs a CRC32 implementation. For normal in-kernel use, kernel's own - * CRC32 module is used instead, and users of this module don't need to - * care about the functions below. - */ -#ifndef XZ_INTERNAL_CRC32 -#ifdef __KERNEL__ -#define XZ_INTERNAL_CRC32 0 -#else -#define XZ_INTERNAL_CRC32 1 -#endif -#endif - -/* - * If CRC64 support has been enabled with XZ_USE_CRC64, a CRC64 - * implementation is needed too. - */ -#ifndef XZ_USE_CRC64 -#undef XZ_INTERNAL_CRC64 -#define XZ_INTERNAL_CRC64 0 -#endif -#ifndef XZ_INTERNAL_CRC64 -#ifdef __KERNEL__ -#error Using CRC64 in the kernel has not been implemented. -#else -#define XZ_INTERNAL_CRC64 1 -#endif -#endif - -#if XZ_INTERNAL_CRC32 -/* - * This must be called before any other xz_* function to initialize - * the CRC32 lookup table. - */ -XZ_EXTERN void xz_crc32_init(void); - -/* - * Update CRC32 value using the polynomial from IEEE-802.3. To start a new - * calculation, the third argument must be zero. To continue the calculation, - * the previously returned value is passed as the third argument. - */ -XZ_EXTERN uint32_t xz_crc32(const uint8_t *buf, size_t size, uint32_t crc); -#endif - -#if XZ_INTERNAL_CRC64 -/* - * This must be called before any other xz_* function (except xz_crc32_init()) - * to initialize the CRC64 lookup table. - */ -XZ_EXTERN void xz_crc64_init(void); - -/* - * Update CRC64 value using the polynomial from ECMA-182. To start a new - * calculation, the third argument must be zero. To continue the calculation, - * the previously returned value is passed as the third argument. - */ -XZ_EXTERN uint64_t xz_crc64(const uint8_t *buf, size_t size, uint64_t crc); -#endif - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/app/src/main/cpp/xz/xz_embedded/xz_config.h b/app/src/main/cpp/xz/xz_embedded/xz_config.h deleted file mode 100644 index 45a052f6e..000000000 --- a/app/src/main/cpp/xz/xz_embedded/xz_config.h +++ /dev/null @@ -1,133 +0,0 @@ -/* SPDX-License-Identifier: 0BSD */ - -/* - * Private includes and definitions for userspace use of XZ Embedded - * - * Author: Lasse Collin - */ - -#ifndef XZ_CONFIG_H -#define XZ_CONFIG_H - -/* Enable building of xz_dec_catrun(). Also defined via CMake. */ -#ifndef XZ_DEC_CONCATENATED -#define XZ_DEC_CONCATENATED -#endif - -/* Uncomment to enable CRC64 support. */ -/* #define XZ_USE_CRC64 */ - -/* Uncomment as needed to enable BCJ filter decoders. */ -/* #define XZ_DEC_X86 */ -/* #define XZ_DEC_ARM */ -/* #define XZ_DEC_ARMTHUMB */ -/* #define XZ_DEC_ARM64 */ -/* #define XZ_DEC_RISCV */ -/* #define XZ_DEC_POWERPC */ -/* #define XZ_DEC_IA64 */ -/* #define XZ_DEC_SPARC */ - -/* - * Visual Studio 2013 update 2 supports only __inline, not inline. - * MSVC v19.0 / VS 2015 and newer support both. - */ -#if defined(_MSC_VER) && _MSC_VER < 1900 && !defined(inline) -#define inline __inline -#endif - -#include -#include -#include - -#include "xz.h" - -#define kmalloc(size, flags) malloc(size) -#define kfree(ptr) free(ptr) -#define vmalloc(size) malloc(size) -#define vfree(ptr) free(ptr) - -#define memeq(a, b, size) (memcmp(a, b, size) == 0) -#define memzero(buf, size) memset(buf, 0, size) - -#ifndef min -#define min(x, y) ((x) < (y) ? (x) : (y)) -#endif -#define min_t(type, x, y) min(x, y) - -#ifndef fallthrough -#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311 -#define fallthrough [[fallthrough]] -#elif (defined(__GNUC__) && __GNUC__ >= 7) || \ - (defined(__clang_major__) && __clang_major__ >= 10) -#define fallthrough __attribute__((__fallthrough__)) -#else -#define fallthrough \ - do { \ - } while (0) -#endif -#endif - -/* - * Some functions have been marked with __always_inline to keep the - * performance reasonable even when the compiler is optimizing for - * small code size. You may be able to save a few bytes by #defining - * __always_inline to plain inline, but don't complain if the code - * becomes slow. - * - * NOTE: System headers on GNU/Linux may #define this macro already, - * so if you want to change it, you need to #undef it first. - */ -#ifndef __always_inline -#ifdef __GNUC__ -#define __always_inline inline __attribute__((__always_inline__)) -#else -#define __always_inline inline -#endif -#endif - -/* Inline functions to access unaligned unsigned 32-bit integers */ -#ifndef get_unaligned_le32 -static inline uint32_t get_unaligned_le32(const uint8_t *buf) { - return (uint32_t)buf[0] | ((uint32_t)buf[1] << 8) | ((uint32_t)buf[2] << 16) | - ((uint32_t)buf[3] << 24); -} -#endif - -#ifndef get_unaligned_be32 -static inline uint32_t get_unaligned_be32(const uint8_t *buf) { - return (uint32_t)((uint32_t)buf[0] << 24) | ((uint32_t)buf[1] << 16) | - ((uint32_t)buf[2] << 8) | (uint32_t)buf[3]; -} -#endif - -#ifndef put_unaligned_le32 -static inline void put_unaligned_le32(uint32_t val, uint8_t *buf) { - buf[0] = (uint8_t)val; - buf[1] = (uint8_t)(val >> 8); - buf[2] = (uint8_t)(val >> 16); - buf[3] = (uint8_t)(val >> 24); -} -#endif - -#ifndef put_unaligned_be32 -static inline void put_unaligned_be32(uint32_t val, uint8_t *buf) { - buf[0] = (uint8_t)(val >> 24); - buf[1] = (uint8_t)(val >> 16); - buf[2] = (uint8_t)(val >> 8); - buf[3] = (uint8_t)val; -} -#endif - -/* - * To keep things simpler, use the generic unaligned methods also for - * aligned access. The only place where performance could matter is - * SHA-256 but files using SHA-256 aren't common. - */ -#ifndef get_le32 -#define get_le32 get_unaligned_le32 -#endif -#ifndef get_be32 -#define get_be32 get_unaligned_be32 -#endif - -#endif diff --git a/app/src/main/cpp/xz/xz_embedded/xz_crc32.c b/app/src/main/cpp/xz/xz_embedded/xz_crc32.c deleted file mode 100644 index 1fc5dc65b..000000000 --- a/app/src/main/cpp/xz/xz_embedded/xz_crc32.c +++ /dev/null @@ -1,56 +0,0 @@ -// SPDX-License-Identifier: 0BSD - -/* - * CRC32 using the polynomial from IEEE-802.3 - * - * Authors: Lasse Collin - * Igor Pavlov - */ - -/* - * This is not the fastest implementation, but it is pretty compact. - * The fastest versions of xz_crc32() on modern CPUs without hardware - * accelerated CRC instruction are 3-5 times as fast as this version, - * but they are bigger and use more memory for the lookup table. - */ - -#include "xz_private.h" - -/* - * STATIC_RW_DATA is used in the pre-boot environment on some architectures. - * See for details. - */ -#ifndef STATIC_RW_DATA -#define STATIC_RW_DATA static -#endif - -STATIC_RW_DATA uint32_t xz_crc32_table[256]; - -XZ_EXTERN void xz_crc32_init(void) { - const uint32_t poly = 0xEDB88320; - - uint32_t i; - uint32_t j; - uint32_t r; - - for (i = 0; i < 256; ++i) { - r = i; - for (j = 0; j < 8; ++j) - r = (r >> 1) ^ (poly & ~((r & 1) - 1)); - - xz_crc32_table[i] = r; - } - - return; -} - -XZ_EXTERN uint32_t xz_crc32(const uint8_t *buf, size_t size, uint32_t crc) { - crc = ~crc; - - while (size != 0) { - crc = xz_crc32_table[*buf++ ^ (crc & 0xFF)] ^ (crc >> 8); - --size; - } - - return ~crc; -} diff --git a/app/src/main/cpp/xz/xz_embedded/xz_crc64.c b/app/src/main/cpp/xz/xz_embedded/xz_crc64.c deleted file mode 100644 index b7bf80452..000000000 --- a/app/src/main/cpp/xz/xz_embedded/xz_crc64.c +++ /dev/null @@ -1,51 +0,0 @@ -// SPDX-License-Identifier: 0BSD - -/* - * CRC64 using the polynomial from ECMA-182 - * - * This file is similar to xz_crc32.c. See the comments there. - * - * Authors: Lasse Collin - * Igor Pavlov - */ - -#include "xz_private.h" - -#ifndef STATIC_RW_DATA -#define STATIC_RW_DATA static -#endif - -STATIC_RW_DATA uint64_t xz_crc64_table[256]; - -XZ_EXTERN void xz_crc64_init(void) { - /* - * The ULL suffix is needed for -std=gnu89 compatibility - * on 32-bit platforms. - */ - const uint64_t poly = 0xC96C5795D7870F42ULL; - - uint32_t i; - uint32_t j; - uint64_t r; - - for (i = 0; i < 256; ++i) { - r = i; - for (j = 0; j < 8; ++j) - r = (r >> 1) ^ (poly & ~((r & 1) - 1)); - - xz_crc64_table[i] = r; - } - - return; -} - -XZ_EXTERN uint64_t xz_crc64(const uint8_t *buf, size_t size, uint64_t crc) { - crc = ~crc; - - while (size != 0) { - crc = xz_crc64_table[*buf++ ^ (crc & 0xFF)] ^ (crc >> 8); - --size; - } - - return ~crc; -} diff --git a/app/src/main/cpp/xz/xz_embedded/xz_dec_bcj.c b/app/src/main/cpp/xz/xz_embedded/xz_dec_bcj.c deleted file mode 100644 index 06018a881..000000000 --- a/app/src/main/cpp/xz/xz_embedded/xz_dec_bcj.c +++ /dev/null @@ -1,707 +0,0 @@ -// SPDX-License-Identifier: 0BSD - -/* - * Branch/Call/Jump (BCJ) filter decoders - * - * Authors: Lasse Collin - * Igor Pavlov - */ - -#include "xz_private.h" - -/* - * The rest of the file is inside this ifdef. It makes things a little more - * convenient when building without support for any BCJ filters. - */ -#ifdef XZ_DEC_BCJ - -struct xz_dec_bcj { - /* Type of the BCJ filter being used */ - enum { - BCJ_X86 = 4, /* x86 or x86-64 */ - BCJ_POWERPC = 5, /* Big endian only */ - BCJ_IA64 = 6, /* Big or little endian */ - BCJ_ARM = 7, /* Little endian only */ - BCJ_ARMTHUMB = 8, /* Little endian only */ - BCJ_SPARC = 9, /* Big or little endian */ - BCJ_ARM64 = 10, /* AArch64 */ - BCJ_RISCV = 11 /* RV32GQC_Zfh, RV64GQC_Zfh */ - } type; - - /* - * Return value of the next filter in the chain. We need to preserve - * this information across calls, because we must not call the next - * filter anymore once it has returned XZ_STREAM_END. - */ - enum xz_ret ret; - - /* True if we are operating in single-call mode. */ - bool single_call; - - /* - * Absolute position relative to the beginning of the uncompressed - * data (in a single .xz Block). We care only about the lowest 32 - * bits so this doesn't need to be uint64_t even with big files. - */ - uint32_t pos; - - /* x86 filter state */ - uint32_t x86_prev_mask; - - /* Temporary space to hold the variables from struct xz_buf */ - uint8_t *out; - size_t out_pos; - size_t out_size; - - struct { - /* Amount of already filtered data in the beginning of buf */ - size_t filtered; - - /* Total amount of data currently stored in buf */ - size_t size; - - /* - * Buffer to hold a mix of filtered and unfiltered data. This - * needs to be big enough to hold Alignment + 2 * Look-ahead: - * - * Type Alignment Look-ahead - * x86 1 4 - * PowerPC 4 0 - * IA-64 16 0 - * ARM 4 0 - * ARM-Thumb 2 2 - * SPARC 4 0 - */ - uint8_t buf[16]; - } temp; -}; - -#ifdef XZ_DEC_X86 -/* - * This is used to test the most significant byte of a memory address - * in an x86 instruction. - */ -static inline int bcj_x86_test_msbyte(uint8_t b) { - return b == 0x00 || b == 0xFF; -} - -static size_t bcj_x86(struct xz_dec_bcj *s, uint8_t *buf, size_t size) { - static const bool mask_to_allowed_status[8] = {true, true, true, false, - true, false, false, false}; - - static const uint8_t mask_to_bit_num[8] = {0, 1, 2, 2, 3, 3, 3, 3}; - - size_t i; - size_t prev_pos = (size_t)-1; - uint32_t prev_mask = s->x86_prev_mask; - uint32_t src; - uint32_t dest; - uint32_t j; - uint8_t b; - - if (size <= 4) - return 0; - - size -= 4; - for (i = 0; i < size; ++i) { - if ((buf[i] & 0xFE) != 0xE8) - continue; - - prev_pos = i - prev_pos; - if (prev_pos > 3) { - prev_mask = 0; - } else { - prev_mask = (prev_mask << (prev_pos - 1)) & 7; - if (prev_mask != 0) { - b = buf[i + 4 - mask_to_bit_num[prev_mask]]; - if (!mask_to_allowed_status[prev_mask] || bcj_x86_test_msbyte(b)) { - prev_pos = i; - prev_mask = (prev_mask << 1) | 1; - continue; - } - } - } - - prev_pos = i; - - if (bcj_x86_test_msbyte(buf[i + 4])) { - src = get_unaligned_le32(buf + i + 1); - while (true) { - dest = src - (s->pos + (uint32_t)i + 5); - if (prev_mask == 0) - break; - - j = mask_to_bit_num[prev_mask] * 8; - b = (uint8_t)(dest >> (24 - j)); - if (!bcj_x86_test_msbyte(b)) - break; - - src = dest ^ (((uint32_t)1 << (32 - j)) - 1); - } - - dest &= 0x01FFFFFF; - dest |= (uint32_t)0 - (dest & 0x01000000); - put_unaligned_le32(dest, buf + i + 1); - i += 4; - } else { - prev_mask = (prev_mask << 1) | 1; - } - } - - prev_pos = i - prev_pos; - s->x86_prev_mask = prev_pos > 3 ? 0 : prev_mask << (prev_pos - 1); - return i; -} -#endif - -#ifdef XZ_DEC_POWERPC -static size_t bcj_powerpc(struct xz_dec_bcj *s, uint8_t *buf, size_t size) { - size_t i; - uint32_t instr; - - size &= ~(size_t)3; - - for (i = 0; i < size; i += 4) { - instr = get_unaligned_be32(buf + i); - if ((instr & 0xFC000003) == 0x48000001) { - instr &= 0x03FFFFFC; - instr -= s->pos + (uint32_t)i; - instr &= 0x03FFFFFC; - instr |= 0x48000001; - put_unaligned_be32(instr, buf + i); - } - } - - return i; -} -#endif - -#ifdef XZ_DEC_IA64 -static size_t bcj_ia64(struct xz_dec_bcj *s, uint8_t *buf, size_t size) { - static const uint8_t branch_table[32] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 4, 4, 6, 6, 0, 0, - 7, 7, 4, 4, 0, 0, 4, 4, 0, 0}; - - /* - * The local variables take a little bit stack space, but it's less - * than what LZMA2 decoder takes, so it doesn't make sense to reduce - * stack usage here without doing that for the LZMA2 decoder too. - */ - - /* Loop counters */ - size_t i; - size_t j; - - /* Instruction slot (0, 1, or 2) in the 128-bit instruction word */ - uint32_t slot; - - /* Bitwise offset of the instruction indicated by slot */ - uint32_t bit_pos; - - /* bit_pos split into byte and bit parts */ - uint32_t byte_pos; - uint32_t bit_res; - - /* Address part of an instruction */ - uint32_t addr; - - /* Mask used to detect which instructions to convert */ - uint32_t mask; - - /* 41-bit instruction stored somewhere in the lowest 48 bits */ - uint64_t instr; - - /* Instruction normalized with bit_res for easier manipulation */ - uint64_t norm; - - size &= ~(size_t)15; - - for (i = 0; i < size; i += 16) { - mask = branch_table[buf[i] & 0x1F]; - for (slot = 0, bit_pos = 5; slot < 3; ++slot, bit_pos += 41) { - if (((mask >> slot) & 1) == 0) - continue; - - byte_pos = bit_pos >> 3; - bit_res = bit_pos & 7; - instr = 0; - for (j = 0; j < 6; ++j) - instr |= (uint64_t)(buf[i + j + byte_pos]) << (8 * j); - - norm = instr >> bit_res; - - if (((norm >> 37) & 0x0F) == 0x05 && ((norm >> 9) & 0x07) == 0) { - addr = (norm >> 13) & 0x0FFFFF; - addr |= ((uint32_t)(norm >> 36) & 1) << 20; - addr <<= 4; - addr -= s->pos + (uint32_t)i; - addr >>= 4; - - norm &= ~((uint64_t)0x8FFFFF << 13); - norm |= (uint64_t)(addr & 0x0FFFFF) << 13; - norm |= (uint64_t)(addr & 0x100000) << (36 - 20); - - instr &= (1 << bit_res) - 1; - instr |= norm << bit_res; - - for (j = 0; j < 6; j++) - buf[i + j + byte_pos] = (uint8_t)(instr >> (8 * j)); - } - } - } - - return i; -} -#endif - -#ifdef XZ_DEC_ARM -static size_t bcj_arm(struct xz_dec_bcj *s, uint8_t *buf, size_t size) { - size_t i; - uint32_t addr; - - size &= ~(size_t)3; - - for (i = 0; i < size; i += 4) { - if (buf[i + 3] == 0xEB) { - addr = (uint32_t)buf[i] | ((uint32_t)buf[i + 1] << 8) | - ((uint32_t)buf[i + 2] << 16); - addr <<= 2; - addr -= s->pos + (uint32_t)i + 8; - addr >>= 2; - buf[i] = (uint8_t)addr; - buf[i + 1] = (uint8_t)(addr >> 8); - buf[i + 2] = (uint8_t)(addr >> 16); - } - } - - return i; -} -#endif - -#ifdef XZ_DEC_ARMTHUMB -static size_t bcj_armthumb(struct xz_dec_bcj *s, uint8_t *buf, size_t size) { - size_t i; - uint32_t addr; - - if (size < 4) - return 0; - - size -= 4; - - for (i = 0; i <= size; i += 2) { - if ((buf[i + 1] & 0xF8) == 0xF0 && (buf[i + 3] & 0xF8) == 0xF8) { - addr = (((uint32_t)buf[i + 1] & 0x07) << 19) | ((uint32_t)buf[i] << 11) | - (((uint32_t)buf[i + 3] & 0x07) << 8) | (uint32_t)buf[i + 2]; - addr <<= 1; - addr -= s->pos + (uint32_t)i + 4; - addr >>= 1; - buf[i + 1] = (uint8_t)(0xF0 | ((addr >> 19) & 0x07)); - buf[i] = (uint8_t)(addr >> 11); - buf[i + 3] = (uint8_t)(0xF8 | ((addr >> 8) & 0x07)); - buf[i + 2] = (uint8_t)addr; - i += 2; - } - } - - return i; -} -#endif - -#ifdef XZ_DEC_SPARC -static size_t bcj_sparc(struct xz_dec_bcj *s, uint8_t *buf, size_t size) { - size_t i; - uint32_t instr; - - size &= ~(size_t)3; - - for (i = 0; i < size; i += 4) { - instr = get_unaligned_be32(buf + i); - if ((instr >> 22) == 0x100 || (instr >> 22) == 0x1FF) { - instr <<= 2; - instr -= s->pos + (uint32_t)i; - instr >>= 2; - instr = ((uint32_t)0x40000000 - (instr & 0x400000)) | 0x40000000 | - (instr & 0x3FFFFF); - put_unaligned_be32(instr, buf + i); - } - } - - return i; -} -#endif - -#ifdef XZ_DEC_ARM64 -static size_t bcj_arm64(struct xz_dec_bcj *s, uint8_t *buf, size_t size) { - size_t i; - uint32_t instr; - uint32_t addr; - - size &= ~(size_t)3; - - for (i = 0; i < size; i += 4) { - instr = get_unaligned_le32(buf + i); - - if ((instr >> 26) == 0x25) { - /* BL instruction */ - addr = instr - ((s->pos + (uint32_t)i) >> 2); - instr = 0x94000000 | (addr & 0x03FFFFFF); - put_unaligned_le32(instr, buf + i); - - } else if ((instr & 0x9F000000) == 0x90000000) { - /* ADRP instruction */ - addr = ((instr >> 29) & 3) | ((instr >> 3) & 0x1FFFFC); - - /* Only convert values in the range +/-512 MiB. */ - if ((addr + 0x020000) & 0x1C0000) - continue; - - addr -= (s->pos + (uint32_t)i) >> 12; - - instr &= 0x9000001F; - instr |= (addr & 3) << 29; - instr |= (addr & 0x03FFFC) << 3; - instr |= (0U - (addr & 0x020000)) & 0xE00000; - - put_unaligned_le32(instr, buf + i); - } - } - - return i; -} -#endif - -#ifdef XZ_DEC_RISCV -static size_t bcj_riscv(struct xz_dec_bcj *s, uint8_t *buf, size_t size) { - size_t i; - uint32_t b1; - uint32_t b2; - uint32_t b3; - uint32_t instr; - uint32_t instr2; - uint32_t instr2_rs1; - uint32_t addr; - - if (size < 8) - return 0; - - size -= 8; - - for (i = 0; i <= size; i += 2) { - instr = buf[i]; - - if (instr == 0xEF) { - /* JAL */ - b1 = buf[i + 1]; - if ((b1 & 0x0D) != 0) - continue; - - b2 = buf[i + 2]; - b3 = buf[i + 3]; - - addr = ((b1 & 0xF0) << 13) | (b2 << 9) | (b3 << 1); - addr -= s->pos + (uint32_t)i; - - buf[i + 1] = (uint8_t)((b1 & 0x0F) | ((addr >> 8) & 0xF0)); - - buf[i + 2] = (uint8_t)(((addr >> 16) & 0x0F) | ((addr >> 7) & 0x10) | - ((addr << 4) & 0xE0)); - - buf[i + 3] = (uint8_t)(((addr >> 4) & 0x7F) | ((addr >> 13) & 0x80)); - - i += 4 - 2; - - } else if ((instr & 0x7F) == 0x17) { - /* AUIPC */ - instr |= (uint32_t)buf[i + 1] << 8; - instr |= (uint32_t)buf[i + 2] << 16; - instr |= (uint32_t)buf[i + 3] << 24; - - if (instr & 0xE80) { - /* AUIPC's rd doesn't equal x0 or x2. */ - instr2 = get_unaligned_le32(buf + i + 4); - - if (((instr << 8) ^ (instr2 - 3)) & 0xF8003) { - i += 6 - 2; - continue; - } - - addr = (instr & 0xFFFFF000) + (instr2 >> 20); - - instr = 0x17 | (2 << 7) | (instr2 << 12); - instr2 = addr; - } else { - /* AUIPC's rd equals x0 or x2. */ - instr2_rs1 = instr >> 27; - - if ((uint32_t)((instr - 0x3117) << 18) >= (instr2_rs1 & 0x1D)) { - i += 4 - 2; - continue; - } - - addr = get_unaligned_be32(buf + i + 4); - addr -= s->pos + (uint32_t)i; - - instr2 = (instr >> 12) | (addr << 20); - - instr = 0x17 | (instr2_rs1 << 7) | ((addr + 0x800) & 0xFFFFF000); - } - - put_unaligned_le32(instr, buf + i); - put_unaligned_le32(instr2, buf + i + 4); - - i += 8 - 2; - } - } - - return i; -} -#endif - -/* - * Apply the selected BCJ filter. Update *pos and s->pos to match the amount - * of data that got filtered. - * - * NOTE: This is implemented as a switch statement to avoid using function - * pointers, which could be problematic in the kernel boot code, which must - * avoid pointers to static data (at least on x86). - */ -static void bcj_apply(struct xz_dec_bcj *s, uint8_t *buf, size_t *pos, - size_t size) { - size_t filtered; - - buf += *pos; - size -= *pos; - - switch (s->type) { -#ifdef XZ_DEC_X86 - case BCJ_X86: - filtered = bcj_x86(s, buf, size); - break; -#endif -#ifdef XZ_DEC_POWERPC - case BCJ_POWERPC: - filtered = bcj_powerpc(s, buf, size); - break; -#endif -#ifdef XZ_DEC_IA64 - case BCJ_IA64: - filtered = bcj_ia64(s, buf, size); - break; -#endif -#ifdef XZ_DEC_ARM - case BCJ_ARM: - filtered = bcj_arm(s, buf, size); - break; -#endif -#ifdef XZ_DEC_ARMTHUMB - case BCJ_ARMTHUMB: - filtered = bcj_armthumb(s, buf, size); - break; -#endif -#ifdef XZ_DEC_SPARC - case BCJ_SPARC: - filtered = bcj_sparc(s, buf, size); - break; -#endif -#ifdef XZ_DEC_ARM64 - case BCJ_ARM64: - filtered = bcj_arm64(s, buf, size); - break; -#endif -#ifdef XZ_DEC_RISCV - case BCJ_RISCV: - filtered = bcj_riscv(s, buf, size); - break; -#endif - default: - /* Never reached but silence compiler warnings. */ - filtered = 0; - break; - } - - *pos += filtered; - s->pos += filtered; -} - -/* - * Flush pending filtered data from temp to the output buffer. - * Move the remaining mixture of possibly filtered and unfiltered - * data to the beginning of temp. - */ -static void bcj_flush(struct xz_dec_bcj *s, struct xz_buf *b) { - size_t copy_size; - - copy_size = min_t(size_t, s->temp.filtered, b->out_size - b->out_pos); - memcpy(b->out + b->out_pos, s->temp.buf, copy_size); - b->out_pos += copy_size; - - s->temp.filtered -= copy_size; - s->temp.size -= copy_size; - memmove(s->temp.buf, s->temp.buf + copy_size, s->temp.size); -} - -/* - * The BCJ filter functions are primitive in sense that they process the - * data in chunks of 1-16 bytes. To hide this issue, this function does - * some buffering. - */ -XZ_EXTERN enum xz_ret xz_dec_bcj_run(struct xz_dec_bcj *s, - struct xz_dec_lzma2 *lzma2, - struct xz_buf *b) { - size_t out_start; - - /* - * Flush pending already filtered data to the output buffer. Return - * immediately if we couldn't flush everything, or if the next - * filter in the chain had already returned XZ_STREAM_END. - */ - if (s->temp.filtered > 0) { - bcj_flush(s, b); - if (s->temp.filtered > 0) - return XZ_OK; - - if (s->ret == XZ_STREAM_END) - return XZ_STREAM_END; - } - - /* - * If we have more output space than what is currently pending in - * temp, copy the unfiltered data from temp to the output buffer - * and try to fill the output buffer by decoding more data from the - * next filter in the chain. Apply the BCJ filter on the new data - * in the output buffer. If everything cannot be filtered, copy it - * to temp and rewind the output buffer position accordingly. - * - * This needs to be always run when temp.size == 0 to handle a special - * case where the output buffer is full and the next filter has no - * more output coming but hasn't returned XZ_STREAM_END yet. - */ - if (s->temp.size < b->out_size - b->out_pos || s->temp.size == 0) { - out_start = b->out_pos; - memcpy(b->out + b->out_pos, s->temp.buf, s->temp.size); - b->out_pos += s->temp.size; - - s->ret = xz_dec_lzma2_run(lzma2, b); - if (s->ret != XZ_STREAM_END && (s->ret != XZ_OK || s->single_call)) - return s->ret; - - bcj_apply(s, b->out, &out_start, b->out_pos); - - /* - * As an exception, if the next filter returned XZ_STREAM_END, - * we can do that too, since the last few bytes that remain - * unfiltered are meant to remain unfiltered. - */ - if (s->ret == XZ_STREAM_END) - return XZ_STREAM_END; - - s->temp.size = b->out_pos - out_start; - b->out_pos -= s->temp.size; - memcpy(s->temp.buf, b->out + b->out_pos, s->temp.size); - - /* - * If there wasn't enough input to the next filter to fill - * the output buffer with unfiltered data, there's no point - * to try decoding more data to temp. - */ - if (b->out_pos + s->temp.size < b->out_size) - return XZ_OK; - } - - /* - * We have unfiltered data in temp. If the output buffer isn't full - * yet, try to fill the temp buffer by decoding more data from the - * next filter. Apply the BCJ filter on temp. Then we hopefully can - * fill the actual output buffer by copying filtered data from temp. - * A mix of filtered and unfiltered data may be left in temp; it will - * be taken care on the next call to this function. - */ - if (b->out_pos < b->out_size) { - /* Make b->out{,_pos,_size} temporarily point to s->temp. */ - s->out = b->out; - s->out_pos = b->out_pos; - s->out_size = b->out_size; - b->out = s->temp.buf; - b->out_pos = s->temp.size; - b->out_size = sizeof(s->temp.buf); - - s->ret = xz_dec_lzma2_run(lzma2, b); - - s->temp.size = b->out_pos; - b->out = s->out; - b->out_pos = s->out_pos; - b->out_size = s->out_size; - - if (s->ret != XZ_OK && s->ret != XZ_STREAM_END) - return s->ret; - - bcj_apply(s, s->temp.buf, &s->temp.filtered, s->temp.size); - - /* - * If the next filter returned XZ_STREAM_END, we mark that - * everything is filtered, since the last unfiltered bytes - * of the stream are meant to be left as is. - */ - if (s->ret == XZ_STREAM_END) - s->temp.filtered = s->temp.size; - - bcj_flush(s, b); - if (s->temp.filtered > 0) - return XZ_OK; - } - - return s->ret; -} - -XZ_EXTERN struct xz_dec_bcj *xz_dec_bcj_create(bool single_call) { - struct xz_dec_bcj *s = kmalloc(sizeof(*s), GFP_KERNEL); - if (s != NULL) - s->single_call = single_call; - - return s; -} - -XZ_EXTERN enum xz_ret xz_dec_bcj_reset(struct xz_dec_bcj *s, uint8_t id) { - switch (id) { -#ifdef XZ_DEC_X86 - case BCJ_X86: -#endif -#ifdef XZ_DEC_POWERPC - case BCJ_POWERPC: -#endif -#ifdef XZ_DEC_IA64 - case BCJ_IA64: -#endif -#ifdef XZ_DEC_ARM - case BCJ_ARM: -#endif -#ifdef XZ_DEC_ARMTHUMB - case BCJ_ARMTHUMB: -#endif -#ifdef XZ_DEC_SPARC - case BCJ_SPARC: -#endif -#ifdef XZ_DEC_ARM64 - case BCJ_ARM64: -#endif -#ifdef XZ_DEC_RISCV - case BCJ_RISCV: -#endif - break; - - default: - /* Unsupported Filter ID */ - return XZ_OPTIONS_ERROR; - } - - s->type = id; - s->ret = XZ_OK; - s->pos = 0; - s->x86_prev_mask = 0; - s->temp.filtered = 0; - s->temp.size = 0; - - return XZ_OK; -} - -#endif diff --git a/app/src/main/cpp/xz/xz_embedded/xz_dec_lzma2.c b/app/src/main/cpp/xz/xz_embedded/xz_dec_lzma2.c deleted file mode 100644 index 3dc00c26a..000000000 --- a/app/src/main/cpp/xz/xz_embedded/xz_dec_lzma2.c +++ /dev/null @@ -1,1290 +0,0 @@ -// SPDX-License-Identifier: 0BSD - -/* - * LZMA2 decoder - * - * Authors: Lasse Collin - * Igor Pavlov - */ - -#include "xz_private.h" -#include "xz_lzma2.h" - -/* - * Range decoder initialization eats the first five bytes of each LZMA chunk. - */ -#define RC_INIT_BYTES 5 - -/* - * Minimum number of usable input buffer to safely decode one LZMA symbol. - * The worst case is that we decode 22 bits using probabilities and 26 - * direct bits. This may decode at maximum of 20 bytes of input. However, - * lzma_main() does an extra normalization before returning, thus we - * need to put 21 here. - */ -#define LZMA_IN_REQUIRED 21 - -/* - * Dictionary (history buffer) - * - * These are always true: - * start <= pos <= full <= end - * pos <= limit <= end - * - * In multi-call mode, also these are true: - * end == size - * size <= size_max - * allocated <= size - * - * Most of these variables are size_t to support single-call mode, - * in which the dictionary variables address the actual output - * buffer directly. - */ -struct dictionary { - /* Beginning of the history buffer */ - uint8_t *buf; - - /* Old position in buf (before decoding more data) */ - size_t start; - - /* Position in buf */ - size_t pos; - - /* - * How full dictionary is. This is used to detect corrupt input that - * would read beyond the beginning of the uncompressed stream. - */ - size_t full; - - /* Write limit; we don't write to buf[limit] or later bytes. */ - size_t limit; - - /* - * End of the dictionary buffer. In multi-call mode, this is - * the same as the dictionary size. In single-call mode, this - * indicates the size of the output buffer. - */ - size_t end; - - /* - * Size of the dictionary as specified in Block Header. This is used - * together with "full" to detect corrupt input that would make us - * read beyond the beginning of the uncompressed stream. - */ - uint32_t size; - - /* - * Maximum allowed dictionary size in multi-call mode. - * This is ignored in single-call mode. - */ - uint32_t size_max; - - /* - * Amount of memory currently allocated for the dictionary. - * This is used only with XZ_DYNALLOC. (With XZ_PREALLOC, - * size_max is always the same as the allocated size.) - */ - uint32_t allocated; - - /* Operation mode */ - enum xz_mode mode; -}; - -/* Range decoder */ -struct rc_dec { - uint32_t range; - uint32_t code; - - /* - * Number of initializing bytes remaining to be read - * by rc_read_init(). - */ - uint32_t init_bytes_left; - - /* - * Buffer from which we read our input. It can be either - * temp.buf or the caller-provided input buffer. - */ - const uint8_t *in; - size_t in_pos; - size_t in_limit; -}; - -/* Probabilities for a length decoder. */ -struct lzma_len_dec { - /* Probability of match length being at least 10 */ - uint16_t choice; - - /* Probability of match length being at least 18 */ - uint16_t choice2; - - /* Probabilities for match lengths 2-9 */ - uint16_t low[POS_STATES_MAX][LEN_LOW_SYMBOLS]; - - /* Probabilities for match lengths 10-17 */ - uint16_t mid[POS_STATES_MAX][LEN_MID_SYMBOLS]; - - /* Probabilities for match lengths 18-273 */ - uint16_t high[LEN_HIGH_SYMBOLS]; -}; - -struct lzma_dec { - /* Distances of latest four matches */ - uint32_t rep0; - uint32_t rep1; - uint32_t rep2; - uint32_t rep3; - - /* Types of the most recently seen LZMA symbols */ - enum lzma_state state; - - /* - * Length of a match. This is updated so that dict_repeat can - * be called again to finish repeating the whole match. - */ - uint32_t len; - - /* - * LZMA properties or related bit masks (number of literal - * context bits, a mask derived from the number of literal - * position bits, and a mask derived from the number - * position bits) - */ - uint32_t lc; - uint32_t literal_pos_mask; /* (1 << lp) - 1 */ - uint32_t pos_mask; /* (1 << pb) - 1 */ - - /* If 1, it's a match. Otherwise it's a single 8-bit literal. */ - uint16_t is_match[STATES][POS_STATES_MAX]; - - /* If 1, it's a repeated match. The distance is one of rep0 .. rep3. */ - uint16_t is_rep[STATES]; - - /* - * If 0, distance of a repeated match is rep0. - * Otherwise check is_rep1. - */ - uint16_t is_rep0[STATES]; - - /* - * If 0, distance of a repeated match is rep1. - * Otherwise check is_rep2. - */ - uint16_t is_rep1[STATES]; - - /* If 0, distance of a repeated match is rep2. Otherwise it is rep3. */ - uint16_t is_rep2[STATES]; - - /* - * If 1, the repeated match has length of one byte. Otherwise - * the length is decoded from rep_len_decoder. - */ - uint16_t is_rep0_long[STATES][POS_STATES_MAX]; - - /* - * Probability tree for the highest two bits of the match - * distance. There is a separate probability tree for match - * lengths of 2 (i.e. MATCH_LEN_MIN), 3, 4, and [5, 273]. - */ - uint16_t dist_slot[DIST_STATES][DIST_SLOTS]; - - /* - * Probability trees for additional bits for match distance - * when the distance is in the range [4, 127]. - */ - uint16_t dist_special[FULL_DISTANCES - DIST_MODEL_END]; - - /* - * Probability tree for the lowest four bits of a match - * distance that is equal to or greater than 128. - */ - uint16_t dist_align[ALIGN_SIZE]; - - /* Length of a normal match */ - struct lzma_len_dec match_len_dec; - - /* Length of a repeated match */ - struct lzma_len_dec rep_len_dec; - - /* Probabilities of literals */ - uint16_t literal[LITERAL_CODERS_MAX][LITERAL_CODER_SIZE]; -}; - -struct lzma2_dec { - /* Position in xz_dec_lzma2_run(). */ - enum lzma2_seq { - SEQ_CONTROL, - SEQ_UNCOMPRESSED_1, - SEQ_UNCOMPRESSED_2, - SEQ_COMPRESSED_0, - SEQ_COMPRESSED_1, - SEQ_PROPERTIES, - SEQ_LZMA_PREPARE, - SEQ_LZMA_RUN, - SEQ_COPY - } sequence; - - /* Next position after decoding the compressed size of the chunk. */ - enum lzma2_seq next_sequence; - - /* Uncompressed size of LZMA chunk (2 MiB at maximum) */ - uint32_t uncompressed; - - /* - * Compressed size of LZMA chunk or compressed/uncompressed - * size of uncompressed chunk (64 KiB at maximum) - */ - uint32_t compressed; - - /* - * True if dictionary reset is needed. This is false before - * the first chunk (LZMA or uncompressed). - */ - bool need_dict_reset; - - /* - * True if new LZMA properties are needed. This is false - * before the first LZMA chunk. - */ - bool need_props; - -#ifdef XZ_DEC_MICROLZMA - bool pedantic_microlzma; -#endif -}; - -struct xz_dec_lzma2 { - /* - * The order below is important on x86 to reduce code size and - * it shouldn't hurt on other platforms. Everything up to and - * including lzma.pos_mask are in the first 128 bytes on x86-32, - * which allows using smaller instructions to access those - * variables. On x86-64, fewer variables fit into the first 128 - * bytes, but this is still the best order without sacrificing - * the readability by splitting the structures. - */ - struct rc_dec rc; - struct dictionary dict; - struct lzma2_dec lzma2; - struct lzma_dec lzma; - - /* - * Temporary buffer which holds small number of input bytes between - * decoder calls. See lzma2_lzma() for details. - */ - struct { - uint32_t size; - uint8_t buf[3 * LZMA_IN_REQUIRED]; - } temp; -}; - -/************** - * Dictionary * - **************/ - -/* - * Reset the dictionary state. When in single-call mode, set up the beginning - * of the dictionary to point to the actual output buffer. - */ -static void dict_reset(struct dictionary *dict, struct xz_buf *b) { - if (DEC_IS_SINGLE(dict->mode)) { - dict->buf = b->out + b->out_pos; - dict->end = b->out_size - b->out_pos; - } - - dict->start = 0; - dict->pos = 0; - dict->limit = 0; - dict->full = 0; -} - -/* Set dictionary write limit */ -static void dict_limit(struct dictionary *dict, size_t out_max) { - if (dict->end - dict->pos <= out_max) - dict->limit = dict->end; - else - dict->limit = dict->pos + out_max; -} - -/* Return true if at least one byte can be written into the dictionary. */ -static inline bool dict_has_space(const struct dictionary *dict) { - return dict->pos < dict->limit; -} - -/* - * Get a byte from the dictionary at the given distance. The distance is - * assumed to valid, or as a special case, zero when the dictionary is - * still empty. This special case is needed for single-call decoding to - * avoid writing a '\0' to the end of the destination buffer. - */ -static inline uint32_t dict_get(const struct dictionary *dict, uint32_t dist) { - size_t offset = dict->pos - dist - 1; - - if (dist >= dict->pos) - offset += dict->end; - - return dict->full > 0 ? dict->buf[offset] : 0; -} - -/* - * Put one byte into the dictionary. It is assumed that there is space for it. - */ -static inline void dict_put(struct dictionary *dict, uint8_t byte) { - dict->buf[dict->pos++] = byte; - - if (dict->full < dict->pos) - dict->full = dict->pos; -} - -/* - * Repeat given number of bytes from the given distance. If the distance is - * invalid, false is returned. On success, true is returned and *len is - * updated to indicate how many bytes were left to be repeated. - */ -static bool dict_repeat(struct dictionary *dict, uint32_t *len, uint32_t dist) { - size_t back; - uint32_t left; - - if (dist >= dict->full || dist >= dict->size) - return false; - - left = min_t(size_t, dict->limit - dict->pos, *len); - *len -= left; - - back = dict->pos - dist - 1; - if (dist >= dict->pos) - back += dict->end; - - do { - dict->buf[dict->pos++] = dict->buf[back++]; - if (back == dict->end) - back = 0; - } while (--left > 0); - - if (dict->full < dict->pos) - dict->full = dict->pos; - - return true; -} - -/* Copy uncompressed data as is from input to dictionary and output buffers. */ -static void dict_uncompressed(struct dictionary *dict, struct xz_buf *b, - uint32_t *left) { - size_t copy_size; - - while (*left > 0 && b->in_pos < b->in_size && b->out_pos < b->out_size) { - copy_size = min(b->in_size - b->in_pos, b->out_size - b->out_pos); - if (copy_size > dict->end - dict->pos) - copy_size = dict->end - dict->pos; - if (copy_size > *left) - copy_size = *left; - - *left -= copy_size; - - /* - * If doing in-place decompression in single-call mode and the - * uncompressed size of the file is larger than the caller - * thought (i.e. it is invalid input!), the buffers below may - * overlap and cause undefined behavior with memcpy(). - * With valid inputs memcpy() would be fine here. - */ - memmove(dict->buf + dict->pos, b->in + b->in_pos, copy_size); - dict->pos += copy_size; - - if (dict->full < dict->pos) - dict->full = dict->pos; - - if (DEC_IS_MULTI(dict->mode)) { - if (dict->pos == dict->end) - dict->pos = 0; - - /* - * Like above but for multi-call mode: use memmove() - * to avoid undefined behavior with invalid input. - */ - memmove(b->out + b->out_pos, b->in + b->in_pos, copy_size); - } - - dict->start = dict->pos; - - b->out_pos += copy_size; - b->in_pos += copy_size; - } -} - -#ifdef XZ_DEC_MICROLZMA -#define DICT_FLUSH_SUPPORTS_SKIPPING true -#else -#define DICT_FLUSH_SUPPORTS_SKIPPING false -#endif - -/* - * Flush pending data from dictionary to b->out. It is assumed that there is - * enough space in b->out. This is guaranteed because caller uses dict_limit() - * before decoding data into the dictionary. - */ -static uint32_t dict_flush(struct dictionary *dict, struct xz_buf *b) { - size_t copy_size = dict->pos - dict->start; - - if (DEC_IS_MULTI(dict->mode)) { - if (dict->pos == dict->end) - dict->pos = 0; - - /* - * These buffers cannot overlap even if doing in-place - * decompression because in multi-call mode dict->buf - * has been allocated by us in this file; it's not - * provided by the caller like in single-call mode. - * - * With MicroLZMA, b->out can be NULL to skip bytes that - * the caller doesn't need. This cannot be done with XZ - * because it would break BCJ filters. - */ - if (!DICT_FLUSH_SUPPORTS_SKIPPING || b->out != NULL) - memcpy(b->out + b->out_pos, dict->buf + dict->start, copy_size); - } - - dict->start = dict->pos; - b->out_pos += copy_size; - return copy_size; -} - -/***************** - * Range decoder * - *****************/ - -/* Reset the range decoder. */ -static void rc_reset(struct rc_dec *rc) { - rc->range = (uint32_t)-1; - rc->code = 0; - rc->init_bytes_left = RC_INIT_BYTES; -} - -/* - * Read the first five initial bytes into rc->code if they haven't been - * read already. (Yes, the first byte gets completely ignored.) - */ -static bool rc_read_init(struct rc_dec *rc, struct xz_buf *b) { - while (rc->init_bytes_left > 0) { - if (b->in_pos == b->in_size) - return false; - - rc->code = (rc->code << 8) + b->in[b->in_pos++]; - --rc->init_bytes_left; - } - - return true; -} - -/* Return true if there may not be enough input for the next decoding loop. */ -static inline bool rc_limit_exceeded(const struct rc_dec *rc) { - return rc->in_pos > rc->in_limit; -} - -/* - * Return true if it is possible (from point of view of range decoder) that - * we have reached the end of the LZMA chunk. - */ -static inline bool rc_is_finished(const struct rc_dec *rc) { - return rc->code == 0; -} - -/* Read the next input byte if needed. */ -static __always_inline void rc_normalize(struct rc_dec *rc) { - if (rc->range < RC_TOP_VALUE) { - rc->range <<= RC_SHIFT_BITS; - rc->code = (rc->code << RC_SHIFT_BITS) + rc->in[rc->in_pos++]; - } -} - -/* - * Decode one bit. In some versions, this function has been split in three - * functions so that the compiler is supposed to be able to more easily avoid - * an extra branch. In this particular version of the LZMA decoder, this - * doesn't seem to be a good idea (tested with GCC 3.3.6, 3.4.6, and 4.3.3 - * on x86). Using a non-split version results in nicer looking code too. - * - * NOTE: This must return an int. Do not make it return a bool or the speed - * of the code generated by GCC 3.x decreases 10-15 %. (GCC 4.3 doesn't care, - * and it generates 10-20 % faster code than GCC 3.x from this file anyway.) - */ -static __always_inline int rc_bit(struct rc_dec *rc, uint16_t *prob) { - uint32_t bound; - int bit; - - rc_normalize(rc); - bound = (rc->range >> RC_BIT_MODEL_TOTAL_BITS) * *prob; - if (rc->code < bound) { - rc->range = bound; - *prob += (RC_BIT_MODEL_TOTAL - *prob) >> RC_MOVE_BITS; - bit = 0; - } else { - rc->range -= bound; - rc->code -= bound; - *prob -= *prob >> RC_MOVE_BITS; - bit = 1; - } - - return bit; -} - -/* Decode a bittree starting from the most significant bit. */ -static __always_inline uint32_t rc_bittree(struct rc_dec *rc, uint16_t *probs, - uint32_t limit) { - uint32_t symbol = 1; - - do { - if (rc_bit(rc, &probs[symbol])) - symbol = (symbol << 1) + 1; - else - symbol <<= 1; - } while (symbol < limit); - - return symbol; -} - -/* Decode a bittree starting from the least significant bit. */ -static __always_inline void rc_bittree_reverse(struct rc_dec *rc, - uint16_t *probs, uint32_t *dest, - uint32_t limit) { - uint32_t symbol = 1; - uint32_t i = 0; - - do { - if (rc_bit(rc, &probs[symbol])) { - symbol = (symbol << 1) + 1; - *dest += 1 << i; - } else { - symbol <<= 1; - } - } while (++i < limit); -} - -/* Decode direct bits (fixed fifty-fifty probability) */ -static inline void rc_direct(struct rc_dec *rc, uint32_t *dest, - uint32_t limit) { - uint32_t mask; - - do { - rc_normalize(rc); - rc->range >>= 1; - rc->code -= rc->range; - mask = (uint32_t)0 - (rc->code >> 31); - rc->code += rc->range & mask; - *dest = (*dest << 1) + (mask + 1); - } while (--limit > 0); -} - -/******** - * LZMA * - ********/ - -/* Get pointer to literal coder probability array. */ -static uint16_t *lzma_literal_probs(struct xz_dec_lzma2 *s) { - uint32_t prev_byte = dict_get(&s->dict, 0); - uint32_t low = prev_byte >> (8 - s->lzma.lc); - uint32_t high = (s->dict.pos & s->lzma.literal_pos_mask) << s->lzma.lc; - return s->lzma.literal[low + high]; -} - -/* Decode a literal (one 8-bit byte) */ -static void lzma_literal(struct xz_dec_lzma2 *s) { - uint16_t *probs; - uint32_t symbol; - uint32_t match_byte; - uint32_t match_bit; - uint32_t offset; - uint32_t i; - - probs = lzma_literal_probs(s); - - if (lzma_state_is_literal(s->lzma.state)) { - symbol = rc_bittree(&s->rc, probs, 0x100); - } else { - symbol = 1; - match_byte = dict_get(&s->dict, s->lzma.rep0) << 1; - offset = 0x100; - - do { - match_bit = match_byte & offset; - match_byte <<= 1; - i = offset + match_bit + symbol; - - if (rc_bit(&s->rc, &probs[i])) { - symbol = (symbol << 1) + 1; - offset &= match_bit; - } else { - symbol <<= 1; - offset &= ~match_bit; - } - } while (symbol < 0x100); - } - - dict_put(&s->dict, (uint8_t)symbol); - lzma_state_literal(&s->lzma.state); -} - -/* Decode the length of the match into s->lzma.len. */ -static void lzma_len(struct xz_dec_lzma2 *s, struct lzma_len_dec *l, - uint32_t pos_state) { - uint16_t *probs; - uint32_t limit; - - if (!rc_bit(&s->rc, &l->choice)) { - probs = l->low[pos_state]; - limit = LEN_LOW_SYMBOLS; - s->lzma.len = MATCH_LEN_MIN; - } else { - if (!rc_bit(&s->rc, &l->choice2)) { - probs = l->mid[pos_state]; - limit = LEN_MID_SYMBOLS; - s->lzma.len = MATCH_LEN_MIN + LEN_LOW_SYMBOLS; - } else { - probs = l->high; - limit = LEN_HIGH_SYMBOLS; - s->lzma.len = MATCH_LEN_MIN + LEN_LOW_SYMBOLS + LEN_MID_SYMBOLS; - } - } - - s->lzma.len += rc_bittree(&s->rc, probs, limit) - limit; -} - -/* Decode a match. The distance will be stored in s->lzma.rep0. */ -static void lzma_match(struct xz_dec_lzma2 *s, uint32_t pos_state) { - uint16_t *probs; - uint32_t dist_slot; - uint32_t limit; - - lzma_state_match(&s->lzma.state); - - s->lzma.rep3 = s->lzma.rep2; - s->lzma.rep2 = s->lzma.rep1; - s->lzma.rep1 = s->lzma.rep0; - - lzma_len(s, &s->lzma.match_len_dec, pos_state); - - probs = s->lzma.dist_slot[lzma_get_dist_state(s->lzma.len)]; - dist_slot = rc_bittree(&s->rc, probs, DIST_SLOTS) - DIST_SLOTS; - - if (dist_slot < DIST_MODEL_START) { - s->lzma.rep0 = dist_slot; - } else { - limit = (dist_slot >> 1) - 1; - s->lzma.rep0 = 2 + (dist_slot & 1); - - if (dist_slot < DIST_MODEL_END) { - s->lzma.rep0 <<= limit; - probs = s->lzma.dist_special + s->lzma.rep0 - dist_slot - 1; - rc_bittree_reverse(&s->rc, probs, &s->lzma.rep0, limit); - } else { - rc_direct(&s->rc, &s->lzma.rep0, limit - ALIGN_BITS); - s->lzma.rep0 <<= ALIGN_BITS; - rc_bittree_reverse(&s->rc, s->lzma.dist_align, &s->lzma.rep0, ALIGN_BITS); - } - } -} - -/* - * Decode a repeated match. The distance is one of the four most recently - * seen matches. The distance will be stored in s->lzma.rep0. - */ -static void lzma_rep_match(struct xz_dec_lzma2 *s, uint32_t pos_state) { - uint32_t tmp; - - if (!rc_bit(&s->rc, &s->lzma.is_rep0[s->lzma.state])) { - if (!rc_bit(&s->rc, &s->lzma.is_rep0_long[s->lzma.state][pos_state])) { - lzma_state_short_rep(&s->lzma.state); - s->lzma.len = 1; - return; - } - } else { - if (!rc_bit(&s->rc, &s->lzma.is_rep1[s->lzma.state])) { - tmp = s->lzma.rep1; - } else { - if (!rc_bit(&s->rc, &s->lzma.is_rep2[s->lzma.state])) { - tmp = s->lzma.rep2; - } else { - tmp = s->lzma.rep3; - s->lzma.rep3 = s->lzma.rep2; - } - - s->lzma.rep2 = s->lzma.rep1; - } - - s->lzma.rep1 = s->lzma.rep0; - s->lzma.rep0 = tmp; - } - - lzma_state_long_rep(&s->lzma.state); - lzma_len(s, &s->lzma.rep_len_dec, pos_state); -} - -/* LZMA decoder core */ -static bool lzma_main(struct xz_dec_lzma2 *s) { - uint32_t pos_state; - - /* - * If the dictionary was reached during the previous call, try to - * finish the possibly pending repeat in the dictionary. - */ - if (dict_has_space(&s->dict) && s->lzma.len > 0) - dict_repeat(&s->dict, &s->lzma.len, s->lzma.rep0); - - /* - * Decode more LZMA symbols. One iteration may consume up to - * LZMA_IN_REQUIRED - 1 bytes. - */ - while (dict_has_space(&s->dict) && !rc_limit_exceeded(&s->rc)) { - pos_state = s->dict.pos & s->lzma.pos_mask; - - if (!rc_bit(&s->rc, &s->lzma.is_match[s->lzma.state][pos_state])) { - lzma_literal(s); - } else { - if (rc_bit(&s->rc, &s->lzma.is_rep[s->lzma.state])) - lzma_rep_match(s, pos_state); - else - lzma_match(s, pos_state); - - if (!dict_repeat(&s->dict, &s->lzma.len, s->lzma.rep0)) - return false; - } - } - - /* - * Having the range decoder always normalized when we are outside - * this function makes it easier to correctly handle end of the chunk. - */ - rc_normalize(&s->rc); - - return true; -} - -/* - * Reset the LZMA decoder and range decoder state. Dictionary is not reset - * here, because LZMA state may be reset without resetting the dictionary. - */ -static void lzma_reset(struct xz_dec_lzma2 *s) { - uint16_t *probs; - size_t i; - - s->lzma.state = STATE_LIT_LIT; - s->lzma.rep0 = 0; - s->lzma.rep1 = 0; - s->lzma.rep2 = 0; - s->lzma.rep3 = 0; - s->lzma.len = 0; - - /* - * All probabilities are initialized to the same value. This hack - * makes the code smaller by avoiding a separate loop for each - * probability array. - * - * This could be optimized so that only that part of literal - * probabilities that are actually required. In the common case - * we would write 12 KiB less. - */ - probs = s->lzma.is_match[0]; - for (i = 0; i < PROBS_TOTAL; ++i) - probs[i] = RC_BIT_MODEL_TOTAL / 2; - - rc_reset(&s->rc); -} - -/* - * Decode and validate LZMA properties (lc/lp/pb) and calculate the bit masks - * from the decoded lp and pb values. On success, the LZMA decoder state is - * reset and true is returned. - */ -static bool lzma_props(struct xz_dec_lzma2 *s, uint8_t props) { - if (props > (4 * 5 + 4) * 9 + 8) - return false; - - s->lzma.pos_mask = 0; - while (props >= 9 * 5) { - props -= 9 * 5; - ++s->lzma.pos_mask; - } - - s->lzma.pos_mask = (1 << s->lzma.pos_mask) - 1; - - s->lzma.literal_pos_mask = 0; - while (props >= 9) { - props -= 9; - ++s->lzma.literal_pos_mask; - } - - s->lzma.lc = props; - - if (s->lzma.lc + s->lzma.literal_pos_mask > 4) - return false; - - s->lzma.literal_pos_mask = (1 << s->lzma.literal_pos_mask) - 1; - - lzma_reset(s); - - return true; -} - -/********* - * LZMA2 * - *********/ - -/* - * The LZMA decoder assumes that if the input limit (s->rc.in_limit) hasn't - * been exceeded, it is safe to read up to LZMA_IN_REQUIRED bytes. This - * wrapper function takes care of making the LZMA decoder's assumption safe. - * - * As long as there is plenty of input left to be decoded in the current LZMA - * chunk, we decode directly from the caller-supplied input buffer until - * there's LZMA_IN_REQUIRED bytes left. Those remaining bytes are copied into - * s->temp.buf, which (hopefully) gets filled on the next call to this - * function. We decode a few bytes from the temporary buffer so that we can - * continue decoding from the caller-supplied input buffer again. - */ -static bool lzma2_lzma(struct xz_dec_lzma2 *s, struct xz_buf *b) { - size_t in_avail; - uint32_t tmp; - - in_avail = b->in_size - b->in_pos; - if (s->temp.size > 0 || s->lzma2.compressed == 0) { - tmp = 2 * LZMA_IN_REQUIRED - s->temp.size; - if (tmp > s->lzma2.compressed - s->temp.size) - tmp = s->lzma2.compressed - s->temp.size; - if (tmp > in_avail) - tmp = in_avail; - - memcpy(s->temp.buf + s->temp.size, b->in + b->in_pos, tmp); - - if (s->temp.size + tmp == s->lzma2.compressed) { - memzero(s->temp.buf + s->temp.size + tmp, - sizeof(s->temp.buf) - s->temp.size - tmp); - s->rc.in_limit = s->temp.size + tmp; - } else if (s->temp.size + tmp < LZMA_IN_REQUIRED) { - s->temp.size += tmp; - b->in_pos += tmp; - return true; - } else { - s->rc.in_limit = s->temp.size + tmp - LZMA_IN_REQUIRED; - } - - s->rc.in = s->temp.buf; - s->rc.in_pos = 0; - - if (!lzma_main(s) || s->rc.in_pos > s->temp.size + tmp) - return false; - - s->lzma2.compressed -= s->rc.in_pos; - - if (s->rc.in_pos < s->temp.size) { - s->temp.size -= s->rc.in_pos; - memmove(s->temp.buf, s->temp.buf + s->rc.in_pos, s->temp.size); - return true; - } - - b->in_pos += s->rc.in_pos - s->temp.size; - s->temp.size = 0; - } - - in_avail = b->in_size - b->in_pos; - if (in_avail >= LZMA_IN_REQUIRED) { - s->rc.in = b->in; - s->rc.in_pos = b->in_pos; - - if (in_avail >= s->lzma2.compressed + LZMA_IN_REQUIRED) - s->rc.in_limit = b->in_pos + s->lzma2.compressed; - else - s->rc.in_limit = b->in_size - LZMA_IN_REQUIRED; - - if (!lzma_main(s)) - return false; - - in_avail = s->rc.in_pos - b->in_pos; - if (in_avail > s->lzma2.compressed) - return false; - - s->lzma2.compressed -= in_avail; - b->in_pos = s->rc.in_pos; - } - - in_avail = b->in_size - b->in_pos; - if (in_avail < LZMA_IN_REQUIRED) { - if (in_avail > s->lzma2.compressed) - in_avail = s->lzma2.compressed; - - memcpy(s->temp.buf, b->in + b->in_pos, in_avail); - s->temp.size = in_avail; - b->in_pos += in_avail; - } - - return true; -} - -/* - * Take care of the LZMA2 control layer, and forward the job of actual LZMA - * decoding or copying of uncompressed chunks to other functions. - */ -XZ_EXTERN enum xz_ret xz_dec_lzma2_run(struct xz_dec_lzma2 *s, - struct xz_buf *b) { - uint32_t tmp; - - while (b->in_pos < b->in_size || s->lzma2.sequence == SEQ_LZMA_RUN) { - switch (s->lzma2.sequence) { - case SEQ_CONTROL: - /* - * LZMA2 control byte - * - * Exact values: - * 0x00 End marker - * 0x01 Dictionary reset followed by - * an uncompressed chunk - * 0x02 Uncompressed chunk (no dictionary reset) - * - * Highest three bits (s->control & 0xE0): - * 0xE0 Dictionary reset, new properties and state - * reset, followed by LZMA compressed chunk - * 0xC0 New properties and state reset, followed - * by LZMA compressed chunk (no dictionary - * reset) - * 0xA0 State reset using old properties, - * followed by LZMA compressed chunk (no - * dictionary reset) - * 0x80 LZMA chunk (no dictionary or state reset) - * - * For LZMA compressed chunks, the lowest five bits - * (s->control & 1F) are the highest bits of the - * uncompressed size (bits 16-20). - * - * A new LZMA2 stream must begin with a dictionary - * reset. The first LZMA chunk must set new - * properties and reset the LZMA state. - * - * Values that don't match anything described above - * are invalid and we return XZ_DATA_ERROR. - */ - tmp = b->in[b->in_pos++]; - - if (tmp == 0x00) - return XZ_STREAM_END; - - if (tmp >= 0xE0 || tmp == 0x01) { - s->lzma2.need_props = true; - s->lzma2.need_dict_reset = false; - dict_reset(&s->dict, b); - } else if (s->lzma2.need_dict_reset) { - return XZ_DATA_ERROR; - } - - if (tmp >= 0x80) { - s->lzma2.uncompressed = (tmp & 0x1F) << 16; - s->lzma2.sequence = SEQ_UNCOMPRESSED_1; - - if (tmp >= 0xC0) { - /* - * When there are new properties, - * state reset is done at - * SEQ_PROPERTIES. - */ - s->lzma2.need_props = false; - s->lzma2.next_sequence = SEQ_PROPERTIES; - - } else if (s->lzma2.need_props) { - return XZ_DATA_ERROR; - - } else { - s->lzma2.next_sequence = SEQ_LZMA_PREPARE; - if (tmp >= 0xA0) - lzma_reset(s); - } - } else { - if (tmp > 0x02) - return XZ_DATA_ERROR; - - s->lzma2.sequence = SEQ_COMPRESSED_0; - s->lzma2.next_sequence = SEQ_COPY; - } - - break; - - case SEQ_UNCOMPRESSED_1: - s->lzma2.uncompressed += (uint32_t)b->in[b->in_pos++] << 8; - s->lzma2.sequence = SEQ_UNCOMPRESSED_2; - break; - - case SEQ_UNCOMPRESSED_2: - s->lzma2.uncompressed += (uint32_t)b->in[b->in_pos++] + 1; - s->lzma2.sequence = SEQ_COMPRESSED_0; - break; - - case SEQ_COMPRESSED_0: - s->lzma2.compressed = (uint32_t)b->in[b->in_pos++] << 8; - s->lzma2.sequence = SEQ_COMPRESSED_1; - break; - - case SEQ_COMPRESSED_1: - s->lzma2.compressed += (uint32_t)b->in[b->in_pos++] + 1; - s->lzma2.sequence = s->lzma2.next_sequence; - break; - - case SEQ_PROPERTIES: - if (!lzma_props(s, b->in[b->in_pos++])) - return XZ_DATA_ERROR; - - s->lzma2.sequence = SEQ_LZMA_PREPARE; - - fallthrough; - - case SEQ_LZMA_PREPARE: - if (s->lzma2.compressed < RC_INIT_BYTES) - return XZ_DATA_ERROR; - - if (!rc_read_init(&s->rc, b)) - return XZ_OK; - - s->lzma2.compressed -= RC_INIT_BYTES; - s->lzma2.sequence = SEQ_LZMA_RUN; - - fallthrough; - - case SEQ_LZMA_RUN: - /* - * Set dictionary limit to indicate how much we want - * to be encoded at maximum. Decode new data into the - * dictionary. Flush the new data from dictionary to - * b->out. Check if we finished decoding this chunk. - * In case the dictionary got full but we didn't fill - * the output buffer yet, we may run this loop - * multiple times without changing s->lzma2.sequence. - */ - dict_limit(&s->dict, min_t(size_t, b->out_size - b->out_pos, - s->lzma2.uncompressed)); - if (!lzma2_lzma(s, b)) - return XZ_DATA_ERROR; - - s->lzma2.uncompressed -= dict_flush(&s->dict, b); - - if (s->lzma2.uncompressed == 0) { - if (s->lzma2.compressed > 0 || s->lzma.len > 0 || - !rc_is_finished(&s->rc)) - return XZ_DATA_ERROR; - - rc_reset(&s->rc); - s->lzma2.sequence = SEQ_CONTROL; - - } else if (b->out_pos == b->out_size || - (b->in_pos == b->in_size && - s->temp.size < s->lzma2.compressed)) { - return XZ_OK; - } - - break; - - case SEQ_COPY: - dict_uncompressed(&s->dict, b, &s->lzma2.compressed); - if (s->lzma2.compressed > 0) - return XZ_OK; - - s->lzma2.sequence = SEQ_CONTROL; - break; - } - } - - return XZ_OK; -} - -XZ_EXTERN struct xz_dec_lzma2 *xz_dec_lzma2_create(enum xz_mode mode, - uint32_t dict_max) { - struct xz_dec_lzma2 *s = kmalloc(sizeof(*s), GFP_KERNEL); - if (s == NULL) - return NULL; - - s->dict.mode = mode; - s->dict.size_max = dict_max; - - if (DEC_IS_PREALLOC(mode)) { - s->dict.buf = vmalloc(dict_max); - if (s->dict.buf == NULL) { - kfree(s); - return NULL; - } - } else if (DEC_IS_DYNALLOC(mode)) { - s->dict.buf = NULL; - s->dict.allocated = 0; - } - - return s; -} - -XZ_EXTERN enum xz_ret xz_dec_lzma2_reset(struct xz_dec_lzma2 *s, - uint8_t props) { - /* This limits dictionary size to 3 GiB to keep parsing simpler. */ - if (props > 39) - return XZ_OPTIONS_ERROR; - - s->dict.size = 2 + (props & 1); - s->dict.size <<= (props >> 1) + 11; - - if (DEC_IS_MULTI(s->dict.mode)) { - if (s->dict.size > s->dict.size_max) - return XZ_MEMLIMIT_ERROR; - - s->dict.end = s->dict.size; - - if (DEC_IS_DYNALLOC(s->dict.mode)) { - if (s->dict.allocated < s->dict.size) { - s->dict.allocated = s->dict.size; - vfree(s->dict.buf); - s->dict.buf = vmalloc(s->dict.size); - if (s->dict.buf == NULL) { - s->dict.allocated = 0; - return XZ_MEM_ERROR; - } - } - } - } - - s->lzma2.sequence = SEQ_CONTROL; - s->lzma2.need_dict_reset = true; - - s->temp.size = 0; - - return XZ_OK; -} - -XZ_EXTERN void xz_dec_lzma2_end(struct xz_dec_lzma2 *s) { - if (DEC_IS_MULTI(s->dict.mode)) - vfree(s->dict.buf); - - kfree(s); -} - -#ifdef XZ_DEC_MICROLZMA -/* This is a wrapper struct to have a nice struct name in the public API. */ -struct xz_dec_microlzma { - struct xz_dec_lzma2 s; -}; - -XZ_EXTERN enum xz_ret xz_dec_microlzma_run(struct xz_dec_microlzma *s_ptr, - struct xz_buf *b) { - struct xz_dec_lzma2 *s = &s_ptr->s; - - /* - * sequence is SEQ_PROPERTIES before the first input byte, - * SEQ_LZMA_PREPARE until a total of five bytes have been read, - * and SEQ_LZMA_RUN for the rest of the input stream. - */ - if (s->lzma2.sequence != SEQ_LZMA_RUN) { - if (s->lzma2.sequence == SEQ_PROPERTIES) { - /* One byte is needed for the props. */ - if (b->in_pos >= b->in_size) - return XZ_OK; - - /* - * Don't increment b->in_pos here. The same byte is - * also passed to rc_read_init() which will ignore it. - */ - if (!lzma_props(s, ~b->in[b->in_pos])) - return XZ_DATA_ERROR; - - s->lzma2.sequence = SEQ_LZMA_PREPARE; - } - - /* - * xz_dec_microlzma_reset() doesn't validate the compressed - * size so we do it here. We have to limit the maximum size - * to avoid integer overflows in lzma2_lzma(). 3 GiB is a nice - * round number and much more than users of this code should - * ever need. - */ - if (s->lzma2.compressed < RC_INIT_BYTES || s->lzma2.compressed > (3U << 30)) - return XZ_DATA_ERROR; - - if (!rc_read_init(&s->rc, b)) - return XZ_OK; - - s->lzma2.compressed -= RC_INIT_BYTES; - s->lzma2.sequence = SEQ_LZMA_RUN; - - dict_reset(&s->dict, b); - } - - /* This is to allow increasing b->out_size between calls. */ - if (DEC_IS_SINGLE(s->dict.mode)) - s->dict.end = b->out_size - b->out_pos; - - while (true) { - dict_limit(&s->dict, - min_t(size_t, b->out_size - b->out_pos, s->lzma2.uncompressed)); - - if (!lzma2_lzma(s, b)) - return XZ_DATA_ERROR; - - s->lzma2.uncompressed -= dict_flush(&s->dict, b); - - if (s->lzma2.uncompressed == 0) { - if (s->lzma2.pedantic_microlzma) { - if (s->lzma2.compressed > 0 || s->lzma.len > 0 || - !rc_is_finished(&s->rc)) - return XZ_DATA_ERROR; - } - - return XZ_STREAM_END; - } - - if (b->out_pos == b->out_size) - return XZ_OK; - - if (b->in_pos == b->in_size && s->temp.size < s->lzma2.compressed) - return XZ_OK; - } -} - -XZ_EXTERN struct xz_dec_microlzma *xz_dec_microlzma_alloc(enum xz_mode mode, - uint32_t dict_size) { - struct xz_dec_microlzma *s; - - /* Restrict dict_size to the same range as in the LZMA2 code. */ - if (dict_size < 4096 || dict_size > (3U << 30)) - return NULL; - - s = kmalloc(sizeof(*s), GFP_KERNEL); - if (s == NULL) - return NULL; - - s->s.dict.mode = mode; - s->s.dict.size = dict_size; - - if (DEC_IS_MULTI(mode)) { - s->s.dict.end = dict_size; - - s->s.dict.buf = vmalloc(dict_size); - if (s->s.dict.buf == NULL) { - kfree(s); - return NULL; - } - } - - return s; -} - -XZ_EXTERN void xz_dec_microlzma_reset(struct xz_dec_microlzma *s, - uint32_t comp_size, uint32_t uncomp_size, - int uncomp_size_is_exact) { - /* - * comp_size is validated in xz_dec_microlzma_run(). - * uncomp_size can safely be anything. - */ - s->s.lzma2.compressed = comp_size; - s->s.lzma2.uncompressed = uncomp_size; - s->s.lzma2.pedantic_microlzma = uncomp_size_is_exact; - - s->s.lzma2.sequence = SEQ_PROPERTIES; - s->s.temp.size = 0; -} - -XZ_EXTERN void xz_dec_microlzma_end(struct xz_dec_microlzma *s) { - if (DEC_IS_MULTI(s->s.dict.mode)) - vfree(s->s.dict.buf); - - kfree(s); -} -#endif diff --git a/app/src/main/cpp/xz/xz_embedded/xz_dec_stream.c b/app/src/main/cpp/xz/xz_embedded/xz_dec_stream.c deleted file mode 100644 index 648ba7901..000000000 --- a/app/src/main/cpp/xz/xz_embedded/xz_dec_stream.c +++ /dev/null @@ -1,945 +0,0 @@ -// SPDX-License-Identifier: 0BSD - -/* - * .xz Stream decoder - * - * Author: Lasse Collin - */ - -#include "xz_private.h" -#include "xz_stream.h" - -#ifdef XZ_USE_CRC64 -#define IS_CRC64(check_type) ((check_type) == XZ_CHECK_CRC64) -#else -#define IS_CRC64(check_type) false -#endif - -#ifdef XZ_USE_SHA256 -#define IS_SHA256(check_type) ((check_type) == XZ_CHECK_SHA256) -#else -#define IS_SHA256(check_type) false -#endif - -/* Hash used to validate the Index field */ -struct xz_dec_hash { - vli_type unpadded; - vli_type uncompressed; - uint32_t crc32; -}; - -struct xz_dec { - /* Position in dec_main() */ - enum { - SEQ_STREAM_HEADER, - SEQ_BLOCK_START, - SEQ_BLOCK_HEADER, - SEQ_BLOCK_UNCOMPRESS, - SEQ_BLOCK_PADDING, - SEQ_BLOCK_CHECK, - SEQ_INDEX, - SEQ_INDEX_PADDING, - SEQ_INDEX_CRC32, - SEQ_STREAM_FOOTER, - SEQ_STREAM_PADDING - } sequence; - - /* Position in variable-length integers and Check fields */ - uint32_t pos; - - /* Variable-length integer decoded by dec_vli() */ - vli_type vli; - - /* Saved in_pos and out_pos */ - size_t in_start; - size_t out_start; - -#ifdef XZ_USE_CRC64 - /* CRC32 or CRC64 value in Block or CRC32 value in Index */ - uint64_t crc; -#else - /* CRC32 value in Block or Index */ - uint32_t crc; -#endif - - /* Type of the integrity check calculated from uncompressed data */ - enum xz_check check_type; - - /* Operation mode */ - enum xz_mode mode; - - /* - * True if the next call to xz_dec_run() is allowed to return - * XZ_BUF_ERROR. - */ - bool allow_buf_error; - - /* Information stored in Block Header */ - struct { - /* - * Value stored in the Compressed Size field, or - * VLI_UNKNOWN if Compressed Size is not present. - */ - vli_type compressed; - - /* - * Value stored in the Uncompressed Size field, or - * VLI_UNKNOWN if Uncompressed Size is not present. - */ - vli_type uncompressed; - - /* Size of the Block Header field */ - uint32_t size; - } block_header; - - /* Information collected when decoding Blocks */ - struct { - /* Observed compressed size of the current Block */ - vli_type compressed; - - /* Observed uncompressed size of the current Block */ - vli_type uncompressed; - - /* Number of Blocks decoded so far */ - vli_type count; - - /* - * Hash calculated from the Block sizes. This is used to - * validate the Index field. - */ - struct xz_dec_hash hash; - } block; - - /* Variables needed when verifying the Index field */ - struct { - /* Position in dec_index() */ - enum { - SEQ_INDEX_COUNT, - SEQ_INDEX_UNPADDED, - SEQ_INDEX_UNCOMPRESSED - } sequence; - - /* Size of the Index in bytes */ - vli_type size; - - /* Number of Records (matches block.count in valid files) */ - vli_type count; - - /* - * Hash calculated from the Records (matches block.hash in - * valid files). - */ - struct xz_dec_hash hash; - } index; - - /* - * Temporary buffer needed to hold Stream Header, Block Header, - * and Stream Footer. The Block Header is the biggest (1 KiB) - * so we reserve space according to that. buf[] has to be aligned - * to a multiple of four bytes; the size_t variables before it - * should guarantee this. - */ - struct { - size_t pos; - size_t size; - uint8_t buf[1024]; - } temp; - - struct xz_dec_lzma2 *lzma2; - -#ifdef XZ_DEC_BCJ - struct xz_dec_bcj *bcj; - bool bcj_active; -#endif - -#ifdef XZ_USE_SHA256 - /* - * SHA-256 value in Block - * - * struct xz_sha256 is over a hundred bytes and it's only accessed - * from a few places. By putting the SHA-256 state near the end - * of struct xz_dec (somewhere after the "index" member) reduces - * code size at least on x86 and RISC-V. It's because the first bytes - * of the struct can be accessed with smaller instructions; the - * members that are accessed from many places should be at the top. - */ - struct xz_sha256 sha256; -#endif -}; - -#if defined(XZ_DEC_ANY_CHECK) || defined(XZ_USE_SHA256) -/* Sizes of the Check field with different Check IDs */ -static const uint8_t check_sizes[16] = {0, 4, 4, 4, 8, 8, 8, 16, - 16, 16, 32, 32, 32, 64, 64, 64}; -#endif - -/* - * Fill s->temp by copying data starting from b->in[b->in_pos]. Caller - * must have set s->temp.pos and s->temp.size to indicate how much data - * we are supposed to copy into s->temp.buf. Return true once s->temp.pos - * has reached s->temp.size. - */ -static bool fill_temp(struct xz_dec *s, struct xz_buf *b) { - size_t copy_size = - min_t(size_t, b->in_size - b->in_pos, s->temp.size - s->temp.pos); - - memcpy(s->temp.buf + s->temp.pos, b->in + b->in_pos, copy_size); - b->in_pos += copy_size; - s->temp.pos += copy_size; - - if (s->temp.pos == s->temp.size) { - s->temp.pos = 0; - return true; - } - - return false; -} - -/* Decode a variable-length integer (little-endian base-128 encoding) */ -static enum xz_ret dec_vli(struct xz_dec *s, const uint8_t *in, size_t *in_pos, - size_t in_size) { - uint8_t byte; - - if (s->pos == 0) - s->vli = 0; - - while (*in_pos < in_size) { - byte = in[*in_pos]; - ++*in_pos; - - s->vli |= (vli_type)(byte & 0x7F) << s->pos; - - if ((byte & 0x80) == 0) { - /* Don't allow non-minimal encodings. */ - if (byte == 0 && s->pos != 0) - return XZ_DATA_ERROR; - - s->pos = 0; - return XZ_STREAM_END; - } - - s->pos += 7; - if (s->pos == 7 * VLI_BYTES_MAX) - return XZ_DATA_ERROR; - } - - return XZ_OK; -} - -/* - * Decode the Compressed Data field from a Block. Update and validate - * the observed compressed and uncompressed sizes of the Block so that - * they don't exceed the values possibly stored in the Block Header - * (validation assumes that no integer overflow occurs, since vli_type - * is normally uint64_t). Update the CRC32 or CRC64 value if presence of - * the CRC32 or CRC64 field was indicated in Stream Header. - * - * Once the decoding is finished, validate that the observed sizes match - * the sizes possibly stored in the Block Header. Update the hash and - * Block count, which are later used to validate the Index field. - */ -static enum xz_ret dec_block(struct xz_dec *s, struct xz_buf *b) { - enum xz_ret ret; - - s->in_start = b->in_pos; - s->out_start = b->out_pos; - -#ifdef XZ_DEC_BCJ - if (s->bcj_active) - ret = xz_dec_bcj_run(s->bcj, s->lzma2, b); - else -#endif - ret = xz_dec_lzma2_run(s->lzma2, b); - - s->block.compressed += b->in_pos - s->in_start; - s->block.uncompressed += b->out_pos - s->out_start; - - /* - * There is no need to separately check for VLI_UNKNOWN, since - * the observed sizes are always smaller than VLI_UNKNOWN. - */ - if (s->block.compressed > s->block_header.compressed || - s->block.uncompressed > s->block_header.uncompressed) - return XZ_DATA_ERROR; - - if (s->check_type == XZ_CHECK_CRC32) - s->crc = xz_crc32(b->out + s->out_start, b->out_pos - s->out_start, s->crc); -#ifdef XZ_USE_CRC64 - else if (s->check_type == XZ_CHECK_CRC64) - s->crc = xz_crc64(b->out + s->out_start, b->out_pos - s->out_start, s->crc); -#endif -#ifdef XZ_USE_SHA256 - else if (s->check_type == XZ_CHECK_SHA256) - xz_sha256_update(b->out + s->out_start, b->out_pos - s->out_start, - &s->sha256); -#endif - - if (ret == XZ_STREAM_END) { - if (s->block_header.compressed != VLI_UNKNOWN && - s->block_header.compressed != s->block.compressed) - return XZ_DATA_ERROR; - - if (s->block_header.uncompressed != VLI_UNKNOWN && - s->block_header.uncompressed != s->block.uncompressed) - return XZ_DATA_ERROR; - - s->block.hash.unpadded += s->block_header.size + s->block.compressed; - -#if defined(XZ_DEC_ANY_CHECK) || defined(XZ_USE_SHA256) - s->block.hash.unpadded += check_sizes[s->check_type]; -#else - if (s->check_type == XZ_CHECK_CRC32) - s->block.hash.unpadded += 4; - else if (IS_CRC64(s->check_type)) - s->block.hash.unpadded += 8; -#endif - - s->block.hash.uncompressed += s->block.uncompressed; - s->block.hash.crc32 = xz_crc32((const uint8_t *)&s->block.hash, - sizeof(s->block.hash), s->block.hash.crc32); - - ++s->block.count; - } - - return ret; -} - -/* Update the Index size and the CRC32 value. */ -static void index_update(struct xz_dec *s, const struct xz_buf *b) { - size_t in_used = b->in_pos - s->in_start; - s->index.size += in_used; - s->crc = xz_crc32(b->in + s->in_start, in_used, s->crc); -} - -/* - * Decode the Number of Records, Unpadded Size, and Uncompressed Size - * fields from the Index field. That is, Index Padding and CRC32 are not - * decoded by this function. - * - * This can return XZ_OK (more input needed), XZ_STREAM_END (everything - * successfully decoded), or XZ_DATA_ERROR (input is corrupt). - */ -static enum xz_ret dec_index(struct xz_dec *s, struct xz_buf *b) { - enum xz_ret ret; - - do { - ret = dec_vli(s, b->in, &b->in_pos, b->in_size); - if (ret != XZ_STREAM_END) { - index_update(s, b); - return ret; - } - - switch (s->index.sequence) { - case SEQ_INDEX_COUNT: - s->index.count = s->vli; - - /* - * Validate that the Number of Records field - * indicates the same number of Records as - * there were Blocks in the Stream. - */ - if (s->index.count != s->block.count) - return XZ_DATA_ERROR; - - s->index.sequence = SEQ_INDEX_UNPADDED; - break; - - case SEQ_INDEX_UNPADDED: - s->index.hash.unpadded += s->vli; - s->index.sequence = SEQ_INDEX_UNCOMPRESSED; - break; - - case SEQ_INDEX_UNCOMPRESSED: - s->index.hash.uncompressed += s->vli; - s->index.hash.crc32 = - xz_crc32((const uint8_t *)&s->index.hash, sizeof(s->index.hash), - s->index.hash.crc32); - --s->index.count; - s->index.sequence = SEQ_INDEX_UNPADDED; - break; - } - } while (s->index.count > 0); - - return XZ_STREAM_END; -} - -/* - * Validate that the next four or eight input bytes match the value - * of s->crc. s->pos must be zero when starting to validate the first byte. - * The "bits" argument allows using the same code for both CRC32 and CRC64. - */ -static enum xz_ret crc_validate(struct xz_dec *s, struct xz_buf *b, - uint32_t bits) { - do { - if (b->in_pos == b->in_size) - return XZ_OK; - - if (((s->crc >> s->pos) & 0xFF) != b->in[b->in_pos++]) - return XZ_DATA_ERROR; - - s->pos += 8; - - } while (s->pos < bits); - - s->crc = 0; - s->pos = 0; - - return XZ_STREAM_END; -} - -#ifdef XZ_DEC_ANY_CHECK -/* - * Skip over the Check field when the Check ID is not supported. - * Returns true once the whole Check field has been skipped over. - */ -static bool check_skip(struct xz_dec *s, struct xz_buf *b) { - while (s->pos < check_sizes[s->check_type]) { - if (b->in_pos == b->in_size) - return false; - - ++b->in_pos; - ++s->pos; - } - - s->pos = 0; - - return true; -} -#endif - -/* Decode the Stream Header field (the first 12 bytes of the .xz Stream). */ -static enum xz_ret dec_stream_header(struct xz_dec *s) { - if (!memeq(s->temp.buf, HEADER_MAGIC, HEADER_MAGIC_SIZE)) - return XZ_FORMAT_ERROR; - - if (xz_crc32(s->temp.buf + HEADER_MAGIC_SIZE, 2, 0) != - get_le32(s->temp.buf + HEADER_MAGIC_SIZE + 2)) - return XZ_DATA_ERROR; - - if (s->temp.buf[HEADER_MAGIC_SIZE] != 0) - return XZ_OPTIONS_ERROR; - - /* - * Of integrity checks, we support none (Check ID = 0), - * CRC32 (Check ID = 1), and optionally CRC64 (Check ID = 4). - * However, if XZ_DEC_ANY_CHECK is defined, we will accept other - * check types too, but then the check won't be verified and - * a warning (XZ_UNSUPPORTED_CHECK) will be given. - */ - if (s->temp.buf[HEADER_MAGIC_SIZE + 1] > XZ_CHECK_MAX) - return XZ_OPTIONS_ERROR; - - s->check_type = s->temp.buf[HEADER_MAGIC_SIZE + 1]; - - if (s->check_type > XZ_CHECK_CRC32 && !IS_CRC64(s->check_type) && - !IS_SHA256(s->check_type)) { -#ifdef XZ_DEC_ANY_CHECK - return XZ_UNSUPPORTED_CHECK; -#else - return XZ_OPTIONS_ERROR; -#endif - } - - return XZ_OK; -} - -/* Decode the Stream Footer field (the last 12 bytes of the .xz Stream) */ -static enum xz_ret dec_stream_footer(struct xz_dec *s) { - if (!memeq(s->temp.buf + 10, FOOTER_MAGIC, FOOTER_MAGIC_SIZE)) - return XZ_DATA_ERROR; - - if (xz_crc32(s->temp.buf + 4, 6, 0) != get_le32(s->temp.buf)) - return XZ_DATA_ERROR; - - /* - * Validate Backward Size. Note that we never added the size of the - * Index CRC32 field to s->index.size, thus we use s->index.size / 4 - * instead of s->index.size / 4 - 1. - */ - if ((s->index.size >> 2) != get_le32(s->temp.buf + 4)) - return XZ_DATA_ERROR; - - if (s->temp.buf[8] != 0 || s->temp.buf[9] != s->check_type) - return XZ_DATA_ERROR; - - /* - * Use XZ_STREAM_END instead of XZ_OK to be more convenient - * for the caller. - */ - return XZ_STREAM_END; -} - -/* Decode the Block Header and initialize the filter chain. */ -static enum xz_ret dec_block_header(struct xz_dec *s) { - enum xz_ret ret; - - /* - * Validate the CRC32. We know that the temp buffer is at least - * eight bytes so this is safe. - */ - s->temp.size -= 4; - if (xz_crc32(s->temp.buf, s->temp.size, 0) != - get_le32(s->temp.buf + s->temp.size)) - return XZ_DATA_ERROR; - - s->temp.pos = 2; - - /* - * Catch unsupported Block Flags. We support only one or two filters - * in the chain, so we catch that with the same test. - */ -#ifdef XZ_DEC_BCJ - if (s->temp.buf[1] & 0x3E) -#else - if (s->temp.buf[1] & 0x3F) -#endif - return XZ_OPTIONS_ERROR; - - /* Compressed Size */ - if (s->temp.buf[1] & 0x40) { - if (dec_vli(s, s->temp.buf, &s->temp.pos, s->temp.size) != XZ_STREAM_END) - return XZ_DATA_ERROR; - - s->block_header.compressed = s->vli; - } else { - s->block_header.compressed = VLI_UNKNOWN; - } - - /* Uncompressed Size */ - if (s->temp.buf[1] & 0x80) { - if (dec_vli(s, s->temp.buf, &s->temp.pos, s->temp.size) != XZ_STREAM_END) - return XZ_DATA_ERROR; - - s->block_header.uncompressed = s->vli; - } else { - s->block_header.uncompressed = VLI_UNKNOWN; - } - -#ifdef XZ_DEC_BCJ - /* If there are two filters, the first one must be a BCJ filter. */ - s->bcj_active = s->temp.buf[1] & 0x01; - if (s->bcj_active) { - if (s->temp.size - s->temp.pos < 2) - return XZ_OPTIONS_ERROR; - - ret = xz_dec_bcj_reset(s->bcj, s->temp.buf[s->temp.pos++]); - if (ret != XZ_OK) - return ret; - - /* - * We don't support custom start offset, - * so Size of Properties must be zero. - */ - if (s->temp.buf[s->temp.pos++] != 0x00) - return XZ_OPTIONS_ERROR; - } -#endif - - /* Valid Filter Flags always take at least two bytes. */ - if (s->temp.size - s->temp.pos < 2) - return XZ_DATA_ERROR; - - /* Filter ID = LZMA2 */ - if (s->temp.buf[s->temp.pos++] != 0x21) - return XZ_OPTIONS_ERROR; - - /* Size of Properties = 1-byte Filter Properties */ - if (s->temp.buf[s->temp.pos++] != 0x01) - return XZ_OPTIONS_ERROR; - - /* Filter Properties contains LZMA2 dictionary size. */ - if (s->temp.size - s->temp.pos < 1) - return XZ_DATA_ERROR; - - ret = xz_dec_lzma2_reset(s->lzma2, s->temp.buf[s->temp.pos++]); - if (ret != XZ_OK) - return ret; - - /* The rest must be Header Padding. */ - while (s->temp.pos < s->temp.size) - if (s->temp.buf[s->temp.pos++] != 0x00) - return XZ_OPTIONS_ERROR; - - s->temp.pos = 0; - s->block.compressed = 0; - s->block.uncompressed = 0; - - return XZ_OK; -} - -static enum xz_ret dec_main(struct xz_dec *s, struct xz_buf *b) { - enum xz_ret ret; - - /* - * Store the start position for the case when we are in the middle - * of the Index field. - */ - s->in_start = b->in_pos; - - while (true) { - switch (s->sequence) { - case SEQ_STREAM_HEADER: - /* - * Stream Header is copied to s->temp, and then - * decoded from there. This way if the caller - * gives us only little input at a time, we can - * still keep the Stream Header decoding code - * simple. Similar approach is used in many places - * in this file. - */ - if (!fill_temp(s, b)) - return XZ_OK; - - /* - * If dec_stream_header() returns - * XZ_UNSUPPORTED_CHECK, it is still possible - * to continue decoding if working in multi-call - * mode. Thus, update s->sequence before calling - * dec_stream_header(). - */ - s->sequence = SEQ_BLOCK_START; - - ret = dec_stream_header(s); - if (ret != XZ_OK) - return ret; - - fallthrough; - - case SEQ_BLOCK_START: - /* We need one byte of input to continue. */ - if (b->in_pos == b->in_size) - return XZ_OK; - - /* See if this is the beginning of the Index field. */ - if (b->in[b->in_pos] == 0) { - s->in_start = b->in_pos++; - s->sequence = SEQ_INDEX; - break; - } - - /* - * Calculate the size of the Block Header and - * prepare to decode it. - */ - s->block_header.size = ((uint32_t)b->in[b->in_pos] + 1) * 4; - - s->temp.size = s->block_header.size; - s->temp.pos = 0; - s->sequence = SEQ_BLOCK_HEADER; - - fallthrough; - - case SEQ_BLOCK_HEADER: - if (!fill_temp(s, b)) - return XZ_OK; - - ret = dec_block_header(s); - if (ret != XZ_OK) - return ret; - -#ifdef XZ_USE_SHA256 - if (s->check_type == XZ_CHECK_SHA256) - xz_sha256_reset(&s->sha256); -#endif - - s->sequence = SEQ_BLOCK_UNCOMPRESS; - - fallthrough; - - case SEQ_BLOCK_UNCOMPRESS: - ret = dec_block(s, b); - if (ret != XZ_STREAM_END) - return ret; - - s->sequence = SEQ_BLOCK_PADDING; - - fallthrough; - - case SEQ_BLOCK_PADDING: - /* - * Size of Compressed Data + Block Padding - * must be a multiple of four. We don't need - * s->block.compressed for anything else - * anymore, so we use it here to test the size - * of the Block Padding field. - */ - while (s->block.compressed & 3) { - if (b->in_pos == b->in_size) - return XZ_OK; - - if (b->in[b->in_pos++] != 0) - return XZ_DATA_ERROR; - - ++s->block.compressed; - } - - s->sequence = SEQ_BLOCK_CHECK; - - fallthrough; - - case SEQ_BLOCK_CHECK: - if (s->check_type == XZ_CHECK_CRC32) { - ret = crc_validate(s, b, 32); - if (ret != XZ_STREAM_END) - return ret; - } else if (IS_CRC64(s->check_type)) { - ret = crc_validate(s, b, 64); - if (ret != XZ_STREAM_END) - return ret; - } -#ifdef XZ_USE_SHA256 - else if (s->check_type == XZ_CHECK_SHA256) { - s->temp.size = 32; - if (!fill_temp(s, b)) - return XZ_OK; - - if (!xz_sha256_validate(s->temp.buf, &s->sha256)) - return XZ_DATA_ERROR; - - s->pos = 0; - } -#endif -#ifdef XZ_DEC_ANY_CHECK - else if (!check_skip(s, b)) { - return XZ_OK; - } -#endif - - s->sequence = SEQ_BLOCK_START; - break; - - case SEQ_INDEX: - ret = dec_index(s, b); - if (ret != XZ_STREAM_END) - return ret; - - s->sequence = SEQ_INDEX_PADDING; - - fallthrough; - - case SEQ_INDEX_PADDING: - while ((s->index.size + (b->in_pos - s->in_start)) & 3) { - if (b->in_pos == b->in_size) { - index_update(s, b); - return XZ_OK; - } - - if (b->in[b->in_pos++] != 0) - return XZ_DATA_ERROR; - } - - /* Finish the CRC32 value and Index size. */ - index_update(s, b); - - /* Compare the hashes to validate the Index field. */ - if (!memeq(&s->block.hash, &s->index.hash, sizeof(s->block.hash))) - return XZ_DATA_ERROR; - - s->sequence = SEQ_INDEX_CRC32; - - fallthrough; - - case SEQ_INDEX_CRC32: - ret = crc_validate(s, b, 32); - if (ret != XZ_STREAM_END) - return ret; - - s->temp.size = STREAM_HEADER_SIZE; - s->sequence = SEQ_STREAM_FOOTER; - - fallthrough; - - case SEQ_STREAM_FOOTER: - if (!fill_temp(s, b)) - return XZ_OK; - - return dec_stream_footer(s); - - case SEQ_STREAM_PADDING: - /* Never reached, only silencing a warning */ - break; - } - } - - /* Never reached */ -} - -/* - * xz_dec_run() is a wrapper for dec_main() to handle some special cases in - * multi-call and single-call decoding. - * - * In multi-call mode, we must return XZ_BUF_ERROR when it seems clear that we - * are not going to make any progress anymore. This is to prevent the caller - * from calling us infinitely when the input file is truncated or otherwise - * corrupt. Since zlib-style API allows that the caller fills the input buffer - * only when the decoder doesn't produce any new output, we have to be careful - * to avoid returning XZ_BUF_ERROR too easily: XZ_BUF_ERROR is returned only - * after the second consecutive call to xz_dec_run() that makes no progress. - * - * In single-call mode, if we couldn't decode everything and no error - * occurred, either the input is truncated or the output buffer is too small. - * Since we know that the last input byte never produces any output, we know - * that if all the input was consumed and decoding wasn't finished, the file - * must be corrupt. Otherwise the output buffer has to be too small or the - * file is corrupt in a way that decoding it produces too big output. - * - * If single-call decoding fails, we reset b->in_pos and b->out_pos back to - * their original values. This is because with some filter chains there won't - * be any valid uncompressed data in the output buffer unless the decoding - * actually succeeds (that's the price to pay of using the output buffer as - * the workspace). - */ -XZ_EXTERN enum xz_ret xz_dec_run(struct xz_dec *s, struct xz_buf *b) { - size_t in_start; - size_t out_start; - enum xz_ret ret; - - if (DEC_IS_SINGLE(s->mode)) - xz_dec_reset(s); - - in_start = b->in_pos; - out_start = b->out_pos; - ret = dec_main(s, b); - - if (DEC_IS_SINGLE(s->mode)) { - if (ret == XZ_OK) - ret = b->in_pos == b->in_size ? XZ_DATA_ERROR : XZ_BUF_ERROR; - - if (ret != XZ_STREAM_END) { - b->in_pos = in_start; - b->out_pos = out_start; - } - - } else if (ret == XZ_OK && in_start == b->in_pos && out_start == b->out_pos) { - if (s->allow_buf_error) - ret = XZ_BUF_ERROR; - - s->allow_buf_error = true; - } else { - s->allow_buf_error = false; - } - - return ret; -} - -#ifdef XZ_DEC_CONCATENATED -XZ_EXTERN enum xz_ret xz_dec_catrun(struct xz_dec *s, struct xz_buf *b, - int finish) { - enum xz_ret ret; - - if (DEC_IS_SINGLE(s->mode)) { - xz_dec_reset(s); - finish = true; - } - - while (true) { - if (s->sequence == SEQ_STREAM_PADDING) { - /* - * Skip Stream Padding. Its size must be a multiple - * of four bytes which is tracked with s->pos. - */ - while (true) { - if (b->in_pos == b->in_size) { - /* - * Note that if we are repeatedly - * given no input and finish is false, - * we will keep returning XZ_OK even - * though no progress is being made. - * The lack of XZ_BUF_ERROR support - * isn't a problem here because a - * reasonable caller will eventually - * provide more input or set finish - * to true. - */ - if (!finish) - return XZ_OK; - - if (s->pos != 0) - return XZ_DATA_ERROR; - - return XZ_STREAM_END; - } - - if (b->in[b->in_pos] != 0x00) { - if (s->pos != 0) - return XZ_DATA_ERROR; - - break; - } - - ++b->in_pos; - s->pos = (s->pos + 1) & 3; - } - - /* - * More input remains. It should be a new Stream. - * - * In single-call mode xz_dec_run() will always call - * xz_dec_reset(). Thus, we need to do it here only - * in multi-call mode. - */ - if (DEC_IS_MULTI(s->mode)) - xz_dec_reset(s); - } - - ret = xz_dec_run(s, b); - - if (ret != XZ_STREAM_END) - break; - - s->sequence = SEQ_STREAM_PADDING; - } - - return ret; -} -#endif - -XZ_EXTERN struct xz_dec *xz_dec_init(enum xz_mode mode, uint32_t dict_max) { - struct xz_dec *s = kmalloc(sizeof(*s), GFP_KERNEL); - if (s == NULL) - return NULL; - - s->mode = mode; - -#ifdef XZ_DEC_BCJ - s->bcj = xz_dec_bcj_create(DEC_IS_SINGLE(mode)); - if (s->bcj == NULL) - goto error_bcj; -#endif - - s->lzma2 = xz_dec_lzma2_create(mode, dict_max); - if (s->lzma2 == NULL) - goto error_lzma2; - - xz_dec_reset(s); - return s; - -error_lzma2: -#ifdef XZ_DEC_BCJ - xz_dec_bcj_end(s->bcj); -error_bcj: -#endif - kfree(s); - return NULL; -} - -XZ_EXTERN void xz_dec_reset(struct xz_dec *s) { - s->sequence = SEQ_STREAM_HEADER; - s->allow_buf_error = false; - s->pos = 0; - s->crc = 0; - memzero(&s->block, sizeof(s->block)); - memzero(&s->index, sizeof(s->index)); - s->temp.pos = 0; - s->temp.size = STREAM_HEADER_SIZE; -} - -XZ_EXTERN void xz_dec_end(struct xz_dec *s) { - if (s != NULL) { - xz_dec_lzma2_end(s->lzma2); -#ifdef XZ_DEC_BCJ - xz_dec_bcj_end(s->bcj); -#endif - kfree(s); - } -} diff --git a/app/src/main/cpp/xz/xz_embedded/xz_lzma2.h b/app/src/main/cpp/xz/xz_embedded/xz_lzma2.h deleted file mode 100644 index 504f4722c..000000000 --- a/app/src/main/cpp/xz/xz_embedded/xz_lzma2.h +++ /dev/null @@ -1,197 +0,0 @@ -/* SPDX-License-Identifier: 0BSD */ - -/* - * LZMA2 definitions - * - * Authors: Lasse Collin - * Igor Pavlov - */ - -#ifndef XZ_LZMA2_H -#define XZ_LZMA2_H - -/* Range coder constants */ -#define RC_SHIFT_BITS 8 -#define RC_TOP_BITS 24 -#define RC_TOP_VALUE (1 << RC_TOP_BITS) -#define RC_BIT_MODEL_TOTAL_BITS 11 -#define RC_BIT_MODEL_TOTAL (1 << RC_BIT_MODEL_TOTAL_BITS) -#define RC_MOVE_BITS 5 - -/* - * Maximum number of position states. A position state is the lowest pb - * number of bits of the current uncompressed offset. In some places there - * are different sets of probabilities for different position states. - */ -#define POS_STATES_MAX (1 << 4) - -/* - * This enum is used to track which LZMA symbols have occurred most recently - * and in which order. This information is used to predict the next symbol. - * - * Symbols: - * - Literal: One 8-bit byte - * - Match: Repeat a chunk of data at some distance - * - Long repeat: Multi-byte match at a recently seen distance - * - Short repeat: One-byte repeat at a recently seen distance - * - * The symbol names are in from STATE_oldest_older_previous. REP means - * either short or long repeated match, and NONLIT means any non-literal. - */ -enum lzma_state { - STATE_LIT_LIT, - STATE_MATCH_LIT_LIT, - STATE_REP_LIT_LIT, - STATE_SHORTREP_LIT_LIT, - STATE_MATCH_LIT, - STATE_REP_LIT, - STATE_SHORTREP_LIT, - STATE_LIT_MATCH, - STATE_LIT_LONGREP, - STATE_LIT_SHORTREP, - STATE_NONLIT_MATCH, - STATE_NONLIT_REP -}; - -/* Total number of states */ -#define STATES 12 - -/* The lowest 7 states indicate that the previous state was a literal. */ -#define LIT_STATES 7 - -/* Indicate that the latest symbol was a literal. */ -static inline void lzma_state_literal(enum lzma_state *state) { - if (*state <= STATE_SHORTREP_LIT_LIT) - *state = STATE_LIT_LIT; - else if (*state <= STATE_LIT_SHORTREP) - *state -= 3; - else - *state -= 6; -} - -/* Indicate that the latest symbol was a match. */ -static inline void lzma_state_match(enum lzma_state *state) { - *state = *state < LIT_STATES ? STATE_LIT_MATCH : STATE_NONLIT_MATCH; -} - -/* Indicate that the latest state was a long repeated match. */ -static inline void lzma_state_long_rep(enum lzma_state *state) { - *state = *state < LIT_STATES ? STATE_LIT_LONGREP : STATE_NONLIT_REP; -} - -/* Indicate that the latest symbol was a short match. */ -static inline void lzma_state_short_rep(enum lzma_state *state) { - *state = *state < LIT_STATES ? STATE_LIT_SHORTREP : STATE_NONLIT_REP; -} - -/* Test if the previous symbol was a literal. */ -static inline bool lzma_state_is_literal(enum lzma_state state) { - return state < LIT_STATES; -} - -/* Each literal coder is divided in three sections: - * - 0x001-0x0FF: Without match byte - * - 0x101-0x1FF: With match byte; match bit is 0 - * - 0x201-0x2FF: With match byte; match bit is 1 - * - * Match byte is used when the previous LZMA symbol was something else than - * a literal (that is, it was some kind of match). - */ -#define LITERAL_CODER_SIZE 0x300 - -/* Maximum number of literal coders */ -#define LITERAL_CODERS_MAX (1 << 4) - -/* Minimum length of a match is two bytes. */ -#define MATCH_LEN_MIN 2 - -/* Match length is encoded with 4, 5, or 10 bits. - * - * Length Bits - * 2-9 4 = Choice=0 + 3 bits - * 10-17 5 = Choice=1 + Choice2=0 + 3 bits - * 18-273 10 = Choice=1 + Choice2=1 + 8 bits - */ -#define LEN_LOW_BITS 3 -#define LEN_LOW_SYMBOLS (1 << LEN_LOW_BITS) -#define LEN_MID_BITS 3 -#define LEN_MID_SYMBOLS (1 << LEN_MID_BITS) -#define LEN_HIGH_BITS 8 -#define LEN_HIGH_SYMBOLS (1 << LEN_HIGH_BITS) -#define LEN_SYMBOLS (LEN_LOW_SYMBOLS + LEN_MID_SYMBOLS + LEN_HIGH_SYMBOLS) - -/* - * Maximum length of a match is 273 which is a result of the encoding - * described above. - */ -#define MATCH_LEN_MAX (MATCH_LEN_MIN + LEN_SYMBOLS - 1) - -/* - * Different sets of probabilities are used for match distances that have - * very short match length: Lengths of 2, 3, and 4 bytes have a separate - * set of probabilities for each length. The matches with longer length - * use a shared set of probabilities. - */ -#define DIST_STATES 4 - -/* - * Get the index of the appropriate probability array for decoding - * the distance slot. - */ -static inline uint32_t lzma_get_dist_state(uint32_t len) { - return len < DIST_STATES + MATCH_LEN_MIN ? len - MATCH_LEN_MIN - : DIST_STATES - 1; -} - -/* - * The highest two bits of a 32-bit match distance are encoded using six bits. - * This six-bit value is called a distance slot. This way encoding a 32-bit - * value takes 6-36 bits, larger values taking more bits. - */ -#define DIST_SLOT_BITS 6 -#define DIST_SLOTS (1 << DIST_SLOT_BITS) - -/* Match distances up to 127 are fully encoded using probabilities. Since - * the highest two bits (distance slot) are always encoded using six bits, - * the distances 0-3 don't need any additional bits to encode, since the - * distance slot itself is the same as the actual distance. DIST_MODEL_START - * indicates the first distance slot where at least one additional bit is - * needed. - */ -#define DIST_MODEL_START 4 - -/* - * Match distances greater than 127 are encoded in three pieces: - * - distance slot: the highest two bits - * - direct bits: 2-26 bits below the highest two bits - * - alignment bits: four lowest bits - * - * Direct bits don't use any probabilities. - * - * The distance slot value of 14 is for distances 128-191. - */ -#define DIST_MODEL_END 14 - -/* Distance slots that indicate a distance <= 127. */ -#define FULL_DISTANCES_BITS (DIST_MODEL_END / 2) -#define FULL_DISTANCES (1 << FULL_DISTANCES_BITS) - -/* - * For match distances greater than 127, only the highest two bits and the - * lowest four bits (alignment) is encoded using probabilities. - */ -#define ALIGN_BITS 4 -#define ALIGN_SIZE (1 << ALIGN_BITS) -#define ALIGN_MASK (ALIGN_SIZE - 1) - -/* Total number of all probability variables */ -#define PROBS_TOTAL (1846 + LITERAL_CODERS_MAX * LITERAL_CODER_SIZE) - -/* - * LZMA remembers the four most recent match distances. Reusing these - * distances tends to take less space than re-encoding the actual - * distance value. - */ -#define REPS 4 - -#endif diff --git a/app/src/main/cpp/xz/xz_embedded/xz_private.h b/app/src/main/cpp/xz/xz_embedded/xz_private.h deleted file mode 100644 index 52e05c329..000000000 --- a/app/src/main/cpp/xz/xz_embedded/xz_private.h +++ /dev/null @@ -1,186 +0,0 @@ -/* SPDX-License-Identifier: 0BSD */ - -/* - * Private includes and definitions - * - * Author: Lasse Collin - */ - -#ifndef XZ_PRIVATE_H -#define XZ_PRIVATE_H - -#ifdef __KERNEL__ -#include -#include -#include -/* XZ_PREBOOT may be defined only via decompress_unxz.c. */ -#ifndef XZ_PREBOOT -#include -#include -#include -#ifdef CONFIG_XZ_DEC_X86 -#define XZ_DEC_X86 -#endif -#ifdef CONFIG_XZ_DEC_POWERPC -#define XZ_DEC_POWERPC -#endif -#ifdef CONFIG_XZ_DEC_IA64 -#define XZ_DEC_IA64 -#endif -#ifdef CONFIG_XZ_DEC_ARM -#define XZ_DEC_ARM -#endif -#ifdef CONFIG_XZ_DEC_ARMTHUMB -#define XZ_DEC_ARMTHUMB -#endif -#ifdef CONFIG_XZ_DEC_SPARC -#define XZ_DEC_SPARC -#endif -#ifdef CONFIG_XZ_DEC_ARM64 -#define XZ_DEC_ARM64 -#endif -#ifdef CONFIG_XZ_DEC_RISCV -#define XZ_DEC_RISCV -#endif -#ifdef CONFIG_XZ_DEC_MICROLZMA -#define XZ_DEC_MICROLZMA -#endif -#define memeq(a, b, size) (memcmp(a, b, size) == 0) -#define memzero(buf, size) memset(buf, 0, size) -#endif -#define get_le32(p) le32_to_cpup((const uint32_t *)(p)) -#else -/* - * For userspace builds, use a separate header to define the required - * macros and functions. This makes it easier to adapt the code into - * different environments and avoids clutter in the Linux kernel tree. - */ -#include "xz_config.h" -#endif - -/* If no specific decoding mode is requested, enable support for all modes. */ -#if !defined(XZ_DEC_SINGLE) && !defined(XZ_DEC_PREALLOC) && \ - !defined(XZ_DEC_DYNALLOC) -#define XZ_DEC_SINGLE -#define XZ_DEC_PREALLOC -#define XZ_DEC_DYNALLOC -#endif - -/* - * The DEC_IS_foo(mode) macros are used in "if" statements. If only some - * of the supported modes are enabled, these macros will evaluate to true or - * false at compile time and thus allow the compiler to omit unneeded code. - */ -#ifdef XZ_DEC_SINGLE -#define DEC_IS_SINGLE(mode) ((mode) == XZ_SINGLE) -#else -#define DEC_IS_SINGLE(mode) (false) -#endif - -#ifdef XZ_DEC_PREALLOC -#define DEC_IS_PREALLOC(mode) ((mode) == XZ_PREALLOC) -#else -#define DEC_IS_PREALLOC(mode) (false) -#endif - -#ifdef XZ_DEC_DYNALLOC -#define DEC_IS_DYNALLOC(mode) ((mode) == XZ_DYNALLOC) -#else -#define DEC_IS_DYNALLOC(mode) (false) -#endif - -#if !defined(XZ_DEC_SINGLE) -#define DEC_IS_MULTI(mode) (true) -#elif defined(XZ_DEC_PREALLOC) || defined(XZ_DEC_DYNALLOC) -#define DEC_IS_MULTI(mode) ((mode) != XZ_SINGLE) -#else -#define DEC_IS_MULTI(mode) (false) -#endif - -/* - * If any of the BCJ filter decoders are wanted, define XZ_DEC_BCJ. - * XZ_DEC_BCJ is used to enable generic support for BCJ decoders. - */ -#ifndef XZ_DEC_BCJ -#if defined(XZ_DEC_X86) || defined(XZ_DEC_POWERPC) || defined(XZ_DEC_IA64) || \ - defined(XZ_DEC_ARM) || defined(XZ_DEC_ARMTHUMB) || \ - defined(XZ_DEC_SPARC) || defined(XZ_DEC_ARM64) || defined(XZ_DEC_RISCV) -#define XZ_DEC_BCJ -#endif -#endif - -struct xz_sha256 { - /* Buffered input data */ - uint8_t data[64]; - - /* Internal state and the final hash value */ - uint32_t state[8]; - - /* Size of the input data */ - uint64_t size; -}; - -/* Reset the SHA-256 state to prepare for a new calculation. */ -XZ_EXTERN void xz_sha256_reset(struct xz_sha256 *s); - -/* Update the SHA-256 state with new data. */ -XZ_EXTERN void xz_sha256_update(const uint8_t *buf, size_t size, - struct xz_sha256 *s); - -/* - * Finish the SHA-256 calculation. Compare the result with the first 32 bytes - * from buf. Return true if the values are equal and false if they aren't. - */ -XZ_EXTERN bool xz_sha256_validate(const uint8_t *buf, struct xz_sha256 *s); - -/* - * Allocate memory for LZMA2 decoder. xz_dec_lzma2_reset() must be used - * before calling xz_dec_lzma2_run(). - */ -XZ_EXTERN struct xz_dec_lzma2 *xz_dec_lzma2_create(enum xz_mode mode, - uint32_t dict_max); - -/* - * Decode the LZMA2 properties (one byte) and reset the decoder. Return - * XZ_OK on success, XZ_MEMLIMIT_ERROR if the preallocated dictionary is not - * big enough, and XZ_OPTIONS_ERROR if props indicates something that this - * decoder doesn't support. - */ -XZ_EXTERN enum xz_ret xz_dec_lzma2_reset(struct xz_dec_lzma2 *s, uint8_t props); - -/* Decode raw LZMA2 stream from b->in to b->out. */ -XZ_EXTERN enum xz_ret xz_dec_lzma2_run(struct xz_dec_lzma2 *s, - struct xz_buf *b); - -/* Free the memory allocated for the LZMA2 decoder. */ -XZ_EXTERN void xz_dec_lzma2_end(struct xz_dec_lzma2 *s); - -#ifdef XZ_DEC_BCJ -/* - * Allocate memory for BCJ decoders. xz_dec_bcj_reset() must be used before - * calling xz_dec_bcj_run(). - */ -XZ_EXTERN struct xz_dec_bcj *xz_dec_bcj_create(bool single_call); - -/* - * Decode the Filter ID of a BCJ filter. This implementation doesn't - * support custom start offsets, so no decoding of Filter Properties - * is needed. Returns XZ_OK if the given Filter ID is supported. - * Otherwise XZ_OPTIONS_ERROR is returned. - */ -XZ_EXTERN enum xz_ret xz_dec_bcj_reset(struct xz_dec_bcj *s, uint8_t id); - -/* - * Decode raw BCJ + LZMA2 stream. This must be used only if there actually is - * a BCJ filter in the chain. If the chain has only LZMA2, xz_dec_lzma2_run() - * must be called directly. - */ -XZ_EXTERN enum xz_ret xz_dec_bcj_run(struct xz_dec_bcj *s, - struct xz_dec_lzma2 *lzma2, - struct xz_buf *b); - -/* Free the memory allocated for the BCJ filters. */ -#define xz_dec_bcj_end(s) kfree(s) -#endif - -#endif diff --git a/app/src/main/cpp/xz/xz_embedded/xz_stream.h b/app/src/main/cpp/xz/xz_embedded/xz_stream.h deleted file mode 100644 index 3b2a3014a..000000000 --- a/app/src/main/cpp/xz/xz_embedded/xz_stream.h +++ /dev/null @@ -1,60 +0,0 @@ -/* SPDX-License-Identifier: 0BSD */ - -/* - * Definitions for handling the .xz file format - * - * Author: Lasse Collin - */ - -#ifndef XZ_STREAM_H -#define XZ_STREAM_H - -#if defined(__KERNEL__) && !XZ_INTERNAL_CRC32 -#include -#undef crc32 -#define xz_crc32(buf, size, crc) (~crc32_le(~(uint32_t)(crc), buf, size)) -#endif - -/* - * See the .xz file format specification at - * https://tukaani.org/xz/xz-file-format.txt - * to understand the container format. - */ - -#define STREAM_HEADER_SIZE 12 - -#define HEADER_MAGIC "\3757zXZ" -#define HEADER_MAGIC_SIZE 6 - -#define FOOTER_MAGIC "YZ" -#define FOOTER_MAGIC_SIZE 2 - -/* - * Variable-length integer can hold a 63-bit unsigned integer or a special - * value indicating that the value is unknown. - * - * Experimental: vli_type can be defined to uint32_t to save a few bytes - * in code size (no effect on speed). Doing so limits the uncompressed and - * compressed size of the file to less than 256 MiB and may also weaken - * error detection slightly. - */ -typedef uint64_t vli_type; - -#define VLI_MAX ((vli_type) - 1 / 2) -#define VLI_UNKNOWN ((vli_type) - 1) - -/* Maximum encoded size of a VLI */ -#define VLI_BYTES_MAX (sizeof(vli_type) * 8 / 7) - -/* Integrity Check types */ -enum xz_check { - XZ_CHECK_NONE = 0, - XZ_CHECK_CRC32 = 1, - XZ_CHECK_CRC64 = 4, - XZ_CHECK_SHA256 = 10 -}; - -/* Maximum possible Check ID */ -#define XZ_CHECK_MAX 15 - -#endif diff --git a/app/src/main/cpp/xz/xz_embedded/xzminidec.c b/app/src/main/cpp/xz/xz_embedded/xzminidec.c deleted file mode 100644 index 7b896da2c..000000000 --- a/app/src/main/cpp/xz/xz_embedded/xzminidec.c +++ /dev/null @@ -1,162 +0,0 @@ -// SPDX-License-Identifier: 0BSD - -/* - * Simple XZ decoder command line tool - * - * Author: Lasse Collin - */ - -/* - * This is a very limited .xz decoder. Only LZMA2 and the BCJ filters - * are supported, and the BCJ filters cannot use Filter Properties. - * SHA256 is not supported as an integrity check. The LZMA2 dictionary - * sizes can be at most 64 MiB, but this can be modified by changing - * DICT_SIZE_MAX. - * - * See xzdec from XZ Utils if a few KiB bigger tool is not a problem. - */ - -#include "xz.h" -#include -#include -#include - -#ifdef _WIN32 -#include -#include -#endif - -#ifndef DICT_SIZE_MAX -#define DICT_SIZE_MAX (64U << 20) -#endif - -static uint8_t in[BUFSIZ]; -static uint8_t out[BUFSIZ]; - -int main(int argc, char **argv) { - struct xz_buf b; - struct xz_dec *s; - enum xz_ret ret; - const char *msg; - -#ifdef _WIN32 - _setmode(_fileno(stdin), _O_BINARY); - _setmode(_fileno(stdout), _O_BINARY); -#endif - - if (argc >= 2 && strcmp(argv[1], "--help") == 0) { - fputs("Uncompress a .xz file from stdin to stdout.\n" - "Arguments other than `--help' are ignored.\n", - stdout); - return 0; - } - - xz_crc32_init(); -#ifdef XZ_USE_CRC64 - xz_crc64_init(); -#endif - - /* - * Support up to 64 MiB dictionary. The actually needed memory - * is allocated once the headers have been parsed. - */ - s = xz_dec_init(XZ_DYNALLOC, DICT_SIZE_MAX); - if (s == NULL) { - msg = "Memory allocation failed\n"; - goto error; - } - - b.in = in; - b.in_pos = 0; - b.in_size = 0; - b.out = out; - b.out_pos = 0; - b.out_size = BUFSIZ; - - while (true) { - if (b.in_pos == b.in_size) { - b.in_size = fread(in, 1, sizeof(in), stdin); - - if (ferror(stdin)) { - msg = "Read error\n"; - goto error; - } - - b.in_pos = 0; - } - - /* - * There are a few ways to set the "finish" (the third) - * argument. We could use feof(stdin) but testing in_size - * is fine too and may also work in applications that don't - * use FILEs. - */ - ret = xz_dec_catrun(s, &b, b.in_size == 0); - - if (b.out_pos == sizeof(out)) { - if (fwrite(out, 1, b.out_pos, stdout) != b.out_pos) { - msg = "Write error\n"; - goto error; - } - - b.out_pos = 0; - } - - if (ret == XZ_OK) - continue; - -#ifdef XZ_DEC_ANY_CHECK - if (ret == XZ_UNSUPPORTED_CHECK) { - fputs(argv[0], stderr); - fputs(": ", stderr); - fputs("Unsupported check; not verifying " - "file integrity\n", - stderr); - continue; - } -#endif - - if (fwrite(out, 1, b.out_pos, stdout) != b.out_pos || fclose(stdout)) { - msg = "Write error\n"; - goto error; - } - - switch (ret) { - case XZ_STREAM_END: - xz_dec_end(s); - return 0; - - case XZ_MEM_ERROR: - msg = "Memory allocation failed\n"; - goto error; - - case XZ_MEMLIMIT_ERROR: - msg = "Memory usage limit reached\n"; - goto error; - - case XZ_FORMAT_ERROR: - msg = "Not a .xz file\n"; - goto error; - - case XZ_OPTIONS_ERROR: - msg = "Unsupported options in the .xz headers\n"; - goto error; - - case XZ_DATA_ERROR: - case XZ_BUF_ERROR: - msg = "File is corrupt\n"; - goto error; - - default: - msg = "Bug!\n"; - goto error; - } - } - -error: - xz_dec_end(s); - fputs(argv[0], stderr); - fputs(": ", stderr); - fputs(msg, stderr); - return 1; -} diff --git a/app/src/main/engine/org/love2d/android/GameActivity.java b/app/src/main/engine/org/love2d/android/GameActivity.java new file mode 100644 index 000000000..271e90b61 --- /dev/null +++ b/app/src/main/engine/org/love2d/android/GameActivity.java @@ -0,0 +1,880 @@ +/** + * Copyright (c) 2006-2023 LOVE Development Team + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + **/ + +package org.love2d.android; + +// WinNative: this file is vendored unmodified from love-android 11.5a except +// for this import. Upstream resolves R from its own package; here the only +// R on the classpath is the host app's, and R.bool.embed is the single +// resource it reads (false, so the game path arrives via the launch Intent). +import com.winlator.cmod.R; +import org.love2d.sdl.SDLActivity; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import android.Manifest; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.res.AssetManager; +import android.media.AudioManager; +import android.net.Uri; +import android.os.Bundle; +import android.os.Environment; +import android.os.Vibrator; +import android.util.Log; +import android.util.DisplayMetrics; +import android.view.*; +import android.content.pm.PackageManager; + +import androidx.annotation.Keep; +import androidx.core.app.ActivityCompat; + +public class GameActivity extends SDLActivity { + private static DisplayMetrics metrics = null; + private static String gamePath = ""; + private static Vibrator vibrator = null; + protected final int[] externalStorageRequestDummy = new int[1]; + protected final int[] recordAudioRequestDummy = new int[1]; + public static final int EXTERNAL_STORAGE_REQUEST_CODE = 2; + public static final int RECORD_AUDIO_REQUEST_CODE = 3; + public static final int FILE_PICKER_REQUEST_CODE = 4; + public static final int FILE_CREATE_REQUEST_CODE = 5; + /** @deprecated Prefer FILE_PICKER_REQUEST_CODE; kept for older call sites. */ + public static final int ROM_PICKER_REQUEST_CODE = FILE_PICKER_REQUEST_CODE; + // Mirrors conf.lua's t.identity ("pokemon-love2d"): where the picked file + // is dropped so RomImporter's existing folder scan finds it -- see + // src/import/RomImporter.lua and Filesystem::setIdentity (sets Android's + // save directory to getExternalFilesDir()/save/). + private static final String ROM_SAVE_IDENTITY = "pokemon-love2d"; + private static final String PICKED_ROM_FILENAME = "picked_rom.gb"; + private static final String PICKED_MOD_FILENAME = "picked_mod.zip"; + private static final String PICKED_SAVE_FILENAME = "picked_save.sav"; + private static final String PENDING_EXPORT_FILENAME = "pending_export.sav"; + private static final String EXPORT_DONE_FILENAME = "export_done.flag"; + // Written when a SAF pick cannot be read at all, with the destination + // basename as its body, so RomImporter:focus can say so in the launcher + // instead of leaving the player on "No ROM imported" (issue #442). + private static final String PICK_ERROR_FILENAME = "pick_error.flag"; + // Destination basename for the in-flight SAF pick (set by showFilePicker). + private String pendingPickFilename = PICKED_ROM_FILENAME; + // Suggested download name for the in-flight SAF create (set by showCreateDocument). + private String pendingCreateSuggestedName = "export.sav"; + private static boolean immersiveActive = false; + private static boolean needToCopyGameInArchive = false; + private boolean storagePermissionUnnecessary = false; + private boolean shortEdgesMode = false; + public boolean embed = false; + public int safeAreaTop = 0; + public int safeAreaLeft = 0; + public int safeAreaBottom = 0; + public int safeAreaRight = 0; + + private static native void nativeSetDefaultStreamValues(int sampleRate, int framesPerBurst); + + @Override + protected String[] getLibraries() { + return new String[] { + "c++_shared", + "mpg123", + "openal", + "love", + }; + } + + @Override + protected String getMainSharedObject() { + String[] libs = getLibraries(); + String libname = "lib" + libs[libs.length - 1] + ".so"; + + // Since Lollipop, you can simply pass "libname.so" to dlopen + // and it will resolve correct paths and load correct library. + // This is mandatory for extractNativeLibs=false support in + // Marshmallow. + if (android.os.Build.VERSION.SDK_INT >= 21) { + return libname; + } else { + return getContext().getApplicationInfo().nativeLibraryDir + "/" + libname; + } + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + Log.d("GameActivity", "started"); + + int res = checkCallingOrSelfPermission(Manifest.permission.VIBRATE); + if (res == PackageManager.PERMISSION_GRANTED) { + vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE); + } else { + Log.d("GameActivity", "Vibration disabled: could not get vibration permission."); + } + + // These 2 variables must be reset or it will use the existing value. + gamePath = ""; + storagePermissionUnnecessary = false; + embed = getResources().getBoolean(R.bool.embed); + needToCopyGameInArchive = embed; + + if (!embed) { + Intent intent = getIntent(); + handleIntent(intent); + intent.setData(null); + } + + super.onCreate(savedInstanceState); + metrics = getResources().getDisplayMetrics(); + + // Set low-latency audio values + nativeSetDefaultStreamValues(getAudioFreq(), getAudioSMP()); + + if (android.os.Build.VERSION.SDK_INT >= 28) { + getWindow().getAttributes().layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER; + shortEdgesMode = false; + } + } + + @Override + protected void onNewIntent(Intent intent) { + Log.d("GameActivity", "onNewIntent() with " + intent); + if (!embed) { + handleIntent(intent); + resetNative(); + startNative(); + } + } + + protected void handleIntent(Intent intent) { + Uri game = intent.getData(); + + if (!embed && game != null) { + String scheme = game.getScheme(); + String path = game.getPath(); + // If we have a game via the intent data we we try to figure out how we have to load it. We + // support the following variations: + // * a main.lua file: set gamePath to the directory containing main.lua + // * otherwise: set gamePath to the file + if (scheme.equals("file")) { + Log.d("GameActivity", "Received file:// intent with path: " + path); + // If we were given the path of a main.lua then use its + // directory. Otherwise use full path. + List path_segments = game.getPathSegments(); + if (path_segments.get(path_segments.size() - 1).equals("main.lua")) { + gamePath = path.substring(0, path.length() - "main.lua".length()); + } else { + gamePath = path; + } + } else if (scheme.equals("content")) { + Log.d("GameActivity", "Received content:// intent with path: " + path); + try { + String filename = "game.love"; + String[] pathSegments = path.split("/"); + if (pathSegments.length > 0) { + filename = pathSegments[pathSegments.length - 1]; + } + + // Sanitize filename to prevent PhysFS complaining later. + filename = filename.replaceAll("[^a-zA-Z0-9_\\\\-\\\\.]", "_"); + + String destination_file = this.getCacheDir().getPath() + "/" + filename; + InputStream data = getContentResolver().openInputStream(game); + + // copyAssetFile automatically closes the InputStream + if (copyAssetFile(data, destination_file)) { + gamePath = destination_file; + storagePermissionUnnecessary = true; + } + } catch (Exception e) { + Log.d("GameActivity", "could not read content uri " + game.toString() + ": " + e.getMessage()); + } + } else { + Log.e("GameActivity", "Unsupported scheme: '" + game.getScheme() + "'."); + + AlertDialog.Builder alert_dialog = new AlertDialog.Builder(this); + alert_dialog.setMessage("Could not load LÖVE game '" + path + + "' as it uses unsupported scheme '" + game.getScheme() + + "'. Please contact the developer."); + alert_dialog.setTitle("LÖVE for Android Error"); + alert_dialog.setPositiveButton("Exit", + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) { + finish(); + } + }); + alert_dialog.setCancelable(false); + alert_dialog.create().show(); + } + } + + Log.d("GameActivity", "new gamePath: " + gamePath); + } + + private void copyGameInsideArchive() { + try { + // If we have a game.love in our assets folder copy it to the cache folder + // so that we can load it from native LÖVE code + AssetManager assetManager = getAssets(); + InputStream gameStream = assetManager.open("game.love"); + String destinationFile = this.getCacheDir().getPath() + "/game.love"; + + if (copyAssetFile(gameStream, destinationFile)) + gamePath = destinationFile; + else + gamePath = "game.love"; + storagePermissionUnnecessary = true; + } catch (IOException e) { + // There's no game.love in our assets + Log.d("GameActivity", "Could not open game.love from assets: " + e.getMessage()); + } + } + + protected void checkLovegameFolder() { + // If no game.love was found and embed flavor is not used, fall back to the game in + // /Android/data//games/lovegame + if (!embed) { + Log.d("GameActivity", "fallback to lovegame folder"); + File ext = getExternalFilesDir("games"); + if ((new File(ext, "/lovegame/main.lua")).exists()) { + gamePath = ext.getPath() + "/lovegame/"; + storagePermissionUnnecessary = true; + } else if (android.os.Build.VERSION.SDK_INT <= 28) { + // Try to fallback to /sdcard/lovegame in Android 9 and earlier too. + if (hasExternalStoragePermission()) { + ext = Environment.getExternalStorageDirectory(); + if ((new File(ext, "/lovegame/main.lua")).exists()) { + gamePath = ext.getPath() + "/lovegame/"; + storagePermissionUnnecessary = false; + } + } else { + Log.d("GameActivity", "Cannot load game from /sdcard/lovegame: permission not granted"); + } + } + + Log.d("GameActivity", "lovegame directory: " + gamePath); + } + } + + @Override + protected void onDestroy() { + if (vibrator != null) { + Log.d("GameActivity", "Cancelling vibration"); + vibrator.cancel(); + } + super.onDestroy(); + } + + @Override + protected void onPause() { + if (vibrator != null) { + Log.d("GameActivity", "Cancelling vibration"); + vibrator.cancel(); + } + super.onPause(); + } + + @Override + public void onResume() { + super.onResume(); + } + + @Keep + public void setImmersiveMode(boolean immersive_mode) { + if (android.os.Build.VERSION.SDK_INT >= 28) { + getWindow().getAttributes().layoutInDisplayCutoutMode = immersive_mode ? + WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES : + WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER; + shortEdgesMode = immersive_mode; + } + + immersiveActive = immersive_mode; + } + + @Keep + public boolean getImmersiveMode() { + return immersiveActive; + } + + @Keep + public static String getGamePath() { + GameActivity self = (GameActivity) mSingleton; // use SDL provided one + Log.d("GameActivity", "called getGamePath(), game path = " + gamePath); + + if (gamePath.length() > 0) { + if (self.storagePermissionUnnecessary || self.hasExternalStoragePermission()) { + return gamePath; + } else { + Log.d("GameActivity", "cannot open game " + gamePath + ": no external storage permission given!"); + } + } else if (needToCopyGameInArchive) { + self.copyGameInsideArchive(); + } else { + self.checkLovegameFolder(); + } + + return gamePath; + } + + public static DisplayMetrics getMetrics() { + return metrics; + } + + @Keep + public static void vibrate(double seconds) { + if (vibrator != null) { + vibrator.vibrate((long) (seconds * 1000.)); + } + } + + @Keep + public static boolean openURLFromLOVE(String url) { + Log.d("GameActivity", "opening url = " + url); + return openURL(url) == 0; + } + + /** + * Shows the system document picker (Storage Access Framework) so the + * player can pick a ROM / mod / save from anywhere (Downloads, Drive, + * etc.) without needing to know where the app's external files folder + * is. Requires API 19+ (ACTION_OPEN_DOCUMENT); the picked file (if any) + * arrives later in onActivityResult, not synchronously here. + * + * @param destFilename basename under the app save identity (e.g. + * picked_rom.gb, picked_mod.zip, picked_save.sav) + */ + @Keep + public static boolean showFilePicker(String destFilename) { + if (android.os.Build.VERSION.SDK_INT < 19) return false; + GameActivity self = (GameActivity) mSingleton; + if (self == null) return false; + if (destFilename == null || destFilename.length() == 0) { + destFilename = PICKED_ROM_FILENAME; + } + // Reject path separators so a hostile JNI caller cannot escape the + // save identity directory. + if (destFilename.indexOf('/') >= 0 || destFilename.indexOf('\\') >= 0) { + Log.d("GameActivity", "refusing unsafe picker dest: " + destFilename); + return false; + } + + self.pendingPickFilename = destFilename; + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("*/*"); + try { + self.startActivityForResult(intent, FILE_PICKER_REQUEST_CODE); + return true; + } catch (Exception e) { + Log.d("GameActivity", "could not open file picker: " + e.getMessage()); + return false; + } + } + + /** ROM convenience wrapper; prefer showFilePicker with an explicit name. */ + @Keep + public static boolean showRomFilePicker() { + return showFilePicker(PICKED_ROM_FILENAME); + } + + /** Mod .zip convenience wrapper used by love.system.pickFile("mod"). */ + @Keep + public static boolean showModFilePicker() { + return showFilePicker(PICKED_MOD_FILENAME); + } + + /** Battery .sav convenience wrapper used by love.system.pickFile("sav"). */ + @Keep + public static boolean showSaveFilePicker() { + return showFilePicker(PICKED_SAVE_FILENAME); + } + + /** + * Shows ACTION_CREATE_DOCUMENT so the player can save a staged export + * (pending_export.sav in the app save identity) to Downloads / Drive / + * etc. Suggested name is the dialog's default filename. + */ + @Keep + public static boolean showCreateDocument(String suggestedName) { + if (android.os.Build.VERSION.SDK_INT < 19) return false; + GameActivity self = (GameActivity) mSingleton; + if (self == null) return false; + if (suggestedName == null || suggestedName.length() == 0) { + suggestedName = "export.sav"; + } + if (suggestedName.indexOf('/') >= 0 || suggestedName.indexOf('\\') >= 0) { + Log.d("GameActivity", "refusing unsafe create name: " + suggestedName); + return false; + } + File source = new File( + new File(self.getExternalFilesDir(null), "save"), + ROM_SAVE_IDENTITY + "/" + PENDING_EXPORT_FILENAME); + if (!source.isFile()) { + Log.d("GameActivity", "no pending export at " + source); + return false; + } + + self.pendingCreateSuggestedName = suggestedName; + Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("application/octet-stream"); + intent.putExtra(Intent.EXTRA_TITLE, suggestedName); + try { + self.startActivityForResult(intent, FILE_CREATE_REQUEST_CODE); + return true; + } catch (Exception e) { + Log.d("GameActivity", "could not open create-document picker: " + e.getMessage()); + return false; + } + } + + private File saveIdentityDir() { + return new File(new File(getExternalFilesDir(null), "save"), ROM_SAVE_IDENTITY); + } + + /** Drops a small flag file in the save identity for Lua to consume on focus. */ + private void writeSaveDirFlag(String name, String body) { + try { + FileOutputStream fos = new FileOutputStream(new File(saveIdentityDir(), name), false); + fos.write(body.getBytes()); + fos.close(); + } catch (IOException e) { + Log.d("GameActivity", "could not write " + name + ": " + e.getMessage()); + } + } + + private boolean copyFileToUri(File source, Uri destUri) { + InputStream in = null; + OutputStream out = null; + try { + in = new BufferedInputStream(new FileInputStream(source)); + out = getContentResolver().openOutputStream(destUri); + if (out == null) return false; + byte[] buf = new byte[8192]; + int n; + while ((n = in.read(buf)) != -1) { + out.write(buf, 0, n); + } + out.flush(); + return true; + } catch (IOException e) { + Log.d("GameActivity", "copy to URI failed: " + e.getMessage()); + return false; + } finally { + try { if (in != null) in.close(); } catch (IOException ignored) {} + try { if (out != null) out.close(); } catch (IOException ignored) {} + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == FILE_CREATE_REQUEST_CODE) { + if (resultCode != RESULT_OK || data == null || data.getData() == null) { + Log.d("GameActivity", "create-document cancelled"); + return; + } + File source = new File(saveIdentityDir(), PENDING_EXPORT_FILENAME); + if (!source.isFile()) { + Log.d("GameActivity", "pending export missing at result time"); + return; + } + Uri uri = data.getData(); + if (copyFileToUri(source, uri)) { + // Signal Lua on next focus that the SAF export finished. + writeSaveDirFlag(EXPORT_DONE_FILENAME, "ok"); + // Keep pending_export.sav so a retry still works; Lua may remove it. + } else { + Log.d("GameActivity", "could not write export to " + uri); + } + return; + } + if (requestCode != FILE_PICKER_REQUEST_CODE) return; + if (resultCode != RESULT_OK || data == null || data.getData() == null) { + Log.d("GameActivity", "file picker returned no file (cancelled?)"); + return; + } + + Uri uri = data.getData(); + File destDir = saveIdentityDir(); + if (!destDir.exists() && !destDir.mkdirs()) { + Log.d("GameActivity", "could not create " + destDir); + return; + } + String destName = pendingPickFilename != null + ? pendingPickFilename : PICKED_ROM_FILENAME; + File destFile = new File(destDir, destName); + + // ACTION_OPEN_DOCUMENT is meant to land in the system documents UI, but + // some OEM shells (ColorOS) offer third-party file managers in a + // chooser, and those hand back either a provider URI this app has no + // grant for (SecurityException / FileNotFoundException) or a bare + // file:// path (unreadable without storage permission on targetSdk 34). + // Try the resolver, then the path, then tell Lua why nothing imported. + InputStream source = null; + try { + source = getContentResolver().openInputStream(uri); + } catch (Exception e) { + Log.d("GameActivity", "could not open picked file: " + e.getMessage()); + } + if (source == null && "file".equals(uri.getScheme()) && uri.getPath() != null) { + try { + source = new FileInputStream(uri.getPath()); + } catch (FileNotFoundException e) { + Log.d("GameActivity", "could not open picked path: " + e.getMessage()); + } + } + if (source == null) { + Log.d("GameActivity", "no readable stream for picked file " + uri); + writeSaveDirFlag(PICK_ERROR_FILENAME, destName); + return; + } + if (!copyAssetFile(source, destFile.getPath())) { + Log.d("GameActivity", "could not copy picked file to " + destFile); + // A truncated pick would only fail verification later, so drop it + // and report instead. + destFile.delete(); + writeSaveDirFlag(PICK_ERROR_FILENAME, destName); + } + } + + /** + * Copies a given file from the assets folder to the destination. + * + * @return true if successful + */ + boolean copyAssetFile(InputStream source, String destinationFileName) { + boolean success = false; + + BufferedOutputStream destination = null; + try { + destination = new BufferedOutputStream(new FileOutputStream(destinationFileName, false)); + } catch (IOException e) { + Log.d("GameActivity", "Could not open destination file: " + e.getMessage()); + } + + // perform the copying + int chunk_read; + int bytes_written = 0; + + assert (source != null && destination != null); + + try { + byte[] buf = new byte[1024]; + chunk_read = source.read(buf); + do { + destination.write(buf, 0, chunk_read); + bytes_written += chunk_read; + chunk_read = source.read(buf); + } while (chunk_read != -1); + } catch (IOException e) { + Log.d("GameActivity", "Copying failed:" + e.getMessage()); + } + + // close streams + try { + source.close(); + destination.close(); + success = true; + } catch (IOException e) { + Log.d("GameActivity", "Copying failed: " + e.getMessage()); + } + + Log.d("GameActivity", "Successfully copied stream to " + destinationFileName + " (" + bytes_written + " bytes written)."); + return success; + } + + @Keep + public boolean hasBackgroundMusic() { + AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); + return audioManager.isMusicActive(); + } + + @Keep + public void showRecordingAudioPermissionMissingDialog() { + Log.d("GameActivity", "showRecordingAudioPermissionMissingDialog()"); + runOnUiThread(new Runnable() { + @Override + public void run() { + AlertDialog dialog = new AlertDialog.Builder(mSingleton) + .setTitle("Audio Recording Permission Missing") + .setMessage("It appears that this game uses mic capabilities. The game may not work correctly without mic permission!") + .setNeutralButton("Continue", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface di, int id) { + synchronized (recordAudioRequestDummy) { + recordAudioRequestDummy.notify(); + } + } + }) + .create(); + dialog.show(); + } + }); + + synchronized (recordAudioRequestDummy) { + try { + recordAudioRequestDummy.wait(); + } catch (InterruptedException e) { + Log.d("GameActivity", "mic permission dialog", e); + } + } + } + + public void showExternalStoragePermissionMissingDialog() { + AlertDialog dialog = new AlertDialog.Builder(mSingleton) + .setTitle("Storage Permission Missing") + .setMessage("LÖVE for Android will not be able to run non-packaged games without storage permission.") + .setNeutralButton("Continue", null) + .create(); + dialog.show(); + } + + @Override + public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { + if (grantResults.length > 0) { + Log.d("GameActivity", "Received a request permission result"); + + switch (requestCode) { + case EXTERNAL_STORAGE_REQUEST_CODE: { + if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { + Log.d("GameActivity", "Permission granted"); + } else { + Log.d("GameActivity", "Did not get permission."); + if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.READ_EXTERNAL_STORAGE)) { + showExternalStoragePermissionMissingDialog(); + } + } + + Log.d("GameActivity", "Unlocking LÖVE thread"); + synchronized (externalStorageRequestDummy) { + externalStorageRequestDummy[0] = grantResults[0]; + externalStorageRequestDummy.notify(); + } + break; + } + case RECORD_AUDIO_REQUEST_CODE: { + if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { + Log.d("GameActivity", "Mic permission granted"); + } else { + Log.d("GameActivity", "Did not get mic permission."); + } + + Log.d("GameActivity", "Unlocking LÖVE thread"); + synchronized (recordAudioRequestDummy) { + recordAudioRequestDummy[0] = grantResults[0]; + recordAudioRequestDummy.notify(); + } + break; + } + default: + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + } + } + } + + @Keep + public boolean hasExternalStoragePermission() { + if (ActivityCompat.checkSelfPermission(this, + Manifest.permission.READ_EXTERNAL_STORAGE) + == PackageManager.PERMISSION_GRANTED) { + return true; + } + + Log.d("GameActivity", "Requesting permission and locking LÖVE thread until we have an answer."); + ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, EXTERNAL_STORAGE_REQUEST_CODE); + + synchronized (externalStorageRequestDummy) { + try { + externalStorageRequestDummy.wait(); + } catch (InterruptedException e) { + Log.d("GameActivity", "requesting external storage permission", e); + return false; + } + } + + return ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; + } + + @Keep + public boolean hasRecordAudioPermission() { + return ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED; + } + + @Keep + public void requestRecordAudioPermission() { + if (ActivityCompat.checkSelfPermission(this, + Manifest.permission.RECORD_AUDIO) + == PackageManager.PERMISSION_GRANTED) { + return; + } + + Log.d("GameActivity", "Requesting mic permission and locking LÖVE thread until we have an answer."); + ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO}, RECORD_AUDIO_REQUEST_CODE); + + synchronized (recordAudioRequestDummy) { + try { + recordAudioRequestDummy.wait(); + } catch (InterruptedException e) { + Log.d("GameActivity", "requesting mic permission", e); + } + } + } + + @Keep + public boolean initializeSafeArea() { + if (android.os.Build.VERSION.SDK_INT >= 28 && shortEdgesMode) { + DisplayCutout cutout = getWindow().getDecorView().getRootWindowInsets().getDisplayCutout(); + + if (cutout != null) { + safeAreaTop = cutout.getSafeInsetTop(); + safeAreaLeft = cutout.getSafeInsetLeft(); + safeAreaBottom = cutout.getSafeInsetBottom(); + safeAreaRight = cutout.getSafeInsetRight(); + return true; + } + } + + return false; + } + + @Keep + public String[] buildFileTree() { + // Map key is path, value is directory flag + HashMap map = buildFileTree(getAssets(), "", new HashMap()); + ArrayList result = new ArrayList(); + + for (Map.Entry data: map.entrySet()) { + result.add((data.getValue() ? "d" : "f") + data.getKey()); + } + + String[] r = new String[result.size()]; + result.toArray(r); + return r; + } + + private HashMap buildFileTree(AssetManager assetManager, String dir, HashMap map) { + String strippedDir = dir.endsWith("/") ? dir.substring(0, dir.length() - 1) : dir; + + // Try open dir + try { + InputStream test = assetManager.open(strippedDir); + // It's a file + test.close(); + map.put(strippedDir, false); + } catch (FileNotFoundException e) { + // It's a directory + String[] list = null; + + // List files + try { + list = assetManager.list(strippedDir); + } catch (IOException e2) { + Log.e("GameActivity", strippedDir, e2); + } + + // Mark as file + map.put(dir, true); + + // This Object comparison is intentional. + if (strippedDir != dir) { + map.put(strippedDir, true); + } + + if (list != null) { + for (String path: list) { + buildFileTree(assetManager, dir + path + "/", map); + } + } + } catch (IOException e) { + Log.e("GameActivity", dir, e); + } + + return map; + } + + public int getAudioSMP() { + int smp = 256; + + if (android.os.Build.VERSION.SDK_INT >= 17) { + AudioManager a = (AudioManager) getSystemService(Context.AUDIO_SERVICE); + int b = Integer.parseInt(a.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER)); + return b > 0 ? b : smp; + } + + return smp; + } + + public int getAudioFreq() { + int freq = 44100; + + if (android.os.Build.VERSION.SDK_INT >= 17) { + AudioManager a = (AudioManager) getSystemService(Context.AUDIO_SERVICE); + int b = Integer.parseInt(a.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE)); + return b > 0 ? b : freq; + } + + return freq; + } + + public boolean isNativeLibsExtracted() { + ApplicationInfo appInfo = getApplicationInfo(); + + if (android.os.Build.VERSION.SDK_INT >= 23) { + return (appInfo.flags & ApplicationInfo.FLAG_EXTRACT_NATIVE_LIBS) != 0; + } + + return true; + } + + @Keep + public String getCRequirePath() { + ApplicationInfo applicationInfo = getApplicationInfo(); + + if (isNativeLibsExtracted()) { + return applicationInfo.nativeLibraryDir + "/?.so"; + } else { + // The native libs are inside the APK and can be loaded directly. + // FIXME: What about split APKs? + String abi; + + if (android.os.Build.VERSION.SDK_INT >= 21) { + abi = android.os.Build.SUPPORTED_ABIS[0]; + } else { + // This codepath should NEVER be taken as if isNativeLibsExtracted() + // returns false, it's 100% safe to assume we're on API level 23 or later. + abi = android.os.Build.CPU_ABI; + } + + return applicationInfo.sourceDir + "!/lib/" + abi + "/?.so"; + } + } +} diff --git a/app/src/main/engine/org/love2d/sdl/HIDDevice.java b/app/src/main/engine/org/love2d/sdl/HIDDevice.java new file mode 100644 index 000000000..6058f7d17 --- /dev/null +++ b/app/src/main/engine/org/love2d/sdl/HIDDevice.java @@ -0,0 +1,22 @@ +package org.love2d.sdl; + +import android.hardware.usb.UsbDevice; + +interface HIDDevice +{ + public int getId(); + public int getVendorId(); + public int getProductId(); + public String getSerialNumber(); + public int getVersion(); + public String getManufacturerName(); + public String getProductName(); + public UsbDevice getDevice(); + public boolean open(); + public int sendFeatureReport(byte[] report); + public int sendOutputReport(byte[] report); + public boolean getFeatureReport(byte[] report); + public void setFrozen(boolean frozen); + public void close(); + public void shutdown(); +} diff --git a/app/src/main/engine/org/love2d/sdl/HIDDeviceBLESteamController.java b/app/src/main/engine/org/love2d/sdl/HIDDeviceBLESteamController.java new file mode 100644 index 000000000..b9a58808f --- /dev/null +++ b/app/src/main/engine/org/love2d/sdl/HIDDeviceBLESteamController.java @@ -0,0 +1,650 @@ +package org.love2d.sdl; + +import android.content.Context; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCallback; +import android.bluetooth.BluetoothGattCharacteristic; +import android.bluetooth.BluetoothGattDescriptor; +import android.bluetooth.BluetoothManager; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothGattService; +import android.hardware.usb.UsbDevice; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; +import android.os.*; + +//import com.android.internal.util.HexDump; + +import java.lang.Runnable; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.UUID; + +class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDevice { + + private static final String TAG = "hidapi"; + private HIDDeviceManager mManager; + private BluetoothDevice mDevice; + private int mDeviceId; + private BluetoothGatt mGatt; + private boolean mIsRegistered = false; + private boolean mIsConnected = false; + private boolean mIsChromebook = false; + private boolean mIsReconnecting = false; + private boolean mFrozen = false; + private LinkedList mOperations; + GattOperation mCurrentOperation = null; + private Handler mHandler; + + private static final int TRANSPORT_AUTO = 0; + private static final int TRANSPORT_BREDR = 1; + private static final int TRANSPORT_LE = 2; + + private static final int CHROMEBOOK_CONNECTION_CHECK_INTERVAL = 10000; + + static public final UUID steamControllerService = UUID.fromString("100F6C32-1735-4313-B402-38567131E5F3"); + static public final UUID inputCharacteristic = UUID.fromString("100F6C33-1735-4313-B402-38567131E5F3"); + static public final UUID reportCharacteristic = UUID.fromString("100F6C34-1735-4313-B402-38567131E5F3"); + static private final byte[] enterValveMode = new byte[] { (byte)0xC0, (byte)0x87, 0x03, 0x08, 0x07, 0x00 }; + + static class GattOperation { + private enum Operation { + CHR_READ, + CHR_WRITE, + ENABLE_NOTIFICATION + } + + Operation mOp; + UUID mUuid; + byte[] mValue; + BluetoothGatt mGatt; + boolean mResult = true; + + private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid) { + mGatt = gatt; + mOp = operation; + mUuid = uuid; + } + + private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid, byte[] value) { + mGatt = gatt; + mOp = operation; + mUuid = uuid; + mValue = value; + } + + public void run() { + // This is executed in main thread + BluetoothGattCharacteristic chr; + + switch (mOp) { + case CHR_READ: + chr = getCharacteristic(mUuid); + //Log.v(TAG, "Reading characteristic " + chr.getUuid()); + if (!mGatt.readCharacteristic(chr)) { + Log.e(TAG, "Unable to read characteristic " + mUuid.toString()); + mResult = false; + break; + } + mResult = true; + break; + case CHR_WRITE: + chr = getCharacteristic(mUuid); + //Log.v(TAG, "Writing characteristic " + chr.getUuid() + " value=" + HexDump.toHexString(value)); + chr.setValue(mValue); + if (!mGatt.writeCharacteristic(chr)) { + Log.e(TAG, "Unable to write characteristic " + mUuid.toString()); + mResult = false; + break; + } + mResult = true; + break; + case ENABLE_NOTIFICATION: + chr = getCharacteristic(mUuid); + //Log.v(TAG, "Writing descriptor of " + chr.getUuid()); + if (chr != null) { + BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")); + if (cccd != null) { + int properties = chr.getProperties(); + byte[] value; + if ((properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) == BluetoothGattCharacteristic.PROPERTY_NOTIFY) { + value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE; + } else if ((properties & BluetoothGattCharacteristic.PROPERTY_INDICATE) == BluetoothGattCharacteristic.PROPERTY_INDICATE) { + value = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE; + } else { + Log.e(TAG, "Unable to start notifications on input characteristic"); + mResult = false; + return; + } + + mGatt.setCharacteristicNotification(chr, true); + cccd.setValue(value); + if (!mGatt.writeDescriptor(cccd)) { + Log.e(TAG, "Unable to write descriptor " + mUuid.toString()); + mResult = false; + return; + } + mResult = true; + } + } + } + } + + public boolean finish() { + return mResult; + } + + private BluetoothGattCharacteristic getCharacteristic(UUID uuid) { + BluetoothGattService valveService = mGatt.getService(steamControllerService); + if (valveService == null) + return null; + return valveService.getCharacteristic(uuid); + } + + static public GattOperation readCharacteristic(BluetoothGatt gatt, UUID uuid) { + return new GattOperation(gatt, Operation.CHR_READ, uuid); + } + + static public GattOperation writeCharacteristic(BluetoothGatt gatt, UUID uuid, byte[] value) { + return new GattOperation(gatt, Operation.CHR_WRITE, uuid, value); + } + + static public GattOperation enableNotification(BluetoothGatt gatt, UUID uuid) { + return new GattOperation(gatt, Operation.ENABLE_NOTIFICATION, uuid); + } + } + + public HIDDeviceBLESteamController(HIDDeviceManager manager, BluetoothDevice device) { + mManager = manager; + mDevice = device; + mDeviceId = mManager.getDeviceIDForIdentifier(getIdentifier()); + mIsRegistered = false; + mIsChromebook = mManager.getContext().getPackageManager().hasSystemFeature("org.chromium.arc.device_management"); + mOperations = new LinkedList(); + mHandler = new Handler(Looper.getMainLooper()); + + mGatt = connectGatt(); + // final HIDDeviceBLESteamController finalThis = this; + // mHandler.postDelayed(new Runnable() { + // @Override + // public void run() { + // finalThis.checkConnectionForChromebookIssue(); + // } + // }, CHROMEBOOK_CONNECTION_CHECK_INTERVAL); + } + + public String getIdentifier() { + return String.format("SteamController.%s", mDevice.getAddress()); + } + + public BluetoothGatt getGatt() { + return mGatt; + } + + // Because on Chromebooks we show up as a dual-mode device, it will attempt to connect TRANSPORT_AUTO, which will use TRANSPORT_BREDR instead + // of TRANSPORT_LE. Let's force ourselves to connect low energy. + private BluetoothGatt connectGatt(boolean managed) { + if (Build.VERSION.SDK_INT >= 23 /* Android 6.0 (M) */) { + try { + return mDevice.connectGatt(mManager.getContext(), managed, this, TRANSPORT_LE); + } catch (Exception e) { + return mDevice.connectGatt(mManager.getContext(), managed, this); + } + } else { + return mDevice.connectGatt(mManager.getContext(), managed, this); + } + } + + private BluetoothGatt connectGatt() { + return connectGatt(false); + } + + protected int getConnectionState() { + + Context context = mManager.getContext(); + if (context == null) { + // We are lacking any context to get our Bluetooth information. We'll just assume disconnected. + return BluetoothProfile.STATE_DISCONNECTED; + } + + BluetoothManager btManager = (BluetoothManager)context.getSystemService(Context.BLUETOOTH_SERVICE); + if (btManager == null) { + // This device doesn't support Bluetooth. We should never be here, because how did + // we instantiate a device to start with? + return BluetoothProfile.STATE_DISCONNECTED; + } + + return btManager.getConnectionState(mDevice, BluetoothProfile.GATT); + } + + public void reconnect() { + + if (getConnectionState() != BluetoothProfile.STATE_CONNECTED) { + mGatt.disconnect(); + mGatt = connectGatt(); + } + + } + + protected void checkConnectionForChromebookIssue() { + if (!mIsChromebook) { + // We only do this on Chromebooks, because otherwise it's really annoying to just attempt + // over and over. + return; + } + + int connectionState = getConnectionState(); + + switch (connectionState) { + case BluetoothProfile.STATE_CONNECTED: + if (!mIsConnected) { + // We are in the Bad Chromebook Place. We can force a disconnect + // to try to recover. + Log.v(TAG, "Chromebook: We are in a very bad state; the controller shows as connected in the underlying Bluetooth layer, but we never received a callback. Forcing a reconnect."); + mIsReconnecting = true; + mGatt.disconnect(); + mGatt = connectGatt(false); + break; + } + else if (!isRegistered()) { + if (mGatt.getServices().size() > 0) { + Log.v(TAG, "Chromebook: We are connected to a controller, but never got our registration. Trying to recover."); + probeService(this); + } + else { + Log.v(TAG, "Chromebook: We are connected to a controller, but never discovered services. Trying to recover."); + mIsReconnecting = true; + mGatt.disconnect(); + mGatt = connectGatt(false); + break; + } + } + else { + Log.v(TAG, "Chromebook: We are connected, and registered. Everything's good!"); + return; + } + break; + + case BluetoothProfile.STATE_DISCONNECTED: + Log.v(TAG, "Chromebook: We have either been disconnected, or the Chromebook BtGatt.ContextMap bug has bitten us. Attempting a disconnect/reconnect, but we may not be able to recover."); + + mIsReconnecting = true; + mGatt.disconnect(); + mGatt = connectGatt(false); + break; + + case BluetoothProfile.STATE_CONNECTING: + Log.v(TAG, "Chromebook: We're still trying to connect. Waiting a bit longer."); + break; + } + + final HIDDeviceBLESteamController finalThis = this; + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + finalThis.checkConnectionForChromebookIssue(); + } + }, CHROMEBOOK_CONNECTION_CHECK_INTERVAL); + } + + private boolean isRegistered() { + return mIsRegistered; + } + + private void setRegistered() { + mIsRegistered = true; + } + + private boolean probeService(HIDDeviceBLESteamController controller) { + + if (isRegistered()) { + return true; + } + + if (!mIsConnected) { + return false; + } + + Log.v(TAG, "probeService controller=" + controller); + + for (BluetoothGattService service : mGatt.getServices()) { + if (service.getUuid().equals(steamControllerService)) { + Log.v(TAG, "Found Valve steam controller service " + service.getUuid()); + + for (BluetoothGattCharacteristic chr : service.getCharacteristics()) { + if (chr.getUuid().equals(inputCharacteristic)) { + Log.v(TAG, "Found input characteristic"); + // Start notifications + BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")); + if (cccd != null) { + enableNotification(chr.getUuid()); + } + } + } + return true; + } + } + + if ((mGatt.getServices().size() == 0) && mIsChromebook && !mIsReconnecting) { + Log.e(TAG, "Chromebook: Discovered services were empty; this almost certainly means the BtGatt.ContextMap bug has bitten us."); + mIsConnected = false; + mIsReconnecting = true; + mGatt.disconnect(); + mGatt = connectGatt(false); + } + + return false; + } + + ////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////////// + + private void finishCurrentGattOperation() { + GattOperation op = null; + synchronized (mOperations) { + if (mCurrentOperation != null) { + op = mCurrentOperation; + mCurrentOperation = null; + } + } + if (op != null) { + boolean result = op.finish(); // TODO: Maybe in main thread as well? + + // Our operation failed, let's add it back to the beginning of our queue. + if (!result) { + mOperations.addFirst(op); + } + } + executeNextGattOperation(); + } + + private void executeNextGattOperation() { + synchronized (mOperations) { + if (mCurrentOperation != null) + return; + + if (mOperations.isEmpty()) + return; + + mCurrentOperation = mOperations.removeFirst(); + } + + // Run in main thread + mHandler.post(new Runnable() { + @Override + public void run() { + synchronized (mOperations) { + if (mCurrentOperation == null) { + Log.e(TAG, "Current operation null in executor?"); + return; + } + + mCurrentOperation.run(); + // now wait for the GATT callback and when it comes, finish this operation + } + } + }); + } + + private void queueGattOperation(GattOperation op) { + synchronized (mOperations) { + mOperations.add(op); + } + executeNextGattOperation(); + } + + private void enableNotification(UUID chrUuid) { + GattOperation op = HIDDeviceBLESteamController.GattOperation.enableNotification(mGatt, chrUuid); + queueGattOperation(op); + } + + public void writeCharacteristic(UUID uuid, byte[] value) { + GattOperation op = HIDDeviceBLESteamController.GattOperation.writeCharacteristic(mGatt, uuid, value); + queueGattOperation(op); + } + + public void readCharacteristic(UUID uuid) { + GattOperation op = HIDDeviceBLESteamController.GattOperation.readCharacteristic(mGatt, uuid); + queueGattOperation(op); + } + + ////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////// BluetoothGattCallback overridden methods + ////////////////////////////////////////////////////////////////////////////////////////////////////// + + public void onConnectionStateChange(BluetoothGatt g, int status, int newState) { + //Log.v(TAG, "onConnectionStateChange status=" + status + " newState=" + newState); + mIsReconnecting = false; + if (newState == 2) { + mIsConnected = true; + // Run directly, without GattOperation + if (!isRegistered()) { + mHandler.post(new Runnable() { + @Override + public void run() { + mGatt.discoverServices(); + } + }); + } + } + else if (newState == 0) { + mIsConnected = false; + } + + // Disconnection is handled in SteamLink using the ACTION_ACL_DISCONNECTED Intent. + } + + public void onServicesDiscovered(BluetoothGatt gatt, int status) { + //Log.v(TAG, "onServicesDiscovered status=" + status); + if (status == 0) { + if (gatt.getServices().size() == 0) { + Log.v(TAG, "onServicesDiscovered returned zero services; something has gone horribly wrong down in Android's Bluetooth stack."); + mIsReconnecting = true; + mIsConnected = false; + gatt.disconnect(); + mGatt = connectGatt(false); + } + else { + probeService(this); + } + } + } + + public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { + //Log.v(TAG, "onCharacteristicRead status=" + status + " uuid=" + characteristic.getUuid()); + + if (characteristic.getUuid().equals(reportCharacteristic) && !mFrozen) { + mManager.HIDDeviceFeatureReport(getId(), characteristic.getValue()); + } + + finishCurrentGattOperation(); + } + + public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { + //Log.v(TAG, "onCharacteristicWrite status=" + status + " uuid=" + characteristic.getUuid()); + + if (characteristic.getUuid().equals(reportCharacteristic)) { + // Only register controller with the native side once it has been fully configured + if (!isRegistered()) { + Log.v(TAG, "Registering Steam Controller with ID: " + getId()); + mManager.HIDDeviceConnected(getId(), getIdentifier(), getVendorId(), getProductId(), getSerialNumber(), getVersion(), getManufacturerName(), getProductName(), 0, 0, 0, 0); + setRegistered(); + } + } + + finishCurrentGattOperation(); + } + + public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { + // Enable this for verbose logging of controller input reports + //Log.v(TAG, "onCharacteristicChanged uuid=" + characteristic.getUuid() + " data=" + HexDump.dumpHexString(characteristic.getValue())); + + if (characteristic.getUuid().equals(inputCharacteristic) && !mFrozen) { + mManager.HIDDeviceInputReport(getId(), characteristic.getValue()); + } + } + + public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { + //Log.v(TAG, "onDescriptorRead status=" + status); + } + + public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { + BluetoothGattCharacteristic chr = descriptor.getCharacteristic(); + //Log.v(TAG, "onDescriptorWrite status=" + status + " uuid=" + chr.getUuid() + " descriptor=" + descriptor.getUuid()); + + if (chr.getUuid().equals(inputCharacteristic)) { + boolean hasWrittenInputDescriptor = true; + BluetoothGattCharacteristic reportChr = chr.getService().getCharacteristic(reportCharacteristic); + if (reportChr != null) { + Log.v(TAG, "Writing report characteristic to enter valve mode"); + reportChr.setValue(enterValveMode); + gatt.writeCharacteristic(reportChr); + } + } + + finishCurrentGattOperation(); + } + + public void onReliableWriteCompleted(BluetoothGatt gatt, int status) { + //Log.v(TAG, "onReliableWriteCompleted status=" + status); + } + + public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) { + //Log.v(TAG, "onReadRemoteRssi status=" + status); + } + + public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) { + //Log.v(TAG, "onMtuChanged status=" + status); + } + + ////////////////////////////////////////////////////////////////////////////////////////////////////// + //////// Public API + ////////////////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public int getId() { + return mDeviceId; + } + + @Override + public int getVendorId() { + // Valve Corporation + final int VALVE_USB_VID = 0x28DE; + return VALVE_USB_VID; + } + + @Override + public int getProductId() { + // We don't have an easy way to query from the Bluetooth device, but we know what it is + final int D0G_BLE2_PID = 0x1106; + return D0G_BLE2_PID; + } + + @Override + public String getSerialNumber() { + // This will be read later via feature report by Steam + return "12345"; + } + + @Override + public int getVersion() { + return 0; + } + + @Override + public String getManufacturerName() { + return "Valve Corporation"; + } + + @Override + public String getProductName() { + return "Steam Controller"; + } + + @Override + public UsbDevice getDevice() { + return null; + } + + @Override + public boolean open() { + return true; + } + + @Override + public int sendFeatureReport(byte[] report) { + if (!isRegistered()) { + Log.e(TAG, "Attempted sendFeatureReport before Steam Controller is registered!"); + if (mIsConnected) { + probeService(this); + } + return -1; + } + + // We need to skip the first byte, as that doesn't go over the air + byte[] actual_report = Arrays.copyOfRange(report, 1, report.length - 1); + //Log.v(TAG, "sendFeatureReport " + HexDump.dumpHexString(actual_report)); + writeCharacteristic(reportCharacteristic, actual_report); + return report.length; + } + + @Override + public int sendOutputReport(byte[] report) { + if (!isRegistered()) { + Log.e(TAG, "Attempted sendOutputReport before Steam Controller is registered!"); + if (mIsConnected) { + probeService(this); + } + return -1; + } + + //Log.v(TAG, "sendFeatureReport " + HexDump.dumpHexString(report)); + writeCharacteristic(reportCharacteristic, report); + return report.length; + } + + @Override + public boolean getFeatureReport(byte[] report) { + if (!isRegistered()) { + Log.e(TAG, "Attempted getFeatureReport before Steam Controller is registered!"); + if (mIsConnected) { + probeService(this); + } + return false; + } + + //Log.v(TAG, "getFeatureReport"); + readCharacteristic(reportCharacteristic); + return true; + } + + @Override + public void close() { + } + + @Override + public void setFrozen(boolean frozen) { + mFrozen = frozen; + } + + @Override + public void shutdown() { + close(); + + BluetoothGatt g = mGatt; + if (g != null) { + g.disconnect(); + g.close(); + mGatt = null; + } + mManager = null; + mIsRegistered = false; + mIsConnected = false; + mOperations.clear(); + } + +} + diff --git a/app/src/main/engine/org/love2d/sdl/HIDDeviceManager.java b/app/src/main/engine/org/love2d/sdl/HIDDeviceManager.java new file mode 100644 index 000000000..f37fe6e72 --- /dev/null +++ b/app/src/main/engine/org/love2d/sdl/HIDDeviceManager.java @@ -0,0 +1,684 @@ +package org.love2d.sdl; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.PendingIntent; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothManager; +import android.bluetooth.BluetoothProfile; +import android.os.Build; +import android.util.Log; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.hardware.usb.*; +import android.os.Handler; +import android.os.Looper; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; + +public class HIDDeviceManager { + private static final String TAG = "hidapi"; + private static final String ACTION_USB_PERMISSION = "org.love2d.sdl.USB_PERMISSION"; + + private static HIDDeviceManager sManager; + private static int sManagerRefCount = 0; + + public static HIDDeviceManager acquire(Context context) { + if (sManagerRefCount == 0) { + sManager = new HIDDeviceManager(context); + } + ++sManagerRefCount; + return sManager; + } + + public static void release(HIDDeviceManager manager) { + if (manager == sManager) { + --sManagerRefCount; + if (sManagerRefCount == 0) { + sManager.close(); + sManager = null; + } + } + } + + private Context mContext; + private HashMap mDevicesById = new HashMap(); + private HashMap mBluetoothDevices = new HashMap(); + private int mNextDeviceId = 0; + private SharedPreferences mSharedPreferences = null; + private boolean mIsChromebook = false; + private UsbManager mUsbManager; + private Handler mHandler; + private BluetoothManager mBluetoothManager; + private List mLastBluetoothDevices; + + private final BroadcastReceiver mUsbBroadcast = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action.equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) { + UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + handleUsbDeviceAttached(usbDevice); + } else if (action.equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) { + UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + handleUsbDeviceDetached(usbDevice); + } else if (action.equals(HIDDeviceManager.ACTION_USB_PERMISSION)) { + UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + handleUsbDevicePermission(usbDevice, intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)); + } + } + }; + + private final BroadcastReceiver mBluetoothBroadcast = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + // Bluetooth device was connected. If it was a Steam Controller, handle it + if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) { + BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + Log.d(TAG, "Bluetooth device connected: " + device); + + if (isSteamController(device)) { + connectBluetoothDevice(device); + } + } + + // Bluetooth device was disconnected, remove from controller manager (if any) + if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) { + BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + Log.d(TAG, "Bluetooth device disconnected: " + device); + + disconnectBluetoothDevice(device); + } + } + }; + + private HIDDeviceManager(final Context context) { + mContext = context; + + HIDDeviceRegisterCallback(); + + mSharedPreferences = mContext.getSharedPreferences("hidapi", Context.MODE_PRIVATE); + mIsChromebook = mContext.getPackageManager().hasSystemFeature("org.chromium.arc.device_management"); + +// if (shouldClear) { +// SharedPreferences.Editor spedit = mSharedPreferences.edit(); +// spedit.clear(); +// spedit.commit(); +// } +// else + { + mNextDeviceId = mSharedPreferences.getInt("next_device_id", 0); + } + } + + public Context getContext() { + return mContext; + } + + public int getDeviceIDForIdentifier(String identifier) { + SharedPreferences.Editor spedit = mSharedPreferences.edit(); + + int result = mSharedPreferences.getInt(identifier, 0); + if (result == 0) { + result = mNextDeviceId++; + spedit.putInt("next_device_id", mNextDeviceId); + } + + spedit.putInt(identifier, result); + spedit.commit(); + return result; + } + + private void initializeUSB() { + mUsbManager = (UsbManager)mContext.getSystemService(Context.USB_SERVICE); + if (mUsbManager == null) { + return; + } + + /* + // Logging + for (UsbDevice device : mUsbManager.getDeviceList().values()) { + Log.i(TAG,"Path: " + device.getDeviceName()); + Log.i(TAG,"Manufacturer: " + device.getManufacturerName()); + Log.i(TAG,"Product: " + device.getProductName()); + Log.i(TAG,"ID: " + device.getDeviceId()); + Log.i(TAG,"Class: " + device.getDeviceClass()); + Log.i(TAG,"Protocol: " + device.getDeviceProtocol()); + Log.i(TAG,"Vendor ID " + device.getVendorId()); + Log.i(TAG,"Product ID: " + device.getProductId()); + Log.i(TAG,"Interface count: " + device.getInterfaceCount()); + Log.i(TAG,"---------------------------------------"); + + // Get interface details + for (int index = 0; index < device.getInterfaceCount(); index++) { + UsbInterface mUsbInterface = device.getInterface(index); + Log.i(TAG," ***** *****"); + Log.i(TAG," Interface index: " + index); + Log.i(TAG," Interface ID: " + mUsbInterface.getId()); + Log.i(TAG," Interface class: " + mUsbInterface.getInterfaceClass()); + Log.i(TAG," Interface subclass: " + mUsbInterface.getInterfaceSubclass()); + Log.i(TAG," Interface protocol: " + mUsbInterface.getInterfaceProtocol()); + Log.i(TAG," Endpoint count: " + mUsbInterface.getEndpointCount()); + + // Get endpoint details + for (int epi = 0; epi < mUsbInterface.getEndpointCount(); epi++) + { + UsbEndpoint mEndpoint = mUsbInterface.getEndpoint(epi); + Log.i(TAG," ++++ ++++ ++++"); + Log.i(TAG," Endpoint index: " + epi); + Log.i(TAG," Attributes: " + mEndpoint.getAttributes()); + Log.i(TAG," Direction: " + mEndpoint.getDirection()); + Log.i(TAG," Number: " + mEndpoint.getEndpointNumber()); + Log.i(TAG," Interval: " + mEndpoint.getInterval()); + Log.i(TAG," Packet size: " + mEndpoint.getMaxPacketSize()); + Log.i(TAG," Type: " + mEndpoint.getType()); + } + } + } + Log.i(TAG," No more devices connected."); + */ + + // Register for USB broadcasts and permission completions + IntentFilter filter = new IntentFilter(); + filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED); + filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); + filter.addAction(HIDDeviceManager.ACTION_USB_PERMISSION); + mContext.registerReceiver(mUsbBroadcast, filter); + + for (UsbDevice usbDevice : mUsbManager.getDeviceList().values()) { + handleUsbDeviceAttached(usbDevice); + } + } + + UsbManager getUSBManager() { + return mUsbManager; + } + + private void shutdownUSB() { + try { + mContext.unregisterReceiver(mUsbBroadcast); + } catch (Exception e) { + // We may not have registered, that's okay + } + } + + private boolean isHIDDeviceInterface(UsbDevice usbDevice, UsbInterface usbInterface) { + if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_HID) { + return true; + } + if (isXbox360Controller(usbDevice, usbInterface) || isXboxOneController(usbDevice, usbInterface)) { + return true; + } + return false; + } + + private boolean isXbox360Controller(UsbDevice usbDevice, UsbInterface usbInterface) { + final int XB360_IFACE_SUBCLASS = 93; + final int XB360_IFACE_PROTOCOL = 1; // Wired + final int XB360W_IFACE_PROTOCOL = 129; // Wireless + final int[] SUPPORTED_VENDORS = { + 0x0079, // GPD Win 2 + 0x044f, // Thrustmaster + 0x045e, // Microsoft + 0x046d, // Logitech + 0x056e, // Elecom + 0x06a3, // Saitek + 0x0738, // Mad Catz + 0x07ff, // Mad Catz + 0x0e6f, // PDP + 0x0f0d, // Hori + 0x1038, // SteelSeries + 0x11c9, // Nacon + 0x12ab, // Unknown + 0x1430, // RedOctane + 0x146b, // BigBen + 0x1532, // Razer Sabertooth + 0x15e4, // Numark + 0x162e, // Joytech + 0x1689, // Razer Onza + 0x1949, // Lab126, Inc. + 0x1bad, // Harmonix + 0x20d6, // PowerA + 0x24c6, // PowerA + 0x2c22, // Qanba + 0x2dc8, // 8BitDo + 0x9886, // ASTRO Gaming + }; + + if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC && + usbInterface.getInterfaceSubclass() == XB360_IFACE_SUBCLASS && + (usbInterface.getInterfaceProtocol() == XB360_IFACE_PROTOCOL || + usbInterface.getInterfaceProtocol() == XB360W_IFACE_PROTOCOL)) { + int vendor_id = usbDevice.getVendorId(); + for (int supportedVid : SUPPORTED_VENDORS) { + if (vendor_id == supportedVid) { + return true; + } + } + } + return false; + } + + private boolean isXboxOneController(UsbDevice usbDevice, UsbInterface usbInterface) { + final int XB1_IFACE_SUBCLASS = 71; + final int XB1_IFACE_PROTOCOL = 208; + final int[] SUPPORTED_VENDORS = { + 0x03f0, // HP + 0x044f, // Thrustmaster + 0x045e, // Microsoft + 0x0738, // Mad Catz + 0x0e6f, // PDP + 0x0f0d, // Hori + 0x10f5, // Turtle Beach + 0x1532, // Razer Wildcat + 0x20d6, // PowerA + 0x24c6, // PowerA + 0x2dc8, // 8BitDo + 0x2e24, // Hyperkin + }; + + if (usbInterface.getId() == 0 && + usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC && + usbInterface.getInterfaceSubclass() == XB1_IFACE_SUBCLASS && + usbInterface.getInterfaceProtocol() == XB1_IFACE_PROTOCOL) { + int vendor_id = usbDevice.getVendorId(); + for (int supportedVid : SUPPORTED_VENDORS) { + if (vendor_id == supportedVid) { + return true; + } + } + } + return false; + } + + private void handleUsbDeviceAttached(UsbDevice usbDevice) { + connectHIDDeviceUSB(usbDevice); + } + + private void handleUsbDeviceDetached(UsbDevice usbDevice) { + List devices = new ArrayList(); + for (HIDDevice device : mDevicesById.values()) { + if (usbDevice.equals(device.getDevice())) { + devices.add(device.getId()); + } + } + for (int id : devices) { + HIDDevice device = mDevicesById.get(id); + mDevicesById.remove(id); + device.shutdown(); + HIDDeviceDisconnected(id); + } + } + + private void handleUsbDevicePermission(UsbDevice usbDevice, boolean permission_granted) { + for (HIDDevice device : mDevicesById.values()) { + if (usbDevice.equals(device.getDevice())) { + boolean opened = false; + if (permission_granted) { + opened = device.open(); + } + HIDDeviceOpenResult(device.getId(), opened); + } + } + } + + private void connectHIDDeviceUSB(UsbDevice usbDevice) { + synchronized (this) { + int interface_mask = 0; + for (int interface_index = 0; interface_index < usbDevice.getInterfaceCount(); interface_index++) { + UsbInterface usbInterface = usbDevice.getInterface(interface_index); + if (isHIDDeviceInterface(usbDevice, usbInterface)) { + // Check to see if we've already added this interface + // This happens with the Xbox Series X controller which has a duplicate interface 0, which is inactive + int interface_id = usbInterface.getId(); + if ((interface_mask & (1 << interface_id)) != 0) { + continue; + } + interface_mask |= (1 << interface_id); + + HIDDeviceUSB device = new HIDDeviceUSB(this, usbDevice, interface_index); + int id = device.getId(); + mDevicesById.put(id, device); + HIDDeviceConnected(id, device.getIdentifier(), device.getVendorId(), device.getProductId(), device.getSerialNumber(), device.getVersion(), device.getManufacturerName(), device.getProductName(), usbInterface.getId(), usbInterface.getInterfaceClass(), usbInterface.getInterfaceSubclass(), usbInterface.getInterfaceProtocol()); + } + } + } + } + + private void initializeBluetooth() { + Log.d(TAG, "Initializing Bluetooth"); + + if (Build.VERSION.SDK_INT <= 30 /* Android 11.0 (R) */ && + mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) { + Log.d(TAG, "Couldn't initialize Bluetooth, missing android.permission.BLUETOOTH"); + return; + } + + if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE) || (Build.VERSION.SDK_INT < 18 /* Android 4.3 (JELLY_BEAN_MR2) */)) { + Log.d(TAG, "Couldn't initialize Bluetooth, this version of Android does not support Bluetooth LE"); + return; + } + + // Find bonded bluetooth controllers and create SteamControllers for them + mBluetoothManager = (BluetoothManager)mContext.getSystemService(Context.BLUETOOTH_SERVICE); + if (mBluetoothManager == null) { + // This device doesn't support Bluetooth. + return; + } + + BluetoothAdapter btAdapter = mBluetoothManager.getAdapter(); + if (btAdapter == null) { + // This device has Bluetooth support in the codebase, but has no available adapters. + return; + } + + // Get our bonded devices. + for (BluetoothDevice device : btAdapter.getBondedDevices()) { + + Log.d(TAG, "Bluetooth device available: " + device); + if (isSteamController(device)) { + connectBluetoothDevice(device); + } + + } + + // NOTE: These don't work on Chromebooks, to my undying dismay. + IntentFilter filter = new IntentFilter(); + filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED); + filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); + mContext.registerReceiver(mBluetoothBroadcast, filter); + + if (mIsChromebook) { + mHandler = new Handler(Looper.getMainLooper()); + mLastBluetoothDevices = new ArrayList(); + + // final HIDDeviceManager finalThis = this; + // mHandler.postDelayed(new Runnable() { + // @Override + // public void run() { + // finalThis.chromebookConnectionHandler(); + // } + // }, 5000); + } + } + + private void shutdownBluetooth() { + try { + mContext.unregisterReceiver(mBluetoothBroadcast); + } catch (Exception e) { + // We may not have registered, that's okay + } + } + + // Chromebooks do not pass along ACTION_ACL_CONNECTED / ACTION_ACL_DISCONNECTED properly. + // This function provides a sort of dummy version of that, watching for changes in the + // connected devices and attempting to add controllers as things change. + public void chromebookConnectionHandler() { + if (!mIsChromebook) { + return; + } + + ArrayList disconnected = new ArrayList(); + ArrayList connected = new ArrayList(); + + List currentConnected = mBluetoothManager.getConnectedDevices(BluetoothProfile.GATT); + + for (BluetoothDevice bluetoothDevice : currentConnected) { + if (!mLastBluetoothDevices.contains(bluetoothDevice)) { + connected.add(bluetoothDevice); + } + } + for (BluetoothDevice bluetoothDevice : mLastBluetoothDevices) { + if (!currentConnected.contains(bluetoothDevice)) { + disconnected.add(bluetoothDevice); + } + } + + mLastBluetoothDevices = currentConnected; + + for (BluetoothDevice bluetoothDevice : disconnected) { + disconnectBluetoothDevice(bluetoothDevice); + } + for (BluetoothDevice bluetoothDevice : connected) { + connectBluetoothDevice(bluetoothDevice); + } + + final HIDDeviceManager finalThis = this; + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + finalThis.chromebookConnectionHandler(); + } + }, 10000); + } + + public boolean connectBluetoothDevice(BluetoothDevice bluetoothDevice) { + Log.v(TAG, "connectBluetoothDevice device=" + bluetoothDevice); + synchronized (this) { + if (mBluetoothDevices.containsKey(bluetoothDevice)) { + Log.v(TAG, "Steam controller with address " + bluetoothDevice + " already exists, attempting reconnect"); + + HIDDeviceBLESteamController device = mBluetoothDevices.get(bluetoothDevice); + device.reconnect(); + + return false; + } + HIDDeviceBLESteamController device = new HIDDeviceBLESteamController(this, bluetoothDevice); + int id = device.getId(); + mBluetoothDevices.put(bluetoothDevice, device); + mDevicesById.put(id, device); + + // The Steam Controller will mark itself connected once initialization is complete + } + return true; + } + + public void disconnectBluetoothDevice(BluetoothDevice bluetoothDevice) { + synchronized (this) { + HIDDeviceBLESteamController device = mBluetoothDevices.get(bluetoothDevice); + if (device == null) + return; + + int id = device.getId(); + mBluetoothDevices.remove(bluetoothDevice); + mDevicesById.remove(id); + device.shutdown(); + HIDDeviceDisconnected(id); + } + } + + public boolean isSteamController(BluetoothDevice bluetoothDevice) { + // Sanity check. If you pass in a null device, by definition it is never a Steam Controller. + if (bluetoothDevice == null) { + return false; + } + + // If the device has no local name, we really don't want to try an equality check against it. + if (bluetoothDevice.getName() == null) { + return false; + } + + return bluetoothDevice.getName().equals("SteamController") && ((bluetoothDevice.getType() & BluetoothDevice.DEVICE_TYPE_LE) != 0); + } + + private void close() { + shutdownUSB(); + shutdownBluetooth(); + synchronized (this) { + for (HIDDevice device : mDevicesById.values()) { + device.shutdown(); + } + mDevicesById.clear(); + mBluetoothDevices.clear(); + HIDDeviceReleaseCallback(); + } + } + + public void setFrozen(boolean frozen) { + synchronized (this) { + for (HIDDevice device : mDevicesById.values()) { + device.setFrozen(frozen); + } + } + } + + ////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////////// + + private HIDDevice getDevice(int id) { + synchronized (this) { + HIDDevice result = mDevicesById.get(id); + if (result == null) { + Log.v(TAG, "No device for id: " + id); + Log.v(TAG, "Available devices: " + mDevicesById.keySet()); + } + return result; + } + } + + ////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////// JNI interface functions + ////////////////////////////////////////////////////////////////////////////////////////////////////// + + public boolean initialize(boolean usb, boolean bluetooth) { + Log.v(TAG, "initialize(" + usb + ", " + bluetooth + ")"); + + if (usb) { + initializeUSB(); + } + if (bluetooth) { + initializeBluetooth(); + } + return true; + } + + public boolean openDevice(int deviceID) { + Log.v(TAG, "openDevice deviceID=" + deviceID); + HIDDevice device = getDevice(deviceID); + if (device == null) { + HIDDeviceDisconnected(deviceID); + return false; + } + + // Look to see if this is a USB device and we have permission to access it + UsbDevice usbDevice = device.getDevice(); + if (usbDevice != null && !mUsbManager.hasPermission(usbDevice)) { + HIDDeviceOpenPending(deviceID); + try { + final int FLAG_MUTABLE = 0x02000000; // PendingIntent.FLAG_MUTABLE, but don't require SDK 31 + int flags; + if (Build.VERSION.SDK_INT >= 31 /* Android 12.0 (S) */) { + flags = FLAG_MUTABLE; + } else { + flags = 0; + } + mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, new Intent(HIDDeviceManager.ACTION_USB_PERMISSION), flags)); + } catch (Exception e) { + Log.v(TAG, "Couldn't request permission for USB device " + usbDevice); + HIDDeviceOpenResult(deviceID, false); + } + return false; + } + + try { + return device.open(); + } catch (Exception e) { + Log.e(TAG, "Got exception: " + Log.getStackTraceString(e)); + } + return false; + } + + public int sendOutputReport(int deviceID, byte[] report) { + try { + //Log.v(TAG, "sendOutputReport deviceID=" + deviceID + " length=" + report.length); + HIDDevice device; + device = getDevice(deviceID); + if (device == null) { + HIDDeviceDisconnected(deviceID); + return -1; + } + + return device.sendOutputReport(report); + } catch (Exception e) { + Log.e(TAG, "Got exception: " + Log.getStackTraceString(e)); + } + return -1; + } + + public int sendFeatureReport(int deviceID, byte[] report) { + try { + //Log.v(TAG, "sendFeatureReport deviceID=" + deviceID + " length=" + report.length); + HIDDevice device; + device = getDevice(deviceID); + if (device == null) { + HIDDeviceDisconnected(deviceID); + return -1; + } + + return device.sendFeatureReport(report); + } catch (Exception e) { + Log.e(TAG, "Got exception: " + Log.getStackTraceString(e)); + } + return -1; + } + + public boolean getFeatureReport(int deviceID, byte[] report) { + try { + //Log.v(TAG, "getFeatureReport deviceID=" + deviceID); + HIDDevice device; + device = getDevice(deviceID); + if (device == null) { + HIDDeviceDisconnected(deviceID); + return false; + } + + return device.getFeatureReport(report); + } catch (Exception e) { + Log.e(TAG, "Got exception: " + Log.getStackTraceString(e)); + } + return false; + } + + public void closeDevice(int deviceID) { + try { + Log.v(TAG, "closeDevice deviceID=" + deviceID); + HIDDevice device; + device = getDevice(deviceID); + if (device == null) { + HIDDeviceDisconnected(deviceID); + return; + } + + device.close(); + } catch (Exception e) { + Log.e(TAG, "Got exception: " + Log.getStackTraceString(e)); + } + } + + + ////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////// Native methods + ////////////////////////////////////////////////////////////////////////////////////////////////////// + + private native void HIDDeviceRegisterCallback(); + private native void HIDDeviceReleaseCallback(); + + native void HIDDeviceConnected(int deviceID, String identifier, int vendorId, int productId, String serial_number, int release_number, String manufacturer_string, String product_string, int interface_number, int interface_class, int interface_subclass, int interface_protocol); + native void HIDDeviceOpenPending(int deviceID); + native void HIDDeviceOpenResult(int deviceID, boolean opened); + native void HIDDeviceDisconnected(int deviceID); + + native void HIDDeviceInputReport(int deviceID, byte[] report); + native void HIDDeviceFeatureReport(int deviceID, byte[] report); +} diff --git a/app/src/main/engine/org/love2d/sdl/HIDDeviceUSB.java b/app/src/main/engine/org/love2d/sdl/HIDDeviceUSB.java new file mode 100644 index 000000000..dcd8daa4c --- /dev/null +++ b/app/src/main/engine/org/love2d/sdl/HIDDeviceUSB.java @@ -0,0 +1,309 @@ +package org.love2d.sdl; + +import android.hardware.usb.*; +import android.os.Build; +import android.util.Log; +import java.util.Arrays; + +class HIDDeviceUSB implements HIDDevice { + + private static final String TAG = "hidapi"; + + protected HIDDeviceManager mManager; + protected UsbDevice mDevice; + protected int mInterfaceIndex; + protected int mInterface; + protected int mDeviceId; + protected UsbDeviceConnection mConnection; + protected UsbEndpoint mInputEndpoint; + protected UsbEndpoint mOutputEndpoint; + protected InputThread mInputThread; + protected boolean mRunning; + protected boolean mFrozen; + + public HIDDeviceUSB(HIDDeviceManager manager, UsbDevice usbDevice, int interface_index) { + mManager = manager; + mDevice = usbDevice; + mInterfaceIndex = interface_index; + mInterface = mDevice.getInterface(mInterfaceIndex).getId(); + mDeviceId = manager.getDeviceIDForIdentifier(getIdentifier()); + mRunning = false; + } + + public String getIdentifier() { + return String.format("%s/%x/%x/%d", mDevice.getDeviceName(), mDevice.getVendorId(), mDevice.getProductId(), mInterfaceIndex); + } + + @Override + public int getId() { + return mDeviceId; + } + + @Override + public int getVendorId() { + return mDevice.getVendorId(); + } + + @Override + public int getProductId() { + return mDevice.getProductId(); + } + + @Override + public String getSerialNumber() { + String result = null; + if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) { + try { + result = mDevice.getSerialNumber(); + } + catch (SecurityException exception) { + //Log.w(TAG, "App permissions mean we cannot get serial number for device " + getDeviceName() + " message: " + exception.getMessage()); + } + } + if (result == null) { + result = ""; + } + return result; + } + + @Override + public int getVersion() { + return 0; + } + + @Override + public String getManufacturerName() { + String result = null; + if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) { + result = mDevice.getManufacturerName(); + } + if (result == null) { + result = String.format("%x", getVendorId()); + } + return result; + } + + @Override + public String getProductName() { + String result = null; + if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) { + result = mDevice.getProductName(); + } + if (result == null) { + result = String.format("%x", getProductId()); + } + return result; + } + + @Override + public UsbDevice getDevice() { + return mDevice; + } + + public String getDeviceName() { + return getManufacturerName() + " " + getProductName() + "(0x" + String.format("%x", getVendorId()) + "/0x" + String.format("%x", getProductId()) + ")"; + } + + @Override + public boolean open() { + mConnection = mManager.getUSBManager().openDevice(mDevice); + if (mConnection == null) { + Log.w(TAG, "Unable to open USB device " + getDeviceName()); + return false; + } + + // Force claim our interface + UsbInterface iface = mDevice.getInterface(mInterfaceIndex); + if (!mConnection.claimInterface(iface, true)) { + Log.w(TAG, "Failed to claim interfaces on USB device " + getDeviceName()); + close(); + return false; + } + + // Find the endpoints + for (int j = 0; j < iface.getEndpointCount(); j++) { + UsbEndpoint endpt = iface.getEndpoint(j); + switch (endpt.getDirection()) { + case UsbConstants.USB_DIR_IN: + if (mInputEndpoint == null) { + mInputEndpoint = endpt; + } + break; + case UsbConstants.USB_DIR_OUT: + if (mOutputEndpoint == null) { + mOutputEndpoint = endpt; + } + break; + } + } + + // Make sure the required endpoints were present + if (mInputEndpoint == null || mOutputEndpoint == null) { + Log.w(TAG, "Missing required endpoint on USB device " + getDeviceName()); + close(); + return false; + } + + // Start listening for input + mRunning = true; + mInputThread = new InputThread(); + mInputThread.start(); + + return true; + } + + @Override + public int sendFeatureReport(byte[] report) { + int res = -1; + int offset = 0; + int length = report.length; + boolean skipped_report_id = false; + byte report_number = report[0]; + + if (report_number == 0x0) { + ++offset; + --length; + skipped_report_id = true; + } + + res = mConnection.controlTransfer( + UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_OUT, + 0x09/*HID set_report*/, + (3/*HID feature*/ << 8) | report_number, + mInterface, + report, offset, length, + 1000/*timeout millis*/); + + if (res < 0) { + Log.w(TAG, "sendFeatureReport() returned " + res + " on device " + getDeviceName()); + return -1; + } + + if (skipped_report_id) { + ++length; + } + return length; + } + + @Override + public int sendOutputReport(byte[] report) { + int r = mConnection.bulkTransfer(mOutputEndpoint, report, report.length, 1000); + if (r != report.length) { + Log.w(TAG, "sendOutputReport() returned " + r + " on device " + getDeviceName()); + } + return r; + } + + @Override + public boolean getFeatureReport(byte[] report) { + int res = -1; + int offset = 0; + int length = report.length; + boolean skipped_report_id = false; + byte report_number = report[0]; + + if (report_number == 0x0) { + /* Offset the return buffer by 1, so that the report ID + will remain in byte 0. */ + ++offset; + --length; + skipped_report_id = true; + } + + res = mConnection.controlTransfer( + UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_IN, + 0x01/*HID get_report*/, + (3/*HID feature*/ << 8) | report_number, + mInterface, + report, offset, length, + 1000/*timeout millis*/); + + if (res < 0) { + Log.w(TAG, "getFeatureReport() returned " + res + " on device " + getDeviceName()); + return false; + } + + if (skipped_report_id) { + ++res; + ++length; + } + + byte[] data; + if (res == length) { + data = report; + } else { + data = Arrays.copyOfRange(report, 0, res); + } + mManager.HIDDeviceFeatureReport(mDeviceId, data); + + return true; + } + + @Override + public void close() { + mRunning = false; + if (mInputThread != null) { + while (mInputThread.isAlive()) { + mInputThread.interrupt(); + try { + mInputThread.join(); + } catch (InterruptedException e) { + // Keep trying until we're done + } + } + mInputThread = null; + } + if (mConnection != null) { + UsbInterface iface = mDevice.getInterface(mInterfaceIndex); + mConnection.releaseInterface(iface); + mConnection.close(); + mConnection = null; + } + } + + @Override + public void shutdown() { + close(); + mManager = null; + } + + @Override + public void setFrozen(boolean frozen) { + mFrozen = frozen; + } + + protected class InputThread extends Thread { + @Override + public void run() { + int packetSize = mInputEndpoint.getMaxPacketSize(); + byte[] packet = new byte[packetSize]; + while (mRunning) { + int r; + try + { + r = mConnection.bulkTransfer(mInputEndpoint, packet, packetSize, 1000); + } + catch (Exception e) + { + Log.v(TAG, "Exception in UsbDeviceConnection bulktransfer: " + e); + break; + } + if (r < 0) { + // Could be a timeout or an I/O error + } + if (r > 0) { + byte[] data; + if (r == packetSize) { + data = packet; + } else { + data = Arrays.copyOfRange(packet, 0, r); + } + + if (!mFrozen) { + mManager.HIDDeviceInputReport(mDeviceId, data); + } + } + } + } + } +} diff --git a/app/src/main/engine/org/love2d/sdl/SDL.java b/app/src/main/engine/org/love2d/sdl/SDL.java new file mode 100644 index 000000000..1c79abde4 --- /dev/null +++ b/app/src/main/engine/org/love2d/sdl/SDL.java @@ -0,0 +1,86 @@ +package org.love2d.sdl; + +import android.content.Context; + +import java.lang.Class; +import java.lang.reflect.Method; + +/** + SDL library initialization +*/ +public class SDL { + + // This function should be called first and sets up the native code + // so it can call into the Java classes + public static void setupJNI() { + SDLActivity.nativeSetupJNI(); + SDLAudioManager.nativeSetupJNI(); + SDLControllerManager.nativeSetupJNI(); + } + + // This function should be called each time the activity is started + public static void initialize() { + setContext(null); + + SDLActivity.initialize(); + SDLAudioManager.initialize(); + SDLControllerManager.initialize(); + } + + // This function stores the current activity (SDL or not) + public static void setContext(Context context) { + SDLAudioManager.setContext(context); + mContext = context; + } + + public static Context getContext() { + return mContext; + } + + public static void loadLibrary(String libraryName) throws UnsatisfiedLinkError, SecurityException, NullPointerException { + + if (libraryName == null) { + throw new NullPointerException("No library name provided."); + } + + try { + // Let's see if we have ReLinker available in the project. This is necessary for + // some projects that have huge numbers of local libraries bundled, and thus may + // trip a bug in Android's native library loader which ReLinker works around. (If + // loadLibrary works properly, ReLinker will simply use the normal Android method + // internally.) + // + // To use ReLinker, just add it as a dependency. For more information, see + // https://github.com/KeepSafe/ReLinker for ReLinker's repository. + // + Class relinkClass = mContext.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker"); + Class relinkListenerClass = mContext.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker$LoadListener"); + Class contextClass = mContext.getClassLoader().loadClass("android.content.Context"); + Class stringClass = mContext.getClassLoader().loadClass("java.lang.String"); + + // Get a 'force' instance of the ReLinker, so we can ensure libraries are reinstalled if + // they've changed during updates. + Method forceMethod = relinkClass.getDeclaredMethod("force"); + Object relinkInstance = forceMethod.invoke(null); + Class relinkInstanceClass = relinkInstance.getClass(); + + // Actually load the library! + Method loadMethod = relinkInstanceClass.getDeclaredMethod("loadLibrary", contextClass, stringClass, stringClass, relinkListenerClass); + loadMethod.invoke(relinkInstance, mContext, libraryName, null, null); + } + catch (final Throwable e) { + // Fall back + try { + System.loadLibrary(libraryName); + } + catch (final UnsatisfiedLinkError ule) { + throw ule; + } + catch (final SecurityException se) { + throw se; + } + } + } + + protected static Context mContext; +} diff --git a/app/src/main/engine/org/love2d/sdl/SDLActivity.java b/app/src/main/engine/org/love2d/sdl/SDLActivity.java new file mode 100644 index 000000000..f703e5ea6 --- /dev/null +++ b/app/src/main/engine/org/love2d/sdl/SDLActivity.java @@ -0,0 +1,2148 @@ +package org.love2d.sdl; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.UiModeManager; +import android.content.ClipboardManager; +import android.content.ClipData; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.res.Configuration; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; +import android.hardware.Sensor; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.text.Editable; +import android.text.InputType; +import android.text.Selection; +import android.util.DisplayMetrics; +import android.util.Log; +import android.util.SparseArray; +import android.view.Display; +import android.view.Gravity; +import android.view.InputDevice; +import android.view.KeyEvent; +import android.view.PointerIcon; +import android.view.Surface; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; +import android.view.inputmethod.BaseInputConnection; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; +import android.view.inputmethod.InputMethodManager; +import android.widget.Button; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import android.widget.TextView; +import android.widget.Toast; + +import java.util.Hashtable; +import java.util.Locale; + + +/** + SDL Activity +*/ +public class SDLActivity extends Activity implements View.OnSystemUiVisibilityChangeListener { + private static final String TAG = "SDL"; + private static final int SDL_MAJOR_VERSION = 2; + private static final int SDL_MINOR_VERSION = 28; + private static final int SDL_MICRO_VERSION = 5; +/* + // Display InputType.SOURCE/CLASS of events and devices + // + // SDLActivity.debugSource(device.getSources(), "device[" + device.getName() + "]"); + // SDLActivity.debugSource(event.getSource(), "event"); + public static void debugSource(int sources, String prefix) { + int s = sources; + int s_copy = sources; + String cls = ""; + String src = ""; + int tst = 0; + int FLAG_TAINTED = 0x80000000; + + if ((s & InputDevice.SOURCE_CLASS_BUTTON) != 0) cls += " BUTTON"; + if ((s & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) cls += " JOYSTICK"; + if ((s & InputDevice.SOURCE_CLASS_POINTER) != 0) cls += " POINTER"; + if ((s & InputDevice.SOURCE_CLASS_POSITION) != 0) cls += " POSITION"; + if ((s & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) cls += " TRACKBALL"; + + + int s2 = s_copy & ~InputDevice.SOURCE_ANY; // keep class bits + s2 &= ~( InputDevice.SOURCE_CLASS_BUTTON + | InputDevice.SOURCE_CLASS_JOYSTICK + | InputDevice.SOURCE_CLASS_POINTER + | InputDevice.SOURCE_CLASS_POSITION + | InputDevice.SOURCE_CLASS_TRACKBALL); + + if (s2 != 0) cls += "Some_Unkown"; + + s2 = s_copy & InputDevice.SOURCE_ANY; // keep source only, no class; + + if (Build.VERSION.SDK_INT >= 23) { + tst = InputDevice.SOURCE_BLUETOOTH_STYLUS; + if ((s & tst) == tst) src += " BLUETOOTH_STYLUS"; + s2 &= ~tst; + } + + tst = InputDevice.SOURCE_DPAD; + if ((s & tst) == tst) src += " DPAD"; + s2 &= ~tst; + + tst = InputDevice.SOURCE_GAMEPAD; + if ((s & tst) == tst) src += " GAMEPAD"; + s2 &= ~tst; + + if (Build.VERSION.SDK_INT >= 21) { + tst = InputDevice.SOURCE_HDMI; + if ((s & tst) == tst) src += " HDMI"; + s2 &= ~tst; + } + + tst = InputDevice.SOURCE_JOYSTICK; + if ((s & tst) == tst) src += " JOYSTICK"; + s2 &= ~tst; + + tst = InputDevice.SOURCE_KEYBOARD; + if ((s & tst) == tst) src += " KEYBOARD"; + s2 &= ~tst; + + tst = InputDevice.SOURCE_MOUSE; + if ((s & tst) == tst) src += " MOUSE"; + s2 &= ~tst; + + if (Build.VERSION.SDK_INT >= 26) { + tst = InputDevice.SOURCE_MOUSE_RELATIVE; + if ((s & tst) == tst) src += " MOUSE_RELATIVE"; + s2 &= ~tst; + + tst = InputDevice.SOURCE_ROTARY_ENCODER; + if ((s & tst) == tst) src += " ROTARY_ENCODER"; + s2 &= ~tst; + } + tst = InputDevice.SOURCE_STYLUS; + if ((s & tst) == tst) src += " STYLUS"; + s2 &= ~tst; + + tst = InputDevice.SOURCE_TOUCHPAD; + if ((s & tst) == tst) src += " TOUCHPAD"; + s2 &= ~tst; + + tst = InputDevice.SOURCE_TOUCHSCREEN; + if ((s & tst) == tst) src += " TOUCHSCREEN"; + s2 &= ~tst; + + if (Build.VERSION.SDK_INT >= 18) { + tst = InputDevice.SOURCE_TOUCH_NAVIGATION; + if ((s & tst) == tst) src += " TOUCH_NAVIGATION"; + s2 &= ~tst; + } + + tst = InputDevice.SOURCE_TRACKBALL; + if ((s & tst) == tst) src += " TRACKBALL"; + s2 &= ~tst; + + tst = InputDevice.SOURCE_ANY; + if ((s & tst) == tst) src += " ANY"; + s2 &= ~tst; + + if (s == FLAG_TAINTED) src += " FLAG_TAINTED"; + s2 &= ~FLAG_TAINTED; + + if (s2 != 0) src += " Some_Unkown"; + + Log.v(TAG, prefix + "int=" + s_copy + " CLASS={" + cls + " } source(s):" + src); + } +*/ + + public static boolean mIsResumedCalled, mHasFocus; + public static final boolean mHasMultiWindow = (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */); + + // Cursor types + // private static final int SDL_SYSTEM_CURSOR_NONE = -1; + private static final int SDL_SYSTEM_CURSOR_ARROW = 0; + private static final int SDL_SYSTEM_CURSOR_IBEAM = 1; + private static final int SDL_SYSTEM_CURSOR_WAIT = 2; + private static final int SDL_SYSTEM_CURSOR_CROSSHAIR = 3; + private static final int SDL_SYSTEM_CURSOR_WAITARROW = 4; + private static final int SDL_SYSTEM_CURSOR_SIZENWSE = 5; + private static final int SDL_SYSTEM_CURSOR_SIZENESW = 6; + private static final int SDL_SYSTEM_CURSOR_SIZEWE = 7; + private static final int SDL_SYSTEM_CURSOR_SIZENS = 8; + private static final int SDL_SYSTEM_CURSOR_SIZEALL = 9; + private static final int SDL_SYSTEM_CURSOR_NO = 10; + private static final int SDL_SYSTEM_CURSOR_HAND = 11; + + protected static final int SDL_ORIENTATION_UNKNOWN = 0; + protected static final int SDL_ORIENTATION_LANDSCAPE = 1; + protected static final int SDL_ORIENTATION_LANDSCAPE_FLIPPED = 2; + protected static final int SDL_ORIENTATION_PORTRAIT = 3; + protected static final int SDL_ORIENTATION_PORTRAIT_FLIPPED = 4; + + protected static int mCurrentOrientation; + protected static Locale mCurrentLocale; + + // Handle the state of the native layer + public enum NativeState { + INIT, RESUMED, PAUSED + } + + public static NativeState mNextNativeState; + public static NativeState mCurrentNativeState; + + public static boolean mExitCalledFromJava; // love2d-mod: allow restarting of the native thread + + /** If shared libraries (e.g. SDL or the native application) could not be loaded. */ + public static boolean mBrokenLibraries = true; + + // Main components + protected static SDLActivity mSingleton; + protected static SDLSurface mSurface; + protected static DummyEdit mTextEdit; + protected static boolean mScreenKeyboardShown; + protected static ViewGroup mLayout; + protected static SDLClipboardHandler mClipboardHandler; + protected static Hashtable mCursors; + protected static int mLastCursorID; + protected static SDLGenericMotionListener_API12 mMotionListener; + protected static HIDDeviceManager mHIDDeviceManager; + + // This is what SDL runs in. It invokes SDL_main(), eventually + protected static Thread mSDLThread; + + protected static SDLGenericMotionListener_API12 getMotionListener() { + if (mMotionListener == null) { + if (Build.VERSION.SDK_INT >= 26 /* Android 8.0 (O) */) { + mMotionListener = new SDLGenericMotionListener_API26(); + } else if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) { + mMotionListener = new SDLGenericMotionListener_API24(); + } else { + mMotionListener = new SDLGenericMotionListener_API12(); + } + } + + return mMotionListener; + } + + /** + * This method returns the name of the shared object with the application entry point + * It can be overridden by derived classes. + */ + protected String getMainSharedObject() { + String library; + String[] libraries = SDLActivity.mSingleton.getLibraries(); + if (libraries.length > 0) { + library = "lib" + libraries[libraries.length - 1] + ".so"; + } else { + library = "libmain.so"; + } + return getContext().getApplicationInfo().nativeLibraryDir + "/" + library; + } + + /** + * This method returns the name of the application entry point + * It can be overridden by derived classes. + */ + protected String getMainFunction() { + return "SDL_main"; + } + + /** + * This method is called by SDL before loading the native shared libraries. + * It can be overridden to provide names of shared libraries to be loaded. + * The default implementation returns the defaults. It never returns null. + * An array returned by a new implementation must at least contain "SDL2". + * Also keep in mind that the order the libraries are loaded may matter. + * @return names of shared libraries to be loaded (e.g. "SDL2", "main"). + */ + protected String[] getLibraries() { + return new String[] { + "SDL2", + // "SDL2_image", + // "SDL2_mixer", + // "SDL2_net", + // "SDL2_ttf", + "main" + }; + } + + // Load the .so + public void loadLibraries() { + for (String lib : getLibraries()) { + SDL.loadLibrary(lib); + } + } + + /** + * This method is called by SDL before starting the native application thread. + * It can be overridden to provide the arguments after the application name. + * The default implementation returns an empty array. It never returns null. + * @return arguments for the native application. + */ + protected String[] getArguments() { + return new String[0]; + } + + public static void initialize() { + // The static nature of the singleton and Android quirkyness force us to initialize everything here + // Otherwise, when exiting the app and returning to it, these variables *keep* their pre exit values + mSingleton = null; + mSurface = null; + mTextEdit = null; + mLayout = null; + mClipboardHandler = null; + mCursors = new Hashtable(); + mLastCursorID = 0; + mSDLThread = null; + mExitCalledFromJava = false; // love2d-mod: allow restarting of the native thread + mIsResumedCalled = false; + mHasFocus = true; + mNextNativeState = NativeState.INIT; + mCurrentNativeState = NativeState.INIT; + } + + protected SDLSurface createSDLSurface(Context context) { + return new SDLSurface(context); + } + + // Setup + @Override + protected void onCreate(Bundle savedInstanceState) { + Log.v(TAG, "Device: " + Build.DEVICE); + Log.v(TAG, "Model: " + Build.MODEL); + Log.v(TAG, "onCreate()"); + super.onCreate(savedInstanceState); + + try { + Thread.currentThread().setName("SDLActivity"); + } catch (Exception e) { + Log.v(TAG, "modify thread properties failed " + e.toString()); + } + + // Load shared libraries + String errorMsgBrokenLib = ""; + try { + loadLibraries(); + mBrokenLibraries = false; /* success */ + } catch(UnsatisfiedLinkError e) { + System.err.println(e.getMessage()); + mBrokenLibraries = true; + errorMsgBrokenLib = e.getMessage(); + } catch(Exception e) { + System.err.println(e.getMessage()); + mBrokenLibraries = true; + errorMsgBrokenLib = e.getMessage(); + } + + if (!mBrokenLibraries) { + String expected_version = String.valueOf(SDL_MAJOR_VERSION) + "." + + String.valueOf(SDL_MINOR_VERSION) + "." + + String.valueOf(SDL_MICRO_VERSION); + String version = nativeGetVersion(); + if (!version.equals(expected_version)) { + mBrokenLibraries = true; + errorMsgBrokenLib = "SDL C/Java version mismatch (expected " + expected_version + ", got " + version + ")"; + } + } + + if (mBrokenLibraries) { + mSingleton = this; + AlertDialog.Builder dlgAlert = new AlertDialog.Builder(this); + dlgAlert.setMessage("An error occurred while trying to start the application. Please try again and/or reinstall." + + System.getProperty("line.separator") + + System.getProperty("line.separator") + + "Error: " + errorMsgBrokenLib); + dlgAlert.setTitle("SDL Error"); + dlgAlert.setPositiveButton("Exit", + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog,int id) { + // if this button is clicked, close current activity + SDLActivity.mSingleton.finish(); + } + }); + dlgAlert.setCancelable(false); + dlgAlert.create().show(); + + return; + } + + startNative(); // love2d-mod: allow restarting of the native thread + } + + // love2d-mod-start: allow restarting of the native thread + public void startNative() { + boolean hadSDLThread = SDLActivity.mSDLThread != null; + + // Set up JNI + SDL.setupJNI(); + + // Initialize state + SDL.initialize(); + + // So we can call stuff from static callbacks + mSingleton = this; + SDL.setContext(this); + + mClipboardHandler = new SDLClipboardHandler(); + + mHIDDeviceManager = HIDDeviceManager.acquire(this); + + // Set up the surface + mSurface = createSDLSurface(this); + + mLayout = new RelativeLayout(this); + mLayout.addView(mSurface); + + // Get our current screen orientation and pass it down. + mCurrentOrientation = SDLActivity.getCurrentOrientation(); + // Only record current orientation + SDLActivity.onNativeOrientationChanged(mCurrentOrientation); + + try { + if (Build.VERSION.SDK_INT < 24 /* Android 7.0 (N) */) { + mCurrentLocale = getContext().getResources().getConfiguration().locale; + } else { + mCurrentLocale = getContext().getResources().getConfiguration().getLocales().get(0); + } + } catch(Exception ignored) { + } + + setContentView(mLayout); + + setWindowStyle(false); + + getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(this); + + // Get filename from "Open with" of another application + Intent intent = getIntent(); + if (intent != null && intent.getData() != null) { + String filename = intent.getData().getPath(); + if (filename != null) { + Log.v(TAG, "Got filename: " + filename); + SDLActivity.onNativeDropFile(filename); + } + } + + if (hadSDLThread) { + resumeNativeThread(); + } + } + // love2d-mod-end: allow restarting of the native thread + + protected void pauseNativeThread() { + mNextNativeState = NativeState.PAUSED; + mIsResumedCalled = false; + + if (SDLActivity.mBrokenLibraries) { + return; + } + + SDLActivity.handleNativeState(); + } + + protected void resumeNativeThread() { + mNextNativeState = NativeState.RESUMED; + mIsResumedCalled = true; + + if (SDLActivity.mBrokenLibraries) { + return; + } + + SDLActivity.handleNativeState(); + } + + // Events + @Override + protected void onPause() { + Log.v(TAG, "onPause()"); + super.onPause(); + + if (mHIDDeviceManager != null) { + mHIDDeviceManager.setFrozen(true); + } + if (!mHasMultiWindow) { + pauseNativeThread(); + } + } + + @Override + protected void onResume() { + Log.v(TAG, "onResume()"); + super.onResume(); + + if (mHIDDeviceManager != null) { + mHIDDeviceManager.setFrozen(false); + } + if (!mHasMultiWindow) { + resumeNativeThread(); + } + } + + @Override + protected void onStop() { + Log.v(TAG, "onStop()"); + super.onStop(); + if (mHasMultiWindow) { + pauseNativeThread(); + } + } + + @Override + protected void onStart() { + Log.v(TAG, "onStart()"); + super.onStart(); + if (mHasMultiWindow) { + resumeNativeThread(); + } + } + + public static int getCurrentOrientation() { + int result = SDL_ORIENTATION_UNKNOWN; + + Activity activity = (Activity)getContext(); + if (activity == null) { + return result; + } + Display display = activity.getWindowManager().getDefaultDisplay(); + + switch (display.getRotation()) { + case Surface.ROTATION_0: + result = SDL_ORIENTATION_PORTRAIT; + break; + + case Surface.ROTATION_90: + result = SDL_ORIENTATION_LANDSCAPE; + break; + + case Surface.ROTATION_180: + result = SDL_ORIENTATION_PORTRAIT_FLIPPED; + break; + + case Surface.ROTATION_270: + result = SDL_ORIENTATION_LANDSCAPE_FLIPPED; + break; + } + + return result; + } + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + Log.v(TAG, "onWindowFocusChanged(): " + hasFocus); + + if (SDLActivity.mBrokenLibraries) { + return; + } + + mHasFocus = hasFocus; + if (hasFocus) { + mNextNativeState = NativeState.RESUMED; + SDLActivity.getMotionListener().reclaimRelativeMouseModeIfNeeded(); + + SDLActivity.handleNativeState(); + nativeFocusChanged(true); + + } else { + nativeFocusChanged(false); + if (!mHasMultiWindow) { + mNextNativeState = NativeState.PAUSED; + SDLActivity.handleNativeState(); + } + } + } + + @Override + public void onLowMemory() { + Log.v(TAG, "onLowMemory()"); + super.onLowMemory(); + + if (SDLActivity.mBrokenLibraries) { + return; + } + + SDLActivity.nativeLowMemory(); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + Log.v(TAG, "onConfigurationChanged()"); + super.onConfigurationChanged(newConfig); + + if (SDLActivity.mBrokenLibraries) { + return; + } + + if (mCurrentLocale == null || !mCurrentLocale.equals(newConfig.locale)) { + mCurrentLocale = newConfig.locale; + SDLActivity.onNativeLocaleChanged(); + } + } + + @Override + protected void onDestroy() { + Log.v(TAG, "onDestroy()"); + + if (mHIDDeviceManager != null) { + HIDDeviceManager.release(mHIDDeviceManager); + mHIDDeviceManager = null; + } + + SDLAudioManager.release(this); + + if (SDLActivity.mBrokenLibraries) { + super.onDestroy(); + return; + } + + appQuitFinish(); // love2d-mod: allow restarting of the native thread + super.onDestroy(); + } + + // love2d-mod-start: allow restarting of the native thread + public void resetNative() { + Log.v("SDL", "resetNative()"); + + SDLActivity.mExitCalledFromJava = true; // love2d-mod: allow restarting of the native thread + + appQuitFinish(); + } + + private void appQuitFinish() { + if (SDLActivity.mSDLThread != null) { + + // Send Quit event to "SDLThread" thread + SDLActivity.nativeSendQuit(); + + // Wait for "SDLThread" thread to end + try { + SDLActivity.mSDLThread.join(); + } catch(Exception e) { + Log.v(TAG, "Problem stopping SDLThread: " + e); + } + } + + SDLActivity.nativeQuit(); + } + // love2d-mod-end: allow restarting of the native thread + + @Override + public void onBackPressed() { + // Check if we want to block the back button in case of mouse right click. + // + // If we do, the normal hardware back button will no longer work and people have to use home, + // but the mouse right click will work. + // + boolean trapBack = SDLActivity.nativeGetHintBoolean("SDL_ANDROID_TRAP_BACK_BUTTON", false); + if (trapBack) { + // Exit and let the mouse handler handle this button (if appropriate) + return; + } + + // Default system back button behavior. + if (!isFinishing()) { + super.onBackPressed(); + } + } + + // Called by JNI from SDL. + public static void manualBackButton() { + mSingleton.pressBackButton(); + } + + // Used to get us onto the activity's main thread + public void pressBackButton() { + runOnUiThread(new Runnable() { + @Override + public void run() { + if (!SDLActivity.this.isFinishing()) { + SDLActivity.this.superOnBackPressed(); + } + } + }); + } + + // Used to access the system back behavior. + public void superOnBackPressed() { + super.onBackPressed(); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + + if (SDLActivity.mBrokenLibraries) { + return false; + } + + int keyCode = event.getKeyCode(); + // Ignore certain special keys so they're handled by Android + if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || + keyCode == KeyEvent.KEYCODE_VOLUME_UP || + keyCode == KeyEvent.KEYCODE_CAMERA || + keyCode == KeyEvent.KEYCODE_ZOOM_IN || /* API 11 */ + keyCode == KeyEvent.KEYCODE_ZOOM_OUT /* API 11 */ + ) { + return false; + } + return super.dispatchKeyEvent(event); + } + + /* Transition to next state */ + public static void handleNativeState() { + + if (mNextNativeState == mCurrentNativeState) { + // Already in same state, discard. + return; + } + + // Try a transition to init state + if (mNextNativeState == NativeState.INIT) { + + mCurrentNativeState = mNextNativeState; + return; + } + + // Try a transition to paused state + if (mNextNativeState == NativeState.PAUSED) { + if (mSDLThread != null) { + nativePause(); + } + if (mSurface != null) { + mSurface.handlePause(); + } + mCurrentNativeState = mNextNativeState; + return; + } + + // Try a transition to resumed state + if (mNextNativeState == NativeState.RESUMED) { + if (mSurface.mIsSurfaceReady && mHasFocus && mIsResumedCalled) { + if (mSDLThread == null) { + // This is the entry point to the C app. + // Start up the C app thread and enable sensor input for the first time + // FIXME: Why aren't we enabling sensor input at start? + + mSDLThread = new Thread(new SDLMain(), "SDLThread"); + mSurface.enableSensor(Sensor.TYPE_ACCELEROMETER, true); + mSDLThread.start(); + + // No nativeResume(), don't signal Android_ResumeSem + } else { + nativeResume(); + } + mSurface.handleResume(); + + mCurrentNativeState = mNextNativeState; + } + } + } + + // Messages from the SDLMain thread + static final int COMMAND_CHANGE_TITLE = 1; + static final int COMMAND_CHANGE_WINDOW_STYLE = 2; + static final int COMMAND_TEXTEDIT_HIDE = 3; + static final int COMMAND_SET_KEEP_SCREEN_ON = 5; + + protected static final int COMMAND_USER = 0x8000; + + protected static boolean mFullscreenModeActive; + + /** + * This method is called by SDL if SDL did not handle a message itself. + * This happens if a received message contains an unsupported command. + * Method can be overwritten to handle Messages in a different class. + * @param command the command of the message. + * @param param the parameter of the message. May be null. + * @return if the message was handled in overridden method. + */ + protected boolean onUnhandledMessage(int command, Object param) { + return false; + } + + /** + * A Handler class for Messages from native SDL applications. + * It uses current Activities as target (e.g. for the title). + * static to prevent implicit references to enclosing object. + */ + protected static class SDLCommandHandler extends Handler { + @Override + public void handleMessage(Message msg) { + Context context = SDL.getContext(); + if (context == null) { + Log.e(TAG, "error handling message, getContext() returned null"); + return; + } + switch (msg.arg1) { + case COMMAND_CHANGE_TITLE: + if (context instanceof Activity) { + ((Activity) context).setTitle((String)msg.obj); + } else { + Log.e(TAG, "error handling message, getContext() returned no Activity"); + } + break; + case COMMAND_CHANGE_WINDOW_STYLE: + if (Build.VERSION.SDK_INT >= 19 /* Android 4.4 (KITKAT) */) { + if (context instanceof Activity) { + Window window = ((Activity) context).getWindow(); + if (window != null) { + if ((msg.obj instanceof Integer) && ((Integer) msg.obj != 0)) { + int flags = View.SYSTEM_UI_FLAG_FULLSCREEN | + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | + View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | + View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | + View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | + View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.INVISIBLE; + window.getDecorView().setSystemUiVisibility(flags); + window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + window.clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); + SDLActivity.mFullscreenModeActive = true; + } else { + int flags = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_VISIBLE; + window.getDecorView().setSystemUiVisibility(flags); + window.addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); + window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + SDLActivity.mFullscreenModeActive = false; + } + } + } else { + Log.e(TAG, "error handling message, getContext() returned no Activity"); + } + } + break; + case COMMAND_TEXTEDIT_HIDE: + if (mTextEdit != null) { + // Note: On some devices setting view to GONE creates a flicker in landscape. + // Setting the View's sizes to 0 is similar to GONE but without the flicker. + // The sizes will be set to useful values when the keyboard is shown again. + mTextEdit.setLayoutParams(new RelativeLayout.LayoutParams(0, 0)); + + InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(mTextEdit.getWindowToken(), 0); + + mScreenKeyboardShown = false; + + mSurface.requestFocus(); + } + break; + case COMMAND_SET_KEEP_SCREEN_ON: + { + if (context instanceof Activity) { + Window window = ((Activity) context).getWindow(); + if (window != null) { + if ((msg.obj instanceof Integer) && ((Integer) msg.obj != 0)) { + window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } else { + window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } + } + } + break; + } + default: + if ((context instanceof SDLActivity) && !((SDLActivity) context).onUnhandledMessage(msg.arg1, msg.obj)) { + Log.e(TAG, "error handling message, command is " + msg.arg1); + } + } + } + } + + // Handler for the messages + Handler commandHandler = new SDLCommandHandler(); + + // Send a message from the SDLMain thread + boolean sendCommand(int command, Object data) { + Message msg = commandHandler.obtainMessage(); + msg.arg1 = command; + msg.obj = data; + boolean result = commandHandler.sendMessage(msg); + + if (Build.VERSION.SDK_INT >= 19 /* Android 4.4 (KITKAT) */) { + if (command == COMMAND_CHANGE_WINDOW_STYLE) { + // Ensure we don't return until the resize has actually happened, + // or 500ms have passed. + + boolean bShouldWait = false; + + if (data instanceof Integer) { + // Let's figure out if we're already laid out fullscreen or not. + Display display = ((WindowManager) getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); + DisplayMetrics realMetrics = new DisplayMetrics(); + display.getRealMetrics(realMetrics); + + boolean bFullscreenLayout = ((realMetrics.widthPixels == mSurface.getWidth()) && + (realMetrics.heightPixels == mSurface.getHeight())); + + if ((Integer) data == 1) { + // If we aren't laid out fullscreen or actively in fullscreen mode already, we're going + // to change size and should wait for surfaceChanged() before we return, so the size + // is right back in native code. If we're already laid out fullscreen, though, we're + // not going to change size even if we change decor modes, so we shouldn't wait for + // surfaceChanged() -- which may not even happen -- and should return immediately. + bShouldWait = !bFullscreenLayout; + } else { + // If we're laid out fullscreen (even if the status bar and nav bar are present), + // or are actively in fullscreen, we're going to change size and should wait for + // surfaceChanged before we return, so the size is right back in native code. + bShouldWait = bFullscreenLayout; + } + } + + if (bShouldWait && (SDLActivity.getContext() != null)) { + // We'll wait for the surfaceChanged() method, which will notify us + // when called. That way, we know our current size is really the + // size we need, instead of grabbing a size that's still got + // the navigation and/or status bars before they're hidden. + // + // We'll wait for up to half a second, because some devices + // take a surprisingly long time for the surface resize, but + // then we'll just give up and return. + // + synchronized (SDLActivity.getContext()) { + try { + SDLActivity.getContext().wait(500); + } catch (InterruptedException ie) { + ie.printStackTrace(); + } + } + } + } + } + + return result; + } + + // C functions we call + public static native String nativeGetVersion(); + public static native int nativeSetupJNI(); + public static native int nativeRunMain(String library, String function, Object arguments); + public static native void nativeLowMemory(); + public static native void nativeSendQuit(); + public static native void nativeQuit(); + public static native void nativePause(); + public static native void nativeResume(); + public static native void nativeFocusChanged(boolean hasFocus); + public static native void onNativeDropFile(String filename); + public static native void nativeSetScreenResolution(int surfaceWidth, int surfaceHeight, int deviceWidth, int deviceHeight, float rate); + public static native void onNativeResize(); + public static native void onNativeKeyDown(int keycode); + public static native void onNativeKeyUp(int keycode); + public static native boolean onNativeSoftReturnKey(); + public static native void onNativeKeyboardFocusLost(); + public static native void onNativeMouse(int button, int action, float x, float y, boolean relative); + public static native void onNativeTouch(int touchDevId, int pointerFingerId, + int action, float x, + float y, float p); + public static native void onNativeAccel(float x, float y, float z); + public static native void onNativeClipboardChanged(); + public static native void onNativeSurfaceCreated(); + public static native void onNativeSurfaceChanged(); + public static native void onNativeSurfaceDestroyed(); + public static native String nativeGetHint(String name); + public static native boolean nativeGetHintBoolean(String name, boolean default_value); + public static native void nativeSetenv(String name, String value); + public static native void onNativeOrientationChanged(int orientation); + public static native void nativeAddTouch(int touchId, String name); + public static native void nativePermissionResult(int requestCode, boolean result); + public static native void onNativeLocaleChanged(); + + /** + * This method is called by SDL using JNI. + */ + public static boolean setActivityTitle(String title) { + // Called from SDLMain() thread and can't directly affect the view + return mSingleton.sendCommand(COMMAND_CHANGE_TITLE, title); + } + + /** + * This method is called by SDL using JNI. + */ + public static void setWindowStyle(boolean fullscreen) { + // Called from SDLMain() thread and can't directly affect the view + mSingleton.sendCommand(COMMAND_CHANGE_WINDOW_STYLE, fullscreen ? 1 : 0); + } + + /** + * This method is called by SDL using JNI. + * This is a static method for JNI convenience, it calls a non-static method + * so that is can be overridden + */ + public static void setOrientation(int w, int h, boolean resizable, String hint) + { + if (mSingleton != null) { + mSingleton.setOrientationBis(w, h, resizable, hint); + } + } + + /** + * This can be overridden + */ + public void setOrientationBis(int w, int h, boolean resizable, String hint) + { + int orientation_landscape = -1; + int orientation_portrait = -1; + + /* If set, hint "explicitly controls which UI orientations are allowed". */ + if (hint.contains("LandscapeRight") && hint.contains("LandscapeLeft")) { + orientation_landscape = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE; + } else if (hint.contains("LandscapeLeft")) { + orientation_landscape = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; + } else if (hint.contains("LandscapeRight")) { + orientation_landscape = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; + } + + /* exact match to 'Portrait' to distinguish with PortraitUpsideDown */ + boolean contains_Portrait = hint.contains("Portrait ") || hint.endsWith("Portrait"); + + if (contains_Portrait && hint.contains("PortraitUpsideDown")) { + orientation_portrait = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT; + } else if (contains_Portrait) { + orientation_portrait = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; + } else if (hint.contains("PortraitUpsideDown")) { + orientation_portrait = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT; + } + + boolean is_landscape_allowed = (orientation_landscape != -1); + boolean is_portrait_allowed = (orientation_portrait != -1); + int req; /* Requested orientation */ + + /* No valid hint, nothing is explicitly allowed */ + if (!is_portrait_allowed && !is_landscape_allowed) { + if (resizable) { + /* All orientations are allowed */ + req = ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR; + } else { + /* Fixed window and nothing specified. Get orientation from w/h of created window */ + req = (w > h ? ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE : ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT); + } + } else { + /* At least one orientation is allowed */ + if (resizable) { + if (is_portrait_allowed && is_landscape_allowed) { + /* hint allows both landscape and portrait, promote to full sensor */ + req = ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR; + } else { + /* Use the only one allowed "orientation" */ + req = (is_landscape_allowed ? orientation_landscape : orientation_portrait); + } + } else { + /* Fixed window and both orientations are allowed. Choose one. */ + if (is_portrait_allowed && is_landscape_allowed) { + req = (w > h ? orientation_landscape : orientation_portrait); + } else { + /* Use the only one allowed "orientation" */ + req = (is_landscape_allowed ? orientation_landscape : orientation_portrait); + } + } + } + + Log.v(TAG, "setOrientation() requestedOrientation=" + req + " width=" + w +" height="+ h +" resizable=" + resizable + " hint=" + hint); + mSingleton.setRequestedOrientation(req); + } + + /** + * This method is called by SDL using JNI. + */ + public static void minimizeWindow() { + + if (mSingleton == null) { + return; + } + + Intent startMain = new Intent(Intent.ACTION_MAIN); + startMain.addCategory(Intent.CATEGORY_HOME); + startMain.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + mSingleton.startActivity(startMain); + } + + /** + * This method is called by SDL using JNI. + */ + public static boolean shouldMinimizeOnFocusLoss() { +/* + if (Build.VERSION.SDK_INT >= 24) { + if (mSingleton == null) { + return true; + } + + if (mSingleton.isInMultiWindowMode()) { + return false; + } + + if (mSingleton.isInPictureInPictureMode()) { + return false; + } + } + + return true; +*/ + return false; + } + + /** + * This method is called by SDL using JNI. + */ + public static boolean isScreenKeyboardShown() + { + if (mTextEdit == null) { + return false; + } + + if (!mScreenKeyboardShown) { + return false; + } + + InputMethodManager imm = (InputMethodManager) SDL.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + return imm.isAcceptingText(); + + } + + /** + * This method is called by SDL using JNI. + */ + public static boolean supportsRelativeMouse() + { + // DeX mode in Samsung Experience 9.0 and earlier doesn't support relative mice properly under + // Android 7 APIs, and simply returns no data under Android 8 APIs. + // + // This is fixed in Samsung Experience 9.5, which corresponds to Android 8.1.0, and + // thus SDK version 27. If we are in DeX mode and not API 27 or higher, as a result, + // we should stick to relative mode. + // + if (Build.VERSION.SDK_INT < 27 /* Android 8.1 (O_MR1) */ && isDeXMode()) { + return false; + } + + return SDLActivity.getMotionListener().supportsRelativeMouse(); + } + + /** + * This method is called by SDL using JNI. + */ + public static boolean setRelativeMouseEnabled(boolean enabled) + { + if (enabled && !supportsRelativeMouse()) { + return false; + } + + return SDLActivity.getMotionListener().setRelativeMouseEnabled(enabled); + } + + /** + * This method is called by SDL using JNI. + */ + public static boolean sendMessage(int command, int param) { + if (mSingleton == null) { + return false; + } + return mSingleton.sendCommand(command, param); + } + + /** + * This method is called by SDL using JNI. + */ + public static Context getContext() { + return SDL.getContext(); + } + + /** + * This method is called by SDL using JNI. + */ + public static boolean isAndroidTV() { + UiModeManager uiModeManager = (UiModeManager) getContext().getSystemService(UI_MODE_SERVICE); + if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION) { + return true; + } + if (Build.MANUFACTURER.equals("MINIX") && Build.MODEL.equals("NEO-U1")) { + return true; + } + if (Build.MANUFACTURER.equals("Amlogic") && Build.MODEL.equals("X96-W")) { + return true; + } + return Build.MANUFACTURER.equals("Amlogic") && Build.MODEL.startsWith("TV"); + } + + public static double getDiagonal() + { + DisplayMetrics metrics = new DisplayMetrics(); + Activity activity = (Activity)getContext(); + if (activity == null) { + return 0.0; + } + activity.getWindowManager().getDefaultDisplay().getMetrics(metrics); + + double dWidthInches = metrics.widthPixels / (double)metrics.xdpi; + double dHeightInches = metrics.heightPixels / (double)metrics.ydpi; + + return Math.sqrt((dWidthInches * dWidthInches) + (dHeightInches * dHeightInches)); + } + + /** + * This method is called by SDL using JNI. + */ + public static boolean isTablet() { + // If our diagonal size is seven inches or greater, we consider ourselves a tablet. + return (getDiagonal() >= 7.0); + } + + /** + * This method is called by SDL using JNI. + */ + public static boolean isChromebook() { + if (getContext() == null) { + return false; + } + return getContext().getPackageManager().hasSystemFeature("org.chromium.arc.device_management"); + } + + /** + * This method is called by SDL using JNI. + */ + public static boolean isDeXMode() { + if (Build.VERSION.SDK_INT < 24 /* Android 7.0 (N) */) { + return false; + } + try { + final Configuration config = getContext().getResources().getConfiguration(); + final Class configClass = config.getClass(); + return configClass.getField("SEM_DESKTOP_MODE_ENABLED").getInt(configClass) + == configClass.getField("semDesktopModeEnabled").getInt(config); + } catch(Exception ignored) { + return false; + } + } + + /** + * This method is called by SDL using JNI. + */ + public static DisplayMetrics getDisplayDPI() { + return getContext().getResources().getDisplayMetrics(); + } + + /** + * This method is called by SDL using JNI. + */ + public static boolean getManifestEnvironmentVariables() { + try { + if (getContext() == null) { + return false; + } + + ApplicationInfo applicationInfo = getContext().getPackageManager().getApplicationInfo(getContext().getPackageName(), PackageManager.GET_META_DATA); + Bundle bundle = applicationInfo.metaData; + if (bundle == null) { + return false; + } + String prefix = "SDL_ENV."; + final int trimLength = prefix.length(); + for (String key : bundle.keySet()) { + if (key.startsWith(prefix)) { + String name = key.substring(trimLength); + String value = bundle.get(key).toString(); + nativeSetenv(name, value); + } + } + /* environment variables set! */ + return true; + } catch (Exception e) { + Log.v(TAG, "exception " + e.toString()); + } + return false; + } + + // This method is called by SDLControllerManager's API 26 Generic Motion Handler. + public static View getContentView() { + return mLayout; + } + + static class ShowTextInputTask implements Runnable { + /* + * This is used to regulate the pan&scan method to have some offset from + * the bottom edge of the input region and the top edge of an input + * method (soft keyboard) + */ + static final int HEIGHT_PADDING = 15; + + public int x, y, w, h; + + public ShowTextInputTask(int x, int y, int w, int h) { + this.x = x; + this.y = y; + this.w = w; + this.h = h; + + /* Minimum size of 1 pixel, so it takes focus. */ + if (this.w <= 0) { + this.w = 1; + } + if (this.h + HEIGHT_PADDING <= 0) { + this.h = 1 - HEIGHT_PADDING; + } + } + + @Override + public void run() { + RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(w, h + HEIGHT_PADDING); + params.leftMargin = x; + params.topMargin = y; + + if (mTextEdit == null) { + mTextEdit = new DummyEdit(SDL.getContext()); + + mLayout.addView(mTextEdit, params); + } else { + mTextEdit.setLayoutParams(params); + } + + mTextEdit.setVisibility(View.VISIBLE); + mTextEdit.requestFocus(); + + InputMethodManager imm = (InputMethodManager) SDL.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + imm.showSoftInput(mTextEdit, 0); + + mScreenKeyboardShown = true; + } + } + + /** + * This method is called by SDL using JNI. + */ + public static boolean showTextInput(int x, int y, int w, int h) { + // Transfer the task to the main thread as a Runnable + return mSingleton.commandHandler.post(new ShowTextInputTask(x, y, w, h)); + } + + public static boolean isTextInputEvent(KeyEvent event) { + + // Key pressed with Ctrl should be sent as SDL_KEYDOWN/SDL_KEYUP and not SDL_TEXTINPUT + if (event.isCtrlPressed()) { + return false; + } + + return event.isPrintingKey() || event.getKeyCode() == KeyEvent.KEYCODE_SPACE; + } + + public static boolean handleKeyEvent(View v, int keyCode, KeyEvent event, InputConnection ic) { + int deviceId = event.getDeviceId(); + int source = event.getSource(); + + if (source == InputDevice.SOURCE_UNKNOWN) { + InputDevice device = InputDevice.getDevice(deviceId); + if (device != null) { + source = device.getSources(); + } + } + +// if (event.getAction() == KeyEvent.ACTION_DOWN) { +// Log.v("SDL", "key down: " + keyCode + ", deviceId = " + deviceId + ", source = " + source); +// } else if (event.getAction() == KeyEvent.ACTION_UP) { +// Log.v("SDL", "key up: " + keyCode + ", deviceId = " + deviceId + ", source = " + source); +// } + + // Dispatch the different events depending on where they come from + // Some SOURCE_JOYSTICK, SOURCE_DPAD or SOURCE_GAMEPAD are also SOURCE_KEYBOARD + // So, we try to process them as JOYSTICK/DPAD/GAMEPAD events first, if that fails we try them as KEYBOARD + // + // Furthermore, it's possible a game controller has SOURCE_KEYBOARD and + // SOURCE_JOYSTICK, while its key events arrive from the keyboard source + // So, retrieve the device itself and check all of its sources + if (SDLControllerManager.isDeviceSDLJoystick(deviceId)) { + // Note that we process events with specific key codes here + if (event.getAction() == KeyEvent.ACTION_DOWN) { + if (SDLControllerManager.onNativePadDown(deviceId, keyCode) == 0) { + return true; + } + } else if (event.getAction() == KeyEvent.ACTION_UP) { + if (SDLControllerManager.onNativePadUp(deviceId, keyCode) == 0) { + return true; + } + } + } + + if ((source & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) { + // on some devices key events are sent for mouse BUTTON_BACK/FORWARD presses + // they are ignored here because sending them as mouse input to SDL is messy + if ((keyCode == KeyEvent.KEYCODE_BACK) || (keyCode == KeyEvent.KEYCODE_FORWARD)) { + switch (event.getAction()) { + case KeyEvent.ACTION_DOWN: + case KeyEvent.ACTION_UP: + // mark the event as handled or it will be handled by system + // handling KEYCODE_BACK by system will call onBackPressed() + return true; + } + } + } + + if (event.getAction() == KeyEvent.ACTION_DOWN) { + if (isTextInputEvent(event)) { + if (ic != null) { + ic.commitText(String.valueOf((char) event.getUnicodeChar()), 1); + } else { + SDLInputConnection.nativeCommitText(String.valueOf((char) event.getUnicodeChar()), 1); + } + } + onNativeKeyDown(keyCode); + return true; + } else if (event.getAction() == KeyEvent.ACTION_UP) { + onNativeKeyUp(keyCode); + return true; + } + + return false; + } + + /** + * This method is called by SDL using JNI. + */ + public static Surface getNativeSurface() { + if (SDLActivity.mSurface == null) { + return null; + } + return SDLActivity.mSurface.getNativeSurface(); + } + + // Input + + /** + * This method is called by SDL using JNI. + */ + public static void initTouch() { + int[] ids = InputDevice.getDeviceIds(); + + for (int id : ids) { + InputDevice device = InputDevice.getDevice(id); + /* Allow SOURCE_TOUCHSCREEN and also Virtual InputDevices because they can send TOUCHSCREEN events */ + if (device != null && ((device.getSources() & InputDevice.SOURCE_TOUCHSCREEN) == InputDevice.SOURCE_TOUCHSCREEN + || device.isVirtual())) { + + int touchDevId = device.getId(); + /* + * Prevent id to be -1, since it's used in SDL internal for synthetic events + * Appears when using Android emulator, eg: + * adb shell input mouse tap 100 100 + * adb shell input touchscreen tap 100 100 + */ + if (touchDevId < 0) { + touchDevId -= 1; + } + nativeAddTouch(touchDevId, device.getName()); + } + } + } + + // Messagebox + + /** Result of current messagebox. Also used for blocking the calling thread. */ + protected final int[] messageboxSelection = new int[1]; + + /** + * This method is called by SDL using JNI. + * Shows the messagebox from UI thread and block calling thread. + * buttonFlags, buttonIds and buttonTexts must have same length. + * @param buttonFlags array containing flags for every button. + * @param buttonIds array containing id for every button. + * @param buttonTexts array containing text for every button. + * @param colors null for default or array of length 5 containing colors. + * @return button id or -1. + */ + public int messageboxShowMessageBox( + final int flags, + final String title, + final String message, + final int[] buttonFlags, + final int[] buttonIds, + final String[] buttonTexts, + final int[] colors) { + + messageboxSelection[0] = -1; + + // sanity checks + + if ((buttonFlags.length != buttonIds.length) && (buttonIds.length != buttonTexts.length)) { + return -1; // implementation broken + } + + // collect arguments for Dialog + + final Bundle args = new Bundle(); + args.putInt("flags", flags); + args.putString("title", title); + args.putString("message", message); + args.putIntArray("buttonFlags", buttonFlags); + args.putIntArray("buttonIds", buttonIds); + args.putStringArray("buttonTexts", buttonTexts); + args.putIntArray("colors", colors); + + // trigger Dialog creation on UI thread + + runOnUiThread(new Runnable() { + @Override + public void run() { + messageboxCreateAndShow(args); + } + }); + + // block the calling thread + + synchronized (messageboxSelection) { + try { + messageboxSelection.wait(); + } catch (InterruptedException ex) { + ex.printStackTrace(); + return -1; + } + } + + // return selected value + + return messageboxSelection[0]; + } + + protected void messageboxCreateAndShow(Bundle args) { + + // TODO set values from "flags" to messagebox dialog + + // get colors + + int[] colors = args.getIntArray("colors"); + int backgroundColor; + int textColor; + int buttonBorderColor; + int buttonBackgroundColor; + int buttonSelectedColor; + if (colors != null) { + int i = -1; + backgroundColor = colors[++i]; + textColor = colors[++i]; + buttonBorderColor = colors[++i]; + buttonBackgroundColor = colors[++i]; + buttonSelectedColor = colors[++i]; + } else { + backgroundColor = Color.TRANSPARENT; + textColor = Color.TRANSPARENT; + buttonBorderColor = Color.TRANSPARENT; + buttonBackgroundColor = Color.TRANSPARENT; + buttonSelectedColor = Color.TRANSPARENT; + } + + // create dialog with title and a listener to wake up calling thread + + final AlertDialog dialog = new AlertDialog.Builder(this).create(); + dialog.setTitle(args.getString("title")); + dialog.setCancelable(false); + dialog.setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface unused) { + synchronized (messageboxSelection) { + messageboxSelection.notify(); + } + } + }); + + // create text + + TextView message = new TextView(this); + message.setGravity(Gravity.CENTER); + message.setText(args.getString("message")); + if (textColor != Color.TRANSPARENT) { + message.setTextColor(textColor); + } + + // create buttons + + int[] buttonFlags = args.getIntArray("buttonFlags"); + int[] buttonIds = args.getIntArray("buttonIds"); + String[] buttonTexts = args.getStringArray("buttonTexts"); + + final SparseArray