diff --git a/.github/workflows/gpui-build.yml b/.github/workflows/gpui-build.yml new file mode 100644 index 0000000..f02199f --- /dev/null +++ b/.github/workflows/gpui-build.yml @@ -0,0 +1,110 @@ +name: PatchOpsIII Native Build + +on: + pull_request: + paths: + - 'native/**' + - 'scripts/*gpui*.py' + - 'scripts/measure_desktop.py' + - 'presets.json' + - 'package.json' + - 'docs/gpui-architecture.md' + - '.github/workflows/gpui-build.yml' + workflow_dispatch: + inputs: + ref: + description: Git ref to build + required: false + type: string + default: '' + workflow_call: + inputs: + ref: + description: Git ref to build + required: false + type: string + default: '' + +permissions: + contents: read + +jobs: + native: + name: Native ${{ matrix.os }} archive + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-24.04 + executable: native/target/release/patchopsiii-gpui + archive: dist/native/PatchOpsIII-native-linux-x64.zip + artifact: PatchOpsIII-native-Linux + - os: windows-2022 + executable: native/target/release/patchopsiii-gpui.exe + archive: dist/native/PatchOpsIII-native-windows-x64.zip + artifact: PatchOpsIII-native-Windows + runs-on: ${{ matrix.os }} + env: + PYTHON_VERSION: '3.12' + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ inputs.ref != '' && inputs.ref || github.ref }} + - uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + - uses: dtolnay/rust-toolchain@master + with: + toolchain: '1.99.0' + components: rustfmt, clippy + - uses: Swatinem/rust-cache@v2 + with: + workspaces: native + - name: Linux native and packaging dependencies + if: runner.os == 'Linux' + run: sudo apt-get update && sudo apt-get install -y binutils libfontconfig1-dev libfreetype6-dev libxkbcommon-dev libxkbcommon-x11-dev libwayland-dev libvulkan-dev libx11-xcb-dev libxcb1-dev libasound2-dev libclang-dev mesa-vulkan-drivers xvfb xauth openbox xcompmgr xdotool bubblewrap sway + - name: Python lifecycle and packaging tests + run: python -m unittest scripts/test_gpui.py + - name: Format + run: cargo fmt --manifest-path native/Cargo.toml --all --check + - name: Lint + run: cargo clippy --locked --manifest-path native/Cargo.toml --workspace --all-targets -- -D warnings + - name: Core unit tests + run: cargo test --locked --manifest-path native/Cargo.toml -p patchops-core --lib + - name: Python-oracle parity tests (Linux isolation harness) + if: runner.os == 'Linux' + run: | + # Ubuntu 24.04 blocks unprivileged user namespaces via AppArmor; + # the bubblewrap sandbox needs them on this ephemeral runner. + sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 + cargo test --locked --manifest-path native/Cargo.toml -p patchops-core --test parity + - name: GPUI tests + run: cargo test --locked --manifest-path native/Cargo.toml -p patchopsiii-gpui + - name: Build native release + run: python scripts/gpui.py build --release + - name: Create self-contained native archive + run: python scripts/package_gpui.py --executable ${{ matrix.executable }} --output ${{ matrix.archive }} + - name: Verify in-process status (Windows) + if: runner.os == 'Windows' + run: python scripts/smoke_gpui_linux.py --status-only --executable ${{ matrix.executable }} + - name: Smoke test the self-contained Linux archive + if: runner.os == 'Linux' + run: | + python -m zipfile -e ${{ matrix.archive }} "${RUNNER_TEMP}/patchops-native" + chmod +x "${RUNNER_TEMP}/patchops-native/PatchOpsIII/patchopsiii-gpui" + xvfb-run -a python scripts/smoke_gpui_linux.py --start-session --executable "${RUNNER_TEMP}/patchops-native/PatchOpsIII/patchopsiii-gpui" + - name: Smoke test the native Wayland window (headless sway) + if: runner.os == 'Linux' + run: python scripts/smoke_gpui_wayland.py --executable "${RUNNER_TEMP}/patchops-native/PatchOpsIII/patchopsiii-gpui" --compositor sway --keep-log "${RUNNER_TEMP}/wayland-smoke.log" + - name: Upload Wayland smoke log + if: failure() && runner.os == 'Linux' + uses: actions/upload-artifact@v4 + with: + name: wayland-smoke-log + path: ${{ runner.temp }}/wayland-smoke.log + if-no-files-found: ignore + - uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.artifact }} + path: ${{ matrix.archive }} + if-no-files-found: error diff --git a/.github/workflows/release-beta.yml b/.github/workflows/release-beta.yml index 25e97cd..e2ccdc2 100644 --- a/.github/workflows/release-beta.yml +++ b/.github/workflows/release-beta.yml @@ -309,6 +309,14 @@ jobs: ref: ${{ needs.prepare.outputs.tag }} secrets: inherit + build-native: + # GPUI + patchops-core archives; no Python backend bundle in native assets. + needs: + - prepare + uses: ./.github/workflows/gpui-build.yml + with: + ref: ${{ needs.prepare.outputs.tag }} + release: name: Publish beta release runs-on: ubuntu-latest @@ -318,6 +326,7 @@ jobs: - prepare - build-windows - build-linux + - build-native steps: - name: Checkout repository uses: actions/checkout@v4 @@ -334,6 +343,16 @@ jobs: with: name: ${{ needs.build-linux.outputs.artifact_name }} path: artifacts/linux + - name: Download native Linux archive + uses: actions/download-artifact@v4 + with: + name: PatchOpsIII-native-Linux + path: artifacts/native-linux + - name: Download native Windows archive + uses: actions/download-artifact@v4 + with: + name: PatchOpsIII-native-Windows + path: artifacts/native-windows - name: Inspect downloaded artifacts run: | set -euo pipefail @@ -468,6 +487,15 @@ jobs: if [ -f "$(dirname "$linux_exec")/PatchOpsIII.AppImage.sig" ]; then cp "$(dirname "$linux_exec")/PatchOpsIII.AppImage.sig" "release/PatchOpsIII-Beta.AppImage.sig" fi + native_linux=$(find artifacts/native-linux -type f -name 'PatchOpsIII-native-linux-x64.zip' -print -quit) + native_windows=$(find artifacts/native-windows -type f -name 'PatchOpsIII-native-windows-x64.zip' -print -quit) + if [ -z "$native_linux" ] || [ -z "$native_windows" ]; then + echo "Native desktop archives were not produced" >&2 + find artifacts/native-linux artifacts/native-windows -type f -print >&2 + exit 1 + fi + cp "$native_linux" "release/PatchOpsIII-native-linux-x64.zip" + cp "$native_windows" "release/PatchOpsIII-native-windows-x64.zip" - name: Publish GitHub release uses: softprops/action-gh-release@v2 with: diff --git a/.github/workflows/release-stable.yml b/.github/workflows/release-stable.yml index 9ada3dc..8594bf1 100644 --- a/.github/workflows/release-stable.yml +++ b/.github/workflows/release-stable.yml @@ -303,6 +303,14 @@ jobs: update_information: gh-releases-zsync|boggedbrush|PatchOpsIII|latest|PatchOpsIII.AppImage.zsync secrets: inherit + build-native: + # GPUI + patchops-core archives; no Python backend bundle in native assets. + needs: + - prepare + uses: ./.github/workflows/gpui-build.yml + with: + ref: ${{ needs.prepare.outputs.tag }} + release: name: Publish stable release runs-on: ubuntu-latest @@ -312,6 +320,7 @@ jobs: - prepare - build-windows - build-linux + - build-native steps: - name: Checkout repository uses: actions/checkout@v4 @@ -328,6 +337,16 @@ jobs: with: name: ${{ needs.build-linux.outputs.artifact_name }} path: artifacts/linux + - name: Download native Linux archive + uses: actions/download-artifact@v4 + with: + name: PatchOpsIII-native-Linux + path: artifacts/native-linux + - name: Download native Windows archive + uses: actions/download-artifact@v4 + with: + name: PatchOpsIII-native-Windows + path: artifacts/native-windows - name: Inspect downloaded artifacts run: | set -euo pipefail @@ -462,6 +481,15 @@ jobs: if [ -f "$(dirname "$linux_exec")/PatchOpsIII.AppImage.sig" ]; then cp "$(dirname "$linux_exec")/PatchOpsIII.AppImage.sig" "release/PatchOpsIII.AppImage.sig" fi + native_linux=$(find artifacts/native-linux -type f -name 'PatchOpsIII-native-linux-x64.zip' -print -quit) + native_windows=$(find artifacts/native-windows -type f -name 'PatchOpsIII-native-windows-x64.zip' -print -quit) + if [ -z "$native_linux" ] || [ -z "$native_windows" ]; then + echo "Native desktop archives were not produced" >&2 + find artifacts/native-linux artifacts/native-windows -type f -print >&2 + exit 1 + fi + cp "$native_linux" "release/PatchOpsIII-native-linux-x64.zip" + cp "$native_windows" "release/PatchOpsIII-native-windows-x64.zip" - name: Publish GitHub release uses: softprops/action-gh-release@v2 with: diff --git a/.github/workflows/virustotal-release-scan.yml b/.github/workflows/virustotal-release-scan.yml index 3d397bd..40f5c02 100644 --- a/.github/workflows/virustotal-release-scan.yml +++ b/.github/workflows/virustotal-release-scan.yml @@ -39,6 +39,7 @@ jobs: --repo "$GITHUB_REPOSITORY" \ --pattern "*.exe" \ --pattern "*.AppImage" \ + --pattern "PatchOpsIII-native-*.zip" \ --dir release-assets find release-assets -maxdepth 1 -type f -print - name: Determine VirusTotal CLI release @@ -107,9 +108,9 @@ jobs: printf "| `%s` | `%s` | [Report](%s) | `%s` |\n" \ "$(basename "$asset")" "$hash" "$vt_url" "$analysis_id" >> "$GITHUB_STEP_SUMMARY" printf "%s %s %s\n" "$(basename "$asset")" "$hash" "$analysis_id" - done < <(find release-assets -maxdepth 1 -type f \( -name "*.exe" -o -name "*.AppImage" \) -print0) + done < <(find release-assets -maxdepth 1 -type f \( -name "*.exe" -o -name "*.AppImage" -o -name "PatchOpsIII-native-*.zip" \) -print0) if [ "$found" -eq 0 ]; then - echo "No .exe or .AppImage assets found for ${{ inputs.tag }}." >&2 + echo "No executable or native archive assets found for ${{ inputs.tag }}." >&2 exit 1 fi diff --git a/.gitignore b/.gitignore index 5dc83a9..3305af2 100644 --- a/.gitignore +++ b/.gitignore @@ -23,9 +23,14 @@ node_modules/ # text files: *.txt !requirements.txt +!native/gpui/assets/icons/LICENSE-lucide.txt # vdf files: *.vdf # codex: -.codex/ \ No newline at end of file +.codex/ + +# Rust native workspace build output (Cargo.lock is committed) +native/target/ +native/gpui/target/ diff --git a/README.md b/README.md index 6d08e37..3277a89 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,8 @@ - [Advanced Tab](#advanced-tab) - [Terminal & Logging](#terminal--logging) - [Installation](#installation) +- [Native desktop architecture](#native-desktop-architecture) +- [Legacy Electron app](#legacy-electron-app) - [Forked Components](#forked-components) - [Quick Start](#quick-start) - [Screenshots](#screenshots) @@ -32,7 +34,7 @@ - [Star History](#star-history) ## Overview -PatchOpsIII streamlines the setup and upkeep of Black Ops III by surfacing popular community tools and quality-of-life tweaks in a single polished Electron interface backed by a local Python API. Whether you are securing your game with T7 Patch, smoothing shader compilation stutter with DXVK, or fine-tuning launch options, PatchOpsIII consolidates every workflow into one cohesive experience. +PatchOpsIII streamlines the setup and upkeep of Black Ops III by surfacing popular community tools and quality-of-life tweaks in a native Rust/GPUI desktop interface with in-process Rust operations. Whether you are securing your game with T7 Patch, smoothing shader compilation stutter with DXVK, or fine-tuning launch options, PatchOpsIII consolidates every workflow into one cohesive experience. The previous Electron/React client remains available in this repository as a legacy fallback. ## Key Features @@ -59,32 +61,46 @@ PatchOpsIII streamlines the setup and upkeep of Black Ops III by surfacing popul ## Installation 1. **Download:** Grab the latest release from the [Releases page](https://github.com/boggedbrush/PatchOpsIII/releases). -2. **Windows:** Run `PatchOpsIII.msi`, follow the installer, then launch PatchOpsIII. The `PatchOpsIII.exe` installer remains available for compatibility with older updaters. -3. **Linux & Steam Deck:** Download `PatchOpsIII.AppImage`, make it executable, then run it. -4. **Dependencies:** The packaged build bundles all required Python dependencies; no additional setup is needed. +2. **Windows:** Download `PatchOpsIII-native-windows-x64.zip`, extract the entire `PatchOpsIII` folder, and run `patchopsiii-gpui.exe` from that folder. +3. **Linux & Steam Deck:** Download `PatchOpsIII-native-linux-x64.zip`, extract it, run `chmod +x PatchOpsIII/patchopsiii-gpui`, then launch that executable from a desktop session. +4. **Dependencies:** Keep the executable and its `resources` directory together. The archive bundles one Rust executable, presets, metadata, and icons; Python is not required on the destination system. Linux still requires a supported X11/Wayland session, system fonts, and working graphics drivers. + +The existing `PatchOpsIII.msi`, `PatchOpsIII.exe`, and `PatchOpsIII.AppImage` release assets are the legacy Electron fallback while native installer/updater work remains open. ### Developer Setup -PatchOpsIII uses a React + TypeScript renderer wrapped by Electron. Bun is the JavaScript runtime/package manager, while Python runs the local backend API. + +The primary desktop app uses Rust 1.99.0, GPUI 0.2.2, GPUI Component 0.5.0, +and the `patchops-core` library in the `native/` Cargo workspace. Install Rust +through rustup and the platform build packages listed in the native README. ```bash -# install Python service dependencies -python -m venv .venv -source .venv/bin/activate # On Windows use: .venv\Scripts\activate -pip install -r requirements.txt +cargo +1.99.0 build --locked --manifest-path native/Cargo.toml -p patchopsiii-gpui +./native/target/debug/patchopsiii-gpui +# Optional Python standard-library launchers (no backend dependencies): +python scripts/gpui.py build +python scripts/gpui.py run +# Equivalent package scripts: bun run gpui:build / bun run gpui:run +``` -# install desktop frontend dependencies -bun install +See [native/gpui/README.md](native/gpui/README.md) for release packaging, +validation commands, and remaining hardware checks. Rust operations run on a +worker thread within the native app; no Python service or HTTP listener starts. -# run Vite and the Python local API -bun run dev +## Native desktop architecture -# run the Electron desktop app -bun run dev:desktop -``` +The primary Rust/GPUI client lives in [native/gpui](native/gpui/README.md) and +shares toolkit-independent operations from [native/core](native/core). +The worker dispatches existing `/api/...` action identifiers directly to Rust, +preserving the JSON shapes consumed by the UI. Downloads use outbound HTTPS. +See [the architecture and migration record](docs/gpui-architecture.md) for the +progress callback contract, migration details, and remaining validation work. -The browser development server uses Vite on `127.0.0.1:5173` and the Python API on `127.0.0.1:8765`. The Electron desktop command uses Vite on `127.0.0.1:5174` and its Python API on `127.0.0.1:8766`, so both commands can run at the same time. The renderer communicates with Python through HTTP APIs and `/ws` WebSockets; Electron IPC is reserved for desktop-specific bridge actions such as selecting a local game directory. +## Legacy Electron app + +The Electron/React application in `src/` remains buildable as a fallback. To work on it, install Python 3.12 dependencies with `python -m pip install -r requirements.txt` and Bun dependencies with `bun install`, then use `bun run dev:desktop`; `bun run dist:linux` and `bun run dist:win` retain the established AppImage/MSI/NSIS builds. The browser development server remains available through `bun run dev`. Electron and GPUI share backend settings, logs, caches, and game files, so do not perform overlapping game mutations in both clients. ## Forked Components + - **BO3 Enhanced Proton fork metadata:** [bo3-enhanced-proton/README.md](bo3-enhanced-proton/README.md) - Upstream source: https://github.com/Weather-OS/GDK-Proton - Current base release: `release10-32` @@ -99,6 +115,8 @@ The browser development server uses Vite on `127.0.0.1:5173` and the Python API 5. Visit the **Advanced** tab to unlock VRAM, tweak frame latency, and set your config to read-only once satisfied. ## Screenshots + +These screenshots show the legacy Electron client; updated native screenshots are tracked with the native UI work. diff --git a/docs/gpui-architecture.md b/docs/gpui-architecture.md new file mode 100644 index 0000000..7eabbb0 --- /dev/null +++ b/docs/gpui-architecture.md @@ -0,0 +1,270 @@ +# Native GPUI architecture and migration record + +Research date: 2026-10-02. Original baseline: the Electron/React/Python app, +which now remains as the legacy fallback. +Related alternatives: [Tauri shell + Rust core PR #36](https://github.com/boggedbrush/PatchOpsIII/pull/36) +and [in-process Rust/Tauri PR #40](https://github.com/boggedbrush/PatchOpsIII/pull/40). +The GPUI implementation started from `main`, independently of both Tauri branches, +and has since been selected as PatchOpsIII's primary desktop client. + +## Findings and architecture decision + +GPUI is Zed's GPU-accelerated Rust framework, combining retained entity state +with declarative view rendering. It is pre-1.0 and APIs can break between +versions. It replaces the renderer as well as the desktop shell: our React, +CSS, browser accessibility, DOM tests, and Electron IPC cannot simply transfer. +The upstream main README now describes a separate `gpui_platform` package; +the published GPUI 0.2.2 selected here still uses `Application::new()` and +includes the platform implementations. Mixing these APIs would break builds. + +GPUI Component supplies keyboard-aware buttons and text inputs, text editing, +focus handling, and the root/theme setup, reducing custom control work. Pinning +the compatible GPUI 0.2.2 / Component 0.5.0 pair and committing the lockfile +avoids adopting an evolving Git revision or silently upgrading the toolkit. +The optional WebView feature is not enabled. Both dependencies use Apache-2.0; +distribution still needs an audit of notices for the complete dependency tree. + +The original GPUI baseline reused Python to compare desktop renderers. The +native client now imports PR #40's Rust operations into `native/core`, a library +with no Tauri/webview dependency. GPUI and operations run in one Rust process; +legacy Electron retains its existing Python service. There is no local HTTP +server or Python supervision in the native binary or its archives. + +The core retains verified downloads, ownership manifests, transactional backup +and rollback logic, Steam/VDF handling, and platform launch helpers from PR #40. +UI, hardware, accessibility and installer/updater validation remain separate +from this backend migration. No comparative performance improvement is claimed. + +Sources inspected: + +- [GPUI upstream README](https://github.com/zed-industries/zed/blob/main/crates/gpui/README.md): architecture, pre-1.0 status, platform requirements. +- [Published GPUI 0.2.2 source](https://crates.io/crates/gpui/0.2.2): standalone application, async contexts, platform folder prompts. +- [GPUI Component 0.5.0 source](https://crates.io/crates/gpui-component/0.5.0): compatible GPUI dependency, inputs, buttons, Root and theme setup. +- Repository `src/main/main.ts`, `src/renderer/lib/api.ts`, `src/renderer/main.tsx`, and `backend/api.py`: actual lifecycle, payloads, config normalization, state, error responses, and logs. +- PR #36 and #40 metadata: the shell-only migration and the subsequent backend rewrite have different scopes and costs. + +## Implemented architecture + +`native/` is a Cargo workspace with `patchops-core` and `patchopsiii-gpui`, a +committed workspace lockfile, and Rust 1.99.0. GPUI/Component stay pinned to +0.2.2/0.5.0. Presets and package.json version are embedded at build time, so +stable/beta release preparation applies to both Electron and GPUI. + +`Backend::start()` creates a `patchops-gpui-core` worker. Public `requests: +Sender` and `replies: Receiver` retain the existing UI contract. +`Request { path: String, body: Option }` uses the existing +`/api/...` identifiers without making HTTP requests. `Reply { state: +Option, message: String, failed: bool, is_status: bool }` retains status, +operation messages, and the Steam depot command marker used by the UI. +Long operations and status assembly run sequentially on the worker; neither +blocks the UI thread. Closing a window does not cancel or join active operations. + +`patchops_core::Engine::dispatch(path, body)` returns the same status keys and +`{ok, state}`, `{ok, error}`, validation, update and depot response envelopes as +the Python API. The Python flag is `ok`, rather than `success`. Internal Rust +errors become failed operation results/replies. Built-in launch profiles omit +`path`; Workshop profiles include it. The UI uses its native folder picker, so +Python's `/api/browse` endpoint is intentionally not implemented. + +Update metadata retains `available`, `channel`, `currentVersion`, +`latestVersion`, `name`, `body`, `pageUrl`, and `asset {name, url, size, +contentType}`. Asset selection now chooses `PatchOpsIII-native-{linux,windows}-x64.zip`; +it does not point the GPUI user to legacy installers. Checks do not install an +update automatically. Draft and stable/prerelease checks are preserved. + +Status hashing is cached by executable path, file size and modification time, +including preserved EXE availability probes. The bounded cache is display-only: +mutation validation always rereads bytes, verifies staging, and checks the active +target after replacement. Files without modification times are not cache hits. + +### Progress and log hook + +PR #40 emits `patchops-log` entries. `AppState::new(data_dir, resource_dir, +on_log)` replaces its AppHandle with an optional `EventCallback = Arc`. `resource_dir: Option` replaces +the Steam module's Tauri resource-path lookup. `set_progress_callback` adds +`ProgressCallback = Arc`. +Callbacks run on the worker and must forward data to the UI thread. + +The GPUI bridge exposes `events: Receiver`, with: + +```rust +pub enum BackendEvent { + Log(patchops_core::models::LogEntry), // category, message, line: String + Progress(OperationProgress), +} +pub struct OperationProgress { + pub op: String, // full request path, e.g. /api/t7-install + pub stage: String, // started/running/download/verify/completed/failed + pub fraction: Option, // stage-local [0,1], None = indeterminate + pub message: String, +} +``` + +`OperationProgress` is re-exported from `backend`; it also derives Serialize and +Deserialize in core. Logs stream as they occur. Operations emit started and +terminal events; downloads emit throttled byte fractions when Content-Length is +known and SHA-256 verification stages. Status/depot polls emit no operation +progress. The UI agent's `on_progress(ProgressEvent, cx)` can map `op` through +`Operation::from_path`, forward Log entries, and map stages/fractions; wiring +that receiver belongs to the UI integration. Existing `send(path, body)` call +sites need no changes. Errors and validation failures are still failed replies; +compatible-depot failures retain `Steam console command: ...` in their message. + +### Main-branch changes reconciled + +- `aadb32d` (#43): September 10, 2026 BuildID 24784313 and SHA-256 are recognized; + T7 uses the current profile. Unit tests cover the constants and hash classification. +- `20e1dbe` (#46), `ca9af04` (#47): prefer exact/versioned T7 release archives, + then unambiguous platform/universal renamed packages with GitHub SHA-256 + digests. Tests cover discovery and ambiguity; unsigned candidates are rejected. +- `8888db6` (#50), `2503485` (#51): Electron AppImage tooling, SquashFS directory + permissions, Firejail validation, UI compact layout, and update metadata do + not apply to native ZIP archives. Their legacy workflows remain untouched. +- `c582a62`, `58f7ba3` and versions from #50/#51: package.json is the embedded + runtime release version; no duplicate hard-coded desktop version is introduced. + +Core keeps PR #40's archive/path validation and ownership rules. The Linux +Python-oracle suite now passes all 31 cases without ignored adapters. Config +writes normalize CRLF to LF, use Python's 0400/0600 read-only modes, and retain +permission error/log text. Steam launch-option applies use a permission-preserving +rolling backup on every apply, including no-ops, and no extra localconfig sibling +backup. Unrelated VDF operations retain their existing recovery policy. + +DXVK settings adopt Python-era root DLL/config installs only when the DLL pair +matches one verified official release; the adoption manifest records real hashes +and preserves the original config. Unknown/foreign DLLs are refused. The parity +suite allows only the exact manifest, config backup, and their directories. +Enhanced first-read status binds matching legacy file records in memory per +AppState and preserves counts/timestamps without rewriting legacy JSON or +claiming destructive ownership. Durable status binding would require a separate +review of its filesystem difference. Verified archive adoption is still required +before installation/cleanup. See `native/core/tests/parity/README.md` for exact +rules, the narrow DXVK allowlist, and test seam limits. + +Native archives contain the Rust binary, resources and docs only. The optional +Python scripts use the standard library as build/package/smoke helpers. Native CI +runs core and GPUI tests, formatting, Clippy, archive checks and a fake-game +window/status smoke. Stable/beta workflows retain native artifact names. + +## Stack comparison + +| Criterion | Legacy Electron | Tauri #36 / #40 | Primary GPUI client | +|---|---|---|---| +| UI technology | React/CSS + Chromium | React/CSS + OS WebView | Native Rust controls + GPU rendering | +| Backend | Python child + HTTP/WS | #36 Python + small Rust core; #40 in-process Rust | In-process patchops-core + worker channels | +| Renderer reuse | Baseline | High | Rewrite required | +| Platform work | Existing Windows/Linux packages | WebView availability and new Rust packaging | GPU/driver compatibility, native UI/input and new packaging | +| Accessibility | Browser infrastructure; app still needs testing | WebView infrastructure; app still needs testing | Must validate controls and screen readers on target OSes | +| Delivery | Existing MSI/NSIS/AppImage fallback | Independent open PRs | Self-contained Windows/Linux ZIPs with one Rust executable | +| Startup/RAM/package size | Measure | Measure each PR separately | Measure complete stack; no claimed improvement | +| Migration cost | Existing maintenance | Shell changes; #40 also ports operations | UI rewrite plus shared Rust operations | + +## Workflow coverage + +| Workflow | Native implementation | Remaining parity work | +|---|---|---| +| Installation detection/directory | Actual status and native folder picker; manual path entry; existing backend validation | Folder picker on Windows, Wayland portals and Steam Deck | +| Launch/profiles | Steam launch, backend-provided profiles, Workshop open/install request | Workshop progress presentation and detailed profile states | +| T7 Patch | Install/update, uninstall, gamertag/color/password, friends-only | Real install/UAC cancellation, name preview, password editing UX | +| EXE Swapper | Compatible/current/Enhanced restore, hash and integrity state, depot error text | Guided depot dialog/polling/copy action; real restore/backup checks | +| Enhanced | Pick/enter source, validate, install, uninstall | Real Windows/Linux game/proton workflows | +| Graphics | Presets, FPS, FOV, refresh, render %, resolution, display modes, V-Sync and FPS counter | Slider UX, valid hardware-specific mode list, side-by-side visual polish | +| Advanced/QoL | Smoothing, hidden options, CPU reduction, frame latency, VRAM target, intro/compiler/read-only toggles | Real config/backup verification on disposable installs | +| DXVK | Install/uninstall, async/cache/HUD, threads, FPS, latency and tear-free settings | Real download/install/proton behavior | +| Logs/maintenance | Up to 80 recent lines, five-second refresh, clear logs/cache, reset stock, update check/channel | Streaming WS logs, copy/export UX and platform updater integration | +| Delivery | Windows/Linux CI and self-contained release archives | Signed native installers, updater, dependency notices and broader release testing | + +Status refresh polls the worker every five seconds. Executable hashes are reused +while size/mtime remain unchanged. Other status work (Steam config, mod state and +small config files) still runs on each poll; measure its idle CPU cost. All +game-modifying validation must use disposable installations and inspect backups. + +## Measurement protocol retained from the selection process + +Use the same Windows PC and Steam Deck/Linux PC for all three candidates. Record +OS, GPU/driver, display scale, power profile, game-directory state, and commit +SHA. Build optimized release artifacts. Exclude compilation, debugger and dev +servers. Compare Electron, #36, #40 and GPUI separately; the original Python-backed GPUI baseline and the current Rust-backed client +must be measured separately. + +For regression tracking and any future comparison, collect ten cold launches and +ten warm launches per candidate. Record +process start to first painted interactive window **and** first loaded status +with screen recording or platform tracing. Include backend startup. Report +median and p95, retaining raw observations. Do not label cargo build time as app +startup time, or GPUI entity creation as first paint. + +Measure idle for 60 seconds after settling, then the same navigation/config and +log workload. Use the native executable PID (Electron still has a process tree): + +```sh +python -m pip install psutil +python scripts/measure_desktop.py --pid 12345 --label gpui-release \ + --seconds 60 --artifact dist/native/PatchOpsIII-native-linux-x64.zip \ + --output measurements/gpui-idle.json +``` + +The sampler captures RSS and CPU for the process tree, including children; RSS +can double-count shared pages and excludes GPU memory. Collect GPU usage with +platform tools. Measure the complete extracted native deliverable. Elevated/detached children must be measured +separately. Evaluate input latency, scrolling, text entry/IME, scaling, keyboard +focus, screen readers, offline/error behavior, and update/install/uninstall +reliability when comparing future changes. GPUI is the selected product client; +that decision does not substitute for collecting hardware measurements. + +## Historical baseline validation and current gates + +The following results describe the original Python-backed baseline, before the +in-process backend migration; they are not current Rust-core parity evidence. +On the supplied Linux cloud environment: + +- Debug and optimized release builds completed with the locked dependency tree. +- Four Rust protocol tests passed, including an actual local HTTP worker request + and HTTP error response. Clippy with warnings denied and rustfmt checks passed. +- Eight Python launcher/packaging tests pass, covering child reaping, startup/UI + failures, missing binaries, early service exit, custom target-directory + consistency, required bundle inputs, and the self-contained archive layout. +- The existing Electron renderer TypeScript check (`tsc --noEmit`) passed. +- A real Python service bound an ephemeral port, returned health/status with no + game installation, and stopped accepting connections after launcher cleanup. +- The native window rendered under Xvfb/Openbox with Mesa's software Vulkan + driver and an X compositor. Dashboard/Graphics navigation and text entry were + inspected; an edited FPS draft remained unchanged across status refreshes. +- The normal X11 close path exposed a GPUI 0.2.2 callback borrow panic. Quit is + now scheduled on a later event-loop turn. A read-only native lifecycle smoke + check exercises the real platform close callback, and Linux CI runs it too. +- The original archive contents and process-tree sampler were smoke checked. The sampler + captured the supervisor, Python API and native GUI; its debug/software-rendered + samples are not comparative performance evidence. + +![GPUI release dashboard in the Linux smoke session](images/gpui-dashboard.png) + +A GUI Apply-click smoke check was rejected by automatic approval review because +it could activate a configuration/file-changing action. No game-mutating UI +clicks were executed. Confirmation behavior was reviewed in code; actual game +operations, Windows, Wayland, Steam Deck, accessibility, and comparative +performance remain manual/hardware validation gates. Native Windows/Linux CI +is configured in this PR; its hosted results are separate from these local checks. + +## Reviewer smoke checklist + +- Build with the pinned lockfile; open the native window on Windows and Linux. +- Start Electron simultaneously; confirm GPUI has no Python child or HTTP + listener. Close GPUI while idle and confirm Electron stays alive. +- With no game directory, verify status/errors and manual folder input. Browse + to a disposable BO3 install, confirm selection, refresh, and relaunch. +- Navigate every section with mouse and keyboard. Edit a field while refresh + runs; confirm the draft remains. Verify current-values reload is explicit. +- Cancel an operation confirmation and verify no files changed. Verify the + selected operation and target before confirming a config mutation. +- Apply a preset and individual config values; inspect config.ini and backend + logs, including readonly error handling and special CPU/hidden-option values. +- On a test install verify each mod install/uninstall and EXE restore preserves + expected backups. Exercise compatible-depot-required and UAC cancellation. +- Verify core initialization failure, download errors, and clean shutdown. + Wait for active file operations before closing; cancellation is not implemented. +- Test X11, Wayland, actual Steam Deck, high DPI, IME and screen-reader behavior. +- Measure optimized complete stacks and record results as native delivery evolves. + Hardware results and full packaged parity remain follow-up gates after the + product-level promotion. diff --git a/docs/images/gpui-dashboard.png b/docs/images/gpui-dashboard.png new file mode 100644 index 0000000..4bfb3a3 Binary files /dev/null and b/docs/images/gpui-dashboard.png differ diff --git a/native/Cargo.lock b/native/Cargo.lock new file mode 100644 index 0000000..77c8649 --- /dev/null +++ b/native/Cargo.lock @@ -0,0 +1,8285 @@ +# 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 0.2.17", + "zeroize", +] + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "const-random", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + +[[package]] +name = "aligned" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee4508988c62edf04abd8d92897fca0c2995d907ce1dfeaf369dac3716a40685" +dependencies = [ + "as-slice", +] + +[[package]] +name = "aligned-vec" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" +dependencies = [ + "equator", +] + +[[package]] +name = "android_system_properties" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "ar_archive_writer" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73cd58deff2140a0a8eae87e417bd01db68a33e148aa93d1e8cd837e55e312b6" +dependencies = [ + "object", +] + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "arc-swap" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b" +dependencies = [ + "rustversion", +] + +[[package]] +name = "arg_enum_proc_macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" + +[[package]] +name = "as-raw-xcb-connection" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" + +[[package]] +name = "as-slice" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "ash" +version = "0.38.0+1.3.281" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" +dependencies = [ + "libloading", +] + +[[package]] +name = "ash-window" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52bca67b61cb81e5553babde81b8211f713cb6db79766f80168f3e5f40ea6c82" +dependencies = [ + "ash", + "raw-window-handle", + "raw-window-metal", +] + +[[package]] +name = "ashpd" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f3f79755c74fd155000314eb349864caa787c6592eace6c6882dad873d9c39" +dependencies = [ + "async-fs", + "async-net", + "enumflags2", + "futures-channel", + "futures-util", + "rand 0.9.5", + "serde", + "serde_repr", + "url", + "wayland-backend", + "wayland-client", + "wayland-protocols 0.32.13", + "zbus", +] + +[[package]] +name = "ashpd" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33a3c86f3fd70c0ffa500ed189abfa90b5a52398a45d5dc372fcc38ebeb7a645" +dependencies = [ + "async-fs", + "async-net", + "enumflags2", + "futures-channel", + "futures-util", + "rand 0.9.5", + "serde", + "serde_repr", + "url", + "zbus", +] + +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener 5.4.2", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener 2.5.3", + "futures-core", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-compression" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee19bd99b43e3691acbad4e840420a4881cea6c0b66a208125a824f8fd53f5a1" +dependencies = [ + "compression-codecs", + "compression-core", + "futures-io", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand 2.5.0", + "futures-lite 2.6.1", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-fs" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8034a681df4aed8b8edbd7fbe472401ecf009251c8b40556b304567052e294c5" +dependencies = [ + "async-lock", + "blocking", + "futures-lite 2.6.1", +] + +[[package]] +name = "async-global-executor" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" +dependencies = [ + "async-channel 2.5.0", + "async-executor", + "async-io", + "async-lock", + "blocking", + "futures-lite 2.6.1", + "once_cell", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite 2.6.1", + "parking", + "polling", + "rustix 1.1.5", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener 5.4.2", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-net" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" +dependencies = [ + "async-io", + "blocking", + "futures-lite 2.6.1", +] + +[[package]] +name = "async-process" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" +dependencies = [ + "async-channel 2.5.0", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener 5.4.2", + "futures-lite 2.6.1", + "rustix 1.1.5", +] + +[[package]] +name = "async-recursion" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f8abc12baad266b1c8cec146854c195b5864b4221d4b2ca7296a7ae82d9e451" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "async-signal" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix 1.1.5", + "signal-hook-registry", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-std" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8e079a4ab67ae52b7403632e4618815d6db36d2a010cfe41b02c1b1578f93b" +dependencies = [ + "async-channel 1.9.0", + "async-global-executor", + "async-io", + "async-lock", + "async-process", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite 2.6.1", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "async_zip" +version = "0.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b9f7252833d5ed4b00aa9604b563529dd5e11de9c23615de2dcdf91eb87b52" +dependencies = [ + "async-compression", + "crc32fast", + "futures-lite 2.6.1", + "pin-project", + "thiserror 1.0.69", +] + +[[package]] +name = "atomic" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" + +[[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 = "av-scenechange" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f321d77c20e19b92c39e7471cf986812cbb46659d2af674adc4331ef3f18394" +dependencies = [ + "aligned", + "anyhow", + "arg_enum_proc_macro", + "arrayvec", + "log", + "num-rational", + "num-traits", + "pastey", + "rayon", + "thiserror 2.0.21", + "v_frame", + "y4m", +] + +[[package]] +name = "av1-grain" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cfddb07216410377231960af4fcab838eaa12e013417781b78bd95ee22077f8" +dependencies = [ + "anyhow", + "arrayvec", + "log", + "nom 8.0.0", + "num-rational", + "v_frame", +] + +[[package]] +name = "avif-serialize" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7178fe5f7d460b13895ebb9dcb28a3a6216d2df2574a0806cb51b555d297f38" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "base62" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3a647dce9631f152a99ec16cfc09267ae55fe280d5dc288964027d458076323" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" + +[[package]] +name = "bindgen" +version = "0.71.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" +dependencies = [ + "bitflags 2.13.2", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash 2.1.3", + "shlex 1.3.0", + "syn 2.0.119", +] + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bit_field" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" + +[[package]] +name = "bitstream-io" +version = "4.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eff00be299a18769011411c9def0d827e8f2d7bf0c3dbf53633147a8867fd1f" +dependencies = [ + "no_std_io2", +] + +[[package]] +name = "blade-graphics" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e71cfb73b98eb9f58ee84048aa1bdf4e7497fd20c141b57523499fa066b48fed" +dependencies = [ + "ash", + "ash-window", + "bitflags 2.13.2", + "bytemuck", + "codespan-reporting", + "glow", + "gpu-alloc", + "gpu-alloc-ash", + "hidden-trait", + "js-sys", + "khronos-egl", + "libloading", + "log", + "mint", + "naga", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "objc2-metal", + "objc2-quartz-core", + "objc2-ui-kit", + "once_cell", + "raw-window-handle", + "slab", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "blade-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27142319e2f4c264581067eaccb9f80acccdde60d8b4bf57cc50cd3152f109ca" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "blade-util" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a6be3a82c001ba7a17b6f8e413ede5d1004e6047213f8efaf0ffc15b5c4904c" +dependencies = [ + "blade-graphics", + "bytemuck", + "log", + "profiling", +] + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[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-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-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 = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2", +] + +[[package]] +name = "blocking" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a70e4329df6cb94385eed412ec92375c3cdd8a6e502493d1229b6414e4036dfa" +dependencies = [ + "async-channel 2.5.0", + "async-task", + "futures-io", + "futures-lite 2.6.1", + "piper", +] + +[[package]] +name = "bstr" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f" +dependencies = [ + "memchr", + "serde_core", +] + +[[package]] +name = "built" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c0e531d93d39c34eef561e929e8a7f86d77a5af08aac4f6d6e39976c51858e9" + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytemuck" +version = "1.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a1f896587b6f2c069c73d2f0913e2d590c3990285cd2f0b6aa02b786b4c679c" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + +[[package]] +name = "bzip2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47" +dependencies = [ + "bzip2-sys", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.13+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" +dependencies = [ + "cc", + "pkg-config", +] + +[[package]] +name = "calloop" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" +dependencies = [ + "bitflags 2.13.2", + "log", + "polling", + "rustix 0.38.44", + "slab", + "thiserror 1.0.69", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" +dependencies = [ + "calloop", + "rustix 0.38.44", + "wayland-backend", + "wayland-client", +] + +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + +[[package]] +name = "cbindgen" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadd868a2ce9ca38de7eeafdcec9c7065ef89b42b32f0839278d55f35c54d1ff" +dependencies = [ + "heck 0.4.1", + "indexmap", + "log", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn 2.0.119", + "tempfile", + "toml 0.8.23", +] + +[[package]] +name = "cc" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f74872d07caf508b30a21f6836e7d7016a2eaf7d9ff4f48deaa58cd8a0407630" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex 2.0.1", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom 7.1.3", +] + +[[package]] +name = "cfg-if" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" + +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "cgl" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff" +dependencies = [ + "libc", +] + +[[package]] +name = "chacha20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "rand_core 0.10.1", +] + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link 0.2.1", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common 0.1.7", + "inout", + "zeroize", +] + +[[package]] +name = "clang-sys" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "157a8ba7b480713b56f4c09fd13fc3e0a22a5dfab8097ba61cbc5feef950788a" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "cocoa" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c" +dependencies = [ + "bitflags 1.3.2", + "block", + "cocoa-foundation 0.1.2", + "core-foundation 0.9.4", + "core-graphics 0.23.2", + "foreign-types", + "libc", + "objc", +] + +[[package]] +name = "cocoa" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f79398230a6e2c08f5c9760610eb6924b52aa9e7950a619602baba59dcbbdbb2" +dependencies = [ + "bitflags 2.13.2", + "block", + "cocoa-foundation 0.2.0", + "core-foundation 0.10.0", + "core-graphics 0.24.0", + "foreign-types", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" +dependencies = [ + "bitflags 1.3.2", + "block", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14045fb83be07b5acf1c0884b2180461635b433455fa35d1cd6f17f1450679d" +dependencies = [ + "bitflags 2.13.2", + "block", + "core-foundation 0.10.0", + "core-graphics-types 0.2.0", + "libc", + "objc", +] + +[[package]] +name = "codespan-reporting" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81" +dependencies = [ + "serde", + "termcolor", + "unicode-width", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "command-fds" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b60b5124979fccd9addd89d8b97a1d6eebb4950694520c75ddd722535ea443f" +dependencies = [ + "nix 0.31.3", + "thiserror 2.0.21", +] + +[[package]] +name = "compression-codecs" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98fc98460ba0ad5317075d3632b8dfc45d0be8c4a49347c2a38272019717614a" +dependencies = [ + "compression-core", + "deflate64", + "flate2", + "memchr", +] + +[[package]] +name = "compression-core" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e8ccc4ea9f6acc32d102c0f6d471d11d913ad15f20c04de743374861fa1d414" + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom 0.2.17", + "once_cell", + "tiny-keccak", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" +dependencies = [ + "bitflags 2.13.2", + "core-foundation 0.10.0", + "core-graphics-types 0.2.0", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-helmer-fork" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32eb7c354ae9f6d437a6039099ce7ecd049337a8109b23d73e48e8ffba8e9cd5" +dependencies = [ + "bitflags 2.13.2", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.13.2", + "core-foundation 0.10.0", + "libc", +] + +[[package]] +name = "core-graphics2" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e4583956b9806b69f73fcb23aee05eb3620efc282972f08f6a6db7504f8334d" +dependencies = [ + "bitflags 2.13.2", + "block", + "cfg-if", + "core-foundation 0.10.0", + "libc", +] + +[[package]] +name = "core-text" +version = "21.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a593227b66cbd4007b2a050dfdd9e1d1318311409c8d600dc82ba1b15ca9c130" +dependencies = [ + "core-foundation 0.10.0", + "core-graphics 0.24.0", + "foreign-types", + "libc", +] + +[[package]] +name = "core-video" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d45e71d5be22206bed53c3c3cb99315fc4c3d31b8963808c6bc4538168c4f8ef" +dependencies = [ + "block", + "core-foundation 0.10.0", + "core-graphics2", + "io-surface", + "libc", + "metal", +] + +[[package]] +name = "core_detect" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48" + +[[package]] +name = "core_maths" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30" +dependencies = [ + "libm", +] + +[[package]] +name = "cosmic-text" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da46a9d5a8905cc538a4a5bceb6a4510de7a51049c5588c0114efce102bcbbe8" +dependencies = [ + "bitflags 2.13.2", + "fontdb 0.16.2", + "log", + "rangemap", + "rustc-hash 1.1.0", + "rustybuzz 0.14.1", + "self_cell", + "smol_str", + "swash", + "sys-locale", + "ttf-parser 0.21.1", + "unicode-bidi", + "unicode-linebreak", + "unicode-script", + "unicode-segmentation", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622f3fc73690be383c7214310406f28a90e6edeadc3cea882f9d71e495b9711a" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03e8bd762f7479489c70ed6c768ddca99d7296857de437a68dcb2a94365b3fae" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "typenum", +] + +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "ctor" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec09e802f5081de6157da9a75701d6c713d8dc3ba52571fd4bd25f412644e8a6" +dependencies = [ + "ctor-proc-macro", + "dtor", +] + +[[package]] +name = "ctor-proc-macro" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2931af7e13dc045d8e9d26afccc6fa115d64e115c9c84b1166288b46f6782c2" + +[[package]] +name = "data-url" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376" + +[[package]] +name = "deflate64" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac6b926516df9c60bfa16e107b21086399f8285a44ca9711344b9e553c5146e2" + +[[package]] +name = "defmt" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8" +dependencies = [ + "defmt-parser", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror 2.0.21", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" + +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "derive_more" +version = "0.99.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.119", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "crypto-common 0.1.7", + "subtle", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.1", + "const-oid", + "crypto-common 0.2.2", +] + +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys 0.3.7", +] + +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys 0.4.1", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys 0.5.0", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users 0.4.6", + "winapi", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users 0.4.6", + "windows-sys 0.48.0", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users 0.5.3", + "windows-sys 0.61.2", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dispatch2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags 2.13.2", + "objc2", +] + +[[package]] +name = "displaydoc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "dlib" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" +dependencies = [ + "libloading", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "dtor" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97cbdf2ad6846025e8e25df05171abfb30e3ababa12ee0a0e44b9bbe570633a8" +dependencies = [ + "dtor-proc-macro", +] + +[[package]] +name = "dtor-proc-macro" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7454e41ff9012c00d53cf7f475c5e3afa3b91b7c90568495495e8d9bf47a1055" + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dwrote" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b35532432acc8b19ceed096e35dfa088d3ea037fe4f3c085f1f97f33b4d02" +dependencies = [ + "lazy_static", + "libc", + "winapi", + "wio", +] + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "either" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" + +[[package]] +name = "embed-resource" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbfdaacccebec3b28e4866b8973543c7647797db5ada1bdab552e48fe665fbbd" +dependencies = [ + "cc", + "memchr", + "rustc_version", + "toml 1.1.6+spec-1.1.0", + "vswhom", + "winreg", +] + +[[package]] +name = "encoding_rs" +version = "0.8.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e985e0451871ad22fb8d2b6b076e2028a502a0d3950998c2c5c0a4f9b5d9679" +dependencies = [ + "cfg-if", + "core_detect", + "multiversion_no_op", + "rustversion", + "scopeguard", + "simdutf8", +] + +[[package]] +name = "endi" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" + +[[package]] +name = "enum-iterator" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4549325971814bda7a44061bf3fe7e487d447cba01e4220a4b454d630d7a016" +dependencies = [ + "enum-iterator-derive", +] + +[[package]] +name = "enum-iterator-derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685adfa4d6f3d765a26bc5dbc936577de9abf756c1feeb3089b01dd395034842" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "env_filter" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + +[[package]] +name = "equator" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" +dependencies = [ + "equator-macro", +] + +[[package]] +name = "equator-macro" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "erased-serde" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec" +dependencies = [ + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "etagere" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc89bf99e5dc15954a60f707c1e09d7540e5cd9af85fa75caa0b510bc08c5342" +dependencies = [ + "euclid", + "svg_fmt", +] + +[[package]] +name = "euclid" +version = "0.22.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a05365e3b1c6d1650318537c7460c6923f1abdd272ad6842baa2b509957a06" +dependencies = [ + "num-traits", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "5.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2" +dependencies = [ + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener 5.4.2", + "pin-project-lite", +] + +[[package]] +name = "exr" +version = "1.74.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711fe42c9964295e01ee3fba3f9fe0e1d24b98886950d68efe81b1c76e21adf3" +dependencies = [ + "bit_field", + "half", + "lebe", + "miniz_oxide 0.8.9", + "num-complex", + "pulp", + "rayon-core", + "smallvec", + "zune-inflate", +] + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[package]] +name = "fastrand" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" + +[[package]] +name = "fax" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf1079563223d5d59d83c85886a56e586cfd5c1a26292e971a0fa266531ac5a" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "filedescriptor" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e40758ed24c9b2eeb76c35fb0aebc66c626084edd827e07e1552279814c6682d" +dependencies = [ + "libc", + "thiserror 1.0.69", + "winapi", +] + +[[package]] +name = "filetime" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" +dependencies = [ + "cfg-if", + "libc", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484" + +[[package]] +name = "flate2" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb" +dependencies = [ + "crc32fast", + "miniz_oxide 0.9.1", + "zlib-rs", +] + +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" + +[[package]] +name = "float-ord" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce81f49ae8a0482e4c55ea62ebbd7e5a686af544c00b9d090bba3ff9be97b3d" + +[[package]] +name = "float_next_after" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8" + +[[package]] +name = "fluent-uri" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17c704e9dbe1ddd863da1e6ff3567795087b1eb201ce80d8fa81162e1516500d" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "nanorand", + "spin", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "font-types" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c92d9081c7ab3bd83ef1eecdfad090f3d966e3ce74106779664114cfe1a6274" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "fontconfig-parser" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbc773e24e02d4ddd8395fd30dc147524273a83e54e0f312d986ea30de5f5646" +dependencies = [ + "roxmltree", +] + +[[package]] +name = "fontdb" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0299020c3ef3f60f526a4f64ab4a3d4ce116b1acbf24cdd22da0068e5d81dc3" +dependencies = [ + "fontconfig-parser", + "log", + "memmap2", + "slotmap", + "tinyvec", + "ttf-parser 0.20.0", +] + +[[package]] +name = "fontdb" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "457e789b3d1202543297a350643cf459f836cade38934e7a4cf6a39e7cde2905" +dependencies = [ + "fontconfig-parser", + "log", + "memmap2", + "slotmap", + "tinyvec", + "ttf-parser 0.25.1", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea5190182e6915eb873ddbc16e23b711b6eb1f9c00a0d0a3a91b5f6228475225" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[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 = "freetype-sys" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7edc5b9669349acfda99533e9e0bcf26a51862ab43b08ee7745c55d28eb134" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + +[[package]] +name = "futf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +dependencies = [ + "mac", + "new_debug_unreachable", +] + +[[package]] +name = "futures" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-executor" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" + +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand 1.9.0", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand 2.5.0", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "futures-sink" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "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 = "gethostname" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" +dependencies = [ + "rustix 1.1.5", + "windows-link 0.2.1", +] + +[[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", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.1", + "wasm-bindgen", +] + +[[package]] +name = "gif" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee8cfcc411d9adbbaba82fb72661cc1bcca13e8bba98b364e62b2dba8f960159" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "glob" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" + +[[package]] +name = "globset" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07c34a9410465b45bd9787443bc7370f37735bad04b0f0cd57ff1a3186c98988" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "globwalk" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc" +dependencies = [ + "bitflags 1.3.2", + "ignore", + "walkdir", +] + +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "glow" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e5ea60d70410161c8bf5da3fdfeaa1c72ed2c15f8bbb9d19fe3a4fad085f08" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gpu-alloc" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45cf04b2726f02df5508c6de726acdc90cdf97ac771a9a0ffd8ba10a6e696bf9" +dependencies = [ + "bitflags 2.13.2", + "gpu-alloc-types", +] + +[[package]] +name = "gpu-alloc-ash" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "643756f08ef6def813c776199e4766596395a4c6530373c9a4374a64de2f53a1" +dependencies = [ + "ash", + "gpu-alloc-types", + "tinyvec", +] + +[[package]] +name = "gpu-alloc-types" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2bbed164dd10ed526c2e4fe3e721ca4a71c61730e5aafac6844b417b3227058" +dependencies = [ + "bitflags 2.13.2", +] + +[[package]] +name = "gpui" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "979b45cfa6ec723b6f42330915a1b3769b930d02b2d505f9697f8ca602bee707" +dependencies = [ + "anyhow", + "as-raw-xcb-connection", + "ashpd 0.11.1", + "async-task", + "bindgen", + "blade-graphics", + "blade-macros", + "blade-util", + "block", + "bytemuck", + "calloop", + "calloop-wayland-source", + "cbindgen", + "cocoa 0.26.0", + "cocoa-foundation 0.2.0", + "core-foundation 0.10.0", + "core-foundation-sys", + "core-graphics 0.24.0", + "core-text", + "core-video", + "cosmic-text", + "ctor", + "derive_more", + "embed-resource", + "etagere", + "filedescriptor", + "flume", + "foreign-types", + "futures", + "gpui-macros", + "gpui_collections", + "gpui_http_client", + "gpui_media", + "gpui_refineable", + "gpui_semantic_version", + "gpui_sum_tree", + "gpui_util", + "gpui_util_macros", + "image", + "inventory", + "itertools 0.14.0", + "libc", + "log", + "lyon", + "metal", + "naga", + "num_cpus", + "objc", + "oo7", + "open", + "parking", + "parking_lot", + "pathfinder_geometry", + "pin-project", + "postage", + "profiling", + "rand 0.9.5", + "raw-window-handle", + "resvg", + "schemars", + "seahash", + "serde", + "serde_json", + "slotmap", + "smallvec", + "smol", + "stacksafe", + "strum 0.27.2", + "taffy", + "thiserror 2.0.21", + "usvg", + "uuid", + "waker-fn", + "wayland-backend", + "wayland-client", + "wayland-cursor", + "wayland-protocols 0.31.2", + "wayland-protocols-plasma", + "windows 0.61.3", + "windows-core 0.61.2", + "windows-numerics", + "windows-registry 0.5.3", + "x11-clipboard", + "x11rb", + "xkbcommon", + "zed-font-kit", + "zed-scap", + "zed-xim", +] + +[[package]] +name = "gpui-component" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dda822a7bab4e0a08a4276ba68e2f82cc5855bf0b377d7b195c58c8061dc7d1" +dependencies = [ + "aho-corasick", + "anyhow", + "chrono", + "enum-iterator", + "gpui", + "gpui-component-macros", + "gpui-macros", + "html5ever", + "itertools 0.13.0", + "lsp-types", + "markdown", + "markup5ever_rcdom", + "notify", + "num-traits", + "once_cell", + "paste", + "regex", + "ropey", + "rust-i18n", + "schemars", + "serde", + "serde_json", + "serde_repr", + "smallvec", + "smol", + "tracing", + "tree-sitter", + "tree-sitter-json", + "unicode-segmentation", + "uuid", + "zed-sum-tree", +] + +[[package]] +name = "gpui-component-macros" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86fbc2d84bf91717b171320e6adc600d91ccb3ed259448f3b006787633c1c615" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "gpui-macros" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcb02dd63a2859714ac7b6b476937617c3c744157af1b49f7c904023a79039be" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "gpui_collections" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae39dc6d3d201be97e4bc08d96dbef2bc5b5c3d5734e05786e8cc3043342351c" +dependencies = [ + "indexmap", + "rustc-hash 2.1.3", +] + +[[package]] +name = "gpui_derive_refineable" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "644de174341a87b3478bd65b66bca38af868bcf2b2e865700523734f83cfc664" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "gpui_http_client" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23822b0a6d2c5e6a42507980a0ab3848610ea908942c8ef98187f646f690335e" +dependencies = [ + "anyhow", + "async-compression", + "async-fs", + "bytes", + "derive_more", + "futures", + "gpui_util", + "http", + "http-body", + "log", + "parking_lot", + "serde", + "serde_json", + "sha2 0.10.9", + "tempfile", + "url", + "zed-async-tar", + "zed-reqwest", +] + +[[package]] +name = "gpui_media" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05cb8912ae17371725132d2b7eec6797a255accc95d58ee5c1134b529810f14b" +dependencies = [ + "anyhow", + "bindgen", + "core-foundation 0.10.0", + "core-video", + "ctor", + "foreign-types", + "metal", + "objc", +] + +[[package]] +name = "gpui_perf" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40a0961dcf598955130e867f4b731150a20546427b41b1a63767c1037a86d77" +dependencies = [ + "gpui_collections", + "serde", + "serde_json", +] + +[[package]] +name = "gpui_refineable" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "258cb099254e9468181aee5614410fba61db4ae115fc1d51b4a0b985f60d6641" +dependencies = [ + "gpui_derive_refineable", +] + +[[package]] +name = "gpui_semantic_version" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "201e45eff7b695528fb3af6560a534943fbc2db5323d755b9d198bd743948e35" +dependencies = [ + "anyhow", + "serde", +] + +[[package]] +name = "gpui_sum_tree" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4f3bedd573fafafa13d1200b356c588cf094fb2786e3684bb3f5ea59b549fa9" +dependencies = [ + "arrayvec", + "log", + "rayon", +] + +[[package]] +name = "gpui_util" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68faea25903ae524de9af83990b9aa51bcbc8dd085929ac0aea7fd41905e05c3" +dependencies = [ + "anyhow", + "async-fs", + "async_zip", + "command-fds", + "dirs 4.0.0", + "dunce", + "futures", + "futures-lite 1.13.0", + "globset", + "gpui_collections", + "itertools 0.14.0", + "libc", + "log", + "nix 0.29.0", + "regex", + "rust-embed", + "schemars", + "serde", + "serde_json", + "serde_json_lenient", + "shlex 1.3.0", + "smol", + "take-until", + "tempfile", + "tendril", + "unicase", + "walkdir", + "which", +] + +[[package]] +name = "gpui_util_macros" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c28f65ef47fb97e21e82fd4dd75ccc2506eda010c846dc8054015ea234f1a22" +dependencies = [ + "gpui_perf", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "grid" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12101ecc8225ea6d675bc70263074eab6169079621c2186fe0c66590b2df9681" + +[[package]] +name = "h2" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef8e5e5a340588f4452631496976cf8636d4a7ecf600239fdc27615d2530bc16" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "num-traits", + "zerocopy", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + +[[package]] +name = "hidden-trait" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ed9e850438ac849bec07e7d09fbe9309cbd396a5988c30b010580ce08860df" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "html5ever" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13771afe0e6e846f1e67d038d4cb29998a6779f93c809212e4e9c32efd244d4" +dependencies = [ + "log", + "mac", + "markup5ever", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "http" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" +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 = "hybrid-array" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" +dependencies = [ + "typenum", +] + +[[package]] +name = "hyper" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa8e654703247911e29c23fbeaa261834bd9bb74efba2f9acddc37bfb127f53" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-native-certs", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots", +] + +[[package]] +name = "hyper-util" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddc03d96684f9226b8a787cdb71488417b53ab5ea8fdb1dac946cb9431cc8bff" +dependencies = [ + "base64 0.23.1", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "httparse", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.62.2", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" + +[[package]] +name = "icu_properties" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" + +[[package]] +name = "icu_provider" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" +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 = "ignore" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b69833ed729dc5aa7d19541d96d6cf8e9137194207a04916d658e43168402f" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata", + "same-file", + "walkdir", + "winapi-util", +] + +[[package]] +name = "image" +version = "0.25.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" +dependencies = [ + "bytemuck", + "byteorder-lite", + "color_quant", + "exr", + "gif", + "image-webp", + "moxcms", + "num-traits", + "png 0.18.1", + "qoi", + "ravif", + "rayon", + "rgb", + "tiff", + "zune-core", + "zune-jpeg", +] + +[[package]] +name = "image-webp" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" +dependencies = [ + "byteorder-lite", + "quick-error", +] + +[[package]] +name = "imagesize" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285" + +[[package]] +name = "imgref" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e44b0a4eaa4c82f441d50a963f2d5f05a787240aeee097597033e72accfd22f" + +[[package]] +name = "indexmap" +version = "2.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "inotify" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd168d97690d0b8c412d6b6c10360277f4d7ee495c5d0d5d5fe0854923255cc" +dependencies = [ + "bitflags 1.3.2", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c033f80b2c113cdf91ab7a33faa9cbc014726dcad99880c8609af2a370edf37d" +dependencies = [ + "libc", +] + +[[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 = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "interpolate_name" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "inventory" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4f0c30c76f2f4ccee3fe55a2435f691ca00c0e4bd87abe4f4a851b1d4dac39b" +dependencies = [ + "rustversion", +] + +[[package]] +name = "io-surface" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "554b8c5d64ec09a3a520fe58e4d48a73e00ff32899cdcbe32a4877afd4968b8e" +dependencies = [ + "cgl", + "core-foundation 0.10.0", + "core-foundation-sys", + "leaky-cow", +] + +[[package]] +name = "ipnet" +version = "2.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" + +[[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jiff" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ab1baf72f08796de0260609515130699b890ac25f30e610ad894bc5856cafdb" +dependencies = [ + "defmt", + "jiff-core", + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", +] + +[[package]] +name = "jiff-core" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e52fe76043ccecc9005d2305ebaadf7d7fc0cc89ca6baa10a94d6bc68c7128c" +dependencies = [ + "defmt", + "log", +] + +[[package]] +name = "jiff-static" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "378268a1116ad67ae6228701118ac9f491d78fda38a40a1f1a9e1348de6f7212" +dependencies = [ + "jiff-core", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "jobserver" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" +dependencies = [ + "getrandom 0.4.3", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "khronos-egl" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" +dependencies = [ + "libc", + "libloading", +] + +[[package]] +name = "kqueue" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d763e5b24120b4ddf50de6c92308156765aabfbbccebf401da7cff2d70a41ea" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07293a4e297ac234359b510362495713f75ea345d5307140414f20c69ffeb087" +dependencies = [ + "bitflags 2.13.2", + "libc", +] + +[[package]] +name = "kurbo" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62026ae44756f8a599ba21140f350303d4f08dcdcc71b5ad9c9bb8128c13c62" +dependencies = [ + "arrayvec", + "euclid", + "smallvec", +] + +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + +[[package]] +name = "lazy_static" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20870f649af7073d53e38067b2a84312175d56ea15217e1b15bc83506ec50afb" +dependencies = [ + "spin", +] + +[[package]] +name = "leak" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd100e01f1154f2908dfa7d02219aeab25d0b9c7fa955164192e3245255a0c73" + +[[package]] +name = "leaky-cow" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40a8225d44241fd324a8af2806ba635fc7c8a7e9a7de4d5cf3ef54e71f5926fc" +dependencies = [ + "leak", +] + +[[package]] +name = "lebe" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9fd2f41a1cba099f79a0b6b6c35656cf7c03351a7bae8ff0f28f25270f929d2" +dependencies = [ + "arbitrary", + "cc", +] + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link 0.2.1", +] + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61ff90caf6077a803a240f62fdbe88645a890bbca49ef8174c3cb0404362171d" +dependencies = [ + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" +dependencies = [ + "serde_core", + "value-bag", +] + +[[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + +[[package]] +name = "lru-slab" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4050469837a6ff301cd14c1f8f24f88549e6d548f24f64e2148eb0f72cebc51f" + +[[package]] +name = "lsp-types" +version = "0.97.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53353550a17c04ac46c585feb189c2db82154fc84b79c7a66c96c2c644f66071" +dependencies = [ + "bitflags 1.3.2", + "fluent-uri", + "serde", + "serde_json", + "serde_repr", +] + +[[package]] +name = "lyon" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0578bdecb7d6d88987b8b2b1e3a4e2f81df9d0ece1078623324a567904e7b7" +dependencies = [ + "lyon_algorithms", + "lyon_tessellation", +] + +[[package]] +name = "lyon_algorithms" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdfa8785f95e57914ddb35e3b59994aeba6f5e79e9cfd03da1c269f010f36009" +dependencies = [ + "lyon_path", + "num-traits", +] + +[[package]] +name = "lyon_geom" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4336502e29e32af93cf2dad2214ed6003c17ceb5bd499df77b1de663b9042b92" +dependencies = [ + "arrayvec", + "euclid", + "num-traits", +] + +[[package]] +name = "lyon_path" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c463f9c428b7fc5ec885dcd39ce4aa61e29111d0e33483f6f98c74e89d8621e" +dependencies = [ + "lyon_geom", + "num-traits", +] + +[[package]] +name = "lyon_tessellation" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b8dcf906637ecef61b3c0740c7a4e7f27caeb31257cfac0cc579ce15be6005" +dependencies = [ + "float_next_after", + "lyon_path", + "num-traits", +] + +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "markdown" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5cab8f2cadc416a82d2e783a1946388b31654d391d1c7d92cc1f03e295b1deb" +dependencies = [ + "unicode-id", +] + +[[package]] +name = "markup5ever" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16ce3abbeba692c8b8441d036ef91aea6df8da2c6b6e21c7e14d3c18e526be45" +dependencies = [ + "log", + "phf", + "phf_codegen", + "string_cache", + "string_cache_codegen", + "tendril", +] + +[[package]] +name = "markup5ever_rcdom" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edaa21ab3701bfee5099ade5f7e1f84553fd19228cf332f13cd6e964bf59be18" +dependencies = [ + "html5ever", + "markup5ever", + "tendril", + "xml5ever", +] + +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", + "rayon", +] + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest 0.10.7", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "memmap2" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "metal" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21" +dependencies = [ + "bitflags 2.13.2", + "block", + "core-graphics-types 0.1.3", + "foreign-types", + "log", + "objc", + "paste", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[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 = "miniz_oxide" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mint" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e53debba6bda7a793e5f99b8dacf19e626084f525f7829104ba9898f367d85ff" + +[[package]] +name = "mio" +version = "1.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1788edb87fdc09c7e26304471e2f5be8cdefb1b6930d6e3985fc02ff53bf86ee" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "moxcms" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" +dependencies = [ + "num-traits", + "pxfm", +] + +[[package]] +name = "multiversion_no_op" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d" + +[[package]] +name = "naga" +version = "25.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b977c445f26e49757f9aca3631c3b8b836942cb278d69a92e7b80d3b24da632" +dependencies = [ + "arrayvec", + "bit-set", + "bitflags 2.13.2", + "cfg_aliases", + "codespan-reporting", + "half", + "hashbrown 0.15.5", + "hexf-parse", + "indexmap", + "log", + "num-traits", + "once_cell", + "rustc-hash 1.1.0", + "spirv", + "strum 0.26.3", + "thiserror 2.0.21", + "unicode-ident", +] + +[[package]] +name = "nanorand" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.13.2", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "nix" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" +dependencies = [ + "bitflags 2.13.2", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "no_std_io2" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418abd1b6d34fbf6cae440dc874771b0525a604428704c76e48b29a5e67b8003" +dependencies = [ + "memchr", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + +[[package]] +name = "normpath" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b11ce00d2594068e8a27c9146fdc5cf9f3ac38eb42c7cd34d05fea618873ac9f" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "notify" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c533b4c39709f9ba5005d8002048266593c1cfaf3c5f0739d5b8ab0c6c504009" +dependencies = [ + "bitflags 2.13.2", + "filetime", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "notify-types", + "walkdir", + "windows-sys 0.52.0", +] + +[[package]] +name = "notify-types" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585d3cb5e12e01aed9e8a1f70d5c6b5e86fe2a6e48fc8cd0b3e0b8df6f6eb174" +dependencies = [ + "instant", +] + +[[package]] +name = "ntapi" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" +dependencies = [ + "winapi", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" +dependencies = [ + "num-integer", + "num-traits", +] + +[[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.8", + "serde", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "bytemuck", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "num-integer" +version = "0.1.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "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 = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", + "objc_exception", +] + +[[package]] +name = "objc-foundation" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +dependencies = [ + "block", + "objc", + "objc_id", +] + +[[package]] +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +dependencies = [ + "bitflags 2.13.2", + "objc2", + "objc2-core-foundation", + "objc2-foundation", + "objc2-quartz-core", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.13.2", + "dispatch2", + "objc2", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags 2.13.2", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-metal" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0125f776a10d00af4152d74616409f0d4a2053a6f57fa5b7d6aa2854ac04794" +dependencies = [ + "bitflags 2.13.2", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +dependencies = [ + "bitflags 2.13.2", + "objc2", + "objc2-core-foundation", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" +dependencies = [ + "bitflags 2.13.2", + "objc2", + "objc2-core-foundation", + "objc2-foundation", + "objc2-quartz-core", +] + +[[package]] +name = "objc_exception" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" +dependencies = [ + "cc", +] + +[[package]] +name = "objc_id" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +dependencies = [ + "objc", +] + +[[package]] +name = "object" +version = "0.39.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e5a6c098c7a3b6547378093f5cc30bc54fd361ce711e05293a5cc589562739b" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "oo7" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3299dd401feaf1d45afd8fd1c0586f10fcfb22f244bb9afa942cec73503b89d" +dependencies = [ + "aes", + "ashpd 0.12.3", + "async-fs", + "async-io", + "async-lock", + "blocking", + "cbc", + "cipher", + "digest 0.10.7", + "endi", + "futures-lite 2.6.1", + "futures-util", + "getrandom 0.3.4", + "hkdf", + "hmac", + "md-5", + "num", + "num-bigint-dig", + "pbkdf2", + "rand 0.9.5", + "serde", + "sha2 0.10.9", + "subtle", + "zbus", + "zbus_macros", + "zeroize", + "zvariant", +] + +[[package]] +name = "open" +version = "5.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa576c76302b7b808eecc68061e67336c47833ef9d22caa74dda10fa9675eebc" +dependencies = [ + "is-wsl", + "libc", +] + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.18", + "smallvec", + "windows-link 0.2.1", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pastey" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" + +[[package]] +name = "patchops-core" +version = "1.3.4" +dependencies = [ + "dirs 6.0.0", + "flate2", + "regex", + "reqwest", + "serde", + "serde_json", + "sha2 0.10.9", + "tar", + "time", + "windows-sys 0.59.0", + "winreg", + "zip", +] + +[[package]] +name = "patchopsiii-gpui" +version = "0.1.0" +dependencies = [ + "anyhow", + "env_logger", + "gpui", + "gpui-component", + "log", + "patchops-core", + "serde_json", +] + +[[package]] +name = "pathfinder_geometry" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b7e7b4ea703700ce73ebf128e1450eb69c3a8329199ffbfb9b2a0418e5ad3" +dependencies = [ + "log", + "pathfinder_simd", +] + +[[package]] +name = "pathfinder_simd" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4500030c302e4af1d423f36f3b958d1aecb6c04184356ed5a833bf6b60435777" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest 0.10.7", + "hmac", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared", + "rand 0.8.8", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + +[[package]] +name = "pin-project" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pin-utils" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13bee6c73da26345c729282832b60b0363cf3dd9f4bfd81d8551b7a1c889a113" + +[[package]] +name = "piper" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" +dependencies = [ + "atomic-waker", + "fastrand 2.5.0", + "futures-io", +] + +[[package]] +name = "pkg-config" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide 0.8.9", +] + +[[package]] +name = "png" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +dependencies = [ + "bitflags 2.13.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide 0.8.9", +] + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix 1.1.5", + "windows-sys 0.61.2", +] + +[[package]] +name = "pollster" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5da3b0203fd7ee5720aa0b5e790b591aa5d3f41c3ed2c34a3a393382198af2f7" + +[[package]] +name = "portable-atomic" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" + +[[package]] +name = "portable-atomic-util" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "postage" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af3fb618632874fb76937c2361a7f22afd393c982a2165595407edc75b06d3c1" +dependencies = [ + "atomic", + "crossbeam-queue", + "futures", + "log", + "parking_lot", + "pin-project", + "pollster", + "static_assertions", + "thiserror 1.0.69", +] + +[[package]] +name = "potential_utf" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.119", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit 0.25.15+spec-1.1.0", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "profiling" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4488a4a36b9a4ba6b9334a32a39971f77c1436ec82c38707bce707699cc3bbcb" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "psm" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd034599e63b970727f70d79e02d62390a4a84f7c6b827c27c46d5ac3fa622" +dependencies = [ + "ar_archive_writer", + "cc", +] + +[[package]] +name = "pulp" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046aa45b989642ec2e4717c8e72d677b13edd831a4d3b6cf37d9a3e54912496a" +dependencies = [ + "bytemuck", + "cfg-if", + "libm", + "num-complex", + "paste", + "pulp-wasm-simd-flag", + "raw-cpuid", + "reborrow", + "version_check", +] + +[[package]] +name = "pulp-wasm-simd-flag" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d8f70e07b9c3962945a74e59ca1c511bba65b6419468acc217c457d93f3c740" + +[[package]] +name = "pxfm" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea" + +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + +[[package]] +name = "quick-xml" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1" +dependencies = [ + "memchr", +] + +[[package]] +name = "quinn" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4051e23e9185c255a7e33ef59cdbca87a22d359052eecd22fc6b901fb37d9d11" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash 2.1.3", + "rustls", + "socket2", + "thiserror 2.0.21", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e750cca55fe4f0439a15d0bb529da9651e79993e8e72c61a899a36d462befbe" +dependencies = [ + "bytes", + "getrandom 0.4.3", + "lru-slab", + "rand 0.10.3", + "rand_pcg", + "ring", + "rustc-hash 2.1.3", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.21", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af66907df18639dcf4db56ca65490cabc4b27a97dbadd96f2926cca73298f016" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.61.2", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +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 = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + +[[package]] +name = "rand" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af" +dependencies = [ + "chacha20", + "getrandom 0.4.3", + "rand_core 0.10.1", +] + +[[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 = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rand_pcg" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" +dependencies = [ + "rand_core 0.10.1", +] + +[[package]] +name = "rangemap" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a611d15b50743feb4c76b7d03edcb0e64f399c26961e4efe6975bc398be6aa3d" + +[[package]] +name = "rav1e" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b6dd56e85d9483277cde964fd1bdb0428de4fec5ebba7540995639a21cb32b" +dependencies = [ + "aligned-vec", + "arbitrary", + "arg_enum_proc_macro", + "arrayvec", + "av-scenechange", + "av1-grain", + "bitstream-io", + "built", + "cfg-if", + "interpolate_name", + "itertools 0.14.0", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "paste", + "profiling", + "rand 0.9.5", + "rand_chacha 0.9.0", + "simd_helpers", + "thiserror 2.0.21", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e52310197d971b0f5be7fe6b57530dcd27beb35c1b013f29d66c1ad73fbbcc45" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rayon", + "rgb", +] + +[[package]] +name = "raw-cpuid" +version = "11.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" +dependencies = [ + "bitflags 2.13.2", +] + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "raw-window-metal" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76e8caa82e31bb98fee12fa8f051c94a6aa36b07cddb03f0d4fc558988360ff1" +dependencies = [ + "cocoa 0.25.0", + "core-graphics 0.23.2", + "objc", + "raw-window-handle", +] + +[[package]] +name = "rayon" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "read-fonts" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046a7d674daf459825b32f5062056d6882db0d2f5a479fbd76ccfc870ac18709" +dependencies = [ + "bytemuck", + "font-types", + "once_cell", +] + +[[package]] +name = "reborrow" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03251193000f4bd3b042892be858ee50e8b3719f2b08e5833ac4353724632430" + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.13.2", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 1.0.69", +] + +[[package]] +name = "redox_users" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60dc65c0ff1a7ae1294b0c67b9f14baf70b644404010370171787bfac1038fc0" +dependencies = [ + "libredox", + "thiserror 2.0.21", +] + +[[package]] +name = "ref-cast" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e440fb4e4b4147295338efb76001ab9e4efc0e5839df2c47fc5ac2381d365c3" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64 0.22.1", + "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", +] + +[[package]] +name = "resvg" +version = "0.45.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8928798c0a55e03c9ca6c4c6846f76377427d2c1e1f7e6de3c06ae57942df43" +dependencies = [ + "log", + "pico-args", + "rgb", + "svgtypes", + "tiny-skia", + "usvg", +] + +[[package]] +name = "rgb" +version = "0.8.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" +dependencies = [ + "bytemuck", +] + +[[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 = "ropey" +version = "2.0.0-beta.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4045a00dc327d084a2bbf126976e14125b54f23bd30511d45b842eba76c52d74" +dependencies = [ + "str_indices", +] + +[[package]] +name = "roxmltree" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" + +[[package]] +name = "rust-embed" +version = "8.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9e7760e252aaba7b09f4be00e36476cf585bdb68a53552ac954cdf504ab4bc9" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "8.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bcfc4d6f53af43755f7a723e4b6b8794fcce052a178dd8c6c1dadc5f5343097" +dependencies = [ + "mime_guess", + "proc-macro2", + "quote", + "rust-embed-utils", + "syn 2.0.119", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "8.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ffa149f6aa81b58a5b3011d01a857c4ed12c7a732d2c51947a4c7c692185f0" +dependencies = [ + "globset", + "sha2 0.11.0", + "walkdir", +] + +[[package]] +name = "rust-i18n" +version = "3.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda2551fdfaf6cc5ee283adc15e157047b92ae6535cf80f6d4962d05717dc332" +dependencies = [ + "globwalk", + "once_cell", + "regex", + "rust-i18n-macro", + "rust-i18n-support", + "smallvec", +] + +[[package]] +name = "rust-i18n-macro" +version = "3.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22baf7d7f56656d23ebe24f6bb57a5d40d2bce2a5f1c503e692b5b2fa450f965" +dependencies = [ + "glob", + "once_cell", + "proc-macro2", + "quote", + "rust-i18n-support", + "serde", + "serde_json", + "serde_yaml", + "syn 2.0.119", +] + +[[package]] +name = "rust-i18n-support" +version = "3.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940ed4f52bba4c0152056d771e563b7133ad9607d4384af016a134b58d758f19" +dependencies = [ + "arc-swap", + "base62", + "globwalk", + "itertools 0.11.0", + "lazy_static", + "normpath", + "once_cell", + "proc-macro2", + "regex", + "serde", + "serde_json", + "serde_yaml", + "siphasher", + "toml 0.8.23", + "triomphe", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.13.2", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d" +dependencies = [ + "bitflags 2.13.2", + "errno", + "libc", + "linux-raw-sys 0.12.1", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "rustybuzz" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfb9cf8877777222e4a3bc7eb247e398b56baba500c38c1c46842431adc8b55c" +dependencies = [ + "bitflags 2.13.2", + "bytemuck", + "libm", + "smallvec", + "ttf-parser 0.21.1", + "unicode-bidi-mirroring 0.2.0", + "unicode-ccc 0.2.0", + "unicode-properties", + "unicode-script", +] + +[[package]] +name = "rustybuzz" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3c7c96f8a08ee34eff8857b11b49b07d71d1c3f4e88f8a88d4c9e9f90b1702" +dependencies = [ + "bitflags 2.13.2", + "bytemuck", + "core_maths", + "log", + "smallvec", + "ttf-parser 0.25.1", + "unicode-bidi-mirroring 0.4.0", + "unicode-ccc 0.4.0", + "unicode-properties", + "unicode-script", +] + +[[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 = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "schemars" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "687274d293b6cdc6e73e0fee520bf2049650090d7164f87672d212a3c530cf4a" +dependencies = [ + "dyn-clone", + "indexmap", + "ref-cast", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98c67716b46af2f0b8cf752abc930f6f9aecfbf671ecfb531db8a31dbe4e2ba" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 3.0.6", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "screencapturekit" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5eeeb57ac94960cfe5ff4c402be6585ae4c8d29a2cf41b276048c2e849d64e" +dependencies = [ + "screencapturekit-sys", +] + +[[package]] +name = "screencapturekit-sys" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22411b57f7d49e7fe08025198813ee6fd65e1ee5eff4ebc7880c12c82bde4c60" +dependencies = [ + "block", + "dispatch", + "objc", + "objc-foundation", + "objc_id", + "once_cell", +] + +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.13.2", + "core-foundation 0.10.0", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "self_cell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab42ca02749e120097e328d91d415325bdf43b1c72c4c8badf37375fe40a813" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "serde_derive_internals" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f852137cce035d6a4df67ccce505ff6b3e9fd3a10e3e52b24dc71e650bb1a9bd" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "serde_fmt" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e497af288b3b95d067a23a4f749f2861121ffcb2f6d8379310dcda040c345ed" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "indexmap", + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_json_lenient" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e033097bf0d2b59a62b42c18ebbb797503839b26afdda2c4e1415cb6c813540" +dependencies = [ + "indexmap", + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + +[[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 = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "sha1_smol" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "digest 0.11.3", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" + +[[package]] +name = "simd_helpers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "simplecss" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9c6883ca9c3c7c90e888de77b7a5c849c779d25d74a1269b0218b14e8b136c" +dependencies = [ + "log", +] + +[[package]] +name = "siphasher" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33f4fe9184a62d842c9ef383018f3306d8ba224fd9d836f56d7288308847c256" + +[[package]] +name = "skrifa" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819ab7d62b1d3e72d9d9dea5650bac30424f9111364bb94928dbf5ecad1baa68" +dependencies = [ + "bytemuck", + "read-fonts", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "slotmap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9395f0f0eee849a9b707b2f06bb92a6a422090e2123bb2ef8e87a0e61892a8e" + +[[package]] +name = "smol" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a33bd3e260892199c3ccfc487c88b2da2265080acb316cd920da72fdfd7c599f" +dependencies = [ + "async-channel 2.5.0", + "async-executor", + "async-fs", + "async-io", + "async-lock", + "async-net", + "async-process", + "blocking", + "futures-lite 2.6.1", +] + +[[package]] +name = "smol_str" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" + +[[package]] +name = "socket2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "spin" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spirv" +version = "0.3.0+sdk-1.3.268.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" +dependencies = [ + "bitflags 2.13.2", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "stacker" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707f49d46706bacf8a2b00d51dace3f9de527c13eec3778f570c411f89e69967" +dependencies = [ + "cc", + "cfg-if", + "libc", + "psm", + "windows-sys 0.61.2", +] + +[[package]] +name = "stacksafe" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d9c1172965d317e87ddb6d364a040d958b40a1db82b6ef97da26253a8b3d090" +dependencies = [ + "stacker", + "stacksafe-macro", +] + +[[package]] +name = "stacksafe-macro" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "172175341049678163e979d9107ca3508046d4d2a7c6682bee46ac541b17db69" +dependencies = [ + "proc-macro-error2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "str_indices" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d08889ec5408683408db66ad89e0e1f93dff55c73a4ccc71c427d5b277ee47e6" + +[[package]] +name = "streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520" + +[[package]] +name = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +dependencies = [ + "float-cmp", +] + +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", +] + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros 0.26.4", +] + +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +dependencies = [ + "strum_macros 0.27.2", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.119", +] + +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "sval" +version = "2.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b81b254da21fe1fcc4e3a74fe39b46e25e3a863078f8b71c954d47f84889dbc6" + +[[package]] +name = "sval_buffer" +version = "2.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50be352d2822ffafb59e3e2ddac9d5ee60f2eeadbb7b5a2a951b9f3651e87a6f" +dependencies = [ + "sval", + "sval_ref", + "zerocopy", +] + +[[package]] +name = "sval_dynamic" +version = "2.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048ca293b998d9a45659159f94a64063791e74cdc670164943dbb434405573d" +dependencies = [ + "sval", +] + +[[package]] +name = "sval_fmt" +version = "2.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6b5888e40f80568733217f27b7317b845f463400ced36c424b1a804730e53b2" +dependencies = [ + "itoa", + "ryu", + "sval", +] + +[[package]] +name = "sval_json" +version = "2.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17664d6bb6b74947afaab9d7c991caa9bf5638d4dee16fcbef637f440796049" +dependencies = [ + "itoa", + "ryu", + "sval", +] + +[[package]] +name = "sval_nested" +version = "2.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07c059969ca5ca163ea7fef6c9661758973d17691aba92abdcf5c428f4ec122c" +dependencies = [ + "sval", + "sval_buffer", + "sval_ref", +] + +[[package]] +name = "sval_ref" +version = "2.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d6b29ff568c85c87561807f51d2adfff4b6016c6363133f7cd1652a12548f3" +dependencies = [ + "sval", +] + +[[package]] +name = "sval_serde" +version = "2.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f33ec9edc42b12764d5c90ca0a1d84189c6bde81ed27507f1e661c6e4e05853" +dependencies = [ + "serde_core", + "sval", + "sval_nested", +] + +[[package]] +name = "svg_fmt" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0193cc4331cfd2f3d2011ef287590868599a2f33c3e69bc22c1a3d3acf9e02fb" + +[[package]] +name = "svgtypes" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68c7541fff44b35860c1a7a47a7cadf3e4a304c457b58f9870d9706ece028afc" +dependencies = [ + "kurbo", + "siphasher", +] + +[[package]] +name = "swash" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c2499c2d826531388872b2268718aed907a39bd785ab0dcfe57fab26283f92e" +dependencies = [ + "skrifa", + "yazi", + "zeno", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee" +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.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "sys-locale" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4" +dependencies = [ + "libc", +] + +[[package]] +name = "sysinfo" +version = "0.31.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "355dbe4f8799b304b05e1b0f05fc59b2a18d36645cf169607da45bde2f69a1be" +dependencies = [ + "core-foundation-sys", + "libc", + "memchr", + "ntapi", + "rayon", + "windows 0.57.0", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.13.2", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "taffy" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13e5d13f79d558b5d353a98072ca8ca0e99da429467804de959aa8c83c9a004" +dependencies = [ + "arrayvec", + "grid", + "serde", + "slotmap", +] + +[[package]] +name = "take-until" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bdb6fa0dfa67b38c1e66b7041ba9dcf23b99d8121907cd31c807a332f7a0bbb" + +[[package]] +name = "tao-core-video-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271450eb289cb4d8d0720c6ce70c72c8c858c93dd61fc625881616752e6b98f6" +dependencies = [ + "cfg-if", + "core-foundation-sys", + "libc", + "objc", +] + +[[package]] +name = "tar" +version = "0.4.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840" +dependencies = [ + "filetime", + "libc", + "xattr 1.6.1", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand 2.5.0", + "getrandom 0.4.3", + "once_cell", + "rustix 1.1.5", + "windows-sys 0.61.2", +] + +[[package]] +name = "tendril" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[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.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" +dependencies = [ + "thiserror-impl 2.0.21", +] + +[[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 2.0.119", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "tiff" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52" +dependencies = [ + "fax", + "flate2", + "half", + "quick-error", + "weezl", + "zune-jpeg", +] + +[[package]] +name = "time" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" +dependencies = [ + "deranged", + "libc", + "num-conv", + "num_threads", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tiny-skia" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "log", + "png 0.17.16", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + +[[package]] +name = "tinystr" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee" + +[[package]] +name = "tokio" +version = "1.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e95f91fcc7a621e8b030f6aa23c71fe9838ae2fb4d8118b75602a328f5144044" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9cc2678c2cdd569ef8215e2afd7954ada2ae20b4fdd2c5fe6139a3b02d105db" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-socks" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7e2948f60dbe26b35f2c7fb74ac2854c1fddded0fe9d7548fcc674a246f7615" +dependencies = [ + "either", + "futures-util", + "thiserror 1.0.69", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "libc", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", +] + +[[package]] +name = "toml" +version = "1.1.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "920602543f0911ab71da12c50d59701da54c196d1a2bf5cb4b75667f137a406a" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned 1.1.1", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 1.0.4", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_write", + "winnow 0.7.15", +] + +[[package]] +name = "toml_edit" +version = "0.25.15+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1340ea94a5856333492c9064b02c778b191dd2c853778d9609debdcdfea3a614" +dependencies = [ + "indexmap", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "winnow 1.0.4", +] + +[[package]] +name = "toml_parser" +version = "1.1.3+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" +dependencies = [ + "winnow 1.0.4", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "toml_writer" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" + +[[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 = [ + "bitflags 2.13.2", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "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-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[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 = "tree-sitter" +version = "0.25.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78f873475d258561b06f1c595d93308a7ed124d9977cb26b148c2084a4a3cc87" +dependencies = [ + "cc", + "regex", + "regex-syntax", + "serde_json", + "streaming-iterator", + "tree-sitter-language", +] + +[[package]] +name = "tree-sitter-json" +version = "0.24.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d727acca406c0020cffc6cf35516764f36c8e3dc4408e5ebe2cb35a947ec471" +dependencies = [ + "cc", + "tree-sitter-language", +] + +[[package]] +name = "tree-sitter-language" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca0d1bf6fdd806e43ae5198f82f527056d359def39e54e67a0f478ac09dac081" + +[[package]] +name = "triomphe" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b40688ea6389c8171614b25491f71d4a27946e0c7ce2da1c6de27e25abf1a0ae" +dependencies = [ + "arc-swap", + "serde", + "stable_deref_trait", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "ttf-parser" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4" + +[[package]] +name = "ttf-parser" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c591d83f69777866b9126b24c6dd9a18351f177e49d625920d19f989fd31cf8" + +[[package]] +name = "ttf-parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" +dependencies = [ + "core_maths", +] + +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "uds_windows" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" +dependencies = [ + "memoffset", + "tempfile", + "windows-sys 0.61.2", +] + +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + +[[package]] +name = "unicode-bidi-mirroring" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cb788ffebc92c5948d0e997106233eeb1d8b9512f93f41651f52b6c5f5af86" + +[[package]] +name = "unicode-bidi-mirroring" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfa6e8c60bb66d49db113e0125ee8711b7647b5579dc7f5f19c42357ed039fe" + +[[package]] +name = "unicode-ccc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df77b101bcc4ea3d78dafc5ad7e4f58ceffe0b2b16bf446aeb50b6cb4157656" + +[[package]] +name = "unicode-ccc" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce61d488bcdc9bc8b5d1772c404828b17fc481c0a582b5581e95fb233aef503e" + +[[package]] +name = "unicode-id" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65dd75ab2d9d379dbe86d4059e91d8b5a6e7054865eaba1ddbfc7a817ff96392" + +[[package]] +name = "unicode-ident" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-properties" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" + +[[package]] +name = "unicode-script" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "383ad40bb927465ec0ce7720e033cb4ca06912855fc35db31b5755d0de75b1ee" + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "unicode-vo" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[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", + "serde_derive", +] + +[[package]] +name = "usvg" +version = "0.45.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80be9b06fbae3b8b303400ab20778c80bbaf338f563afe567cf3c9eea17b47ef" +dependencies = [ + "base64 0.22.1", + "data-url", + "flate2", + "fontdb 0.23.0", + "imagesize", + "kurbo", + "log", + "pico-args", + "roxmltree", + "rustybuzz 0.20.1", + "simplecss", + "siphasher", + "strict-num", + "svgtypes", + "tiny-skia-path", + "unicode-bidi", + "unicode-script", + "unicode-vo", + "xmlwriter", +] + +[[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 = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97277d36b9c3ace13e58fa6e753f8b0bbbf302a18dd193240d70f9e29681059a" +dependencies = [ + "getrandom 0.4.3", + "js-sys", + "serde_core", + "sha1_smol", + "wasm-bindgen", +] + +[[package]] +name = "v_frame" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2" +dependencies = [ + "aligned-vec", + "num-traits", + "wasm-bindgen", +] + +[[package]] +name = "value-bag" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2799ffb329a792ecfd902b71306c8a815a6ef1c0470fa9953a6aa4d4cecbe511" +dependencies = [ + "value-bag-serde1", + "value-bag-sval2", +] + +[[package]] +name = "value-bag-serde1" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0941feceafbe7a8f59ea1096d45b97002884a41306315ad797b3684b63a81d8c" +dependencies = [ + "erased-serde", + "serde_core", + "serde_fmt", +] + +[[package]] +name = "value-bag-sval2" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "839752af8179287d27eb2b94164641b1ede9e60ab7424163388dc21ebd0508cd" +dependencies = [ + "sval", + "sval_buffer", + "sval_dynamic", + "sval_fmt", + "sval_json", + "sval_ref", + "sval_serde", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "waker-fn" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" + +[[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.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cbab34de2d982e9b48e18d216d04c4a6f641066ff19ffb699980f591ee3610e" +dependencies = [ + "js-sys", + "tokio", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 3.0.6", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wayland-backend" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38a91b4eaddff87b1cd1074985e3713da4af2c49742d1b356b2c01670a67a078" +dependencies = [ + "cc", + "downcast-rs", + "rustix 1.1.5", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c36a0f861ad76d0901f2800b46321410d9f73f2ea88aac0650d86c32688073" +dependencies = [ + "bitflags 2.13.2", + "rustix 1.1.5", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-cursor" +version = "0.31.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a52d18780be9b1314328a3de5f930b73d2200112e3849ca6cb11822793fb34d" +dependencies = [ + "rustix 1.1.5", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.31.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4" +dependencies = [ + "bitflags 2.13.2", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d0c813de3daa2ed6520af85a3bd49b0e722a3078506899aa9686fea58dc4b6" +dependencies = [ + "bitflags 2.13.2", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-plasma" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23803551115ff9ea9bce586860c5c5a971e360825a0309264102a9495a5ff479" +dependencies = [ + "bitflags 2.13.2", + "wayland-backend", + "wayland-client", + "wayland-protocols 0.31.2", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "338e30461b3a2b67d70eb30a6d89f8e0c93a833e07d2ae89085cd070c4a00ac0" +dependencies = [ + "proc-macro2", + "quick-xml", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be" +dependencies = [ + "dlib", + "log", + "once_cell", + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88261b9deccee56594c11a3460c462c41f58d148598fe70ad77070126a68aba4" +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 = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "weezl" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" + +[[package]] +name = "which" +version = "6.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ee928febd44d98f2f459a4a79bd4d928591333a494a10a868418ac1b39cf1f" +dependencies = [ + "either", + "home", + "rustix 0.38.44", + "winsafe", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[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 = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" +dependencies = [ + "windows-core 0.57.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core 0.61.2", + "windows-future", + "windows-link 0.1.3", + "windows-numerics", +] + +[[package]] +name = "windows-capture" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a4df73e95feddb9ec1a7e9c2ca6323b8c97d5eeeff78d28f1eccdf19c882b24" +dependencies = [ + "parking_lot", + "rayon", + "thiserror 2.0.21", + "windows 0.61.3", + "windows-future", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.2", +] + +[[package]] +name = "windows-core" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" +dependencies = [ + "windows-implement 0.57.0", + "windows-interface 0.57.0", + "windows-result 0.1.2", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement 0.60.2", + "windows-interface 0.59.3", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement 0.60.2", + "windows-interface 0.59.3", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-registry" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" +dependencies = [ + "windows-result 0.3.4", + "windows-strings 0.3.1", + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-registry" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" +dependencies = [ + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-result" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-strings" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[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.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[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 0.2.1", + "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-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[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 = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.55.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" +dependencies = [ + "cfg-if", + "windows-sys 0.59.0", +] + +[[package]] +name = "winsafe" +version = "0.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" + +[[package]] +name = "wio" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" +dependencies = [ + "winapi", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "workspace-hack" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "beffa227304dbaea3ad6a06ac674f9bc83a3dec3b7f63eeb442de37e7cb6bb01" + +[[package]] +name = "writeable" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" + +[[package]] +name = "x11" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "x11-clipboard" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "662d74b3d77e396b8e5beb00b9cad6a9eccf40b2ef68cc858784b14c41d535a3" +dependencies = [ + "libc", + "x11rb", +] + +[[package]] +name = "x11rb" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" +dependencies = [ + "as-raw-xcb-connection", + "gethostname", + "libc", + "rustix 1.1.5", + "x11rb-protocol", + "xcursor", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" + +[[package]] +name = "xattr" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc" +dependencies = [ + "libc", +] + +[[package]] +name = "xattr" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" +dependencies = [ + "libc", + "rustix 1.1.5", +] + +[[package]] +name = "xcb" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6c2ad15e0e922856ee89afe862b8992334bbe7953adad56cd1199358cb30566" +dependencies = [ + "bitflags 2.13.2", + "libc", + "quick-xml", + "x11", +] + +[[package]] +name = "xcursor" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "163b33ed8786455e2fa5d72f554057ce3f3182425434f756cd39c99839d88e23" + +[[package]] +name = "xim-ctext" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ac61a7062c40f3c37b6e82eeeef835d5cc7824b632a72784a89b3963c33284c" +dependencies = [ + "encoding_rs", +] + +[[package]] +name = "xim-parser" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dcee45f89572d5a65180af3a84e7ddb24f5ea690a6d3aa9de231281544dd7b7" +dependencies = [ + "bitflags 2.13.2", +] + +[[package]] +name = "xkbcommon" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d66ca9352cbd4eecbbc40871d8a11b4ac8107cfc528a6e14d7c19c69d0e1ac9" +dependencies = [ + "as-raw-xcb-connection", + "libc", + "memmap2", + "xkeysym", +] + +[[package]] +name = "xkeysym" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" + +[[package]] +name = "xml5ever" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bbb26405d8e919bc1547a5aa9abc95cbfa438f04844f5fdd9dc7596b748bf69" +dependencies = [ + "log", + "mac", + "markup5ever", +] + +[[package]] +name = "xmlwriter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" + +[[package]] +name = "y4m" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448" + +[[package]] +name = "yazi" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01738255b5a16e78bbb83e7fbba0a1e7dd506905cfc53f4622d89015a03fbb5" + +[[package]] +name = "yeslogic-fontconfig-sys" +version = "6.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d8b8abf912b9a29ff112e1671c97c33636903d13a69712037190e6805af4f76" +dependencies = [ + "dlib", + "once_cell", + "pkg-config", +] + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec8ebde2db3681e8c9980cc27822030e68752690ddfa9473e739aeb4dbde6d71" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", + "synstructure", +] + +[[package]] +name = "zbus" +version = "5.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5db4be7c075cb421e4b7ee645541604239bd243ba7c357511f4ff3a74b555907" +dependencies = [ + "async-broadcast", + "async-executor", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener 5.4.2", + "futures-core", + "futures-lite 2.6.1", + "hex", + "libc", + "ordered-stream", + "rustix 1.1.5", + "serde", + "serde_repr", + "tracing", + "uds_windows", + "uuid", + "windows-sys 0.61.2", + "winnow 1.0.4", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "5.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2990635d09ade6df1868f72f8cac69a876a90981e8bd3c40b1be413f8dc88f40" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 3.0.6", + "zbus_names", + "zvariant", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "4.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8bf88b4a3ff53e883001e0e0115b297a9d53c31b9c1edd2bfdd853e3428624e" +dependencies = [ + "serde", + "winnow 1.0.4", + "zvariant", +] + +[[package]] +name = "zcheapstr" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1afec51604565183aeb5c54c20aeab286120d4e4460f7f76e3e8bb8c0d99473" +dependencies = [ + "serde", +] + +[[package]] +name = "zed-async-tar" +version = "0.5.0-zed" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cf4b5f655e29700e473cb1acd914ab112b37b62f96f7e642d5fc6a0c02eb881" +dependencies = [ + "async-std", + "filetime", + "libc", + "pin-project", + "redox_syscall 0.2.16", + "xattr 0.2.3", +] + +[[package]] +name = "zed-font-kit" +version = "0.14.1-zed" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3898e450f36f852edda72e3f985c34426042c4951790b23b107f93394f9bff5" +dependencies = [ + "bitflags 2.13.2", + "byteorder", + "core-foundation 0.10.0", + "core-graphics 0.24.0", + "core-text", + "dirs 5.0.1", + "dwrote", + "float-ord", + "freetype-sys", + "lazy_static", + "libc", + "log", + "pathfinder_geometry", + "pathfinder_simd", + "walkdir", + "winapi", + "yeslogic-fontconfig-sys", +] + +[[package]] +name = "zed-reqwest" +version = "0.12.15-zed" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac2d05756ff48539950c3282ad7acf3817ad3f08797c205ad1c34a2ce03b9970" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "mime_guess", + "once_cell", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-native-certs", + "rustls-pemfile", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-rustls", + "tokio-socks", + "tokio-util", + "tower", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "windows-registry 0.4.0", +] + +[[package]] +name = "zed-scap" +version = "0.0.8-zed" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6b338d705ae33a43ca00287c11129303a7a0aa57b101b72a1c08c863f698ac8" +dependencies = [ + "anyhow", + "cocoa 0.25.0", + "core-graphics-helmer-fork", + "log", + "objc", + "rand 0.8.8", + "screencapturekit", + "screencapturekit-sys", + "sysinfo", + "tao-core-video-sys", + "windows 0.61.3", + "windows-capture", + "x11", + "xcb", +] + +[[package]] +name = "zed-sum-tree" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d490156d0d7311855564d6e1d6dccab992405a0c0e15e1c8ef18920c02177e35" +dependencies = [ + "arrayvec", + "log", + "rayon", + "workspace-hack", +] + +[[package]] +name = "zed-xim" +version = "0.4.0-zed" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0b46ed118eba34d9ba53d94ddc0b665e0e06a2cf874cfa2dd5dec278148642" +dependencies = [ + "ahash", + "hashbrown 0.14.5", + "log", + "x11rb", + "xim-ctext", + "xim-parser", +] + +[[package]] +name = "zeno" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6df3dc4292935e51816d896edcd52aa30bc297907c26167fec31e2b0c6a32524" + +[[package]] +name = "zerocopy" +version = "0.8.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6df92bf3d9227be3d53173901ddbffac2babc27ae50f397776ffd6dc33f800cb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac4f328cf2f05d084e496c3e9c3f33ed0a183656a16e1fcec4d464d8373aec82" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[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.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zerotrie" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "zip" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50" +dependencies = [ + "arbitrary", + "bzip2", + "crc32fast", + "crossbeam-utils", + "displaydoc", + "flate2", + "indexmap", + "memchr", + "thiserror 2.0.21", + "zopfli", + "zstd", +] + +[[package]] +name = "zlib-rs" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112" + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" + +[[package]] +name = "zopfli" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64d80649ab6db9d9f6f9c80a40becd948eda4714a0a5ac8c4d157a32231c7882" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.1.0+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ef0a8027ec3ee71300ab3bcbcd0393f434aa72b91ca6d635a39941deae8eea0" +dependencies = [ + "cc", + "pkg-config", +] + +[[package]] +name = "zune-core" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56377fd46368984a170bc5aac5567e52ca5da874caa60bea39fcbca78fb658b" + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "zune-jpeg" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" +dependencies = [ + "zune-core", +] + +[[package]] +name = "zvariant" +version = "5.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1d34c27cc6cdd1f458427519dd6b8612f7b7e3f7b9a0b2355d041dda9869147" +dependencies = [ + "endi", + "enumflags2", + "serde", + "url", + "winnow 1.0.4", + "zcheapstr", + "zvariant_derive", + "zvariant_utils", +] + +[[package]] +name = "zvariant_derive" +version = "5.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "864155e69b4352db0c7f374917bf45d1e0c8d17659c8b3dbf9795f3673f8c497" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 3.0.6", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad0294361a320b694a328460dc73add56c306150f5cb6bfafc44446120008a3" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "syn 3.0.6", + "winnow 1.0.4", +] diff --git a/native/Cargo.toml b/native/Cargo.toml new file mode 100644 index 0000000..7f8d004 --- /dev/null +++ b/native/Cargo.toml @@ -0,0 +1,8 @@ +[workspace] +members = ["core", "gpui"] +resolver = "3" + +[profile.release] +lto = "thin" +strip = "debuginfo" + diff --git a/native/core/Cargo.toml b/native/core/Cargo.toml new file mode 100644 index 0000000..da37eb1 --- /dev/null +++ b/native/core/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "patchops-core" +version = "1.3.4" +edition = "2024" +publish = false +license = "MIT" + +[dependencies] +dirs = "6" +flate2 = "1" +regex = "1" +reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "rustls-tls"] } +serde = { version = "1", features = ["derive"] } +serde_json = { version = "1", features = ["preserve_order"] } +sha2 = "0.10" +tar = "0.4" +time = { version = "0.3", features = ["formatting", "local-offset"] } +zip = { version = "2", default-features = false, features = ["deflate", "bzip2", "zstd"] } + +[build-dependencies] +serde_json = "1" + +[target.'cfg(windows)'.dependencies] +windows-sys = { version = "0.59", features = ["Win32_Foundation", "Win32_Storage_FileSystem", "Win32_UI_Shell"] } +winreg = "0.55" + diff --git a/native/core/build.rs b/native/core/build.rs new file mode 100644 index 0000000..e90fbdb --- /dev/null +++ b/native/core/build.rs @@ -0,0 +1,11 @@ +fn main() { + // Release workflows update package.json before building all deliverables. + // Embed the same stable/beta version without a runtime resource lookup. + println!("cargo:rerun-if-changed=../../package.json"); + let package: serde_json::Value = serde_json::from_str(include_str!("../../package.json")) + .expect("package.json must contain valid release metadata"); + let version = package["version"] + .as_str() + .expect("package.json version is required"); + println!("cargo:rustc-env=PATCHOPSIII_VERSION={version}"); +} diff --git a/native/core/src/app.rs b/native/core/src/app.rs new file mode 100644 index 0000000..5a72cad --- /dev/null +++ b/native/core/src/app.rs @@ -0,0 +1,919 @@ +use std::{ + collections::VecDeque, + fs::{self, OpenOptions}, + io::Write, + path::{Path, PathBuf}, + sync::{Arc, Mutex, OnceLock}, +}; + +use serde::{Deserialize, Serialize}; +use serde_json::{Map, Value}; +use time::OffsetDateTime; + +use crate::{ + fs_ops::{atomic_write, backup_path, existing_backup}, + models::{AdvancedState, GraphicsState, LogEntry, OperationProgress, QolState}, +}; + +pub const APP_VERSION: &str = env!("PATCHOPSIII_VERSION"); + +/// Toolkit-neutral hook for forwarding structured log entries to a UI. +pub type EventCallback = Arc; +/// Embedders can replace Steam process management (false = close, true = open). +/// The default implementation still closes Steam and waits before config writes. +pub type SteamLifecycleCallback = Arc Result<(), String> + Send + Sync + 'static>; +pub type ProgressCallback = Arc; + +#[derive(Clone)] +pub struct AppState(Arc); + +struct AppStateInner { + data_dir: PathBuf, + resource_dir: Option, + on_log: Option, + on_progress: Mutex>, + steam_lifecycle: Mutex>, + dxvk_legacy_hash_provider: Mutex>>, + legacy_enhanced_binding: Mutex>, + active_operation: Mutex>, + logs: Mutex>, + operation: Mutex<()>, +} + +pub fn has_game_executable(directory: &Path) -> bool { + directory.is_dir() + && ["BlackOpsIII.exe", "BlackOps3.exe"] + .iter() + .any(|name| directory.join(name).is_file()) +} + +pub fn set_game_directory(state: &AppState, path: &str) -> Result<(), String> { + if !has_game_executable(Path::new(path)) { + return Err("BlackOps3.exe or BlackOpsIII.exe was not found.".into()); + } + let candidate = Path::new(path) + .canonicalize() + .map_err(|error| format!("Unable to open the selected directory: {error}"))?; + if !has_game_executable(&candidate) { + return Err("BlackOps3.exe or BlackOpsIII.exe was not found.".into()); + } + let mut settings = state.load_settings(); + settings.game_dir = Some(candidate.to_string_lossy().into_owned()); + state.save_settings(&settings)?; + state.log( + "Success", + format!("Game directory set to {}", candidate.display()), + ); + Ok(()) +} + +pub fn release_channel(settings: &Settings) -> &str { + match settings.release_channel.as_deref() { + Some("beta") => "beta", + _ => "stable", + } +} + +pub fn set_release_channel(state: &AppState, channel: &str) -> Result<(), String> { + if !matches!(channel, "stable" | "beta") { + return Err("Release channel must be beta or stable.".into()); + } + let mut settings = state.load_settings(); + settings.release_channel = Some(channel.to_owned()); + state.save_settings(&settings)?; + state.log( + "Success", + format!("Release channel set to {}.", title_case(channel)), + ); + Ok(()) +} + +fn title_case(value: &str) -> String { + let mut chars = value.chars(); + chars + .next() + .map(|first| first.to_uppercase().collect::() + chars.as_str()) + .unwrap_or_default() +} + +pub fn config_path(game_dir: &Path) -> PathBuf { + game_dir.join("players").join("config.ini") +} + +pub fn read_config(game_dir: &Path) -> String { + fs::read(config_path(game_dir)) + .map(|bytes| String::from_utf8_lossy(&bytes).into_owned()) + .unwrap_or_default() +} + +fn config_value<'a>(content: &'a str, key: &str) -> Option<&'a str> { + content.lines().find_map(|line| { + let line = line.trim_start(); + let rest = line.strip_prefix(key)?; + if !rest.starts_with(char::is_whitespace) && !rest.starts_with('=') { + return None; + } + let value = rest.trim_start().strip_prefix('=')?.trim_start(); + let value = value.strip_prefix('"')?; + value.find('"').map(|end| &value[..end]) + }) +} + +fn config_i32(content: &str, key: &str, default: i32) -> i32 { + config_value(content, key) + .and_then(|value| value.parse::().ok()) + .map(|value| value as i32) + .unwrap_or(default) +} + +fn config_f64(content: &str, key: &str, default: f64) -> f64 { + config_value(content, key) + .and_then(|value| value.parse().ok()) + .unwrap_or(default) +} + +fn config_bool(content: &str, key: &str, enabled: &str, default: &str) -> bool { + config_value(content, key).unwrap_or(default) == enabled +} + +pub fn graphics_state(content: &str) -> GraphicsState { + GraphicsState { + max_fps: config_i32(content, "MaxFPS", 165), + fov: config_i32(content, "FOV", 80), + display_mode: config_i32(content, "FullScreenMode", 1), + resolution: config_value(content, "WindowSize") + .unwrap_or("1920x1080") + .to_owned(), + refresh_rate: config_f64(content, "RefreshRate", 60.0), + render_resolution: config_i32(content, "ResolutionPercent", 100), + vsync: config_bool(content, "Vsync", "1", "1"), + draw_fps: config_bool(content, "DrawFPS", "1", "0"), + } +} + +pub fn advanced_state(game_dir: Option<&Path>, content: &str) -> AdvancedState { + let video_memory = config_value(content, "VideoMemory").unwrap_or("1"); + let stream_min = config_value(content, "StreamMinResident").unwrap_or("0"); + AdvancedState { + smooth_framerate: config_bool(content, "SmoothFramerate", "1", "0"), + unlock_options: config_bool(content, "RestrictGraphicsOptions", "0", "1"), + reduce_cpu: config_bool(content, "SerializeRender", "2", "0"), + max_frame_latency: config_i32(content, "MaxFrameLatency", 1), + vram_limited: video_memory != "1" || stream_min != "0", + vram_target: (config_f64(content, "VideoMemory", 0.75) * 100.0) as i32, + config_readonly: game_dir + .map(config_path) + .and_then(|path| path.metadata().ok()) + .is_some_and(|metadata| metadata.permissions().readonly()), + } +} + +fn valid_config_key(key: &str) -> bool { + !key.is_empty() + && key + .chars() + .all(|character| character.is_ascii_alphanumeric() || character == '_') +} + +pub fn write_config_values( + game_dir: &Path, + updates: &[(String, String, String)], +) -> Result<(), String> { + let path = config_path(game_dir); + if !path.is_file() { + return Err(format!("config.ini not found at {}", path.display())); + } + if path + .metadata() + .map_err(|error| error.to_string())? + .permissions() + .readonly() + { + return Err(format!( + "[Errno 13] Permission denied: '{}'", + path.display() + )); + } + let mut content = read_config(game_dir); + // Python text-mode writes normalize CRLF input to LF. + let line_ending = "\n"; + let mut lines: Vec = content.lines().map(str::to_owned).collect(); + + for (key, value, comment) in updates { + if !valid_config_key(key) + || value.chars().any(char::is_control) + || comment + .chars() + .any(|character| matches!(character, '\r' | '\n')) + { + return Err("Invalid configuration value.".into()); + } + let replacement = format!("{key} = \"{value}\" // {comment}"); + let mut matched = false; + for line in &mut lines { + let trimmed = line.trim_start(); + let Some(rest) = trimmed.strip_prefix(key) else { + continue; + }; + if rest.starts_with(char::is_whitespace) || rest.starts_with('=') { + *line = replacement.clone(); + matched = true; + } + } + if !matched { + lines.push(replacement); + } + } + content = lines.join(line_ending); + content.push_str(line_ending); + atomic_write(&path, content.as_bytes()) +} + +fn config_input(key: &str, value: &Value) -> Result<(String, &'static str), String> { + let raw = match value { + Value::String(value) => value.clone(), + Value::Number(value) => value.to_string(), + _ => return Err("Invalid configuration value.".into()), + }; + let number = || { + raw.parse::() + .map_err(|_| "Expected a whole number.".to_string()) + }; + let ranged = |minimum, maximum| { + let value = number()?; + if (minimum..=maximum).contains(&value) { + Ok(value.to_string()) + } else { + Err(format!("Value must be between {minimum} and {maximum}.")) + } + }; + let result = match key { + "MaxFPS" => (ranged(0, 1000)?, "Maximum FPS cap"), + "FOV" => (ranged(65, 120)?, "Field of view"), + "FullScreenMode" => ( + ranged(0, 2)?, + "0=Windowed,1=Fullscreen,2=Fullscreen Windowed", + ), + "RefreshRate" => (ranged(1, 240)?, "1 to 240"), + "ResolutionPercent" => (ranged(50, 200)?, "50 to 200"), + "MaxFrameLatency" => (ranged(0, 4)?, "Maximum frame latency"), + "Vsync" if matches!(raw.as_str(), "0" | "1") => (raw, "Vertical sync"), + "DrawFPS" if matches!(raw.as_str(), "0" | "1") => (raw, "FPS counter"), + "SmoothFramerate" if matches!(raw.as_str(), "0" | "1") => (raw, "Frame smoothing"), + "RestrictGraphicsOptions" if matches!(raw.as_str(), "0" | "1") => { + (raw, "Expose all graphics options") + } + "SerializeRender" if matches!(raw.as_str(), "0" | "2") => (raw, "Reduce CPU pressure"), + "WindowSize" => { + let mut parts = raw.split('x'); + let width = parts.next().and_then(|part| part.parse::().ok()); + let height = parts.next().and_then(|part| part.parse::().ok()); + if parts.next().is_some() + || !width.is_some_and(|value| (320..=16384).contains(&value)) + || !height.is_some_and(|value| (200..=16384).contains(&value)) + { + return Err("Resolution must look like 1920x1080.".into()); + } + (raw, "Screen resolution") + } + _ => return Err("Unsupported configuration key or value.".into()), + }; + Ok(result) +} + +pub fn set_config_value( + state: &AppState, + game_dir: &Path, + key: &str, + value: Value, + comment: Option<&str>, +) -> Result<(), String> { + let (value, default_comment) = config_input(key, &value)?; + let comment = comment.unwrap_or(default_comment); + write_config_values( + game_dir, + &[(key.to_owned(), value.clone(), comment.to_owned())], + )?; + state.log("Success", format!("Set {key} to {value}.")); + Ok(()) +} + +pub fn set_vram_target( + state: &AppState, + game_dir: &Path, + limited: bool, + target: i32, +) -> Result<(), String> { + if limited && !(75..=100).contains(&target) { + return Err("VRAM target must be between 75 and 100.".into()); + } + let updates = if limited { + vec![ + ( + "VideoMemory".into(), + serde_json::json!(target as f64 / 100.0).to_string(), + "0.75 to 1".into(), + ), + ("StreamMinResident".into(), "1".into(), "0 or 1".into()), + ] + } else { + vec![ + ("VideoMemory".into(), "1".into(), "0.75 to 1".into()), + ("StreamMinResident".into(), "0".into(), "0 or 1".into()), + ] + }; + write_config_values(game_dir, &updates)?; + state.log( + "Success", + if limited { + format!("Limited VRAM usage set to {target}%.") + } else { + "Enabled full VRAM usage.".into() + }, + ); + Ok(()) +} + +pub fn set_config_readonly(state: &AppState, game_dir: &Path, enabled: bool) -> Result<(), String> { + let path = config_path(game_dir); + let metadata = path + .metadata() + .map_err(|_| "config.ini was not found.".to_string())?; + let mut permissions = metadata.permissions(); + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + permissions.set_mode(if enabled { 0o400 } else { 0o600 }); + } + #[cfg(not(unix))] + permissions.set_readonly(enabled); + fs::set_permissions(&path, permissions).map_err(|error| error.to_string())?; + state.log( + "Success", + format!( + "config.ini set to {}.", + if enabled { "read-only" } else { "writable" } + ), + ); + Ok(()) +} + +pub fn qol_state(game_dir: Option<&Path>) -> QolState { + let Some(game_dir) = game_dir else { + return QolState::default(); + }; + let video_dir = game_dir.join("video"); + let intro = video_dir.join("BO3_Global_Logo_LogoSequence.mkv"); + let d3dcompiler = game_dir.join("d3dcompiler_46.dll"); + let mut mkv = 0; + let mut backups = 0; + if let Ok(entries) = fs::read_dir(video_dir) { + for path in entries.flatten().map(|entry| entry.path()) { + let name = path + .file_name() + .and_then(|name| name.to_str()) + .unwrap_or_default(); + if name.ends_with(".mkv") { + mkv += 1; + } + if name.ends_with(".mkv.patchops.bak") || name.ends_with(".mkv.bak") { + backups += 1; + } + } + } + QolState { + d3dcompiler: existing_backup(&d3dcompiler).is_some(), + intro: existing_backup(&intro).is_some(), + all_intros: backups > 0 && mkv == 0, + } +} + +pub fn set_intro_skip(state: &AppState, game_dir: &Path, enabled: bool) -> Result<(), String> { + let target = game_dir + .join("video") + .join("BO3_Global_Logo_LogoSequence.mkv"); + toggle_backup(&target, enabled)?; + state.log( + "Success", + if enabled { + "Intro video skipped." + } else { + "Intro video restored." + }, + ); + Ok(()) +} + +pub fn set_d3dcompiler(state: &AppState, game_dir: &Path, enabled: bool) -> Result<(), String> { + let target = game_dir.join("d3dcompiler_46.dll"); + let already_skipped = enabled && existing_backup(&target).is_some(); + toggle_backup(&target, enabled)?; + state.log( + "Success", + if already_skipped { + "Already using latest d3dcompiler." + } else if enabled { + "Renamed d3dcompiler_46.dll to reduce stuttering." + } else { + "Restored d3dcompiler_46.dll." + }, + ); + Ok(()) +} + +fn toggle_backup(target: &Path, enabled: bool) -> Result<(), String> { + if enabled { + if existing_backup(target).is_some() { + return Ok(()); + } + if !target.is_file() { + return Err(format!("{} was not found.", target.display())); + } + fs::rename(target, backup_path(target)).map_err(|error| error.to_string()) + } else { + let backup = existing_backup(target) + .ok_or_else(|| format!("No backup was found for {}.", target.display()))?; + if target.exists() { + return Err(format!("Refusing to overwrite {}.", target.display())); + } + fs::rename(backup, target).map_err(|error| error.to_string()) + } +} + +pub fn set_all_intro_skip(state: &AppState, game_dir: &Path, enabled: bool) -> Result<(), String> { + let video_dir = game_dir.join("video"); + if !video_dir.is_dir() { + return Err("Video directory was not found.".into()); + } + let mut operations = Vec::new(); + for path in fs::read_dir(&video_dir) + .map_err(|error| error.to_string())? + .flatten() + .map(|entry| entry.path()) + { + let name = path + .file_name() + .and_then(|name| name.to_str()) + .unwrap_or_default(); + if enabled && name.ends_with(".mkv") && existing_backup(&path).is_none() { + operations.push((path.clone(), backup_path(&path))); + } else if !enabled && (name.ends_with(".mkv.patchops.bak") || name.ends_with(".mkv.bak")) { + let suffix = if name.ends_with(".patchops.bak") { + ".patchops.bak" + } else { + ".bak" + }; + let target = path.with_file_name(&name[..name.len() - suffix.len()]); + if !target.exists() { + operations.push((path.clone(), target)); + } + } + } + let mut completed = Vec::new(); + for (source, destination) in &operations { + if let Err(error) = fs::rename(source, destination) { + for (source, destination) in completed.into_iter().rev() { + let _ = fs::rename(destination, source); + } + return Err(error.to_string()); + } + completed.push((source.clone(), destination.clone())); + } + state.log( + "Success", + if enabled && operations.is_empty() { + "Intro videos were already skipped." + } else if enabled { + "All intro videos skipped." + } else { + "Intro videos restored." + }, + ); + Ok(()) +} + +static PRESETS: OnceLock> = OnceLock::new(); + +fn presets() -> &'static Map { + PRESETS.get_or_init(|| { + serde_json::from_str(include_str!("../../../presets.json")) + .expect("bundled presets.json must be valid") + }) +} + +pub fn preset_names() -> Vec { + presets().keys().cloned().collect() +} + +pub fn apply_preset(state: &AppState, game_dir: &Path, name: &str) -> Result<(), String> { + let preset = presets() + .get(name) + .and_then(Value::as_object) + .ok_or_else(|| "Unknown preset.".to_string())?; + let mut updates = Vec::with_capacity(preset.len()); + for (key, item) in preset { + if key == "ReduceStutter" { + continue; + } + let values = item + .as_array() + .filter(|values| values.len() == 2) + .ok_or_else(|| format!("Preset value for {key} is invalid."))?; + let value = values[0] + .as_str() + .map(str::to_owned) + .unwrap_or_else(|| values[0].to_string()); + let comment = values[1] + .as_str() + .ok_or_else(|| format!("Preset comment for {key} is invalid."))?; + updates.push((key.clone(), value, comment.to_owned())); + } + write_config_values(game_dir, &updates)?; + state.log("Success", format!("Applied preset '{name}'.")); + Ok(()) +} + +pub fn platform_name() -> &'static str { + match std::env::consts::OS { + "windows" => "Windows", + "linux" => "Linux", + "macos" => "Darwin", + other => other, + } +} + +fn kernel_release() -> String { + #[cfg(target_os = "linux")] + { + fs::read_to_string("/proc/sys/kernel/osrelease") + .unwrap_or_default() + .trim() + .to_owned() + } + #[cfg(not(target_os = "linux"))] + { + String::new() + } +} + +pub fn log_payload(state: &AppState) -> String { + let body = fs::read_to_string(state.log_path()).unwrap_or_default(); + format!( + "PatchOpsIII {APP_VERSION} - {} {} ({}) logs:\n```\n{}\n```", + platform_name(), + kernel_release(), + std::env::consts::ARCH, + if body.trim().is_empty() { + "(no log entries found)" + } else { + body.trim() + } + ) +} + +pub fn clear_mod_files(state: &AppState) -> Result<(), String> { + let directory = state.mod_files_dir(); + fs::create_dir_all(&directory).map_err(|error| error.to_string())?; + for entry in fs::read_dir(&directory).map_err(|error| error.to_string())? { + let path = entry.map_err(|error| error.to_string())?.path(); + let metadata = fs::symlink_metadata(&path).map_err(|error| error.to_string())?; + if metadata.is_dir() && !metadata.file_type().is_symlink() { + fs::remove_dir_all(path).map_err(|error| error.to_string())?; + } else { + fs::remove_file(path).map_err(|error| error.to_string())?; + } + } + state.log( + "Success", + format!("Cleared mod files in {}", directory.display()), + ); + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::atomic::{AtomicUsize, Ordering}; + + static NEXT: AtomicUsize = AtomicUsize::new(0); + + fn temp_dir(label: &str) -> PathBuf { + let path = std::env::temp_dir().join(format!( + "patchops-app-{label}-{}-{}", + std::process::id(), + NEXT.fetch_add(1, Ordering::Relaxed) + )); + fs::create_dir_all(&path).unwrap(); + path + } + + #[test] + fn config_reader_uses_first_quoted_value() { + let content = "MaxFPS = \"120\" // first\nMaxFPS = \"240\" // second\nLoose=12\n"; + assert_eq!(config_value(content, "MaxFPS"), Some("120")); + assert_eq!(config_value(content, "Loose"), None); + } + + #[test] + fn only_known_config_inputs_are_accepted() { + assert!(config_input("MaxFPS", &Value::from(240)).is_ok()); + assert!(config_input("MaxFPS", &Value::from(1001)).is_err()); + assert!(config_input("WindowSize", &Value::from("1920x1080")).is_ok()); + assert!(config_input("WindowSize", &Value::from("1920x1080\nInjected=1")).is_err()); + assert!(config_input("Unknown", &Value::from(1)).is_err()); + } + + #[test] + fn legacy_settings_round_trip_without_losing_unknown_fields() { + let settings: Settings = serde_json::from_str( + r#"{"game_dir":"C:\\Games\\BO3","release_channel":"beta","future":true}"#, + ) + .unwrap(); + assert_eq!(settings.game_dir.as_deref(), Some(r"C:\Games\BO3")); + assert_eq!(release_channel(&settings), "beta"); + + let saved = serde_json::to_value(settings).unwrap(); + assert_eq!(saved["game_dir"], r"C:\Games\BO3"); + assert_eq!(saved["release_channel"], "beta"); + assert_eq!(saved["future"], true); + } + + #[test] + fn readonly_config_cannot_be_replaced() { + let game = temp_dir("readonly-config"); + let path = config_path(&game); + fs::create_dir_all(path.parent().unwrap()).unwrap(); + let original = b"MaxFPS = \"120\" // Maximum FPS cap\n"; + fs::write(&path, original).unwrap(); + let mut permissions = path.metadata().unwrap().permissions(); + permissions.set_readonly(true); + fs::set_permissions(&path, permissions).unwrap(); + + let updates = vec![("MaxFPS".into(), "240".into(), "Maximum FPS cap".into())]; + let error = write_config_values(&game, &updates).unwrap_err(); + assert!(error.contains("Permission denied")); + assert_eq!(fs::read(&path).unwrap(), original); + + let mut permissions = path.metadata().unwrap().permissions(); + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + permissions.set_mode(0o644); + } + #[cfg(not(unix))] + #[allow( + clippy::permissions_set_readonly_false, + reason = "Non-Unix cleanup clears the readonly flag; Unix cleanup restores mode bits above." + )] + permissions.set_readonly(false); + fs::set_permissions(&path, permissions).unwrap(); + fs::remove_dir_all(game).unwrap(); + } +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +pub struct Settings { + #[serde(default)] + pub game_dir: Option, + #[serde(default)] + pub release_channel: Option, + #[serde(default)] + pub enhanced_dump_source: Option, + #[serde(default)] + pub enhanced_exe_hash: Option, + #[serde(default)] + pub enhanced_exe_hashes: Map, + #[serde(flatten)] + pub extra: Map, +} + +impl AppState { + /// Override legacy DLL recognition for synthetic fixtures. Manifest and + /// backup checks always use physical hashes, as do downloads and mutations. + pub fn set_dxvk_legacy_hash_provider(&self, provider: Arc) { + *self + .0 + .dxvk_legacy_hash_provider + .lock() + .expect("DXVK hash provider lock") = Some(provider); + } + + pub(crate) fn dxvk_legacy_hash_provider(&self) -> Option> { + self.0.dxvk_legacy_hash_provider.lock().ok()?.clone() + } + + pub(crate) fn legacy_enhanced_binding(&self) -> &Mutex> { + &self.0.legacy_enhanced_binding + } + + pub fn set_steam_lifecycle_callback(&self, callback: SteamLifecycleCallback) { + *self.0.steam_lifecycle.lock().expect("Steam lifecycle lock") = Some(callback); + } + + pub(crate) fn steam_lifecycle_override(&self, opening: bool) -> Option> { + let callback = self.0.steam_lifecycle.lock().ok()?.clone()?; + Some(callback(opening)) + } + + pub fn new( + data_dir: PathBuf, + resource_dir: Option, + on_log: Option, + ) -> Result { + fs::create_dir_all(&data_dir).map_err(|error| error.to_string())?; + Ok(Self(Arc::new(AppStateInner { + data_dir, + resource_dir, + on_log, + on_progress: Mutex::new(None), + steam_lifecycle: Mutex::new(None), + dxvk_legacy_hash_provider: Mutex::new(None), + legacy_enhanced_binding: Mutex::new(None), + active_operation: Mutex::new(None), + logs: Mutex::new(VecDeque::with_capacity(300)), + operation: Mutex::new(()), + }))) + } + + /// Construct the normal desktop state without depending on a GUI toolkit. + pub fn for_desktop(on_log: Option) -> Result { + let data_dir = + match std::env::var_os("PATCHOPSIII_DATA_DIR").filter(|path| !path.is_empty()) { + Some(path) => PathBuf::from(path), + None => dirs::data_dir() + .ok_or_else(|| "Unable to resolve the application data directory.".to_string())? + .join("PatchOpsIII"), + }; + let resource_dir = std::env::current_exe().ok().and_then(|executable| { + let directory = executable.parent()?; + let candidates = [directory.join("resources"), directory.to_path_buf()]; + candidates.into_iter().find(|path| path.is_dir()) + }); + Self::new(data_dir, resource_dir, on_log) + } + + /// Callbacks run on the operation worker. Forward to a channel; never touch + /// GUI entities here. Installing this callback does not require a toolkit. + pub fn set_progress_callback(&self, callback: ProgressCallback) { + *self.0.on_progress.lock().expect("progress callback lock") = Some(callback); + } + + pub(crate) fn begin_progress(&self, op: &str) { + *self + .0 + .active_operation + .lock() + .expect("operation progress lock") = Some(op.into()); + self.progress("started", None, "Operation started"); + } + + pub(crate) fn end_progress(&self, error: Option<&str>) { + self.progress( + if error.is_some() { + "failed" + } else { + "completed" + }, + error.is_none().then_some(1.0), + error.unwrap_or("Operation completed"), + ); + *self + .0 + .active_operation + .lock() + .expect("operation progress lock") = None; + } + + pub(crate) fn progress(&self, stage: &str, fraction: Option, message: &str) { + let op = self + .0 + .active_operation + .lock() + .ok() + .and_then(|op| op.clone()); + let callback = self.0.on_progress.lock().ok().and_then(|cb| cb.clone()); + if let (Some(op), Some(callback)) = (op, callback) { + callback(OperationProgress { + op, + stage: stage.into(), + fraction, + message: message.into(), + }); + } + } + + pub fn resource_dir(&self) -> Option<&Path> { + self.0.resource_dir.as_deref() + } + + pub fn data_dir(&self) -> &Path { + &self.0.data_dir + } + + pub fn settings_path(&self) -> PathBuf { + self.data_dir().join("electron-settings.json") + } + + pub fn mod_files_dir(&self) -> PathBuf { + self.data_dir().join("BO3 Mod Files") + } + + pub fn log_path(&self) -> PathBuf { + self.data_dir().join("PatchOpsIII.log") + } + + pub fn lock_operation(&self) -> Result, String> { + self.0 + .operation + .lock() + .map_err(|_| "another operation failed unexpectedly".to_string()) + } + + pub fn load_settings(&self) -> Settings { + fs::read_to_string(self.settings_path()) + .ok() + .and_then(|body| serde_json::from_str(&body).ok()) + .unwrap_or_default() + } + + pub fn save_settings(&self, settings: &Settings) -> Result<(), String> { + // Retain existing key order and avoid materializing absent/null defaults + // in Electron's shared settings document. + let mut document = fs::read(self.settings_path()) + .ok() + .and_then(|body| serde_json::from_slice::>(&body).ok()) + .unwrap_or_default(); + let updates = serde_json::to_value(settings).map_err(|error| error.to_string())?; + for (key, value) in updates.as_object().expect("Settings is an object") { + if document.contains_key(key) + || (!value.is_null() + && !(key == "enhanced_exe_hashes" + && value.as_object().is_some_and(Map::is_empty))) + { + document.insert(key.clone(), value.clone()); + } + } + let body = serde_json::to_vec_pretty(&document).map_err(|error| error.to_string())?; + atomic_write(&self.settings_path(), &body) + } + + pub fn log(&self, category: &str, message: impl Into) { + let message = message.into(); + let now = OffsetDateTime::now_local().unwrap_or_else(|_| OffsetDateTime::now_utc()); + let timestamp = format!( + "{:04}-{:02}-{:02} {:02}:{:02}:{:02}", + now.year(), + now.month() as u8, + now.day(), + now.hour(), + now.minute(), + now.second() + ); + let line = format!("{timestamp} - {category}: {message}"); + let entry = LogEntry { + category: category.to_owned(), + message, + line: line.clone(), + }; + + if let Ok(mut logs) = self.0.logs.lock() { + if logs.len() == 300 { + logs.pop_front(); + } + logs.push_back(entry.clone()); + } + if let Ok(mut file) = OpenOptions::new() + .create(true) + .append(true) + .open(self.log_path()) + { + let _ = writeln!(file, "{line}"); + } + self.progress("running", None, &entry.message); + if let Some(callback) = &self.0.on_log { + callback(entry); + } + } + + pub fn recent_logs(&self) -> Vec { + self.0 + .logs + .lock() + .map(|logs| logs.iter().rev().take(80).cloned().collect::>()) + .map(|mut logs| { + logs.reverse(); + logs + }) + .unwrap_or_default() + } + + pub fn clear_logs(&self) -> Result<(), String> { + atomic_write(&self.log_path(), b"")?; + self.0 + .logs + .lock() + .map_err(|_| "log buffer is unavailable".to_string())? + .clear(); + Ok(()) + } +} diff --git a/native/core/src/dxvk.rs b/native/core/src/dxvk.rs new file mode 100644 index 0000000..c50844d --- /dev/null +++ b/native/core/src/dxvk.rs @@ -0,0 +1,1553 @@ +use std::{ + collections::HashSet, + fs, + io::Read, + path::{Path, PathBuf}, + sync::atomic::{AtomicU64, Ordering}, + time::Duration, +}; + +use serde::{Deserialize, Serialize}; + +use crate::{ + app::AppState, + fs_ops::{self, Snapshot, remove_file_if_present}, + models::{DxvkSettings, DxvkState}, +}; + +const RELEASE_API: &str = + "https://gitlab.com/api/v4/projects/Ph42oN%2Fdxvk-gplasync/releases/v3.0-1"; +const DXVK_FILES: [&str; 2] = ["dxgi.dll", "d3d11.dll"]; +const MANAGED_FILES: [&str; 3] = ["dxgi.dll", "d3d11.dll", "dxvk.conf"]; +const MANAGED_STATE_DIRECTORY: &str = "DXVK Managed"; +const MANAGED_MANIFEST_FILENAME: &str = "manifest.json"; +const MANAGED_BACKUP_DIRECTORY: &str = "originals"; +const MANAGED_STATE_VERSION: u32 = 1; +const MAX_RELEASE_JSON_BYTES: u64 = 4 * 1024 * 1024; +const MAX_ARCHIVE_BYTES: u64 = 2 * 1024 * 1024 * 1024; +const UNMANAGED_CONFIG_MESSAGE: &str = "DXVK is not managed by PatchOpsIII; install it before applying settings so existing files can be backed up safely."; +// Exact x64 member pairs from every official DXVK-GPLAsync release that the +// legacy application's dynamic "latest" installer could have installed since +// PatchOpsIII's first public beta. Pairing prevents mixed-release adoption. +const KNOWN_LEGACY_DXVK_PAIRS: [[&str; 2]; 8] = [ + // v2.5.2-1 + [ + "e534c7b9aadd90bf5744be7637abcccbeb66dd09ccf6b998e11b303bb14d1294", + "a3bd2c6fdec65f3984550cd8dc852e1ac2bff9a30430bcba897cac78a2d9bcb3", + ], + // v2.5.3-1 (latest when PatchOpsIII's first public beta shipped) + [ + "a5c3fa8b51559c77e9046a7c530b734eaf4a4d8a6f3d66edaa6375264bd3f4e9", + "c373241f40f465f762e15a3bf53c236585bfae755e6d8eaadad091c8bcd15c55", + ], + // v2.6-1 + [ + "d159fc62a0f8f47e8cfafd8b87a479c71ab907ba91fb597e640b997b46a0b237", + "e628c11af143ee75870c7758425dd2fe0039f898362176bef8cc23d6fee25be9", + ], + // v2.6.1-1 + [ + "7db4863ee96ee5358a64b70358c124c3c5228fa25f619c121dd9a38b48a2db71", + "8cd8cda2d5923c91c9cb095e22490f01cfd098254b1b261c1f8c5a916985dfd8", + ], + // v2.6.2-1 + [ + "d4811fca51d8e2517f63c680b724a9d566d163fad94c858d2029c6ab466d4163", + "225b611cd14b6291ddd46d598b05f0362263869b1ef2252c1b637d43565c3b9d", + ], + // v2.7-1 + [ + "5e291e1b41a2a5ec0727a629fab7b43b8ebc9a269f58439ea8eed937925084f9", + "e9b3a452bc623c39b025e06e248276a9b8bcd87eb7eee7de55e8d5c5bf822522", + ], + // v2.7.1-1 + [ + "8d5ae3c40962a4846e4dae9d373a8660227e85f051d12e5bb882ef92d136dfca", + "7f20637b7a9527fbed53985fa55c5fc56c7d8bd132589219e92bd49f488c2754", + ], + // v3.0-1 + [ + "177cea0f3d64ac7a2834e24637aecb4ab133e036c50c2568489079cefb8fd7ec", + "c9e9d1a7844077df38cd0e540be1b435db7c9fed878a60fc32d22c778345fc09", + ], +]; +static STAGE_COUNTER: AtomicU64 = AtomicU64::new(0); + +// App-owned trust pin for the public GitLab artifact produced by release v3.0-1. +// Provenance: GitLab job 15069375988, 18,365,240 bytes; the downloaded artifact's +// MD5 also matched GitLab's x-goog-hash/ETag value ae79003c73e0e36c7bd1ea985d77478f. +const RELEASE_PINS: [ReleasePin; 1] = [ReleasePin { + tag_name: "v3.0-1", + asset_name: "dxvk-gplasync-v3.0-1.zip", + url: "https://gitlab.com/Ph42oN/dxvk-gplasync/-/jobs/15069375988/artifacts/download?file_type=archive", + sha256: "83af6b77a080373ae00718239491e815f2bdc3145e074e8198ed7200cb296f7b", +}]; + +#[derive(Debug, Deserialize)] +struct Release { + #[serde(default)] + tag_name: String, + #[serde(default)] + name: String, + #[serde(default)] + assets: ReleaseAssets, +} + +#[derive(Debug, Default, Deserialize)] +struct ReleaseAssets { + #[serde(default)] + links: Vec, +} + +#[derive(Debug, Deserialize)] +struct ReleaseLink { + #[serde(default)] + name: String, + #[serde(default)] + url: String, +} + +#[derive(Clone, Copy, Debug)] +struct ReleasePin { + tag_name: &'static str, + asset_name: &'static str, + url: &'static str, + sha256: &'static str, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum ArchiveKind { + Zip, + TarGz, +} + +#[derive(Debug, PartialEq, Eq)] +struct ReleaseAsset { + url: String, + filename: String, + kind: ArchiveKind, + sha256: String, +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)] +struct ManagedManifest { + version: u32, + game_dir: PathBuf, + files: Vec, +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)] +struct ManagedFile { + name: String, + installed_sha256: String, + original_sha256: Option, +} + +/// Read DXVK installation and configuration state. Invalid or stale game paths +/// are reported as an uninstalled default state. +pub fn status(game_dir: Option<&Path>) -> DxvkState { + let Some(game_dir) = game_dir.filter(|path| !path.as_os_str().is_empty() && path.is_dir()) + else { + return DxvkState::default(); + }; + + let conf = game_dir.join("dxvk.conf"); + let conf_exists = regular_file(&conf); + let settings = if conf_exists { + fs::read(&conf) + .map(|body| parse_conf(&String::from_utf8_lossy(&body))) + .unwrap_or_default() + } else { + DxvkSettings::default() + }; + + DxvkState { + installed: DXVK_FILES + .iter() + .all(|filename| regular_file(&game_dir.join(filename))), + conf_exists, + settings, + } +} + +/// Validate and atomically replace `dxvk.conf` using the renderer's existing +/// settings model. +pub fn configure(state: &AppState, game_dir: &Path, settings: &DxvkSettings) -> Result<(), String> { + validate_settings(settings)?; + let game_dir = validate_game_dir(game_dir)?; + let provider = state.dxvk_legacy_hash_provider(); + adopt_legacy_install_with_hash_provider( + state.data_dir(), + &game_dir, + &known_legacy_pairs(), + provider.as_deref(), + )?; + configure_managed(state.data_dir(), &game_dir, settings)?; + state.log("Success", "Updated dxvk.conf from DXVK settings."); + Ok(()) +} + +fn configure_managed( + storage_dir: &Path, + game_dir: &Path, + settings: &DxvkSettings, +) -> Result<(), String> { + let conf = flat_target(game_dir, "dxvk.conf")?; + let mut manifest = managed_manifest_for_configure(storage_dir, game_dir)?; + verify_managed_targets(&manifest, game_dir, storage_dir)?; + let stage = create_private_dir(storage_dir, "dxvk-configure")?; + let snapshot = Snapshot::capture(std::slice::from_ref(&conf), &stage.join("rollback"))?; + let result = (|| { + fs_ops::atomic_write(&conf, build_conf(settings, true).as_bytes())?; + update_installed_hashes(&mut manifest, game_dir)?; + save_managed_manifest(storage_dir, &manifest) + })(); + if let Err(error) = result { + return match snapshot.restore() { + Ok(()) => { + let _ = fs::remove_dir_all(&stage); + Err(format!("{error}; previous DXVK configuration was restored")) + } + Err(rollback) => Err(format!( + "{error}; rollback failed: {rollback}. Recovery files remain in {}", + stage.display() + )), + }; + } + let _ = fs::remove_dir_all(&stage); + Ok(()) +} + +/// Download the pinned DXVK-GPLAsync release, validate it in an isolated +/// staging directory, and commit the two required DLLs plus `dxvk.conf` with +/// rollback on failure. +pub fn install(state: &AppState, game_dir: &Path, settings: &DxvkSettings) -> Result<(), String> { + validate_settings(settings)?; + let game_dir = validate_game_dir(game_dir)?; + let stage = create_stage(state, "dxvk")?; + + let result = (|| { + state.log("Info", "Downloading DXVK-GPLAsync..."); + let release = fetch_pinned_release()?; + let asset = select_release_asset(&release)?; + let archive = stage.join(&asset.filename); + fs_ops::download( + state, + &asset.url, + &archive, + Some(&asset.sha256), + MAX_ARCHIVE_BYTES, + )?; + state.log("Success", "Downloaded DXVK-GPLAsync successfully."); + + let extracted = stage.join("extracted"); + match asset.kind { + ArchiveKind::Zip => fs_ops::extract_zip(&archive, &extracted)?, + ArchiveKind::TarGz => fs_ops::extract_tar_gz(&archive, &extracted)?, + } + let sources = find_dxvk_files(&extracted)?; + state.log( + "Success", + "Extracted and validated DXVK-GPLAsync successfully.", + ); + + apply_managed_install( + state.data_dir(), + &game_dir, + &sources, + settings, + supports_gpl_async_cache(&release), + &stage.join("rollback"), + )?; + + state.log("Success", "DXVK-GPLAsync installed successfully."); + Ok(()) + })(); + + if result.is_ok() { + let _ = fs::remove_dir_all(&stage); + } else { + state.log( + "Warning", + format!("DXVK staging files were retained at {}.", stage.display()), + ); + } + result +} + +/// Remove PatchOpsIII-managed DXVK files with rollback if any removal fails. +pub fn uninstall(state: &AppState, game_dir: &Path) -> Result<(), String> { + let game_dir = validate_game_dir(game_dir)?; + let known = known_legacy_pairs(); + adopt_legacy_install(state.data_dir(), &game_dir, &known)?; + match uninstall_managed_files(state.data_dir(), &game_dir) { + Ok(true) => { + state.log("Success", "DXVK-GPLAsync has been uninstalled."); + Ok(()) + } + Ok(false) => { + state.log("Info", "DXVK-GPLAsync was not installed."); + Ok(()) + } + Err(error) => { + state.log( + "Warning", + format!( + "DXVK recovery state was retained at {}.", + managed_state_dir(state.data_dir()).display() + ), + ); + Err(error) + } + } +} + +fn fetch_pinned_release() -> Result { + let response = reqwest::blocking::Client::builder() + .connect_timeout(Duration::from_secs(15)) + .timeout(Duration::from_secs(30)) + .build() + .map_err(|error| error.to_string())? + .get(RELEASE_API) + .header(reqwest::header::USER_AGENT, "PatchOpsIII") + .send() + .and_then(reqwest::blocking::Response::error_for_status) + .map_err(|error| format!("failed to query DXVK releases: {error}"))?; + if response + .content_length() + .is_some_and(|length| length > MAX_RELEASE_JSON_BYTES) + { + return Err("DXVK release metadata is unexpectedly large".into()); + } + let mut body = Vec::new(); + response + .take(MAX_RELEASE_JSON_BYTES + 1) + .read_to_end(&mut body) + .map_err(|error| error.to_string())?; + if body.len() as u64 > MAX_RELEASE_JSON_BYTES { + return Err("DXVK release metadata is unexpectedly large".into()); + } + serde_json::from_slice::(&body) + .map_err(|error| format!("invalid DXVK release metadata: {error}")) +} + +fn select_release_asset(release: &Release) -> Result { + select_release_asset_with_pins(release, &RELEASE_PINS) +} + +fn select_release_asset_with_pins( + release: &Release, + pins: &[ReleasePin], +) -> Result { + let pin = pins + .iter() + .find(|pin| release.tag_name.trim() == pin.tag_name) + .ok_or_else(|| { + format!( + "DXVK-GPLAsync release {} is not supported by this PatchOpsIII build", + release.tag_name.trim() + ) + })?; + let sha256 = normalize_sha256(pin.sha256).ok_or_else(|| { + format!( + "DXVK-GPLAsync release {} does not have a valid app-owned SHA-256 pin", + pin.tag_name + ) + })?; + validate_download_url(pin.url)?; + let kind = archive_kind(pin.asset_name).ok_or_else(|| { + format!( + "DXVK-GPLAsync release {} has an unsupported pinned archive type", + pin.tag_name + ) + })?; + let link = release + .assets + .links + .iter() + .find(|link| link.name.trim() == pin.asset_name && link.url.trim() == pin.url) + .ok_or_else(|| { + format!( + "DXVK-GPLAsync release {} did not contain its exact pinned asset", + pin.tag_name + ) + })?; + Ok(ReleaseAsset { + url: link.url.trim().to_owned(), + filename: archive_filename(&link.name, &link.url, kind), + kind, + sha256, + }) +} + +fn archive_kind(value: &str) -> Option { + let value = value + .split(['?', '#']) + .next() + .unwrap_or(value) + .to_ascii_lowercase(); + if value.ends_with(".zip") { + Some(ArchiveKind::Zip) + } else if value.ends_with(".tar.gz") || value.ends_with(".tgz") { + Some(ArchiveKind::TarGz) + } else { + None + } +} + +fn archive_filename(name: &str, url: &str, kind: ArchiveKind) -> String { + let from_url = reqwest::Url::parse(url) + .ok() + .and_then(|url| url.path_segments()?.next_back().map(str::to_string)); + [Some(name.trim().to_string()), from_url] + .into_iter() + .flatten() + .find(|value| { + !value.is_empty() + && value.len() <= 200 + && !value.contains(['/', '\\']) + && archive_kind(value) == Some(kind) + }) + .unwrap_or_else(|| match kind { + ArchiveKind::Zip => "dxvk-gplasync.zip".into(), + ArchiveKind::TarGz => "dxvk-gplasync.tar.gz".into(), + }) +} + +fn validate_download_url(value: &str) -> Result<(), String> { + let url = reqwest::Url::parse(value).map_err(|_| "invalid DXVK download URL".to_string())?; + if url.scheme() != "https" + || url.host_str() != Some("gitlab.com") + || !url.username().is_empty() + || url.password().is_some() + || url.port_or_known_default() != Some(443) + || url.fragment().is_some() + || url.path() != "/Ph42oN/dxvk-gplasync/-/jobs/15069375988/artifacts/download" + || url.query() != Some("file_type=archive") + { + return Err("DXVK release metadata returned an unexpected download URL".into()); + } + Ok(()) +} + +fn normalize_sha256(value: &str) -> Option { + let value = value.trim(); + let value = value + .split_once(':') + .filter(|(prefix, _)| prefix.eq_ignore_ascii_case("sha256")) + .map_or(value, |(_, digest)| digest) + .trim() + .to_ascii_lowercase(); + (value.len() == 64 && value.bytes().all(|byte| byte.is_ascii_hexdigit())).then_some(value) +} + +fn managed_state_dir(storage_dir: &Path) -> PathBuf { + storage_dir.join(MANAGED_STATE_DIRECTORY) +} + +fn managed_manifest_path(storage_dir: &Path) -> PathBuf { + managed_state_dir(storage_dir).join(MANAGED_MANIFEST_FILENAME) +} + +fn managed_backup_path(state_dir: &Path, name: &str) -> Result { + if !MANAGED_FILES.contains(&name) { + return Err("DXVK managed state contains an invalid filename".into()); + } + Ok(state_dir.join(MANAGED_BACKUP_DIRECTORY).join(name)) +} + +fn managed_targets(game_dir: &Path) -> Result, String> { + MANAGED_FILES + .iter() + .map(|name| flat_target(game_dir, name)) + .collect() +} + +fn load_managed_manifest( + storage_dir: &Path, + game_dir: &Path, +) -> Result, String> { + let state_dir = managed_state_dir(storage_dir); + match fs::symlink_metadata(&state_dir) { + Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(None), + Err(error) => return Err(error.to_string()), + Ok(metadata) if metadata.file_type().is_symlink() || !metadata.is_dir() => { + return Err(format!( + "refusing unsafe DXVK managed state at {}", + state_dir.display() + )); + } + Ok(_) => {} + } + let path = managed_manifest_path(storage_dir); + if !regular_file(&path) { + return Err(format!( + "DXVK managed state is incomplete; recovery files were retained at {}", + state_dir.display() + )); + } + let body = fs::read(&path).map_err(|error| error.to_string())?; + if body.len() > 64 * 1024 { + return Err("DXVK managed manifest is unexpectedly large".into()); + } + let manifest: ManagedManifest = serde_json::from_slice(&body) + .map_err(|error| format!("invalid DXVK managed manifest: {error}"))?; + validate_managed_manifest(&manifest, game_dir, &state_dir)?; + Ok(Some(manifest)) +} + +fn managed_manifest_for_configure( + storage_dir: &Path, + game_dir: &Path, +) -> Result { + load_managed_manifest(storage_dir, game_dir)? + .ok_or_else(|| UNMANAGED_CONFIG_MESSAGE.to_string()) +} + +fn validate_managed_manifest( + manifest: &ManagedManifest, + game_dir: &Path, + state_dir: &Path, +) -> Result<(), String> { + if manifest.version != MANAGED_STATE_VERSION { + return Err("unsupported DXVK managed manifest version".into()); + } + if !fs_ops::same_directory(&manifest.game_dir, game_dir) { + return Err(format!( + "DXVK is managed for {}, not {}", + manifest.game_dir.display(), + game_dir.display() + )); + } + let mut names = HashSet::new(); + for file in &manifest.files { + if !MANAGED_FILES.contains(&file.name.as_str()) || !names.insert(file.name.as_str()) { + return Err("DXVK managed manifest contains invalid or duplicate files".into()); + } + if normalize_sha256(&file.installed_sha256).is_none() { + return Err("DXVK managed manifest contains an invalid installed checksum".into()); + } + let backup = managed_backup_path(state_dir, &file.name)?; + match &file.original_sha256 { + Some(expected) => { + if normalize_sha256(expected).is_none() + || !regular_file(&backup) + || !fs_ops::sha256_file(&backup)?.eq_ignore_ascii_case(expected) + { + return Err(format!( + "DXVK original backup {} failed validation", + backup.display() + )); + } + } + None if backup.exists() => { + return Err(format!( + "DXVK managed state contains an unexpected backup at {}", + backup.display() + )); + } + None => {} + } + } + if names.len() != MANAGED_FILES.len() { + return Err("DXVK managed manifest is incomplete".into()); + } + Ok(()) +} + +fn verify_managed_targets( + manifest: &ManagedManifest, + game_dir: &Path, + storage_dir: &Path, +) -> Result<(), String> { + validate_managed_manifest(manifest, game_dir, &managed_state_dir(storage_dir))?; + let mut changed = Vec::new(); + for file in &manifest.files { + let target = flat_target(game_dir, &file.name)?; + let matches = regular_file(&target) + && fs_ops::sha256_file(&target) + .map(|actual| actual.eq_ignore_ascii_case(&file.installed_sha256)) + .unwrap_or(false); + if !matches { + changed.push(file.name.clone()); + } + } + if changed.is_empty() { + Ok(()) + } else { + Err(format!( + "DXVK files changed after PatchOpsIII installed them ({}); refusing to overwrite them. Recovery state remains at {}", + changed.join(", "), + managed_state_dir(storage_dir).display() + )) + } +} + +fn known_legacy_pairs() -> Vec<[String; 2]> { + KNOWN_LEGACY_DXVK_PAIRS + .iter() + .map(|pair| [pair[0].to_owned(), pair[1].to_owned()]) + .collect() +} + +fn source_pair(sources: &[PathBuf; 2]) -> Result<[String; 2], String> { + Ok([ + fs_ops::sha256_file(&sources[0])?, + fs_ops::sha256_file(&sources[1])?, + ]) +} + +/// Adopt only an exact origin/main DXVK payload. The DLL pair proves the +/// legacy release; the user-editable config is backed up as an original so an +/// uninstall restores its current bytes instead of deleting them. +fn adopt_legacy_install( + storage_dir: &Path, + game_dir: &Path, + expected_pairs: &[[String; 2]], +) -> Result { + adopt_legacy_install_with_hash_provider(storage_dir, game_dir, expected_pairs, None) +} + +fn adopt_legacy_install_with_hash_provider( + storage_dir: &Path, + game_dir: &Path, + expected_pairs: &[[String; 2]], + provider: Option<&crate::exe::ExeHashProvider>, +) -> Result { + if load_managed_manifest(storage_dir, game_dir)?.is_some() { + return Ok(false); + } + let targets = managed_targets(game_dir)?; + let dlls = &targets[..DXVK_FILES.len()]; + if dlls.iter().all(|target| !target.exists()) { + return Ok(false); + } + if dlls.iter().any(|target| !regular_file(target)) { + return Err( + "The unmanaged DXVK DLL pair is incomplete or unsafe; no files were changed".into(), + ); + } + let actual = [ + fs_ops::sha256_file(&dlls[0])?, + fs_ops::sha256_file(&dlls[1])?, + ]; + let identity = match provider { + Some(provider) => [provider(&dlls[0])?, provider(&dlls[1])?], + None => actual.clone(), + }; + if !expected_pairs.iter().any(|pair| pair == &identity) { + return Err("The unmanaged DXVK DLLs do not match the same verified release known to this build; no files were changed. Remove or restore the unknown installation manually before installing PatchOpsIII-managed DXVK.".into()); + } + let config = &targets[2]; + if !regular_file(config) { + return Err("The unmanaged DXVK config is missing or unsafe; no files were changed".into()); + } + + let state_dir = managed_state_dir(storage_dir); + fs::create_dir(&state_dir).map_err(|error| error.to_string())?; + let result = (|| { + let backup_dir = state_dir.join(MANAGED_BACKUP_DIRECTORY); + fs::create_dir(&backup_dir).map_err(|error| error.to_string())?; + let config_hash = fs_ops::sha256_file(config)?; + let config_backup = managed_backup_path(&state_dir, "dxvk.conf")?; + fs::copy(config, &config_backup).map_err(|error| error.to_string())?; + if !fs_ops::sha256_file(&config_backup)?.eq_ignore_ascii_case(&config_hash) { + return Err("DXVK legacy config backup failed verification".into()); + } + let manifest = ManagedManifest { + version: MANAGED_STATE_VERSION, + game_dir: game_dir.to_path_buf(), + files: vec![ + ManagedFile { + name: "dxgi.dll".into(), + installed_sha256: actual[0].clone(), + original_sha256: None, + }, + ManagedFile { + name: "d3d11.dll".into(), + installed_sha256: actual[1].clone(), + original_sha256: None, + }, + ManagedFile { + name: "dxvk.conf".into(), + installed_sha256: config_hash.clone(), + original_sha256: Some(config_hash), + }, + ], + }; + save_managed_manifest(storage_dir, &manifest) + })(); + if let Err(error) = result { + let _ = remove_new_managed_state(storage_dir); + return Err(error); + } + Ok(true) +} + +fn prepare_managed_install( + storage_dir: &Path, + game_dir: &Path, + targets: &[PathBuf], +) -> Result<(ManagedManifest, bool), String> { + if let Some(manifest) = load_managed_manifest(storage_dir, game_dir)? { + verify_managed_targets(&manifest, game_dir, storage_dir)?; + return Ok((manifest, false)); + } + if targets.len() != MANAGED_FILES.len() { + return Err("DXVK managed target list is incomplete".into()); + } + + let state_dir = managed_state_dir(storage_dir); + fs::create_dir(&state_dir).map_err(|error| error.to_string())?; + let result = (|| { + let backup_dir = state_dir.join(MANAGED_BACKUP_DIRECTORY); + fs::create_dir(&backup_dir).map_err(|error| error.to_string())?; + let mut files = Vec::with_capacity(MANAGED_FILES.len()); + for (name, target) in MANAGED_FILES.iter().zip(targets) { + let original_sha256 = if target.exists() { + if !regular_file(target) { + return Err(format!("refusing to back up {}", target.display())); + } + let expected = fs_ops::sha256_file(target)?; + let backup = managed_backup_path(&state_dir, name)?; + fs::copy(target, &backup).map_err(|error| error.to_string())?; + if fs_ops::sha256_file(&backup)? != expected { + return Err(format!( + "DXVK original backup {} failed verification", + backup.display() + )); + } + Some(expected) + } else { + None + }; + files.push(ManagedFile { + name: (*name).to_owned(), + installed_sha256: String::new(), + original_sha256, + }); + } + Ok(ManagedManifest { + version: MANAGED_STATE_VERSION, + game_dir: game_dir.to_path_buf(), + files, + }) + })(); + if result.is_err() { + let _ = fs::remove_dir_all(&state_dir); + } + result.map(|manifest| (manifest, true)) +} + +fn apply_managed_install( + storage_dir: &Path, + game_dir: &Path, + sources: &[PathBuf; 2], + settings: &DxvkSettings, + include_gpl_async_cache: bool, + rollback_directory: &Path, +) -> Result<(), String> { + let targets = managed_targets(game_dir)?; + let snapshot = Snapshot::capture(&targets, rollback_directory)?; + let mut expected_pairs = vec![source_pair(sources)?]; + expected_pairs.extend(known_legacy_pairs()); + let adopted = adopt_legacy_install(storage_dir, game_dir, &expected_pairs)?; + let (mut manifest, new_managed_state) = + match prepare_managed_install(storage_dir, game_dir, &targets) { + Ok(prepared) => prepared, + Err(error) => { + if adopted { + remove_new_managed_state(storage_dir).map_err(|cleanup| { + format!("{error}; legacy ownership migration cleanup failed: {cleanup}") + })?; + } + return Err(error); + } + }; + let commit = (|| { + for (source, target) in sources.iter().zip(targets.iter()) { + fs::copy(source, target).map_err(|error| { + format!( + "failed to install {} to {}: {error}", + source.display(), + target.display() + ) + })?; + } + fs_ops::atomic_write( + &targets[2], + build_conf(settings, include_gpl_async_cache).as_bytes(), + )?; + if !targets.iter().all(|path| regular_file(path)) { + return Err("DXVK install verification failed".to_string()); + } + update_installed_hashes(&mut manifest, game_dir)?; + save_managed_manifest(storage_dir, &manifest)?; + Ok(()) + })(); + + if let Err(error) = commit { + return match snapshot.restore() { + Ok(()) => { + if new_managed_state || adopted { + remove_new_managed_state(storage_dir).map_err(|cleanup| { + format!( + "{error}; previous DXVK files were restored, but recovery-state cleanup failed: {cleanup}" + ) + })?; + } + Err(format!("{error}; previous DXVK files were restored")) + } + Err(rollback) => Err(format!( + "{error}; rollback failed: {rollback}. Recovery files remain in {}", + rollback_directory.display() + )), + }; + } + Ok(()) +} + +fn update_installed_hashes(manifest: &mut ManagedManifest, game_dir: &Path) -> Result<(), String> { + for file in &mut manifest.files { + let target = flat_target(game_dir, &file.name)?; + if !regular_file(&target) { + return Err(format!( + "DXVK install verification failed for {}", + target.display() + )); + } + file.installed_sha256 = fs_ops::sha256_file(&target)?; + } + Ok(()) +} + +fn save_managed_manifest(storage_dir: &Path, manifest: &ManagedManifest) -> Result<(), String> { + validate_managed_manifest( + manifest, + &manifest.game_dir, + &managed_state_dir(storage_dir), + )?; + let body = serde_json::to_vec_pretty(manifest).map_err(|error| error.to_string())?; + fs_ops::atomic_write(&managed_manifest_path(storage_dir), &body) +} + +fn remove_new_managed_state(storage_dir: &Path) -> Result<(), String> { + let state_dir = managed_state_dir(storage_dir); + let metadata = fs::symlink_metadata(&state_dir).map_err(|error| error.to_string())?; + if metadata.file_type().is_symlink() || !metadata.is_dir() { + return Err(format!( + "refusing unsafe DXVK managed state at {}", + state_dir.display() + )); + } + fs::remove_dir_all(state_dir).map_err(|error| error.to_string()) +} + +fn uninstall_managed_files(storage_dir: &Path, game_dir: &Path) -> Result { + let targets = managed_targets(game_dir)?; + let Some(manifest) = load_managed_manifest(storage_dir, game_dir)? else { + if targets.iter().any(|target| target.exists()) { + return Err( + "No PatchOpsIII-managed DXVK record exists; refusing to remove unowned files" + .into(), + ); + } + return Ok(false); + }; + verify_managed_targets(&manifest, game_dir, storage_dir)?; + + let transaction = create_private_dir(storage_dir, "dxvk-uninstall")?; + let snapshot = match Snapshot::capture(&targets, &transaction.join("rollback")) { + Ok(snapshot) => snapshot, + Err(error) => { + let _ = fs::remove_dir_all(&transaction); + return Err(error); + } + }; + let state_dir = managed_state_dir(storage_dir); + let moved_state = transaction.join("managed-state"); + if let Err(error) = fs::rename(&state_dir, &moved_state) { + let _ = fs::remove_dir_all(&transaction); + return Err(format!("failed to secure DXVK recovery state: {error}")); + } + + let restore = (|| { + for file in &manifest.files { + let target = flat_target(game_dir, &file.name)?; + if let Some(expected) = &file.original_sha256 { + let backup = managed_backup_path(&moved_state, &file.name)?; + fs::copy(&backup, &target) + .map_err(|error| format!("failed to restore {}: {error}", target.display()))?; + if fs_ops::sha256_file(&target)? != *expected { + return Err(format!( + "restored DXVK file {} failed verification", + target.display() + )); + } + } else { + remove_file_if_present(&target)?; + if target.exists() { + return Err(format!("failed to remove {}", target.display())); + } + } + } + Ok(()) + })(); + + if let Err(error) = restore { + let rollback = snapshot.restore(); + let state_restore = fs::rename(&moved_state, &state_dir); + if rollback.is_ok() && state_restore.is_ok() { + let _ = fs::remove_dir_all(&transaction); + return Err(format!( + "{error}; the managed DXVK installation was restored" + )); + } + return Err(format!( + "{error}; recovery was incomplete. Files were retained at {}", + transaction.display() + )); + } + + fs::remove_dir_all(&transaction).map_err(|error| { + format!( + "DXVK files were restored, but recovery-state cleanup failed at {}: {error}", + transaction.display() + ) + })?; + Ok(true) +} + +fn supports_gpl_async_cache(release: &Release) -> bool { + let label = if release.tag_name.trim().is_empty() { + release.name.as_str() + } else { + release.tag_name.as_str() + }; + let Some(start) = label.find(|character: char| character.is_ascii_digit()) else { + return true; + }; + let version = &label[start..]; + let major_end = version + .find(|character: char| !character.is_ascii_digit()) + .unwrap_or(version.len()); + let Ok(major) = version[..major_end].parse::() else { + return true; + }; + let Some(minor_text) = version + .get(major_end..) + .and_then(|rest| rest.strip_prefix('.')) + else { + return true; + }; + let minor_end = minor_text + .find(|character: char| !character.is_ascii_digit()) + .unwrap_or(minor_text.len()); + let Ok(minor) = minor_text[..minor_end].parse::() else { + return true; + }; + (major, minor) < (2, 7) +} + +fn validate_settings(settings: &DxvkSettings) -> Result<(), String> { + if !(0..=64).contains(&settings.num_compiler_threads) { + return Err("DXVK compiler threads must be between 0 and 64".into()); + } + if !(0..=360).contains(&settings.max_frame_rate) { + return Err("DXVK frame rate must be between 0 and 360".into()); + } + if !(0..=16).contains(&settings.max_frame_latency) { + return Err("DXVK frame latency must be between 0 and 16".into()); + } + if !matches!(settings.tear_free.as_str(), "Auto" | "True" | "False") { + return Err("DXVK tear-free mode must be Auto, True, or False".into()); + } + Ok(()) +} + +fn build_conf(settings: &DxvkSettings, include_gpl_async_cache: bool) -> String { + let mut lines = vec![format!( + "dxvk.enableAsync={}", + if settings.enable_async { + "true" + } else { + "false" + } + )]; + if include_gpl_async_cache && settings.gpl_async_cache { + lines.push("dxvk.gplAsyncCache=true".into()); + } + lines.extend([ + format!("dxvk.numCompilerThreads={}", settings.num_compiler_threads), + format!("dxgi.maxFrameRate={}", settings.max_frame_rate), + format!("dxgi.maxFrameLatency={}", settings.max_frame_latency), + format!("dxvk.tearFree={}", settings.tear_free), + ]); + if settings.hud_enabled { + lines.push("dxvk.hud=fps,frametimes,gpuload".into()); + } + lines.join("\n") + "\n" +} + +fn parse_conf(contents: &str) -> DxvkSettings { + let mut settings = DxvkSettings::default(); + for line in contents.lines() { + if line.trim_start().starts_with('#') { + continue; + } + let Some((key, value)) = line.split_once('=') else { + continue; + }; + let (key, value) = (key.trim(), value.trim()); + match key { + "dxvk.enableAsync" => settings.enable_async = value.eq_ignore_ascii_case("true"), + "dxvk.gplAsyncCache" => settings.gpl_async_cache = value.eq_ignore_ascii_case("true"), + "dxvk.numCompilerThreads" => { + if let Ok(value) = value.parse() { + settings.num_compiler_threads = value; + } + } + "dxgi.maxFrameRate" => { + if let Ok(value) = value.parse() { + settings.max_frame_rate = value; + } + } + "dxgi.maxFrameLatency" => { + if let Ok(value) = value.parse() { + settings.max_frame_latency = value; + } + } + "dxvk.tearFree" => settings.tear_free = value.to_string(), + "dxvk.hud" => settings.hud_enabled = !value.is_empty(), + _ => {} + } + } + settings +} + +fn find_dxvk_files(root: &Path) -> Result<[PathBuf; 2], String> { + let mut directories = vec![root.to_path_buf()]; + let mut index = 0; + while index < directories.len() { + let directory = directories[index].clone(); + index += 1; + for entry in fs::read_dir(&directory).map_err(|error| error.to_string())? { + let entry = entry.map_err(|error| error.to_string())?; + let metadata = fs::symlink_metadata(entry.path()).map_err(|error| error.to_string())?; + if metadata.file_type().is_symlink() { + return Err("DXVK archive contains a link".into()); + } + if metadata.is_dir() { + directories.push(entry.path()); + } + } + } + directories.sort_by_key(|path| { + let components = path + .components() + .filter_map(|component| component.as_os_str().to_str()) + .map(str::to_ascii_lowercase) + .collect::>(); + let architecture = if components.iter().any(|part| part == "x64") { + 0 + } else if components.iter().any(|part| part == "x32") { + 2 + } else { + 1 + }; + ( + architecture, + components.len(), + path.to_string_lossy().to_lowercase(), + ) + }); + + for directory in directories { + let components = directory + .components() + .filter_map(|component| component.as_os_str().to_str()) + .collect::>(); + if components + .iter() + .any(|part| part.eq_ignore_ascii_case("x32")) + && !components + .iter() + .any(|part| part.eq_ignore_ascii_case("x64")) + { + continue; + } + let files = fs::read_dir(&directory) + .map_err(|error| error.to_string())? + .filter_map(Result::ok) + .filter(|entry| regular_file(&entry.path())) + .map(|entry| { + ( + entry.file_name().to_string_lossy().to_ascii_lowercase(), + entry.path(), + ) + }) + .collect::>(); + let dxgi = files.iter().find(|(name, _)| name == "dxgi.dll"); + let d3d11 = files.iter().find(|(name, _)| name == "d3d11.dll"); + if let (Some((_, dxgi)), Some((_, d3d11))) = (dxgi, d3d11) { + return Ok([dxgi.clone(), d3d11.clone()]); + } + } + Err("Required 64-bit DXVK files were not found in the archive".into()) +} + +fn validate_game_dir(path: &Path) -> Result { + if path.as_os_str().is_empty() { + return Err("Game directory is not set".into()); + } + let path = path + .canonicalize() + .map_err(|error| format!("invalid game directory: {error}"))?; + if !path.is_dir() { + return Err("Game directory is not a directory".into()); + } + if !["BlackOpsIII.exe", "BlackOps3.exe"] + .iter() + .any(|name| regular_file(&path.join(name))) + { + return Err("Game directory does not contain a Black Ops III executable".into()); + } + Ok(path) +} + +fn flat_target(root: &Path, filename: &str) -> Result { + if filename.is_empty() || filename.contains(['/', '\\']) || filename == "." || filename == ".." + { + return Err("invalid game filename".into()); + } + let target = root.join(filename); + if let Ok(metadata) = fs::symlink_metadata(&target) { + if metadata.file_type().is_symlink() { + return Err(format!( + "refusing to replace linked path {}", + target.display() + )); + } + if !metadata.is_file() { + return Err(format!("expected a file at {}", target.display())); + } + } + Ok(target) +} + +fn regular_file(path: &Path) -> bool { + fs::symlink_metadata(path) + .map(|metadata| metadata.is_file() && !metadata.file_type().is_symlink()) + .unwrap_or(false) +} + +fn create_stage(state: &AppState, label: &str) -> Result { + let root = state.mod_files_dir(); + fs::create_dir_all(&root).map_err(|error| error.to_string())?; + create_private_dir(&root, label) +} + +fn create_private_dir(root: &Path, label: &str) -> Result { + fs::create_dir_all(root).map_err(|error| error.to_string())?; + for _ in 0..10 { + let id = STAGE_COUNTER.fetch_add(1, Ordering::Relaxed); + let stage = root.join(format!(".{label}-stage-{}-{id}", std::process::id())); + match fs::create_dir(&stage) { + Ok(()) => return Ok(stage), + Err(error) if error.kind() == std::io::ErrorKind::AlreadyExists => continue, + Err(error) => return Err(error.to_string()), + } + } + Err("could not create a unique staging directory".into()) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::atomic::{AtomicU64, Ordering}; + + static TEST_COUNTER: AtomicU64 = AtomicU64::new(0); + + fn temp_dir(label: &str) -> PathBuf { + let path = std::env::temp_dir().join(format!( + "patchops-dxvk-{label}-{}-{}", + std::process::id(), + TEST_COUNTER.fetch_add(1, Ordering::Relaxed) + )); + fs::create_dir_all(&path).unwrap(); + path + } + + #[test] + fn writes_and_parses_the_existing_dxvk_format() { + let settings = DxvkSettings { + hud_enabled: true, + ..DxvkSettings::default() + }; + let conf = build_conf(&settings, true); + assert_eq!( + conf, + "dxvk.enableAsync=true\n\ +dxvk.gplAsyncCache=true\n\ +dxvk.numCompilerThreads=0\n\ +dxgi.maxFrameRate=0\n\ +dxgi.maxFrameLatency=1\n\ +dxvk.tearFree=True\n\ +dxvk.hud=fps,frametimes,gpuload\n" + ); + assert_eq!( + parse_conf(&format!("# preserved comment\n{conf}")), + settings + ); + assert!(!build_conf(&settings, false).contains("gplAsyncCache")); + } + + #[test] + fn release_selection_requires_the_exact_app_owned_pin() { + let release: Release = serde_json::from_str( + r#"{ + "tag_name":"v3.0-1", + "assets":{"links":[ + {"name":"dxvk-gplasync-v3.0-1.tar.gz","url":"https://gitlab.com/Ph42oN/dxvk-gplasync/-/raw/main/releases/dxvk-gplasync-v3.0-1.tar.gz"}, + {"name":"dxvk-gplasync-v3.0-1.zip","url":"https://gitlab.com/Ph42oN/dxvk-gplasync/-/jobs/15069375988/artifacts/download?file_type=archive"} + ]} + }"#, + ) + .unwrap(); + let selected = select_release_asset(&release).unwrap(); + assert_eq!(selected.kind, ArchiveKind::Zip); + assert_eq!(selected.sha256, RELEASE_PINS[0].sha256); + assert!(!supports_gpl_async_cache(&release)); + + let raw_main_only: Release = serde_json::from_str( + r#"{ + "tag_name":"v3.0-1", + "assets":{"links":[ + {"name":"dxvk-gplasync-v3.0-1.zip","url":"https://gitlab.com/Ph42oN/dxvk-gplasync/-/raw/main/releases/dxvk-gplasync-v3.0-1.zip"} + ]} + }"#, + ) + .unwrap(); + assert!(select_release_asset(&raw_main_only).is_err()); + } + + #[test] + fn release_selection_rejects_missing_or_invalid_pinned_digests() { + let release: Release = serde_json::from_str( + r#"{ + "tag_name":"v3.0-1", + "assets":{"links":[ + {"name":"dxvk-gplasync-v3.0-1.zip","url":"https://gitlab.com/Ph42oN/dxvk-gplasync/-/jobs/15069375988/artifacts/download?file_type=archive"} + ]} + }"#, + ) + .unwrap(); + for sha256 in ["", "sha256:not-a-digest"] { + let pins = [ReleasePin { + sha256, + ..RELEASE_PINS[0] + }]; + let error = select_release_asset_with_pins(&release, &pins).unwrap_err(); + assert!(error.contains("valid app-owned SHA-256")); + } + } + + #[test] + fn accepts_only_the_pinned_gitlab_release_url() { + assert!(validate_download_url(RELEASE_PINS[0].url).is_ok()); + for url in [ + "http://gitlab.com/Ph42oN/dxvk-gplasync/-/jobs/15069375988/artifacts/download?file_type=archive", + "https://gitlab.com.evil.invalid/Ph42oN/dxvk-gplasync/-/jobs/15069375988/artifacts/download?file_type=archive", + "https://gitlab.com/Ph42oN/dxvk-gplasync/-/raw/main/releases/dxvk-gplasync-v3.0-1.zip", + "https://gitlab.com/Ph42oN/dxvk-gplasync/-/jobs/999/artifacts/download?file_type=archive", + "https://gitlab.com:444/Ph42oN/dxvk-gplasync/-/jobs/15069375988/artifacts/download?file_type=archive", + ] { + assert!(validate_download_url(url).is_err(), "accepted {url}"); + } + } + + #[test] + fn chooses_x64_payload_and_snapshot_rolls_back() { + let root = temp_dir("source"); + for architecture in ["x32", "x64"] { + let dir = root.join(architecture); + fs::create_dir_all(&dir).unwrap(); + fs::write(dir.join("dxgi.dll"), architecture).unwrap(); + fs::write(dir.join("d3d11.dll"), architecture).unwrap(); + } + let selected = find_dxvk_files(&root).unwrap(); + assert!( + selected + .iter() + .all(|path| path.starts_with(root.join("x64"))) + ); + + let target = root.join("installed.dll"); + fs::write(&target, b"original").unwrap(); + let snapshot = + Snapshot::capture(std::slice::from_ref(&target), &root.join("rollback")).unwrap(); + fs::write(&target, b"partial").unwrap(); + snapshot.restore().unwrap(); + assert_eq!(fs::read(&target).unwrap(), b"original"); + fs::remove_dir_all(root).unwrap(); + } + + #[test] + fn rejects_out_of_range_or_injected_settings() { + let settings = DxvkSettings { + max_frame_rate: 361, + ..DxvkSettings::default() + }; + assert!(validate_settings(&settings).is_err()); + let settings = DxvkSettings { + tear_free: "True\ndxvk.hud=full".into(), + ..DxvkSettings::default() + }; + assert!(validate_settings(&settings).is_err()); + } + + #[test] + fn managed_uninstall_restores_exact_originals_and_removes_created_files() { + let root = temp_dir("managed-restore"); + let storage = root.join("data"); + let game = root.join("game"); + fs::create_dir_all(&storage).unwrap(); + fs::create_dir_all(&game).unwrap(); + fs::write(game.join("dxgi.dll"), b"original-dxgi").unwrap(); + fs::write(game.join("dxvk.conf"), b"original-conf").unwrap(); + + let targets = managed_targets(&game).unwrap(); + let (mut manifest, new_state) = prepare_managed_install(&storage, &game, &targets).unwrap(); + assert!(new_state); + fs::write(game.join("dxgi.dll"), b"managed-dxgi").unwrap(); + fs::write(game.join("d3d11.dll"), b"managed-d3d11").unwrap(); + fs::write(game.join("dxvk.conf"), b"managed-conf").unwrap(); + update_installed_hashes(&mut manifest, &game).unwrap(); + save_managed_manifest(&storage, &manifest).unwrap(); + + assert!(uninstall_managed_files(&storage, &game).unwrap()); + assert_eq!(fs::read(game.join("dxgi.dll")).unwrap(), b"original-dxgi"); + assert!(!game.join("d3d11.dll").exists()); + assert_eq!(fs::read(game.join("dxvk.conf")).unwrap(), b"original-conf"); + assert!(!managed_state_dir(&storage).exists()); + fs::remove_dir_all(root).unwrap(); + } + + #[test] + fn managed_uninstall_refuses_changed_or_unowned_files() { + let root = temp_dir("managed-conflict"); + let storage = root.join("data"); + let game = root.join("game"); + fs::create_dir_all(&storage).unwrap(); + fs::create_dir_all(&game).unwrap(); + let targets = managed_targets(&game).unwrap(); + let (mut manifest, _) = prepare_managed_install(&storage, &game, &targets).unwrap(); + for (name, body) in [ + ("dxgi.dll", b"managed-dxgi".as_slice()), + ("d3d11.dll", b"managed-d3d11".as_slice()), + ("dxvk.conf", b"managed-conf".as_slice()), + ] { + fs::write(game.join(name), body).unwrap(); + } + update_installed_hashes(&mut manifest, &game).unwrap(); + save_managed_manifest(&storage, &manifest).unwrap(); + fs::write(game.join("dxgi.dll"), b"user-modified").unwrap(); + + let error = uninstall_managed_files(&storage, &game).unwrap_err(); + assert!(error.contains("changed after PatchOpsIII installed")); + assert_eq!(fs::read(game.join("dxgi.dll")).unwrap(), b"user-modified"); + assert_eq!(fs::read(game.join("d3d11.dll")).unwrap(), b"managed-d3d11"); + assert!(managed_state_dir(&storage).is_dir()); + + let unmanaged_storage = root.join("unmanaged-data"); + fs::create_dir_all(&unmanaged_storage).unwrap(); + let error = uninstall_managed_files(&unmanaged_storage, &game).unwrap_err(); + assert!(error.contains("refusing to remove unowned files")); + assert_eq!(fs::read(game.join("dxgi.dll")).unwrap(), b"user-modified"); + fs::remove_dir_all(root).unwrap(); + } + + #[test] + fn managed_reinstall_and_reconfigure_preserve_the_first_originals() { + let root = temp_dir("managed-update"); + let storage = root.join("data"); + let game = root.join("game"); + fs::create_dir_all(&storage).unwrap(); + fs::create_dir_all(&game).unwrap(); + for (name, body) in [ + ("dxgi.dll", b"original-dxgi".as_slice()), + ("d3d11.dll", b"original-d3d11".as_slice()), + ("dxvk.conf", b"original-conf".as_slice()), + ] { + fs::write(game.join(name), body).unwrap(); + } + + let targets = managed_targets(&game).unwrap(); + let (mut first, _) = prepare_managed_install(&storage, &game, &targets).unwrap(); + for target in &targets { + fs::write(target, b"managed-v1").unwrap(); + } + update_installed_hashes(&mut first, &game).unwrap(); + save_managed_manifest(&storage, &first).unwrap(); + + let (mut second, new_state) = prepare_managed_install(&storage, &game, &targets).unwrap(); + assert!(!new_state); + for target in &targets { + fs::write(target, b"managed-v2").unwrap(); + } + update_installed_hashes(&mut second, &game).unwrap(); + save_managed_manifest(&storage, &second).unwrap(); + fs_ops::atomic_write(&game.join("dxvk.conf"), b"reconfigured").unwrap(); + update_installed_hashes(&mut second, &game).unwrap(); + save_managed_manifest(&storage, &second).unwrap(); + + assert!(uninstall_managed_files(&storage, &game).unwrap()); + assert_eq!(fs::read(game.join("dxgi.dll")).unwrap(), b"original-dxgi"); + assert_eq!(fs::read(game.join("d3d11.dll")).unwrap(), b"original-d3d11"); + assert_eq!(fs::read(game.join("dxvk.conf")).unwrap(), b"original-conf"); + fs::remove_dir_all(root).unwrap(); + } + + #[test] + fn configure_adopts_verified_python_layout_and_refuses_foreign_dlls() { + let root = temp_dir("configure-adoption"); + let game = root.join("game"); + let storage = root.join("storage"); + fs::create_dir_all(&game).unwrap(); + fs::write(game.join("BlackOpsIII.exe"), b"game").unwrap(); + fs::write(game.join("dxgi.dll"), b"synthetic dxgi").unwrap(); + fs::write(game.join("d3d11.dll"), b"synthetic d3d11").unwrap(); + let original = b"dxvk.enableAsync=true\ndxgi.maxFrameLatency=1\n"; + fs::write(game.join("dxvk.conf"), original).unwrap(); + let state = AppState::new(storage.clone(), None, None).unwrap(); + let settings = DxvkSettings { + max_frame_rate: 144, + ..Default::default() + }; + assert!(configure(&state, &game, &settings).is_err()); + assert_eq!(fs::read(game.join("dxvk.conf")).unwrap(), original); + assert!(!managed_state_dir(&storage).exists()); + state.set_dxvk_legacy_hash_provider(std::sync::Arc::new(|path| { + let index = if path.file_name().unwrap() == "dxgi.dll" { + 0 + } else { + 1 + }; + Ok(KNOWN_LEGACY_DXVK_PAIRS[7][index].to_owned()) + })); + configure(&state, &game, &settings).unwrap(); + let manifest = load_managed_manifest(&storage, &game).unwrap().unwrap(); + assert_eq!( + manifest.files[0].installed_sha256, + fs_ops::sha256_file(&game.join("dxgi.dll")).unwrap() + ); + assert_eq!( + fs::read(managed_backup_path(&managed_state_dir(&storage), "dxvk.conf").unwrap()) + .unwrap(), + original + ); + configure(&state, &game, &settings).unwrap(); + uninstall_managed_files(&storage, &game).unwrap(); + assert_eq!(fs::read(game.join("dxvk.conf")).unwrap(), original); + fs::remove_dir_all(root).unwrap(); + } + + #[test] + fn exact_legacy_pair_is_adopted_and_custom_config_is_restored() { + let root = temp_dir("legacy-adopt"); + let storage = root.join("data"); + let game = root.join("game"); + fs::create_dir_all(&storage).unwrap(); + fs::create_dir_all(&game).unwrap(); + fs::write(game.join("dxgi.dll"), b"legacy-dxgi").unwrap(); + fs::write(game.join("d3d11.dll"), b"legacy-d3d11").unwrap(); + fs::write(game.join("dxvk.conf"), b"custom legacy config\n").unwrap(); + let pair = [[ + fs_ops::sha256_file(&game.join("dxgi.dll")).unwrap(), + fs_ops::sha256_file(&game.join("d3d11.dll")).unwrap(), + ]]; + + assert!(adopt_legacy_install(&storage, &game, &pair).unwrap()); + let mut manifest = load_managed_manifest(&storage, &game).unwrap().unwrap(); + assert!( + manifest.files[..2] + .iter() + .all(|file| file.original_sha256.is_none()) + ); + assert!(manifest.files[2].original_sha256.is_some()); + + for target in managed_targets(&game).unwrap() { + fs::write(target, b"updated managed bytes").unwrap(); + } + update_installed_hashes(&mut manifest, &game).unwrap(); + save_managed_manifest(&storage, &manifest).unwrap(); + assert!(uninstall_managed_files(&storage, &game).unwrap()); + assert!(!game.join("dxgi.dll").exists()); + assert!(!game.join("d3d11.dll").exists()); + assert_eq!( + fs::read(game.join("dxvk.conf")).unwrap(), + b"custom legacy config\n" + ); + fs::remove_dir_all(root).unwrap(); + } + + #[test] + fn historical_release_pairs_remain_exact_and_pair_bound() { + let pairs = known_legacy_pairs(); + assert_eq!(pairs.len(), KNOWN_LEGACY_DXVK_PAIRS.len()); + assert!(pairs.contains(&[ + "8d5ae3c40962a4846e4dae9d373a8660227e85f051d12e5bb882ef92d136dfca".into(), + "7f20637b7a9527fbed53985fa55c5fc56c7d8bd132589219e92bd49f488c2754".into(), + ])); + assert!(!pairs.contains(&[ + KNOWN_LEGACY_DXVK_PAIRS[6][0].into(), + KNOWN_LEGACY_DXVK_PAIRS[7][1].into(), + ])); + } + + #[test] + fn legacy_adoption_rejects_incomplete_or_mixed_dll_pairs_atomically() { + let root = temp_dir("legacy-reject"); + let storage = root.join("data"); + let game = root.join("game"); + fs::create_dir_all(&storage).unwrap(); + fs::create_dir_all(&game).unwrap(); + fs::write(game.join("dxgi.dll"), b"known-dxgi").unwrap(); + fs::write(game.join("dxvk.conf"), b"custom config").unwrap(); + let pair = [[ + fs_ops::sha256_file(&game.join("dxgi.dll")).unwrap(), + fs_ops::sha256_file(&game.join("dxvk.conf")).unwrap(), + ]]; + assert!(adopt_legacy_install(&storage, &game, &pair).is_err()); + assert!(!managed_state_dir(&storage).exists()); + assert_eq!(fs::read(game.join("dxgi.dll")).unwrap(), b"known-dxgi"); + + fs::write(game.join("d3d11.dll"), b"different-release").unwrap(); + assert!(adopt_legacy_install(&storage, &game, &pair).is_err()); + assert!(!managed_state_dir(&storage).exists()); + assert_eq!( + fs::read(game.join("d3d11.dll")).unwrap(), + b"different-release" + ); + assert_eq!(fs::read(game.join("dxvk.conf")).unwrap(), b"custom config"); + fs::remove_dir_all(root).unwrap(); + } + + #[test] + fn configure_without_managed_state_cannot_overwrite_an_existing_config() { + let root = temp_dir("configure-unmanaged"); + let storage = root.join("data"); + let game = root.join("game"); + fs::create_dir_all(&storage).unwrap(); + fs::create_dir_all(&game).unwrap(); + let config = game.join("dxvk.conf"); + fs::write(&config, b"unmanaged sentinel").unwrap(); + + let error = configure_managed(&storage, &game, &DxvkSettings::default()).unwrap_err(); + assert_eq!(error, UNMANAGED_CONFIG_MESSAGE); + assert_eq!(fs::read(&config).unwrap(), b"unmanaged sentinel"); + assert!(!managed_state_dir(&storage).exists()); + + fs::remove_dir_all(root).unwrap(); + } +} diff --git a/native/core/src/engine.rs b/native/core/src/engine.rs new file mode 100644 index 0000000..b9978af --- /dev/null +++ b/native/core/src/engine.rs @@ -0,0 +1,818 @@ +use std::{ + cmp::Ordering, + io::Read, + path::{Path, PathBuf}, + sync::Arc, + time::Duration, +}; + +use serde::de::DeserializeOwned; +use serde_json::{Value, json}; + +use crate::{ + app::{self, AppState}, + dxvk, enhanced, exe, + models::{DxvkSettings, MaintenanceState, ModsState, PatchOpsState}, + steam, t7, +}; + +/// Synchronous operations facade. Callers are expected to own it on a worker +/// thread; no method requires a GUI or async runtime. +pub struct Engine { + state: AppState, + exe_hash_provider: Option>, +} + +impl Engine { + pub fn new(state: AppState) -> Self { + Self { + state, + exe_hash_provider: None, + } + } + + /// Replace only EXE status hashing; installation and activation remain verified. + pub fn with_exe_hash_provider(mut self, provider: Arc) -> Self { + self.exe_hash_provider = Some(provider); + self + } + + /// Resolve mocked or cached release metadata through the installation selector. + pub fn resolve_t7_release_asset( + &self, + asset_key: &str, + release: &Value, + ) -> Result { + let asset = t7::select_t7_release_asset(asset_key, release)? + .ok_or_else(|| "No unambiguous trusted T7 release asset.".to_owned())?; + if asset_key == "current_archive" && asset.name != t7::PATCH_ARCHIVE_NAME { + self.state.log( + "Info", + format!("Using latest T7 Patch release asset {}.", asset.name), + ); + } + Ok(asset) + } + + pub fn state(&self) -> &AppState { + &self.state + } + + pub fn current_state(&self) -> Result { + current_state(&self.state, self.exe_hash_provider.as_deref()) + } + + /// Dispatch the legacy local-HTTP paths without HTTP. The returned JSON + /// intentionally retains the Python API's wire shapes so existing UI code + /// can migrate independently from the backend. + pub fn dispatch(&self, path: &str, body: Option) -> Result { + let report = !matches!( + path, + "/api/status" | "/api/health" | "/api/logs/payload" | "/api/exe-swap/compatible-depot" + ); + if report { + self.state.begin_progress(path); + } + let requested_directory = body + .as_ref() + .and_then(|value| value["path"].as_str()) + .map(str::to_owned); + let result = self.dispatch_inner(path, body); + if report { + let error = result.as_ref().err().map(String::as_str).or_else(|| { + result + .as_ref() + .ok() + .filter(|value| value["ok"] == false || value["valid"] == false) + .and_then(|value| value["error"].as_str().or(value["message"].as_str())) + }); + if let Some(error) = error.filter(|error| { + // Python returns these input validation failures before its log/try block. + !(path == "/api/t7-config" + && (error.starts_with("Gamertag ") + || error.starts_with("t7patch.conf was not found."))) + }) { + let (category, message) = match path { + "/api/game-directory" => ( + "Error", + format!( + "Selected directory is not a Black Ops III install: {}", + requested_directory.as_deref().unwrap_or_default() + ), + ), + "/api/launch-options" if error == "Unsupported launch option." => { + ("Warning", error.to_owned()) + } + "/api/t7-config" => ( + "Error", + format!("Failed to update T7 Patch settings: {error}"), + ), + _ => ("Error", error.to_owned()), + }; + self.state.log(category, message); + } + self.state.end_progress(error); + } + result + .or_else(|error| { + if path == "/api/launch-options" && error == "Unsupported launch option." { + Ok(json!({"ok": false, "error": error, "state": self.current_state()?})) + } else if report { + Ok(json!({"ok": false, "error": error})) + } else { + Err(error) + } + }) + .map(normalize_wire_value) + } + + fn dispatch_inner(&self, path: &str, body: Option) -> Result { + let body = body.unwrap_or_else(|| json!({})); + match path { + "/api/health" => Ok(json!({"ok": true, "version": app::APP_VERSION})), + "/api/status" => serde_json::to_value(self.current_state()?).map_err(string_error), + "/api/game-directory" => { + let path = required_string(&body, "path")?; + self.mutate(|| app::set_game_directory(&self.state, path)) + } + "/api/launch" => self.mutate(|| steam::launch_game(&self.state)), + "/api/launch-options" => { + let options = required_string(&body, "options")?; + let preserve = optional_bool(&body, "preserve_fs_game").unwrap_or(false); + self.mutate(|| steam::apply_launch_options(&self.state, options, preserve)) + } + "/api/workshop-install" => { + let profile = required_string(&body, "profileId")?; + self.mutate(|| steam::install_workshop_profile(&self.state, profile)) + } + "/api/update-check" => self.update_check(), + "/api/release-channel" => { + let channel = required_string(&body, "channel")?; + self.mutate(|| app::set_release_channel(&self.state, channel)) + } + "/api/config" => { + let key = required_string(&body, "key")?; + let value = body + .get("value") + .cloned() + .ok_or_else(|| "Missing value.".to_string())?; + self.mutate(|| { + app::set_config_value( + &self.state, + &required_game_dir(&self.state)?, + key, + value, + body["comment"].as_str(), + ) + }) + } + "/api/dxvk-install" => { + let settings: DxvkSettings = decode(body)?; + self.mutate(|| { + dxvk::install(&self.state, &required_game_dir(&self.state)?, &settings) + }) + } + "/api/dxvk-uninstall" => { + self.mutate(|| dxvk::uninstall(&self.state, &required_game_dir(&self.state)?)) + } + "/api/dxvk-config" => { + let settings: DxvkSettings = decode(body)?; + self.mutate(|| { + dxvk::configure(&self.state, &required_game_dir(&self.state)?, &settings) + }) + } + "/api/t7-config" => { + let gamertag = optional_string(&body, "gamertag"); + let color = optional_string(&body, "colorCode").unwrap_or_default(); + let password = optional_string(&body, "networkPassword"); + let friends = optional_bool(&body, "friendsOnly"); + self.mutate(|| { + t7::configure( + &self.state, + &required_game_dir(&self.state)?, + gamertag.as_deref(), + &color, + password.as_deref(), + friends, + ) + }) + } + "/api/t7-install" => self.mutate(|| { + let game_dir = required_game_dir(&self.state)?; + let profile = exe::status(&self.state.load_settings(), Some(&game_dir)).profile; + t7::install(&self.state, &game_dir, &profile) + }), + "/api/t7-uninstall" => { + self.mutate(|| t7::uninstall(&self.state, &required_game_dir(&self.state)?)) + } + "/api/enhanced-validate" => { + let source = required_string(&body, "dumpSource")?.trim(); + if source.is_empty() { + return self.validation_response(false, "Select a dump source first."); + } + let valid = + enhanced::validate_and_remember_dump_source(&self.state, Path::new(source))?; + self.validation_response( + valid, + if valid { + "Source looks ready." + } else { + "Source is missing required dump files." + }, + ) + } + "/api/enhanced-install" => { + let source = required_string(&body, "dumpSource")?; + self.mutate(|| { + enhanced::install( + &self.state, + &required_game_dir(&self.state)?, + Path::new(source), + ) + }) + } + "/api/enhanced-uninstall" => { + self.mutate(|| enhanced::uninstall(&self.state, &required_game_dir(&self.state)?)) + } + "/api/exe-swap/compatible" => self.activate_compatible(), + "/api/exe-swap/compatible-depot" => Ok(json!({ + "ok": true, + "available": exe::compatible_depot_available(), + "state": self.current_state()?, + })), + "/api/exe-swap/current" => { + self.mutate(|| exe::activate_current(&self.state, &required_game_dir(&self.state)?)) + } + "/api/exe-swap/enhanced" => self + .mutate(|| exe::activate_enhanced(&self.state, &required_game_dir(&self.state)?)), + "/api/presets/apply" => { + let name = required_string(&body, "name")?; + self.mutate(|| { + app::apply_preset(&self.state, &required_game_dir(&self.state)?, name) + }) + } + "/api/intro-skip" => { + let enabled = required_bool(&body, "enabled")?; + self.mutate(|| { + app::set_intro_skip(&self.state, &required_game_dir(&self.state)?, enabled) + }) + } + "/api/d3dcompiler" => { + let enabled = required_bool(&body, "enabled")?; + self.mutate(|| { + app::set_d3dcompiler(&self.state, &required_game_dir(&self.state)?, enabled) + }) + } + "/api/all-intros-skip" => { + let enabled = required_bool(&body, "enabled")?; + self.mutate(|| { + app::set_all_intro_skip(&self.state, &required_game_dir(&self.state)?, enabled) + }) + } + "/api/config-readonly" => { + let enabled = required_bool(&body, "enabled")?; + self.mutate(|| { + app::set_config_readonly(&self.state, &required_game_dir(&self.state)?, enabled) + }) + } + "/api/vram-target" => { + let limited = required_bool(&body, "limited")?; + let target = required_i32(&body, "target")?; + self.mutate(|| { + app::set_vram_target( + &self.state, + &required_game_dir(&self.state)?, + limited, + target, + ) + }) + } + "/api/logs/payload" => Ok(json!({ + "ok": true, + "payload": app::log_payload(&self.state), + })), + "/api/logs/clear" => self.mutate(|| { + self.state.clear_logs()?; + self.state.log("Success", "Logs cleared."); + Ok(()) + }), + "/api/mod-files/clear" => self.mutate(|| app::clear_mod_files(&self.state)), + "/api/reset-stock" => self.mutate(|| reset_to_stock_inner(&self.state)), + _ => Err(format!("Unsupported backend path: {path}")), + } + } + + fn mutate(&self, operation: impl FnOnce() -> Result<(), String>) -> Result { + let _guard = self.state.lock_operation()?; + operation()?; + Ok(json!({"ok": true, "state": self.current_state()?})) + } + + fn validation_response(&self, valid: bool, message: &str) -> Result { + Ok(json!({ + "ok": true, + "valid": valid, + "message": message, + "state": self.current_state()?, + })) + } + + fn activate_compatible(&self) -> Result { + let _guard = self.state.lock_operation()?; + let game_dir = required_game_dir(&self.state)?; + match exe::activate_compatible(&self.state, &game_dir) { + Ok(()) => Ok(json!({"ok": true, "state": self.current_state()?})), + Err(error) if error == exe::COMPATIBLE_DEPOT_REQUIRED_MESSAGE => Ok(json!({ + "ok": false, + "error": error, + "depotRequired": true, + "depotCommand": exe::COMPATIBLE_DEPOT_COMMAND, + "state": self.current_state()?, + })), + Err(error) => Err(error), + } + } + + fn update_check(&self) -> Result { + let _guard = self.state.lock_operation()?; + let result = check_update(&self.state)?; + Ok(json!({ + "ok": true, + "update": result, + "state": self.current_state()?, + })) + } +} + +fn normalize_wire_value(mut value: Value) -> Value { + // Python omits `path` for built-in profiles but includes null for Workshop. + let state = if value.get("state").is_some() { + &mut value["state"] + } else { + &mut value + }; + if let Some(profiles) = state + .get_mut("launchProfiles") + .and_then(Value::as_array_mut) + { + for profile in profiles { + if matches!(profile["id"].as_str(), Some("default" | "offline")) + && let Some(profile) = profile.as_object_mut() + { + profile.remove("path"); + } + } + } + value +} + +fn current_state( + state: &AppState, + hash_provider: Option<&exe::ExeHashProvider>, +) -> Result { + let settings = state.load_settings(); + let game_dir = steam::find_game_directory(settings.game_dir.as_deref()); + let current_launch_options = steam::current_launch_options(); + let launch_profiles = steam::launch_profiles(current_launch_options.as_deref()); + let active_launch_profile = launch_profiles + .iter() + .find(|profile| profile.active) + .map(|profile| profile.id.clone()) + .unwrap_or_else(|| "custom".into()); + let exe_swap = exe::status_with_hash_provider(&settings, game_dir.as_deref(), hash_provider); + let t7_state = t7::status(game_dir.as_deref(), Some(&exe_swap.profile)); + let dxvk_state = dxvk::status(game_dir.as_deref()); + let enhanced_state = enhanced::status( + state, + game_dir.as_deref(), + current_launch_options.as_deref(), + settings.enhanced_dump_source.clone().unwrap_or_default(), + ); + let config = game_dir + .as_deref() + .map(app::read_config) + .unwrap_or_default(); + let qol = app::qol_state(game_dir.as_deref()); + let mods = ModsState { + t7_patch: t7_state.installed, + dxvk: dxvk_state.installed, + enhanced: enhanced_state.installed, + }; + + Ok(PatchOpsState { + app_version: app::APP_VERSION.into(), + platform: app::platform_name().into(), + game_dir: game_dir + .as_ref() + .map(|path| path.to_string_lossy().into_owned()), + game_detected: game_dir.is_some(), + config_exists: !config.is_empty(), + steam_user_id: steam::user_id(), + log_path: state.log_path().to_string_lossy().into_owned(), + presets: app::preset_names(), + current_launch_options, + active_launch_profile, + release_channel: app::release_channel(&settings).into(), + launch_profiles, + enhanced: enhanced_state, + exe_swap, + t7: t7_state, + dxvk: dxvk_state, + qol, + graphics: app::graphics_state(&config), + advanced: app::advanced_state(game_dir.as_deref(), &config), + maintenance: MaintenanceState { + mod_files_dir: state.mod_files_dir().to_string_lossy().into_owned(), + log_payload: app::log_payload(state), + }, + mods, + logs: state.recent_logs(), + }) +} + +fn detected_game_dir(state: &AppState) -> Option { + steam::find_game_directory(state.load_settings().game_dir.as_deref()) +} + +fn required_game_dir(state: &AppState) -> Result { + detected_game_dir(state).ok_or_else(|| "Game directory is not set.".into()) +} + +fn decode(value: Value) -> Result { + serde_json::from_value(value).map_err(|error| format!("Invalid request payload: {error}")) +} + +fn required_string<'a>(body: &'a Value, key: &str) -> Result<&'a str, String> { + body.get(key) + .and_then(Value::as_str) + .ok_or_else(|| format!("Missing or invalid {key}.")) +} + +fn optional_string(body: &Value, key: &str) -> Option { + body.get(key).and_then(Value::as_str).map(str::to_owned) +} + +fn required_bool(body: &Value, key: &str) -> Result { + body.get(key) + .and_then(Value::as_bool) + .ok_or_else(|| format!("Missing or invalid {key}.")) +} + +fn optional_bool(body: &Value, key: &str) -> Option { + body.get(key).and_then(Value::as_bool) +} + +fn required_i32(body: &Value, key: &str) -> Result { + body.get(key) + .and_then(Value::as_i64) + .and_then(|value| i32::try_from(value).ok()) + .ok_or_else(|| format!("Missing or invalid {key}.")) +} + +fn string_error(error: impl std::fmt::Display) -> String { + error.to_string() +} + +fn version_key(value: &str) -> (Vec, bool, u64) { + let value = value.trim().trim_start_matches(['v', 'V']); + let base_end = value + .find(|character: char| !character.is_ascii_digit() && character != '.') + .unwrap_or(value.len()); + let mut base = value[..base_end] + .split('.') + .filter_map(|part| part.parse().ok()) + .collect::>(); + base.resize(3, 0); + let suffix = &value[base_end..]; + let prerelease_number = suffix + .split(|character: char| !character.is_ascii_digit()) + .filter_map(|part| part.parse().ok()) + .next_back() + .unwrap_or(0); + (base, suffix.is_empty(), prerelease_number) +} + +fn compare_versions(left: &str, right: &str) -> Ordering { + version_key(left).cmp(&version_key(right)) +} + +fn check_update(state: &AppState) -> Result { + const MAX_BODY: u64 = 4 * 1024 * 1024; + let settings = state.load_settings(); + let channel = app::release_channel(&settings); + let endpoint = if channel == "beta" { + "https://api.github.com/repos/boggedbrush/PatchOpsIII/releases?per_page=20" + } else { + "https://api.github.com/repos/boggedbrush/PatchOpsIII/releases/latest" + }; + let response = reqwest::blocking::Client::builder() + .connect_timeout(Duration::from_secs(15)) + .timeout(Duration::from_secs(30)) + .build() + .map_err(string_error)? + .get(endpoint) + .header(reqwest::header::USER_AGENT, "PatchOpsIII") + .send() + .and_then(reqwest::blocking::Response::error_for_status) + .map_err(|error| format!("Update check failed: {error}"))?; + if response + .content_length() + .is_some_and(|length| length > MAX_BODY) + { + return Err("Update metadata is unexpectedly large.".into()); + } + let mut body = Vec::new(); + response + .take(MAX_BODY + 1) + .read_to_end(&mut body) + .map_err(string_error)?; + if body.len() as u64 > MAX_BODY { + return Err("Update metadata is unexpectedly large.".into()); + } + let root: Value = serde_json::from_slice(&body).map_err(string_error)?; + let release = if channel == "beta" { + root.as_array() + .and_then(|releases| { + releases.iter().find(|release| { + release.get("draft").and_then(Value::as_bool) != Some(true) + && release.get("prerelease").and_then(Value::as_bool) == Some(true) + && format!( + "{} {}", + release + .get("tag_name") + .and_then(Value::as_str) + .unwrap_or_default(), + release + .get("name") + .and_then(Value::as_str) + .unwrap_or_default() + ) + .to_ascii_lowercase() + .contains("beta") + }) + }) + .ok_or_else(|| "No beta release was found.".to_string())? + } else { + &root + }; + let result = update_metadata(release, channel, std::env::consts::OS); + let available = result["available"].as_bool() == Some(true); + state.log( + if available { "Success" } else { "Info" }, + if available { + format!( + "{} update available: {}", + title_case(channel), + result["latestVersion"].as_str().unwrap_or_default() + ) + } else { + format!("No {channel} updates available.") + }, + ); + Ok(result) +} + +// Preserve Python's metadata shape but choose the native client artifact. +fn update_metadata(release: &Value, channel: &str, platform: &str) -> Value { + let expected_name = match platform { + "windows" => "PatchOpsIII-native-windows-x64.zip", + "linux" => "PatchOpsIII-native-linux-x64.zip", + _ => "", + }; + let asset = release["assets"].as_array().and_then(|assets| { + assets.iter().find_map(|asset| { + let name = asset["name"].as_str()?; + if expected_name.is_empty() || !name.eq_ignore_ascii_case(expected_name) { + return None; + } + let url = asset["browser_download_url"] + .as_str() + .filter(|url| !url.is_empty())?; + Some(json!({ + "name": name, "url": url, + "size": asset["size"].as_u64().unwrap_or(0), + "contentType": asset["content_type"].as_str().unwrap_or("application/octet-stream"), + })) + }) + }); + let latest = release["tag_name"] + .as_str() + .filter(|tag| !tag.is_empty()) + .or(release["name"].as_str()) + .unwrap_or("0.0.0"); + let available = asset.is_some() + && release["draft"] != true + && (channel == "beta" || release["prerelease"] != true) + && compare_versions(latest, app::APP_VERSION).is_gt(); + json!({ + "available": available, "channel": channel, "currentVersion": app::APP_VERSION, + "latestVersion": latest, "name": release["name"].as_str().unwrap_or("PatchOpsIII"), + "body": release["body"].as_str().unwrap_or(""), + "pageUrl": release["html_url"].as_str().unwrap_or("https://github.com/boggedbrush/PatchOpsIII/releases"), + "asset": asset, + }) +} + +fn title_case(value: &str) -> String { + let mut chars = value.chars(); + chars + .next() + .map(|first| first.to_uppercase().collect::() + chars.as_str()) + .unwrap_or_default() +} + +fn reset_to_stock_inner(state: &AppState) -> Result<(), String> { + let game_dir = required_game_dir(state)?; + let mut errors = Vec::new(); + if (enhanced::detect_install(&game_dir) || enhanced::has_owned_install(state, &game_dir)) + && let Err(error) = enhanced::uninstall(state, &game_dir) + { + errors.push(format!("Enhanced: {error}")); + } + if exe::status(&state.load_settings(), Some(&game_dir)).profile != exe::CURRENT_EXE_ID + && let Err(error) = exe::activate_current(state, &game_dir) + { + errors.push(format!("EXE: {error}")); + } + if let Err(error) = t7::uninstall(state, &game_dir) { + errors.push(format!("T7 Patch: {error}")); + } + if let Err(error) = dxvk::uninstall(state, &game_dir) { + errors.push(format!("DXVK: {error}")); + } + let qol = app::qol_state(Some(&game_dir)); + if qol.d3dcompiler + && let Err(error) = app::set_d3dcompiler(state, &game_dir, false) + { + errors.push(format!("d3dcompiler: {error}")); + } + if qol.all_intros { + if let Err(error) = app::set_all_intro_skip(state, &game_dir, false) { + errors.push(format!("intros: {error}")); + } + } else if qol.intro + && let Err(error) = app::set_intro_skip(state, &game_dir, false) + { + errors.push(format!("intro: {error}")); + } + if app::config_path(&game_dir).is_file() { + let updates = vec![ + ("SmoothFramerate".into(), "0".into(), "0 or 1".into()), + ("VideoMemory".into(), "1".into(), "0.75 to 1".into()), + ("StreamMinResident".into(), "0".into(), "0 or 1".into()), + ("MaxFrameLatency".into(), "1".into(), "0 to 4".into()), + ("SerializeRender".into(), "0".into(), "0 to 2".into()), + ( + "RestrictGraphicsOptions".into(), + "1".into(), + "0 or 1".into(), + ), + ]; + if let Err(error) = app::write_config_values(&game_dir, &updates) { + errors.push(format!("config: {error}")); + } + } + if let Err(error) = steam::apply_launch_options(state, "", false) { + errors.push(format!("launch options: {error}")); + } + if errors.is_empty() { + state.log("Success", "Reset to stock complete."); + Ok(()) + } else { + let message = format!("Reset completed with errors: {}", errors.join("; ")); + state.log("Error", &message); + Err(message) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::{fs, sync::Arc}; + + fn test_engine(name: &str) -> (Engine, PathBuf) { + let root = std::env::temp_dir().join(format!( + "patchops-core-engine-{name}-{}", + std::process::id() + )); + let _ = fs::remove_dir_all(&root); + fs::create_dir_all(&root).unwrap(); + ( + Engine::new(AppState::new(root.join("data"), None, None).unwrap()), + root, + ) + } + + #[test] + fn status_preserves_legacy_json_shape() { + let (engine, root) = test_engine("status"); + let status = engine.dispatch("/api/status", None).unwrap(); + assert!(status["appVersion"].is_string()); + assert!(status["gameDetected"].is_boolean()); + assert!(status["exeSwap"]["currentBuildId"].is_string()); + assert!(status["launchProfiles"].is_array()); + assert_eq!(status["platform"], app::platform_name()); + assert!(status["maintenance"]["logPayload"].is_string()); + assert!(status["launchProfiles"][0].get("path").is_none()); + assert!(status["launchProfiles"][2].get("path").is_some()); + assert!( + engine + .dispatch("/api/health", None) + .unwrap() + .get("launchProfiles") + .is_none() + ); + fs::remove_dir_all(root).unwrap(); + } + + #[test] + fn log_callback_is_toolkit_independent() { + let root = + std::env::temp_dir().join(format!("patchops-core-events-{}", std::process::id())); + let _ = fs::remove_dir_all(&root); + let seen = Arc::new(std::sync::Mutex::new(Vec::new())); + let capture = seen.clone(); + let state = AppState::new( + root.join("data"), + None, + Some(Arc::new(move |entry| capture.lock().unwrap().push(entry))), + ) + .unwrap(); + state.log("Info", "hello"); + assert_eq!(seen.lock().unwrap()[0].message, "hello"); + fs::remove_dir_all(root).unwrap(); + } + + #[test] + fn updates_select_native_archives_and_preserve_python_metadata() { + let release = json!({"tag_name": "v9.0.0", "name": "New release", "body": "Notes", "html_url": "https://example.invalid/release", "assets": [ + {"name": "PatchOpsIII.AppImage", "browser_download_url": "https://example.invalid/legacy"}, + {"name": "PatchOpsIII-native-linux-x64.zip", "browser_download_url": "https://example.invalid/linux", "size": 42, "content_type": "application/zip"}, + {"name": "PatchOpsIII-native-windows-x64.zip", "browser_download_url": "https://example.invalid/windows"} + ]}); + let linux = update_metadata(&release, "stable", "linux"); + assert_eq!(linux["asset"]["url"], "https://example.invalid/linux"); + assert_eq!(linux["asset"]["size"], 42); + assert_eq!(linux["body"], "Notes"); + assert_eq!(linux["available"], true); + assert_eq!( + update_metadata(&release, "stable", "windows")["asset"]["url"], + "https://example.invalid/windows" + ); + assert_eq!( + update_metadata(&release, "stable", "macos")["available"], + false + ); + let mut beta = release.clone(); + beta["prerelease"] = json!(true); + assert_eq!( + update_metadata(&beta, "stable", "linux")["available"], + false + ); + assert_eq!(update_metadata(&beta, "beta", "linux")["available"], true); + beta["draft"] = json!(true); + assert_eq!(update_metadata(&beta, "beta", "linux")["available"], false); + beta["assets"] = json!([]); + assert!(update_metadata(&beta, "stable", "linux")["asset"].is_null()); + } + + #[test] + fn progress_reports_failure_and_stays_quiet_during_status_polls() { + let (engine, root) = test_engine("progress"); + let seen = Arc::new(std::sync::Mutex::new(Vec::new())); + let capture = seen.clone(); + engine + .state() + .set_progress_callback(Arc::new(move |event| capture.lock().unwrap().push(event))); + engine.dispatch("/api/status", None).unwrap(); + assert!(seen.lock().unwrap().is_empty()); + assert_eq!( + engine + .dispatch("/api/config", Some(json!({"key": "FOV", "value": 80}))) + .unwrap()["ok"], + false + ); + let events = seen.lock().unwrap(); + assert_eq!(events.first().unwrap().stage, "started"); + assert_eq!(events.last().unwrap().stage, "failed"); + assert_eq!(events.last().unwrap().op, "/api/config"); + assert!(events.last().unwrap().fraction.is_none()); + drop(events); + engine + .dispatch("/api/release-channel", Some(json!({"channel": "beta"}))) + .unwrap(); + let events = seen.lock().unwrap(); + assert_eq!(events.last().unwrap().stage, "completed"); + assert_eq!(events.last().unwrap().op, "/api/release-channel"); + assert_eq!(events.last().unwrap().fraction, Some(1.0)); + fs::remove_dir_all(root).unwrap(); + } + + #[test] + fn version_order_matches_stable_and_beta_channels() { + assert!(compare_versions("v1.3.1", "1.3.0-beta3").is_gt()); + assert!(compare_versions("1.3.0", "1.3.0-beta3").is_gt()); + assert!(compare_versions("1.3.0-beta4", "1.3.0-beta3").is_gt()); + assert_eq!(compare_versions("v1.3", "1.3.0"), Ordering::Equal); + } +} diff --git a/native/core/src/enhanced.rs b/native/core/src/enhanced.rs new file mode 100644 index 0000000..aa43215 --- /dev/null +++ b/native/core/src/enhanced.rs @@ -0,0 +1,2477 @@ +use std::{ + collections::{BTreeMap, BTreeSet, HashSet}, + fs::{self, File}, + io::{self, Read}, + path::{Component, Path, PathBuf}, + sync::Mutex, +}; + +use serde::{Deserialize, Serialize}; +use serde_json::{Map, Value}; +use sha2::{Digest, Sha256}; +use time::OffsetDateTime; + +#[cfg(target_os = "linux")] +use crate::steam; +use crate::{app::AppState, exe, fs_ops, models::EnhancedState}; + +pub const GITHUB_LATEST_ENHANCED_API: &str = + "https://api.github.com/repos/shiversoftdev/BO3Enhanced/releases/latest"; +pub const STATE_FILENAME: &str = "bo3_enhanced_state.json"; +pub const ENHANCED_ARCHIVE_NAME: &str = "BO3Enhanced_latest.zip"; +pub const CHECKSUMS_FILENAME: &str = "bo3_enhanced_checksums.json"; + +const EXPECTED_ENHANCED_FILES: [&str; 4] = [ + "T7WSBootstrapper.dll", + "T7InternalWS.dll", + "steam_api65.dll", + "WindowsCodecs.dll", +]; +const EXPECTED_DUMP_FILES: [&str; 2] = ["BlackOps3.exe", "MicrosoftGame.config"]; +const UWP_DUMP_WHITELIST: [&str; 8] = [ + "BlackOps3.exe", + "MicrosoftGame.config", + "GameChat2.dll", + "Party.dll", + "PartyXboxLive.dll", + "PlayFabMultiplayerGDK.dll", + "libScePad.dll", + "XCurl.dll", +]; +const MAX_ARCHIVE_BYTES: u64 = 16 * 1024 * 1024 * 1024; +const MAX_ARCHIVE_ENTRIES: usize = 100_000; +const MAX_DOWNLOAD_BYTES: u64 = 2 * 1024 * 1024 * 1024; +const MAX_RELEASE_METADATA_BYTES: u64 = 4 * 1024 * 1024; +const ENHANCED_RELEASE_PATH_PREFIX: &str = "/shiversoftdev/BO3Enhanced/releases/download/"; + +#[derive(Clone, Default, Serialize, Deserialize)] +struct PersistedState { + #[serde(default)] + game_directory: Option, + #[serde(default)] + installed: bool, + #[serde(default)] + detected_at: Option, + #[serde(default)] + acknowledged_at: Option, + #[serde(default)] + installed_files: Vec, + #[serde(default)] + dump_only_files: Vec, + #[serde(default)] + created_files: Vec, + #[serde(default)] + owned_backups: BTreeMap, + #[serde(default)] + installed_hashes: BTreeMap, + #[serde(default)] + original_backup_hashes: BTreeMap, + #[serde(flatten)] + extra: Map, +} + +enum MemberSource { + File(PathBuf), + Zip(usize), +} + +struct PayloadMember { + relative: PathBuf, + source: MemberSource, +} + +struct AppliedFile { + target: PathBuf, + rollback_copy: Option, + new_permanent_backup: Option, +} + +#[derive(Clone)] +enum UninstallAction { + Restore(PathBuf), + Remove, +} + +#[derive(Clone)] +struct PlannedUninstall { + target: PathBuf, + action: UninstallAction, +} + +struct AppliedUninstall { + plan: PlannedUninstall, + displaced: Option, +} + +struct EnhancedUninstallTransaction { + applied: Vec, + transaction: Option, + storage_dir: PathBuf, + original_state: PersistedState, +} + +#[derive(Debug)] +struct EnhancedRelease { + version: String, + asset_url: String, + asset_sha256: String, +} + +fn state_path(storage_dir: &Path) -> PathBuf { + storage_dir.join(STATE_FILENAME) +} + +fn checksums_path(storage_dir: &Path) -> PathBuf { + storage_dir.join(CHECKSUMS_FILENAME) +} + +fn load_state(storage_dir: &Path) -> PersistedState { + fs::read(state_path(storage_dir)) + .ok() + .and_then(|body| serde_json::from_slice(&body).ok()) + .unwrap_or_default() +} + +fn save_state(storage_dir: &Path, state: &PersistedState) -> Result<(), String> { + let body = serde_json::to_vec_pretty(state).map_err(|error| error.to_string())?; + fs_ops::atomic_write(&state_path(storage_dir), &body) +} + +fn canonical_game_key(game_dir: &Path) -> Result { + let path = fs::canonicalize(game_dir).map_err(|error| error.to_string())?; + let key = path.to_string_lossy().into_owned(); + #[cfg(windows)] + return Ok(key.to_ascii_lowercase()); + #[cfg(not(windows))] + Ok(key) +} + +fn state_belongs_to_game(state: &PersistedState, game_dir: &Path) -> bool { + canonical_game_key(game_dir) + .ok() + .is_some_and(|key| state.game_directory.as_deref() == Some(key.as_str())) +} + +fn state_has_ownership(state: &PersistedState) -> bool { + state.installed + || !state.installed_files.is_empty() + || !state.dump_only_files.is_empty() + || !state.created_files.is_empty() + || !state.owned_backups.is_empty() + || !state.installed_hashes.is_empty() + || !state.original_backup_hashes.is_empty() +} + +fn state_has_owned_install(state: &PersistedState, game_dir: &Path) -> bool { + state_belongs_to_game(state, game_dir) && state_has_ownership(state) +} + +pub fn has_owned_install(state: &AppState, game_dir: &Path) -> bool { + state_has_owned_install(&load_state(state.data_dir()), game_dir) +} + +fn load_checksums(storage_dir: &Path) -> BTreeMap { + fs::read(checksums_path(storage_dir)) + .ok() + .and_then(|body| serde_json::from_slice(&body).ok()) + .unwrap_or_default() +} + +fn save_checksums(storage_dir: &Path, checksums: &BTreeMap) -> Result<(), String> { + let body = serde_json::to_vec_pretty(checksums).map_err(|error| error.to_string())?; + fs_ops::atomic_write(&checksums_path(storage_dir), &body) +} + +fn utc_timestamp() -> String { + let now = OffsetDateTime::now_utc(); + format!( + "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z", + now.year(), + now.month() as u8, + now.day(), + now.hour(), + now.minute(), + now.second() + ) +} + +pub fn detect_install(game_dir: &Path) -> bool { + EXPECTED_ENHANCED_FILES + .iter() + .all(|name| game_dir.join(name).is_file()) +} + +fn legacy_status_files_match(state: &PersistedState, game_dir: &Path) -> bool { + if !state.installed + || !EXPECTED_ENHANCED_FILES.iter().all(|name| { + fs::symlink_metadata(game_dir.join(name)) + .is_ok_and(|meta| meta.is_file() && !meta.file_type().is_symlink()) + }) + || state.installed_files.is_empty() + || !state.created_files.is_empty() + || !state.owned_backups.is_empty() + || !state.original_backup_hashes.is_empty() + { + return false; + } + let mut seen = HashSet::new(); + state + .installed_files + .iter() + .chain(state.dump_only_files.iter()) + .all(|name| { + let Ok((relative, normalized)) = normalized_legacy_path(name) else { + return false; + }; + if !seen.insert(normalized.clone()) && !state.dump_only_files.contains(name) { + return false; + } + let allowed = EXPECTED_ENHANCED_FILES.contains(&normalized.as_str()) + || (relative.components().count() == 1 + && UWP_DUMP_WHITELIST.contains(&normalized.as_str())); + let Ok(target) = contained_target(game_dir, &relative) else { + return false; + }; + allowed + && fs::symlink_metadata(&target) + .is_ok_and(|meta| meta.is_file() && !meta.file_type().is_symlink()) + && state.installed_hashes.get(name).is_none_or(|expected| { + fs_ops::sha256_file(&target) + .is_ok_and(|actual| actual.eq_ignore_ascii_case(expected)) + }) + }) +} + +fn status_at( + storage_dir: &Path, + legacy_binding: &Mutex>, + game_dir: Option<&Path>, + current_launch_options: Option<&str>, + dump_source: String, +) -> (EnhancedState, Option) { + let mut stored = load_state(storage_dir); + // Legacy Python status metadata has no destructive ownership proof. Bind + // it in this AppState only; verified archive adoption remains required for + // install/uninstall. Do not rewrite the legacy JSON merely by reading it. + if let Some(game_dir) = game_dir + && stored.game_directory.is_none() + && legacy_status_files_match(&stored, game_dir) + && let Ok(key) = canonical_game_key(game_dir) + && let Ok(mut binding) = legacy_binding.lock() + { + if binding.is_none() { + *binding = Some(key.clone()); + } + if binding.as_deref() == Some(&key) { + stored.game_directory = Some(key); + } + } + let mut persisted = match game_dir.filter(|game_dir| state_belongs_to_game(&stored, game_dir)) { + Some(_) => stored, + None => PersistedState::default(), + }; + let installed_files = persisted.installed_files.len(); + let detected = game_dir.is_some_and(detect_install); + let stale_state_error = + if !detected && persisted.installed_files.is_empty() && persisted.installed { + persisted.installed = false; + save_state(storage_dir, &persisted).err() + } else { + None + }; + let installed = game_dir.is_some() && (detected || persisted.installed); + ( + EnhancedState { + installed, + detected_at: persisted.detected_at, + acknowledged_at: persisted.acknowledged_at, + launch_options_active: current_launch_options + .unwrap_or_default() + .to_ascii_lowercase() + .contains("windowscodecs=n,b"), + dump_source, + files_installed: installed_files, + backup_status: if installed && installed_files > 0 { + "Created".into() + } else { + "Not created".into() + }, + }, + stale_state_error, + ) +} + +pub fn status( + state: &AppState, + game_dir: Option<&Path>, + current_launch_options: Option<&str>, + dump_source: String, +) -> EnhancedState { + let (result, stale_state_error) = status_at( + state.data_dir(), + state.legacy_enhanced_binding(), + game_dir, + current_launch_options, + dump_source, + ); + if let Some(error) = stale_state_error { + state.log( + "Warning", + format!("Failed to clear stale BO3 Enhanced state: {error}"), + ); + } + result +} + +fn safe_relative(path: &Path) -> bool { + path.components() + .all(|component| matches!(component, Component::Normal(_) | Component::CurDir)) +} + +fn contained_target(root: &Path, relative: &Path) -> Result { + if !safe_relative(relative) { + return Err(format!( + "Refusing unsafe payload path: {}", + relative.display() + )); + } + let canonical_root = fs::canonicalize(root).map_err(|error| error.to_string())?; + let target = root.join(relative); + let mut existing_parent = target.parent().unwrap_or(root); + while !existing_parent.exists() { + existing_parent = existing_parent + .parent() + .ok_or_else(|| "Payload target has no contained parent.".to_string())?; + } + let canonical_parent = fs::canonicalize(existing_parent).map_err(|error| error.to_string())?; + if !canonical_parent.starts_with(&canonical_root) { + return Err(format!( + "Payload target escapes the game directory: {}", + target.display() + )); + } + Ok(target) +} + +fn archive_relative(name: &str) -> Result { + if name.contains('\0') { + return Err("Archive contains an unsafe path.".into()); + } + let normalized = name.replace('\\', "/"); + let relative = PathBuf::from(normalized); + if !safe_relative(&relative) { + return Err(format!("Archive contains an unsafe path: {name}")); + } + Ok(relative) +} + +fn strip_dump_prefix(path: &Path) -> PathBuf { + let mut components = path.components(); + if components + .next() + .is_some_and(|component| component.as_os_str() == "DUMP") + { + components.as_path().to_owned() + } else { + path.to_owned() + } +} + +fn should_copy_dump_member(relative: &Path) -> bool { + relative + .file_name() + .and_then(|name| name.to_str()) + .is_some_and(|name| UWP_DUMP_WHITELIST.contains(&name)) +} + +fn validate_required_dump_files(members: &[PayloadMember]) -> Result<(), String> { + let found: HashSet<_> = members + .iter() + .map(|member| member.relative.to_string_lossy().replace('\\', "/")) + .collect(); + let missing: Vec<_> = EXPECTED_DUMP_FILES + .iter() + .filter(|name| !found.contains(**name)) + .copied() + .collect(); + if missing.is_empty() { + Ok(()) + } else { + Err(format!( + "Dump source missing required files: {}", + missing.join(", ") + )) + } +} + +fn collect_directory_members( + root: &Path, + directory: &Path, + members: &mut Vec, + seen: &mut HashSet, + total: &mut u64, + count: &mut usize, + depth: usize, +) -> Result<(), String> { + if depth > 64 { + return Err("Dump source directory nesting is too deep.".into()); + } + for entry in fs::read_dir(directory).map_err(|error| error.to_string())? { + let entry = entry.map_err(|error| error.to_string())?; + let file_type = entry.file_type().map_err(|error| error.to_string())?; + if file_type.is_symlink() { + return Err(format!( + "Dump source contains a symbolic link: {}", + entry.path().display() + )); + } + if file_type.is_dir() { + collect_directory_members(root, &entry.path(), members, seen, total, count, depth + 1)?; + continue; + } + if !file_type.is_file() { + return Err(format!( + "Dump source contains an unsupported file type: {}", + entry.path().display() + )); + } + *count += 1; + if *count > MAX_ARCHIVE_ENTRIES { + return Err("Dump source contains too many files.".into()); + } + *total = total.saturating_add(entry.metadata().map_err(|error| error.to_string())?.len()); + if *total > MAX_ARCHIVE_BYTES { + return Err("Dump source is larger than the allowed size.".into()); + } + let relative = entry + .path() + .strip_prefix(root) + .map_err(|error| error.to_string())? + .to_owned(); + let relative = strip_dump_prefix(&relative); + if relative.as_os_str().is_empty() || !should_copy_dump_member(&relative) { + continue; + } + let key = relative_string(&relative).to_ascii_lowercase(); + if !safe_relative(&relative) || !seen.insert(key) { + return Err(format!( + "Dump source contains an unsafe or duplicate path: {}", + relative.display() + )); + } + members.push(PayloadMember { + relative, + source: MemberSource::File(entry.path()), + }); + } + Ok(()) +} + +fn zip_entry_is_link(entry: &zip::read::ZipFile<'_>) -> bool { + entry + .unix_mode() + .is_some_and(|mode| mode & 0o170000 == 0o120000) +} + +fn dump_members(source: &Path) -> Result, String> { + let mut members = Vec::new(); + let mut seen = HashSet::new(); + let mut total = 0_u64; + if source.is_dir() { + let mut count = 0; + collect_directory_members( + source, + source, + &mut members, + &mut seen, + &mut total, + &mut count, + 0, + )?; + } else { + let mut archive = zip::ZipArchive::new( + File::open(source).map_err(|error| format!("{}: {error}", source.display()))?, + ) + .map_err(|_| "Dump source is not a valid zip file or directory.".to_string())?; + if archive.len() > MAX_ARCHIVE_ENTRIES { + return Err("Dump archive contains too many entries.".into()); + } + for index in 0..archive.len() { + let entry = archive.by_index(index).map_err(|error| error.to_string())?; + let relative = archive_relative(entry.name())?; + if zip_entry_is_link(&entry) { + return Err(format!("Dump archive contains a link: {}", entry.name())); + } + total = total.saturating_add(entry.size()); + if total > MAX_ARCHIVE_BYTES { + return Err("Dump archive expands beyond the allowed size.".into()); + } + if entry.is_dir() { + continue; + } + let relative = strip_dump_prefix(&relative); + if relative.as_os_str().is_empty() || !should_copy_dump_member(&relative) { + continue; + } + let key = relative_string(&relative).to_ascii_lowercase(); + if !safe_relative(&relative) || !seen.insert(key) { + return Err(format!( + "Dump archive contains an unsafe or duplicate path: {}", + relative.display() + )); + } + members.push(PayloadMember { + relative, + source: MemberSource::Zip(index), + }); + } + } + validate_required_dump_files(&members)?; + Ok(members) +} + +fn enhanced_members(archive_path: &Path) -> Result, String> { + let mut archive = zip::ZipArchive::new( + File::open(archive_path).map_err(|error| format!("{}: {error}", archive_path.display()))?, + ) + .map_err(|_| "Enhanced archive is not a valid zip.".to_string())?; + if archive.len() > MAX_ARCHIVE_ENTRIES { + return Err("Enhanced archive contains too many entries.".into()); + } + + let mut members = Vec::new(); + let mut found = HashSet::new(); + let mut total = 0_u64; + for index in 0..archive.len() { + let entry = archive.by_index(index).map_err(|error| error.to_string())?; + let relative = archive_relative(entry.name())?; + if zip_entry_is_link(&entry) { + return Err(format!( + "Enhanced archive contains a link: {}", + entry.name() + )); + } + total = total.saturating_add(entry.size()); + if total > MAX_ARCHIVE_BYTES { + return Err("Enhanced archive expands beyond the allowed size.".into()); + } + if entry.is_dir() { + continue; + } + let Some(name) = relative.file_name().and_then(|name| name.to_str()) else { + continue; + }; + if !EXPECTED_ENHANCED_FILES.contains(&name) { + continue; + } + if !found.insert(name.to_owned()) { + return Err(format!( + "Enhanced archive contains duplicate expected file: {name}" + )); + } + members.push(PayloadMember { + relative: PathBuf::from(name), + source: MemberSource::Zip(index), + }); + } + let missing: Vec<_> = EXPECTED_ENHANCED_FILES + .iter() + .filter(|name| !found.contains(**name)) + .copied() + .collect(); + if missing.is_empty() { + Ok(members) + } else { + Err(format!( + "Enhanced archive missing expected files: {}", + missing.join(", ") + )) + } +} + +pub fn validate_dump_source(source: &Path) -> Result { + if source.as_os_str().is_empty() || !source.exists() { + return Ok(false); + } + Ok(dump_members(source).is_ok()) +} + +pub fn validate_enhanced_archive(archive: &Path) -> Result { + if !archive.is_file() { + return Ok(false); + } + Ok(enhanced_members(archive).is_ok()) +} + +pub fn validate_and_remember_dump_source(state: &AppState, source: &Path) -> Result { + let mut settings = state.load_settings(); + settings.enhanced_dump_source = Some(source.display().to_string()); + state.save_settings(&settings)?; + validate_dump_source(source) +} + +fn valid_enhanced_asset_url(value: &str) -> bool { + reqwest::Url::parse(value).is_ok_and(|url| { + url.scheme() == "https" + && url.host_str() == Some("github.com") + && url.port().is_none() + && url.username().is_empty() + && url.password().is_none() + && url.path().starts_with(ENHANCED_RELEASE_PATH_PREFIX) + && url.path().to_ascii_lowercase().ends_with(".zip") + && url.query().is_none() + && url.fragment().is_none() + }) +} + +fn fetch_latest_release() -> Result { + let response = reqwest::blocking::Client::builder() + .connect_timeout(std::time::Duration::from_secs(15)) + .timeout(std::time::Duration::from_secs(30)) + .build() + .map_err(|error| error.to_string())? + .get(GITHUB_LATEST_ENHANCED_API) + .header(reqwest::header::USER_AGENT, "PatchOpsIII") + .header(reqwest::header::ACCEPT, "application/vnd.github+json") + .send() + .and_then(reqwest::blocking::Response::error_for_status) + .map_err(|error| format!("Failed to fetch BO3 Enhanced release metadata: {error}"))?; + if response + .content_length() + .is_some_and(|length| length > MAX_RELEASE_METADATA_BYTES) + { + return Err("BO3 Enhanced release metadata was unexpectedly large.".into()); + } + let mut body = Vec::new(); + response + .take(MAX_RELEASE_METADATA_BYTES + 1) + .read_to_end(&mut body) + .map_err(|error| error.to_string())?; + if body.len() as u64 > MAX_RELEASE_METADATA_BYTES { + return Err("BO3 Enhanced release metadata was unexpectedly large.".into()); + } + let data: Value = serde_json::from_slice(&body) + .map_err(|error| format!("Invalid BO3 Enhanced release metadata: {error}"))?; + + let assets = data + .get("assets") + .and_then(Value::as_array) + .ok_or_else(|| { + "No downloadable asset found for BO3 Enhanced latest release.".to_string() + })?; + let selected = assets + .iter() + .find(|asset| { + asset + .get("name") + .and_then(Value::as_str) + .is_some_and(|name| name.to_ascii_lowercase().ends_with(".zip")) + }) + .ok_or_else(|| { + "No downloadable asset found for BO3 Enhanced latest release.".to_string() + })?; + let digest = selected + .get("digest") + .and_then(Value::as_str) + .unwrap_or_default() + .trim() + .to_ascii_lowercase(); + let checksum = digest.strip_prefix("sha256:").unwrap_or_default().trim(); + if checksum.len() != 64 || !checksum.bytes().all(|byte| byte.is_ascii_hexdigit()) { + return Err("Latest BO3 Enhanced release metadata did not include a SHA-256 digest; refusing unverified download.".into()); + } + let asset_url = selected + .get("browser_download_url") + .and_then(Value::as_str) + .filter(|url| valid_enhanced_asset_url(url)) + .ok_or_else(|| "BO3 Enhanced release asset has an invalid download URL.".to_string())?; + Ok(EnhancedRelease { + version: data + .get("tag_name") + .and_then(Value::as_str) + .filter(|value| !value.is_empty()) + .or_else(|| data.get("name").and_then(Value::as_str)) + .unwrap_or("0.0.0") + .to_owned(), + asset_url: asset_url.to_owned(), + asset_sha256: checksum.to_owned(), + }) +} + +pub fn download_latest(state: &AppState) -> Result { + let release = fetch_latest_release()?; + fs::create_dir_all(state.mod_files_dir()).map_err(|error| error.to_string())?; + let destination = state.mod_files_dir().join(ENHANCED_ARCHIVE_NAME); + let checksum = fs_ops::download( + state, + &release.asset_url, + &destination, + Some(&release.asset_sha256), + MAX_DOWNLOAD_BYTES, + )?; + if !validate_enhanced_archive(&destination)? { + let _ = fs::remove_file(&destination); + return Err("Enhanced archive failed validation.".into()); + } + let mut checksums = load_checksums(state.data_dir()); + checksums.insert(ENHANCED_ARCHIVE_NAME.into(), checksum); + save_checksums(state.data_dir(), &checksums)?; + state.log( + "Success", + format!( + "Downloaded BO3 Enhanced {} to {}", + release.version, + destination.display() + ), + ); + Ok(destination) +} + +fn verify_cached_checksum(storage_dir: &Path, archive: &Path) -> Result<(), String> { + let Some(filename) = archive.file_name().and_then(|name| name.to_str()) else { + return Err("Invalid Enhanced archive filename.".into()); + }; + let checksums = load_checksums(storage_dir); + let Some(expected) = checksums.get(filename) else { + return Ok(()); + }; + let actual = fs_ops::sha256_file(archive)?; + if actual.eq_ignore_ascii_case(expected) { + Ok(()) + } else { + Err("Enhanced archive failed cached SHA-256 verification.".into()) + } +} + +fn sha256_reader(reader: &mut impl Read) -> Result { + let mut digest = Sha256::new(); + let mut buffer = [0_u8; 1024 * 1024]; + loop { + let read = reader + .read(&mut buffer) + .map_err(|error| error.to_string())?; + if read == 0 { + break; + } + digest.update(&buffer[..read]); + } + Ok(format!("{:x}", digest.finalize())) +} + +fn member_hashes( + source_archive: Option<&Path>, + members: &[PayloadMember], +) -> Result, String> { + let mut archive = source_archive + .map(|path| { + zip::ZipArchive::new( + File::open(path).map_err(|error| format!("{}: {error}", path.display()))?, + ) + .map_err(|error| error.to_string()) + }) + .transpose()?; + let mut hashes = BTreeMap::new(); + for member in members { + if !safe_relative(&member.relative) || member.relative.as_os_str().is_empty() { + return Err(format!( + "Refusing unsafe legacy provenance path: {}", + member.relative.display() + )); + } + let hash = match &member.source { + MemberSource::File(source) => { + if !regular_file_without_links(source) { + return Err(format!( + "Legacy provenance source is missing or unsafe: {}", + source.display() + )); + } + let hash = fs_ops::sha256_file(source)?; + if !regular_file_without_links(source) { + return Err(format!( + "Legacy provenance source changed while it was read: {}", + source.display() + )); + } + hash + } + MemberSource::Zip(index) => { + let mut entry = archive + .as_mut() + .ok_or_else(|| "Missing legacy provenance archive.".to_string())? + .by_index(*index) + .map_err(|error| error.to_string())?; + if entry.is_dir() || zip_entry_is_link(&entry) { + return Err(format!( + "Legacy provenance archive member is unsafe: {}", + entry.name() + )); + } + sha256_reader(&mut entry)? + } + }; + let relative = relative_string(&member.relative); + if hashes.insert(relative.clone(), hash).is_some() { + return Err(format!( + "Legacy provenance contains a duplicate path: {relative}" + )); + } + } + Ok(hashes) +} + +fn verified_cached_enhanced_hashes( + storage_dir: &Path, + archive: &Path, +) -> Result, String> { + if !regular_file_without_links(archive) { + return Err( + "Legacy Enhanced ownership cannot be verified because the cached archive is missing or unsafe." + .into(), + ); + } + let filename = archive + .file_name() + .and_then(|name| name.to_str()) + .ok_or_else(|| "Invalid cached Enhanced archive filename.".to_string())?; + let checksums = load_checksums(storage_dir); + let expected = checksums.get(filename).ok_or_else(|| { + "Legacy Enhanced ownership cannot be verified because the cached archive has no recorded SHA-256 checksum." + .to_string() + })?; + if expected.len() != 64 || !expected.bytes().all(|byte| byte.is_ascii_hexdigit()) { + return Err( + "Legacy Enhanced ownership cannot be verified because the cached archive checksum is invalid." + .into(), + ); + } + let before = fs_ops::sha256_file(archive)?; + if !before.eq_ignore_ascii_case(expected) { + return Err( + "Legacy Enhanced ownership cannot be verified because the cached archive failed SHA-256 verification." + .into(), + ); + } + let members = enhanced_members(archive)?; + let hashes = member_hashes(Some(archive), &members)?; + let after = fs_ops::sha256_file(archive)?; + if !after.eq_ignore_ascii_case(expected) || !after.eq_ignore_ascii_case(&before) { + return Err( + "Legacy Enhanced ownership cannot be verified because the cached archive changed while it was inspected." + .into(), + ); + } + Ok(hashes) +} + +fn verified_dump_member_hashes(source: &Path) -> Result, String> { + let metadata = fs::symlink_metadata(source).map_err(|_| { + "Legacy Enhanced ownership cannot be verified because the original dump source is unavailable." + .to_string() + })?; + if metadata.file_type().is_symlink() || (!metadata.is_file() && !metadata.is_dir()) { + return Err( + "Legacy Enhanced ownership cannot be verified because the original dump source is unsafe." + .into(), + ); + } + let archive_hash = metadata + .is_file() + .then(|| fs_ops::sha256_file(source)) + .transpose()?; + let members = dump_members(source)?; + let hashes = member_hashes(metadata.is_file().then_some(source), &members)?; + if let Some(before) = archive_hash { + let after = fs_ops::sha256_file(source)?; + if !after.eq_ignore_ascii_case(&before) { + return Err( + "Legacy Enhanced ownership cannot be verified because the dump archive changed while it was inspected." + .into(), + ); + } + } + Ok(hashes) +} + +fn normalized_legacy_path(value: &str) -> Result<(PathBuf, String), String> { + let normalized = value.replace('\\', "/"); + let relative = PathBuf::from(&normalized); + if normalized.is_empty() || !safe_relative(&relative) { + return Err(format!( + "Legacy Enhanced state contains an unsafe path: {value}." + )); + } + Ok((relative, normalized)) +} + +fn legacy_backup_for_target( + game_dir: &Path, + target: &Path, + relative_text: &str, +) -> Result, String> { + for candidate in [ + fs_ops::backup_path(target), + fs_ops::legacy_backup_path(target), + ] { + let relative = candidate.strip_prefix(game_dir).map_err(|_| { + format!("Legacy Enhanced backup escaped the game directory for {relative_text}.") + })?; + if !safe_relative(relative) || relative.as_os_str().is_empty() { + return Err(format!( + "Legacy Enhanced backup path is unsafe for {relative_text}." + )); + } + let candidate = contained_target(game_dir, relative)?; + match fs::symlink_metadata(&candidate) { + Ok(metadata) if metadata.is_file() && !metadata.file_type().is_symlink() => { + let hash = fs_ops::sha256_file(&candidate)?; + return Ok(Some((relative_string(relative), hash))); + } + Ok(_) => { + return Err(format!( + "Legacy Enhanced backup is not a regular file for {relative_text}: {}.", + candidate.display() + )); + } + Err(error) if error.kind() == io::ErrorKind::NotFound => {} + Err(error) => return Err(error.to_string()), + } + } + Ok(None) +} + +fn adopt_legacy_state( + game_dir: &Path, + enhanced_archive: &Path, + dump_source: Option<&Path>, + storage_dir: &Path, +) -> Result { + let mut persisted = load_state(storage_dir); + if persisted.game_directory.is_some() || !state_has_ownership(&persisted) { + return Ok(false); + } + if !game_dir.is_dir() { + return Err("Invalid game directory for legacy Enhanced ownership adoption.".into()); + } + if !persisted.created_files.is_empty() + || !persisted.owned_backups.is_empty() + || !persisted.installed_hashes.is_empty() + || !persisted.original_backup_hashes.is_empty() + { + return Err( + "Enhanced ownership state is unbound but already contains partial ownership metadata; refusing unsafe adoption." + .into(), + ); + } + + let mut tracked = BTreeSet::new(); + for value in persisted + .installed_files + .iter() + .chain(persisted.dump_only_files.iter()) + { + let (_, normalized) = normalized_legacy_path(value)?; + tracked.insert(normalized); + } + if tracked.is_empty() { + return Err( + "Legacy Enhanced ownership cannot be verified because no managed files were recorded." + .into(), + ); + } + + let mut needs_enhanced = false; + let mut needs_dump = false; + for relative_text in &tracked { + let (relative, _) = normalized_legacy_path(relative_text)?; + if EXPECTED_ENHANCED_FILES.contains(&relative_text.as_str()) { + needs_enhanced = true; + } else if relative + .file_name() + .and_then(|name| name.to_str()) + .is_some_and(|name| UWP_DUMP_WHITELIST.contains(&name)) + { + needs_dump = true; + } else { + return Err(format!( + "Legacy Enhanced state contains a path outside the managed payload: {relative_text}." + )); + } + } + + let enhanced_hashes = needs_enhanced + .then(|| verified_cached_enhanced_hashes(storage_dir, enhanced_archive)) + .transpose()? + .unwrap_or_default(); + let dump_hashes = if needs_dump { + let source = dump_source.ok_or_else(|| { + "Legacy Enhanced ownership cannot be verified because the original dump source is unavailable." + .to_string() + })?; + verified_dump_member_hashes(source)? + } else { + BTreeMap::new() + }; + + let mut created = BTreeSet::new(); + let mut owned_backups = BTreeMap::new(); + let mut installed_hashes = BTreeMap::new(); + let mut original_backup_hashes = BTreeMap::new(); + let mut canonical_targets = HashSet::new(); + for relative_text in &tracked { + let (relative, _) = normalized_legacy_path(relative_text)?; + let expected = enhanced_hashes + .get(relative_text) + .or_else(|| dump_hashes.get(relative_text)) + .ok_or_else(|| { + format!( + "Legacy Enhanced ownership cannot be verified because {relative_text} is absent from its provenance source." + ) + })?; + let target = contained_target(game_dir, &relative)?; + let metadata = fs::symlink_metadata(&target).map_err(|_| { + format!( + "Legacy Enhanced ownership cannot be verified because the managed target is missing: {}.", + target.display() + ) + })?; + if metadata.file_type().is_symlink() || !metadata.is_file() { + return Err(format!( + "Legacy Enhanced ownership cannot be verified because the managed target is unsafe: {}.", + target.display() + )); + } + let canonical_target = canonical_game_key(&target)?; + if !canonical_targets.insert(canonical_target) { + return Err(format!( + "Legacy Enhanced state refers to the same target more than once: {relative_text}." + )); + } + let actual = fs_ops::sha256_file(&target)?; + if !actual.eq_ignore_ascii_case(expected) { + return Err(format!( + "Legacy Enhanced target does not match its verified payload source: {}.", + target.display() + )); + } + installed_hashes.insert(relative_text.clone(), actual); + + match legacy_backup_for_target(game_dir, &target, relative_text)? { + Some((backup, hash)) => { + owned_backups.insert(relative_text.clone(), backup); + original_backup_hashes.insert(relative_text.clone(), hash); + } + None if relative + .file_name() + .and_then(|name| name.to_str()) + .is_some_and(|name| name.eq_ignore_ascii_case("BlackOps3.exe")) => + { + return Err( + "Legacy Enhanced ownership cannot be adopted because BlackOps3.exe has no original backup." + .into(), + ); + } + None => { + created.insert(relative_text.clone()); + } + } + } + + let mut dump_only = BTreeSet::new(); + for value in &persisted.dump_only_files { + let (_, normalized) = normalized_legacy_path(value)?; + dump_only.insert(normalized); + } + persisted.game_directory = Some(canonical_game_key(game_dir)?); + persisted.installed_files = tracked.into_iter().collect(); + persisted.dump_only_files = dump_only.into_iter().collect(); + persisted.created_files = created.into_iter().collect(); + persisted.owned_backups = owned_backups; + persisted.installed_hashes = installed_hashes; + persisted.original_backup_hashes = original_backup_hashes; + save_state(storage_dir, &persisted)?; + Ok(true) +} + +fn private_dir(parent: &Path, label: &str) -> Result { + for index in 1..1000 { + let candidate = parent.join(format!(".patchops-{label}-{}-{index}", std::process::id())); + match fs::create_dir(&candidate) { + Ok(()) => return Ok(candidate), + Err(error) if error.kind() == io::ErrorKind::AlreadyExists => continue, + Err(error) => return Err(error.to_string()), + } + } + Err(format!("Could not allocate a private {label} directory.")) +} + +fn regular_file_without_links(path: &Path) -> bool { + fs::symlink_metadata(path) + .is_ok_and(|metadata| metadata.is_file() && !metadata.file_type().is_symlink()) +} + +fn allocate_owned_backup(game_dir: &Path, target: &Path) -> Result<(PathBuf, String), String> { + let name = target + .file_name() + .and_then(|name| name.to_str()) + .ok_or_else(|| "Invalid Enhanced backup filename.".to_string())?; + for index in 1..1000 { + let backup = target.with_file_name(format!( + "{name}.patchops.original-{}-{index}.bak", + std::process::id() + )); + match fs::symlink_metadata(&backup) { + Ok(_) => continue, + Err(error) if error.kind() == io::ErrorKind::NotFound => {} + Err(error) => return Err(error.to_string()), + } + let relative = backup + .strip_prefix(game_dir) + .map_err(|_| "Enhanced backup escaped the game directory.".to_string())? + .to_path_buf(); + if !safe_relative(&relative) { + return Err("Enhanced backup path was unsafe.".into()); + } + return Ok((backup, relative_string(&relative))); + } + Err(format!( + "Could not allocate a unique backup for {}.", + target.display() + )) +} + +fn exact_owned_backup( + game_dir: &Path, + state: &PersistedState, + relative_text: &str, +) -> Result, String> { + let Some(stored) = state.owned_backups.get(relative_text) else { + return Ok(None); + }; + let relative = PathBuf::from(stored); + if relative.as_os_str().is_empty() || !safe_relative(&relative) || stored == relative_text { + return Err(format!( + "Enhanced backup manifest is invalid for {relative_text}." + )); + } + let backup = contained_target(game_dir, &relative)?; + if !regular_file_without_links(&backup) { + return Err(format!( + "The exact PatchOpsIII backup for {relative_text} is missing or unsafe." + )); + } + let expected = state + .original_backup_hashes + .get(relative_text) + .ok_or_else(|| { + format!( + "No original-backup checksum is recorded for {relative_text}; refusing to restore it." + ) + })?; + let actual = fs_ops::sha256_file(&backup)?; + if !actual.eq_ignore_ascii_case(expected) { + return Err(format!( + "The exact PatchOpsIII backup for {relative_text} was modified or corrupted; refusing to restore it." + )); + } + Ok(Some(backup)) +} + +fn stage_members( + source_archive: Option<&Path>, + members: &[PayloadMember], + destination: &Path, +) -> Result<(), String> { + let mut archive = source_archive + .map(|path| { + zip::ZipArchive::new(File::open(path).map_err(|error| error.to_string())?) + .map_err(|error| error.to_string()) + }) + .transpose()?; + for member in members { + if !safe_relative(&member.relative) { + return Err(format!( + "Refusing unsafe payload path: {}", + member.relative.display() + )); + } + let output = destination.join(&member.relative); + if let Some(parent) = output.parent() { + fs::create_dir_all(parent).map_err(|error| error.to_string())?; + } + match &member.source { + MemberSource::File(source) => { + fs::copy(source, &output).map_err(|error| error.to_string())?; + } + MemberSource::Zip(index) => { + let mut entry = archive + .as_mut() + .ok_or_else(|| "Missing source archive.".to_string())? + .by_index(*index) + .map_err(|error| error.to_string())?; + let mut output = File::create(&output).map_err(|error| error.to_string())?; + io::copy(&mut entry, &mut output).map_err(|error| error.to_string())?; + } + } + } + Ok(()) +} + +fn relative_string(path: &Path) -> String { + path.to_string_lossy().replace('\\', "/") +} + +fn rollback_install(applied: &mut Vec) -> Result<(), String> { + let mut errors = Vec::new(); + for file in applied.drain(..).rev() { + if file.target.exists() + && let Err(error) = fs::remove_file(&file.target) + { + errors.push(format!("{}: {error}", file.target.display())); + continue; + } + let restore = file + .rollback_copy + .as_ref() + .filter(|path| path.exists()) + .or_else(|| { + file.new_permanent_backup + .as_ref() + .filter(|path| path.exists()) + }); + if let Some(restore) = restore + && let Err(error) = fs::rename(restore, &file.target) + { + errors.push(format!("{}: {error}", file.target.display())); + } + } + if errors.is_empty() { + Ok(()) + } else { + Err(errors.join("; ")) + } +} + +fn install_files( + game_dir: &Path, + enhanced_archive: &Path, + dump_source: &Path, + storage_dir: &Path, +) -> Result<(), String> { + if !game_dir.is_dir() { + return Err("Invalid game directory for Enhanced install.".into()); + } + let game_key = canonical_game_key(game_dir)?; + verify_cached_checksum(storage_dir, enhanced_archive)?; + let enhanced_archive_hash = fs_ops::sha256_file(enhanced_archive)?; + let dump = dump_members(dump_source)?; + let enhanced = enhanced_members(enhanced_archive)?; + let stage = private_dir(game_dir, "enhanced-stage")?; + let rollback = match private_dir(game_dir, "enhanced-rollback") { + Ok(path) => path, + Err(error) => { + let _ = fs::remove_dir_all(&stage); + return Err(error); + } + }; + + let mut applied = Vec::new(); + let result = (|| { + stage_members( + (!dump_source.is_dir()).then_some(dump_source), + &dump, + &stage, + )?; + stage_members(Some(enhanced_archive), &enhanced, &stage)?; + if !fs_ops::sha256_file(enhanced_archive)?.eq_ignore_ascii_case(&enhanced_archive_hash) { + return Err("Enhanced archive changed while it was being staged.".into()); + } + + let loaded = load_state(storage_dir); + let mut persisted = if loaded.game_directory.as_deref() == Some(game_key.as_str()) { + loaded + } else if state_has_ownership(&loaded) { + return Err( + "Enhanced ownership state belongs to a different or unbound game directory; refusing to reuse it." + .into(), + ); + } else { + PersistedState::default() + }; + persisted.game_directory = Some(game_key.clone()); + let prior_tracked: HashSet = persisted.installed_files.iter().cloned().collect(); + let mut installed: BTreeSet = persisted.installed_files.drain(..).collect(); + let mut dump_files: BTreeSet = persisted.dump_only_files.drain(..).collect(); + let mut created: BTreeSet = persisted.created_files.drain(..).collect(); + let mut payload = Vec::new(); + for member in &dump { + let path = relative_string(&member.relative); + dump_files.insert(path.clone()); + payload.push((member.relative.clone(), path)); + } + for member in &enhanced { + let path = relative_string(&member.relative); + payload.push((member.relative.clone(), path)); + } + + for (relative, relative_text) in &payload { + let target = contained_target(game_dir, relative)?; + if prior_tracked.contains(relative_text) + && persisted.owned_backups.contains_key(relative_text) + { + exact_owned_backup(game_dir, &persisted, relative_text)?; + } + if prior_tracked.contains(relative_text) + && !persisted.owned_backups.contains_key(relative_text) + && !created.contains(relative_text) + { + return Err(format!( + "Enhanced ownership data is incomplete for {}; refusing to overwrite it.", + target.display() + )); + } + match fs::symlink_metadata(&target) { + Ok(metadata) if metadata.file_type().is_symlink() || !metadata.is_file() => { + return Err(format!( + "Refusing to replace an unsafe Enhanced target: {}", + target.display() + )); + } + Ok(_) if prior_tracked.contains(relative_text) => { + if let Some(expected) = persisted.installed_hashes.get(relative_text) { + let actual = fs_ops::sha256_file(&target)?; + if !actual.eq_ignore_ascii_case(expected) { + return Err(format!( + "Enhanced target was modified since installation; refusing to overwrite {}.", + target.display() + )); + } + } else { + return Err(format!( + "Enhanced ownership data is incomplete for {}; refusing to overwrite it.", + target.display() + )); + } + } + Ok(_) => {} + Err(error) if error.kind() == io::ErrorKind::NotFound => {} + Err(error) => return Err(error.to_string()), + } + } + + for (relative, relative_text) in payload { + if !safe_relative(&relative) { + return Err(format!("Refusing unsafe payload path: {relative_text}")); + } + let target = contained_target(game_dir, &relative)?; + if let Some(parent) = target.parent() { + fs::create_dir_all(parent).map_err(|error| error.to_string())?; + } + let existed_before = regular_file_without_links(&target); + let mut new_permanent_backup = None; + if existed_before + && !persisted.owned_backups.contains_key(&relative_text) + && !created.contains(&relative_text) + { + let (backup, backup_relative) = allocate_owned_backup(game_dir, &target)?; + fs::rename(&target, &backup).map_err(|error| error.to_string())?; + persisted + .owned_backups + .insert(relative_text.clone(), backup_relative); + new_permanent_backup = Some(backup); + } else if !prior_tracked.contains(&relative_text) && !existed_before { + created.insert(relative_text.clone()); + } + + applied.push(AppliedFile { + target: target.clone(), + rollback_copy: None, + new_permanent_backup, + }); + if let Some(backup) = applied + .last() + .and_then(|file| file.new_permanent_backup.as_ref()) + { + persisted + .original_backup_hashes + .insert(relative_text.clone(), fs_ops::sha256_file(backup)?); + } + if target.is_file() { + let path = rollback.join(&relative); + if let Some(parent) = path.parent() { + fs::create_dir_all(parent).map_err(|error| error.to_string())?; + } + fs::rename(&target, &path).map_err(|error| error.to_string())?; + applied.last_mut().expect("applied entry").rollback_copy = Some(path); + } + fs::rename(stage.join(&relative), &target).map_err(|error| error.to_string())?; + persisted + .installed_hashes + .insert(relative_text.clone(), fs_ops::sha256_file(&target)?); + installed.insert(relative_text); + } + + persisted.installed = !installed.is_empty(); + persisted.detected_at.get_or_insert_with(utc_timestamp); + persisted.installed_files = installed.into_iter().collect(); + persisted.dump_only_files = dump_files.into_iter().collect(); + persisted.created_files = created.into_iter().collect(); + save_state(storage_dir, &persisted)?; + Ok(()) + })(); + + let mut remove_rollback = true; + let result = match result { + Ok(()) => Ok(()), + Err(error) => match rollback_install(&mut applied) { + Ok(()) => Err(error), + Err(rollback_error) => { + remove_rollback = false; + Err(format!( + "{error} Rollback also failed: {rollback_error}. Recovery files were preserved at {}", + rollback.display() + )) + } + }, + }; + let _ = fs::remove_dir_all(&stage); + if remove_rollback { + let _ = fs::remove_dir_all(&rollback); + } + result +} + +fn apply_uninstall_plan( + plan: PlannedUninstall, + transaction: &Path, + index: usize, +) -> Result { + let displaced = if plan.target.exists() { + let path = transaction.join(index.to_string()); + fs::rename(&plan.target, &path).map_err(|error| error.to_string())?; + Some(path) + } else { + None + }; + + if let UninstallAction::Restore(backup) = &plan.action + && let Err(error) = fs::rename(backup, &plan.target) + { + return if let Some(displaced) = displaced { + match fs::rename(&displaced, &plan.target) { + Ok(()) => Err(error.to_string()), + Err(rollback) => Err(format!( + "{error}; restoring the managed file also failed: {rollback}" + )), + } + } else { + Err(error.to_string()) + }; + } + + Ok(AppliedUninstall { plan, displaced }) +} + +fn rollback_uninstall(applied: &mut Vec) -> Result<(), String> { + let mut errors = Vec::new(); + for item in applied.drain(..).rev() { + if let UninstallAction::Restore(backup) = &item.plan.action + && item.plan.target.exists() + && let Err(error) = fs::rename(&item.plan.target, backup) + { + errors.push(format!("{}: {error}", backup.display())); + continue; + } + if let Some(displaced) = item.displaced + && let Err(error) = fs::rename(displaced, &item.plan.target) + { + errors.push(format!("{}: {error}", item.plan.target.display())); + } + } + if errors.is_empty() { + Ok(()) + } else { + Err(errors.join("; ")) + } +} + +impl EnhancedUninstallTransaction { + fn rollback(mut self) -> Result<(), String> { + let mut errors = Vec::new(); + if let Err(error) = rollback_uninstall(&mut self.applied) { + errors.push(error); + } + if let Err(error) = save_state(&self.storage_dir, &self.original_state) { + errors.push(format!("ownership-state rollback failed: {error}")); + } + if errors.is_empty() { + if let Some(transaction) = self.transaction { + let _ = fs::remove_dir_all(transaction); + } + Ok(()) + } else { + Err(errors.join("; ")) + } + } + + fn commit(self) -> Result<(), String> { + match self.transaction { + Some(transaction) => fs::remove_dir_all(&transaction) + .map_err(|error| format!("{}: {error}", transaction.display())), + None => Ok(()), + } + } +} + +fn begin_uninstall_files( + game_dir: &Path, + storage_dir: &Path, +) -> Result { + if !game_dir.is_dir() { + return Err("Invalid game directory for Enhanced uninstall.".into()); + } + let game_key = canonical_game_key(game_dir)?; + let original_state = load_state(storage_dir); + match original_state.game_directory.as_deref() { + Some(bound) if bound == game_key => {} + Some(_) => { + return Err( + "Enhanced ownership state belongs to a different game directory; refusing cleanup." + .into(), + ); + } + None if state_has_ownership(&original_state) => { + return Err( + "Enhanced ownership state is not bound to a game directory; refusing cleanup." + .into(), + ); + } + None => {} + } + let mut persisted = original_state.clone(); + let tracked: BTreeSet = persisted.installed_files.iter().cloned().collect(); + if tracked.is_empty() { + if persisted.installed || detect_install(game_dir) { + return Err( + "Enhanced files were detected without an ownership manifest; refusing unsafe cleanup." + .into(), + ); + } + return Ok(EnhancedUninstallTransaction { + applied: Vec::new(), + transaction: None, + storage_dir: storage_dir.to_path_buf(), + original_state, + }); + } + let created: HashSet = persisted.created_files.iter().cloned().collect(); + let mut plans = Vec::new(); + + for relative_text in tracked { + let relative = PathBuf::from(&relative_text); + if relative.as_os_str().is_empty() || !safe_relative(&relative) { + return Err(format!( + "Enhanced ownership manifest contains an unsafe path: {relative_text}." + )); + } + let target = contained_target(game_dir, &relative)?; + let backup = exact_owned_backup(game_dir, &persisted, &relative_text)?; + let target_exists = match fs::symlink_metadata(&target) { + Ok(metadata) if metadata.is_file() && !metadata.file_type().is_symlink() => true, + Ok(_) => { + return Err(format!( + "Enhanced target is no longer a regular file; refusing cleanup: {}.", + target.display() + )); + } + Err(error) if error.kind() == io::ErrorKind::NotFound => false, + Err(error) => return Err(error.to_string()), + }; + if target_exists { + let expected = persisted + .installed_hashes + .get(&relative_text) + .ok_or_else(|| { + format!( + "No managed checksum is recorded for {}; refusing to overwrite or remove it.", + target.display() + ) + })?; + let actual = fs_ops::sha256_file(&target)?; + if !actual.eq_ignore_ascii_case(expected) { + return Err(format!( + "Enhanced target was modified after installation; leaving it and its backup untouched: {}.", + target.display() + )); + } + } + + match backup { + Some(backup) => plans.push(PlannedUninstall { + target, + action: UninstallAction::Restore(backup), + }), + None if created.contains(&relative_text) => { + if target_exists { + plans.push(PlannedUninstall { + target, + action: UninstallAction::Remove, + }); + } + } + None if !target_exists => { + return Err(format!( + "The original file and exact PatchOpsIII backup are both missing for {relative_text}." + )); + } + None => { + return Err(format!( + "No exact PatchOpsIII-owned backup is recorded for {}; refusing unsafe cleanup.", + target.display() + )); + } + } + } + + let transaction = private_dir(game_dir, "enhanced-uninstall")?; + let mut applied = Vec::new(); + let result = (|| { + for (index, plan) in plans.into_iter().enumerate() { + applied.push(apply_uninstall_plan(plan, &transaction, index)?); + } + persisted.installed = false; + persisted.installed_files.clear(); + persisted.dump_only_files.clear(); + persisted.created_files.clear(); + persisted.owned_backups.clear(); + persisted.installed_hashes.clear(); + persisted.original_backup_hashes.clear(); + save_state(storage_dir, &persisted) + })(); + + match result { + Ok(()) => Ok(EnhancedUninstallTransaction { + applied, + transaction: Some(transaction), + storage_dir: storage_dir.to_path_buf(), + original_state, + }), + Err(error) => match rollback_uninstall(&mut applied) { + Ok(()) => { + let state_error = save_state(storage_dir, &original_state).err(); + let _ = fs::remove_dir_all(&transaction); + Err(match state_error { + Some(rollback) => { + format!("{error} Ownership-state rollback also failed: {rollback}") + } + None => error, + }) + } + Err(rollback) => Err(format!( + "{error} Rollback also failed: {rollback}. Recovery files were preserved at {}", + transaction.display() + )), + }, + } +} + +fn uninstall_files(game_dir: &Path, storage_dir: &Path) -> Result<(), String> { + begin_uninstall_files(game_dir, storage_dir)?.commit() +} + +fn expand_home(path: &Path) -> PathBuf { + let Some(text) = path.to_str() else { + return path.to_owned(); + }; + if (text == "~" || text.starts_with("~/") || text.starts_with("~\\")) + && let Some(home) = std::env::var_os("HOME").or_else(|| std::env::var_os("USERPROFILE")) + { + return PathBuf::from(home) + .join(text.trim_start_matches('~').trim_start_matches(['/', '\\'])); + } + path.to_owned() +} + +#[cfg(target_os = "linux")] +fn configure_linux(state: &AppState) -> Result<(), String> { + steam::configure_bo3_enhanced_linux(state, None) +} + +#[cfg(not(target_os = "linux"))] +fn configure_linux(_: &AppState) -> Result<(), String> { + Ok(()) +} + +#[cfg(target_os = "linux")] +fn cleanup_linux(state: &AppState) -> Result<(), String> { + steam::cleanup_bo3_enhanced_linux(state) +} + +#[cfg(not(target_os = "linux"))] +fn cleanup_linux(_: &AppState) -> Result<(), String> { + Ok(()) +} + +pub fn install(state: &AppState, game_dir: &Path, dump_source: &Path) -> Result<(), String> { + let dump_source = expand_home(dump_source); + if dump_source.as_os_str().is_empty() || !dump_source.exists() { + return Err("The selected dump source does not exist.".into()); + } + if !validate_dump_source(&dump_source)? { + return Err("The dump source is missing required files.".into()); + } + let mut settings = state.load_settings(); + settings.enhanced_dump_source = Some(dump_source.display().to_string()); + state.save_settings(&settings)?; + + let cached_archive = state.mod_files_dir().join(ENHANCED_ARCHIVE_NAME); + if adopt_legacy_state( + game_dir, + &cached_archive, + Some(&dump_source), + state.data_dir(), + )? { + state.log( + "Info", + "Adopted verified legacy BO3 Enhanced ownership before updating the cached release.", + ); + } + + state.log("Info", "Fetching latest BO3 Enhanced release..."); + let archive = download_latest(state)?; + state.log("Info", "Installing BO3 Enhanced files..."); + install_files(game_dir, &archive, &dump_source, state.data_dir())?; + if let Err(error) = configure_linux(state) { + let rollback = uninstall_files(game_dir, state.data_dir()).err(); + return Err(match rollback { + Some(rollback) => format!( + "Linux compatibility setup failed: {error} File rollback also failed: {rollback}" + ), + None => format!("Linux compatibility setup failed: {error}"), + }); + } + + if let Some(executable) = exe::find_executable(game_dir) { + let hash = fs_ops::sha256_file(&executable)?; + exe::record_enhanced_hash(state, game_dir, &hash)?; + } + if let Err(error) = exe::write_variant(game_dir, exe::ENHANCED_EXE_ID) { + state.log( + "Warning", + format!( + "Enhanced was installed, but its EXE variant marker could not be saved: {error}" + ), + ); + } + state.log("Success", "Installed BO3 Enhanced successfully."); + Ok(()) +} + +pub fn uninstall(state: &AppState, game_dir: &Path) -> Result<(), String> { + let dump_source = state + .load_settings() + .enhanced_dump_source + .map(|source| expand_home(Path::new(&source))); + let cached_archive = state.mod_files_dir().join(ENHANCED_ARCHIVE_NAME); + if adopt_legacy_state( + game_dir, + &cached_archive, + dump_source.as_deref(), + state.data_dir(), + )? { + state.log("Info", "Adopted verified legacy BO3 Enhanced ownership."); + } + let transaction = begin_uninstall_files(game_dir, state.data_dir())?; + if let Err(error) = cleanup_linux(state) { + return Err(match transaction.rollback() { + Ok(()) => error, + Err(rollback) => format!( + "Linux compatibility cleanup failed: {error} Enhanced file rollback also failed: {rollback}" + ), + }); + } + transaction.commit()?; + if let Err(error) = exe::write_variant(game_dir, "default") { + state.log( + "Warning", + format!("Enhanced was removed, but its EXE variant marker could not be saved: {error}"), + ); + } + state.log("Success", "Uninstalled BO3 Enhanced successfully."); + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::{ + io::Write, + sync::atomic::{AtomicUsize, Ordering}, + }; + use zip::write::SimpleFileOptions; + + static NEXT: AtomicUsize = AtomicUsize::new(0); + + fn temp_dir(label: &str) -> PathBuf { + let path = std::env::temp_dir().join(format!( + "patchops-enhanced-{label}-{}-{}", + std::process::id(), + NEXT.fetch_add(1, Ordering::Relaxed) + )); + fs::create_dir_all(&path).unwrap(); + path + } + + fn write_zip(path: &Path, files: &[(&str, &[u8])]) { + let file = File::create(path).unwrap(); + let mut archive = zip::ZipWriter::new(file); + for (name, body) in files { + archive + .start_file(*name, SimpleFileOptions::default()) + .unwrap(); + archive.write_all(body).unwrap(); + } + archive.finish().unwrap(); + } + + fn enhanced_zip(path: &Path) { + write_zip( + path, + &EXPECTED_ENHANCED_FILES + .iter() + .map(|name| (*name, b"enhanced".as_slice())) + .collect::>(), + ); + } + + fn enhanced_zip_with_body(path: &Path, body: &'static [u8]) { + write_zip( + path, + &EXPECTED_ENHANCED_FILES + .iter() + .map(|name| (*name, body)) + .collect::>(), + ); + } + + fn record_cached_checksum(storage: &Path, archive: &Path) { + let mut checksums = BTreeMap::new(); + checksums.insert( + archive.file_name().unwrap().to_string_lossy().into_owned(), + fs_ops::sha256_file(archive).unwrap(), + ); + save_checksums(storage, &checksums).unwrap(); + } + + #[test] + fn adopts_verified_legacy_state_before_upgrade_and_restores_originals() { + let root = temp_dir("legacy-adoption-roundtrip"); + let game = root.join("game"); + let storage = root.join("storage"); + fs::create_dir_all(&game).unwrap(); + fs::create_dir_all(&storage).unwrap(); + + let enhanced = root.join(ENHANCED_ARCHIVE_NAME); + enhanced_zip_with_body(&enhanced, b"legacy enhanced"); + record_cached_checksum(&storage, &enhanced); + let dump = root.join("dump.zip"); + write_zip( + &dump, + &[ + ("DUMP/BlackOps3.exe", b"legacy dump exe"), + ("DUMP/MicrosoftGame.config", b"legacy dump config"), + ("DUMP/GameChat2.dll", b"legacy dump dll"), + ], + ); + + for name in EXPECTED_ENHANCED_FILES { + fs::write(game.join(name), b"legacy enhanced").unwrap(); + } + fs::write(game.join("BlackOps3.exe"), b"legacy dump exe").unwrap(); + fs::write(game.join("BlackOps3.exe.patchops.bak"), b"original exe").unwrap(); + fs::write(game.join("MicrosoftGame.config"), b"legacy dump config").unwrap(); + fs::write(game.join("MicrosoftGame.config.bak"), b"original config").unwrap(); + fs::write(game.join("GameChat2.dll"), b"legacy dump dll").unwrap(); + + let mut legacy_files = EXPECTED_ENHANCED_FILES + .iter() + .map(|name| Value::String((*name).to_owned())) + .collect::>(); + legacy_files.extend([ + Value::String("BlackOps3.exe".into()), + Value::String("MicrosoftGame.config".into()), + Value::String("GameChat2.dll".into()), + ]); + let legacy = serde_json::json!({ + "installed": true, + "detected_at": "2025-01-02T03:04:05Z", + "installed_files": legacy_files, + "dump_only_files": ["GameChat2.dll"], + "legacy_custom": {"keep": true} + }); + fs::write( + state_path(&storage), + serde_json::to_vec_pretty(&legacy).unwrap(), + ) + .unwrap(); + + assert!(adopt_legacy_state(&game, &enhanced, Some(&dump), &storage).unwrap()); + let adopted = load_state(&storage); + assert!(state_belongs_to_game(&adopted, &game)); + assert_eq!( + adopted.extra.get("legacy_custom"), + Some(&serde_json::json!({"keep": true})) + ); + assert!( + adopted + .created_files + .contains(&"T7WSBootstrapper.dll".into()) + ); + assert!(adopted.created_files.contains(&"GameChat2.dll".into())); + assert!(!adopted.created_files.contains(&"BlackOps3.exe".into())); + assert_eq!( + adopted.owned_backups.get("BlackOps3.exe"), + Some(&"BlackOps3.exe.patchops.bak".into()) + ); + assert_eq!( + adopted.owned_backups.get("MicrosoftGame.config"), + Some(&"MicrosoftGame.config.bak".into()) + ); + assert_eq!(adopted.installed_hashes.len(), 7); + assert_eq!(adopted.original_backup_hashes.len(), 2); + + enhanced_zip_with_body(&enhanced, b"current enhanced"); + record_cached_checksum(&storage, &enhanced); + install_files(&game, &enhanced, &dump, &storage).unwrap(); + let upgraded = load_state(&storage); + for name in EXPECTED_ENHANCED_FILES { + assert!(upgraded.created_files.contains(&name.to_owned())); + assert!(!upgraded.owned_backups.contains_key(name)); + assert_eq!(fs::read(game.join(name)).unwrap(), b"current enhanced"); + } + + uninstall_files(&game, &storage).unwrap(); + assert_eq!( + fs::read(game.join("BlackOps3.exe")).unwrap(), + b"original exe" + ); + assert_eq!( + fs::read(game.join("MicrosoftGame.config")).unwrap(), + b"original config" + ); + assert!(!game.join("GameChat2.dll").exists()); + assert!( + EXPECTED_ENHANCED_FILES + .iter() + .all(|name| !game.join(name).exists()) + ); + assert_eq!( + load_state(&storage).extra.get("legacy_custom"), + Some(&serde_json::json!({"keep": true})) + ); + fs::remove_dir_all(root).unwrap(); + } + + #[test] + fn legacy_adoption_requires_a_verified_cached_archive_without_mutating_state() { + let root = temp_dir("legacy-cache-provenance"); + let game = root.join("game"); + let storage = root.join("storage"); + fs::create_dir_all(&game).unwrap(); + fs::create_dir_all(&storage).unwrap(); + let enhanced = root.join(ENHANCED_ARCHIVE_NAME); + enhanced_zip(&enhanced); + for name in EXPECTED_ENHANCED_FILES { + fs::write(game.join(name), b"enhanced").unwrap(); + } + let legacy = serde_json::json!({ + "installed": true, + "installed_files": EXPECTED_ENHANCED_FILES, + "legacy_custom": "unchanged" + }); + let original = serde_json::to_vec_pretty(&legacy).unwrap(); + fs::write(state_path(&storage), &original).unwrap(); + + let error = adopt_legacy_state(&game, &enhanced, None, &storage).unwrap_err(); + assert!(error.contains("no recorded SHA-256 checksum")); + assert_eq!(fs::read(state_path(&storage)).unwrap(), original); + fs::remove_dir_all(root).unwrap(); + } + + #[test] + fn legacy_status_binding_preserves_metadata_and_cannot_follow_another_game() { + let root = temp_dir("legacy-status-binding"); + let game = root.join("game"); + let other = root.join("other"); + let storage = root.join("storage"); + fs::create_dir_all(&storage).unwrap(); + for directory in [&game, &other] { + fs::create_dir_all(directory).unwrap(); + for name in EXPECTED_ENHANCED_FILES { + fs::write(directory.join(name), b"legacy enhanced").unwrap(); + } + } + let legacy = serde_json::json!({"installed": true, + "installed_files": EXPECTED_ENHANCED_FILES, + "detected_at": "2026-09-10T12:00:00Z", "acknowledged_at": "2026-09-10T13:00:00Z"}); + let original = serde_json::to_vec(&legacy).unwrap(); + fs::write(state_path(&storage), &original).unwrap(); + let binding = Mutex::new(None); + let (result, error) = status_at(&storage, &binding, Some(&game), None, String::new()); + assert!(error.is_none()); + assert_eq!(result.files_installed, 4); + assert_eq!(result.backup_status, "Created"); + assert_eq!(result.detected_at.as_deref(), Some("2026-09-10T12:00:00Z")); + assert_eq!( + result.acknowledged_at.as_deref(), + Some("2026-09-10T13:00:00Z") + ); + assert_eq!( + binding.lock().unwrap().as_deref(), + Some(canonical_game_key(&game).unwrap().as_str()) + ); + let (result, _) = status_at(&storage, &binding, Some(&other), None, String::new()); + assert_eq!(result.files_installed, 0); + assert_eq!(fs::read(state_path(&storage)).unwrap(), original); + assert!(!state_has_owned_install(&load_state(&storage), &game)); + fs::remove_dir_all(root).unwrap(); + } + + #[test] + fn legacy_status_does_not_bind_missing_unsafe_or_hash_mismatched_files() { + let root = temp_dir("legacy-status-refusal"); + let game = root.join("game"); + fs::create_dir_all(&game).unwrap(); + let mut stored = PersistedState { + installed: true, + installed_files: EXPECTED_ENHANCED_FILES + .iter() + .map(|name| (*name).into()) + .collect(), + ..Default::default() + }; + assert!(!legacy_status_files_match(&stored, &game)); + for name in EXPECTED_ENHANCED_FILES { + fs::write(game.join(name), b"legacy enhanced").unwrap(); + } + assert!(legacy_status_files_match(&stored, &game)); + stored.installed_files.push("../foreign.dll".into()); + assert!(!legacy_status_files_match(&stored, &game)); + stored.installed_files.pop(); + stored + .installed_hashes + .insert(EXPECTED_ENHANCED_FILES[0].into(), "a".repeat(64)); + assert!(!legacy_status_files_match(&stored, &game)); + #[cfg(unix)] + { + stored.installed_hashes.clear(); + let path = game.join(EXPECTED_ENHANCED_FILES[0]); + fs::remove_file(&path).unwrap(); + std::os::unix::fs::symlink(game.join(EXPECTED_ENHANCED_FILES[1]), path).unwrap(); + assert!(!legacy_status_files_match(&stored, &game)); + } + fs::remove_dir_all(root).unwrap(); + } + + #[test] + fn legacy_marker_without_tracked_files_remains_unowned() { + let root = temp_dir("legacy-marker-only"); + let game = root.join("game"); + let storage = root.join("storage"); + fs::create_dir_all(&game).unwrap(); + fs::create_dir_all(&storage).unwrap(); + for name in EXPECTED_ENHANCED_FILES { + fs::write(game.join(name), b"enhanced").unwrap(); + } + let legacy = serde_json::json!({ + "installed": true, + "legacy_custom": "unchanged" + }); + let original = serde_json::to_vec_pretty(&legacy).unwrap(); + fs::write(state_path(&storage), &original).unwrap(); + + let error = adopt_legacy_state(&game, &root.join(ENHANCED_ARCHIVE_NAME), None, &storage) + .unwrap_err(); + assert!(error.contains("no managed files were recorded")); + assert_eq!(fs::read(state_path(&storage)).unwrap(), original); + fs::remove_dir_all(root).unwrap(); + } + + #[test] + fn legacy_adoption_rejects_dump_mismatch_without_mutating_state() { + let root = temp_dir("legacy-dump-mismatch"); + let game = root.join("game"); + let storage = root.join("storage"); + fs::create_dir_all(&game).unwrap(); + fs::create_dir_all(&storage).unwrap(); + fs::write(game.join("BlackOps3.exe"), b"modified dump exe").unwrap(); + fs::write(game.join("BlackOps3.exe.patchops.bak"), b"original exe").unwrap(); + let dump = root.join("dump.zip"); + write_zip( + &dump, + &[ + ("DUMP/BlackOps3.exe", b"verified dump exe"), + ("DUMP/MicrosoftGame.config", b"verified config"), + ], + ); + let legacy = serde_json::json!({ + "installed": false, + "dump_only_files": ["BlackOps3.exe"], + "legacy_custom": 42 + }); + let original = serde_json::to_vec_pretty(&legacy).unwrap(); + fs::write(state_path(&storage), &original).unwrap(); + + let error = adopt_legacy_state( + &game, + &root.join(ENHANCED_ARCHIVE_NAME), + Some(&dump), + &storage, + ) + .unwrap_err(); + assert!(error.contains("does not match its verified payload source")); + assert_eq!(fs::read(state_path(&storage)).unwrap(), original); + fs::remove_dir_all(root).unwrap(); + } + + #[test] + fn legacy_adoption_rejects_missing_executable_backup_and_unsafe_backup() { + let root = temp_dir("legacy-backup-preflight"); + let game = root.join("game"); + let storage = root.join("storage"); + fs::create_dir_all(&game).unwrap(); + fs::create_dir_all(&storage).unwrap(); + fs::write(game.join("BlackOps3.exe"), b"dump exe").unwrap(); + let dump = root.join("dump.zip"); + write_zip( + &dump, + &[ + ("DUMP/BlackOps3.exe", b"dump exe"), + ("DUMP/MicrosoftGame.config", b"config"), + ], + ); + let legacy = serde_json::json!({ + "installed": false, + "dump_only_files": ["BlackOps3.exe"] + }); + let original = serde_json::to_vec_pretty(&legacy).unwrap(); + fs::write(state_path(&storage), &original).unwrap(); + + let error = adopt_legacy_state( + &game, + &root.join(ENHANCED_ARCHIVE_NAME), + Some(&dump), + &storage, + ) + .unwrap_err(); + assert!(error.contains("BlackOps3.exe has no original backup")); + assert_eq!(fs::read(state_path(&storage)).unwrap(), original); + + fs::create_dir(game.join("BlackOps3.exe.patchops.bak")).unwrap(); + let error = adopt_legacy_state( + &game, + &root.join(ENHANCED_ARCHIVE_NAME), + Some(&dump), + &storage, + ) + .unwrap_err(); + assert!(error.contains("backup is not a regular file")); + assert_eq!(fs::read(state_path(&storage)).unwrap(), original); + fs::remove_dir_all(root).unwrap(); + } + + #[test] + fn accepts_only_expected_enhanced_release_urls() { + assert!(valid_enhanced_asset_url( + "https://github.com/shiversoftdev/BO3Enhanced/releases/download/v1.15/BO3Enhanced.zip" + )); + assert!(!valid_enhanced_asset_url( + "http://github.com/shiversoftdev/BO3Enhanced/releases/download/v1.15/BO3Enhanced.zip" + )); + assert!(!valid_enhanced_asset_url( + "https://github.com.evil.invalid/shiversoftdev/BO3Enhanced/releases/download/v1.15/BO3Enhanced.zip" + )); + assert!(!valid_enhanced_asset_url( + "https://github.com/other/BO3Enhanced/releases/download/v1.15/BO3Enhanced.zip" + )); + assert!(!valid_enhanced_asset_url( + "https://github.com/shiversoftdev/BO3Enhanced/releases/download/v1.15/BO3Enhanced.7z" + )); + } + + #[test] + fn rejects_archive_path_traversal() { + let root = temp_dir("traversal"); + let archive = root.join("dump.zip"); + write_zip( + &archive, + &[ + ("DUMP/BlackOps3.exe", b"exe"), + ("DUMP/MicrosoftGame.config", b"config"), + ("../GameChat2.dll", b"escape"), + ], + ); + assert!(dump_members(&archive).is_err()); + fs::remove_dir_all(root).unwrap(); + } + + #[test] + fn combined_uninstall_restores_every_dump_file() { + let root = temp_dir("roundtrip"); + let game = root.join("game"); + let storage = root.join("storage"); + fs::create_dir_all(&game).unwrap(); + fs::create_dir_all(&storage).unwrap(); + fs::write(game.join("BlackOps3.exe"), b"original exe").unwrap(); + fs::write(game.join("MicrosoftGame.config"), b"original config").unwrap(); + + let enhanced = root.join(ENHANCED_ARCHIVE_NAME); + enhanced_zip(&enhanced); + let dump = root.join("dump.zip"); + write_zip( + &dump, + &[ + ("DUMP/BlackOps3.exe", b"dump exe"), + ("DUMP/MicrosoftGame.config", b"dump config"), + ("DUMP/GameChat2.dll", b"dump dll"), + ], + ); + + install_files(&game, &enhanced, &dump, &storage).unwrap(); + assert_eq!(fs::read(game.join("BlackOps3.exe")).unwrap(), b"dump exe"); + assert!(game.join("GameChat2.dll").is_file()); + uninstall_files(&game, &storage).unwrap(); + assert_eq!( + fs::read(game.join("BlackOps3.exe")).unwrap(), + b"original exe" + ); + assert_eq!( + fs::read(game.join("MicrosoftGame.config")).unwrap(), + b"original config" + ); + assert!(!game.join("GameChat2.dll").exists()); + assert!( + EXPECTED_ENHANCED_FILES + .iter() + .all(|name| !game.join(name).exists()) + ); + + fs::remove_dir_all(root).unwrap(); + } + + #[test] + fn uninstall_restores_exact_owned_backup_and_leaves_stale_backup_untouched() { + let root = temp_dir("owned-backup"); + let game = root.join("game"); + let storage = root.join("storage"); + fs::create_dir_all(&game).unwrap(); + fs::create_dir_all(&storage).unwrap(); + fs::write(game.join("BlackOps3.exe"), b"exact original").unwrap(); + fs::write(game.join("MicrosoftGame.config"), b"original config").unwrap(); + let stale = game.join("BlackOps3.exe.patchops.bak"); + fs::write(&stale, b"stale backup").unwrap(); + + let enhanced = root.join(ENHANCED_ARCHIVE_NAME); + enhanced_zip(&enhanced); + let dump = root.join("dump.zip"); + write_zip( + &dump, + &[ + ("DUMP/BlackOps3.exe", b"dump exe"), + ("DUMP/MicrosoftGame.config", b"dump config"), + ], + ); + + install_files(&game, &enhanced, &dump, &storage).unwrap(); + let state = load_state(&storage); + let owned = state.owned_backups.get("BlackOps3.exe").unwrap(); + assert_ne!(owned, "BlackOps3.exe.patchops.bak"); + assert_eq!(fs::read(&stale).unwrap(), b"stale backup"); + + uninstall_files(&game, &storage).unwrap(); + assert_eq!( + fs::read(game.join("BlackOps3.exe")).unwrap(), + b"exact original" + ); + assert_eq!(fs::read(&stale).unwrap(), b"stale backup"); + fs::remove_dir_all(root).unwrap(); + } + + #[test] + fn uninstall_refuses_modified_managed_file_without_consuming_backup() { + let root = temp_dir("modified-conflict"); + let game = root.join("game"); + let storage = root.join("storage"); + fs::create_dir_all(&game).unwrap(); + fs::create_dir_all(&storage).unwrap(); + fs::write(game.join("BlackOps3.exe"), b"exact original").unwrap(); + fs::write(game.join("MicrosoftGame.config"), b"original config").unwrap(); + + let enhanced = root.join(ENHANCED_ARCHIVE_NAME); + enhanced_zip(&enhanced); + let dump = root.join("dump.zip"); + write_zip( + &dump, + &[ + ("DUMP/BlackOps3.exe", b"dump exe"), + ("DUMP/MicrosoftGame.config", b"dump config"), + ], + ); + + install_files(&game, &enhanced, &dump, &storage).unwrap(); + let state = load_state(&storage); + let backup = game.join(state.owned_backups.get("BlackOps3.exe").unwrap()); + fs::write(game.join("BlackOps3.exe"), b"user modified").unwrap(); + + let error = uninstall_files(&game, &storage).unwrap_err(); + assert!(error.contains("modified after installation")); + assert_eq!( + fs::read(game.join("BlackOps3.exe")).unwrap(), + b"user modified" + ); + assert_eq!(fs::read(&backup).unwrap(), b"exact original"); + assert!(load_state(&storage).installed); + fs::remove_dir_all(root).unwrap(); + } + + #[test] + fn deferred_uninstall_can_restore_files_and_ownership_state() { + let root = temp_dir("deferred-uninstall"); + let game = root.join("game"); + let storage = root.join("storage"); + fs::create_dir_all(&game).unwrap(); + fs::create_dir_all(&storage).unwrap(); + fs::write(game.join("BlackOps3.exe"), b"original exe").unwrap(); + fs::write(game.join("MicrosoftGame.config"), b"original config").unwrap(); + let enhanced = root.join(ENHANCED_ARCHIVE_NAME); + enhanced_zip(&enhanced); + let dump = root.join("dump.zip"); + write_zip( + &dump, + &[ + ("DUMP/BlackOps3.exe", b"dump exe"), + ("DUMP/MicrosoftGame.config", b"dump config"), + ], + ); + + install_files(&game, &enhanced, &dump, &storage).unwrap(); + let transaction = begin_uninstall_files(&game, &storage).unwrap(); + assert_eq!( + fs::read(game.join("BlackOps3.exe")).unwrap(), + b"original exe" + ); + assert!(!load_state(&storage).installed); + + transaction.rollback().unwrap(); + assert_eq!(fs::read(game.join("BlackOps3.exe")).unwrap(), b"dump exe"); + let state = load_state(&storage); + assert!(state.installed); + assert!(state.owned_backups.contains_key("BlackOps3.exe")); + uninstall_files(&game, &storage).unwrap(); + fs::remove_dir_all(root).unwrap(); + } + + #[test] + fn ownership_state_is_bound_to_one_canonical_game_directory() { + let root = temp_dir("game-binding"); + let game_a = root.join("game-a"); + let game_b = root.join("game-b"); + let storage = root.join("storage"); + for game in [&game_a, &game_b] { + fs::create_dir_all(game).unwrap(); + fs::write(game.join("BlackOps3.exe"), b"original exe").unwrap(); + fs::write(game.join("MicrosoftGame.config"), b"original config").unwrap(); + } + fs::create_dir_all(&storage).unwrap(); + let enhanced = root.join(ENHANCED_ARCHIVE_NAME); + enhanced_zip(&enhanced); + let dump = root.join("dump.zip"); + write_zip( + &dump, + &[ + ("DUMP/BlackOps3.exe", b"dump exe"), + ("DUMP/MicrosoftGame.config", b"dump config"), + ], + ); + + install_files(&game_a, &enhanced, &dump, &storage).unwrap(); + let state = load_state(&storage); + assert!(state_belongs_to_game(&state, &game_a)); + assert!(!state_belongs_to_game(&state, &game_b)); + + assert!( + install_files(&game_b, &enhanced, &dump, &storage) + .unwrap_err() + .contains("different or unbound game directory") + ); + let error = match begin_uninstall_files(&game_b, &storage) { + Err(error) => error, + Ok(_) => panic!("uninstall unexpectedly accepted another game directory"), + }; + assert!(error.contains("different game directory")); + assert_eq!(fs::read(game_a.join("BlackOps3.exe")).unwrap(), b"dump exe"); + assert_eq!( + fs::read(game_b.join("BlackOps3.exe")).unwrap(), + b"original exe" + ); + uninstall_files(&game_a, &storage).unwrap(); + fs::remove_dir_all(root).unwrap(); + } + + #[test] + fn partial_owned_install_remains_eligible_for_cleanup() { + let root = temp_dir("partial-owned-install"); + let game_a = root.join("game-a"); + let game_b = root.join("game-b"); + fs::create_dir_all(&game_a).unwrap(); + fs::create_dir_all(&game_b).unwrap(); + + let state = PersistedState { + game_directory: Some(canonical_game_key(&game_a).unwrap()), + installed: false, + installed_files: vec!["T7WSBootstrapper.dll".into()], + ..PersistedState::default() + }; + assert!(state_has_owned_install(&state, &game_a)); + assert!(!state_has_owned_install(&state, &game_b)); + + fs::remove_dir_all(root).unwrap(); + } + + #[test] + fn uninstall_refuses_a_changed_exact_original_backup() { + let root = temp_dir("changed-original-backup"); + let game = root.join("game"); + let storage = root.join("storage"); + fs::create_dir_all(&game).unwrap(); + fs::create_dir_all(&storage).unwrap(); + fs::write(game.join("BlackOps3.exe"), b"original exe").unwrap(); + fs::write(game.join("MicrosoftGame.config"), b"original config").unwrap(); + let enhanced = root.join(ENHANCED_ARCHIVE_NAME); + enhanced_zip(&enhanced); + let dump = root.join("dump.zip"); + write_zip( + &dump, + &[ + ("DUMP/BlackOps3.exe", b"dump exe"), + ("DUMP/MicrosoftGame.config", b"dump config"), + ], + ); + + install_files(&game, &enhanced, &dump, &storage).unwrap(); + let state = load_state(&storage); + assert_eq!( + state.original_backup_hashes.len(), + state.owned_backups.len() + ); + let backup = game.join(state.owned_backups.get("BlackOps3.exe").unwrap()); + fs::write(&backup, b"corrupt backup").unwrap(); + + let error = uninstall_files(&game, &storage).unwrap_err(); + assert!(error.contains("modified or corrupted")); + assert_eq!(fs::read(game.join("BlackOps3.exe")).unwrap(), b"dump exe"); + assert_eq!(fs::read(backup).unwrap(), b"corrupt backup"); + assert!(load_state(&storage).installed); + fs::remove_dir_all(root).unwrap(); + } +} diff --git a/native/core/src/exe.rs b/native/core/src/exe.rs new file mode 100644 index 0000000..02bd6ac --- /dev/null +++ b/native/core/src/exe.rs @@ -0,0 +1,882 @@ +use std::{ + collections::{HashMap, HashSet}, + fs, + path::{Path, PathBuf}, + sync::{Mutex, OnceLock}, + time::SystemTime, +}; + +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +use crate::{ + app::{AppState, Settings}, + fs_ops, + models::ExeSwapState, + steam, +}; + +pub const CURRENT_EXE_ID: &str = "current"; +pub const COMPATIBLE_EXE_ID: &str = "compatible"; +pub const ENHANCED_EXE_ID: &str = "enhanced"; +pub const DEFAULT_STEAM_EXE_SHA256: &str = + "51ca63bbc660e0826943c60da67606f6bcb4b3b519528b5e0548c68c9423a323"; +pub const COMPATIBLE_BUILD_SHA256: &[&str] = + &["66b95eb4667bd5b3b3d230e7bed1d29ccd261d48ca2699f01216c863be24ff44"]; +pub const COMPATIBLE_DEPOT_MANIFEST_ID: &str = "9084453472036406216"; +pub const COMPATIBLE_DEPOT_COMMAND: &str = "download_depot 311210 311211 9084453472036406216"; +pub const COMPATIBLE_DEPOT_REQUIRED_MESSAGE: &str = "Compatible Steam depot was not found. Copy the depot command, open Steam Console, run it, then continue once the download finishes."; +pub const CURRENT_STEAM_BUILD_ID: &str = "24784313"; +pub const CURRENT_STEAM_BUILD_DATE: &str = "Sep 10, 2026"; +pub const COMPATIBLE_STEAM_BUILD_ID: &str = "10650222"; +pub const COMPATIBLE_STEAM_BUILD_DATE: &str = "Mar 3, 2023"; + +const GAME_EXECUTABLE_NAMES: [&str; 2] = ["BlackOpsIII.exe", "BlackOps3.exe"]; +const VARIANT_STATE_FILENAME: &str = ".patchops_exe_variant.json"; + +#[derive(Clone)] +struct HashCache { + length: u64, + modified: Option, + hash: String, +} + +static ACTIVE_EXE_HASH: OnceLock>> = OnceLock::new(); + +fn active_exe_hash(path: &Path) -> Result { + let metadata = path.metadata().map_err(|error| error.to_string())?; + let modified = metadata.modified().ok(); + let cache = ACTIVE_EXE_HASH.get_or_init(|| Mutex::new(HashMap::new())); + if let Ok(cached) = cache.lock() + && let Some(cached) = cached.get(path) + && modified.is_some() + && cached.length == metadata.len() + && cached.modified == modified + { + return Ok(cached.hash.clone()); + } + let hash = fs_ops::sha256_file(path)?; + if let Ok(mut cached) = cache.lock() { + if cached.len() >= 32 { + cached.clear(); + } + cached.insert( + path.to_owned(), + HashCache { + length: metadata.len(), + modified, + hash: hash.clone(), + }, + ); + } + Ok(hash) +} + +// Status probes may reuse metadata-identified hashes. Mutations always reread +// bytes, including the source, staging file and activated target. +pub type ExeHashProvider = dyn Fn(&Path) -> Result + Send + Sync; + +#[derive(Clone, Copy)] +enum HashProbe<'a> { + Cached, + Fresh, + Provider(&'a ExeHashProvider), +} + +fn hash_for_probe(path: &Path, probe: HashProbe<'_>) -> Result { + match probe { + HashProbe::Cached => active_exe_hash(path), + HashProbe::Fresh => fs_ops::sha256_file(path), + HashProbe::Provider(provider) => provider(path), + } +} + +#[derive(Clone, Debug)] +struct Integrity { + trusted: bool, + status: &'static str, + profile: &'static str, + message: String, +} + +#[derive(Serialize, Deserialize)] +struct VariantState { + variant: String, +} + +pub fn find_executable(game_dir: &Path) -> Option { + GAME_EXECUTABLE_NAMES + .iter() + .map(|name| game_dir.join(name)) + .find(|candidate| candidate.is_file()) +} + +pub fn read_variant(game_dir: &Path) -> Option { + let body = fs::read(game_dir.join(VARIANT_STATE_FILENAME)).ok()?; + serde_json::from_slice::(&body) + .ok() + .map(|state| state.variant) +} + +pub fn write_variant(game_dir: &Path, variant: &str) -> Result<(), String> { + let body = serde_json::to_vec_pretty(&VariantState { + variant: variant.to_owned(), + }) + .map_err(|error| error.to_string())?; + fs_ops::atomic_write(&game_dir.join(VARIANT_STATE_FILENAME), &body) +} + +fn known_enhanced_hashes(settings: &Settings, game_dir: Option<&Path>) -> HashSet { + let mut known = HashSet::new(); + if let Some(game_dir) = game_dir + && let Some(value) = settings + .enhanced_exe_hashes + .get(&game_dir.display().to_string()) + { + match value { + Value::String(hash) if !hash.is_empty() => { + known.insert(hash.to_ascii_lowercase()); + } + Value::Array(hashes) => { + known.extend( + hashes + .iter() + .filter_map(Value::as_str) + .filter(|hash| !hash.is_empty()) + .map(str::to_ascii_lowercase), + ); + } + _ => {} + } + } + if let Some(hash) = settings + .enhanced_exe_hash + .as_deref() + .filter(|hash| !hash.is_empty()) + { + known.insert(hash.to_ascii_lowercase()); + } + known +} + +pub fn record_enhanced_hash( + state: &AppState, + game_dir: &Path, + exe_hash: &str, +) -> Result<(), String> { + let normalized = exe_hash.to_ascii_lowercase(); + if normalized.is_empty() { + return Ok(()); + } + + let mut settings = state.load_settings(); + let key = game_dir.display().to_string(); + let mut values = match settings.enhanced_exe_hashes.remove(&key) { + Some(Value::String(value)) => vec![Value::String(value)], + Some(Value::Array(values)) => values, + _ => Vec::new(), + }; + if !values + .iter() + .filter_map(Value::as_str) + .any(|value| value.eq_ignore_ascii_case(&normalized)) + { + values.push(Value::String(normalized)); + } + if values.len() > 5 { + values.drain(..values.len() - 5); + } + settings + .enhanced_exe_hashes + .insert(key, Value::Array(values)); + state.save_settings(&settings) +} + +fn integrity_for_hash( + exe_hash: &str, + executable_exists: bool, + enhanced_active: bool, + enhanced_hash_known: bool, +) -> Integrity { + let normalized = exe_hash.to_ascii_lowercase(); + if !executable_exists { + return Integrity { + trusted: false, + status: "missing", + profile: "", + message: "BlackOps3.exe or BlackOpsIII.exe was not found.".into(), + }; + } + if normalized == DEFAULT_STEAM_EXE_SHA256 { + return Integrity { + trusted: true, + status: "trusted", + profile: CURRENT_EXE_ID, + message: format!("Executable matches latest Steam BuildID {CURRENT_STEAM_BUILD_ID}."), + }; + } + if COMPATIBLE_BUILD_SHA256.contains(&normalized.as_str()) { + return Integrity { + trusted: true, + status: "trusted", + profile: COMPATIBLE_EXE_ID, + message: format!("Executable matches compatible BuildID {COMPATIBLE_STEAM_BUILD_ID}."), + }; + } + if enhanced_active || enhanced_hash_known { + return Integrity { + trusted: true, + status: "enhanced", + profile: ENHANCED_EXE_ID, + message: "Executable is managed by BO3 Enhanced.".into(), + }; + } + let short_hash = if normalized.is_empty() { + "unreadable" + } else { + &normalized[..normalized.len().min(12)] + }; + Integrity { + trusted: false, + status: "unverified", + profile: "", + message: format!( + "Active executable hash {short_hash} does not match latest BuildID {CURRENT_STEAM_BUILD_ID}, compatible BuildID {COMPATIBLE_STEAM_BUILD_ID}, or a PatchOpsIII-preserved Enhanced EXE." + ), + } +} + +fn active_integrity(state: &AppState, game_dir: &Path, target: &Path) -> Integrity { + let hash = fs_ops::sha256_file(target) + .unwrap_or_default() + .to_ascii_lowercase(); + let settings = state.load_settings(); + integrity_for_hash( + &hash, + target.is_file(), + crate::enhanced::detect_install(game_dir), + known_enhanced_hashes(&settings, Some(game_dir)).contains(&hash), + ) +} + +fn inactive_build_path(target: &Path, build_id: &str) -> PathBuf { + target.with_file_name(format!( + "{}.{}.bak", + target + .file_stem() + .and_then(|name| name.to_str()) + .unwrap_or("BlackOps3"), + build_id + )) +} + +fn enhanced_backup_path(target: &Path) -> PathBuf { + target.with_file_name(format!( + "{}.enhanced.bak", + target + .file_stem() + .and_then(|name| name.to_str()) + .unwrap_or("BlackOps3") + )) +} + +fn sorted_matching_files(parent: &Path, prefix: &str, suffix: &str) -> Vec { + let mut matches: Vec<_> = fs::read_dir(parent) + .into_iter() + .flatten() + .flatten() + .filter_map(|entry| { + let path = entry.path(); + let name = path.file_name()?.to_str()?; + (path.is_file() && name.starts_with(prefix) && name.ends_with(suffix)).then_some(path) + }) + .collect(); + matches.sort_by_key(|path| { + std::cmp::Reverse( + path.metadata() + .and_then(|metadata| metadata.modified()) + .unwrap_or(SystemTime::UNIX_EPOCH), + ) + }); + matches +} + +fn build_backup_candidates(target: &Path, build_id: &str, role: &str) -> Vec { + let stem = target + .file_stem() + .and_then(|name| name.to_str()) + .unwrap_or("BlackOps3"); + let extension = target + .extension() + .and_then(|name| name.to_str()) + .unwrap_or(""); + let exact = inactive_build_path(target, build_id); + let old_exact = target.with_file_name(format!("{stem}.{role}-{build_id}.{extension}")); + let mut candidates = Vec::new(); + if exact.is_file() { + candidates.push(exact); + } + if old_exact.is_file() { + candidates.push(old_exact); + } + candidates.extend(sorted_matching_files( + target.parent().unwrap_or_else(|| Path::new(".")), + &format!("{stem}.{role}-{build_id}-"), + &format!(".{extension}"), + )); + candidates +} + +fn first_matching_hash( + candidates: Vec, + hashes: &[&str], + probe: HashProbe<'_>, +) -> Option { + candidates.into_iter().find(|candidate| { + hash_for_probe(candidate, probe) + .map(|hash| { + hashes + .iter() + .any(|expected| hash.eq_ignore_ascii_case(expected)) + }) + .unwrap_or(false) + }) +} + +fn validated_preserved_compatible(target: &Path, probe: HashProbe<'_>) -> Option { + first_matching_hash( + build_backup_candidates(target, COMPATIBLE_STEAM_BUILD_ID, "compatible"), + COMPATIBLE_BUILD_SHA256, + probe, + ) +} + +fn validated_latest_backup(target: &Path, probe: HashProbe<'_>) -> Option { + first_matching_hash( + build_backup_candidates(target, CURRENT_STEAM_BUILD_ID, "current"), + &[DEFAULT_STEAM_EXE_SHA256], + probe, + ) + .or_else(|| { + fs_ops::existing_backup(target).filter(|candidate| { + hash_for_probe(candidate, probe) + .map(|hash| hash.eq_ignore_ascii_case(DEFAULT_STEAM_EXE_SHA256)) + .unwrap_or(false) + }) + }) +} + +fn validated_enhanced_backup( + settings: &Settings, + game_dir: Option<&Path>, + target: &Path, + probe: HashProbe<'_>, +) -> Option { + let known = known_enhanced_hashes(settings, game_dir); + if known.is_empty() { + return None; + } + let stem = target + .file_stem() + .and_then(|name| name.to_str()) + .unwrap_or("BlackOps3"); + let exact = enhanced_backup_path(target); + let mut candidates = if exact.is_file() { + vec![exact] + } else { + Vec::new() + }; + candidates.extend(sorted_matching_files( + target.parent().unwrap_or_else(|| Path::new(".")), + &format!("{stem}.enhanced-"), + ".bak", + )); + candidates.into_iter().find(|candidate| { + hash_for_probe(candidate, probe) + .map(|hash| known.contains(&hash.to_ascii_lowercase())) + .unwrap_or(false) + }) +} + +pub fn status(settings: &Settings, game_dir: Option<&Path>) -> ExeSwapState { + status_with_hash_provider(settings, game_dir, None) +} + +/// Inject hashes for status probes only. EXE mutations always hash actual bytes. +pub fn status_with_hash_provider( + settings: &Settings, + game_dir: Option<&Path>, + provider: Option<&ExeHashProvider>, +) -> ExeSwapState { + let probe = provider.map_or(HashProbe::Cached, HashProbe::Provider); + let executable = game_dir.and_then(find_executable); + let exe_hash = executable + .as_deref() + .and_then(|path| hash_for_probe(path, probe).ok()) + .unwrap_or_default() + .to_ascii_lowercase(); + let enhanced_active = game_dir.is_some_and(crate::enhanced::detect_install); + let integrity = integrity_for_hash( + &exe_hash, + executable.is_some(), + enhanced_active, + known_enhanced_hashes(settings, game_dir).contains(&exe_hash), + ); + let mut profile = if integrity.profile.is_empty() { + game_dir.and_then(read_variant).unwrap_or_default() + } else { + integrity.profile.to_owned() + }; + let (active_build_id, active_build_date) = if executable.is_none() { + ("Unknown", "") + } else if profile == ENHANCED_EXE_ID { + ("Enhanced", "") + } else if profile == COMPATIBLE_EXE_ID { + (COMPATIBLE_STEAM_BUILD_ID, COMPATIBLE_STEAM_BUILD_DATE) + } else if profile == CURRENT_EXE_ID || profile == "default" { + profile = CURRENT_EXE_ID.into(); + (CURRENT_STEAM_BUILD_ID, CURRENT_STEAM_BUILD_DATE) + } else { + ("Unverified", "") + }; + + let latest_available = executable + .as_deref() + .is_some_and(|path| validated_latest_backup(path, probe).is_some()); + let compatible_available = executable + .as_deref() + .is_some_and(|path| validated_preserved_compatible(path, probe).is_some()); + let enhanced_available = executable + .as_deref() + .is_some_and(|path| validated_enhanced_backup(settings, game_dir, path, probe).is_some()); + let patch_label = if profile == COMPATIBLE_EXE_ID { + "T7 Patch 2.04" + } else { + "T7 Patch Scroptss/T7Patch" + }; + let mode_label = match profile.as_str() { + COMPATIBLE_EXE_ID => "Compatible EXE", + CURRENT_EXE_ID => "Current EXE", + ENHANCED_EXE_ID => "Enhanced", + _ => "Unknown", + }; + + ExeSwapState { + profile: profile.clone(), + mode_label: mode_label.into(), + patch_label: patch_label.into(), + display_label: format!("{} T7 installs will use {patch_label}.", integrity.message), + state: integrity.status.into(), + active_build_id: active_build_id.into(), + active_build_date: active_build_date.into(), + current_build_id: CURRENT_STEAM_BUILD_ID.into(), + current_build_date: CURRENT_STEAM_BUILD_DATE.into(), + compatible_build_id: COMPATIBLE_STEAM_BUILD_ID.into(), + compatible_build_date: COMPATIBLE_STEAM_BUILD_DATE.into(), + enhanced_build_id: "Enhanced".into(), + enhanced_build_date: String::new(), + executable: executable + .as_deref() + .map(|path| path.display().to_string()) + .unwrap_or_default(), + executable_name: executable + .as_deref() + .and_then(Path::file_name) + .map(|name| name.to_string_lossy().into_owned()) + .unwrap_or_default(), + executable_hash: exe_hash, + trusted_executable: integrity.trusted, + integrity_status: integrity.status.into(), + integrity_message: integrity.message, + backup_available: latest_available, + latest_available, + compatible_available, + enhanced_available: enhanced_available || profile == ENHANCED_EXE_ID, + compatible_active: profile == COMPATIBLE_EXE_ID, + enhanced_exe_active: profile == ENHANCED_EXE_ID, + enhanced_active, + } +} + +fn available_sibling_path(path: &Path) -> Result { + if !path.exists() { + return Ok(path.to_owned()); + } + let stem = path + .file_stem() + .and_then(|name| name.to_str()) + .ok_or_else(|| "Invalid executable backup filename.".to_string())?; + let extension = path + .extension() + .and_then(|name| name.to_str()) + .unwrap_or(""); + for index in 2..1000 { + let candidate = path.with_file_name(if extension.is_empty() { + format!("{stem}-{index}") + } else { + format!("{stem}-{index}.{extension}") + }); + if !candidate.exists() { + return Ok(candidate); + } + } + Err(format!( + "No available filename found near {}", + path.display() + )) +} + +fn unique_stage_path(target: &Path) -> Result { + let name = target + .file_name() + .and_then(|name| name.to_str()) + .ok_or_else(|| "Invalid executable filename.".to_string())?; + for index in 1..1000 { + let candidate = target.with_file_name(format!( + ".{name}.patchops-stage-{}-{index}", + std::process::id() + )); + if !candidate.exists() { + return Ok(candidate); + } + } + Err("Could not allocate an executable staging path.".into()) +} + +fn activate_source( + target: &Path, + source: &Path, + expected_hashes: &[&str], + preserved_active: &Path, + consume_source: bool, +) -> Result<(), String> { + let source_hash = fs_ops::sha256_file(source)?; + if !expected_hashes + .iter() + .any(|expected| source_hash.eq_ignore_ascii_case(expected)) + { + return Err(format!( + "Refusing executable with untrusted SHA-256: {}", + source.display() + )); + } + + let stage = unique_stage_path(target)?; + fs::copy(source, &stage) + .map_err(|error| format!("Failed to stage {}: {error}", source.display()))?; + let staged_hash = fs_ops::sha256_file(&stage); + if !staged_hash.is_ok_and(|hash| hash.eq_ignore_ascii_case(&source_hash)) { + let _ = fs::remove_file(&stage); + return Err("Staged executable failed SHA-256 verification.".into()); + } + + let mut active_preserved = false; + let result = (|| { + if target.exists() { + fs::rename(target, preserved_active) + .map_err(|error| format!("Failed to preserve active executable: {error}"))?; + active_preserved = true; + } + fs::rename(&stage, target) + .map_err(|error| format!("Failed to activate staged executable: {error}"))?; + let activated_hash = fs_ops::sha256_file(target)?; + if !activated_hash.eq_ignore_ascii_case(&source_hash) + || !expected_hashes + .iter() + .any(|expected| activated_hash.eq_ignore_ascii_case(expected)) + { + return Err("Activated executable failed SHA-256 verification.".into()); + } + Ok(()) + })(); + + if let Err(error) = result { + let _ = fs::remove_file(&stage); + let _ = fs::remove_file(target); + let rollback = if active_preserved { + fs::rename(preserved_active, target).map_err(|rollback| rollback.to_string()) + } else { + Ok(()) + }; + return match rollback { + Ok(()) => Err(error), + Err(rollback) => Err(format!("{error} Rollback also failed: {rollback}")), + }; + } + + if consume_source { + let _ = fs::remove_file(source); + } + Ok(()) +} + +fn preserved_path_for_profile(target: &Path, profile: &str) -> Result { + let preferred = match profile { + CURRENT_EXE_ID => inactive_build_path(target, CURRENT_STEAM_BUILD_ID), + COMPATIBLE_EXE_ID => inactive_build_path(target, COMPATIBLE_STEAM_BUILD_ID), + ENHANCED_EXE_ID => enhanced_backup_path(target), + _ => return Err("Active executable is not a recognized PatchOpsIII build.".into()), + }; + available_sibling_path(&preferred) +} + +fn prepare_activation( + state: &AppState, + game_dir: &Path, + target: &Path, + desired_profile: &str, +) -> Result, String> { + if !target.is_file() { + return Ok(Some(available_sibling_path( + &target.with_file_name(".patchops-missing-exe.rollback"), + )?)); + } + let integrity = active_integrity(state, game_dir, target); + if integrity.profile == desired_profile { + return Ok(None); + } + if !integrity.trusted || integrity.profile.is_empty() { + return Err(integrity.message); + } + if integrity.profile == ENHANCED_EXE_ID { + let hash = fs_ops::sha256_file(target)?; + record_enhanced_hash(state, game_dir, &hash)?; + } + preserved_path_for_profile(target, integrity.profile).map(Some) +} + +fn copy_depot_installscript(state: &AppState, depot: &Path, game_dir: &Path) { + let source = depot.join("installscript_311210.vdf"); + if !source.is_file() { + return; + } + let target = game_dir.join("installscript_311210.vdf"); + let backup = fs_ops::backup_path(&target); + let result = (|| { + if target.is_file() && !backup.exists() { + fs::copy(&target, &backup).map_err(|error| error.to_string())?; + } + let body = fs::read(&source).map_err(|error| error.to_string())?; + fs_ops::atomic_write(&target, &body) + })(); + if let Err(error) = result { + state.log("Warning", format!("Compatible executable is active, but installscript_311210.vdf could not be updated: {error}")); + } +} + +fn restore_installscript_backup(state: &AppState, game_dir: &Path) { + let target = game_dir.join("installscript_311210.vdf"); + let backup = fs_ops::backup_path(&target); + if !backup.is_file() { + return; + } + let result = fs::read(&backup) + .and_then(|body| fs_ops::atomic_write(&target, &body).map_err(std::io::Error::other)); + if let Err(error) = result { + state.log("Warning", format!("Current executable is active, but installscript_311210.vdf could not be restored: {error}")); + } +} + +fn remember_variant(state: &AppState, game_dir: &Path, variant: &str) { + if let Err(error) = write_variant(game_dir, variant) { + state.log( + "Warning", + format!("Executable was activated, but its variant marker could not be saved: {error}"), + ); + } +} + +fn activate_compatible_from_depot( + state: &AppState, + game_dir: &Path, + depot: Option, +) -> Result<(), String> { + let target = find_executable(game_dir) + .ok_or_else(|| "BlackOps3.exe or BlackOpsIII.exe was not found.".to_string())?; + let Some(preserved_active) = prepare_activation(state, game_dir, &target, COMPATIBLE_EXE_ID)? + else { + state.log("Info", "Compatible executable is already active."); + return Ok(()); + }; + + if let Some(source) = validated_preserved_compatible(&target, HashProbe::Fresh) { + activate_source( + &target, + &source, + COMPATIBLE_BUILD_SHA256, + &preserved_active, + true, + )?; + remember_variant(state, game_dir, COMPATIBLE_EXE_ID); + state.log( + "Success", + format!( + "Reused preserved compatible executable from {}.", + source.file_name().unwrap_or_default().to_string_lossy() + ), + ); + return Ok(()); + } + + let depot = depot.ok_or_else(|| COMPATIBLE_DEPOT_REQUIRED_MESSAGE.to_string())?; + activate_source( + &target, + &depot.join("BlackOps3.exe"), + COMPATIBLE_BUILD_SHA256, + &preserved_active, + false, + )?; + copy_depot_installscript(state, &depot, game_dir); + remember_variant(state, game_dir, COMPATIBLE_EXE_ID); + state.log( + "Success", + format!("Compatible executable installed from Steam depot {COMPATIBLE_DEPOT_MANIFEST_ID}."), + ); + Ok(()) +} + +pub fn activate_current(state: &AppState, game_dir: &Path) -> Result<(), String> { + if !game_dir.is_dir() { + return Err("Game directory is not set.".into()); + } + let target = find_executable(game_dir).unwrap_or_else(|| game_dir.join("BlackOps3.exe")); + let Some(preserved_active) = prepare_activation(state, game_dir, &target, CURRENT_EXE_ID)? + else { + state.log("Info", "Latest executable is already active."); + return Ok(()); + }; + let source = validated_latest_backup(&target, HashProbe::Fresh) + .ok_or_else(|| "No backup executable found. Cannot restore the current EXE.".to_string())?; + activate_source( + &target, + &source, + &[DEFAULT_STEAM_EXE_SHA256], + &preserved_active, + true, + )?; + restore_installscript_backup(state, game_dir); + remember_variant(state, game_dir, "default"); + state.log("Success", "Current executable restored from backup."); + Ok(()) +} + +pub fn activate_enhanced(state: &AppState, game_dir: &Path) -> Result<(), String> { + if !game_dir.is_dir() { + return Err("Game directory is not set.".into()); + } + let target = find_executable(game_dir).unwrap_or_else(|| game_dir.join("BlackOps3.exe")); + let Some(preserved_active) = prepare_activation(state, game_dir, &target, ENHANCED_EXE_ID)? + else { + state.log("Info", "Enhanced executable is already active."); + return Ok(()); + }; + let settings = state.load_settings(); + let source = validated_enhanced_backup(&settings, Some(game_dir), &target, HashProbe::Fresh) + .ok_or_else(|| "No PatchOpsIII-preserved Enhanced executable was found.".to_string())?; + let hash = fs_ops::sha256_file(&source)?; + let expected = [hash.as_str()]; + activate_source(&target, &source, &expected, &preserved_active, true)?; + record_enhanced_hash(state, game_dir, &hash)?; + remember_variant(state, game_dir, ENHANCED_EXE_ID); + state.log( + "Success", + "Enhanced executable restored from PatchOpsIII backup.", + ); + Ok(()) +} + +pub fn compatible_depot_available() -> bool { + steam::compatible_depot_download_exists() +} + +pub fn activate_compatible(state: &AppState, game_dir: &Path) -> Result<(), String> { + activate_compatible_from_depot(state, game_dir, steam::find_valid_compatible_depot()) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::atomic::{AtomicUsize, Ordering}; + + static NEXT: AtomicUsize = AtomicUsize::new(0); + + fn temp_dir(label: &str) -> PathBuf { + let path = std::env::temp_dir().join(format!( + "patchops-exe-{label}-{}-{}", + std::process::id(), + NEXT.fetch_add(1, Ordering::Relaxed) + )); + fs::create_dir_all(&path).unwrap(); + path + } + + #[test] + fn classifies_exact_supported_hashes() { + let current = integrity_for_hash(DEFAULT_STEAM_EXE_SHA256, true, false, false); + assert!(current.trusted); + assert_eq!(current.profile, CURRENT_EXE_ID); + + let compatible = integrity_for_hash(COMPATIBLE_BUILD_SHA256[0], true, false, false); + assert!(compatible.trusted); + assert_eq!(compatible.profile, COMPATIBLE_EXE_ID); + + let unknown = integrity_for_hash(&"a".repeat(64), true, false, false); + assert!(!unknown.trusted); + assert_eq!(unknown.status, "unverified"); + } + + #[test] + fn recognizes_september_2026_steam_build() { + assert_eq!( + DEFAULT_STEAM_EXE_SHA256, + "51ca63bbc660e0826943c60da67606f6bcb4b3b519528b5e0548c68c9423a323" + ); + assert_eq!(CURRENT_STEAM_BUILD_ID, "24784313"); + assert_eq!(CURRENT_STEAM_BUILD_DATE, "Sep 10, 2026"); + } + + #[test] + fn active_executable_hash_cache_invalidates_when_file_changes() { + let root = temp_dir("hash-cache"); + let executable = root.join("BlackOps3.exe"); + fs::write(&executable, b"one").unwrap(); + let first = active_exe_hash(&executable).unwrap(); + assert_eq!(active_exe_hash(&executable).unwrap(), first); + fs::write(&executable, b"a longer executable").unwrap(); + assert_ne!(active_exe_hash(&executable).unwrap(), first); + fs::remove_dir_all(root).unwrap(); + } + + #[test] + fn staged_activation_verifies_before_touching_active_exe() { + let root = temp_dir("staged"); + let target = root.join("BlackOps3.exe"); + let source = root.join("source.exe"); + let preserved = root.join("BlackOps3.21201493.bak"); + fs::write(&target, b"current").unwrap(); + fs::write(&source, b"compatible").unwrap(); + let expected = fs_ops::sha256_file(&source).unwrap(); + + activate_source(&target, &source, &[&expected], &preserved, false).unwrap(); + assert_eq!(fs::read(&target).unwrap(), b"compatible"); + assert_eq!(fs::read(&preserved).unwrap(), b"current"); + + fs::remove_dir_all(root).unwrap(); + } + + #[test] + fn hash_mismatch_leaves_active_exe_untouched() { + let root = temp_dir("mismatch"); + let target = root.join("BlackOps3.exe"); + let source = root.join("source.exe"); + let preserved = root.join("BlackOps3.21201493.bak"); + fs::write(&target, b"current").unwrap(); + fs::write(&source, b"tampered").unwrap(); + + assert!(activate_source(&target, &source, &[&"0".repeat(64)], &preserved, false).is_err()); + assert_eq!(fs::read(&target).unwrap(), b"current"); + assert!(!preserved.exists()); + + fs::remove_dir_all(root).unwrap(); + } +} diff --git a/native/core/src/fs_ops.rs b/native/core/src/fs_ops.rs new file mode 100644 index 0000000..9b39152 --- /dev/null +++ b/native/core/src/fs_ops.rs @@ -0,0 +1,671 @@ +use std::{ + collections::HashSet, + fs::{self, File, OpenOptions}, + io::{Read, Write}, + path::{Component, Path, PathBuf}, + sync::atomic::{AtomicU64, Ordering}, + time::Duration, +}; + +use flate2::read::GzDecoder; +use sha2::{Digest, Sha256}; + +pub const PATCHOPS_BACKUP_SUFFIX: &str = ".patchops.bak"; +pub const LEGACY_BACKUP_SUFFIX: &str = ".bak"; +const MAX_ARCHIVE_BYTES: u64 = 16 * 1024 * 1024 * 1024; +const MAX_ARCHIVE_ENTRIES: usize = 100_000; +static TEMP_COUNTER: AtomicU64 = AtomicU64::new(0); + +pub(crate) struct Snapshot { + entries: Vec<(PathBuf, Option)>, +} + +pub(crate) fn remove_file_if_present(path: &Path) -> Result<(), String> { + match fs::symlink_metadata(path) { + Ok(metadata) if metadata.file_type().is_symlink() || !metadata.is_file() => Err(format!( + "refusing to remove non-file path {}", + path.display() + )), + Ok(_) => fs::remove_file(path).map_err(|error| error.to_string()), + Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()), + Err(error) => Err(error.to_string()), + } +} + +impl Snapshot { + pub(crate) fn capture(targets: &[PathBuf], directory: &Path) -> Result { + fs::create_dir_all(directory).map_err(|error| error.to_string())?; + let mut seen = HashSet::new(); + let mut entries = Vec::new(); + for target in targets { + if !seen.insert(target.clone()) { + continue; + } + let backup = match fs::symlink_metadata(target) { + Ok(metadata) if metadata.is_file() && !metadata.file_type().is_symlink() => { + let backup = directory.join(entries.len().to_string()); + fs::copy(target, &backup).map_err(|error| error.to_string())?; + Some(backup) + } + Ok(_) => return Err(format!("refusing to replace {}", target.display())), + Err(error) if error.kind() == std::io::ErrorKind::NotFound => None, + Err(error) => return Err(error.to_string()), + }; + entries.push((target.clone(), backup)); + } + Ok(Self { entries }) + } + + pub(crate) fn restore(&self) -> Result<(), String> { + let mut errors = Vec::new(); + for (target, backup) in self.entries.iter().rev() { + if let Err(error) = remove_file_if_present(target) { + errors.push(error); + continue; + } + if let Some(backup) = backup { + if let Some(parent) = target.parent() + && let Err(error) = fs::create_dir_all(parent) + { + errors.push(format!("failed to recreate {}: {error}", parent.display())); + continue; + } + if let Err(error) = fs::copy(backup, target) { + errors.push(format!("failed to restore {}: {error}", target.display())); + } + } + } + if errors.is_empty() { + Ok(()) + } else { + Err(errors.join("; ")) + } + } +} + +/// Whether two paths name the same directory. Both sides are canonicalized +/// (resolving symlinks and, on Windows, 8.3 short names and the `\\?\` +/// prefix); Windows comparisons ignore ASCII case like the filesystem does. +pub fn same_directory(left: &Path, right: &Path) -> bool { + fn key(path: &Path) -> String { + let resolved = path.canonicalize().unwrap_or_else(|_| path.to_path_buf()); + let key = resolved.to_string_lossy().into_owned(); + if cfg!(windows) { + key.to_ascii_lowercase() + } else { + key + } + } + left == right || key(left) == key(right) +} + +pub fn sha256_file(path: &Path) -> Result { + let mut file = File::open(path).map_err(|error| format!("{}: {error}", path.display()))?; + let mut digest = Sha256::new(); + let mut buffer = [0_u8; 1024 * 1024]; + loop { + let read = file.read(&mut buffer).map_err(|error| error.to_string())?; + if read == 0 { + break; + } + digest.update(&buffer[..read]); + } + Ok(format!("{:x}", digest.finalize())) +} + +pub fn atomic_write(path: &Path, contents: &[u8]) -> Result<(), String> { + let parent = path + .parent() + .ok_or_else(|| format!("{} has no parent directory", path.display()))?; + fs::create_dir_all(parent).map_err(|error| error.to_string())?; + let name = path + .file_name() + .and_then(|name| name.to_str()) + .ok_or_else(|| "invalid output filename".to_string())?; + let (temporary, mut file) = (0..100) + .find_map(|_| { + let id = TEMP_COUNTER.fetch_add(1, Ordering::Relaxed); + let temporary = + parent.join(format!(".{name}.patchops-{}-{id}.tmp", std::process::id())); + match OpenOptions::new() + .create_new(true) + .write(true) + .open(&temporary) + { + Ok(file) => Some(Ok((temporary, file))), + Err(error) if error.kind() == std::io::ErrorKind::AlreadyExists => None, + Err(error) => Some(Err(error.to_string())), + } + }) + .unwrap_or_else(|| Err("could not allocate a unique temporary file".into()))?; + let permissions = path.metadata().ok().map(|metadata| metadata.permissions()); + let write_result = permissions + .map_or(Ok(()), |permissions| file.set_permissions(permissions)) + .and_then(|_| file.write_all(contents)) + .and_then(|_| file.sync_all()) + .map_err(|error| error.to_string()); + drop(file); + if let Err(error) = write_result { + let _ = fs::remove_file(&temporary); + return Err(error); + } + if let Err(error) = replace_path(&temporary, path) { + let _ = fs::remove_file(&temporary); + return Err(error); + } + Ok(()) +} + +#[cfg(not(windows))] +fn replace_path(source: &Path, destination: &Path) -> Result<(), String> { + fs::rename(source, destination).map_err(|error| error.to_string()) +} + +#[cfg(windows)] +fn replace_path(source: &Path, destination: &Path) -> Result<(), String> { + use std::os::windows::ffi::OsStrExt; + use windows_sys::Win32::Storage::FileSystem::{ + MOVEFILE_REPLACE_EXISTING, MOVEFILE_WRITE_THROUGH, MoveFileExW, + }; + + let source: Vec = source.as_os_str().encode_wide().chain(Some(0)).collect(); + let destination: Vec = destination + .as_os_str() + .encode_wide() + .chain(Some(0)) + .collect(); + let ok = unsafe { + MoveFileExW( + source.as_ptr(), + destination.as_ptr(), + MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH, + ) + }; + if ok == 0 { + Err(std::io::Error::last_os_error().to_string()) + } else { + Ok(()) + } +} + +pub fn backup_path(path: &Path) -> PathBuf { + PathBuf::from(format!("{}{}", path.display(), PATCHOPS_BACKUP_SUFFIX)) +} + +pub fn legacy_backup_path(path: &Path) -> PathBuf { + PathBuf::from(format!("{}{}", path.display(), LEGACY_BACKUP_SUFFIX)) +} + +pub fn existing_backup(path: &Path) -> Option { + [backup_path(path), legacy_backup_path(path)] + .into_iter() + .find(|candidate| candidate.is_file()) +} + +pub fn download( + state: &crate::AppState, + url: &str, + destination: &Path, + expected_sha256: Option<&str>, + max_bytes: u64, +) -> Result { + state.progress("download", None, "Connecting to download server"); + let response = reqwest::blocking::Client::builder() + .connect_timeout(Duration::from_secs(15)) + .timeout(Duration::from_secs(180)) + .build() + .map_err(|error| error.to_string())? + .get(url) + .header(reqwest::header::USER_AGENT, "PatchOpsIII") + .send() + .and_then(reqwest::blocking::Response::error_for_status) + .map_err(|error| error.to_string())?; + if response + .content_length() + .is_some_and(|length| length > max_bytes) + { + return Err("download is larger than the allowed size".into()); + } + + let parent = destination + .parent() + .ok_or_else(|| "download destination has no parent".to_string())?; + fs::create_dir_all(parent).map_err(|error| error.to_string())?; + let part = PathBuf::from(format!("{}.part", destination.display())); + let mut output = File::create(&part).map_err(|error| error.to_string())?; + let length = response.content_length().filter(|length| *length > 0); + let mut input = response; + let mut last_progress = std::time::Instant::now(); + let mut digest = Sha256::new(); + let mut total = 0_u64; + let mut buffer = [0_u8; 128 * 1024]; + let result = (|| { + loop { + let read = input.read(&mut buffer).map_err(|error| error.to_string())?; + if read == 0 { + break; + } + total += read as u64; + if last_progress.elapsed() >= Duration::from_millis(100) { + state.progress( + "download", + length.map(|length| (total as f32 / length as f32).min(1.0)), + &format!("Downloaded {total} bytes"), + ); + last_progress = std::time::Instant::now(); + } + if total > max_bytes { + return Err("download is larger than the allowed size".into()); + } + digest.update(&buffer[..read]); + output + .write_all(&buffer[..read]) + .map_err(|error| error.to_string())?; + } + output.sync_all().map_err(|error| error.to_string())?; + state.progress("verify", None, "Verifying download SHA-256"); + let actual = format!("{:x}", digest.finalize()); + if expected_sha256.is_some_and(|expected| !actual.eq_ignore_ascii_case(expected)) { + return Err("download failed SHA-256 verification".into()); + } + replace_path(&part, destination)?; + state.progress("download", Some(1.0), "Download verified"); + Ok(actual) + })(); + if result.is_err() { + let _ = fs::remove_file(part); + } + result +} + +fn safe_relative(path: &Path) -> bool { + path.components() + .all(|component| matches!(component, Component::Normal(_) | Component::CurDir)) +} + +fn resolved_link_target(base: &Path, target: &Path) -> Option { + let mut parts = base + .components() + .filter_map(|component| match component { + Component::Normal(part) => Some(part.to_owned()), + Component::CurDir => None, + _ => None, + }) + .collect::>(); + for component in target.components() { + match component { + Component::CurDir => {} + Component::Normal(part) => parts.push(part.to_owned()), + Component::ParentDir => { + parts.pop()?; + } + Component::RootDir | Component::Prefix(_) => return None, + } + } + Some(parts.into_iter().collect()) +} + +fn prepare_empty_directory(destination: &Path) -> Result<(), String> { + match fs::symlink_metadata(destination) { + Ok(metadata) if metadata.file_type().is_symlink() || !metadata.is_dir() => { + return Err("archive destination is not a regular directory".into()); + } + Ok(_) => { + if fs::read_dir(destination) + .map_err(|error| error.to_string())? + .next() + .is_some() + { + return Err("archive destination must be empty".into()); + } + } + Err(error) if error.kind() == std::io::ErrorKind::NotFound => { + fs::create_dir_all(destination).map_err(|error| error.to_string())?; + } + Err(error) => return Err(error.to_string()), + } + Ok(()) +} + +pub fn extract_zip(archive: &Path, destination: &Path) -> Result<(), String> { + prepare_empty_directory(destination)?; + let file = File::open(archive).map_err(|error| error.to_string())?; + let mut zip = zip::ZipArchive::new(file).map_err(|error| error.to_string())?; + if zip.len() > MAX_ARCHIVE_ENTRIES { + return Err("archive contains too many entries".into()); + } + let mut total = 0_u64; + let mut seen = HashSet::new(); + for index in 0..zip.len() { + let mut entry = zip.by_index(index).map_err(|error| error.to_string())?; + let relative = entry + .enclosed_name() + .ok_or_else(|| "archive contains an unsafe path".to_string())? + .to_path_buf(); + if !safe_relative(&relative) || !seen.insert(relative.clone()) { + return Err("archive contains an unsafe or duplicate path".into()); + } + if entry + .unix_mode() + .is_some_and(|mode| mode & 0o170000 == 0o120000) + { + return Err("archive links are not allowed".into()); + } + total = total.saturating_add(entry.size()); + if total > MAX_ARCHIVE_BYTES { + return Err("archive expands beyond the allowed size".into()); + } + let output = destination.join(relative); + if entry.is_dir() { + fs::create_dir_all(&output).map_err(|error| error.to_string())?; + continue; + } + if let Some(parent) = output.parent() { + fs::create_dir_all(parent).map_err(|error| error.to_string())?; + } + let mut file = File::create(output).map_err(|error| error.to_string())?; + std::io::copy(&mut entry, &mut file).map_err(|error| error.to_string())?; + } + Ok(()) +} + +pub fn extract_tar_gz(archive: &Path, destination: &Path) -> Result<(), String> { + prepare_empty_directory(destination)?; + let decoder = GzDecoder::new(File::open(archive).map_err(|error| error.to_string())?); + let mut tar = tar::Archive::new(decoder); + let mut count = 0_usize; + let mut total = 0_u64; + let mut seen = HashSet::new(); + let mut hard_links = Vec::new(); + for item in tar.entries().map_err(|error| error.to_string())? { + let mut entry = item.map_err(|error| error.to_string())?; + count += 1; + if count > MAX_ARCHIVE_ENTRIES { + return Err("archive contains too many entries".into()); + } + let relative = entry + .path() + .map_err(|error| error.to_string())? + .into_owned(); + if !safe_relative(&relative) || !seen.insert(relative.clone()) { + return Err("archive contains an unsafe or duplicate path".into()); + } + let kind = entry.header().entry_type(); + total = total.saturating_add(entry.size()); + if total > MAX_ARCHIVE_BYTES { + return Err("archive expands beyond the allowed size".into()); + } + let output = destination.join(&relative); + if kind.is_file() || kind.is_dir() { + entry.unpack(&output).map_err(|error| error.to_string())?; + continue; + } + if !kind.is_symlink() && !kind.is_hard_link() { + return Err("archive contains a special file".into()); + } + let target = entry + .link_name() + .map_err(|error| error.to_string())? + .ok_or_else(|| "archive link has no target".to_string())? + .into_owned(); + let base = if kind.is_symlink() { + relative.parent().unwrap_or_else(|| Path::new("")) + } else { + Path::new("") + }; + let resolved = resolved_link_target(base, &target) + .ok_or_else(|| "archive link escapes the destination".to_string())?; + if let Some(parent) = output.parent() { + fs::create_dir_all(parent).map_err(|error| error.to_string())?; + } + if kind.is_hard_link() { + hard_links.push((destination.join(resolved), output)); + continue; + } + #[cfg(unix)] + std::os::unix::fs::symlink(&target, &output).map_err(|error| error.to_string())?; + #[cfg(not(unix))] + return Err("archive symbolic links are not supported on this platform".into()); + } + for (target, output) in hard_links { + if !target.is_file() { + return Err("archive hard link target is missing or is not a file".into()); + } + fs::hard_link(target, output).map_err(|error| error.to_string())?; + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use zip::{CompressionMethod, ZipWriter, write::SimpleFileOptions}; + + struct TestDirectory(PathBuf); + + impl TestDirectory { + fn new(label: &str) -> Self { + let path = std::env::temp_dir().join(format!( + "patchops-{label}-{}-{}", + std::process::id(), + TEMP_COUNTER.fetch_add(1, Ordering::Relaxed) + )); + fs::create_dir_all(&path).unwrap(); + Self(path) + } + + fn path(&self) -> &Path { + &self.0 + } + } + + impl Drop for TestDirectory { + fn drop(&mut self) { + let _ = fs::remove_dir_all(&self.0); + } + } + + fn write_zip(path: &Path, method: CompressionMethod, entries: &[(&str, &[u8])]) { + let mut writer = ZipWriter::new(File::create(path).unwrap()); + let options = SimpleFileOptions::default().compression_method(method); + for (name, contents) in entries { + writer.start_file(*name, options).unwrap(); + writer.write_all(contents).unwrap(); + } + writer.finish().unwrap(); + } + + #[test] + fn snapshot_restores_deleted_parents_and_removes_new_files() { + let root = TestDirectory::new("snapshot"); + let parent = root.path().join("game"); + fs::create_dir_all(&parent).unwrap(); + let original = parent.join("original.dll"); + let created = root.path().join("new.dll"); + fs::write(&original, b"original").unwrap(); + let snapshot = Snapshot::capture( + &[original.clone(), created.clone(), original.clone()], + &root.path().join("rollback"), + ) + .unwrap(); + assert_eq!(snapshot.entries.len(), 2); + fs::remove_dir_all(&parent).unwrap(); + fs::write(&created, b"new").unwrap(); + snapshot.restore().unwrap(); + assert_eq!(fs::read(&original).unwrap(), b"original"); + assert!(!created.exists()); + } + + #[cfg(unix)] + #[test] + fn atomic_write_preserves_existing_permissions() { + use std::os::unix::fs::PermissionsExt; + let root = + std::env::temp_dir().join(format!("patchops-permissions-{}", std::process::id())); + fs::create_dir_all(&root).unwrap(); + let path = root.join("settings.json"); + fs::write(&path, b"old").unwrap(); + fs::set_permissions(&path, fs::Permissions::from_mode(0o600)).unwrap(); + atomic_write(&path, b"new").unwrap(); + assert_eq!(fs::read(&path).unwrap(), b"new"); + assert_eq!(path.metadata().unwrap().permissions().mode() & 0o777, 0o600); + fs::remove_dir_all(root).unwrap(); + } + + #[test] + fn hashes_known_bytes() { + let path = std::env::temp_dir().join(format!("patchops-hash-{}", std::process::id())); + fs::write(&path, b"patchops").unwrap(); + assert_eq!( + sha256_file(&path).unwrap(), + "3e0cec93c1876296af254daff5dba161649c003683320e5fefbaf1ad21e67f98" + ); + fs::remove_file(path).unwrap(); + } + + #[test] + fn rejects_parent_paths() { + assert!(!safe_relative(Path::new("../outside"))); + assert!(!safe_relative(Path::new("/outside"))); + assert!(safe_relative(Path::new("inside/file"))); + } + + #[test] + fn link_targets_must_resolve_inside_the_archive_root() { + assert_eq!( + resolved_link_target(Path::new("bin"), Path::new("../lib/tool")), + Some(PathBuf::from("lib/tool")) + ); + assert_eq!( + resolved_link_target(Path::new("bin"), Path::new("../../outside")), + None + ); + assert_eq!( + resolved_link_target(Path::new(""), Path::new("/outside")), + None + ); + } + + #[test] + fn extracts_each_enabled_zip_compression_method() { + let root = TestDirectory::new("zip-compression-methods"); + let payload = b"PatchOpsIII archive fixture\n".repeat(128); + for (label, method) in [ + ("stored", CompressionMethod::Stored), + ("deflate", CompressionMethod::Deflated), + ("bzip2", CompressionMethod::Bzip2), + ("zstd", CompressionMethod::Zstd), + ] { + let archive = root.path().join(format!("{label}.zip")); + let destination = root.path().join(format!("{label}-output")); + write_zip( + &archive, + method, + &[("nested/payload.txt", payload.as_slice())], + ); + { + let mut zip = zip::ZipArchive::new(File::open(&archive).unwrap()).unwrap(); + assert_eq!(zip.by_index(0).unwrap().compression(), method); + } + + extract_zip(&archive, &destination).unwrap(); + + assert_eq!( + fs::read(destination.join("nested/payload.txt")).unwrap(), + payload + ); + } + } + + #[test] + fn rejects_malformed_zip_without_writing_files() { + let root = TestDirectory::new("malformed-zip"); + let archive = root.path().join("malformed.zip"); + let destination = root.path().join("output"); + fs::write(&archive, b"this is not a ZIP archive").unwrap(); + + assert!(extract_zip(&archive, &destination).is_err()); + assert_eq!(fs::read_dir(destination).unwrap().count(), 0); + } + + #[test] + fn rejects_zip_path_traversal_and_allows_caller_cleanup() { + let root = TestDirectory::new("zip-traversal"); + let archive = root.path().join("traversal.zip"); + let destination = root.path().join("output"); + let entries: &[(&str, &[u8])] = &[ + ("safe.txt", b"written before the invalid entry"), + ("../outside.txt", b"must not escape"), + ]; + write_zip(&archive, CompressionMethod::Stored, entries); + + let error = extract_zip(&archive, &destination).unwrap_err(); + + assert!(error.contains("unsafe path"), "{error}"); + assert_eq!( + fs::read(destination.join("safe.txt")).unwrap(), + entries[0].1 + ); + assert!(!root.path().join("outside.txt").exists()); + fs::remove_dir_all(&destination).unwrap(); + assert!(!destination.exists()); + } + + #[test] + fn rejects_zip_symlinks() { + let root = TestDirectory::new("zip-symlink"); + let archive = root.path().join("symlink.zip"); + let destination = root.path().join("output"); + let mut writer = ZipWriter::new(File::create(&archive).unwrap()); + writer + .add_symlink("link", "../outside", SimpleFileOptions::default()) + .unwrap(); + writer.finish().unwrap(); + + let error = extract_zip(&archive, &destination).unwrap_err(); + + assert!(error.contains("links are not allowed"), "{error}"); + assert_eq!(fs::read_dir(destination).unwrap().count(), 0); + } + + #[test] + fn rejects_equivalent_duplicate_zip_paths_without_overwriting_partial_output() { + let root = TestDirectory::new("zip-duplicate"); + let archive = root.path().join("duplicate.zip"); + let destination = root.path().join("output"); + let entries: &[(&str, &[u8])] = &[ + ("nested/duplicate.txt", b"first entry"), + ("nested/./duplicate.txt", b"second entry"), + ]; + write_zip(&archive, CompressionMethod::Stored, entries); + + let error = extract_zip(&archive, &destination).unwrap_err(); + + assert!(error.contains("duplicate path"), "{error}"); + assert_eq!( + fs::read(destination.join("nested/duplicate.txt")).unwrap(), + b"first entry" + ); + } + + #[cfg(unix)] + #[test] + fn atomic_write_does_not_follow_a_predictable_temporary_symlink() { + let root = std::env::temp_dir().join(format!( + "patchops-atomic-{}-{}", + std::process::id(), + TEMP_COUNTER.fetch_add(1, Ordering::Relaxed) + )); + fs::create_dir_all(&root).unwrap(); + let target = root.join("settings.json"); + let victim = root.join("victim"); + fs::write(&victim, b"untouched").unwrap(); + std::os::unix::fs::symlink(&victim, root.join(".settings.json.patchops.tmp")).unwrap(); + + atomic_write(&target, b"new settings").unwrap(); + + assert_eq!(fs::read(&target).unwrap(), b"new settings"); + assert_eq!(fs::read(&victim).unwrap(), b"untouched"); + fs::remove_dir_all(root).unwrap(); + } +} diff --git a/native/core/src/lib.rs b/native/core/src/lib.rs new file mode 100644 index 0000000..4261a07 --- /dev/null +++ b/native/core/src/lib.rs @@ -0,0 +1,17 @@ +//! Toolkit-independent PatchOpsIII operations and JSON-compatible dispatcher. + +mod app; +mod dxvk; +mod engine; +mod enhanced; +mod exe; +mod fs_ops; +pub mod models; +mod steam; +mod t7; + +pub use app::{AppState, EventCallback, ProgressCallback, SteamLifecycleCallback}; +pub use engine::Engine; + +pub use exe::ExeHashProvider; +pub use t7::{T7ReleaseAsset, select_t7_release_asset}; diff --git a/native/core/src/models.rs b/native/core/src/models.rs new file mode 100644 index 0000000..e7f7685 --- /dev/null +++ b/native/core/src/models.rs @@ -0,0 +1,220 @@ +use serde::{Deserialize, Serialize}; + +/// Worker-thread progress. Fractions are stage-local; None means indeterminate. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct OperationProgress { + /// Legacy request path, e.g. `/api/t7-install`. + pub op: String, + /// `started`, `running`, `download`, `verify`, `completed`, or `failed`. + pub stage: String, + pub fraction: Option, + pub message: String, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct LogEntry { + pub category: String, + pub message: String, + pub line: String, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct LaunchProfile { + pub id: String, + pub label: String, + pub option: String, + pub active: bool, + pub installed: bool, + pub subscribed: bool, + pub state: String, + pub path: Option, +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct EnhancedState { + pub installed: bool, + pub detected_at: Option, + pub acknowledged_at: Option, + pub launch_options_active: bool, + pub dump_source: String, + pub files_installed: usize, + pub backup_status: String, +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct ExeSwapState { + pub profile: String, + pub mode_label: String, + pub patch_label: String, + pub display_label: String, + pub state: String, + pub active_build_id: String, + pub active_build_date: String, + pub current_build_id: String, + pub current_build_date: String, + pub compatible_build_id: String, + pub compatible_build_date: String, + pub enhanced_build_id: String, + pub enhanced_build_date: String, + pub executable: String, + pub executable_name: String, + pub executable_hash: String, + pub trusted_executable: bool, + pub integrity_status: String, + pub integrity_message: String, + pub backup_available: bool, + pub latest_available: bool, + pub compatible_available: bool, + pub enhanced_available: bool, + pub compatible_active: bool, + pub enhanced_exe_active: bool, + pub enhanced_active: bool, +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct T7State { + pub installed: bool, + pub conf_exists: bool, + pub gamertag: String, + pub plain_name: String, + pub color_code: String, + pub network_password: String, + pub friends_only: bool, + pub mode: String, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct DxvkSettings { + pub enable_async: bool, + pub gpl_async_cache: bool, + pub num_compiler_threads: i32, + pub max_frame_rate: i32, + pub max_frame_latency: i32, + pub tear_free: String, + pub hud_enabled: bool, +} + +impl Default for DxvkSettings { + fn default() -> Self { + Self { + enable_async: true, + gpl_async_cache: true, + num_compiler_threads: 0, + max_frame_rate: 0, + max_frame_latency: 1, + tear_free: "True".into(), + hud_enabled: false, + } + } +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct DxvkState { + pub installed: bool, + pub conf_exists: bool, + pub settings: DxvkSettings, +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct QolState { + pub d3dcompiler: bool, + pub intro: bool, + pub all_intros: bool, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct GraphicsState { + pub max_fps: i32, + pub fov: i32, + pub display_mode: i32, + pub resolution: String, + pub refresh_rate: f64, + pub render_resolution: i32, + pub vsync: bool, + pub draw_fps: bool, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct AdvancedState { + pub smooth_framerate: bool, + pub unlock_options: bool, + pub reduce_cpu: bool, + pub max_frame_latency: i32, + pub vram_limited: bool, + pub vram_target: i32, + pub config_readonly: bool, +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct MaintenanceState { + pub mod_files_dir: String, + pub log_payload: String, +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct ModsState { + pub t7_patch: bool, + pub dxvk: bool, + pub enhanced: bool, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct PatchOpsState { + pub app_version: String, + pub platform: String, + pub game_dir: Option, + pub game_detected: bool, + pub config_exists: bool, + pub steam_user_id: Option, + pub log_path: String, + pub presets: Vec, + pub current_launch_options: Option, + pub active_launch_profile: String, + pub release_channel: String, + pub launch_profiles: Vec, + pub enhanced: EnhancedState, + pub exe_swap: ExeSwapState, + pub t7: T7State, + pub dxvk: DxvkState, + pub qol: QolState, + pub graphics: GraphicsState, + pub advanced: AdvancedState, + pub maintenance: MaintenanceState, + pub mods: ModsState, + pub logs: Vec, +} + +#[derive(Clone, Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CompatibleExeResult { + pub state: PatchOpsState, + pub depot_command: Option, +} + +#[derive(Clone, Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct DepotStatus { + pub available: bool, +} + +#[derive(Clone, Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct EnhancedValidation { + pub valid: bool, + pub message: String, + pub state: PatchOpsState, +} diff --git a/native/core/src/steam.rs b/native/core/src/steam.rs new file mode 100644 index 0000000..ebc977c --- /dev/null +++ b/native/core/src/steam.rs @@ -0,0 +1,1517 @@ +use std::{ + cmp::Ordering, + collections::HashSet, + env, fs, + path::{Path, PathBuf}, + process::{Command, Stdio}, + time::UNIX_EPOCH, +}; + +use crate::{app::AppState, fs_ops, models::LaunchProfile}; +use regex::Regex; + +// Proton and compatibility-tool ownership are used only by Linux Enhanced installs. +#[cfg(target_os = "linux")] +mod linux; +#[cfg(target_os = "linux")] +pub(super) use linux::{cleanup_bo3_enhanced_linux, configure_bo3_enhanced_linux}; + +pub const APP_ID: &str = "311210"; +pub const COMPATIBLE_DEPOT_APP_ID: &str = APP_ID; +pub const COMPATIBLE_DEPOT_ID: &str = "311211"; + +const GAME_DIRECTORY: &str = "Call of Duty Black Ops III"; +const GAME_EXECUTABLES: [&str; 2] = ["BlackOpsIII.exe", "BlackOps3.exe"]; +const COMPATIBLE_BUILD_SHA256: &str = + "66b95eb4667bd5b3b3d230e7bed1d29ccd261d48ca2699f01216c863be24ff44"; +const STEAM_ID64_OFFSET: u64 = 76_561_197_960_265_728; + +#[derive(Clone, Copy)] +struct WorkshopProfile { + id: &'static str, + label: &'static str, + workshop_id: &'static str, + option: &'static str, +} + +const WORKSHOP_PROFILES: [WorkshopProfile; 3] = [ + WorkshopProfile { + id: "all_around", + label: "All-around Enhancement Lite", + workshop_id: "2994481309", + option: "+set fs_game 2994481309", + }, + WorkshopProfile { + id: "ultimate", + label: "Ultimate Experience Mod", + workshop_id: "2942053577", + option: "+set fs_game 2942053577", + }, + WorkshopProfile { + id: "reforged", + label: "BO3 Reforged", + workshop_id: "3667377161", + option: "+set fs_game 3667377161", + }, +]; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct WorkshopState { + pub state: String, + pub installed: bool, + pub subscribed: bool, + pub path: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +enum VdfValue { + String(String), + Object(VdfObject), +} + +type VdfObject = Vec; + +#[derive(Clone, Debug, PartialEq, Eq)] +struct VdfEntry { + key: String, + value: VdfValue, + condition: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +enum Token { + Text(String), + Open, + Close, + Condition(String), +} + +fn lex_vdf(input: &str) -> Result, String> { + let chars: Vec = input.trim_start_matches('\u{feff}').chars().collect(); + let mut tokens = Vec::new(); + let mut index = 0; + while index < chars.len() { + if chars[index].is_whitespace() { + index += 1; + continue; + } + if chars[index] == '/' && chars.get(index + 1) == Some(&'/') { + index += 2; + while index < chars.len() && chars[index] != '\n' { + index += 1; + } + continue; + } + match chars[index] { + '{' => { + tokens.push(Token::Open); + index += 1; + } + '}' => { + tokens.push(Token::Close); + index += 1; + } + '[' => { + index += 1; + let start = index; + while index < chars.len() && chars[index] != ']' { + index += 1; + } + if index == chars.len() { + return Err("unterminated VDF condition".into()); + } + tokens.push(Token::Condition(chars[start..index].iter().collect())); + index += 1; + } + '"' => { + index += 1; + let mut value = String::new(); + let mut closed = false; + while index < chars.len() { + match chars[index] { + '"' => { + index += 1; + closed = true; + break; + } + '\\' if index + 1 < chars.len() => { + let escaped = chars[index + 1]; + match escaped { + '"' => value.push('"'), + '\\' => value.push('\\'), + 'n' => value.push('\n'), + 'r' => value.push('\r'), + 't' => value.push('\t'), + other => { + value.push('\\'); + value.push(other); + } + } + index += 2; + } + character => { + value.push(character); + index += 1; + } + } + } + if !closed { + return Err("unterminated quoted VDF value".into()); + } + tokens.push(Token::Text(value)); + } + _ => { + let start = index; + while index < chars.len() + && !chars[index].is_whitespace() + && !matches!(chars[index], '{' | '}' | '[' | '"') + && !(chars[index] == '/' && chars.get(index + 1) == Some(&'/')) + { + index += 1; + } + if start == index { + return Err("invalid VDF token".into()); + } + tokens.push(Token::Text(chars[start..index].iter().collect())); + } + } + } + Ok(tokens) +} + +fn parse_vdf(input: &str) -> Result { + fn entries(tokens: &[Token], index: &mut usize, nested: bool) -> Result { + let mut result = Vec::new(); + while *index < tokens.len() { + if tokens[*index] == Token::Close { + if !nested { + return Err("unexpected closing VDF brace".into()); + } + *index += 1; + return Ok(result); + } + let key = match tokens.get(*index) { + Some(Token::Text(value)) => value.clone(), + Some(_) => return Err("expected a VDF key".into()), + None => break, + }; + *index += 1; + let value = match tokens.get(*index) { + Some(Token::Text(value)) => { + *index += 1; + VdfValue::String(value.clone()) + } + Some(Token::Open) => { + *index += 1; + VdfValue::Object(entries(tokens, index, true)?) + } + _ => return Err(format!("missing VDF value for '{key}'")), + }; + let condition = match tokens.get(*index) { + Some(Token::Condition(value)) => { + *index += 1; + Some(value.clone()) + } + _ => None, + }; + result.push(VdfEntry { + key, + value, + condition, + }); + } + if nested { + Err("unterminated VDF object".into()) + } else { + Ok(result) + } + } + + let tokens = lex_vdf(input)?; + let mut index = 0; + let object = entries(&tokens, &mut index, false)?; + if index == tokens.len() { + Ok(object) + } else { + Err("trailing VDF tokens".into()) + } +} + +fn quote_vdf(value: &str) -> String { + let mut quoted = String::with_capacity(value.len() + 2); + quoted.push('"'); + for character in value.chars() { + match character { + '"' => quoted.push_str("\\\""), + '\\' => quoted.push_str("\\\\"), + '\n' => quoted.push_str("\\n"), + '\r' => quoted.push_str("\\r"), + '\t' => quoted.push_str("\\t"), + other => quoted.push(other), + } + } + quoted.push('"'); + quoted +} + +fn serialize_vdf(object: &VdfObject) -> String { + fn write_entries(object: &VdfObject, depth: usize, output: &mut String) { + let indent = "\t".repeat(depth); + for entry in object { + output.push_str(&indent); + output.push_str("e_vdf(&entry.key)); + match &entry.value { + VdfValue::String(value) => { + output.push(' '); + output.push_str("e_vdf(value)); + if let Some(condition) = &entry.condition { + output.push_str("\t["); + output.push_str(condition); + output.push(']'); + } + output.push('\n'); + } + VdfValue::Object(children) => { + output.push('\n'); + output.push_str(&indent); + output.push_str("{\n"); + write_entries(children, depth + 1, output); + output.push_str(&indent); + output.push('}'); + if let Some(condition) = &entry.condition { + output.push_str("\t["); + output.push_str(condition); + output.push(']'); + } + output.push('\n'); + } + } + } + } + + let mut output = String::new(); + write_entries(object, 0, &mut output); + output +} + +fn entry_index(object: &VdfObject, key: &str) -> Option { + object + .iter() + .rposition(|entry| entry.key.eq_ignore_ascii_case(key)) +} + +fn value_at<'a>(object: &'a VdfObject, path: &[&str]) -> Option<&'a VdfValue> { + let (key, remaining) = path.split_first()?; + let value = &object.get(entry_index(object, key)?)?.value; + if remaining.is_empty() { + return Some(value); + } + match value { + VdfValue::Object(children) => value_at(children, remaining), + VdfValue::String(_) => None, + } +} + +fn ensure_object<'a>(object: &'a mut VdfObject, key: &str) -> Result<&'a mut VdfObject, String> { + let index = match entry_index(object, key) { + Some(index) => index, + None => { + object.push(VdfEntry { + key: key.into(), + value: VdfValue::Object(Vec::new()), + condition: None, + }); + object.len() - 1 + } + }; + match &mut object[index].value { + VdfValue::Object(children) => Ok(children), + VdfValue::String(_) => Err(format!("VDF key '{key}' is not an object")), + } +} + +fn ensure_path<'a>(object: &'a mut VdfObject, path: &[&str]) -> Result<&'a mut VdfObject, String> { + match path.split_first() { + Some((key, remaining)) => ensure_path(ensure_object(object, key)?, remaining), + None => Ok(object), + } +} + +fn set_string(object: &mut VdfObject, key: &str, value: String) -> Result<(), String> { + if let Some(index) = entry_index(object, key) { + match &mut object[index].value { + VdfValue::String(existing) => *existing = value, + VdfValue::Object(_) => return Err(format!("VDF key '{key}' is not a string")), + } + } else { + object.push(VdfEntry { + key: key.into(), + value: VdfValue::String(value), + condition: None, + }); + } + Ok(()) +} + +fn read_vdf(path: &Path) -> Result { + let bytes = fs::read(path).map_err(|error| format!("{}: {error}", path.display()))?; + let text = String::from_utf8(bytes) + .map_err(|error| format!("{} is not valid UTF-8: {error}", path.display()))?; + parse_vdf(&text).map_err(|error| format!("{}: {error}", path.display())) +} + +fn atomic_temp_path(path: &Path) -> Option { + Some( + path.parent()? + .join(format!(".{}.patchops.tmp", path.file_name()?.to_str()?)), + ) +} + +fn preserve_original( + path: &Path, + original: &[u8], + legacy_backup: Option<&Path>, +) -> Result<(), String> { + let backup = fs_ops::backup_path(path); + match fs::metadata(&backup) { + Ok(metadata) if metadata.is_file() => {} + Ok(_) => return Err(format!("{} is not a file", backup.display())), + Err(error) if error.kind() == std::io::ErrorKind::NotFound => { + fs_ops::atomic_write(&backup, original)?; + } + Err(error) => return Err(format!("{}: {error}", backup.display())), + } + if let Some(legacy_backup) = legacy_backup { + fs_ops::atomic_write(legacy_backup, original)?; + } + Ok(()) +} + +fn update_vdf_with_backup( + path: &Path, + legacy_backup: Option<&Path>, + rolling: bool, + change: impl FnOnce(&mut VdfObject) -> Result<(), String>, +) -> Result<(), String> { + let original = fs::read(path).map_err(|error| format!("{}: {error}", path.display()))?; + let text = String::from_utf8(original.clone()) + .map_err(|error| format!("{} is not valid UTF-8: {error}", path.display()))?; + let mut document = parse_vdf(&text).map_err(|error| format!("{}: {error}", path.display()))?; + change(&mut document)?; + let updated = serialize_vdf(&document); + parse_vdf(&updated).map_err(|error| format!("refusing to write invalid VDF: {error}"))?; + if rolling { + let backup = + legacy_backup.ok_or_else(|| "Rolling VDF backup path is required.".to_owned())?; + // Match Python copy2: refresh bytes and permissions on every apply. + fs_ops::atomic_write(backup, &original)?; + fs::set_permissions( + backup, + fs::metadata(path).map_err(|e| e.to_string())?.permissions(), + ) + .map_err(|e| e.to_string())?; + } + if updated.as_bytes() == original { + return Ok(()); + } + if !rolling { + preserve_original(path, &original, legacy_backup)?; + } + if let Err(write_error) = fs_ops::atomic_write(path, updated.as_bytes()) { + if let Some(temporary) = atomic_temp_path(path) { + let _ = fs::remove_file(temporary); + } + return match fs_ops::atomic_write(path, &original) { + Ok(()) => Err(format!( + "failed to update {}: {write_error}; original restored", + path.display() + )), + Err(rollback_error) => Err(format!( + "failed to update {}: {write_error}; rollback also failed: {rollback_error}", + path.display() + )), + }; + } + Ok(()) +} + +#[cfg(not(windows))] +fn home_dir() -> Option { + let variable = "HOME"; + env::var_os(variable) + .filter(|value| !value.is_empty()) + .map(PathBuf::from) +} + +#[cfg(windows)] +fn windows_steam_roots() -> Vec { + use winreg::{RegKey, enums::*}; + + let mut candidates = Vec::new(); + for (hive, subkey, value_name) in [ + (HKEY_CURRENT_USER, r"Software\Valve\Steam", "SteamPath"), + ( + HKEY_LOCAL_MACHINE, + r"SOFTWARE\WOW6432Node\Valve\Steam", + "InstallPath", + ), + (HKEY_LOCAL_MACHINE, r"SOFTWARE\Valve\Steam", "InstallPath"), + ] { + if let Ok(key) = RegKey::predef(hive).open_subkey(subkey) + && let Ok(path) = key.get_value::(value_name) + { + candidates.push(PathBuf::from(path)); + } + } + for variable in ["PROGRAMFILES(X86)", "PROGRAMFILES"] { + if let Some(path) = env::var_os(variable) { + candidates.push(PathBuf::from(path).join("Steam")); + } + } + candidates.push(PathBuf::from(r"C:\Program Files (x86)\Steam")); + candidates.push(PathBuf::from(r"C:\Program Files\Steam")); + candidates +} + +fn candidate_steam_roots() -> Vec { + #[cfg(windows)] + let candidates = windows_steam_roots(); + + #[cfg(target_os = "macos")] + let candidates = home_dir() + .map(|home| vec![home.join("Library/Application Support/Steam")]) + .unwrap_or_default(); + + #[cfg(all(unix, not(target_os = "macos")))] + let candidates = home_dir() + .map(|home| vec![home.join(".steam/steam"), home.join(".local/share/Steam")]) + .unwrap_or_default(); + + dedupe_existing_dirs(candidates) +} + +fn normalized_path_key(path: &Path) -> String { + let resolved = path.canonicalize().unwrap_or_else(|_| path.to_path_buf()); + #[cfg(windows)] + return resolved.to_string_lossy().to_ascii_lowercase(); + #[cfg(not(windows))] + resolved.to_string_lossy().into_owned() +} + +fn dedupe_existing_dirs(paths: impl IntoIterator) -> Vec { + let mut seen = HashSet::new(); + paths + .into_iter() + .filter(|path| path.is_dir()) + .filter(|path| seen.insert(normalized_path_key(path))) + .collect() +} + +pub(crate) fn library_paths_from_roots(roots: Vec) -> Vec { + let mut paths = roots.clone(); + for root in roots { + let library_file = root.join("steamapps/libraryfolders.vdf"); + let Ok(document) = read_vdf(&library_file) else { + continue; + }; + let Some(VdfValue::Object(folders)) = value_at(&document, &["libraryfolders"]) else { + continue; + }; + for entry in folders { + if entry.key.eq_ignore_ascii_case("contentstatsid") { + continue; + } + match &entry.value { + VdfValue::String(path) => paths.push(PathBuf::from(path)), + VdfValue::Object(folder) => { + if let Some(VdfValue::String(path)) = value_at(folder, &["path"]) { + paths.push(PathBuf::from(path)); + } + } + } + } + } + dedupe_existing_dirs(paths) +} + +pub fn steam_root() -> Option { + candidate_steam_roots().into_iter().next() +} + +pub fn library_paths() -> Vec { + library_paths_from_roots(candidate_steam_roots()) +} + +fn has_game_executable(directory: &Path) -> bool { + directory.is_dir() + && GAME_EXECUTABLES + .iter() + .any(|executable| directory.join(executable).is_file()) +} + +pub fn find_game_directory(saved: Option<&str>) -> Option { + if let Some(saved) = saved + .map(PathBuf::from) + .filter(|path| has_game_executable(path)) + { + return Some(saved); + } + for library in library_paths() { + let candidate = library.join("steamapps/common").join(GAME_DIRECTORY); + if has_game_executable(&candidate) { + return Some(candidate); + } + } + + #[cfg(windows)] + let candidates = windows_steam_roots() + .into_iter() + .map(|root| root.join("steamapps/common").join(GAME_DIRECTORY)) + .collect::>(); + #[cfg(target_os = "macos")] + let candidates = vec![ + home_dir()? + .join("Library/Application Support/Steam/steamapps/common") + .join(GAME_DIRECTORY), + ]; + #[cfg(all(unix, not(target_os = "macos")))] + let candidates = { + let home = home_dir()?; + vec![ + home.join(".steam/steam/steamapps/common") + .join(GAME_DIRECTORY), + home.join(".local/share/Steam/steamapps/common") + .join(GAME_DIRECTORY), + ] + }; + candidates + .into_iter() + .find(|path| has_game_executable(path)) +} + +fn numeric_id_cmp(left: &str, right: &str) -> Ordering { + let left = left.trim_start_matches('0'); + let right = right.trim_start_matches('0'); + left.len().cmp(&right.len()).then_with(|| left.cmp(right)) +} + +fn numeric_user_ids(userdata: &Path) -> Vec { + let mut ids = fs::read_dir(userdata) + .ok() + .into_iter() + .flatten() + .filter_map(Result::ok) + .filter(|entry| entry.path().is_dir()) + .filter_map(|entry| entry.file_name().into_string().ok()) + .filter(|name| !name.is_empty() && name.bytes().all(|byte| byte.is_ascii_digit())) + .collect::>(); + ids.sort_by(|left, right| numeric_id_cmp(left, right).then_with(|| left.cmp(right))); + ids +} + +fn user_id_in(steam_root: &Path) -> Option { + let ids = numeric_user_ids(&steam_root.join("userdata")); + let login_users = read_vdf(&steam_root.join("config/loginusers.vdf")).ok(); + let recent = login_users + .as_ref() + .and_then(|document| value_at(document, &["users"])) + .and_then(|value| match value { + VdfValue::Object(users) => Some(users), + VdfValue::String(_) => None, + }) + .into_iter() + .flatten() + .filter(|entry| { + matches!( + &entry.value, + VdfValue::Object(user) + if matches!(value_at(user, &["MostRecent"]), Some(VdfValue::String(value)) if value == "1") + ) + }) + .filter_map(|entry| { + if ids.iter().any(|id| id == &entry.key) { + return Some(entry.key.clone()); + } + entry + .key + .parse::() + .ok()? + .checked_sub(STEAM_ID64_OFFSET) + .map(|id| id.to_string()) + .filter(|id| ids.iter().any(|candidate| candidate == id)) + }) + .min_by(|left, right| numeric_id_cmp(left, right).then_with(|| left.cmp(right))); + recent.or_else(|| ids.into_iter().next()) +} + +pub fn user_id() -> Option { + user_id_in(&steam_root()?) +} + +fn local_config_path_in(steam_root: &Path, user: &str) -> Option { + if user.is_empty() || !user.bytes().all(|byte| byte.is_ascii_digit()) { + return None; + } + Some( + steam_root + .join("userdata") + .join(user) + .join("config/localconfig.vdf"), + ) +} + +fn local_config_path(user: &str) -> Option { + local_config_path_in(&steam_root()?, user) +} + +fn read_launch_options_at(path: &Path, game_app_id: &str) -> Result { + let document = read_vdf(path)?; + Ok( + match value_at( + &document, + &[ + "UserLocalConfigStore", + "Software", + "Valve", + "Steam", + "apps", + game_app_id, + "LaunchOptions", + ], + ) { + Some(VdfValue::String(options)) => options.clone(), + _ => String::new(), + }, + ) +} + +pub fn current_launch_options() -> Option { + let user = user_id()?; + read_launch_options_at(&local_config_path(&user)?, APP_ID).ok() +} + +fn normalized_options(options: &str) -> String { + options.split_whitespace().collect::>().join(" ") +} + +fn without_token(options: &str, token: &str) -> String { + options + .split_whitespace() + .filter(|part| *part != token) + .collect::>() + .join(" ") +} + +fn merged_launch_options(current: &str, requested: &str, preserve_fs_game: bool) -> String { + let fs_game = Regex::new(r"\+set\s+fs_game\s+\S+").expect("fixed regular expression"); + let existing_fs = fs_game + .find_iter(current) + .map(|matched| matched.as_str().to_owned()) + .collect::>(); + let requested_fs = fs_game + .find_iter(requested) + .map(|matched| matched.as_str().to_owned()) + .collect::>(); + let selected_fs = if requested_fs.is_empty() && preserve_fs_game { + existing_fs + } else { + requested_fs + }; + + let wine_override = "WINEDLLOVERRIDES=\"dsound=n,b\""; + let command_marker = "%command%"; + let current_without_fs = fs_game.replace_all(current, ""); + let requested_without_fs = fs_game.replace_all(requested, ""); + let has_wine = current_without_fs + .split_whitespace() + .any(|part| part == wine_override) + || requested_without_fs + .split_whitespace() + .any(|part| part == wine_override); + let mut has_command = current_without_fs + .split_whitespace() + .any(|part| part == command_marker) + || requested_without_fs + .split_whitespace() + .any(|part| part == command_marker); + if has_wine { + has_command = true; + } + + let cleaned_current = without_token( + &without_token(¤t_without_fs, wine_override), + command_marker, + ); + let cleaned_requested = without_token( + &without_token(&requested_without_fs, wine_override), + command_marker, + ); + let mut segments = Vec::new(); + if has_wine { + segments.push(wine_override.to_owned()); + } + if has_command { + segments.push(command_marker.to_owned()); + } + if !cleaned_current.is_empty() { + segments.push(normalized_options(&cleaned_current)); + } + if !cleaned_requested.is_empty() { + segments.push(normalized_options(&cleaned_requested)); + } + let mut unique_fs = Vec::new(); + for entry in selected_fs { + if !unique_fs.contains(&entry) { + unique_fs.push(entry); + } + } + if !unique_fs.is_empty() { + segments.push(normalized_options(&unique_fs.join(" "))); + } + normalized_options(&segments.join(" ")) +} + +fn set_launch_options_at( + config: &Path, + legacy_backup: &Path, + game_app_id: &str, + requested: &str, + preserve_fs_game: bool, + exact: bool, +) -> Result { + let current = read_launch_options_at(config, game_app_id)?; + let final_options = if exact { + requested.to_owned() + } else { + merged_launch_options(¤t, requested, preserve_fs_game) + }; + update_vdf_with_backup(config, Some(legacy_backup), true, |document| { + let app = ensure_path( + document, + &[ + "UserLocalConfigStore", + "Software", + "Valve", + "Steam", + "apps", + game_app_id, + ], + )?; + set_string(app, "LaunchOptions", final_options.clone()) + })?; + Ok(final_options) +} + +fn workshop_profile(profile_id: &str) -> Option { + WORKSHOP_PROFILES + .iter() + .copied() + .find(|profile| profile.id == profile_id) +} + +fn profile_option(profile_id: &str) -> Option<&'static str> { + match profile_id { + "default" => Some(""), + "offline" => Some("+set fs_game offlinemp"), + _ => workshop_profile(profile_id).map(|profile| profile.option), + } +} + +fn supported_option(option: &str) -> bool { + option.is_empty() + || option == "+set fs_game offlinemp" + || WORKSHOP_PROFILES + .iter() + .any(|profile| profile.option == option) +} + +fn vdf_contains(value: &VdfValue, needle: &str) -> bool { + match value { + VdfValue::String(value) => value == needle, + VdfValue::Object(object) => object + .iter() + .any(|entry| entry.key == needle || vdf_contains(&entry.value, needle)), + } +} + +fn workshop_item_state_in( + libraries: &[PathBuf], + game_app_id: &str, + workshop_id: &str, +) -> WorkshopState { + let mut subscribed = false; + for library in libraries { + let install_dir = library + .join("steamapps/workshop/content") + .join(game_app_id) + .join(workshop_id); + if install_dir.is_dir() + && fs::read_dir(&install_dir) + .ok() + .and_then(|mut entries| entries.next()) + .is_some() + { + return WorkshopState { + state: "Installed".into(), + installed: true, + subscribed: true, + path: Some(install_dir), + }; + } + + let manifest = library + .join("steamapps/workshop") + .join(format!("appworkshop_{game_app_id}.acf")); + if let Ok(document) = read_vdf(&manifest) + && let Some(value) = value_at(&document, &["AppWorkshop"]) + { + subscribed |= vdf_contains(value, workshop_id); + } + } + if subscribed { + WorkshopState { + state: "Subscribed (not installed yet)".into(), + installed: false, + subscribed: true, + path: None, + } + } else { + WorkshopState { + state: "Not Subscribed".into(), + installed: false, + subscribed: false, + path: None, + } + } +} + +pub fn workshop_item_state(game_app_id: &str, workshop_id: &str) -> WorkshopState { + workshop_item_state_in(&library_paths(), game_app_id, workshop_id) +} + +pub fn launch_profiles(current: Option<&str>) -> Vec { + let current = current.unwrap_or_default(); + let mut profiles = vec![ + LaunchProfile { + id: "default".into(), + label: "Default (None)".into(), + option: String::new(), + active: current.trim().is_empty(), + installed: true, + subscribed: true, + state: "Ready".into(), + path: None, + }, + LaunchProfile { + id: "offline".into(), + label: "Play Offline".into(), + option: "+set fs_game offlinemp".into(), + active: current.contains("+set fs_game offlinemp"), + installed: true, + subscribed: true, + state: "Ready".into(), + path: None, + }, + ]; + for profile in WORKSHOP_PROFILES { + let workshop = workshop_item_state(APP_ID, profile.workshop_id); + profiles.push(LaunchProfile { + id: profile.id.into(), + label: profile.label.into(), + option: profile.option.into(), + active: current.contains(profile.option), + installed: workshop.installed, + subscribed: workshop.subscribed, + state: workshop.state, + path: workshop + .path + .map(|path| path.to_string_lossy().into_owned()), + }); + } + profiles +} + +#[cfg(all(unix, not(target_os = "macos")))] +fn find_on_path(command: &str) -> Option { + env::var_os("PATH") + .into_iter() + .flat_map(|paths| env::split_paths(&paths).collect::>()) + .map(|directory| directory.join(command)) + .find(|path| path.is_file()) +} + +#[cfg(windows)] +fn steam_executable() -> Option { + candidate_steam_roots() + .into_iter() + .map(|root| root.join("steam.exe")) + .find(|path| path.is_file()) +} + +#[cfg(all(unix, not(target_os = "macos")))] +fn steam_executable() -> Option { + find_on_path("steam").or_else(|| { + steam_root() + .map(|root| root.join("steam.sh")) + .filter(|path| path.is_file()) + }) +} + +fn close_steam(app: &AppState) -> Result<(), String> { + if let Some(result) = app.steam_lifecycle_override(false) { + return result; + } + #[cfg(windows)] + let result = Command::new("taskkill") + .args(["/F", "/IM", "steam.exe"]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status(); + #[cfg(target_os = "linux")] + let result = Command::new("pkill") + .args(["-x", "steam"]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status(); + + #[cfg(target_os = "macos")] + let result = Command::new("pkill") + .args(["-x", "steam"]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status(); + + #[cfg(windows)] + match result { + Ok(status) if status.success() => { + app.log("Info", "Closed Steam before updating its configuration.") + } + Ok(_) => app.log("Info", "Steam was not running."), + Err(error) => return Err(format!("Could not close Steam: {error}")), + } + + #[cfg(unix)] + match result { + Ok(status) if status.success() => { + app.log("Info", "Closed Steam before updating its configuration.") + } + Ok(status) if status.code() == Some(1) => app.log("Info", "Steam was not running."), + Ok(status) => { + return Err(format!( + "Could not close Steam (exit code {}).", + status + .code() + .map_or_else(|| "unknown".into(), |code| code.to_string()) + )); + } + Err(error) => return Err(format!("Could not close Steam: {error}")), + } + + #[cfg(target_os = "linux")] + linux::wait_for_steam_exit_with(50, linux::linux_steam_running, || { + std::thread::sleep(std::time::Duration::from_millis(100)) + })?; + + Ok(()) +} + +fn open_steam(app: &AppState) -> Result<(), String> { + if let Some(result) = app.steam_lifecycle_override(true) { + return result; + } + #[cfg(windows)] + let child = steam_executable() + .ok_or_else(|| "Steam executable was not found.".to_string()) + .and_then(|executable| { + Command::new(executable) + .arg("-silent") + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + .map_err(|error| error.to_string()) + }); + + #[cfg(all(unix, not(target_os = "macos")))] + let child = steam_executable() + .ok_or_else(|| "Steam executable was not found.".to_string()) + .and_then(|executable| { + Command::new(executable) + .arg("-silent") + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + .map_err(|error| error.to_string()) + }); + + #[cfg(target_os = "macos")] + let child = Command::new("open") + .args(["-a", "Steam"]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + .map_err(|error| error.to_string()); + + child.map(|_| app.log("Success", "Opened Steam.")) +} + +pub fn apply_launch_options( + app: &AppState, + options: &str, + preserve_fs_game: bool, +) -> Result<(), String> { + if !supported_option(options) { + return Err("Unsupported launch option.".into()); + } + let user = user_id().ok_or_else(|| "Steam user ID not found.".to_string())?; + let config = + local_config_path(&user).ok_or_else(|| "Steam userdata path not found.".to_string())?; + let legacy_backup = app.data_dir().join("backups/localconfig_backup.vdf"); + close_steam(app)?; + let result = set_launch_options_at( + &config, + &legacy_backup, + APP_ID, + options, + preserve_fs_game, + false, + ); + let result = result.map(|final_options| { + app.log( + "Success", + format!("Config backup created at {}", legacy_backup.display()), + ); + app.log( + "Info", + format!("Setting launch options to: {final_options}"), + ); + }); + if let Err(error) = open_steam(app) { + app.log( + "Warning", + format!("Launch options were processed, but Steam could not be reopened: {error}"), + ); + } + result +} + +pub fn apply_launch_profile(app: &AppState, profile_id: &str) -> Result<(), String> { + let option = + profile_option(profile_id).ok_or_else(|| "Unsupported launch profile.".to_string())?; + apply_launch_options(app, option, false) +} + +fn open_workshop_page(workshop_id: &str) -> Result<(), String> { + let uri = format!( + "steam://openurl/https://steamcommunity.com/sharedfiles/filedetails/?id={workshop_id}" + ); + #[cfg(windows)] + let result = steam_executable() + .ok_or_else(|| "Steam executable was not found.".to_string()) + .and_then(|executable| { + Command::new(executable) + .arg(&uri) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + .map_err(|error| error.to_string()) + }); + #[cfg(all(unix, not(target_os = "macos")))] + let result = if let Some(executable) = steam_executable() { + Command::new(executable) + .arg(&uri) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + .map_err(|error| error.to_string()) + } else if let Some(xdg_open) = find_on_path("xdg-open") { + Command::new(xdg_open) + .arg(&uri) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + .map_err(|error| error.to_string()) + } else { + Err("No Steam URI launcher was found.".into()) + }; + #[cfg(target_os = "macos")] + let result = Command::new("open") + .arg(&uri) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + .map_err(|error| error.to_string()); + result.map(|_| ()) +} + +pub fn install_workshop_profile(app: &AppState, profile_id: &str) -> Result<(), String> { + let profile = workshop_profile(profile_id) + .ok_or_else(|| "Select an installable Workshop mod.".to_string())?; + apply_launch_profile(app, profile_id)?; + open_workshop_page(profile.workshop_id)?; + app.log( + "Info", + format!("Opened {} Workshop page in Steam.", profile.label), + ); + Ok(()) +} + +pub fn launch_game(app: &AppState) -> Result<(), String> { + #[cfg(windows)] + let result = steam_executable() + .ok_or_else(|| "Steam executable was not found.".to_string()) + .and_then(|executable| { + Command::new(executable) + .args(["-applaunch", APP_ID]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + .map_err(|error| error.to_string()) + }); + #[cfg(all(unix, not(target_os = "macos")))] + let result = if let Some(executable) = steam_executable() { + Command::new(executable) + .args(["-applaunch", APP_ID]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + .map_err(|error| error.to_string()) + } else if let Some(xdg_open) = find_on_path("xdg-open") { + Command::new(xdg_open) + .arg(format!("steam://rungameid/{APP_ID}")) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + .map_err(|error| error.to_string()) + } else { + Err("No Steam launcher was found.".into()) + }; + #[cfg(target_os = "macos")] + let result = Command::new("open") + .arg(format!("steam://rungameid/{APP_ID}")) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + .map_err(|error| error.to_string()); + + result.map(|_| { + app.log( + "Success", + format!("Launched Black Ops III via Steam (AppID: {APP_ID})."), + ); + }) +} + +pub fn compatible_depot_candidates() -> Vec { + let mut roots = library_paths(); + if let Some(root) = steam_root() { + roots.push(root); + } + #[cfg(windows)] + roots.extend(windows_steam_roots()); + let mut seen = HashSet::new(); + roots + .into_iter() + .map(|root| { + root.join("steamapps/content") + .join(format!("app_{COMPATIBLE_DEPOT_APP_ID}")) + .join(format!("depot_{COMPATIBLE_DEPOT_ID}")) + }) + .filter(|path| seen.insert(normalized_path_key(path))) + .collect() +} + +pub fn compatible_depot_download_exists() -> bool { + compatible_depot_candidates().into_iter().any(|directory| { + let executable = directory.join("BlackOps3.exe"); + executable + .metadata() + .is_ok_and(|metadata| metadata.is_file() && metadata.len() > 0) + && directory.join("installscript_311210.vdf").is_file() + }) +} + +pub fn find_valid_compatible_depot() -> Option { + compatible_depot_candidates() + .into_iter() + .filter(|directory| { + fs_ops::sha256_file(&directory.join("BlackOps3.exe")) + .is_ok_and(|hash| hash.eq_ignore_ascii_case(COMPATIBLE_BUILD_SHA256)) + }) + .max_by_key(|directory| { + directory + .join("BlackOps3.exe") + .metadata() + .and_then(|metadata| metadata.modified()) + .unwrap_or(UNIX_EPOCH) + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::time::SystemTime; + + pub(super) fn fixture_dir(name: &str) -> PathBuf { + let path = env::temp_dir().join(format!( + "patchops-steam-{name}-{}-{}", + std::process::id(), + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_nanos() + )); + fs::create_dir_all(&path).unwrap(); + path + } + + pub(super) fn string_value<'a>(document: &'a VdfObject, path: &[&str]) -> &'a str { + match value_at(document, path).unwrap() { + VdfValue::String(value) => value, + VdfValue::Object(_) => panic!("expected string"), + } + } + + #[cfg(unix)] + pub(super) fn steam_config(options: &str) -> String { + format!( + r#""UserLocalConfigStore" {{ + "Software" {{ "Valve" {{ "Steam" {{ "apps" {{ + "{APP_ID}" {{ "LaunchOptions" {} }} + }} }} }} }} +}}"#, + quote_vdf(options) + ) + } + + #[test] + fn vdf_launch_update_preserves_unknown_fields_and_rolling_backup() { + let root = fixture_dir("vdf"); + let config = root.join("localconfig.vdf"); + let legacy = root.join("backups/localconfig_backup.vdf"); + let fixture = r#" +"UserLocalConfigStore" +{ + "UnknownTop" "keep me" + "Software" + { + "Valve" + { + "Steam" + { + "apps" + { + "311210" + { + "LaunchOptions" "-novid +set fs_game old" + "Cloud" { "enabled" "1" } + } + } + } + } + } +} +"OtherRoot" "still here" [$LINUX] +"#; + fs::write(&config, fixture).unwrap(); + + let options = set_launch_options_at( + &config, + &legacy, + APP_ID, + "+set fs_game 2994481309", + false, + false, + ) + .unwrap(); + assert_eq!(options, "-novid +set fs_game 2994481309"); + let updated = read_vdf(&config).unwrap(); + assert_eq!( + string_value(&updated, &["UserLocalConfigStore", "UnknownTop"]), + "keep me" + ); + assert_eq!( + string_value( + &updated, + &[ + "UserLocalConfigStore", + "Software", + "Valve", + "Steam", + "apps", + APP_ID, + "Cloud", + "enabled", + ], + ), + "1" + ); + assert_eq!(string_value(&updated, &["OtherRoot"]), "still here"); + assert_eq!(fs::read(&legacy).unwrap(), fixture.as_bytes()); + assert!(!fs_ops::backup_path(&config).exists()); + let first_update = fs::read(&config).unwrap(); + set_launch_options_at(&config, &legacy, APP_ID, "", true, false).unwrap(); + assert_eq!(fs::read(&legacy).unwrap(), first_update); + assert_eq!(fs::read(&config).unwrap(), first_update); + set_launch_options_at(&config, &legacy, APP_ID, "+set fs_game next", false, false).unwrap(); + assert_eq!(fs::read(&legacy).unwrap(), first_update); + fs::remove_dir_all(root).unwrap(); + } + + #[test] + fn launch_backup_failure_leaves_steam_config_unchanged() { + let root = fixture_dir("launch-backup-failure"); + let config = root.join("localconfig.vdf"); + let original = b"\"UserLocalConfigStore\" { \"unknown\" \"keep\" }\n"; + fs::write(&config, original).unwrap(); + let blocked = root.join("blocked"); + fs::write(&blocked, b"not a directory").unwrap(); + assert!( + set_launch_options_at( + &config, + &blocked.join("backup.vdf"), + APP_ID, + "-novid", + false, + false + ) + .is_err() + ); + assert_eq!(fs::read(&config).unwrap(), original); + assert!(!fs_ops::backup_path(&config).exists()); + fs::remove_dir_all(root).unwrap(); + } + + #[cfg(unix)] + #[test] + fn rolling_launch_backup_preserves_source_permissions() { + use std::os::unix::fs::PermissionsExt; + let root = fixture_dir("launch-backup-mode"); + let config = root.join("localconfig.vdf"); + let backup = root.join("backups/backup.vdf"); + fs::write(&config, b"\"UserLocalConfigStore\" {}\n").unwrap(); + fs::set_permissions(&config, fs::Permissions::from_mode(0o600)).unwrap(); + set_launch_options_at(&config, &backup, APP_ID, "-novid", false, false).unwrap(); + assert_eq!( + config.metadata().unwrap().permissions().mode() & 0o777, + 0o600 + ); + assert_eq!( + backup.metadata().unwrap().permissions().mode() & 0o777, + 0o600 + ); + fs::set_permissions(&config, fs::Permissions::from_mode(0o640)).unwrap(); + let first = fs::read(&config).unwrap(); + set_launch_options_at(&config, &backup, APP_ID, "", true, false).unwrap(); + assert_eq!(fs::read(&backup).unwrap(), first); + assert_eq!( + backup.metadata().unwrap().permissions().mode() & 0o777, + 0o640 + ); + fs::remove_dir_all(root).unwrap(); + } + + #[test] + fn discovers_modern_and_legacy_library_folders_and_game() { + let root = fixture_dir("libraries"); + let primary = root.join("primary"); + let legacy = root.join("legacy"); + let modern = root.join("modern"); + for directory in [&primary, &legacy, &modern] { + fs::create_dir_all(directory.join("steamapps")).unwrap(); + } + fs::write( + primary.join("steamapps/libraryfolders.vdf"), + format!( + "\"libraryfolders\"\n{{\n\"0\" \"{}\"\n\"1\" {{ \"path\" \"{}\" \"apps\" {{ \"311210\" \"1\" }} }}\n}}", + legacy.display(), + modern.display() + ), + ) + .unwrap(); + let game = modern.join("steamapps/common").join(GAME_DIRECTORY); + fs::create_dir_all(&game).unwrap(); + fs::write(game.join("BlackOps3.exe"), b"exe").unwrap(); + + let libraries = library_paths_from_roots(vec![primary.clone()]); + assert_eq!(libraries, vec![primary, legacy, modern.clone()]); + assert!(has_game_executable( + &modern.join("steamapps/common").join(GAME_DIRECTORY) + )); + fs::remove_dir_all(root).unwrap(); + } + + #[test] + fn selects_only_numeric_steam_users_deterministically() { + let root = fixture_dir("users"); + for name in ["config", "200", "00010", "9x", "9"] { + fs::create_dir_all(root.join("userdata").join(name)).unwrap(); + } + assert_eq!(user_id_in(&root).as_deref(), Some("9")); + fs::remove_dir_all(root).unwrap(); + } + + #[test] + fn selects_most_recent_login_user_when_present() { + let root = fixture_dir("recent-user"); + fs::create_dir_all(root.join("userdata/10")).unwrap(); + fs::create_dir_all(root.join("userdata/20")).unwrap(); + fs::create_dir_all(root.join("config")).unwrap(); + fs::write( + root.join("config/loginusers.vdf"), + format!( + r#""users" {{ + "{}" {{ "MostRecent" "0" }} + "{}" {{ "MostRecent" "1" }} +}}"#, + STEAM_ID64_OFFSET + 10, + STEAM_ID64_OFFSET + 20, + ), + ) + .unwrap(); + + assert_eq!(user_id_in(&root).as_deref(), Some("20")); + fs::remove_dir_all(root).unwrap(); + } + + #[test] + fn reports_subscribed_then_installed_workshop_state() { + let root = fixture_dir("workshop"); + let workshop = root.join("steamapps/workshop"); + fs::create_dir_all(&workshop).unwrap(); + fs::write( + workshop.join("appworkshop_311210.acf"), + r#""AppWorkshop" { "WorkshopItemsInstalled" { "2994481309" { "size" "4" } } }"#, + ) + .unwrap(); + let libraries = vec![root.clone()]; + let state = workshop_item_state_in(&libraries, APP_ID, "2994481309"); + assert_eq!(state.state, "Subscribed (not installed yet)"); + assert!(state.subscribed); + assert!(!state.installed); + + let content = workshop.join("content/311210/2994481309"); + fs::create_dir_all(&content).unwrap(); + fs::write(content.join("mod.ff"), b"data").unwrap(); + let state = workshop_item_state_in(&libraries, APP_ID, "2994481309"); + assert_eq!(state.state, "Installed"); + assert!(state.installed); + assert_eq!(state.path.as_deref(), Some(content.as_path())); + fs::remove_dir_all(root).unwrap(); + } + + #[test] + fn merge_matches_legacy_launch_option_rules() { + assert_eq!( + merged_launch_options( + "WINEDLLOVERRIDES=\"dsound=n,b\" %command% -novid +set fs_game old", + "+set fs_game new", + false, + ), + "WINEDLLOVERRIDES=\"dsound=n,b\" %command% -novid +set fs_game new" + ); + assert_eq!( + merged_launch_options("-novid +set fs_game old", "", true), + "-novid +set fs_game old" + ); + } +} diff --git a/native/core/src/steam/linux.rs b/native/core/src/steam/linux.rs new file mode 100644 index 0000000..d4a8c6e --- /dev/null +++ b/native/core/src/steam/linux.rs @@ -0,0 +1,2887 @@ +//! Linux Proton installation, compatibility mappings, and rollback transactions. + +use super::{ + APP_ID, AppState, VdfEntry, VdfObject, VdfValue, close_steam, ensure_path, entry_index, fs_ops, + local_config_path_in, open_steam, parse_vdf, read_launch_options_at, read_vdf, serialize_vdf, + set_launch_options_at, set_string, steam_root, update_vdf_with_backup, user_id_in, value_at, +}; +use serde::{Deserialize, Serialize}; +use serde_json::{Map as JsonMap, Value as JsonValue, json}; +use std::{ + collections::HashSet, + fs, + path::{Path, PathBuf}, + time::{SystemTime, UNIX_EPOCH}, +}; + +const ENHANCED_TOOL_NAME: &str = "BO3 Enhanced"; +const ENHANCED_LAUNCH_OPTIONS: &str = "WINEDLLOVERRIDES=\"WindowsCodecs=n,b\" %command%"; + +const ENHANCED_PROTON_TAG: &str = "release10-32"; +const ENHANCED_PROTON_ASSET: &str = "GDK-Proton10-32.tar.gz"; +const ENHANCED_PROTON_DOWNLOAD: &str = "https://github.com/Weather-OS/GDK-Proton/releases/download/release10-32/GDK-Proton10-32.tar.gz"; +const ENHANCED_PROTON_SHA256: &str = + "1e80f4e714f877f42101d5775bd38ca0a15a38d304e24af1f15c6deec4ebac2d"; +const MAX_PROTON_ARCHIVE_BYTES: u64 = 4 * 1024 * 1024 * 1024; + +const TOOL_OWNERSHIP_VERSION: u8 = 1; +const TOOL_MARKER_FILENAME: &str = ".patchops-owner.json"; + +fn set_value(object: &mut VdfObject, key: &str, value: VdfValue) { + if let Some(index) = entry_index(object, key) { + object[index].value = value; + } else { + object.push(VdfEntry { + key: key.into(), + value, + condition: None, + }); + } +} + +fn update_vdf( + path: &Path, + legacy_backup: Option<&Path>, + change: impl FnOnce(&mut VdfObject) -> Result<(), String>, +) -> Result<(), String> { + update_vdf_with_backup(path, legacy_backup, false, change) +} + +fn write_new_or_update_vdf( + path: &Path, + change: impl FnOnce(&mut VdfObject) -> Result<(), String>, +) -> Result<(), String> { + if path.exists() { + return update_vdf(path, None, change); + } + let mut document = Vec::new(); + change(&mut document)?; + let updated = serialize_vdf(&document); + parse_vdf(&updated).map_err(|error| format!("refusing to write invalid VDF: {error}"))?; + fs_ops::atomic_write(path, updated.as_bytes()) +} + +pub(super) fn wait_for_steam_exit_with( + checks: usize, + mut is_running: impl FnMut() -> Result, + mut pause: impl FnMut(), +) -> Result<(), String> { + for check in 0..checks { + if !is_running()? { + return Ok(()); + } + if check + 1 < checks { + pause(); + } + } + Err("Steam did not exit before the configuration timeout.".into()) +} + +pub(super) fn linux_steam_running() -> Result { + for entry in fs::read_dir("/proc").map_err(|error| format!("/proc: {error}"))? { + let Ok(entry) = entry else { + continue; + }; + if !entry + .file_name() + .to_string_lossy() + .bytes() + .all(|byte| byte.is_ascii_digit()) + { + continue; + } + if fs::read_to_string(entry.path().join("comm")).is_ok_and(|name| name.trim() == "steam") { + return Ok(true); + } + } + Ok(false) +} + +fn vdf_to_json(value: &VdfValue) -> JsonValue { + match value { + VdfValue::String(value) => JsonValue::String(value.clone()), + VdfValue::Object(object) => { + let mut values = JsonMap::new(); + for entry in object { + values.insert(entry.key.clone(), vdf_to_json(&entry.value)); + } + JsonValue::Object(values) + } + } +} + +fn json_to_vdf(value: &JsonValue) -> Option { + match value { + JsonValue::String(value) => Some(VdfValue::String(value.clone())), + JsonValue::Object(object) => Some(VdfValue::Object( + object + .iter() + .filter_map(|(key, value)| { + Some(VdfEntry { + key: key.clone(), + value: json_to_vdf(value)?, + condition: None, + }) + }) + .collect(), + )), + JsonValue::Bool(value) => Some(VdfValue::String(value.to_string())), + JsonValue::Number(value) => Some(VdfValue::String(value.to_string())), + _ => None, + } +} + +fn atomic_json(path: &Path, value: &JsonValue) -> Result<(), String> { + let bytes = serde_json::to_vec_pretty(value).map_err(|error| error.to_string())?; + fs_ops::atomic_write(path, &bytes) +} + +fn mapping_snapshot_path(data_dir: &Path) -> PathBuf { + data_dir.join(format!("backups/compat_mapping_{APP_ID}.json")) +} + +fn launch_snapshot_path(data_dir: &Path) -> PathBuf { + data_dir.join(format!("backups/launch_options_{APP_ID}.json")) +} + +fn tool_ownership_path(data_dir: &Path) -> PathBuf { + data_dir.join(format!("backups/compat_tool_{APP_ID}.json")) +} + +fn required_json(path: &Path) -> Result { + let metadata = + fs::symlink_metadata(path).map_err(|error| format!("{}: {error}", path.display()))?; + if metadata.file_type().is_symlink() || !metadata.is_file() { + return Err(format!("{} is not a regular file", path.display())); + } + let body = fs::read(path).map_err(|error| format!("{}: {error}", path.display()))?; + serde_json::from_slice(&body) + .map_err(|error| format!("{} is not valid ownership data: {error}", path.display())) +} + +fn enhanced_mapping_value() -> VdfValue { + VdfValue::Object(vec![ + VdfEntry { + key: "name".into(), + value: VdfValue::String(ENHANCED_TOOL_NAME.into()), + condition: None, + }, + VdfEntry { + key: "config".into(), + value: VdfValue::String(String::new()), + condition: None, + }, + VdfEntry { + key: "priority".into(), + value: VdfValue::String("250".into()), + condition: None, + }, + ]) +} + +fn mapping_restore_value(snapshot: &Path) -> Result<(bool, Option), String> { + let saved = required_json(snapshot)?; + let object = saved + .as_object() + .ok_or_else(|| format!("{} has invalid mapping ownership data", snapshot.display()))?; + let had_entry = object + .get("had_entry") + .and_then(JsonValue::as_bool) + .ok_or_else(|| format!("{} has invalid mapping ownership data", snapshot.display()))?; + let previous = object.get("entry").and_then(json_to_vdf); + if had_entry && previous.is_none() { + return Err(format!( + "{} is missing the original compatibility mapping", + snapshot.display() + )); + } + Ok((had_entry, previous)) +} + +fn set_compatibility_mapping_at( + config: &Path, + legacy_backup: &Path, + snapshot: &Path, +) -> Result<(), String> { + let map_path = [ + "InstallConfigStore", + "Software", + "Valve", + "Steam", + "CompatToolMapping", + ]; + match fs::symlink_metadata(snapshot) { + Ok(_) => { + mapping_restore_value(snapshot)?; + let document = read_vdf(config)?; + let mut managed_path = map_path.to_vec(); + managed_path.push(APP_ID); + if value_at(&document, &managed_path) != Some(&enhanced_mapping_value()) { + return Err( + "Steam compatibility mapping changed after PatchOpsIII configured it; refusing to overwrite it." + .into(), + ); + } + } + Err(error) if error.kind() == std::io::ErrorKind::NotFound => { + let document = read_vdf(config)?; + let previous = match value_at(&document, &map_path) { + Some(VdfValue::Object(map)) => { + entry_index(map, APP_ID).map(|index| map[index].value.clone()) + } + Some(VdfValue::String(_)) => { + return Err("Steam CompatToolMapping is not an object".into()); + } + None => None, + }; + atomic_json( + snapshot, + &json!({ + "had_entry": previous.is_some(), + "entry": previous.as_ref().map(vdf_to_json).unwrap_or(JsonValue::Null), + }), + )?; + } + Err(error) => return Err(format!("{}: {error}", snapshot.display())), + } + + update_vdf(config, Some(legacy_backup), |document| { + let map = ensure_path( + document, + &[ + "InstallConfigStore", + "Software", + "Valve", + "Steam", + "CompatToolMapping", + ], + )?; + set_value(map, APP_ID, enhanced_mapping_value()); + Ok(()) + }) +} + +fn clear_compatibility_mapping_at( + config: &Path, + legacy_backup: &Path, + snapshot: &Path, +) -> Result<(), String> { + let (had_entry, previous) = mapping_restore_value(snapshot)?; + let document = read_vdf(config)?; + let map_path = [ + "InstallConfigStore", + "Software", + "Valve", + "Steam", + "CompatToolMapping", + ]; + let mut managed_path = map_path.to_vec(); + managed_path.push(APP_ID); + if value_at(&document, &managed_path) != Some(&enhanced_mapping_value()) { + return Err( + "Steam compatibility mapping changed after PatchOpsIII configured it; leaving it untouched." + .into(), + ); + } + + update_vdf(config, Some(legacy_backup), |document| { + let map = ensure_path( + document, + &[ + "InstallConfigStore", + "Software", + "Valve", + "Steam", + "CompatToolMapping", + ], + )?; + if let (true, Some(previous)) = (had_entry, previous) { + set_value(map, APP_ID, previous); + } else { + map.retain(|entry| !entry.key.eq_ignore_ascii_case(APP_ID)); + } + Ok(()) + })?; + if snapshot.exists() { + fs::remove_file(snapshot).map_err(|error| format!("{}: {error}", snapshot.display()))?; + } + Ok(()) +} + +fn update_compatibility_manifest(path: &Path) -> Result<(), String> { + write_new_or_update_vdf(path, |document| { + let tools = ensure_path(document, &["compatibilitytools", "compat_tools"])?; + let index = entry_index(tools, ENHANCED_TOOL_NAME).or_else(|| { + tools + .iter() + .position(|entry| matches!(entry.value, VdfValue::Object(_))) + }); + let index = match index { + Some(index) => { + tools[index].key = ENHANCED_TOOL_NAME.into(); + index + } + None => { + tools.push(VdfEntry { + key: ENHANCED_TOOL_NAME.into(), + value: VdfValue::Object(Vec::new()), + condition: None, + }); + tools.len() - 1 + } + }; + let VdfValue::Object(tool) = &mut tools[index].value else { + return Err("compatibility tool manifest entry is not an object".into()); + }; + for (key, default) in [ + ("install_path", "."), + ("display_name", ENHANCED_TOOL_NAME), + ("from_oslist", "windows"), + ("to_oslist", "linux"), + ] { + let value = if matches!(key, "install_path" | "from_oslist" | "to_oslist") { + match value_at(tool, &[key]) { + Some(VdfValue::String(value)) => value.clone(), + _ => default.into(), + } + } else { + default.into() + }; + set_string(tool, key, value)?; + } + Ok(()) + }) +} + +fn copy_directory(source: &Path, destination: &Path) -> Result<(), String> { + let metadata = fs::symlink_metadata(source).map_err(|error| error.to_string())?; + if metadata.file_type().is_symlink() || !metadata.is_dir() { + return Err(format!( + "Compatibility tool source is not a safe directory: {}", + source.display() + )); + } + let root = source.canonicalize().map_err(|error| error.to_string())?; + copy_directory_inside(&root, &root, destination, &mut HashSet::new()) +} + +fn copy_directory_inside( + root: &Path, + source: &Path, + destination: &Path, + active: &mut HashSet, +) -> Result<(), String> { + let resolved = source.canonicalize().map_err(|error| error.to_string())?; + if !resolved.starts_with(root) { + return Err(format!( + "Compatibility tool link escapes its source: {}", + source.display() + )); + } + if !active.insert(resolved.clone()) { + return Err(format!( + "Compatibility tool contains a directory-link cycle: {}", + source.display() + )); + } + fs::create_dir_all(destination).map_err(|error| error.to_string())?; + let result = (|| { + for entry in + fs::read_dir(&resolved).map_err(|error| format!("{}: {error}", source.display()))? + { + let entry = entry.map_err(|error| error.to_string())?; + let source_path = entry.path(); + let destination_path = destination.join(entry.file_name()); + let metadata = fs::symlink_metadata(&source_path).map_err(|error| error.to_string())?; + let resolved_path = if metadata.file_type().is_symlink() { + let target = fs::read_link(&source_path).map_err(|error| error.to_string())?; + if target.is_absolute() { + return Err(format!( + "Compatibility tool contains an absolute link: {}", + source_path.display() + )); + } + source_path + .canonicalize() + .map_err(|error| format!("{}: {error}", source_path.display()))? + } else { + source_path.clone() + }; + if !resolved_path.starts_with(root) { + return Err(format!( + "Compatibility tool link escapes its source: {}", + source_path.display() + )); + } + let resolved_metadata = + fs::metadata(&resolved_path).map_err(|error| error.to_string())?; + if resolved_metadata.is_dir() { + copy_directory_inside(root, &resolved_path, &destination_path, active)?; + } else if resolved_metadata.is_file() { + fs::copy(&resolved_path, &destination_path).map_err(|error| error.to_string())?; + } else { + return Err(format!( + "unsupported compatibility tool file: {}", + source_path.display() + )); + } + } + Ok(()) + })(); + active.remove(&resolved); + result +} + +fn unique_suffix() -> String { + format!( + "{}-{}", + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_nanos(), + std::process::id() + ) +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +struct ToolOwnership { + version: u8, + transaction_id: String, + original_backup: Option, +} + +#[derive(Debug)] +struct ToolInstallTransaction { + destination: PathBuf, + displaced_managed: Option, + original_backup: Option, + ownership_path: PathBuf, + transaction_id: String, + new_ownership: bool, +} + +#[derive(Debug)] +struct ToolRemovalTransaction { + destination: PathBuf, + displaced_managed: Option, + restored_backup: Option, + ownership_path: PathBuf, + transaction_id: String, +} + +#[derive(Debug)] +struct LegacyToolAdoption { + destination: PathBuf, + ownership_path: PathBuf, + transaction_id: String, +} + +fn tool_directory(path: &Path) -> Result { + match fs::symlink_metadata(path) { + Ok(metadata) if metadata.is_dir() && !metadata.file_type().is_symlink() => Ok(true), + Ok(_) => Err(format!("{} is not a safe tool directory", path.display())), + Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(false), + Err(error) => Err(format!("{}: {error}", path.display())), + } +} + +fn unique_tool_path(directory: &Path, prefix: &str) -> PathBuf { + for index in 0..1000_u16 { + let candidate = directory.join(format!("{prefix}-{}-{index}", unique_suffix())); + if fs::symlink_metadata(&candidate).is_err() { + return candidate; + } + } + directory.join(format!("{prefix}-{}", unique_suffix())) +} + +fn load_tool_ownership(path: &Path) -> Result, String> { + match fs::symlink_metadata(path) { + Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(None), + Err(error) => return Err(format!("{}: {error}", path.display())), + Ok(_) => {} + } + let value = required_json(path)?; + let ownership: ToolOwnership = serde_json::from_value(value).map_err(|error| { + format!( + "{} has invalid tool ownership data: {error}", + path.display() + ) + })?; + if ownership.version != TOOL_OWNERSHIP_VERSION + || ownership.transaction_id.is_empty() + || !ownership + .transaction_id + .bytes() + .all(|byte| byte.is_ascii_alphanumeric() || byte == b'-') + { + return Err(format!( + "{} has invalid tool ownership data", + path.display() + )); + } + if let Some(name) = &ownership.original_backup { + let path = Path::new(name); + if path.parent() != Some(Path::new("")) + || !name.starts_with(&format!("{ENHANCED_TOOL_NAME}.patchops.original-")) + { + return Err("Compatibility tool ownership contains an unsafe backup path.".into()); + } + } + Ok(Some(ownership)) +} + +fn compare_tool_trees( + expected: &Path, + actual: &Path, + allow_actual_links: bool, + ignore_source_marker: bool, +) -> Result<(), String> { + let actual_metadata = + fs::symlink_metadata(actual).map_err(|error| format!("{}: {error}", actual.display()))?; + if actual_metadata.file_type().is_symlink() || !actual_metadata.is_dir() { + return Err(format!("{} is not a safe tool directory", actual.display())); + } + let actual_root = actual.canonicalize().map_err(|error| error.to_string())?; + compare_tool_trees_inside( + expected, + actual, + Path::new(""), + &actual_root, + allow_actual_links, + ignore_source_marker, + &mut HashSet::new(), + ) +} + +#[allow(clippy::too_many_arguments)] +fn compare_tool_trees_inside( + expected: &Path, + actual: &Path, + relative: &Path, + actual_root: &Path, + allow_actual_links: bool, + ignore_source_marker: bool, + active: &mut HashSet, +) -> Result<(), String> { + let expected_metadata = fs::symlink_metadata(expected) + .map_err(|error| format!("{}: {error}", expected.display()))?; + if expected_metadata.file_type().is_symlink() { + return Err(format!( + "Trusted compatibility tool contains an unexpected link at {}.", + relative.display() + )); + } + let metadata = + fs::symlink_metadata(actual).map_err(|error| format!("{}: {error}", actual.display()))?; + let actual_resolved = if metadata.file_type().is_symlink() { + if !allow_actual_links { + return Err(format!( + "Legacy compatibility tool contains an unsafe link at {}.", + relative.display() + )); + } + let target = fs::read_link(actual).map_err(|error| error.to_string())?; + if target.is_absolute() { + return Err(format!( + "Compatibility tool contains an absolute link at {}.", + relative.display() + )); + } + actual + .canonicalize() + .map_err(|error| format!("{}: {error}", actual.display()))? + } else { + actual.to_path_buf() + }; + if !actual_resolved + .canonicalize() + .is_ok_and(|path| path.starts_with(actual_root)) + { + return Err(format!( + "Compatibility tool link escapes its source at {}.", + relative.display() + )); + } + let actual_metadata = fs::metadata(&actual_resolved).map_err(|error| error.to_string())?; + if expected_metadata.is_dir() && actual_metadata.is_dir() { + let resolved_directory = actual_resolved + .canonicalize() + .map_err(|error| error.to_string())?; + if !active.insert(resolved_directory.clone()) { + return Err(format!( + "Compatibility tool contains a directory-link cycle at {}.", + relative.display() + )); + } + let names = |directory: &Path, ignore_marker: bool| -> Result, String> { + let mut names = fs::read_dir(directory) + .map_err(|error| format!("{}: {error}", directory.display()))? + .map(|entry| { + entry + .map(|entry| entry.file_name()) + .map_err(|error| error.to_string()) + }) + .collect::, _>>()?; + if ignore_marker { + names.retain(|name| name != ".patchops-source.json"); + } + names.sort(); + Ok(names) + }; + let expected_names = names(expected, false)?; + if expected_names + != names( + &actual_resolved, + ignore_source_marker && relative.as_os_str().is_empty(), + )? + { + active.remove(&resolved_directory); + return Err(format!( + "Legacy compatibility tool contents differ at {}.", + relative.display() + )); + } + for name in expected_names { + compare_tool_trees_inside( + &expected.join(&name), + &actual.join(&name), + &relative.join(name), + actual_root, + allow_actual_links, + ignore_source_marker, + active, + )?; + } + active.remove(&resolved_directory); + return Ok(()); + } + if expected_metadata.is_file() && actual_metadata.is_file() { + if relative == Path::new("compatibilitytool.vdf") { + if read_vdf(expected)? != read_vdf(actual)? { + return Err("Legacy compatibility-tool manifest differs from release10-32.".into()); + } + } else if fs_ops::sha256_file(expected)? != fs_ops::sha256_file(actual)? { + return Err(format!( + "Legacy compatibility tool bytes differ at {}.", + relative.display() + )); + } + return Ok(()); + } + Err(format!( + "Legacy compatibility tool has an unexpected file type at {}.", + relative.display() + )) +} + +fn require_legacy_compatibility_state( + steam_config: &Path, + mapping_snapshot: &Path, + local_config: &Path, + launch_snapshot: &Path, +) -> Result<(), String> { + mapping_restore_value(mapping_snapshot)?; + launch_restore_value(launch_snapshot)?; + let document = read_vdf(steam_config)?; + if value_at( + &document, + &[ + "InstallConfigStore", + "Software", + "Valve", + "Steam", + "CompatToolMapping", + APP_ID, + ], + ) != Some(&enhanced_mapping_value()) + { + return Err("Legacy Steam compatibility mapping is not the exact managed value.".into()); + } + if read_launch_options_at(local_config, APP_ID)? != ENHANCED_LAUNCH_OPTIONS { + return Err("Legacy Steam launch options are not the exact managed value.".into()); + } + Ok(()) +} + +fn legacy_tool_backup(directory: &Path) -> Result, String> { + let entries = match fs::read_dir(directory) { + Ok(entries) => entries, + Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(None), + Err(error) => return Err(format!("{}: {error}", directory.display())), + }; + let prefix = format!("{ENHANCED_TOOL_NAME}.patchops.bak-"); + for entry in entries { + let entry = entry.map_err(|error| error.to_string())?; + if entry + .file_name() + .to_str() + .is_some_and(|name| name.starts_with(&prefix)) + { + return Ok(Some(entry.path())); + } + } + Ok(None) +} + +fn compatibility_is_fully_clean( + steam_root: &Path, + ownership_path: &Path, + steam_config: &Path, + mapping_snapshot: &Path, + local_config: &Path, + launch_snapshot: &Path, +) -> Result { + if load_tool_ownership(ownership_path)?.is_some() { + return Ok(false); + } + let destination = steam_root + .join("compatibilitytools.d") + .join(ENHANCED_TOOL_NAME); + if tool_directory(&destination)? { + return Ok(false); + } + if legacy_tool_backup(&steam_root.join("compatibilitytools.d"))?.is_some() { + return Ok(false); + } + for snapshot in [mapping_snapshot, launch_snapshot] { + match fs::symlink_metadata(snapshot) { + Ok(_) => return Ok(false), + Err(error) if error.kind() == std::io::ErrorKind::NotFound => {} + Err(error) => return Err(format!("{}: {error}", snapshot.display())), + } + } + let document = read_vdf(steam_config)?; + let managed_mapping = value_at( + &document, + &[ + "InstallConfigStore", + "Software", + "Valve", + "Steam", + "CompatToolMapping", + APP_ID, + ], + ) == Some(&enhanced_mapping_value()); + let managed_launch = read_launch_options_at(local_config, APP_ID)? == ENHANCED_LAUNCH_OPTIONS; + Ok(!managed_mapping && !managed_launch) +} + +fn adopt_legacy_compatibility_tool_at( + steam_root: &Path, + trusted_source: &Path, + ownership_path: &Path, + steam_config: &Path, + mapping_snapshot: &Path, + local_config: &Path, + launch_snapshot: &Path, +) -> Result { + if load_tool_ownership(ownership_path)?.is_some() { + return Err( + "Compatibility-tool ownership already exists; legacy adoption is invalid.".into(), + ); + } + let directory = steam_root.join("compatibilitytools.d"); + let destination = directory.join(ENHANCED_TOOL_NAME); + if !tool_directory(&destination)? { + return Err("Legacy BO3 Enhanced compatibility tool is missing.".into()); + } + match fs::symlink_metadata(destination.join(TOOL_MARKER_FILENAME)) { + Ok(_) => { + return Err( + "Untracked compatibility tool already contains an ownership marker.".into(), + ); + } + Err(error) if error.kind() == std::io::ErrorKind::NotFound => {} + Err(error) => return Err(error.to_string()), + } + if let Some(backup) = legacy_tool_backup(&directory)? { + return Err(format!( + "Legacy compatibility-tool backup is ambiguous: {}.", + backup.display() + )); + } + require_legacy_compatibility_state( + steam_config, + mapping_snapshot, + local_config, + launch_snapshot, + )?; + compare_tool_trees(trusted_source, &destination, false, false)?; + + let transaction_id = unique_suffix(); + atomic_json( + &destination.join(TOOL_MARKER_FILENAME), + &json!({ "transaction_id": transaction_id }), + )?; + if let Err(error) = save_tool_ownership( + ownership_path, + &ToolOwnership { + version: TOOL_OWNERSHIP_VERSION, + transaction_id: transaction_id.clone(), + original_backup: None, + }, + ) { + let rollback = fs::remove_file(destination.join(TOOL_MARKER_FILENAME)); + return Err(match rollback { + Ok(()) => error, + Err(rollback) => format!("{error}; removing the adoption marker failed: {rollback}"), + }); + } + Ok(LegacyToolAdoption { + destination, + ownership_path: ownership_path.to_path_buf(), + transaction_id, + }) +} + +impl LegacyToolAdoption { + fn rollback(self) -> Result<(), String> { + require_managed_tool(&self.destination, &self.transaction_id)?; + fs::remove_file(self.destination.join(TOOL_MARKER_FILENAME)) + .map_err(|error| error.to_string())?; + fs::remove_file(&self.ownership_path) + .map_err(|error| format!("{}: {error}", self.ownership_path.display())) + } +} + +fn finish_legacy_adoption( + adoption: Option, + result: Result, +) -> Result { + match (adoption, result) { + (_, Ok(value)) => Ok(value), + (None, Err(error)) => Err(error), + (Some(adoption), Err(error)) => match adoption.rollback() { + Ok(()) => Err(error), + Err(rollback) => Err(format!( + "{error}; legacy adoption rollback failed: {rollback}" + )), + }, + } +} + +fn save_tool_ownership(path: &Path, ownership: &ToolOwnership) -> Result<(), String> { + let value = serde_json::to_value(ownership).map_err(|error| error.to_string())?; + atomic_json(path, &value) +} + +fn tool_marker_id(path: &Path) -> Result { + required_json(&path.join(TOOL_MARKER_FILENAME))? + .get("transaction_id") + .and_then(JsonValue::as_str) + .filter(|id| !id.is_empty()) + .map(str::to_owned) + .ok_or_else(|| format!("{} has invalid PatchOpsIII ownership", path.display())) +} + +fn require_managed_tool(path: &Path, transaction_id: &str) -> Result<(), String> { + if tool_marker_id(path)? != transaction_id { + return Err(format!( + "{} is no longer the compatibility tool installed by PatchOpsIII; leaving it untouched", + path.display() + )); + } + Ok(()) +} + +fn exact_tool_backup( + directory: &Path, + ownership: &ToolOwnership, +) -> Result, String> { + let Some(name) = &ownership.original_backup else { + return Ok(None); + }; + let backup = directory.join(name); + if !tool_directory(&backup)? { + return Err(format!( + "The exact PatchOpsIII-owned tool backup is missing: {}", + backup.display() + )); + } + Ok(Some(backup)) +} + +fn remove_managed_tool(path: &Path, transaction_id: &str) -> Result<(), String> { + require_managed_tool(path, transaction_id)?; + fs::remove_dir_all(path).map_err(|error| format!("{}: {error}", path.display())) +} + +impl ToolInstallTransaction { + fn rollback(self) -> Result<(), String> { + remove_managed_tool(&self.destination, &self.transaction_id)?; + let restore = if self.new_ownership { + self.original_backup.as_ref() + } else { + self.displaced_managed.as_ref() + }; + if let Some(restore) = restore { + fs::rename(restore, &self.destination).map_err(|error| { + format!( + "failed to restore compatibility tool {}: {error}", + self.destination.display() + ) + })?; + } + if self.new_ownership { + fs::remove_file(&self.ownership_path) + .map_err(|error| format!("{}: {error}", self.ownership_path.display()))?; + } + Ok(()) + } + + fn commit(mut self) -> Result<(), Box<(String, Self)>> { + if let Some(displaced) = &self.displaced_managed + && let Err(error) = require_managed_tool(displaced, &self.transaction_id) + { + return Err(Box::new((error, self))); + } + if let Some(displaced) = self.displaced_managed.take() { + // Best-effort reaping keeps an unlink failure from becoming an + // unrollbackable partial commit. Add persisted cleanup if this ever leaks. + let _ = fs::remove_dir_all(displaced); + } + Ok(()) + } +} + +impl ToolRemovalTransaction { + fn rollback(self) -> Result<(), String> { + if let Some(backup) = self.restored_backup { + if tool_directory(&backup)? { + return Err(format!( + "Cannot roll back because {} already exists", + backup.display() + )); + } + fs::rename(&self.destination, &backup).map_err(|error| { + format!( + "failed to return the original compatibility tool to {}: {error}", + backup.display() + ) + })?; + } + if let Some(displaced) = self.displaced_managed { + fs::rename(&displaced, &self.destination).map_err(|error| { + format!("failed to restore the managed compatibility tool: {error}") + })?; + } + Ok(()) + } + + fn commit(mut self) -> Result<(), Box<(String, Self)>> { + if let Some(displaced) = &self.displaced_managed + && let Err(error) = require_managed_tool(displaced, &self.transaction_id) + { + return Err(Box::new((error, self))); + } + if let Err(error) = fs::remove_file(&self.ownership_path) { + return Err(Box::new(( + format!("{}: {error}", self.ownership_path.display()), + self, + ))); + } + if let Some(displaced) = self.displaced_managed.take() { + // Best-effort reaping keeps the ownership commit rollback-safe. + // Add persisted cleanup if managed leftovers become measurable. + let _ = fs::remove_dir_all(displaced); + } + Ok(()) + } +} + +fn install_compatibility_tool_at( + steam_root: &Path, + source: &Path, + ownership_path: &Path, +) -> Result { + if !source.is_dir() { + return Err(format!( + "Compatibility tool source not found: {}", + source.display() + )); + } + let directory = steam_root.join("compatibilitytools.d"); + fs::create_dir_all(&directory).map_err(|error| error.to_string())?; + let destination = directory.join(ENHANCED_TOOL_NAME); + let existing_ownership = load_tool_ownership(ownership_path)?; + let new_ownership = existing_ownership.is_none(); + let transaction_id = existing_ownership + .as_ref() + .map(|ownership| ownership.transaction_id.clone()) + .unwrap_or_else(unique_suffix); + let original_backup = match &existing_ownership { + Some(ownership) => exact_tool_backup(&directory, ownership)?, + None if tool_directory(&destination)? => Some(unique_tool_path( + &directory, + &format!("{ENHANCED_TOOL_NAME}.patchops.original"), + )), + None => None, + }; + if existing_ownership.is_some() && tool_directory(&destination)? { + require_managed_tool(&destination, &transaction_id)?; + } + + let staging = unique_tool_path(&directory, &format!("{ENHANCED_TOOL_NAME}.patchops.tmp")); + if let Err(error) = copy_directory(source, &staging) { + let _ = fs::remove_dir_all(&staging); + return Err(error); + } + if let Err(error) = update_compatibility_manifest(&staging.join("compatibilitytool.vdf")) { + let _ = fs::remove_dir_all(&staging); + return Err(error); + } + if let Err(error) = atomic_json( + &staging.join(TOOL_MARKER_FILENAME), + &json!({ "transaction_id": transaction_id }), + ) { + let _ = fs::remove_dir_all(&staging); + return Err(error); + } + + if new_ownership { + let original_backup_name = original_backup + .as_ref() + .and_then(|path| path.file_name()) + .and_then(|name| name.to_str()) + .map(str::to_owned); + if let Err(error) = save_tool_ownership( + ownership_path, + &ToolOwnership { + version: TOOL_OWNERSHIP_VERSION, + transaction_id: transaction_id.clone(), + original_backup: original_backup_name, + }, + ) { + let _ = fs::remove_dir_all(&staging); + return Err(error); + } + } + + let displaced_managed = if !new_ownership && tool_directory(&destination)? { + let displaced = unique_tool_path( + &directory, + &format!("{ENHANCED_TOOL_NAME}.patchops.rollback"), + ); + if let Err(error) = fs::rename(&destination, &displaced) { + let _ = fs::remove_dir_all(&staging); + return Err(error.to_string()); + } + Some(displaced) + } else { + None + }; + if new_ownership + && let Some(backup) = &original_backup + && let Err(error) = fs::rename(&destination, backup) + { + let _ = fs::remove_dir_all(&staging); + let _ = fs::remove_file(ownership_path); + return Err(error.to_string()); + } + if let Err(error) = fs::rename(&staging, &destination) { + let _ = fs::remove_dir_all(&staging); + let restore = if new_ownership { + original_backup.as_ref() + } else { + displaced_managed.as_ref() + }; + if let Some(restore) = restore + && let Err(rollback_error) = fs::rename(restore, &destination) + { + return Err(format!( + "{error}; restoring the previous compatibility tool also failed: {rollback_error}" + )); + } + if new_ownership { + let _ = fs::remove_file(ownership_path); + } + return Err(error.to_string()); + } + Ok(ToolInstallTransaction { + destination, + displaced_managed, + original_backup, + ownership_path: ownership_path.to_path_buf(), + transaction_id, + new_ownership, + }) +} + +fn remove_compatibility_tool_at( + steam_root: &Path, + ownership_path: &Path, +) -> Result { + let directory = steam_root.join("compatibilitytools.d"); + let ownership = load_tool_ownership(ownership_path)?.ok_or_else(|| { + "No PatchOpsIII compatibility-tool ownership record exists; refusing unsafe cleanup." + .to_string() + })?; + let destination = directory.join(ENHANCED_TOOL_NAME); + let original_backup = exact_tool_backup(&directory, &ownership)?; + let displaced_managed = if tool_directory(&destination)? { + require_managed_tool(&destination, &ownership.transaction_id)?; + let displaced = + unique_tool_path(&directory, &format!("{ENHANCED_TOOL_NAME}.patchops.remove")); + fs::rename(&destination, &displaced).map_err(|error| error.to_string())?; + Some(displaced) + } else { + None + }; + if let Some(backup) = &original_backup + && let Err(error) = fs::rename(backup, &destination) + { + if let Some(displaced) = &displaced_managed + && let Err(rollback) = fs::rename(displaced, &destination) + { + return Err(format!( + "{error}; restoring the managed compatibility tool also failed: {rollback}" + )); + } + return Err(error.to_string()); + } + Ok(ToolRemovalTransaction { + destination, + displaced_managed, + restored_backup: original_backup, + ownership_path: ownership_path.to_path_buf(), + transaction_id: ownership.transaction_id, + }) +} + +#[derive(Clone)] +struct FileSnapshot { + path: PathBuf, + contents: Option>, +} + +fn capture_files(paths: &[PathBuf]) -> Result, String> { + paths + .iter() + .map(|path| { + let contents = match fs::symlink_metadata(path) { + Ok(metadata) if metadata.is_file() && !metadata.file_type().is_symlink() => { + Some(fs::read(path).map_err(|error| { + format!("failed to snapshot {}: {error}", path.display()) + })?) + } + Ok(_) => { + return Err(format!( + "Refusing to snapshot unsafe transaction path: {}", + path.display() + )); + } + Err(error) if error.kind() == std::io::ErrorKind::NotFound => None, + Err(error) => return Err(format!("{}: {error}", path.display())), + }; + Ok(FileSnapshot { + path: path.clone(), + contents, + }) + }) + .collect() +} + +fn restore_files(snapshots: &[FileSnapshot]) -> Result<(), String> { + let mut errors = Vec::new(); + for snapshot in snapshots.iter().rev() { + let result = match &snapshot.contents { + Some(contents) => fs_ops::atomic_write(&snapshot.path, contents), + None => match fs::symlink_metadata(&snapshot.path) { + Ok(metadata) if metadata.is_file() && !metadata.file_type().is_symlink() => { + fs::remove_file(&snapshot.path).map_err(|error| error.to_string()) + } + Ok(_) => Err("transaction created an unsafe path".into()), + Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()), + Err(error) => Err(error.to_string()), + }, + }; + if let Err(error) = result { + errors.push(format!("{}: {error}", snapshot.path.display())); + } + } + if errors.is_empty() { + Ok(()) + } else { + Err(errors.join("; ")) + } +} + +fn launch_restore_value(snapshot: &Path) -> Result { + required_json(snapshot)? + .get("launch_options") + .and_then(JsonValue::as_str) + .map(str::to_owned) + .ok_or_else(|| { + format!( + "{} has invalid launch-option ownership data", + snapshot.display() + ) + }) +} + +fn set_enhanced_launch_options_at( + config: &Path, + legacy_backup: &Path, + snapshot: &Path, +) -> Result<(), String> { + match fs::symlink_metadata(snapshot) { + Ok(_) => { + launch_restore_value(snapshot)?; + if read_launch_options_at(config, APP_ID)? != ENHANCED_LAUNCH_OPTIONS { + return Err( + "Steam launch options changed after PatchOpsIII configured them; refusing to overwrite them." + .into(), + ); + } + } + Err(error) if error.kind() == std::io::ErrorKind::NotFound => { + let previous = read_launch_options_at(config, APP_ID)?; + atomic_json(snapshot, &json!({ "launch_options": previous }))?; + } + Err(error) => return Err(format!("{}: {error}", snapshot.display())), + } + set_launch_options_at( + config, + legacy_backup, + APP_ID, + ENHANCED_LAUNCH_OPTIONS, + false, + true, + ) + .map(|_| ()) +} + +fn restore_enhanced_launch_options_at( + config: &Path, + legacy_backup: &Path, + snapshot: &Path, +) -> Result<(), String> { + let previous = launch_restore_value(snapshot)?; + if read_launch_options_at(config, APP_ID)? != ENHANCED_LAUNCH_OPTIONS { + return Err( + "Steam launch options changed after PatchOpsIII configured them; leaving them untouched." + .into(), + ); + } + set_launch_options_at(config, legacy_backup, APP_ID, &previous, false, true)?; + fs::remove_file(snapshot).map_err(|error| format!("{}: {error}", snapshot.display())) +} + +fn steam_transaction_files( + steam_config: &Path, + steam_backup: &Path, + mapping_snapshot: &Path, + local_config: &Path, + local_backup: &Path, + launch_snapshot: &Path, +) -> Vec { + vec![ + steam_config.to_path_buf(), + fs_ops::backup_path(steam_config), + steam_backup.to_path_buf(), + mapping_snapshot.to_path_buf(), + local_config.to_path_buf(), + fs_ops::backup_path(local_config), + local_backup.to_path_buf(), + launch_snapshot.to_path_buf(), + ] +} + +fn rollback_error(error: String, file_error: Option, tool_error: Option) -> String { + let mut rollbacks = Vec::new(); + if let Some(error) = file_error { + rollbacks.push(format!("Steam file rollback failed: {error}")); + } + if let Some(error) = tool_error { + rollbacks.push(format!("compatibility tool rollback failed: {error}")); + } + if rollbacks.is_empty() { + error + } else { + format!("{error}; {}", rollbacks.join("; ")) + } +} + +fn finish_tool_install( + tool: ToolInstallTransaction, + snapshots: Vec, + destination: PathBuf, + result: Result<(), String>, +) -> Result { + match result { + Ok(()) => match tool.commit() { + Ok(()) => Ok(destination), + Err(failure) => { + let (error, tool) = *failure; + let file_error = restore_files(&snapshots).err(); + let tool_error = tool.rollback().err(); + Err(rollback_error(error, file_error, tool_error)) + } + }, + Err(error) => { + let file_error = restore_files(&snapshots).err(); + let tool_error = tool.rollback().err(); + Err(rollback_error(error, file_error, tool_error)) + } + } +} + +fn finish_tool_removal( + tool: ToolRemovalTransaction, + snapshots: Vec, + result: Result<(), String>, +) -> Result<(), String> { + match result { + Ok(()) => match tool.commit() { + Ok(()) => Ok(()), + Err(failure) => { + let (error, tool) = *failure; + let file_error = restore_files(&snapshots).err(); + let tool_error = tool.rollback().err(); + Err(rollback_error(error, file_error, tool_error)) + } + }, + Err(error) => { + let file_error = restore_files(&snapshots).err(); + let tool_error = tool.rollback().err(); + Err(rollback_error(error, file_error, tool_error)) + } + } +} + +#[allow(clippy::too_many_arguments)] +fn configure_compatibility_at( + steam_root: &Path, + source: &Path, + trusted_legacy_source: Option<&Path>, + ownership_path: &Path, + steam_config: &Path, + steam_backup: &Path, + mapping_snapshot: &Path, + local_config: &Path, + local_backup: &Path, + launch_snapshot: &Path, +) -> Result { + let snapshots = capture_files(&steam_transaction_files( + steam_config, + steam_backup, + mapping_snapshot, + local_config, + local_backup, + launch_snapshot, + ))?; + let existing_ownership = load_tool_ownership(ownership_path)?; + if existing_ownership.is_some() { + mapping_restore_value(mapping_snapshot)?; + launch_restore_value(launch_snapshot)?; + } + let destination = steam_root + .join("compatibilitytools.d") + .join(ENHANCED_TOOL_NAME); + let destination_exists = match fs::symlink_metadata(&destination) { + Ok(_) => true, + Err(error) if error.kind() == std::io::ErrorKind::NotFound => false, + Err(error) => return Err(format!("{}: {error}", destination.display())), + }; + let adoption = if existing_ownership.is_none() && destination_exists { + Some(adopt_legacy_compatibility_tool_at( + steam_root, + trusted_legacy_source.ok_or_else(|| { + "The exact release10-32 source is required to adopt a legacy compatibility tool." + .to_string() + })?, + ownership_path, + steam_config, + mapping_snapshot, + local_config, + launch_snapshot, + )?) + } else { + None + }; + let result = (|| { + let tool = install_compatibility_tool_at(steam_root, source, ownership_path)?; + let destination = tool.destination.clone(); + let result = (|| { + set_compatibility_mapping_at(steam_config, steam_backup, mapping_snapshot)?; + set_enhanced_launch_options_at(local_config, local_backup, launch_snapshot) + })(); + finish_tool_install(tool, snapshots, destination, result) + })(); + finish_legacy_adoption(adoption, result) +} + +#[allow(clippy::too_many_arguments)] +fn cleanup_compatibility_at( + steam_root: &Path, + trusted_legacy_source: Option<&Path>, + ownership_path: &Path, + steam_config: &Path, + steam_backup: &Path, + mapping_snapshot: &Path, + local_config: &Path, + local_backup: &Path, + launch_snapshot: &Path, +) -> Result<(), String> { + if compatibility_is_fully_clean( + steam_root, + ownership_path, + steam_config, + mapping_snapshot, + local_config, + launch_snapshot, + )? { + return Ok(()); + } + let snapshots = capture_files(&steam_transaction_files( + steam_config, + steam_backup, + mapping_snapshot, + local_config, + local_backup, + launch_snapshot, + ))?; + let adoption = if load_tool_ownership(ownership_path)?.is_none() { + Some(adopt_legacy_compatibility_tool_at( + steam_root, + trusted_legacy_source.ok_or_else(|| { + "The exact release10-32 source is required to adopt a legacy compatibility tool." + .to_string() + })?, + ownership_path, + steam_config, + mapping_snapshot, + local_config, + launch_snapshot, + )?) + } else { + None + }; + let result = (|| { + let tool = remove_compatibility_tool_at(steam_root, ownership_path)?; + let result = (|| { + clear_compatibility_mapping_at(steam_config, steam_backup, mapping_snapshot)?; + restore_enhanced_launch_options_at(local_config, local_backup, launch_snapshot) + })(); + finish_tool_removal(tool, snapshots, result) + })(); + finish_legacy_adoption(adoption, result) +} + +fn valid_sha256(value: &str) -> bool { + value.len() == 64 && value.bytes().all(|byte| byte.is_ascii_hexdigit()) +} + +fn valid_proton_layout(path: &Path) -> bool { + let Ok(metadata) = fs::symlink_metadata(path) else { + return false; + }; + if metadata.file_type().is_symlink() || !metadata.is_dir() { + return false; + } + let Ok(root) = path.canonicalize() else { + return false; + }; + [("proton", false), ("files", true)] + .into_iter() + .all(|(name, directory)| { + let entry = path.join(name); + let Ok(resolved) = entry.canonicalize() else { + return false; + }; + resolved.starts_with(&root) + && fs::metadata(resolved).is_ok_and(|metadata| { + if directory { + metadata.is_dir() + } else { + metadata.is_file() + } + }) + }) +} + +fn valid_cached_proton(path: &Path) -> bool { + if !valid_proton_layout(path) { + return false; + } + let Ok(manifest) = read_vdf(&path.join("compatibilitytool.vdf")) else { + return false; + }; + if !matches!( + value_at( + &manifest, + &["compatibilitytools", "compat_tools", ENHANCED_TOOL_NAME] + ), + Some(VdfValue::Object(_)) + ) { + return false; + } + let Ok(marker) = required_json(&path.join(".patchops-source.json")) else { + return false; + }; + marker.get("tag").and_then(JsonValue::as_str) == Some(ENHANCED_PROTON_TAG) + && marker.get("asset").and_then(JsonValue::as_str) == Some(ENHANCED_PROTON_ASSET) + && marker + .get("sha256") + .and_then(JsonValue::as_str) + .is_some_and(|hash| hash.eq_ignore_ascii_case(ENHANCED_PROTON_SHA256)) +} + +#[derive(Debug)] +struct TrustedProtonSource { + temporary_root: PathBuf, + source: PathBuf, +} + +impl Drop for TrustedProtonSource { + fn drop(&mut self) { + let _ = fs::remove_dir_all(&self.temporary_root); + } +} + +fn trusted_proton_source_at( + cache_root: &Path, + expected_digest: &str, +) -> Result { + if !valid_sha256(expected_digest) { + return Err("Pinned GDK-Proton archive digest is invalid.".into()); + } + let archive = cache_root.join(format!("GDK-Proton-{ENHANCED_PROTON_TAG}.tar.gz")); + let metadata = fs::symlink_metadata(&archive) + .map_err(|error| format!("Verified GDK-Proton archive is unavailable: {error}"))?; + if metadata.file_type().is_symlink() || !metadata.is_file() { + return Err("Verified GDK-Proton archive is not a regular file.".into()); + } + if !fs_ops::sha256_file(&archive)?.eq_ignore_ascii_case(expected_digest) { + return Err("Cached GDK-Proton archive failed release10-32 verification.".into()); + } + + let temporary_root = cache_root + .parent() + .unwrap_or_else(|| Path::new(".")) + .join(format!(".patchops-trusted-proton-{}", unique_suffix())); + fs::create_dir(&temporary_root).map_err(|error| error.to_string())?; + let result = (|| { + let extraction = temporary_root.join("extracted"); + fs_ops::extract_tar_gz(&archive, &extraction)?; + if !fs_ops::sha256_file(&archive)?.eq_ignore_ascii_case(expected_digest) { + return Err("Cached GDK-Proton archive changed during verification.".into()); + } + let mut candidates = Vec::new(); + if valid_proton_layout(&extraction) { + candidates.push(extraction.clone()); + } + for entry in fs::read_dir(&extraction) + .map_err(|error| format!("{}: {error}", extraction.display()))? + { + let path = entry.map_err(|error| error.to_string())?.path(); + if valid_proton_layout(&path) { + candidates.push(path); + } + } + if candidates.len() != 1 { + return Err( + "Verified GDK-Proton archive did not contain one unambiguous tool directory." + .into(), + ); + } + let source = temporary_root.join("normalized"); + copy_directory(&candidates[0], &source)?; + update_compatibility_manifest(&source.join("compatibilitytool.vdf"))?; + fs::remove_dir_all(&extraction).map_err(|error| error.to_string())?; + Ok(source) + })(); + match result { + Ok(source) => Ok(TrustedProtonSource { + temporary_root, + source, + }), + Err(error) => { + let _ = fs::remove_dir_all(&temporary_root); + Err(error) + } + } +} + +fn prepare_enhanced_proton_cache(app: &AppState) -> Result { + let cache_root = app.data_dir().join("bo3-enhanced-proton-cache"); + let target = cache_root.join(ENHANCED_TOOL_NAME); + if valid_cached_proton(&target) + && let Ok(trusted) = trusted_proton_source_at(&cache_root, ENHANCED_PROTON_SHA256) + && compare_tool_trees(&trusted.source, &target, true, true).is_ok() + { + return Ok(target); + } + fs::create_dir_all(&cache_root).map_err(|error| error.to_string())?; + let digest = ENHANCED_PROTON_SHA256.to_owned(); + let archive = cache_root.join(format!("GDK-Proton-{ENHANCED_PROTON_TAG}.tar.gz")); + let archive_valid = fs::symlink_metadata(&archive).is_ok_and(|metadata| { + !metadata.file_type().is_symlink() + && metadata.is_file() + && fs_ops::sha256_file(&archive) + .is_ok_and(|actual| actual.eq_ignore_ascii_case(&digest)) + }); + if !archive_valid { + match fs::symlink_metadata(&archive) { + Ok(_) => fs::remove_file(&archive).map_err(|error| error.to_string())?, + Err(error) if error.kind() == std::io::ErrorKind::NotFound => {} + Err(error) => return Err(error.to_string()), + } + app.log( + "Info", + format!("Downloading BO3 Enhanced Proton ({ENHANCED_PROTON_TAG})..."), + ); + fs_ops::download( + app, + ENHANCED_PROTON_DOWNLOAD, + &archive, + Some(&digest), + MAX_PROTON_ARCHIVE_BYTES, + )?; + } + + let suffix = unique_suffix(); + let extraction = cache_root.join(format!("extract-{ENHANCED_PROTON_TAG}-{suffix}")); + let staging = cache_root.join(format!("{ENHANCED_TOOL_NAME}.tmp-{suffix}")); + let backup = cache_root.join(format!("{ENHANCED_TOOL_NAME}.patchops.bak-{suffix}")); + let result = (|| { + fs_ops::extract_tar_gz(&archive, &extraction)?; + let source = if valid_proton_layout(&extraction) { + extraction.clone() + } else { + fs::read_dir(&extraction) + .map_err(|error| error.to_string())? + .filter_map(Result::ok) + .map(|entry| entry.path()) + .find(|path| valid_proton_layout(path)) + .ok_or_else(|| { + "Verified GDK-Proton archive did not contain a valid tool directory." + .to_string() + })? + }; + fs::rename(&source, &staging).map_err(|error| error.to_string())?; + update_compatibility_manifest(&staging.join("compatibilitytool.vdf"))?; + atomic_json( + &staging.join(".patchops-source.json"), + &json!({ + "tag": ENHANCED_PROTON_TAG, + "asset": ENHANCED_PROTON_ASSET, + "sha256": digest, + }), + )?; + if !valid_cached_proton(&staging) { + return Err("Prepared GDK-Proton cache failed validation.".into()); + } + + let moved_existing = if tool_directory(&target)? { + fs::rename(&target, &backup).map_err(|error| error.to_string())?; + true + } else { + false + }; + if let Err(error) = fs::rename(&staging, &target) { + if moved_existing && let Err(rollback_error) = fs::rename(&backup, &target) { + return Err(format!( + "{error}; restoring the previous Proton cache also failed: {rollback_error}" + )); + } + return Err(error.to_string()); + } + if moved_existing { + let _ = fs::remove_dir_all(&backup); + } + Ok(target.clone()) + })(); + let _ = fs::remove_dir_all(&extraction); + if result.is_err() { + let _ = fs::remove_dir_all(&staging); + } + if result.is_ok() { + app.log( + "Success", + format!( + "Prepared verified BO3 Enhanced Proton cache at {}.", + target.display() + ), + ); + } + result +} + +fn resolve_enhanced_tool_source( + app: &AppState, + preferred: Option<&Path>, +) -> Result { + if let Some(preferred) = preferred { + if valid_proton_layout(preferred) { + return Ok(preferred.to_path_buf()); + } + return Err(format!( + "Compatibility tool source not found: {}", + preferred.display() + )); + } + if let Some(resources) = app.resource_dir() { + let bundled = resources.join("bo3-enhanced-proton/BO3 Enhanced"); + if valid_proton_layout(&bundled) { + return Ok(bundled); + } + } + prepare_enhanced_proton_cache(app) +} + +pub fn configure_bo3_enhanced_linux( + app: &AppState, + tool_source: Option<&Path>, +) -> Result<(), String> { + let root = steam_root().ok_or_else(|| "Steam root path could not be resolved.".to_string())?; + let user = user_id_in(&root).ok_or_else(|| "Steam user ID not found.".to_string())?; + let local_config = local_config_path_in(&root, &user) + .ok_or_else(|| "Steam userdata path not found.".to_string())?; + let steam_config = root.join("config/config.vdf"); + let steam_backup = app.data_dir().join("backups/steam_config_backup.vdf"); + let local_backup = app.data_dir().join("backups/localconfig_backup.vdf"); + let mapping_snapshot = mapping_snapshot_path(app.data_dir()); + let launch_snapshot = launch_snapshot_path(app.data_dir()); + let ownership = tool_ownership_path(app.data_dir()); + let destination = root.join("compatibilitytools.d").join(ENHANCED_TOOL_NAME); + let destination_exists = match fs::symlink_metadata(&destination) { + Ok(_) => true, + Err(error) if error.kind() == std::io::ErrorKind::NotFound => false, + Err(error) => return Err(format!("{}: {error}", destination.display())), + }; + let trusted = if load_tool_ownership(&ownership)?.is_none() && destination_exists { + Some(trusted_proton_source_at( + &app.data_dir().join("bo3-enhanced-proton-cache"), + ENHANCED_PROTON_SHA256, + )?) + } else { + None + }; + let source = match &trusted { + Some(trusted) => trusted.source.clone(), + None => resolve_enhanced_tool_source(app, tool_source)?, + }; + + close_steam(app)?; + let result = configure_compatibility_at( + &root, + &source, + trusted.as_ref().map(|trusted| trusted.source.as_path()), + &ownership, + &steam_config, + &steam_backup, + &mapping_snapshot, + &local_config, + &local_backup, + &launch_snapshot, + ) + .map(|destination| { + app.log( + "Success", + format!( + "Installed '{ENHANCED_TOOL_NAME}' to {}.", + destination.display() + ), + ); + }); + if let Err(error) = open_steam(app) { + app.log("Warning", format!("Steam could not be reopened: {error}")); + } + if result.is_ok() { + app.log( + "Success", + "Linux BO3 Enhanced compatibility tool, mapping, and launch options configured.", + ); + } + result +} + +pub fn cleanup_bo3_enhanced_linux(app: &AppState) -> Result<(), String> { + let root = steam_root().ok_or_else(|| "Steam root path could not be resolved.".to_string())?; + let user = user_id_in(&root).ok_or_else(|| "Steam user ID not found.".to_string())?; + let local_config = local_config_path_in(&root, &user) + .ok_or_else(|| "Steam userdata path not found.".to_string())?; + let steam_config = root.join("config/config.vdf"); + let steam_backup = app.data_dir().join("backups/steam_config_backup.vdf"); + let local_backup = app.data_dir().join("backups/localconfig_backup.vdf"); + let mapping_snapshot = mapping_snapshot_path(app.data_dir()); + let launch_snapshot = launch_snapshot_path(app.data_dir()); + let ownership = tool_ownership_path(app.data_dir()); + if compatibility_is_fully_clean( + &root, + &ownership, + &steam_config, + &mapping_snapshot, + &local_config, + &launch_snapshot, + )? { + app.log( + "Info", + "Linux BO3 Enhanced compatibility state was already clean.", + ); + return Ok(()); + } + let trusted = if load_tool_ownership(&ownership)?.is_none() { + Some(trusted_proton_source_at( + &app.data_dir().join("bo3-enhanced-proton-cache"), + ENHANCED_PROTON_SHA256, + )?) + } else { + None + }; + close_steam(app)?; + + let result = cleanup_compatibility_at( + &root, + trusted.as_ref().map(|trusted| trusted.source.as_path()), + &ownership, + &steam_config, + &steam_backup, + &mapping_snapshot, + &local_config, + &local_backup, + &launch_snapshot, + ); + if let Err(error) = open_steam(app) { + app.log("Warning", format!("Steam could not be reopened: {error}")); + } + if result.is_ok() { + app.log( + "Success", + "Linux BO3 Enhanced compatibility mapping, launch options, and tool removed.", + ); + } + result +} + +#[cfg(test)] +mod tests { + use super::super::tests::{fixture_dir, steam_config, string_value}; + use super::*; + + fn tool_source(root: &Path) -> PathBuf { + let source = root.join("source"); + fs::create_dir_all(source.join("files")).unwrap(); + fs::write(source.join("proton"), b"managed proton").unwrap(); + source + } + + fn normalized_tool_source(root: &Path, name: &str, proton: &[u8]) -> PathBuf { + let source = root.join(name); + fs::create_dir_all(source.join("files")).unwrap(); + fs::write(source.join("proton"), proton).unwrap(); + fs::write(source.join("files/payload"), b"release payload").unwrap(); + update_compatibility_manifest(&source.join("compatibilitytool.vdf")).unwrap(); + source + } + + fn compatibility_config() -> &'static str { + r#""InstallConfigStore" { + "Unrelated" "preserve" + "Software" { "Valve" { "Steam" { "CompatToolMapping" { + "311210" { "name" "GE-Proton" "priority" "75" "custom" "yes" } + } } } } +}"# + } + + fn enhanced_compatibility_config() -> &'static str { + r#""InstallConfigStore" +{ + "Software" + { + "Valve" + { + "Steam" + { + "CompatToolMapping" + { + "311210" { "name" "BO3 Enhanced" "config" "" "priority" "250" } + } + } + } + } +}"# + } + + struct LegacyCompatibilityFixture { + root: PathBuf, + steam: PathBuf, + trusted: PathBuf, + destination: PathBuf, + steam_vdf: PathBuf, + local_vdf: PathBuf, + steam_backup: PathBuf, + local_backup: PathBuf, + mapping_snapshot: PathBuf, + launch_snapshot: PathBuf, + ownership: PathBuf, + } + + fn legacy_compatibility_fixture(label: &str) -> LegacyCompatibilityFixture { + let root = fixture_dir(label); + let steam = root.join("steam"); + let trusted = normalized_tool_source(&root, "trusted", b"legacy proton"); + let destination = steam.join("compatibilitytools.d").join(ENHANCED_TOOL_NAME); + copy_directory(&trusted, &destination).unwrap(); + let steam_vdf = steam.join("config/config.vdf"); + let local_vdf = steam.join("userdata/1/config/localconfig.vdf"); + fs::create_dir_all(steam_vdf.parent().unwrap()).unwrap(); + fs::create_dir_all(local_vdf.parent().unwrap()).unwrap(); + fs::write(&steam_vdf, enhanced_compatibility_config()).unwrap(); + fs::write(&local_vdf, steam_config(ENHANCED_LAUNCH_OPTIONS)).unwrap(); + let backups = root.join("data/backups"); + let mapping_snapshot = backups.join("compat_mapping_311210.json"); + let launch_snapshot = backups.join("launch_options_311210.json"); + let ownership = backups.join("compat_tool_311210.json"); + atomic_json( + &mapping_snapshot, + &json!({ + "had_entry": true, + "entry": { "name": "GE-Proton", "priority": "75", "custom": "yes" } + }), + ) + .unwrap(); + atomic_json(&launch_snapshot, &json!({ "launch_options": "-novid" })).unwrap(); + LegacyCompatibilityFixture { + root, + steam, + trusted, + destination, + steam_vdf, + local_vdf, + steam_backup: backups.join("steam_config_backup.vdf"), + local_backup: backups.join("localconfig_backup.vdf"), + mapping_snapshot, + launch_snapshot, + ownership, + } + } + + fn assert_legacy_unowned(fixture: &LegacyCompatibilityFixture) { + assert!(!fixture.ownership.exists()); + assert!(!fixture.destination.join(TOOL_MARKER_FILENAME).exists()); + assert_eq!( + fs::read(fixture.destination.join("proton")).unwrap(), + b"legacy proton" + ); + assert_eq!( + read_launch_options_at(&fixture.local_vdf, APP_ID).unwrap(), + ENHANCED_LAUNCH_OPTIONS + ); + } + + fn adopt_fixture(fixture: &LegacyCompatibilityFixture) -> Result { + adopt_legacy_compatibility_tool_at( + &fixture.steam, + &fixture.trusted, + &fixture.ownership, + &fixture.steam_vdf, + &fixture.mapping_snapshot, + &fixture.local_vdf, + &fixture.launch_snapshot, + ) + } + + fn proton_archive_fixture(cache_root: &Path) -> (PathBuf, String) { + use flate2::{Compression, write::GzEncoder}; + use tar::{Builder, EntryType, Header}; + + fs::create_dir_all(cache_root).unwrap(); + let archive = cache_root.join(format!("GDK-Proton-{ENHANCED_PROTON_TAG}.tar.gz")); + let encoder = GzEncoder::new(fs::File::create(&archive).unwrap(), Compression::default()); + let mut builder = Builder::new(encoder); + for path in [ + "GDK-Proton10-32/", + "GDK-Proton10-32/files/", + "GDK-Proton10-32/protonfixes/", + "GDK-Proton10-32/protonfixes/gamefixes-steam/", + "GDK-Proton10-32/protonfixes/gamefixes-umu/", + ] { + let mut header = Header::new_gnu(); + header.set_entry_type(EntryType::Directory); + header.set_size(0); + header.set_mode(0o755); + header.set_path(path).unwrap(); + header.set_cksum(); + builder.append(&header, std::io::empty()).unwrap(); + } + { + let mut append = |path: &str, contents: &[u8]| { + let mut header = Header::new_gnu(); + header.set_entry_type(EntryType::Regular); + header.set_size(contents.len() as u64); + header.set_mode(0o755); + header.set_path(path).unwrap(); + header.set_cksum(); + builder.append(&header, contents).unwrap(); + }; + append("GDK-Proton10-32/proton", b"proton"); + append("GDK-Proton10-32/files/payload", b"payload"); + append( + "GDK-Proton10-32/compatibilitytool.vdf", + br#""compatibilitytools" { "compat_tools" { "GDK-Proton10-32" { "install_path" "." "display_name" "GDK-Proton10-32" "from_oslist" "windows" "to_oslist" "linux" } } }"#, + ); + append( + "GDK-Proton10-32/protonfixes/gamefixes-steam/271590.py", + b"fix", + ); + } + let mut link = Header::new_gnu(); + link.set_entry_type(EntryType::Symlink); + link.set_size(0); + link.set_mode(0o777); + link.set_path("GDK-Proton10-32/protonfixes/gamefixes-umu/umu-271590.py") + .unwrap(); + link.set_link_name("../gamefixes-steam/271590.py").unwrap(); + link.set_cksum(); + builder.append(&link, std::io::empty()).unwrap(); + builder.into_inner().unwrap().finish().unwrap(); + let digest = fs_ops::sha256_file(&archive).unwrap(); + (archive, digest) + } + + #[test] + fn steam_exit_wait_is_bounded_and_needs_no_test_sleep() { + let mut checks = 0; + let mut pauses = 0; + wait_for_steam_exit_with( + 5, + || { + checks += 1; + Ok(checks < 3) + }, + || pauses += 1, + ) + .unwrap(); + assert_eq!(checks, 3); + assert_eq!(pauses, 2); + + let mut timeout_checks = 0; + let error = wait_for_steam_exit_with( + 3, + || { + timeout_checks += 1; + Ok(true) + }, + || {}, + ) + .unwrap_err(); + assert_eq!(timeout_checks, 3); + assert!(error.contains("did not exit")); + } + + #[test] + fn mapping_snapshot_restores_prior_entry_and_unknown_fields() { + let root = fixture_dir("mapping"); + let config = root.join("config.vdf"); + let legacy = root.join("backups/steam_config_backup.vdf"); + let snapshot = root.join("backups/compat_mapping_311210.json"); + fs::write( + &config, + r#" +"InstallConfigStore" { + "Unrelated" "preserve" + "Software" { "Valve" { "Steam" { "CompatToolMapping" { + "311210" { "name" "GE-Proton" "priority" "75" "custom" "yes" } + "other" { "name" "Other Tool" } + } } } } +} +"#, + ) + .unwrap(); + + set_compatibility_mapping_at(&config, &legacy, &snapshot).unwrap(); + let mapped = read_vdf(&config).unwrap(); + assert_eq!( + string_value( + &mapped, + &[ + "InstallConfigStore", + "Software", + "Valve", + "Steam", + "CompatToolMapping", + APP_ID, + "name", + ], + ), + ENHANCED_TOOL_NAME + ); + assert_eq!( + string_value(&mapped, &["InstallConfigStore", "Unrelated"]), + "preserve" + ); + + clear_compatibility_mapping_at(&config, &legacy, &snapshot).unwrap(); + let restored = read_vdf(&config).unwrap(); + assert_eq!( + string_value( + &restored, + &[ + "InstallConfigStore", + "Software", + "Valve", + "Steam", + "CompatToolMapping", + APP_ID, + "name", + ], + ), + "GE-Proton" + ); + assert_eq!( + string_value( + &restored, + &[ + "InstallConfigStore", + "Software", + "Valve", + "Steam", + "CompatToolMapping", + APP_ID, + "custom", + ], + ), + "yes" + ); + assert!(!snapshot.exists()); + fs::remove_dir_all(root).unwrap(); + } + + #[test] + fn exact_legacy_tool_is_adopted_and_cleaned_up() { + let fixture = legacy_compatibility_fixture("legacy-cleanup"); + + cleanup_compatibility_at( + &fixture.steam, + Some(&fixture.trusted), + &fixture.ownership, + &fixture.steam_vdf, + &fixture.steam_backup, + &fixture.mapping_snapshot, + &fixture.local_vdf, + &fixture.local_backup, + &fixture.launch_snapshot, + ) + .unwrap(); + + assert!(!fixture.destination.exists()); + assert!(!fixture.ownership.exists()); + assert!(!fixture.mapping_snapshot.exists()); + assert!(!fixture.launch_snapshot.exists()); + assert_eq!( + string_value( + &read_vdf(&fixture.steam_vdf).unwrap(), + &[ + "InstallConfigStore", + "Software", + "Valve", + "Steam", + "CompatToolMapping", + APP_ID, + "name", + ], + ), + "GE-Proton" + ); + assert_eq!( + read_launch_options_at(&fixture.local_vdf, APP_ID).unwrap(), + "-novid" + ); + fs::remove_dir_all(fixture.root).unwrap(); + } + + #[test] + fn exact_legacy_tool_can_be_upgraded_then_cleaned_up() { + let fixture = legacy_compatibility_fixture("legacy-upgrade"); + let upgrade = normalized_tool_source(&fixture.root, "upgrade", b"upgraded proton"); + + configure_compatibility_at( + &fixture.steam, + &upgrade, + Some(&fixture.trusted), + &fixture.ownership, + &fixture.steam_vdf, + &fixture.steam_backup, + &fixture.mapping_snapshot, + &fixture.local_vdf, + &fixture.local_backup, + &fixture.launch_snapshot, + ) + .unwrap(); + assert_eq!( + fs::read(fixture.destination.join("proton")).unwrap(), + b"upgraded proton" + ); + assert_eq!( + load_tool_ownership(&fixture.ownership) + .unwrap() + .unwrap() + .original_backup, + None + ); + + cleanup_compatibility_at( + &fixture.steam, + None, + &fixture.ownership, + &fixture.steam_vdf, + &fixture.steam_backup, + &fixture.mapping_snapshot, + &fixture.local_vdf, + &fixture.local_backup, + &fixture.launch_snapshot, + ) + .unwrap(); + assert!(!fixture.destination.exists()); + assert_eq!( + read_launch_options_at(&fixture.local_vdf, APP_ID).unwrap(), + "-novid" + ); + fs::remove_dir_all(fixture.root).unwrap(); + } + + #[test] + fn compatibility_cleanup_is_idempotent_only_for_the_fully_clean_tuple() { + let clean = legacy_compatibility_fixture("compat-clean-retry"); + fs::remove_dir_all(&clean.destination).unwrap(); + fs::remove_file(&clean.mapping_snapshot).unwrap(); + fs::remove_file(&clean.launch_snapshot).unwrap(); + fs::write(&clean.steam_vdf, compatibility_config()).unwrap(); + fs::write(&clean.local_vdf, steam_config("-novid")).unwrap(); + let steam_before = fs::read(&clean.steam_vdf).unwrap(); + let local_before = fs::read(&clean.local_vdf).unwrap(); + + cleanup_compatibility_at( + &clean.steam, + None, + &clean.ownership, + &clean.steam_vdf, + &clean.steam_backup, + &clean.mapping_snapshot, + &clean.local_vdf, + &clean.local_backup, + &clean.launch_snapshot, + ) + .unwrap(); + + assert_eq!(fs::read(&clean.steam_vdf).unwrap(), steam_before); + assert_eq!(fs::read(&clean.local_vdf).unwrap(), local_before); + assert!(!clean.ownership.exists()); + assert!(!clean.destination.exists()); + fs::remove_dir_all(clean.root).unwrap(); + } + + #[test] + fn compatibility_cleanup_rejects_partial_unowned_state_without_mutation() { + let stale_snapshot = legacy_compatibility_fixture("compat-partial-snapshot"); + fs::remove_dir_all(&stale_snapshot.destination).unwrap(); + fs::remove_file(&stale_snapshot.launch_snapshot).unwrap(); + fs::write(&stale_snapshot.steam_vdf, compatibility_config()).unwrap(); + fs::write(&stale_snapshot.local_vdf, steam_config("-novid")).unwrap(); + let mapping_before = fs::read(&stale_snapshot.mapping_snapshot).unwrap(); + let error = cleanup_compatibility_at( + &stale_snapshot.steam, + Some(&stale_snapshot.trusted), + &stale_snapshot.ownership, + &stale_snapshot.steam_vdf, + &stale_snapshot.steam_backup, + &stale_snapshot.mapping_snapshot, + &stale_snapshot.local_vdf, + &stale_snapshot.local_backup, + &stale_snapshot.launch_snapshot, + ) + .unwrap_err(); + assert!(error.contains("missing"), "{error}"); + assert_eq!( + fs::read(&stale_snapshot.mapping_snapshot).unwrap(), + mapping_before + ); + assert!(!stale_snapshot.ownership.exists()); + assert!(!stale_snapshot.destination.exists()); + fs::remove_dir_all(stale_snapshot.root).unwrap(); + + let managed_values = legacy_compatibility_fixture("compat-partial-values"); + fs::remove_dir_all(&managed_values.destination).unwrap(); + fs::remove_file(&managed_values.mapping_snapshot).unwrap(); + fs::remove_file(&managed_values.launch_snapshot).unwrap(); + let steam_before = fs::read(&managed_values.steam_vdf).unwrap(); + let local_before = fs::read(&managed_values.local_vdf).unwrap(); + let error = cleanup_compatibility_at( + &managed_values.steam, + Some(&managed_values.trusted), + &managed_values.ownership, + &managed_values.steam_vdf, + &managed_values.steam_backup, + &managed_values.mapping_snapshot, + &managed_values.local_vdf, + &managed_values.local_backup, + &managed_values.launch_snapshot, + ) + .unwrap_err(); + assert!(error.contains("missing"), "{error}"); + assert_eq!(fs::read(&managed_values.steam_vdf).unwrap(), steam_before); + assert_eq!(fs::read(&managed_values.local_vdf).unwrap(), local_before); + assert!(!managed_values.ownership.exists()); + fs::remove_dir_all(managed_values.root).unwrap(); + } + + #[test] + fn owned_reconfigure_requires_both_restore_snapshots_before_mutation() { + for (label, remove_mapping) in [ + ("owned-missing-mapping", true), + ("owned-missing-launch", false), + ] { + let fixture = legacy_compatibility_fixture(label); + let installed = normalized_tool_source(&fixture.root, "installed", b"installed"); + configure_compatibility_at( + &fixture.steam, + &installed, + Some(&fixture.trusted), + &fixture.ownership, + &fixture.steam_vdf, + &fixture.steam_backup, + &fixture.mapping_snapshot, + &fixture.local_vdf, + &fixture.local_backup, + &fixture.launch_snapshot, + ) + .unwrap(); + let missing = if remove_mapping { + &fixture.mapping_snapshot + } else { + &fixture.launch_snapshot + }; + fs::remove_file(missing).unwrap(); + let ownership_before = fs::read(&fixture.ownership).unwrap(); + let marker_before = fs::read(fixture.destination.join(TOOL_MARKER_FILENAME)).unwrap(); + let steam_before = fs::read(&fixture.steam_vdf).unwrap(); + let local_before = fs::read(&fixture.local_vdf).unwrap(); + let replacement = normalized_tool_source(&fixture.root, "replacement", b"replacement"); + + let error = configure_compatibility_at( + &fixture.steam, + &replacement, + None, + &fixture.ownership, + &fixture.steam_vdf, + &fixture.steam_backup, + &fixture.mapping_snapshot, + &fixture.local_vdf, + &fixture.local_backup, + &fixture.launch_snapshot, + ) + .unwrap_err(); + assert!(error.contains(&missing.display().to_string()), "{error}"); + assert!(!missing.exists()); + assert_eq!(fs::read(&fixture.ownership).unwrap(), ownership_before); + assert_eq!( + fs::read(fixture.destination.join(TOOL_MARKER_FILENAME)).unwrap(), + marker_before + ); + assert_eq!( + fs::read(fixture.destination.join("proton")).unwrap(), + b"installed" + ); + assert_eq!(fs::read(&fixture.steam_vdf).unwrap(), steam_before); + assert_eq!(fs::read(&fixture.local_vdf).unwrap(), local_before); + fs::remove_dir_all(fixture.root).unwrap(); + } + } + + #[test] + fn legacy_adoption_rejects_missing_or_mismatched_state_without_mutation() { + let missing = legacy_compatibility_fixture("legacy-missing-snapshot"); + fs::remove_file(&missing.launch_snapshot).unwrap(); + assert!( + adopt_fixture(&missing) + .unwrap_err() + .contains("launch_options") + ); + assert_legacy_unowned(&missing); + fs::remove_dir_all(missing.root).unwrap(); + + let mapping = legacy_compatibility_fixture("legacy-mapping-mismatch"); + fs::write(&mapping.steam_vdf, compatibility_config()).unwrap(); + assert!(adopt_fixture(&mapping).unwrap_err().contains("mapping")); + assert_legacy_unowned(&mapping); + fs::remove_dir_all(mapping.root).unwrap(); + + let launch = legacy_compatibility_fixture("legacy-launch-mismatch"); + fs::write(&launch.local_vdf, steam_config("-novid")).unwrap(); + assert!( + adopt_fixture(&launch) + .unwrap_err() + .contains("launch options") + ); + assert!(!launch.ownership.exists()); + assert!(!launch.destination.join(TOOL_MARKER_FILENAME).exists()); + fs::remove_dir_all(launch.root).unwrap(); + } + + #[test] + fn legacy_adoption_rejects_payload_mismatch_and_backup_sibling_without_mutation() { + let mismatch = legacy_compatibility_fixture("legacy-payload-mismatch"); + fs::write(mismatch.destination.join("proton"), b"modified").unwrap(); + assert!( + adopt_fixture(&mismatch) + .unwrap_err() + .contains("bytes differ") + ); + assert!(!mismatch.ownership.exists()); + assert!(!mismatch.destination.join(TOOL_MARKER_FILENAME).exists()); + assert_eq!( + fs::read(mismatch.destination.join("proton")).unwrap(), + b"modified" + ); + fs::remove_dir_all(mismatch.root).unwrap(); + + let backup = legacy_compatibility_fixture("legacy-backup"); + let ambiguous = backup + .steam + .join("compatibilitytools.d/BO3 Enhanced.patchops.bak-20240101-000000"); + fs::create_dir_all(&ambiguous).unwrap(); + assert!(adopt_fixture(&backup).unwrap_err().contains("ambiguous")); + assert_legacy_unowned(&backup); + assert!(ambiguous.exists()); + fs::remove_dir_all(backup.root).unwrap(); + } + + #[cfg(unix)] + #[test] + fn legacy_adoption_rejects_a_symlinked_payload_without_mutation() { + use std::os::unix::fs::symlink; + + let fixture = legacy_compatibility_fixture("legacy-symlink"); + fs::remove_file(fixture.destination.join("files/payload")).unwrap(); + symlink( + fixture.trusted.join("files/payload"), + fixture.destination.join("files/payload"), + ) + .unwrap(); + + assert!(adopt_fixture(&fixture).unwrap_err().contains("unsafe link")); + assert!(!fixture.ownership.exists()); + assert!(!fixture.destination.join(TOOL_MARKER_FILENAME).exists()); + assert!( + fs::symlink_metadata(fixture.destination.join("files/payload")) + .unwrap() + .file_type() + .is_symlink() + ); + fs::remove_dir_all(fixture.root).unwrap(); + } + + #[cfg(unix)] + #[test] + fn safe_relative_source_links_are_materialized_and_escaping_links_are_rejected() { + use std::os::unix::fs::symlink; + + let root = fixture_dir("source-links"); + let source = normalized_tool_source(&root, "linked-source", b"proton"); + fs::create_dir_all(source.join("protonfixes/gamefixes-steam")).unwrap(); + fs::create_dir_all(source.join("protonfixes/gamefixes-umu")).unwrap(); + fs::write( + source.join("protonfixes/gamefixes-steam/271590.py"), + b"trusted fix", + ) + .unwrap(); + symlink( + "../gamefixes-steam/271590.py", + source.join("protonfixes/gamefixes-umu/umu-271590.py"), + ) + .unwrap(); + let copied = root.join("copied"); + copy_directory(&source, &copied).unwrap(); + assert_eq!( + fs::read(copied.join("protonfixes/gamefixes-umu/umu-271590.py")).unwrap(), + b"trusted fix" + ); + assert!( + fs::symlink_metadata(copied.join("protonfixes/gamefixes-umu/umu-271590.py")) + .unwrap() + .is_file() + ); + + let outside = root.join("outside"); + fs::write(&outside, b"outside").unwrap(); + symlink( + "../../../outside", + source.join("protonfixes/gamefixes-umu/escape"), + ) + .unwrap(); + let error = copy_directory(&source, &root.join("rejected")).unwrap_err(); + assert!(error.contains("escapes its source"), "{error}"); + assert_eq!(fs::read(outside).unwrap(), b"outside"); + fs::remove_dir_all(root).unwrap(); + } + + #[cfg(all(target_os = "linux", unix))] + #[test] + fn trusted_archive_requires_exact_regular_bytes_and_materializes_safe_links() { + use std::os::unix::fs::symlink; + + let root = fixture_dir("trusted-archive"); + let cache = root.join("cache"); + let (archive, digest) = proton_archive_fixture(&cache); + { + let trusted = trusted_proton_source_at(&cache, &digest).unwrap(); + assert_eq!(fs::read(trusted.source.join("proton")).unwrap(), b"proton"); + let link = trusted + .source + .join("protonfixes/gamefixes-umu/umu-271590.py"); + assert_eq!(fs::read(&link).unwrap(), b"fix"); + assert!(!fs::symlink_metadata(link).unwrap().file_type().is_symlink()); + } + assert!(archive.is_file()); + + fs::remove_file(&archive).unwrap(); + assert!( + trusted_proton_source_at(&cache, &digest) + .unwrap_err() + .contains("unavailable") + ); + let outside = root.join("outside.tar.gz"); + fs::write(&outside, b"not trusted").unwrap(); + symlink(&outside, &archive).unwrap(); + assert!( + trusted_proton_source_at(&cache, &digest) + .unwrap_err() + .contains("regular file") + ); + assert!( + fs::symlink_metadata(&archive) + .unwrap() + .file_type() + .is_symlink() + ); + assert_eq!(fs::read(outside).unwrap(), b"not trusted"); + fs::remove_dir_all(root).unwrap(); + } + + #[cfg(unix)] + #[test] + fn failed_configure_rolls_legacy_adoption_back_to_unowned() { + use std::os::unix::fs::symlink; + + let fixture = legacy_compatibility_fixture("legacy-rollback"); + let invalid_source = normalized_tool_source(&fixture.root, "invalid-upgrade", b"new"); + fs::write(fixture.root.join("outside"), b"outside").unwrap(); + symlink("../../outside", invalid_source.join("files/escape")).unwrap(); + + let error = configure_compatibility_at( + &fixture.steam, + &invalid_source, + Some(&fixture.trusted), + &fixture.ownership, + &fixture.steam_vdf, + &fixture.steam_backup, + &fixture.mapping_snapshot, + &fixture.local_vdf, + &fixture.local_backup, + &fixture.launch_snapshot, + ) + .unwrap_err(); + assert!(error.contains("escapes its source"), "{error}"); + assert_legacy_unowned(&fixture); + assert!(fixture.mapping_snapshot.exists()); + assert!(fixture.launch_snapshot.exists()); + fs::remove_dir_all(fixture.root).unwrap(); + } + + #[test] + fn tool_cleanup_restores_only_its_exact_backup() { + let root = fixture_dir("tool-ownership"); + let steam = root.join("steam"); + let directory = steam.join("compatibilitytools.d"); + let destination = directory.join(ENHANCED_TOOL_NAME); + let stale = directory.join(format!("{ENHANCED_TOOL_NAME}.patchops.bak-stale")); + let ownership = root.join("data/backups/compat_tool_311210.json"); + let source = tool_source(&root); + fs::create_dir_all(&destination).unwrap(); + fs::write(destination.join("original"), b"original tool").unwrap(); + fs::create_dir_all(&stale).unwrap(); + fs::write(stale.join("keep"), b"unowned").unwrap(); + + install_compatibility_tool_at(&steam, &source, &ownership) + .unwrap() + .commit() + .unwrap(); + let state = load_tool_ownership(&ownership).unwrap().unwrap(); + let exact_backup = directory.join(state.original_backup.as_ref().unwrap()); + assert_eq!( + fs::read(exact_backup.join("original")).unwrap(), + b"original tool" + ); + + remove_compatibility_tool_at(&steam, &ownership) + .unwrap() + .commit() + .unwrap(); + assert_eq!( + fs::read(destination.join("original")).unwrap(), + b"original tool" + ); + assert_eq!(fs::read(stale.join("keep")).unwrap(), b"unowned"); + assert!(!ownership.exists()); + fs::remove_dir_all(root).unwrap(); + } + + #[test] + fn tool_cleanup_refuses_a_replaced_destination() { + let root = fixture_dir("tool-conflict"); + let steam = root.join("steam"); + let directory = steam.join("compatibilitytools.d"); + let destination = directory.join(ENHANCED_TOOL_NAME); + let ownership = root.join("data/backups/compat_tool_311210.json"); + let source = tool_source(&root); + fs::create_dir_all(&destination).unwrap(); + fs::write(destination.join("original"), b"original tool").unwrap(); + + install_compatibility_tool_at(&steam, &source, &ownership) + .unwrap() + .commit() + .unwrap(); + let state = load_tool_ownership(&ownership).unwrap().unwrap(); + let exact_backup = directory.join(state.original_backup.as_ref().unwrap()); + atomic_json( + &destination.join(TOOL_MARKER_FILENAME), + &json!({ "transaction_id": "someone-else" }), + ) + .unwrap(); + + let error = match remove_compatibility_tool_at(&steam, &ownership) { + Ok(_) => panic!("replaced tool must not be removed"), + Err(error) => error, + }; + assert!(error.contains("leaving it untouched")); + assert!(destination.join("proton").is_file()); + assert_eq!( + fs::read(exact_backup.join("original")).unwrap(), + b"original tool" + ); + assert!(ownership.is_file()); + fs::remove_dir_all(root).unwrap(); + } + + #[test] + fn configure_failure_restores_tool_mapping_launch_and_backups() { + let root = fixture_dir("configure-rollback"); + let steam = root.join("steam"); + let destination = steam.join("compatibilitytools.d").join(ENHANCED_TOOL_NAME); + let steam_vdf = steam.join("config/config.vdf"); + let local_vdf = steam.join("userdata/1/config/localconfig.vdf"); + let data = root.join("data/backups"); + let steam_backup = data.join("steam_config_backup.vdf"); + let local_backup = data.join("localconfig_backup.vdf"); + let mapping_snapshot = data.join("compat_mapping_311210.json"); + let launch_snapshot = data.join("launch_options_311210.json"); + let ownership = data.join("compat_tool_311210.json"); + let source = tool_source(&root); + fs::create_dir_all(steam_vdf.parent().unwrap()).unwrap(); + fs::create_dir_all(local_vdf.parent().unwrap()).unwrap(); + fs::write(&steam_vdf, enhanced_compatibility_config()).unwrap(); + fs::write(&local_vdf, b"invalid-vdf").unwrap(); + install_compatibility_tool_at(&steam, &source, &ownership) + .unwrap() + .commit() + .unwrap(); + atomic_json( + &mapping_snapshot, + &json!({ "had_entry": false, "entry": null }), + ) + .unwrap(); + atomic_json(&launch_snapshot, &json!({ "launch_options": "-novid" })).unwrap(); + let original_steam = fs::read(&steam_vdf).unwrap(); + + let error = configure_compatibility_at( + &steam, + &source, + None, + &ownership, + &steam_vdf, + &steam_backup, + &mapping_snapshot, + &local_vdf, + &local_backup, + &launch_snapshot, + ) + .unwrap_err(); + assert!(error.contains("localconfig.vdf")); + assert_eq!(fs::read(&steam_vdf).unwrap(), original_steam); + assert_eq!(fs::read(&local_vdf).unwrap(), b"invalid-vdf"); + assert_eq!( + fs::read(destination.join("proton")).unwrap(), + b"managed proton" + ); + assert!(mapping_snapshot.is_file()); + assert!(launch_snapshot.is_file()); + for path in [ + steam_backup, + local_backup, + fs_ops::backup_path(&steam_vdf), + fs_ops::backup_path(&local_vdf), + ] { + assert!(!path.exists(), "rollback left {}", path.display()); + } + fs::remove_dir_all(root).unwrap(); + } + + #[test] + fn cleanup_conflict_rolls_back_tool_and_mapping() { + let root = fixture_dir("cleanup-rollback"); + let steam = root.join("steam"); + let destination = steam.join("compatibilitytools.d").join(ENHANCED_TOOL_NAME); + let steam_vdf = steam.join("config/config.vdf"); + let local_vdf = steam.join("userdata/1/config/localconfig.vdf"); + let data = root.join("data/backups"); + let steam_backup = data.join("steam_config_backup.vdf"); + let local_backup = data.join("localconfig_backup.vdf"); + let mapping_snapshot = data.join("compat_mapping_311210.json"); + let launch_snapshot = data.join("launch_options_311210.json"); + let ownership = data.join("compat_tool_311210.json"); + let source = tool_source(&root); + fs::create_dir_all(&destination).unwrap(); + fs::create_dir_all(steam_vdf.parent().unwrap()).unwrap(); + fs::create_dir_all(local_vdf.parent().unwrap()).unwrap(); + fs::write(destination.join("original"), b"original tool").unwrap(); + fs::write(&steam_vdf, compatibility_config()).unwrap(); + fs::write(&local_vdf, steam_config("-novid")).unwrap(); + install_compatibility_tool_at(&steam, &source, &ownership) + .unwrap() + .commit() + .unwrap(); + atomic_json( + &mapping_snapshot, + &json!({ + "had_entry": true, + "entry": { "name": "GE-Proton", "priority": "75", "custom": "yes" } + }), + ) + .unwrap(); + atomic_json(&launch_snapshot, &json!({ "launch_options": "-novid" })).unwrap(); + fs::write(&steam_vdf, enhanced_compatibility_config()).unwrap(); + fs::write(&local_vdf, steam_config(ENHANCED_LAUNCH_OPTIONS)).unwrap(); + fs::write(&local_vdf, steam_config("user-modified")).unwrap(); + let mapping_before = fs::read(&steam_vdf).unwrap(); + + let error = cleanup_compatibility_at( + &steam, + None, + &ownership, + &steam_vdf, + &steam_backup, + &mapping_snapshot, + &local_vdf, + &local_backup, + &launch_snapshot, + ) + .unwrap_err(); + assert!(error.contains("launch options changed")); + assert_eq!(fs::read(&steam_vdf).unwrap(), mapping_before); + assert_eq!( + read_launch_options_at(&local_vdf, APP_ID).unwrap(), + "user-modified" + ); + assert_eq!( + fs::read(destination.join("proton")).unwrap(), + b"managed proton" + ); + let state = load_tool_ownership(&ownership).unwrap().unwrap(); + assert!( + steam + .join("compatibilitytools.d") + .join(state.original_backup.unwrap()) + .join("original") + .is_file() + ); + assert!(mapping_snapshot.is_file()); + assert!(launch_snapshot.is_file()); + fs::remove_dir_all(root).unwrap(); + } + + #[test] + fn configure_commit_failure_restores_tool_and_file_snapshots() { + let root = fixture_dir("configure-commit-rollback"); + let steam = root.join("steam"); + let directory = steam.join("compatibilitytools.d"); + let destination = directory.join(ENHANCED_TOOL_NAME); + let ownership = root.join("data/backups/compat_tool_311210.json"); + let config = root.join("config.vdf"); + let source = tool_source(&root); + fs::create_dir_all(&destination).unwrap(); + fs::write(destination.join("original"), b"original tool").unwrap(); + fs::write(&config, b"before").unwrap(); + install_compatibility_tool_at(&steam, &source, &ownership) + .unwrap() + .commit() + .unwrap(); + + fs::write(source.join("proton"), b"replacement proton").unwrap(); + let snapshots = capture_files(std::slice::from_ref(&config)).unwrap(); + let tool = install_compatibility_tool_at(&steam, &source, &ownership).unwrap(); + let displaced = tool.displaced_managed.as_ref().unwrap(); + atomic_json( + &displaced.join(TOOL_MARKER_FILENAME), + &json!({ "transaction_id": "commit-conflict" }), + ) + .unwrap(); + fs::write(&config, b"after").unwrap(); + + let error = finish_tool_install(tool, snapshots, destination.clone(), Ok(())).unwrap_err(); + assert!(error.contains("leaving it untouched")); + assert_eq!(fs::read(&config).unwrap(), b"before"); + assert_eq!( + fs::read(destination.join("proton")).unwrap(), + b"managed proton" + ); + assert!(ownership.is_file()); + fs::remove_dir_all(root).unwrap(); + } + + #[test] + fn cleanup_commit_failure_restores_tool_and_file_snapshots() { + let root = fixture_dir("cleanup-commit-rollback"); + let steam = root.join("steam"); + let directory = steam.join("compatibilitytools.d"); + let destination = directory.join(ENHANCED_TOOL_NAME); + let ownership = root.join("data/backups/compat_tool_311210.json"); + let config = root.join("config.vdf"); + let source = tool_source(&root); + fs::create_dir_all(&destination).unwrap(); + fs::write(destination.join("original"), b"original tool").unwrap(); + fs::write(&config, b"before").unwrap(); + install_compatibility_tool_at(&steam, &source, &ownership) + .unwrap() + .commit() + .unwrap(); + let state = load_tool_ownership(&ownership).unwrap().unwrap(); + let exact_backup = directory.join(state.original_backup.unwrap()); + + let snapshots = capture_files(std::slice::from_ref(&config)).unwrap(); + let tool = remove_compatibility_tool_at(&steam, &ownership).unwrap(); + let displaced = tool.displaced_managed.as_ref().unwrap(); + atomic_json( + &displaced.join(TOOL_MARKER_FILENAME), + &json!({ "transaction_id": "commit-conflict" }), + ) + .unwrap(); + fs::write(&config, b"after").unwrap(); + + let error = finish_tool_removal(tool, snapshots, Ok(())).unwrap_err(); + assert!(error.contains("leaving it untouched")); + assert_eq!(fs::read(&config).unwrap(), b"before"); + assert_eq!( + fs::read(destination.join("proton")).unwrap(), + b"managed proton" + ); + assert_eq!( + fs::read(exact_backup.join("original")).unwrap(), + b"original tool" + ); + assert!(ownership.is_file()); + fs::remove_dir_all(root).unwrap(); + } +} diff --git a/native/core/src/t7.rs b/native/core/src/t7.rs new file mode 100644 index 0000000..1d6ecc9 --- /dev/null +++ b/native/core/src/t7.rs @@ -0,0 +1,2848 @@ +use std::{ + collections::{HashMap, HashSet}, + fs, + io::Read, + path::{Component, Path, PathBuf}, + sync::atomic::{AtomicU64, Ordering}, + time::{Duration, SystemTime, UNIX_EPOCH}, +}; + +use serde::{Deserialize, Serialize}; + +use crate::{ + app::AppState, + fs_ops::{self, Snapshot, remove_file_if_present}, + models::T7State, +}; + +const CURRENT_RELEASE_API: &str = "https://api.github.com/repos/Scroptss/T7Patch/releases/latest"; +const CURRENT_ARCHIVE_URL: &str = "https://github.com/Scroptss/T7Patch/releases/latest/download/Linux.Steamdeck.and.Manual.Windows.Install.zip"; +const LEGACY_RELEASE_API: &str = + "https://api.github.com/repos/shiversoftdev/t7patch/releases/tags/Current"; +const COMPATIBLE_ARCHIVE_URL: &str = "https://github.com/shiversoftdev/t7patch/releases/download/Current/Linux.Steamdeck.and.Manual.Windows.Install.zip"; +const LPC_ARCHIVE_URL: &str = + "https://github.com/shiversoftdev/t7patch/releases/download/Current/LPC.1.zip"; +pub(crate) const PATCH_ARCHIVE_NAME: &str = "Linux.Steamdeck.and.Manual.Windows.Install.zip"; +const LPC_ARCHIVE_NAME: &str = "LPC.1.zip"; +const COMPATIBLE_ARCHIVE_SHA256: &str = + "388491c01643b0abd51f13290d0c36dec9737fcfbb0ed5e2f5ef6804e1b73dcb"; +const LPC_ARCHIVE_SHA256: &str = "c94855841a233c9dcdea2799c12693fed8554d0e59fe68257ae66ffbdf2fa58b"; +const T7_MARKERS: [&str; 2] = ["t7patchloader.dll", "t7patch.dll"]; +const T7_REQUIRED_FILES: [&str; 2] = ["t7patch.dll", "t7patchloader.dll"]; +const T7_GAME_FILES: [&str; 6] = [ + "t7patch.dll", + "t7patch.conf", + "discord_game_sdk.dll", + "dsound.dll", + "t7patchloader.dll", + "zbr2.dll", +]; +const COMPATIBLE_ONLY_FILES: [&str; 2] = ["discord_game_sdk.dll", "zbr2.dll"]; +const MAX_RELEASE_JSON_BYTES: u64 = 4 * 1024 * 1024; +const MAX_ARCHIVE_BYTES: u64 = 512 * 1024 * 1024; +const MAX_MANIFEST_BYTES: u64 = 1024 * 1024; +const OWNERSHIP_DIRECTORY: &str = ".patchopsiii"; +const OWNERSHIP_BACKUP_DIRECTORY: &str = "t7-backups"; +const OWNERSHIP_MANIFEST: &str = "t7-ownership.json"; +const OWNERSHIP_VERSION: u32 = 1; +const UNTRACKED_UNINSTALL_ERROR: &str = "T7 ownership manifest was not found; refusing to remove files from a legacy or untracked install. PatchOpsIII cannot safely infer ownership from .bak or .patchops.bak filenames"; +// Exact member hashes from the verified release archives shipped by +// origin/main at migration time: current archive SHA-256 +// 57e5cb5abba203382a0549e46c431ed34c4ce6c7aa3f4cdd8003ef0325d3dce5, +// plus the pinned compatible/LPC archives declared above. They are used only +// to recognize legacy installs; new downloads still use their archive digest. +const KNOWN_CURRENT_FILES: [(&str, &str); 3] = [ + ( + "dsound.dll", + "fe05893af78173bdae36bd8202f8e69739d7c1d16c562941eef25a0b5e7c57d2", + ), + ( + "t7patch.dll", + "bc4c9e4edb895f237477a9074552a8186e69b7d36db4886d983dc13c143645c6", + ), + ( + "t7patchloader.dll", + "17614526688ee9d8c85fe89ade78700687ef19d04885dfc133e7ba587fdda811", + ), +]; +// Historical Scroptss archives that this application's legacy dynamic +// `releases/latest` installer could have installed before v3.04 became +// current. The release API supplied the archive digests and these member +// hashes were derived from those exact official archives. +// v3.03 archive SHA-256: 609546e72dd4f9c5649b8197cfdf71dcee47318b438754035e947b5586ee9493. +const KNOWN_SCROPTSS_V3_03_FILES: [(&str, &str); 3] = [ + ( + "dsound.dll", + "fe05893af78173bdae36bd8202f8e69739d7c1d16c562941eef25a0b5e7c57d2", + ), + ( + "t7patch.dll", + "935cb98a4cc7d33ad023444d403be25f6da3865f028975b65c69c7a8884d4078", + ), + ( + "t7patchloader.dll", + "17614526688ee9d8c85fe89ade78700687ef19d04885dfc133e7ba587fdda811", + ), +]; +// v3.02 archive SHA-256: e34411e70d3c99773445ab758851304d7f6a80867a987ec7f4a1a1df72b11bb1. +const KNOWN_SCROPTSS_V3_02_FILES: [(&str, &str); 3] = [ + ( + "dsound.dll", + "fe05893af78173bdae36bd8202f8e69739d7c1d16c562941eef25a0b5e7c57d2", + ), + ( + "t7patch.dll", + "4c582171dfa4409440f31141b4b0fa48f297fcca98b3e36917960bb55d3a1542", + ), + ( + "t7patchloader.dll", + "1ef71784bd654afb45ec58dde5f7e454ea91351faf8c4977d65affa236e1b7ba", + ), +]; +// v2.03 archive SHA-256: 5e6159c264a1d6f2c4425fafdaf2ca382ef49abf4b1915d7c7d183bcb5513440. +const KNOWN_SHIVERSOFT_V2_03_FILES: [(&str, &str); 5] = [ + ( + "discord_game_sdk.dll", + "527768710ddb0953fce5eb1700c2566b6451135d76f1d0610b63907cd5ba94c5", + ), + ( + "dsound.dll", + "fe05893af78173bdae36bd8202f8e69739d7c1d16c562941eef25a0b5e7c57d2", + ), + ( + "t7patch.dll", + "21dd4bac65d467b3f890ec4f7d77d73944646803e0c95fca8c1ee5b6ba71f92d", + ), + ( + "t7patchloader.dll", + "a140a38d7cb451c2ccf44e693921b5305cb0fbdccfde05913efcb40e5b83509c", + ), + ( + "zbr2.dll", + "065fe09942b8005099319feee7734404872d5d0e9a2a1ee6de4877de4c030ede", + ), +]; +const KNOWN_COMPATIBLE_FILES: [(&str, &str); 5] = [ + ( + "discord_game_sdk.dll", + "527768710ddb0953fce5eb1700c2566b6451135d76f1d0610b63907cd5ba94c5", + ), + ( + "dsound.dll", + "fe05893af78173bdae36bd8202f8e69739d7c1d16c562941eef25a0b5e7c57d2", + ), + ( + "t7patch.dll", + "aaaaaaea2e889db7c4453fa355c86f17bebf74b6822ea7a3701d420dae481d3f", + ), + ( + "t7patchloader.dll", + "4df88503030a671d0804012ea585e6f6b01b70bec74cf3ddc42b9f4c73fe73cd", + ), + ( + "zbr2.dll", + "065fe09942b8005099319feee7734404872d5d0e9a2a1ee6de4877de4c030ede", + ), +]; +const KNOWN_LPC_FILES: [(&str, &str); 13] = [ + ( + "bp_core_ffotd_tu32_593.ff", + "729ff5a20e81085be6884a131cf68300b56f141dc0e10a1be56d1b27e96f2d25", + ), + ( + "core_ffotd_tu32_593.ff", + "beba007a3474abc3d59bc9048da222bdd9757401f59657683e5f99cf05b6befb", + ), + ( + "ea_core_ffotd_tu32_593.ff", + "b2e03032eefee5b20706009b0b76bc3dd187e200a65de37144746799621229bb", + ), + ( + "en_core_ffotd_tu32_593.ff", + "218d1ae315a222ef13483d43b4f92f2ec1f974556d8483746f9c2046883f68b2", + ), + ( + "es_core_ffotd_tu32_593.ff", + "ed6db64b5444fd4990d70a642d4b088ac3cee09d09c690c1a45d6258a035ba2f", + ), + ( + "fr_core_ffotd_tu32_593.ff", + "36403f40c2e81eb4e98a9ff49591c9afecb8f7390e35d55d6779a213be1aba93", + ), + ( + "ge_core_ffotd_tu32_593.ff", + "a132a000248646b50a8919a1f2f10455d51102efba3e1068d9f884a81d4af54a", + ), + ( + "it_core_ffotd_tu32_593.ff", + "e3cf42fbdef69b2ebe996495bafecc367cc0c9b434b2b7e08cf473b337c60c80", + ), + ( + "ja_core_ffotd_tu32_593.ff", + "f1d7acb16e7634bea8cac749c7f115b7605300d5c9d9fd63256a91ce719a963d", + ), + ( + "po_core_ffotd_tu32_593.ff", + "cd189d4ad235aa75cc3f32228e2b7782f6ee2decd7efa37c41f5d580c708932d", + ), + ( + "ru_core_ffotd_tu32_593.ff", + "ce96501ac3e5a5cbd50c493ecc3110ffad8a8208fe33c5c256bb56b30ed65fb7", + ), + ( + "sc_core_ffotd_tu32_593.ff", + "f04645efe12219d6fef3f706e8cd01afcbf000de95bfb0107e385362a5dc5ae6", + ), + ( + "tc_core_ffotd_tu32_593.ff", + "031066009975c5c6c1d1d68a54f18855077287eadee556482a8a45e060d08b2f", + ), +]; +static STAGE_COUNTER: AtomicU64 = AtomicU64::new(0); + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum PatchProfile { + Current, + Compatible, +} + +#[derive(Clone, Copy)] +struct AssetSpec { + name: &'static str, + url: &'static str, + pinned_sha256: Option<&'static str>, +} + +const CURRENT_ARCHIVE: AssetSpec = AssetSpec { + name: PATCH_ARCHIVE_NAME, + url: CURRENT_ARCHIVE_URL, + pinned_sha256: None, +}; +const COMPATIBLE_ARCHIVE: AssetSpec = AssetSpec { + name: PATCH_ARCHIVE_NAME, + url: COMPATIBLE_ARCHIVE_URL, + pinned_sha256: Some(COMPATIBLE_ARCHIVE_SHA256), +}; +const LPC_ARCHIVE: AssetSpec = AssetSpec { + name: LPC_ARCHIVE_NAME, + url: LPC_ARCHIVE_URL, + pinned_sha256: Some(LPC_ARCHIVE_SHA256), +}; + +#[derive(Debug, Deserialize)] +struct GitHubRelease { + #[serde(default)] + assets: Vec, +} + +#[derive(Debug, Deserialize)] +struct GitHubAsset { + #[serde(default)] + name: String, + #[serde(default)] + digest: String, + #[serde(default)] + browser_download_url: String, +} + +#[derive(Clone, Debug)] +struct ReleaseAsset { + name: String, + digest: String, + download_url: String, +} + +#[derive(Clone, Debug)] +struct InstallOperation { + source: PathBuf, + relative: PathBuf, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +struct OwnershipManifest { + version: u32, + entries: Vec, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +struct ManagedFile { + path: PathBuf, + managed_sha256: String, + original: OriginalFile, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(tag = "kind", rename_all = "snake_case")] +enum OriginalFile { + Created, + Overwritten { + backup_path: PathBuf, + sha256: String, + #[serde(default)] + active_was_missing: bool, + }, +} + +/// Read T7 Patch markers and `t7patch.conf`. `executable_profile` is the +/// already-validated EXE profile (`current`, `compatible`, or `enhanced`) and +/// avoids hashing the large game executable again during ordinary state reads. +pub fn status(game_dir: Option<&Path>, executable_profile: Option<&str>) -> T7State { + let Some(game_dir) = game_dir.filter(|path| !path.as_os_str().is_empty() && path.is_dir()) + else { + return empty_state("Unknown"); + }; + + let conf = game_dir.join("t7patch.conf"); + let conf_exists = regular_file(&conf); + let mut result = if conf_exists { + fs::read(&conf) + .map(|body| parse_conf(&String::from_utf8_lossy(&body))) + .unwrap_or_else(|_| empty_state("Unknown")) + } else { + empty_state("Unknown") + }; + result.installed = T7_MARKERS + .iter() + .any(|name| regular_file(&game_dir.join(name))); + result.conf_exists = conf_exists; + result.mode = if crate::enhanced::detect_install(game_dir) { + "Enhanced" + } else if executable_profile == Some("enhanced") { + "Custom" + } else { + mode_for_profile(game_dir, executable_profile) + } + .into(); + result +} + +/// Update one or more T7 config values while preserving all unrelated lines. +pub fn configure( + state: &AppState, + game_dir: &Path, + gamertag: Option<&str>, + color_code: &str, + network_password: Option<&str>, + friends_only: Option, +) -> Result<(), String> { + let game_dir = validate_game_dir(game_dir)?; + let conf = safe_target(&game_dir, Path::new("t7patch.conf"))?; + if !regular_file(&conf) { + return Err( + "t7patch.conf was not found. Install T7 Patch before updating the gamertag.".into(), + ); + } + + let name = gamertag + .map(|value| validate_gamertag(value, color_code)) + .transpose()?; + let password = network_password.map(validate_config_value).transpose()?; + let contents = fs::read_to_string(&conf).map_err(|error| error.to_string())?; + let updated = update_conf( + &contents, + name.as_deref(), + password.as_deref(), + friends_only, + ); + let cache_existed = state.mod_files_dir().exists(); + let stage = create_stage(state, "t7-config")?; + let result = + write_config_with_ownership(&game_dir, updated.as_bytes(), &stage.join("rollback")); + if result.is_ok() { + let _ = fs::remove_dir_all(&stage); + if !cache_existed { + let _ = fs::remove_dir(state.mod_files_dir()); + } + } else { + state.log( + "Warning", + format!( + "T7 configuration recovery files were retained at {}.", + stage.display() + ), + ); + } + result?; + + if let Some(name) = name { + state.log( + "Success", + format!("Updated 'playername' in t7patch.conf to '{name}'."), + ); + } + if let Some(password) = password { + state.log( + "Success", + if password.is_empty() { + "Cleared network password in t7patch.conf." + } else { + "Updated network password in t7patch.conf." + }, + ); + } + if let Some(enabled) = friends_only { + state.log( + "Success", + format!( + "Updated 'isfriendsonly' in t7patch.conf to {}.", + if enabled { "On" } else { "Off" } + ), + ); + } + Ok(()) +} + +/// Install the T7 archive matching the active executable profile together with +/// the pinned legacy LPC payload. Everything is staged and validated before +/// the game directory is changed, then restored from a snapshot on failure. +pub fn install(state: &AppState, game_dir: &Path, executable_profile: &str) -> Result<(), String> { + let profile = patch_profile(executable_profile)?; + let game_dir = validate_game_dir(game_dir)?; + let stage = create_stage(state, "t7")?; + + let result = (|| { + let legacy_release = fetch_release_digests(LEGACY_RELEASE_API); + let current_release = if profile == PatchProfile::Current { + Some(fetch_release_digests(CURRENT_RELEASE_API)) + } else { + None + }; + let patch_spec = match profile { + PatchProfile::Current => CURRENT_ARCHIVE, + PatchProfile::Compatible => COMPATIBLE_ARCHIVE, + }; + let patch_release = current_release.as_ref().unwrap_or(&legacy_release); + let (patch_url, patch_digest) = trusted_asset( + patch_spec, + patch_release, + profile == PatchProfile::Current, + state, + )?; + let (lpc_url, lpc_digest) = trusted_asset(LPC_ARCHIVE, &legacy_release, false, state)?; + + state.log( + "Info", + format!( + "Detected {}. Installing {}...", + if profile == PatchProfile::Compatible { + "Compatible EXE" + } else { + "Current EXE" + }, + if profile == PatchProfile::Compatible { + "T7 Patch 2.04" + } else { + "T7 Patch Scroptss/T7Patch" + } + ), + ); + + let patch_archive = stage.join("T7Patch.zip"); + fs_ops::download( + state, + &patch_url, + &patch_archive, + Some(&patch_digest), + MAX_ARCHIVE_BYTES, + )?; + let patch_extract = stage.join("patch"); + fs_ops::extract_zip(&patch_archive, &patch_extract)?; + let patch_source = find_patch_source(&patch_extract)?; + let mut patch_files = collect_files(&patch_source)?; + patch_files.retain(|(_, relative)| { + relative.components().count() == 1 + && relative + .file_name() + .and_then(|name| name.to_str()) + .is_some_and(|name| { + T7_GAME_FILES + .iter() + .any(|allowed| name.eq_ignore_ascii_case(allowed)) + }) + }); + if profile == PatchProfile::Current { + patch_files.retain(|(_, relative)| { + relative + .file_name() + .and_then(|name| name.to_str()) + .is_none_or(|name| { + !COMPATIBLE_ONLY_FILES + .iter() + .any(|blocked| name.eq_ignore_ascii_case(blocked)) + }) + }); + } + + let lpc_archive = stage.join("LPC.zip"); + fs_ops::download( + state, + &lpc_url, + &lpc_archive, + Some(&lpc_digest), + MAX_ARCHIVE_BYTES, + )?; + let lpc_extract = stage.join("lpc"); + fs_ops::extract_zip(&lpc_archive, &lpc_extract)?; + let lpc_files = find_lpc_files(&lpc_extract)?; + state.log( + "Success", + "Downloaded and validated T7 Patch and LPC archives.", + ); + + validate_lpc_dir(&game_dir)?; + let mut operations = Vec::new(); + let mut retained = Vec::new(); + for (source, relative) in patch_files { + let target = safe_target(&game_dir, &relative)?; + if relative + .file_name() + .and_then(|name| name.to_str()) + .is_some_and(|name| name.eq_ignore_ascii_case("t7patch.conf")) + && regular_file(&target) + { + retained.push(relative); + continue; + } + operations.push(InstallOperation { source, relative }); + } + for source in lpc_files { + let filename = source + .file_name() + .ok_or_else(|| "invalid LPC filename".to_string())? + .to_owned(); + operations.push(InstallOperation { + source, + relative: Path::new("LPC").join(filename), + }); + } + + let staged_hashes = expected_install_hashes(&operations)?; + let legacy_hashes = match known_legacy_hashes(&game_dir) { + Ok(Some(expected)) => expected, + Ok(None) | Err(_) => staged_hashes, + }; + let adopted = adopt_legacy_install(&game_dir, &legacy_hashes)?; + let install_result = reject_unsafe_profile_transition(&game_dir, profile).and_then(|()| { + apply_managed_install(&game_dir, &operations, &retained, &stage.join("rollback")) + }); + if let Err(error) = install_result { + if adopted { + discard_adopted_manifest(&game_dir).map_err(|cleanup| { + format!("{error}; legacy ownership migration cleanup failed: {cleanup}") + })?; + } + return Err(error); + } + + state.log( + "Success", + if profile == PatchProfile::Compatible { + "Installed T7 Patch 2.04 successfully." + } else { + "Installed T7 Patch Scroptss/T7Patch successfully." + }, + ); + Ok(()) + })(); + + if result.is_ok() { + let _ = fs::remove_dir_all(&stage); + } else { + state.log( + "Warning", + format!("T7 staging files were retained at {}.", stage.display()), + ); + } + result +} + +/// Remove only files recorded in the T7 ownership manifest. Managed bytes must +/// still match their recorded hashes, and overwritten files are restored only +/// from the exact PatchOps-owned backups recorded at install time. +pub fn uninstall(state: &AppState, game_dir: &Path) -> Result<(), String> { + let game_dir = validate_game_dir(game_dir)?; + if load_manifest(&game_dir)?.is_none() { + let Some(expected) = known_legacy_hashes(&game_dir)? else { + state.log("Info", "T7 Patch was not installed."); + return Ok(()); + }; + adopt_legacy_install(&game_dir, &expected)?; + } + let stage = create_stage(state, "t7-uninstall")?; + let result = apply_managed_uninstall(&game_dir, &stage.join("rollback")); + + if result.is_ok() { + let _ = fs::remove_dir_all(&stage); + clear_legacy_cache(state); + state.log("Success", "T7 Patch has been completely uninstalled."); + } else { + state.log( + "Warning", + format!("T7 recovery files were retained at {}.", stage.display()), + ); + } + result +} + +fn patch_profile(value: &str) -> Result { + match value.trim().to_ascii_lowercase().as_str() { + "current" | "default" | "enhanced" => Ok(PatchProfile::Current), + "compatible" => Ok(PatchProfile::Compatible), + _ => Err("Cannot install T7 Patch for an unverified executable profile".into()), + } +} + +fn mode_for_profile(game_dir: &Path, profile: Option<&str>) -> &'static str { + match profile.map(str::trim) { + Some(value) if value.eq_ignore_ascii_case("enhanced") => "Enhanced", + Some(value) + if value.eq_ignore_ascii_case("current") || value.eq_ignore_ascii_case("default") => + { + "Default" + } + Some(value) if value.eq_ignore_ascii_case("compatible") => "Custom", + Some(_) => "Custom", + None if ["BlackOpsIII.exe", "BlackOps3.exe"] + .iter() + .any(|name| regular_file(&game_dir.join(name))) => + { + "Custom" + } + None => "Unknown", + } +} + +fn empty_state(mode: &str) -> T7State { + T7State { + installed: false, + conf_exists: false, + gamertag: String::new(), + plain_name: String::new(), + color_code: String::new(), + network_password: String::new(), + friends_only: false, + mode: mode.into(), + } +} + +fn parse_conf(contents: &str) -> T7State { + let mut state = empty_state("Unknown"); + for line in contents.lines() { + if let Some(value) = line.strip_prefix("playername=") { + state.gamertag = value.trim().into(); + if state.gamertag.starts_with('^') && state.gamertag.chars().count() > 2 { + state.color_code = state.gamertag.chars().take(2).collect(); + state.plain_name = state.gamertag.chars().skip(2).collect(); + } else { + state.plain_name = state.gamertag.clone(); + } + } else if let Some(value) = line.strip_prefix("networkpassword=") { + state.network_password = value.trim().into(); + } else if let Some(value) = line.strip_prefix("isfriendsonly=") { + state.friends_only = value.trim() == "1"; + } + } + state +} + +fn validate_gamertag(value: &str, color_code: &str) -> Result { + let value = value.trim(); + validate_config_value(value)?; + if value.is_empty() { + return Err("Gamertag cannot be empty.".into()); + } + if value.chars().count() > 20 { + return Err("Gamertag cannot exceed 20 characters.".into()); + } + if !matches!( + color_code, + "" | "^0" | "^1" | "^2" | "^3" | "^4" | "^5" | "^6" | "^8" | "^9" + ) { + return Err("Invalid gamertag color code".into()); + } + Ok(format!("{color_code}{value}")) +} + +fn validate_config_value(value: &str) -> Result { + let value = value.trim(); + if value.contains(['\r', '\n', '\0']) { + return Err("T7 Patch settings cannot contain line breaks or NUL bytes".into()); + } + Ok(value.into()) +} + +fn update_conf( + contents: &str, + name: Option<&str>, + password: Option<&str>, + friends_only: Option, +) -> String { + let mut name_found = false; + let mut password_found = false; + let mut friends_found = false; + let mut lines = contents + .split_inclusive('\n') + .map(|line| { + if let Some(value) = name.filter(|_| line.starts_with("playername=")) { + name_found = true; + format!("playername={value}\n") + } else if let Some(value) = password.filter(|_| line.starts_with("networkpassword=")) { + password_found = true; + format!("networkpassword={value}\n") + } else if let Some(value) = friends_only.filter(|_| line.starts_with("isfriendsonly=")) + { + friends_found = true; + format!("isfriendsonly={}\n", i32::from(value)) + } else { + line.to_string() + } + }) + .collect::>(); + if contents.is_empty() { + lines.clear(); + } + if let Some(value) = name.filter(|_| !name_found) { + lines.insert(0, format!("playername={value}\n")); + } + if let Some(value) = password.filter(|_| !password_found) { + lines.insert(0, format!("networkpassword={value}\n")); + } + if let Some(value) = friends_only.filter(|_| !friends_found) { + lines.insert(0, format!("isfriendsonly={}\n", i32::from(value))); + } + lines.concat() +} + +fn fetch_release_digests(api: &str) -> Result, String> { + let response = reqwest::blocking::Client::builder() + .connect_timeout(Duration::from_secs(15)) + .timeout(Duration::from_secs(30)) + .build() + .map_err(|error| error.to_string())? + .get(api) + .header(reqwest::header::USER_AGENT, "PatchOpsIII") + .send() + .and_then(reqwest::blocking::Response::error_for_status) + .map_err(|error| format!("failed to query T7 Patch release metadata: {error}"))?; + if response + .content_length() + .is_some_and(|length| length > MAX_RELEASE_JSON_BYTES) + { + return Err("T7 Patch release metadata is unexpectedly large".into()); + } + let mut body = Vec::new(); + response + .take(MAX_RELEASE_JSON_BYTES + 1) + .read_to_end(&mut body) + .map_err(|error| error.to_string())?; + if body.len() as u64 > MAX_RELEASE_JSON_BYTES { + return Err("T7 Patch release metadata is unexpectedly large".into()); + } + release_digests_from_json(&body) +} + +fn release_digests_from_json(body: &[u8]) -> Result, String> { + let release: GitHubRelease = serde_json::from_slice(body) + .map_err(|error| format!("invalid T7 Patch release metadata: {error}"))?; + Ok(release + .assets + .into_iter() + .filter_map(|asset| { + let digest = normalize_sha256(&asset.digest)?; + let name = asset.name; + Some(( + name.clone(), + ReleaseAsset { + name, + digest, + download_url: asset.browser_download_url, + }, + )) + }) + .collect()) +} + +/// Release selection without HTTP or downloads. Only assets with valid GitHub +/// SHA-256 metadata participate, using the same selector as installation. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct T7ReleaseAsset { + pub name: String, + pub url: String, + pub sha256: String, +} + +pub fn select_t7_release_asset( + asset_key: &str, + release: &serde_json::Value, +) -> Result, String> { + let (spec, discover_current) = match asset_key { + "current_archive" => (CURRENT_ARCHIVE, true), + "compatible_archive" => (COMPATIBLE_ARCHIVE, false), + "lpc_archive" => (LPC_ARCHIVE, false), + _ => return Err("Unsupported T7 release asset key.".into()), + }; + let assets = + release_digests_from_json(&serde_json::to_vec(release).map_err(|e| e.to_string())?)?; + Ok( + select_release_asset(spec, &assets, discover_current).map(|asset| T7ReleaseAsset { + name: asset.name.clone(), + url: if asset.download_url.is_empty() { + spec.url.into() + } else { + asset.download_url.clone() + }, + sha256: asset.digest.clone(), + }), + ) +} + +fn trusted_asset( + spec: AssetSpec, + release: &Result, String>, + discover_current: bool, + state: &AppState, +) -> Result<(String, String), String> { + if let Ok(assets) = release + && let Some(asset) = select_release_asset(spec, assets, discover_current) + { + if discover_current && asset.name != spec.name { + state.log( + "Info", + format!("Using latest T7 Patch release asset {}.", asset.name), + ); + } + let url = if asset.download_url.is_empty() { + spec.url.to_owned() + } else { + asset.download_url.clone() + }; + return Ok((url, asset.digest.clone())); + } + if let Some(digest) = spec.pinned_sha256 { + state.log( + "Warning", + format!( + "Release metadata digest unavailable for {}; using pinned trusted hash.", + spec.name + ), + ); + return Ok((spec.url.into(), digest.into())); + } + Err(match release { + Err(error) => format!("{error}; no trusted SHA-256 is pinned for {}", spec.name), + Ok(_) => format!("No GitHub SHA-256 digest available for {}", spec.name), + }) +} + +fn select_release_asset( + spec: AssetSpec, + assets: &HashMap, + discover_current: bool, +) -> Option<&ReleaseAsset> { + if let Some(asset) = assets.get(spec.name) { + return Some(asset); + } + if spec.name.to_ascii_lowercase().ends_with(".zip") { + let stem = &spec.name[..spec.name.len() - 4]; + let prefix = format!("{stem}.").to_ascii_lowercase(); + let mut versioned = assets + .values() + .filter(|asset| { + let name = asset.name.to_ascii_lowercase(); + name.strip_prefix(&prefix) + .and_then(|suffix| suffix.strip_suffix(".zip")) + .is_some_and(|version| !version.is_empty() && !version.contains('/')) + }) + .collect::>(); + if versioned.len() == 1 { + return versioned.pop(); + } + } + if !discover_current { + return None; + } + let mut zip_assets = assets + .values() + .filter(|asset| asset.name.to_ascii_lowercase().ends_with(".zip")) + .collect::>(); + let mut candidates = zip_assets + .iter() + .copied() + .filter(|asset| { + let name = asset.name.to_ascii_lowercase(); + if cfg!(windows) { + name.contains("windows") + } else { + name.contains("linux") || name.contains("steamdeck") + } + }) + .collect::>(); + if candidates.is_empty() { + candidates = zip_assets + .drain(..) + .filter(|asset| { + let name = asset.name.to_ascii_lowercase(); + !["windows", "linux", "steamdeck"] + .iter() + .any(|token| name.contains(token)) + }) + .collect(); + } + let universal = candidates + .iter() + .copied() + .filter(|asset| { + let name = asset.name.to_ascii_lowercase(); + name.contains("linux") && name.contains("windows") + }) + .collect::>(); + if !universal.is_empty() { + candidates = universal; + } + (candidates.len() == 1).then(|| candidates[0]) +} + +fn normalize_sha256(value: &str) -> Option { + let value = value.trim(); + let value = value + .split_once(':') + .filter(|(prefix, _)| prefix.eq_ignore_ascii_case("sha256")) + .map_or(value, |(_, digest)| digest) + .trim() + .to_ascii_lowercase(); + (value.len() == 64 && value.bytes().all(|byte| byte.is_ascii_hexdigit())).then_some(value) +} + +fn find_patch_source(root: &Path) -> Result { + let mut directories = collect_directories(root)?; + directories.sort_by_key(|path| { + ( + if path + .file_name() + .and_then(|name| name.to_str()) + .is_some_and(|name| name.eq_ignore_ascii_case("linux")) + { + 0 + } else { + 1 + }, + path.components().count(), + path.to_string_lossy().to_ascii_lowercase(), + ) + }); + directories + .into_iter() + .find(|directory| { + let names = fs::read_dir(directory) + .into_iter() + .flatten() + .filter_map(Result::ok) + .filter(|entry| regular_file(&entry.path())) + .map(|entry| entry.file_name().to_string_lossy().to_ascii_lowercase()) + .collect::>(); + T7_REQUIRED_FILES.iter().all(|name| names.contains(*name)) + }) + .ok_or_else(|| "T7 Patch archive did not contain the expected files".into()) +} + +fn find_lpc_files(root: &Path) -> Result, String> { + let mut directories = collect_directories(root)?; + directories.sort_by_key(|path| { + ( + if path + .file_name() + .and_then(|name| name.to_str()) + .is_some_and(|name| name.eq_ignore_ascii_case("lpc")) + { + 0 + } else { + 1 + }, + path.components().count(), + path.to_string_lossy().to_ascii_lowercase(), + ) + }); + for directory in directories { + let mut files = fs::read_dir(&directory) + .map_err(|error| error.to_string())? + .filter_map(Result::ok) + .filter(|entry| { + regular_file(&entry.path()) + && entry + .file_name() + .to_str() + .is_some_and(|name| name.ends_with(".ff")) + }) + .map(|entry| entry.path()) + .collect::>(); + files.sort(); + if !files.is_empty() { + return Ok(files); + } + } + Err("LPC archive did not contain any .ff files".into()) +} + +fn collect_directories(root: &Path) -> Result, String> { + let mut directories = vec![root.to_path_buf()]; + let mut index = 0; + while index < directories.len() { + let directory = directories[index].clone(); + index += 1; + for entry in fs::read_dir(&directory).map_err(|error| error.to_string())? { + let entry = entry.map_err(|error| error.to_string())?; + let metadata = fs::symlink_metadata(entry.path()).map_err(|error| error.to_string())?; + if metadata.file_type().is_symlink() { + return Err("archive contains a link".into()); + } + if metadata.is_dir() { + directories.push(entry.path()); + } else if !metadata.is_file() { + return Err("archive contains a special file".into()); + } + } + } + Ok(directories) +} + +fn collect_files(root: &Path) -> Result, String> { + let mut files = Vec::new(); + let mut folded = HashSet::new(); + for directory in collect_directories(root)? { + for entry in fs::read_dir(&directory).map_err(|error| error.to_string())? { + let entry = entry.map_err(|error| error.to_string())?; + if !regular_file(&entry.path()) { + continue; + } + let relative = entry + .path() + .strip_prefix(root) + .map_err(|_| "archive path escaped extraction root".to_string())? + .to_path_buf(); + if !safe_relative(&relative) { + return Err("archive contains an unsafe path".into()); + } + let key = relative + .to_string_lossy() + .replace('\\', "/") + .to_ascii_lowercase(); + if !folded.insert(key) { + return Err("archive contains case-colliding paths".into()); + } + files.push((entry.path(), relative)); + } + } + files.sort_by(|left, right| left.1.cmp(&right.1)); + Ok(files) +} + +fn safe_relative(path: &Path) -> bool { + !path.as_os_str().is_empty() + && path + .components() + .all(|component| matches!(component, Component::Normal(_))) +} + +fn validate_game_dir(path: &Path) -> Result { + if path.as_os_str().is_empty() { + return Err("Game directory is not set".into()); + } + let path = path + .canonicalize() + .map_err(|error| format!("invalid game directory: {error}"))?; + if !path.is_dir() { + return Err("Game directory is not a directory".into()); + } + if !["BlackOpsIII.exe", "BlackOps3.exe"] + .iter() + .any(|name| regular_file(&path.join(name))) + { + return Err("Game directory does not contain a Black Ops III executable".into()); + } + Ok(path) +} + +fn validate_lpc_dir(game_dir: &Path) -> Result { + let lpc = game_dir.join("LPC"); + match fs::symlink_metadata(&lpc) { + Ok(metadata) if metadata.file_type().is_symlink() || !metadata.is_dir() => { + Err(format!("refusing to use unsafe LPC path {}", lpc.display())) + } + Ok(_) => Ok(lpc), + Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(lpc), + Err(error) => Err(error.to_string()), + } +} + +fn safe_target(root: &Path, relative: &Path) -> Result { + if !safe_relative(relative) { + return Err("invalid game-relative path".into()); + } + let components = relative.components().collect::>(); + let mut target = root.to_path_buf(); + for (index, component) in components.iter().enumerate() { + let Component::Normal(component) = component else { + return Err("invalid game-relative path".into()); + }; + target.push(component); + if let Ok(metadata) = fs::symlink_metadata(&target) { + if metadata.file_type().is_symlink() { + return Err(format!( + "refusing to follow linked path {}", + target.display() + )); + } + if index + 1 < components.len() && !metadata.is_dir() { + return Err(format!("expected a directory at {}", target.display())); + } + if index + 1 == components.len() && !metadata.is_file() { + return Err(format!("expected a file at {}", target.display())); + } + } + } + Ok(target) +} + +fn regular_file(path: &Path) -> bool { + fs::symlink_metadata(path) + .map(|metadata| metadata.is_file() && !metadata.file_type().is_symlink()) + .unwrap_or(false) +} + +fn regular_directory(path: &Path) -> bool { + fs::symlink_metadata(path) + .map(|metadata| metadata.is_dir() && !metadata.file_type().is_symlink()) + .unwrap_or(false) +} + +fn copy_file(source: &Path, target: &Path) -> Result<(), String> { + if let Some(parent) = target.parent() { + fs::create_dir_all(parent).map_err(|error| error.to_string())?; + } + fs::copy(source, target) + .map(|_| ()) + .map_err(|error| format!("failed to install {}: {error}", target.display())) +} + +fn create_backup_copy(source: &Path, target: &Path) -> Result<(), String> { + let mut input = fs::File::open(source).map_err(|error| error.to_string())?; + let mut output = fs::OpenOptions::new() + .create_new(true) + .write(true) + .open(target) + .map_err(|error| format!("failed to reserve backup {}: {error}", target.display()))?; + let result = std::io::copy(&mut input, &mut output) + .and_then(|_| output.sync_all()) + .map_err(|error| format!("failed to write backup {}: {error}", target.display())); + if result.is_err() { + drop(output); + let _ = fs::remove_file(target); + } + result +} + +fn ownership_manifest_relative() -> PathBuf { + Path::new(OWNERSHIP_DIRECTORY).join(OWNERSHIP_MANIFEST) +} + +fn ownership_manifest_path(game_dir: &Path) -> Result { + safe_target(game_dir, &ownership_manifest_relative()) +} + +fn folded_path(path: &Path) -> String { + path.to_string_lossy() + .replace('\\', "/") + .to_ascii_lowercase() +} + +fn valid_sha256(value: &str) -> bool { + value.len() == 64 && value.bytes().all(|byte| byte.is_ascii_hexdigit()) +} + +fn managed_relative(path: &Path) -> bool { + if !safe_relative(path) || path.starts_with(OWNERSHIP_DIRECTORY) { + return false; + } + let components = path.components().collect::>(); + match components.as_slice() { + [Component::Normal(name)] => name.to_str().is_some_and(|name| { + T7_GAME_FILES + .iter() + .any(|allowed| name.eq_ignore_ascii_case(allowed)) + }), + [Component::Normal(directory), Component::Normal(name)] => { + directory + .to_str() + .is_some_and(|directory| directory.eq_ignore_ascii_case("LPC")) + && name.to_str().is_some_and(|name| { + name.to_ascii_lowercase().ends_with(".ff") + && !name.ends_with(fs_ops::PATCHOPS_BACKUP_SUFFIX) + && !name.ends_with(fs_ops::LEGACY_BACKUP_SUFFIX) + }) + } + _ => false, + } +} + +fn owned_backup_relative(path: &Path) -> bool { + if !safe_relative(path) { + return false; + } + let components = path.components().collect::>(); + let [ + Component::Normal(owner), + Component::Normal(directory), + Component::Normal(generation), + Component::Normal(filename), + ] = components.as_slice() + else { + return false; + }; + owner == &std::ffi::OsStr::new(OWNERSHIP_DIRECTORY) + && directory == &std::ffi::OsStr::new(OWNERSHIP_BACKUP_DIRECTORY) + && generation.to_str().is_some_and(|value| { + !value.is_empty() + && value + .bytes() + .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_')) + }) + && filename.to_str().is_some_and(|value| { + let digits = value + .strip_prefix("entry-") + .and_then(|value| value.strip_suffix(".original")); + digits.is_some_and(|digits| { + !digits.is_empty() && digits.bytes().all(|byte| byte.is_ascii_digit()) + }) + }) +} + +fn validate_manifest(manifest: &OwnershipManifest) -> Result<(), String> { + if manifest.version != OWNERSHIP_VERSION { + return Err(format!( + "unsupported T7 ownership manifest version {}", + manifest.version + )); + } + let mut paths = HashSet::new(); + let mut backups = HashSet::new(); + for entry in &manifest.entries { + if !managed_relative(&entry.path) { + return Err(format!( + "T7 ownership manifest contains an invalid managed path: {}", + entry.path.display() + )); + } + if !paths.insert(folded_path(&entry.path)) { + return Err("T7 ownership manifest contains duplicate managed paths".into()); + } + if !valid_sha256(&entry.managed_sha256) { + return Err("T7 ownership manifest contains an invalid managed SHA-256".into()); + } + if let OriginalFile::Overwritten { + backup_path, + sha256, + .. + } = &entry.original + { + if !owned_backup_relative(backup_path) { + return Err(format!( + "T7 ownership manifest contains an invalid backup path: {}", + backup_path.display() + )); + } + if !backups.insert(folded_path(backup_path)) { + return Err("T7 ownership manifest reuses a backup path".into()); + } + if !valid_sha256(sha256) { + return Err("T7 ownership manifest contains an invalid backup SHA-256".into()); + } + } + } + Ok(()) +} + +fn load_manifest(game_dir: &Path) -> Result, String> { + let path = ownership_manifest_path(game_dir)?; + let metadata = match fs::symlink_metadata(&path) { + Ok(metadata) if metadata.is_file() && !metadata.file_type().is_symlink() => metadata, + Ok(_) => { + return Err(format!( + "refusing to use unsafe T7 ownership manifest {}", + path.display() + )); + } + Err(error) if error.kind() == std::io::ErrorKind::NotFound => { + let state_dir = game_dir.join(OWNERSHIP_DIRECTORY); + return match fs::symlink_metadata(&state_dir) { + Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(None), + Err(error) => Err(error.to_string()), + Ok(metadata) if metadata.file_type().is_symlink() || !metadata.is_dir() => Err( + format!("refusing unsafe T7 ownership state {}", state_dir.display()), + ), + Ok(_) => { + let mut entries = + fs::read_dir(&state_dir).map_err(|error| error.to_string())?; + if entries.next().is_none() { + Ok(None) + } else { + Err(format!( + "T7 ownership state is incomplete; recovery files were retained at {}", + state_dir.display() + )) + } + } + }; + } + Err(error) => return Err(error.to_string()), + }; + if metadata.len() > MAX_MANIFEST_BYTES { + return Err("T7 ownership manifest is unexpectedly large".into()); + } + let mut body = Vec::new(); + fs::File::open(&path) + .map_err(|error| error.to_string())? + .take(MAX_MANIFEST_BYTES + 1) + .read_to_end(&mut body) + .map_err(|error| error.to_string())?; + if body.len() as u64 > MAX_MANIFEST_BYTES { + return Err("T7 ownership manifest is unexpectedly large".into()); + } + let manifest: OwnershipManifest = serde_json::from_slice(&body) + .map_err(|error| format!("invalid T7 ownership manifest: {error}"))?; + validate_manifest(&manifest)?; + Ok(Some(manifest)) +} + +fn write_manifest(game_dir: &Path, manifest: &mut OwnershipManifest) -> Result<(), String> { + manifest + .entries + .sort_by_key(|entry| folded_path(&entry.path)); + validate_manifest(manifest)?; + let path = ownership_manifest_path(game_dir)?; + let parent = path + .parent() + .ok_or_else(|| "T7 ownership manifest has no parent".to_string())?; + fs::create_dir_all(parent).map_err(|error| error.to_string())?; + ownership_manifest_path(game_dir)?; + safe_target( + game_dir, + &Path::new(OWNERSHIP_DIRECTORY).join(format!(".{OWNERSHIP_MANIFEST}.patchops.tmp")), + )?; + let mut body = serde_json::to_vec_pretty(manifest).map_err(|error| error.to_string())?; + body.push(b'\n'); + fs_ops::atomic_write(&path, &body) +} + +fn verify_managed_entry(game_dir: &Path, entry: &ManagedFile) -> Result<(), String> { + let active = safe_target(game_dir, &entry.path)?; + let restore_only = matches!( + &entry.original, + OriginalFile::Overwritten { + sha256, + active_was_missing: true, + .. + } if sha256.eq_ignore_ascii_case(&entry.managed_sha256) + ); + match fs::symlink_metadata(&active) { + Ok(metadata) if metadata.is_file() && !metadata.file_type().is_symlink() => { + let actual = fs_ops::sha256_file(&active)?; + if !actual.eq_ignore_ascii_case(&entry.managed_sha256) { + return Err(format!( + "T7 ownership conflict: {} changed outside PatchOpsIII; no files were removed", + active.display() + )); + } + } + Err(error) if error.kind() == std::io::ErrorKind::NotFound && restore_only => {} + _ => { + return Err(format!( + "T7 ownership conflict: managed file is missing or unsafe: {}", + active.display() + )); + } + } + if let OriginalFile::Overwritten { + backup_path, + sha256, + .. + } = &entry.original + { + let backup = safe_target(game_dir, backup_path)?; + if !regular_file(&backup) { + return Err(format!( + "T7 recovery conflict: original backup is missing or unsafe: {}", + backup.display() + )); + } + let actual = fs_ops::sha256_file(&backup)?; + if !actual.eq_ignore_ascii_case(sha256) { + return Err(format!( + "T7 recovery conflict: original backup changed: {}", + backup.display() + )); + } + } + Ok(()) +} + +fn create_backup_generation(game_dir: &Path) -> Result { + let backup_root_relative = Path::new(OWNERSHIP_DIRECTORY).join(OWNERSHIP_BACKUP_DIRECTORY); + let probe = backup_root_relative.join(".patchops-directory-probe"); + safe_target(game_dir, &probe)?; + let backup_root = game_dir.join(&backup_root_relative); + fs::create_dir_all(&backup_root).map_err(|error| error.to_string())?; + safe_target(game_dir, &probe)?; + for _ in 0..100 { + let counter = STAGE_COUNTER.fetch_add(1, Ordering::Relaxed); + let timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_nanos(); + let generation = format!("{timestamp}-{}-{counter}", std::process::id()); + let path = backup_root.join(&generation); + match fs::create_dir(&path) { + Ok(()) => return Ok(generation), + Err(error) if error.kind() == std::io::ErrorKind::AlreadyExists => continue, + Err(error) => return Err(error.to_string()), + } + } + Err("could not allocate a unique T7 backup directory".into()) +} + +fn backup_relative(generation: &str, index: usize) -> PathBuf { + Path::new(OWNERSHIP_DIRECTORY) + .join(OWNERSHIP_BACKUP_DIRECTORY) + .join(generation) + .join(format!("entry-{index:04}.original")) +} + +fn expected_install_hashes( + operations: &[InstallOperation], +) -> Result, String> { + operations + .iter() + .map(|operation| { + Ok(( + folded_path(&operation.relative), + fs_ops::sha256_file(&operation.source)?, + )) + }) + .collect() +} + +fn known_t7_profile(marker_hashes: [&str; 2]) -> Option<&'static [(&'static str, &'static str)]> { + [ + KNOWN_CURRENT_FILES.as_slice(), + KNOWN_SCROPTSS_V3_03_FILES.as_slice(), + KNOWN_SCROPTSS_V3_02_FILES.as_slice(), + KNOWN_COMPATIBLE_FILES.as_slice(), + KNOWN_SHIVERSOFT_V2_03_FILES.as_slice(), + ] + .into_iter() + .find(|profile| { + T7_REQUIRED_FILES.iter().all(|name| { + let expected = profile + .iter() + .find(|(candidate, _)| candidate.eq_ignore_ascii_case(name)) + .map(|(_, hash)| *hash); + let actual = T7_REQUIRED_FILES + .iter() + .position(|candidate| candidate == name) + .map(|index| marker_hashes[index]); + expected.is_some() && expected == actual + }) + }) +} + +fn known_legacy_hashes(game_dir: &Path) -> Result>, String> { + let marker_hashes = T7_REQUIRED_FILES + .iter() + .map(|name| { + let target = safe_target(game_dir, Path::new(name))?; + if !target.exists() { + return Ok(None); + } + if !regular_file(&target) { + return Err(format!( + "refusing unsafe legacy T7 file {}", + target.display() + )); + } + fs_ops::sha256_file(&target).map(Some) + }) + .collect::, String>>()?; + if marker_hashes.iter().all(Option::is_none) { + return Ok(None); + } + if marker_hashes.iter().any(Option::is_none) { + return Err(format!( + "{UNTRACKED_UNINSTALL_ERROR}. The legacy T7 marker pair is incomplete" + )); + } + + let profile = known_t7_profile([ + marker_hashes[0] + .as_deref() + .expect("complete marker pair checked above"), + marker_hashes[1] + .as_deref() + .expect("complete marker pair checked above"), + ]) + .ok_or_else(|| { + format!( + "{UNTRACKED_UNINSTALL_ERROR}. The installed T7 payload does not match a release known to this build" + ) + })?; + + let mut expected = profile + .iter() + .map(|(path, hash)| (folded_path(Path::new(path)), (*hash).to_owned())) + .collect::>(); + expected.extend(KNOWN_LPC_FILES.iter().map(|(name, hash)| { + ( + folded_path(&Path::new("LPC").join(name)), + (*hash).to_owned(), + ) + })); + Ok(Some(expected)) +} + +fn suffixed_path(path: &Path, suffix: &str) -> PathBuf { + let mut value = path.as_os_str().to_os_string(); + value.push(suffix); + value.into() +} + +/// Enumerate only direct legacy LPC backups that the Python implementation +/// could have created. Matching is case-insensitive for ownership purposes, +/// while `.patchops.bak` wins over the older `.bak` when both exist. +fn legacy_lpc_backups(game_dir: &Path) -> Result, String> { + let lpc = validate_lpc_dir(game_dir)?; + match fs::symlink_metadata(&lpc) { + Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(Vec::new()), + Err(error) => return Err(error.to_string()), + Ok(metadata) if metadata.is_dir() && !metadata.file_type().is_symlink() => {} + Ok(_) => return Err(format!("refusing unsafe LPC directory {}", lpc.display())), + } + + let mut selected = HashMap::::new(); + for item in fs::read_dir(&lpc).map_err(|error| error.to_string())? { + let item = item.map_err(|error| error.to_string())?; + let Some(filename) = item.file_name().to_str().map(str::to_owned) else { + continue; + }; + let (base, priority) = + if let Some(base) = filename.strip_suffix(fs_ops::PATCHOPS_BACKUP_SUFFIX) { + (base, 2) + } else if let Some(base) = filename.strip_suffix(fs_ops::LEGACY_BACKUP_SUFFIX) { + (base, 1) + } else { + continue; + }; + if !base.to_ascii_lowercase().ends_with(".ff") { + continue; + } + let relative = Path::new("LPC").join(base); + if !managed_relative(&relative) { + return Err(format!( + "refusing unsafe legacy LPC backup {}", + item.path().display() + )); + } + let key = folded_path(&relative); + if let Some((existing_relative, _, existing_priority)) = selected.get(&key) { + if existing_relative != &relative { + return Err(format!( + "legacy LPC backups contain ambiguous path casing for {}", + relative.display() + )); + } + if priority <= *existing_priority { + continue; + } + } + selected.insert(key, (relative, item.path(), priority)); + } + let mut selected = selected + .into_values() + .map(|(relative, backup, _)| (relative, backup)) + .collect::>(); + selected.sort_by_key(|(relative, _)| folded_path(relative)); + Ok(selected) +} + +/// Convert an origin/main T7 install into the ownership format only when every +/// fixed payload byte matches a verified archive member. User-editable config +/// remains unowned. LPC suffix backups are copied into app-owned recovery +/// storage; the legacy copies are kept until the follow-on operation succeeds. +fn adopt_legacy_install( + game_dir: &Path, + expected: &HashMap, +) -> Result { + if load_manifest(game_dir)?.is_some() { + return Ok(false); + } + let marker_paths = T7_REQUIRED_FILES + .iter() + .map(|name| safe_target(game_dir, Path::new(name))) + .collect::, _>>()?; + if marker_paths.iter().all(|path| !path.exists()) { + return Ok(false); + } + if marker_paths.iter().any(|path| !regular_file(path)) { + return Err(format!( + "{UNTRACKED_UNINSTALL_ERROR}. The legacy T7 marker pair is incomplete or unsafe" + )); + } + for path in &marker_paths { + let relative = path + .strip_prefix(game_dir) + .map_err(|_| "legacy T7 path escaped the game directory".to_string())?; + let Some(expected_hash) = expected.get(&folded_path(relative)) else { + return Err(format!( + "{UNTRACKED_UNINSTALL_ERROR}. The installed T7 payload is not recognized by this build" + )); + }; + if !fs_ops::sha256_file(path)?.eq_ignore_ascii_case(expected_hash) { + return Err(format!( + "{UNTRACKED_UNINSTALL_ERROR}. {} does not match the verified legacy payload", + path.display() + )); + } + } + + let config = safe_target(game_dir, Path::new("t7patch.conf"))?; + if config.exists() && !regular_file(&config) { + return Err(format!( + "refusing unsafe legacy T7 config {}", + config.display() + )); + } + + let mut entries = Vec::new(); + for name in T7_GAME_FILES { + if name.eq_ignore_ascii_case("t7patch.conf") { + continue; + } + let relative = PathBuf::from(name); + let target = safe_target(game_dir, &relative)?; + if !target.exists() { + continue; + } + if !regular_file(&target) { + return Err(format!( + "refusing unsafe legacy T7 file {}", + target.display() + )); + } + let Some(expected_hash) = expected.get(&folded_path(&relative)) else { + return Err(format!( + "legacy T7 file {} is not part of the recognized payload; no files were changed", + target.display() + )); + }; + let actual = fs_ops::sha256_file(&target)?; + if !actual.eq_ignore_ascii_case(expected_hash) { + return Err(format!( + "legacy T7 file {} changed after installation; no files were changed", + target.display() + )); + } + entries.push(ManagedFile { + path: relative, + managed_sha256: actual, + original: OriginalFile::Created, + }); + } + + let mut generation = None; + let mut copied_backups = Vec::new(); + let result = (|| { + for (name, _) in KNOWN_LPC_FILES { + let relative = Path::new("LPC").join(name); + let Some(expected_hash) = expected.get(&folded_path(&relative)) else { + continue; + }; + let target = safe_target(game_dir, &relative)?; + if !target.exists() { + continue; + } + if !regular_file(&target) { + return Err(format!( + "refusing unsafe legacy LPC file {}", + target.display() + )); + } + let actual = fs_ops::sha256_file(&target)?; + if !actual.eq_ignore_ascii_case(expected_hash) { + return Err(format!( + "legacy LPC file {} changed after installation; no files were changed", + target.display() + )); + } + + let patchops = suffixed_path(&target, fs_ops::PATCHOPS_BACKUP_SUFFIX); + let legacy = suffixed_path(&target, fs_ops::LEGACY_BACKUP_SUFFIX); + let selected = if patchops.exists() { + Some(patchops) + } else if legacy.exists() { + Some(legacy) + } else { + None + }; + let original = if let Some(selected) = selected { + if !regular_file(&selected) { + return Err(format!( + "refusing unsafe legacy LPC backup {}", + selected.display() + )); + } + let original_sha256 = fs_ops::sha256_file(&selected)?; + if generation.is_none() { + generation = Some(create_backup_generation(game_dir)?); + } + let owned_relative = backup_relative( + generation + .as_deref() + .ok_or_else(|| "failed to allocate T7 backup generation".to_string())?, + copied_backups.len(), + ); + let owned = safe_target(game_dir, &owned_relative)?; + create_backup_copy(&selected, &owned)?; + if !fs_ops::sha256_file(&owned)?.eq_ignore_ascii_case(&original_sha256) { + return Err(format!( + "failed to verify adopted LPC backup {}", + owned.display() + )); + } + copied_backups.push(owned); + OriginalFile::Overwritten { + backup_path: owned_relative, + sha256: original_sha256, + active_was_missing: false, + } + } else { + OriginalFile::Created + }; + entries.push(ManagedFile { + path: relative, + managed_sha256: actual, + original, + }); + } + + let mut adopted_paths = entries + .iter() + .map(|entry| folded_path(&entry.path)) + .collect::>(); + for (relative, selected) in legacy_lpc_backups(game_dir)? { + let key = folded_path(&relative); + if adopted_paths.contains(&key) { + continue; + } + if !regular_file(&selected) { + return Err(format!( + "refusing unsafe legacy LPC backup {}", + selected.display() + )); + } + let target = safe_target(game_dir, &relative)?; + match fs::symlink_metadata(&target) { + Err(error) if error.kind() == std::io::ErrorKind::NotFound => {} + Err(error) => return Err(error.to_string()), + Ok(_) => { + return Err(format!( + "legacy LPC target {} is not part of the recognized payload; no files were changed", + target.display() + )); + } + } + let original_sha256 = fs_ops::sha256_file(&selected)?; + if generation.is_none() { + generation = Some(create_backup_generation(game_dir)?); + } + let owned_relative = backup_relative( + generation + .as_deref() + .ok_or_else(|| "failed to allocate T7 backup generation".to_string())?, + copied_backups.len(), + ); + let owned = safe_target(game_dir, &owned_relative)?; + create_backup_copy(&selected, &owned)?; + if !fs_ops::sha256_file(&owned)?.eq_ignore_ascii_case(&original_sha256) { + return Err(format!( + "failed to verify adopted LPC backup {}", + owned.display() + )); + } + copied_backups.push(owned); + entries.push(ManagedFile { + path: relative, + managed_sha256: original_sha256.clone(), + original: OriginalFile::Overwritten { + backup_path: owned_relative, + sha256: original_sha256, + active_was_missing: true, + }, + }); + adopted_paths.insert(key); + } + write_manifest( + game_dir, + &mut OwnershipManifest { + version: OWNERSHIP_VERSION, + entries, + }, + ) + })(); + if let Err(error) = result { + for backup in copied_backups { + let _ = remove_file_if_present(&backup); + } + if let Some(generation) = generation { + let directory = game_dir + .join(OWNERSHIP_DIRECTORY) + .join(OWNERSHIP_BACKUP_DIRECTORY) + .join(generation); + let _ = fs::remove_dir(directory); + let _ = fs::remove_dir( + game_dir + .join(OWNERSHIP_DIRECTORY) + .join(OWNERSHIP_BACKUP_DIRECTORY), + ); + let _ = fs::remove_dir(game_dir.join(OWNERSHIP_DIRECTORY)); + } + return Err(error); + } + Ok(true) +} + +fn discard_adopted_manifest(game_dir: &Path) -> Result<(), String> { + let Some(manifest) = load_manifest(game_dir)? else { + return Ok(()); + }; + for entry in &manifest.entries { + if let OriginalFile::Overwritten { backup_path, .. } = &entry.original { + remove_file_if_present(&safe_target(game_dir, backup_path)?)?; + } + } + remove_file_if_present(&ownership_manifest_path(game_dir)?)?; + remove_empty_ownership_directories(game_dir); + Ok(()) +} + +fn remove_empty_ownership_directories(game_dir: &Path) { + let backup_root = game_dir + .join(OWNERSHIP_DIRECTORY) + .join(OWNERSHIP_BACKUP_DIRECTORY); + if regular_directory(&backup_root) { + if let Ok(entries) = fs::read_dir(&backup_root) { + for entry in entries.filter_map(Result::ok) { + let path = entry.path(); + if regular_directory(&path) { + let _ = fs::remove_dir(path); + } + } + } + let _ = fs::remove_dir(&backup_root); + } + let _ = fs::remove_dir(game_dir.join(OWNERSHIP_DIRECTORY)); +} + +fn reject_unsafe_profile_transition(game_dir: &Path, profile: PatchProfile) -> Result<(), String> { + if profile != PatchProfile::Current { + return Ok(()); + } + let Some(manifest) = load_manifest(game_dir)? else { + return Ok(()); + }; + let would_restore_compatible_file = manifest.entries.iter().any(|entry| { + entry + .path + .file_name() + .and_then(|name| name.to_str()) + .is_some_and(|name| { + COMPATIBLE_ONLY_FILES + .iter() + .any(|candidate| candidate.eq_ignore_ascii_case(name)) + }) + && matches!(entry.original, OriginalFile::Overwritten { .. }) + }); + if would_restore_compatible_file { + Err("Cannot switch this managed T7 install to the Current profile because doing so would restore a pre-existing compatible-only DLL into the active game directory. Uninstall T7 Patch first and review the restored file before installing the Current profile.".into()) + } else { + Ok(()) + } +} + +fn rollback_transaction( + snapshot: &Snapshot, + error: String, + rollback_directory: &Path, +) -> Result<(), String> { + match snapshot.restore() { + Ok(()) => Err(format!("{error}; previous T7 files were restored")), + Err(rollback) => Err(format!( + "{error}; rollback failed: {rollback}. Recovery files remain in {}", + rollback_directory.display() + )), + } +} + +fn apply_managed_install( + game_dir: &Path, + operations: &[InstallOperation], + retained: &[PathBuf], + rollback_directory: &Path, +) -> Result<(), String> { + let previous = load_manifest(game_dir)?.unwrap_or(OwnershipManifest { + version: OWNERSHIP_VERSION, + entries: Vec::new(), + }); + let mut previous_by_path = previous + .entries + .into_iter() + .map(|entry| (folded_path(&entry.path), entry)) + .collect::>(); + let mut generation = None; + let mut desired_paths = HashSet::new(); + let mut prepared = Vec::new(); + let mut new_backup_index = 0_usize; + + for operation in operations { + if !regular_file(&operation.source) || !managed_relative(&operation.relative) { + return Err(format!( + "refusing unsafe T7 install operation for {}", + operation.relative.display() + )); + } + let key = folded_path(&operation.relative); + if !desired_paths.insert(key.clone()) { + return Err("T7 install contains duplicate target paths".into()); + } + let active = safe_target(game_dir, &operation.relative)?; + let managed_sha256 = fs_ops::sha256_file(&operation.source)?; + let (original, new_backup) = if let Some(previous) = previous_by_path.remove(&key) { + if previous.path.as_path() != operation.relative.as_path() { + return Err(format!( + "T7 ownership path casing changed from {} to {}; refusing an ambiguous update", + previous.path.display(), + operation.relative.display() + )); + } + verify_managed_entry(game_dir, &previous)?; + (previous.original, None) + } else if regular_file(&active) { + let original_sha256 = fs_ops::sha256_file(&active)?; + if generation.is_none() { + generation = Some(create_backup_generation(game_dir)?); + } + let generation = generation + .as_deref() + .ok_or_else(|| "failed to allocate T7 backup generation".to_string())?; + let relative = backup_relative(generation, new_backup_index); + new_backup_index += 1; + let backup = safe_target(game_dir, &relative)?; + match fs::symlink_metadata(&backup) { + Err(error) if error.kind() == std::io::ErrorKind::NotFound => {} + Ok(_) => return Err(format!("refusing to reuse backup {}", backup.display())), + Err(error) => return Err(error.to_string()), + } + ( + OriginalFile::Overwritten { + backup_path: relative, + sha256: original_sha256, + active_was_missing: false, + }, + Some(backup), + ) + } else { + (OriginalFile::Created, None) + }; + prepared.push(( + operation, + active, + ManagedFile { + path: operation.relative.clone(), + managed_sha256, + original, + }, + new_backup, + )); + } + + let mut entries = prepared + .iter() + .map(|(_, _, entry, _)| entry.clone()) + .collect::>(); + for relative in retained { + if !managed_relative(relative) { + return Err(format!( + "refusing unsafe retained T7 path {}", + relative.display() + )); + } + let key = folded_path(relative); + if !desired_paths.insert(key.clone()) { + return Err("T7 install contains duplicate retained paths".into()); + } + if let Some(previous) = previous_by_path.remove(&key) { + if previous.path.as_path() != relative.as_path() { + return Err(format!( + "T7 ownership path casing changed from {} to {}; refusing an ambiguous update", + previous.path.display(), + relative.display() + )); + } + verify_managed_entry(game_dir, &previous)?; + entries.push(previous); + } + } + + let retired = previous_by_path.into_values().collect::>(); + for entry in &retired { + verify_managed_entry(game_dir, entry)?; + } + + let manifest_path = ownership_manifest_path(game_dir)?; + let mut targets = vec![manifest_path]; + for (_, active, _, new_backup) in &prepared { + targets.push(active.clone()); + if let Some(backup) = new_backup { + targets.push(backup.clone()); + } + } + for entry in &retired { + targets.push(safe_target(game_dir, &entry.path)?); + } + let snapshot = Snapshot::capture(&targets, rollback_directory)?; + + let commit: Result<(), String> = (|| { + for (_, active, entry, new_backup) in &prepared { + if let Some(backup) = new_backup { + create_backup_copy(active, backup)?; + let expected = match &entry.original { + OriginalFile::Overwritten { sha256, .. } => sha256, + OriginalFile::Created => unreachable!(), + }; + if !fs_ops::sha256_file(backup)?.eq_ignore_ascii_case(expected) { + return Err(format!( + "failed to verify original T7 backup {}", + backup.display() + )); + } + } + } + for (operation, active, _, _) in &prepared { + copy_file(&operation.source, active)?; + } + for entry in &retired { + let active = safe_target(game_dir, &entry.path)?; + match &entry.original { + OriginalFile::Created => { + remove_file_if_present(&active)?; + if active.exists() { + return Err(format!( + "failed to retire managed T7 file {}", + active.display() + )); + } + } + OriginalFile::Overwritten { + backup_path, + sha256, + .. + } => { + let backup = safe_target(game_dir, backup_path)?; + copy_file(&backup, &active)?; + if !fs_ops::sha256_file(&active)?.eq_ignore_ascii_case(sha256) { + return Err(format!( + "failed to verify restored T7 file {}", + active.display() + )); + } + } + } + } + for entry in &entries { + let active = safe_target(game_dir, &entry.path)?; + if !regular_file(&active) + || !fs_ops::sha256_file(&active)?.eq_ignore_ascii_case(&entry.managed_sha256) + { + return Err(format!( + "T7 Patch install verification failed for {}", + active.display() + )); + } + } + write_manifest( + game_dir, + &mut OwnershipManifest { + version: OWNERSHIP_VERSION, + entries, + }, + ) + })(); + + if let Err(error) = commit { + return rollback_transaction(&snapshot, error, rollback_directory); + } + for entry in &retired { + if let OriginalFile::Overwritten { backup_path, .. } = &entry.original + && let Ok(backup) = safe_target(game_dir, backup_path) + { + let _ = remove_file_if_present(&backup); + } + } + remove_empty_ownership_directories(game_dir); + Ok(()) +} + +fn apply_managed_uninstall(game_dir: &Path, rollback_directory: &Path) -> Result<(), String> { + let manifest = load_manifest(game_dir)?.ok_or_else(|| UNTRACKED_UNINSTALL_ERROR.to_string())?; + for entry in &manifest.entries { + verify_managed_entry(game_dir, entry)?; + } + + let manifest_path = ownership_manifest_path(game_dir)?; + let mut targets = vec![manifest_path.clone()]; + for entry in &manifest.entries { + targets.push(safe_target(game_dir, &entry.path)?); + if let OriginalFile::Overwritten { backup_path, .. } = &entry.original { + targets.push(safe_target(game_dir, backup_path)?); + } + } + let snapshot = Snapshot::capture(&targets, rollback_directory)?; + let commit: Result<(), String> = (|| { + for entry in &manifest.entries { + let active = safe_target(game_dir, &entry.path)?; + match &entry.original { + OriginalFile::Created => { + remove_file_if_present(&active)?; + if active.exists() { + return Err(format!( + "failed to remove managed T7 file {}", + active.display() + )); + } + } + OriginalFile::Overwritten { + backup_path, + sha256, + .. + } => { + let backup = safe_target(game_dir, backup_path)?; + copy_file(&backup, &active)?; + if !fs_ops::sha256_file(&active)?.eq_ignore_ascii_case(sha256) { + return Err(format!( + "failed to verify restored T7 file {}", + active.display() + )); + } + remove_file_if_present(&backup)?; + } + } + } + remove_file_if_present(&manifest_path) + })(); + if let Err(error) = commit { + return rollback_transaction(&snapshot, error, rollback_directory); + } + remove_empty_ownership_directories(game_dir); + Ok(()) +} + +fn write_config_with_ownership( + game_dir: &Path, + contents: &[u8], + rollback_directory: &Path, +) -> Result<(), String> { + let relative = PathBuf::from("t7patch.conf"); + let path = safe_target(game_dir, &relative)?; + let Some(mut manifest) = load_manifest(game_dir)? else { + return fs_ops::atomic_write(&path, contents); + }; + let Some(index) = manifest + .entries + .iter() + .position(|entry| folded_path(&entry.path) == folded_path(&relative)) + else { + return fs_ops::atomic_write(&path, contents); + }; + verify_managed_entry(game_dir, &manifest.entries[index])?; + let manifest_path = ownership_manifest_path(game_dir)?; + let snapshot = Snapshot::capture(&[path.clone(), manifest_path], rollback_directory)?; + let commit: Result<(), String> = (|| { + fs_ops::atomic_write(&path, contents)?; + manifest.entries[index].managed_sha256 = fs_ops::sha256_file(&path)?; + write_manifest(game_dir, &mut manifest) + })(); + match commit { + Ok(()) => Ok(()), + Err(error) => rollback_transaction(&snapshot, error, rollback_directory), + } +} + +fn create_stage(state: &AppState, label: &str) -> Result { + let root = state.mod_files_dir(); + fs::create_dir_all(&root).map_err(|error| error.to_string())?; + for _ in 0..10 { + let id = STAGE_COUNTER.fetch_add(1, Ordering::Relaxed); + let stage = root.join(format!(".{label}-stage-{}-{id}", std::process::id())); + match fs::create_dir(&stage) { + Ok(()) => return Ok(stage), + Err(error) if error.kind() == std::io::ErrorKind::AlreadyExists => continue, + Err(error) => return Err(error.to_string()), + } + } + Err("could not create a unique staging directory".into()) +} + +fn clear_legacy_cache(state: &AppState) { + let root = state.mod_files_dir(); + for directory in [ + root.join("linux"), + root.join("Linux.Steamdeck.and.Manual.Windows.Install"), + root.join("T7Patch_extracted"), + ] { + if fs::symlink_metadata(&directory) + .is_ok_and(|metadata| metadata.is_dir() && !metadata.file_type().is_symlink()) + { + let _ = fs::remove_dir_all(directory); + } + } + let archive = root.join("T7Patch.zip"); + if regular_file(&archive) { + let _ = fs::remove_file(archive); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::atomic::{AtomicU64, Ordering}; + + static TEST_COUNTER: AtomicU64 = AtomicU64::new(0); + + fn temp_dir(label: &str) -> PathBuf { + let path = std::env::temp_dir().join(format!( + "patchops-t7-{label}-{}-{}", + std::process::id(), + TEST_COUNTER.fetch_add(1, Ordering::Relaxed) + )); + fs::create_dir_all(&path).unwrap(); + path + } + + fn operation(source_root: &Path, relative: &str, contents: &[u8]) -> InstallOperation { + let source = source_root.join(relative); + fs::create_dir_all(source.parent().unwrap()).unwrap(); + fs::write(&source, contents).unwrap(); + InstallOperation { + source, + relative: PathBuf::from(relative), + } + } + + #[test] + fn parses_and_updates_t7_config_without_losing_unknown_lines() { + let original = "unknown=keep\nplayername=Old\nnetworkpassword=old\nisfriendsonly=0\n"; + let updated = update_conf(original, Some("^2New"), Some("secret"), Some(true)); + assert_eq!( + updated, + "unknown=keep\nplayername=^2New\nnetworkpassword=secret\nisfriendsonly=1\n" + ); + let parsed = parse_conf(&updated); + assert_eq!(parsed.gamertag, "^2New"); + assert_eq!(parsed.color_code, "^2"); + assert_eq!(parsed.plain_name, "New"); + assert_eq!(parsed.network_password, "secret"); + assert!(parsed.friends_only); + + assert!(validate_gamertag("bad\nname", "^1").is_err()); + assert!(validate_gamertag("name", "^7").is_err()); + } + + #[test] + fn selects_patch_by_executable_profile_and_parses_github_digest() { + assert_eq!(patch_profile("current").unwrap(), PatchProfile::Current); + assert_eq!(patch_profile("enhanced").unwrap(), PatchProfile::Current); + assert_eq!( + patch_profile("compatible").unwrap(), + PatchProfile::Compatible + ); + assert!(patch_profile("unverified").is_err()); + + let game = temp_dir("profile"); + assert_eq!(mode_for_profile(&game, Some("compatible")), "Custom"); + fs::remove_dir_all(game).unwrap(); + + let digests = release_digests_from_json( + br#"{"assets":[{"name":"Linux.Steamdeck.and.Manual.Windows.Install.zip","digest":"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","browser_download_url":"https://example.invalid/exact.zip"}]}"#, + ) + .unwrap(); + assert_eq!( + digests + .get(PATCH_ARCHIVE_NAME) + .map(|asset| asset.digest.as_str()), + Some("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + ); + } + + #[test] + fn resolves_versioned_latest_release_archive() { + let assets = release_digests_from_json( + br#"{"assets":[{"name":"Linux.Steamdeck.and.Manual.Windows.Install.3.4.1.zip","digest":"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","browser_download_url":"https://github.com/Scroptss/T7Patch/releases/download/v3.4.1/archive.zip"}]}"#, + ) + .unwrap(); + let selected = select_release_asset(CURRENT_ARCHIVE, &assets, true).unwrap(); + assert_eq!( + selected.name, + "Linux.Steamdeck.and.Manual.Windows.Install.3.4.1.zip" + ); + } + + #[test] + fn discovers_unambiguously_renamed_universal_release_archive() { + let assets = release_digests_from_json( + br#"{"assets":[ + {"name":"T7Patch-Windows.zip","digest":"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","browser_download_url":"https://example.invalid/windows.zip"}, + {"name":"T7Patch-Linux-Windows.zip","digest":"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","browser_download_url":"https://example.invalid/universal.zip"}, + {"name":"checksums.txt","digest":"sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc","browser_download_url":"https://example.invalid/checksums.txt"} + ]}"#, + ) + .unwrap(); + let selected = select_release_asset(CURRENT_ARCHIVE, &assets, true).unwrap(); + assert_eq!(selected.name, "T7Patch-Linux-Windows.zip"); + assert_eq!( + selected.download_url, + "https://example.invalid/universal.zip" + ); + } + + #[test] + fn rejects_ambiguous_renamed_release_archives() { + let assets = release_digests_from_json( + br#"{"assets":[ + {"name":"T7Patch-Linux-a.zip","digest":"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","browser_download_url":"https://example.invalid/a.zip"}, + {"name":"T7Patch-Linux-b.zip","digest":"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","browser_download_url":"https://example.invalid/b.zip"} + ]}"#, + ) + .unwrap(); + assert!(select_release_asset(CURRENT_ARCHIVE, &assets, true).is_none()); + } + + #[test] + fn renamed_assets_need_valid_github_digests() { + let assets = release_digests_from_json(br#"{"assets":[ + {"name":"T7Patch-Linux-Windows.zip","digest":"","browser_download_url":"https://example.invalid/unsigned.zip"}, + {"name":"T7Patch-Linux.zip","digest":"sha256:not-a-digest"} + ]}"#).unwrap(); + assert!(assets.is_empty()); + assert!(select_release_asset(CURRENT_ARCHIVE, &assets, true).is_none()); + } + + #[test] + fn historical_t7_marker_pairs_select_only_their_exact_release_profile() { + assert_eq!( + known_t7_profile([ + KNOWN_SCROPTSS_V3_03_FILES[1].1, + KNOWN_SCROPTSS_V3_03_FILES[2].1, + ]), + Some(KNOWN_SCROPTSS_V3_03_FILES.as_slice()) + ); + assert_eq!( + known_t7_profile([ + KNOWN_SHIVERSOFT_V2_03_FILES[2].1, + KNOWN_SHIVERSOFT_V2_03_FILES[3].1, + ]), + Some(KNOWN_SHIVERSOFT_V2_03_FILES.as_slice()) + ); + assert!( + known_t7_profile([ + KNOWN_SCROPTSS_V3_03_FILES[1].1, + KNOWN_SHIVERSOFT_V2_03_FILES[3].1, + ]) + .is_none() + ); + } + + #[test] + fn ownership_manifest_preserves_originals_across_updates_and_covers_lpc() { + let game = temp_dir("ownership"); + let sources = temp_dir("ownership-sources"); + let rollback = temp_dir("ownership-rollback"); + fs::create_dir_all(game.join("LPC")).unwrap(); + fs::write(game.join("t7patch.dll"), b"original-patch").unwrap(); + fs::write(game.join("discord_game_sdk.dll"), b"original-discord").unwrap(); + fs::write(game.join("LPC/english.ff"), b"original-lpc").unwrap(); + + let unrelated_patchops = game.join("LPC/english.ff.patchops.bak"); + let unrelated_legacy = game.join("LPC/english.ff.bak"); + fs::write(&unrelated_patchops, b"unrelated-patchops-backup").unwrap(); + fs::write(&unrelated_legacy, b"unrelated-legacy-backup").unwrap(); + + let first = [ + operation(&sources, "first/t7patch.dll", b"managed-patch-v1"), + operation(&sources, "first/t7patchloader.dll", b"managed-loader-v1"), + operation( + &sources, + "first/discord_game_sdk.dll", + b"managed-discord-v1", + ), + operation(&sources, "first/english.ff", b"managed-lpc-v1"), + ]; + let first = [ + InstallOperation { + source: first[0].source.clone(), + relative: PathBuf::from("t7patch.dll"), + }, + InstallOperation { + source: first[1].source.clone(), + relative: PathBuf::from("t7patchloader.dll"), + }, + InstallOperation { + source: first[2].source.clone(), + relative: PathBuf::from("discord_game_sdk.dll"), + }, + InstallOperation { + source: first[3].source.clone(), + relative: PathBuf::from("LPC/english.ff"), + }, + ]; + apply_managed_install(&game, &first, &[], &rollback.join("first")).unwrap(); + + let first_manifest = load_manifest(&game).unwrap().unwrap(); + assert_eq!(first_manifest.entries.len(), 4); + let patch_entry = first_manifest + .entries + .iter() + .find(|entry| entry.path == Path::new("t7patch.dll")) + .unwrap(); + let OriginalFile::Overwritten { + backup_path: patch_backup, + sha256: patch_original_hash, + .. + } = &patch_entry.original + else { + panic!("existing patch file was not recorded as overwritten"); + }; + assert!(owned_backup_relative(patch_backup)); + assert!(!patch_backup.to_string_lossy().ends_with(".bak")); + assert_eq!( + fs_ops::sha256_file(&game.join(patch_backup)).unwrap(), + *patch_original_hash + ); + let preserved_patch_backup = patch_backup.clone(); + let lpc_entry = first_manifest + .entries + .iter() + .find(|entry| entry.path == Path::new("LPC/english.ff")) + .unwrap(); + assert!(matches!( + lpc_entry.original, + OriginalFile::Overwritten { .. } + )); + let loader_entry = first_manifest + .entries + .iter() + .find(|entry| entry.path == Path::new("t7patchloader.dll")) + .unwrap(); + assert!(matches!(loader_entry.original, OriginalFile::Created)); + + let second = [ + InstallOperation { + source: operation(&sources, "second/t7patch.dll", b"managed-patch-v2").source, + relative: PathBuf::from("t7patch.dll"), + }, + InstallOperation { + source: operation(&sources, "second/t7patchloader.dll", b"managed-loader-v2") + .source, + relative: PathBuf::from("t7patchloader.dll"), + }, + InstallOperation { + source: operation(&sources, "second/english.ff", b"managed-lpc-v2").source, + relative: PathBuf::from("LPC/english.ff"), + }, + ]; + apply_managed_install(&game, &second, &[], &rollback.join("second")).unwrap(); + + let second_manifest = load_manifest(&game).unwrap().unwrap(); + let updated_patch = second_manifest + .entries + .iter() + .find(|entry| entry.path == Path::new("t7patch.dll")) + .unwrap(); + let OriginalFile::Overwritten { backup_path, .. } = &updated_patch.original else { + panic!("updated patch lost its original backup"); + }; + assert_eq!(backup_path, &preserved_patch_backup); + assert_eq!(fs::read(game.join(backup_path)).unwrap(), b"original-patch"); + assert_eq!( + fs::read(game.join("discord_game_sdk.dll")).unwrap(), + b"original-discord" + ); + assert!( + second_manifest + .entries + .iter() + .all(|entry| entry.path != Path::new("discord_game_sdk.dll")) + ); + assert_eq!( + fs::read(&unrelated_patchops).unwrap(), + b"unrelated-patchops-backup" + ); + assert_eq!( + fs::read(&unrelated_legacy).unwrap(), + b"unrelated-legacy-backup" + ); + + apply_managed_uninstall(&game, &rollback.join("uninstall")).unwrap(); + assert_eq!( + fs::read(game.join("t7patch.dll")).unwrap(), + b"original-patch" + ); + assert_eq!( + fs::read(game.join("LPC/english.ff")).unwrap(), + b"original-lpc" + ); + assert!(!game.join("t7patchloader.dll").exists()); + assert_eq!( + fs::read(&unrelated_patchops).unwrap(), + b"unrelated-patchops-backup" + ); + assert_eq!( + fs::read(&unrelated_legacy).unwrap(), + b"unrelated-legacy-backup" + ); + assert!(load_manifest(&game).unwrap().is_none()); + + fs::remove_dir_all(game).unwrap(); + fs::remove_dir_all(sources).unwrap(); + fs::remove_dir_all(rollback).unwrap(); + } + + #[test] + fn exact_legacy_install_is_adopted_and_lpc_original_is_restored() { + let game = temp_dir("legacy-adopt"); + let rollback = temp_dir("legacy-adopt-rollback"); + fs::create_dir_all(game.join("LPC")).unwrap(); + fs::write(game.join("t7patch.dll"), b"legacy-patch").unwrap(); + fs::write(game.join("t7patchloader.dll"), b"legacy-loader").unwrap(); + fs::write(game.join("t7patch.conf"), b"playername=custom\n").unwrap(); + fs::write(game.join("LPC/en_core_ffotd_tu32_593.ff"), b"legacy-lpc").unwrap(); + let suffix_backup = game.join("LPC/en_core_ffotd_tu32_593.ff.patchops.bak"); + fs::write(&suffix_backup, b"original-lpc").unwrap(); + let extra_backup = game.join("LPC/custom_language.ff.patchops.bak"); + fs::write(&extra_backup, b"custom-original-lpc").unwrap(); + let expected = [ + PathBuf::from("t7patch.dll"), + PathBuf::from("t7patchloader.dll"), + PathBuf::from("LPC/en_core_ffotd_tu32_593.ff"), + ] + .into_iter() + .map(|relative| { + let hash = fs_ops::sha256_file(&game.join(&relative)).unwrap(); + (folded_path(&relative), hash) + }) + .collect::>(); + + assert!(adopt_legacy_install(&game, &expected).unwrap()); + let manifest = load_manifest(&game).unwrap().unwrap(); + assert!( + manifest + .entries + .iter() + .all(|entry| entry.path != Path::new("t7patch.conf")) + ); + let lpc = manifest + .entries + .iter() + .find(|entry| entry.path == Path::new("LPC/en_core_ffotd_tu32_593.ff")) + .unwrap(); + let OriginalFile::Overwritten { backup_path, .. } = &lpc.original else { + panic!("legacy LPC backup was not adopted"); + }; + assert_ne!(game.join(backup_path), suffix_backup); + assert_eq!(fs::read(game.join(backup_path)).unwrap(), b"original-lpc"); + let extra = manifest + .entries + .iter() + .find(|entry| entry.path == Path::new("LPC/custom_language.ff")) + .expect("backup-only legacy LPC original should be adopted"); + assert!(matches!( + extra.original, + OriginalFile::Overwritten { + active_was_missing: true, + .. + } + )); + assert!(!game.join("LPC/custom_language.ff").exists()); + + apply_managed_uninstall(&game, &rollback).unwrap(); + assert!(!game.join("t7patch.dll").exists()); + assert!(!game.join("t7patchloader.dll").exists()); + assert_eq!( + fs::read(game.join("t7patch.conf")).unwrap(), + b"playername=custom\n" + ); + assert_eq!( + fs::read(game.join("LPC/en_core_ffotd_tu32_593.ff")).unwrap(), + b"original-lpc" + ); + assert_eq!(fs::read(&suffix_backup).unwrap(), b"original-lpc"); + assert_eq!( + fs::read(game.join("LPC/custom_language.ff")).unwrap(), + b"custom-original-lpc" + ); + assert_eq!(fs::read(&extra_backup).unwrap(), b"custom-original-lpc"); + fs::remove_dir_all(game).unwrap(); + fs::remove_dir_all(rollback).unwrap(); + } + + #[test] + fn legacy_adoption_rejects_unknown_or_incomplete_markers_without_state() { + let game = temp_dir("legacy-reject"); + fs::write(game.join("t7patch.dll"), b"known-patch").unwrap(); + let expected = [( + folded_path(Path::new("t7patch.dll")), + fs_ops::sha256_file(&game.join("t7patch.dll")).unwrap(), + )] + .into_iter() + .collect::>(); + assert!(adopt_legacy_install(&game, &expected).is_err()); + assert!(!game.join(OWNERSHIP_DIRECTORY).exists()); + assert_eq!(fs::read(game.join("t7patch.dll")).unwrap(), b"known-patch"); + + fs::write(game.join("t7patchloader.dll"), b"unexpected-loader").unwrap(); + assert!(adopt_legacy_install(&game, &expected).is_err()); + assert!(!game.join(OWNERSHIP_DIRECTORY).exists()); + assert_eq!( + fs::read(game.join("t7patchloader.dll")).unwrap(), + b"unexpected-loader" + ); + fs::remove_dir_all(game).unwrap(); + } + + #[test] + fn clean_legacy_probe_is_an_idempotent_noop() { + let game = temp_dir("legacy-clean"); + assert!(known_legacy_hashes(&game).unwrap().is_none()); + assert!(!adopt_legacy_install(&game, &HashMap::new()).unwrap()); + assert!(!game.join(OWNERSHIP_DIRECTORY).exists()); + fs::remove_dir_all(game).unwrap(); + } + + #[test] + fn incomplete_ownership_state_is_not_treated_as_a_clean_install() { + let game = temp_dir("incomplete-state"); + let recovery = game + .join(OWNERSHIP_DIRECTORY) + .join(OWNERSHIP_BACKUP_DIRECTORY) + .join("retained"); + fs::create_dir_all(&recovery).unwrap(); + fs::write(recovery.join("entry-0000.original"), b"recovery").unwrap(); + let error = load_manifest(&game).unwrap_err(); + assert!(error.contains("ownership state is incomplete")); + assert_eq!( + fs::read(recovery.join("entry-0000.original")).unwrap(), + b"recovery" + ); + fs::remove_dir_all(game).unwrap(); + } + + #[test] + fn current_profile_refuses_to_restore_compatible_only_originals_mid_install() { + let game = temp_dir("profile-transition"); + let sources = temp_dir("profile-transition-sources"); + let rollback = temp_dir("profile-transition-rollback"); + fs::write(game.join("discord_game_sdk.dll"), b"pre-existing").unwrap(); + let operations = [InstallOperation { + source: operation(&sources, "discord_game_sdk.dll", b"compatible-managed").source, + relative: PathBuf::from("discord_game_sdk.dll"), + }]; + apply_managed_install(&game, &operations, &[], &rollback).unwrap(); + let before = fs::read(game.join("discord_game_sdk.dll")).unwrap(); + let error = reject_unsafe_profile_transition(&game, PatchProfile::Current).unwrap_err(); + assert!(error.contains("Uninstall T7 Patch first")); + assert_eq!(fs::read(game.join("discord_game_sdk.dll")).unwrap(), before); + fs::remove_dir_all(game).unwrap(); + fs::remove_dir_all(sources).unwrap(); + fs::remove_dir_all(rollback).unwrap(); + } + + #[test] + fn uninstall_and_update_refuse_modified_managed_bytes_without_losing_recovery_state() { + let game = temp_dir("conflict"); + let sources = temp_dir("conflict-sources"); + let rollback = temp_dir("conflict-rollback"); + fs::write(game.join("t7patch.dll"), b"original").unwrap(); + let first = [InstallOperation { + source: operation(&sources, "v1.dll", b"managed-v1").source, + relative: PathBuf::from("t7patch.dll"), + }]; + apply_managed_install(&game, &first, &[], &rollback.join("install")).unwrap(); + let manifest_path = ownership_manifest_path(&game).unwrap(); + let manifest_before = fs::read(&manifest_path).unwrap(); + let manifest = load_manifest(&game).unwrap().unwrap(); + let backup = match &manifest.entries[0].original { + OriginalFile::Overwritten { backup_path, .. } => game.join(backup_path), + OriginalFile::Created => panic!("expected an original backup"), + }; + let backup_before = fs::read(&backup).unwrap(); + fs::write(game.join("t7patch.dll"), b"user-modified").unwrap(); + + let update = [InstallOperation { + source: operation(&sources, "v2.dll", b"managed-v2").source, + relative: PathBuf::from("t7patch.dll"), + }]; + let update_error = + apply_managed_install(&game, &update, &[], &rollback.join("refused-update")) + .unwrap_err(); + assert!(update_error.contains("changed outside PatchOpsIII")); + let uninstall_error = + apply_managed_uninstall(&game, &rollback.join("refused-uninstall")).unwrap_err(); + assert!(uninstall_error.contains("changed outside PatchOpsIII")); + assert_eq!( + fs::read(game.join("t7patch.dll")).unwrap(), + b"user-modified" + ); + assert_eq!(fs::read(&manifest_path).unwrap(), manifest_before); + assert_eq!(fs::read(&backup).unwrap(), backup_before); + + fs::write(game.join("t7patch.dll"), b"managed-v1").unwrap(); + fs::write(&backup, b"tampered-backup").unwrap(); + let backup_error = + apply_managed_uninstall(&game, &rollback.join("refused-backup")).unwrap_err(); + assert!(backup_error.contains("original backup changed")); + assert_eq!(fs::read(game.join("t7patch.dll")).unwrap(), b"managed-v1"); + assert!(manifest_path.is_file()); + assert_eq!(fs::read(&backup).unwrap(), b"tampered-backup"); + + fs::remove_dir_all(game).unwrap(); + fs::remove_dir_all(sources).unwrap(); + fs::remove_dir_all(rollback).unwrap(); + } + + #[test] + fn configure_refreshes_the_owned_config_hash() { + let game = temp_dir("config-owned"); + let sources = temp_dir("config-owned-sources"); + let rollback = temp_dir("config-owned-rollback"); + let install = [InstallOperation { + source: operation(&sources, "t7patch.conf", b"playername=Old\nunknown=keep\n").source, + relative: PathBuf::from("t7patch.conf"), + }]; + apply_managed_install(&game, &install, &[], &rollback.join("install")).unwrap(); + let before = load_manifest(&game).unwrap().unwrap().entries[0] + .managed_sha256 + .clone(); + let updated = b"playername=^2New\nunknown=keep\n"; + write_config_with_ownership(&game, updated, &rollback.join("configure")).unwrap(); + let after = load_manifest(&game).unwrap().unwrap().entries[0] + .managed_sha256 + .clone(); + assert_ne!(before, after); + assert_eq!( + after, + fs_ops::sha256_file(&game.join("t7patch.conf")).unwrap() + ); + + apply_managed_uninstall(&game, &rollback.join("uninstall")).unwrap(); + assert!(!game.join("t7patch.conf").exists()); + fs::remove_dir_all(game).unwrap(); + fs::remove_dir_all(sources).unwrap(); + fs::remove_dir_all(rollback).unwrap(); + } + + #[test] + fn legacy_install_without_manifest_is_left_byte_for_byte_untouched() { + let game = temp_dir("legacy-untracked"); + let rollback = temp_dir("legacy-untracked-rollback"); + fs::create_dir_all(game.join("LPC")).unwrap(); + let active_patch = game.join("t7patch.dll"); + let active_lpc = game.join("LPC/english.ff"); + let patchops_backup = game.join("LPC/english.ff.patchops.bak"); + let legacy_backup = game.join("LPC/english.ff.bak"); + fs::write(&active_patch, b"legacy-managed-or-user-patch").unwrap(); + fs::write(&active_lpc, b"legacy-managed-or-user-lpc").unwrap(); + fs::write(&patchops_backup, b"ambiguous-patchops-backup").unwrap(); + fs::write(&legacy_backup, b"ambiguous-legacy-backup").unwrap(); + + let error = apply_managed_uninstall(&game, &rollback).unwrap_err(); + assert!(error.contains("legacy or untracked install")); + assert_eq!( + fs::read(&active_patch).unwrap(), + b"legacy-managed-or-user-patch" + ); + assert_eq!( + fs::read(&active_lpc).unwrap(), + b"legacy-managed-or-user-lpc" + ); + assert_eq!( + fs::read(&patchops_backup).unwrap(), + b"ambiguous-patchops-backup" + ); + assert_eq!( + fs::read(&legacy_backup).unwrap(), + b"ambiguous-legacy-backup" + ); + assert!(load_manifest(&game).unwrap().is_none()); + + fs::remove_dir_all(game).unwrap(); + fs::remove_dir_all(rollback).unwrap(); + } + + #[test] + fn manifest_cannot_claim_legacy_or_suffix_based_backups() { + let game = temp_dir("reject-suffix-backup"); + let rollback = temp_dir("reject-suffix-backup-rollback"); + let active = game.join("t7patch.dll"); + let unrelated = game.join("t7patch.dll.bak"); + fs::write(&active, b"managed").unwrap(); + fs::write(&unrelated, b"unrelated-original").unwrap(); + let manifest = OwnershipManifest { + version: OWNERSHIP_VERSION, + entries: vec![ManagedFile { + path: PathBuf::from("t7patch.dll"), + managed_sha256: fs_ops::sha256_file(&active).unwrap(), + original: OriginalFile::Overwritten { + backup_path: PathBuf::from("t7patch.dll.bak"), + sha256: fs_ops::sha256_file(&unrelated).unwrap(), + active_was_missing: false, + }, + }], + }; + let manifest_path = game.join(ownership_manifest_relative()); + fs::create_dir_all(manifest_path.parent().unwrap()).unwrap(); + fs::write(&manifest_path, serde_json::to_vec(&manifest).unwrap()).unwrap(); + + let error = apply_managed_uninstall(&game, &rollback).unwrap_err(); + assert!(error.contains("invalid backup path")); + assert_eq!(fs::read(&active).unwrap(), b"managed"); + assert_eq!(fs::read(&unrelated).unwrap(), b"unrelated-original"); + assert!(manifest_path.is_file()); + + fs::remove_dir_all(game).unwrap(); + fs::remove_dir_all(rollback).unwrap(); + } + + #[test] + fn snapshot_restores_partial_file_changes() { + let root = temp_dir("rollback"); + let existing = root.join("existing.dll"); + let created = root.join("created.dll"); + fs::write(&existing, b"original").unwrap(); + let snapshot = Snapshot::capture( + &[existing.clone(), created.clone()], + &root.join("snapshots"), + ) + .unwrap(); + fs::write(&existing, b"partial").unwrap(); + fs::write(&created, b"partial").unwrap(); + snapshot.restore().unwrap(); + assert_eq!(fs::read(existing).unwrap(), b"original"); + assert!(!created.exists()); + fs::remove_dir_all(root).unwrap(); + } +} diff --git a/native/core/tests/parity.rs b/native/core/tests/parity.rs new file mode 100644 index 0000000..af62dbb --- /dev/null +++ b/native/core/tests/parity.rs @@ -0,0 +1,526 @@ +//! Python main is the oracle. See parity/README.md for isolation and normalization. +#![cfg(target_os = "linux")] +use patchops_core::{AppState, Engine}; +use regex::Regex; +use serde_json::{Value, json}; +use sha2::{Digest, Sha256}; +#[cfg(unix)] +use std::os::unix::fs::PermissionsExt; +use std::{ + env, fs, + path::{Path, PathBuf}, + process::Command, + time::{SystemTime, UNIX_EPOCH}, +}; + +fn decode64(text: &str) -> Vec { + let mut accumulator = 0u32; + let mut bits = 0; + let mut bytes = Vec::new(); + for byte in text.bytes().filter(|byte| *byte != b'=') { + let value = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" + .iter() + .position(|candidate| *candidate == byte) + .expect("valid fixture base64"); + accumulator = (accumulator << 6) | value as u32; + bits += 6; + if bits >= 8 { + bits -= 8; + bytes.push((accumulator >> bits) as u8); + } + } + bytes +} + +fn encode64(bytes: &[u8]) -> String { + let alphabet = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + let mut output = String::new(); + for chunk in bytes.chunks(3) { + let value = ((chunk[0] as u32) << 16) + | ((chunk.get(1).copied().unwrap_or(0) as u32) << 8) + | chunk.get(2).copied().unwrap_or(0) as u32; + for offset in (0..4).rev() { + let index = 3 - offset; + output.push(if index > chunk.len() { + '=' + } else { + alphabet[((value >> (6 * offset)) & 63) as usize] as char + }); + } + } + output +} + +fn normalize_text(text: &str, root: &Path) -> String { + let mut text = text.replace(root.to_str().unwrap(), "$ROOT"); + if let Ok(kernel) = fs::read_to_string("/proc/sys/kernel/osrelease") { + text = text.replace(kernel.trim(), "$KERNEL"); + } + Regex::new(r"\b\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\b") + .unwrap() + .replace_all(&text, regex::NoExpand("$TIMESTAMP")) + .into_owned() +} + +fn normalize(value: Value, root: &Path) -> Value { + match value { + Value::String(text) => Value::String(normalize_text(&text, root)), + Value::Array(items) => Value::Array( + items + .into_iter() + .map(|item| normalize(item, root)) + .collect(), + ), + Value::Object(items) => Value::Object( + items + .into_iter() + .map(|(key, value)| (normalize_text(&key, root), normalize(value, root))) + .collect(), + ), + value => value, + } +} + +fn recreate(root: &Path, fixture: &Value) { + fs::create_dir_all(root).unwrap(); + for directory in fixture["directories"].as_array().unwrap() { + fs::create_dir_all(root.join(directory.as_str().unwrap())).unwrap(); + } + for file in fixture["files"].as_array().unwrap() { + let path = root.join(file["path"].as_str().unwrap()); + let bytes = if let Some(text) = file["text"].as_str() { + text.replace("$ROOT", root.to_str().unwrap()).into_bytes() + } else { + decode64(file["base64"].as_str().unwrap()) + }; + fs::write(&path, bytes).unwrap(); + #[cfg(unix)] + fs::set_permissions( + path, + fs::Permissions::from_mode(file["mode"].as_u64().unwrap() as u32), + ) + .unwrap(); + } +} + +fn snapshot(root: &Path) -> Value { + fn visit(root: &Path, path: &Path, directories: &mut Vec, files: &mut Vec) { + let mut children: Vec<_> = fs::read_dir(path) + .unwrap() + .map(|entry| entry.unwrap().path()) + .collect(); + children.sort(); + for path in children { + let relative = path + .strip_prefix(root) + .unwrap() + .to_str() + .unwrap() + .replace('\\', "/"); + if path.is_dir() { + directories.push(relative); + visit(root, &path, directories, files); + } else { + let raw = fs::read(&path).unwrap(); + #[cfg(unix)] + let mode = path.metadata().unwrap().permissions().mode() & 0o777; + #[cfg(not(unix))] + let mode = 0o644; + let mut entry = json!({"path": relative, "mode": mode}); + let normalized = match String::from_utf8(raw.clone()) { + Ok(text) => { + let text = normalize_text(&text, root); + entry["text"] = json!(text); + text.into_bytes() + } + Err(_) => { + entry["base64"] = json!(encode64(&raw)); + raw + } + }; + entry["size"] = json!(normalized.len()); + entry["sha256"] = json!(format!("{:x}", Sha256::digest(&normalized))); + files.push(entry); + } + } + } + let mut directories = Vec::new(); + let mut files = Vec::new(); + visit(root, root, &mut directories, &mut files); + directories.sort(); + files.sort_by(|a, b| a["path"].as_str().cmp(&b["path"].as_str())); + json!({"directories": directories, "files": files}) +} + +fn differences(case: &str, location: &str, expected: &Value, actual: &Value, count: &mut usize) { + if expected == actual { + return; + } + match (expected, actual) { + (Value::Object(expected), Value::Object(actual)) => { + let keys: std::collections::BTreeSet<_> = + expected.keys().chain(actual.keys()).collect(); + for key in keys { + if expected.contains_key(key) && actual.contains_key(key) { + differences( + case, + &format!("{location}/{key}"), + &expected[key], + &actual[key], + count, + ); + } else { + *count += 1; + println!( + "PARITY_DIFF {}", + json!({"case": case, "path": format!("{location}/{key}"), "expected": expected.get(key), "actual": actual.get(key), "expectedPresent": expected.contains_key(key), "actualPresent": actual.contains_key(key)}) + ); + } + } + } + // Compare filesystem entries by path so one extra log/settings file doesn't + // shift every subsequent entry and hide the real changes. + (Value::Array(expected), Value::Array(actual)) if location.ends_with("/files") => { + let as_map = |files: &[Value]| -> Value { + Value::Object( + files + .iter() + .map(|file| (file["path"].as_str().unwrap().to_owned(), file.clone())) + .collect(), + ) + }; + differences(case, location, &as_map(expected), &as_map(actual), count); + } + (Value::Array(expected), Value::Array(actual)) if expected.len() == actual.len() => { + for (index, (expected, actual)) in expected.iter().zip(actual).enumerate() { + differences( + case, + &format!("{location}/{index}"), + expected, + actual, + count, + ); + } + } + _ => { + *count += 1; + println!( + "PARITY_DIFF {}", + json!({"case": case, "path": location, "expected": expected, "actual": actual}) + ); + } + } +} + +// Reviewed DXVK adoption differences only. Construct exact expected ownership +// bytes from the Python fixture and expected config, never from Rust output. +fn expected_snapshot(name: &str, golden: &Value, snapshot: &Value) -> Value { + let mut expected = snapshot.clone(); + if name != "dxvk_config_write" { + return expected; + } + let fixture_file = |name: &str| { + golden["fixture"]["files"] + .as_array() + .unwrap() + .iter() + .find(|file| file["path"] == format!("game/{name}")) + .unwrap() + }; + let config = expected["files"] + .as_array() + .unwrap() + .iter() + .find(|file| file["path"] == "game/dxvk.conf") + .unwrap(); + let manifest = json!({ + "version": 1, + "game_dir": "$ROOT/game", + "files": [ + {"name": "dxgi.dll", "installed_sha256": fixture_file("dxgi.dll")["sha256"], "original_sha256": null}, + {"name": "d3d11.dll", "installed_sha256": fixture_file("d3d11.dll")["sha256"], "original_sha256": null}, + {"name": "dxvk.conf", "installed_sha256": config["sha256"], "original_sha256": fixture_file("dxvk.conf")["sha256"]} + ] + }); + let text = serde_json::to_string_pretty(&manifest).unwrap(); + let mut backup = fixture_file("dxvk.conf").clone(); + backup["path"] = json!("data/DXVK Managed/originals/dxvk.conf"); + let files = expected["files"].as_array_mut().unwrap(); + files.push(backup); + files.push(json!({"path": "data/DXVK Managed/manifest.json", "mode": 0o664, + "sha256": format!("{:x}", Sha256::digest(text.as_bytes())), "size": text.len(), "text": text})); + files.sort_by(|a, b| a["path"].as_str().cmp(&b["path"].as_str())); + let directories = expected["directories"].as_array_mut().unwrap(); + directories.extend([ + json!("data/DXVK Managed"), + json!("data/DXVK Managed/originals"), + ]); + directories.sort_by(|a, b| a.as_str().cmp(&b.as_str())); + expected +} + +fn run_case(name: &str) { + let repo = Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../..") + .canonicalize() + .unwrap(); + let mut golden: Value = serde_json::from_slice( + &fs::read(repo.join(format!("native/core/tests/parity/{name}.json"))).unwrap(), + ) + .unwrap(); + if let Ok(root) = env::var("PATCHOPS_PARITY_CHILD_ROOT") { + let root = PathBuf::from(root); + let state = AppState::new(root.join("data"), Some(root.join("resources")), None).unwrap(); + if name == "dxvk_config_write" { + // Synthetic fixture payloads stand in for one verified GPLAsync + // release pair. Production has no override and hashes real DLLs. + let game = root.join("game"); + state.set_dxvk_legacy_hash_provider(std::sync::Arc::new(move |path| { + if path == game.join("dxgi.dll") { + assert_eq!(fs::read(path).unwrap(), b"\x00\xfffixture-dxgi"); + Ok("177cea0f3d64ac7a2834e24637aecb4ab133e036c50c2568489079cefb8fd7ec".into()) + } else if path == game.join("d3d11.dll") { + assert_eq!(fs::read(path).unwrap(), b"fixture-d3d11"); + Ok("c9e9d1a7844077df38cd0e540be1b435db7c9fed878a60fc32d22c778345fc09".into()) + } else { + Err("unexpected legacy DLL probe".into()) + } + })); + } + // Mirror the Python oracle's disabled Steam lifecycle hooks. + state.set_steam_lifecycle_callback(std::sync::Arc::new(|_| Ok(()))); + let aliases: std::collections::HashMap = golden["fixture"]["files"] + .as_array() + .unwrap() + .iter() + .filter_map(|file| { + Some(( + root.join(file["path"].as_str()?), + file["logicalSha256"].as_str()?.to_owned(), + )) + }) + .collect(); + fn strip_alias_metadata(value: &mut Value) { + match value { + Value::Object(object) => { + object.remove("logicalSha256"); + for value in object.values_mut() { + strip_alias_metadata(value); + } + } + Value::Array(array) => { + for value in array { + strip_alias_metadata(value); + } + } + _ => {} + } + } + strip_alias_metadata(&mut golden); + let engine = Engine::new(state).with_exe_hash_provider(std::sync::Arc::new(move |path| { + aliases.get(path).cloned().map(Ok).unwrap_or_else(|| { + fs::read(path) + .map(|bytes| format!("{:x}", Sha256::digest(bytes))) + .map_err(|e| e.to_string()) + }) + })); + let mut count = 0; + let internal = golden["internalOperation"].as_str() == Some("t7_release_asset_discovery"); + if internal { + let actual = golden["expected"]["responses"][0] + .as_array() + .unwrap() + .iter() + .map(|item| { + let mut item = item.clone(); + let asset = engine + .resolve_t7_release_asset( + item["assetKey"].as_str().unwrap(), + &item["release"], + ) + .unwrap(); + item["resolved"] = json!({"url": asset.url, "sha256": [asset.sha256]}); + item + }) + .collect::>(); + differences( + name, + "responses/0", + &golden["expected"]["responses"][0], + &json!(actual), + &mut count, + ); + } + for (index, operation) in golden["operations"] + .as_array() + .unwrap() + .iter() + .enumerate() + .filter(|_| !internal) + { + let body = operation.get("body").map(|body| { + serde_json::from_str::( + &body.to_string().replace("$ROOT", root.to_str().unwrap()), + ) + .unwrap() + }); + let actual = engine + .dispatch(operation["path"].as_str().unwrap(), body) + .unwrap_or_else(|error| json!({"dispatchError": error})); + differences( + name, + &format!("responses/{index}"), + &golden["expected"]["responses"][index], + &normalize(actual, &root), + &mut count, + ); + differences( + name, + &format!("checkpoints/{index}"), + &expected_snapshot(name, &golden, &golden["expected"]["checkpoints"][index]), + &snapshot(&root), + &mut count, + ); + } + differences( + name, + "filesystem", + &expected_snapshot(name, &golden, &golden["expected"]["filesystem"]), + &snapshot(&root), + &mut count, + ); + assert_eq!( + count, 0, + "{name}: {count} parity differences; see PARITY_DIFF records" + ); + return; + } + let unique = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_nanos(); + let scratch = env::temp_dir().join(format!( + "patchops-rust-parity-{}-{unique}-{name}", + std::process::id() + )); + let root = scratch.join("root"); + recreate(&root, &golden["fixture"]); + let mut physical_fixture = golden["fixture"].clone(); + for file in physical_fixture["files"].as_array_mut().unwrap() { + file.as_object_mut().unwrap().remove("logicalSha256"); + } + assert_eq!( + snapshot(&root), + physical_fixture, + "fixture reconstruction must be lossless" + ); + let tools = scratch.join("tools"); + fs::create_dir_all(&tools).unwrap(); + for (name, exit) in [("steam", 0), ("pkill", 1)] { + let path = tools.join(name); + // Change comm to `true` before exiting: a zombie shell named `steam` + // would make the real /proc polling incorrectly report Steam running. + let script = if name == "steam" { + "#!/bin/sh\nexec /usr/bin/true\n".to_owned() + } else { + format!("#!/bin/sh\nexit {exit}\n") + }; + fs::write(&path, script).unwrap(); + #[cfg(unix)] + fs::set_permissions(path, fs::Permissions::from_mode(0o755)).unwrap(); + } + let home = env::var_os("HOME").expect("HOME is required for the existing Steam discovery API"); + let home = PathBuf::from(home); + let output = Command::new("bwrap") + .args([ + "--die-with-parent", + "--ro-bind", + "/", + "/", + "--unshare-pid", + "--proc", + "/proc", + "--dev", + "/dev", + ]) + .arg("--tmpfs") + .arg(&home) + .arg("--ro-bind") + .arg(&repo) + .arg(&repo) + .arg("--bind") + .arg(&scratch) + .arg(&scratch) + .arg("--dir") + .arg(home.join(".steam")) + .arg("--symlink") + .arg(root.join("steam")) + .arg(home.join(".steam/steam")) + .args(["/bin/sh", "-c", "umask 002; exec \"$@\"", "parity"]) + .arg(env::current_exe().unwrap()) + .args(["--exact", name, "--nocapture"]) + .env("PATCHOPS_PARITY_CHILD_ROOT", &root) + .env( + "PATH", + format!( + "{}:{}", + tools.display(), + env::var("PATH").unwrap_or_default() + ), + ) + .output() + .expect("bubblewrap must be installed; no unsafe host fallback is permitted"); + fs::remove_dir_all(scratch).unwrap(); + print!("{}", String::from_utf8_lossy(&output.stdout)); + eprint!("{}", String::from_utf8_lossy(&output.stderr)); + assert!( + output.status.success(), + "{name}: isolated parity test failed ({})", + output.status + ); +} + +macro_rules! case { + ($name:ident) => { + #[test] + #[cfg_attr( + not(target_os = "linux"), + ignore = "needs a platform filesystem/process isolation adapter" + )] + fn $name() { + run_case(stringify!($name)); + } + }; +} +case!(status_no_game); +case!(status_unverified_base); +case!(status_steam_secondary_library); +case!(game_directory_validation_invalid); +case!(game_directory_validation_valid); +case!(config_graphics_advanced_write); +case!(t7_config_write); +case!(dxvk_config_write); +case!(launch_options_apply); +case!(qol_toggles); +case!(status_t7_installed); +case!(status_dxvk_installed); +case!(status_enhanced_installed); +case!(status_empty_config); +case!(status_config_variants); +case!(config_append_and_crlf); +case!(config_missing_file); +case!(t7_config_missing); +case!(t7_config_clear_and_append); +case!(t7_config_invalid_name); +case!(launch_options_unsupported); +case!(qol_restore_legacy); +case!(qol_toggle_roundtrip); +case!(exe_untrusted_backup_detection); +case!(exe_preserved_enhanced_detection); +case!(status_alternate_executable); +case!(config_readonly_roundtrip); +case!(vram_target_roundtrip); + +case!(status_september_2026_current); +case!(status_compatible_build); +case!(t7_release_asset_discovery); diff --git a/native/core/tests/parity/README.md b/native/core/tests/parity/README.md new file mode 100644 index 0000000..df1f78a --- /dev/null +++ b/native/core/tests/parity/README.md @@ -0,0 +1,158 @@ +# Python → Rust behavioral parity + +The case JSON files are goldens generated from the Python backend on `main`, not +handwritten Rust expectations. `sourceHashes` records the five Python modules +used. At generation, those modules were identical to local `main`. + +Run from the repository root: + +```sh +python3 scripts/gen_parity_fixtures.py +python3 scripts/gen_parity_fixtures.py --check +cd native +~/.cargo/bin/cargo test -p patchops-core --test parity -- --nocapture +``` + +The Python runner prefers FastAPI TestClient when FastAPI, Pydantic, and HTTPX +are available. This environment lacks FastAPI; the existing temporary venv +also has neither pip nor ensurepip. These goldens therefore use direct async +endpoint calls with minimal import/model shims, plus installed `requests` and +`vdf`. They exercise endpoint logic and filesystem effects, **not FastAPI +request validation or lifecycle hooks**. Installing the real dependencies may +change `runner` metadata; regenerate before using `--check` in that environment. + +## Coverage + +31 cases cover: + +- Status without a game, base game, secondary Steam library discovered through + `libraryfolders.vdf`, both executable names, empty/malformed/decimal graphics + configuration, installed T7/DXVK/Enhanced, current September 2026 and + compatible executable profiles. +- Valid and invalid game-directory selection; graphics/advanced writes; config + append and CRLF input; missing config; read-only/write/unlock; VRAM limit and + unlimited round trips. +- T7 gamertag/color/password/friends-only edits, missing keys, trimmed values, + empty/overlong names, missing configuration, preservation of unknown keys. +- DXVK settings writes with existing DLL/config files. +- Supported Steam launch-option application and preservation of existing Wine, + command marker, custom flags, and `fs_game`; unsupported options. +- Intro, all-intro, and compiler toggles, repeated enable, restoration, and + legacy `.bak` restoration. +- EXE integrity/state detection, untrusted build backups, and a preserved + Enhanced hash with real fixture bytes. +- Mock release metadata for version-suffixed legacy T7 archives and renamed + universal current archives (the #46/#47 behavior). + +Each golden records initial directories/files, operations, endpoint responses, +filesystem snapshots **after every operation**, and final filesystem state. +Files include relative path, mode, normalized byte size, SHA-256, and text or +base64 content. Fixture recreation is checked before invoking Rust. + +## Isolation + +Generation uses temporary roots, fixture settings/presets/Steam paths, disabled +Steam process operations, synchronous log capture, and a fail-closed Requests +network guard. Release discovery alone replaces `requests.get` with a fake +response. No game payload is downloaded or redistributed. + +The Rust tests target the public `AppState::new` and `Engine::dispatch` APIs. +On Linux they re-exec each case through **bubblewrap** with a read-only host +filesystem, a temporary home mount, a fixture Steam symlink, writable fixture +root, isolated PID namespace, and fake `steam`/`pkill` commands. The home +environment variable is read, never changed; real Steam files/processes cannot +be affected. No network-performing Rust dispatch is exercised. Missing or +unsupported bubblewrap fails explicitly; there is no host fallback. Tests on +other platforms compile an empty parity test target and skip these Linux-only +cases cleanly, pending equivalent filesystem/process isolation. Linux CI needs +the apt package `bubblewrap`; the native Ubuntu workflow already includes it. + +## Normalization rules + +1. Replace each temporary root in responses, object keys, and UTF-8 file + contents with `$ROOT`. Paths in snapshots are relative and use `/`. +2. Replace generated `YYYY-MM-DD HH:MM:SS` log timestamps with `$TIMESTAMP`. + Seeded ISO Enhanced timestamps are fixed inputs and retained verbatim. +3. Replace the host kernel release in Python log headers with `$KERNEL`. + Version strings, platform naming, log header structure, messages, and + category/order are retained and compared. +4. Sort filesystem entries/directories and JSON object keys. Preserve response + array order, config comments, line endings, permission bits, VDF whitespace, + JSON file formatting, missing-vs-null fields, and every extra/deleted file. + JSON numbers are compared exactly, including integer versus float representation. +5. Text file size/hash describe normalized UTF-8 bytes, not the physical + temporary-path-expanded file. Binary files use unmodified bytes/base64. + File mtimes/ownership and temporary directory names are not recorded. Both + runners use fixed umask `002`; fixture files explicitly start at `0644`. + This catches atomic replacements that change existing permission bits. +6. Known current/compatible EXEs contain nonempty marker bytes and a + `logicalSha256` alias used by the Python and Rust status hash seams. Their physical + sizes/hashes are also recorded. Production code checks build identity by + SHA-256, not a fixed EXE byte size; fake bytes cannot have the real digest. + +## Test seams and reviewed migration differences + +All 31 cases run on Linux; there are no ignored adapter cases. Historical +`rustIgnore` fixture metadata records the original gaps and does not skip tests. +`Engine::with_exe_hash_provider` injects status hashes, including preserved EXE +probes. Mutation checks always hash physical bytes. `logicalSha256` is adapter +metadata excluded from snapshot comparison; physical sizes/hashes/content still +compare exactly. `select_t7_release_asset` is a public pure JSON selector using +the installer selection logic, and the Engine wrapper retains discovery logs. +Steam lifecycle callbacks mirror the Python runner's no-op process hooks; +production still closes/waits/reopens Steam. No process lifecycle is verified +by these goldens. + +DXVK configure first adopts an unbound legacy install only when both root DLLs +are regular, non-symlink files whose SHA-256 pair matches the same known official +GPLAsync release, and root `dxvk.conf` is regular and non-symlink. Unknown, +incomplete, mixed, or unsafe installs remain refused. The manifest records the +canonical game directory and physical installed hashes. The original config is +copied and verified before configuring; uninstall restores it. Existing managed +state must validate before reuse. Filenames/config shape alone never prove +ownership. The fake DLL fixture uses a narrowly scoped legacy recognition hash +provider for the v3.0-1 pair; manifest/backup hashes always use real fixture bytes. + +The **only filesystem expected-diff allowlist**, in `expected_snapshot` in +`../parity.rs`, applies to `dxvk_config_write` checkpoints/final state: + +- Add `data/DXVK Managed` and `data/DXVK Managed/originals` directories. +- Add `data/DXVK Managed/manifest.json`: version 1, `$ROOT/game` binding, exact + physical fixture DLL hashes, Python's expected updated config hash, and the + original fixture config hash. Expected JSON bytes/mode/size/hash are computed + independently from the fixture and Python golden, never from Rust output. +- Add `data/DXVK Managed/originals/dxvk.conf`: exact original fixture bytes and + mode. No DLL backup, other extra file/directory, response/log difference, or + existing-file rewrite is allowed by this policy. + +These are intentional ownership records required by the reviewed safety policy. +No Python golden is changed or regenerated to accept Rust behavior. + +Enhanced legacy status metadata is bound **in memory per AppState** on first +matching status read: legacy state must be installed with nonempty recorded +files, all four Enhanced markers and all recorded files must be regular files +inside the current game directory, paths must be safe and belong to the Enhanced +or root dump whitelist, and recorded hashes (if present) must match. Partial +modern ownership metadata is refused. The first canonical binding cannot follow +a second game directory in the same AppState. Counts, backup status, and timestamps +are preserved. The JSON file is left byte-for-byte intact, keeping Python status +reads free of filesystem writes. This display binding does not authorize +install/uninstall; the existing verified archive/ownership adoption still applies. +A durable binding across app restarts would require a separately reviewed +Enhanced filesystem allowlist, which this task did not authorize. + +## Results + +See [RESULTS.md](RESULTS.md) for concrete regressions and +[results.jsonl](results.jsonl) for **every** leaf mismatch (including intermediate +checkpoints and derivative size/hash differences). Dispatch errors are captured +as `{"dispatchError": ...}` diagnostic values: this wrapper is not a production +API response and is not accepted as equivalent to Python's `ok/error/state` +JSON. The latest isolated run passes all 31 cases with the exact DXVK ownership +allowlist above. + +Status-envelope differences do not prevent the harness from checking the +remaining response fields or filesystem changes. +Byte-only formatting differences are reported alongside functional differences +and should be assessed separately. Do not replace goldens with Rust output to +make the suite green. diff --git a/native/core/tests/parity/RESULTS.md b/native/core/tests/parity/RESULTS.md new file mode 100644 index 0000000..f8c921c --- /dev/null +++ b/native/core/tests/parity/RESULTS.md @@ -0,0 +1,38 @@ +# Latest parity results + +Linux isolated run: **31 passed, 0 failed, 0 ignored; zero leaf mismatches**. +`results.jsonl` is empty; `results.provenance.json` records the exact source +hashes, compiled test binary SHA-256, and reviewed DXVK ownership allowlist. +No Python golden or generator change was needed. + +Validation: 100 core unit tests and 3 GPUI tests passed in the final workspace run, as did workspace tests, +format check, workspace/all-target Clippy with `-D warnings`, and +`python3 scripts/gen_parity_fixtures.py --check` (all 31 deterministic goldens). +The test target is Linux-only; other platforms compile it as an empty target. +Ubuntu CI requires `bubblewrap`, already in the native workflow's apt packages. + +| Case / group | Final behavior | +| --- | --- | +| `config_append_and_crlf` | Text-mode parity: CRLF input is rewritten with LF. | +| `config_readonly_roundtrip` | chmod uses 0400/0600; rejected write returns/logs the Python Permission denied message and leaves the file untouched. | +| `launch_options_apply` | Space between VDF string key/value, rolling backup on each apply including no-op, source permissions retained, no localconfig sibling backup, Python operation log text/order. Lifecycle hooks are mocked on both runners. | +| `launch_options_unsupported`, invalid game directory, invalid/missing T7 config | Existing endpoint-specific envelopes/severity and no validation log side effects pass unchanged. | +| QoL toggles and VRAM | Existing repeated-enable “already” messages and `1.0` limited-VRAM formatting pass unchanged. | +| `dxvk_config_write` | Verified legacy DLL pair is adopted before configuring; exact manifest and original-config backup are asserted by the narrow allowlist. Foreign/mixed/incomplete pairs remain refused. Synthetic DLL recognition uses a fixture-only injected hash provider. | +| `status_enhanced_installed` | Matching legacy records bind to one canonical game directory in memory per AppState, preserving count/backup status/timestamps and the exact legacy JSON. Missing/unsafe/mismatched files cannot bind; binding never grants destructive ownership. | +| September 2026 and compatible EXEs | Both formerly ignored cases now execute through the status-only hash provider. Compatible T7 status uses Python's `Custom` label. EXE mutation hashing remains physical. | +| Versioned/renamed T7 release discovery | Formerly ignored case executes via a public pure selector taking release JSON, using the installer's selection algorithm and the Engine's discovery log wrapper. | + +The allowlist accepts only the two DXVK ownership directories, exact manifest, +and original config backup; all Python responses, existing files, modes, and +logs remain strict. Read `README.md` for exact adoption, normalization, and +binding rules. Historical `rustIgnore` metadata in immutable goldens is no +longer acted on. + +Limits: the Python oracle uses direct endpoint calls with import/model shims in +this environment; it does not verify FastAPI validation or lifecycle hooks. +Steam lifecycle is disabled in both fixture runners. No network/download or +real-game operation is performed. GitHub-hosted Ubuntu/Windows jobs were not +executed here. A durable Enhanced status binding across app restarts needs an +additional reviewed filesystem difference; this implementation deliberately +keeps the Python golden JSON intact and uses an AppState-local binding. diff --git a/native/core/tests/parity/config_append_and_crlf.json b/native/core/tests/parity/config_append_and_crlf.json new file mode 100644 index 0000000..8ca7b68 --- /dev/null +++ b/native/core/tests/parity/config_append_and_crlf.json @@ -0,0 +1,483 @@ +{ + "expected": { + "checkpoints": [ + { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 436, + "path": "data/PatchOpsIII.log", + "sha256": "5bbcfaa7b01899957078f15e497c18c301b1f36dba18a4b230d7171bec43a4ac", + "size": 41, + "text": "$TIMESTAMP - Success: Set MaxFPS to 200.\n" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "88cdfbed938b27580ce91830d4f72a25e720dd529767ec6804f8f014dea678ed", + "size": 46, + "text": "FOV = \"80\"\nMaxFPS = \"200\" // fixture appended\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + } + ], + "filesystem": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 436, + "path": "data/PatchOpsIII.log", + "sha256": "5bbcfaa7b01899957078f15e497c18c301b1f36dba18a4b230d7171bec43a4ac", + "size": 41, + "text": "$TIMESTAMP - Success: Set MaxFPS to 200.\n" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "88cdfbed938b27580ce91830d4f72a25e720dd529767ec6804f8f014dea678ed", + "size": 46, + "text": "FOV = \"80\"\nMaxFPS = \"200\" // fixture appended\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "responses": [ + { + "ok": true, + "state": { + "activeLaunchProfile": "default", + "advanced": { + "configReadonly": false, + "maxFrameLatency": 1, + "reduceCpu": false, + "smoothFramerate": false, + "unlockOptions": false, + "vramLimited": false, + "vramTarget": 75 + }, + "appVersion": "v1.3.4", + "configExists": true, + "currentLaunchOptions": "", + "dxvk": { + "confExists": false, + "installed": false, + "settings": { + "enableAsync": true, + "gplAsyncCache": true, + "hudEnabled": false, + "maxFrameLatency": 1, + "maxFrameRate": 0, + "numCompilerThreads": 0, + "tearFree": "True" + } + }, + "enhanced": { + "acknowledgedAt": null, + "backupStatus": "Not created", + "detectedAt": null, + "dumpSource": "", + "filesInstalled": 0, + "installed": false, + "launchOptionsActive": false + }, + "exeSwap": { + "activeBuildDate": "", + "activeBuildId": "Unverified", + "backupAvailable": false, + "compatibleActive": false, + "compatibleAvailable": false, + "compatibleBuildDate": "Mar 3, 2023", + "compatibleBuildId": "10650222", + "currentBuildDate": "Sep 10, 2026", + "currentBuildId": "24784313", + "displayLabel": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE. T7 installs will use T7 Patch Scroptss/T7Patch.", + "enhancedActive": false, + "enhancedAvailable": false, + "enhancedBuildDate": "", + "enhancedBuildId": "Enhanced", + "enhancedExeActive": false, + "executable": "$ROOT/game/BlackOpsIII.exe", + "executableHash": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "executableName": "BlackOpsIII.exe", + "integrityMessage": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE.", + "integrityStatus": "unverified", + "latestAvailable": false, + "modeLabel": "Unknown", + "patchLabel": "T7 Patch Scroptss/T7Patch", + "profile": "", + "state": "unverified", + "trustedExecutable": false + }, + "gameDetected": true, + "gameDir": "$ROOT/game", + "graphics": { + "displayMode": 1, + "drawFps": false, + "fov": 80, + "maxFps": 200, + "refreshRate": 60.0, + "renderResolution": 100, + "resolution": "1920x1080", + "vsync": true + }, + "launchProfiles": [ + { + "active": true, + "id": "default", + "installed": true, + "label": "Default (None)", + "option": "", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "offline", + "installed": true, + "label": "Play Offline", + "option": "+set fs_game offlinemp", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "all_around", + "installed": false, + "label": "All-around Enhancement Lite", + "option": "+set fs_game 2994481309", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "ultimate", + "installed": false, + "label": "Ultimate Experience Mod", + "option": "+set fs_game 2942053577", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "reforged", + "installed": false, + "label": "BO3 Reforged", + "option": "+set fs_game 3667377161", + "path": null, + "state": "Not Subscribed", + "subscribed": false + } + ], + "logPath": "$ROOT/data/PatchOpsIII.log", + "logs": [ + { + "category": "Success", + "line": "$TIMESTAMP - Success: Set MaxFPS to 200.", + "message": "Set MaxFPS to 200." + } + ], + "maintenance": { + "logPayload": "PatchOpsIII v1.3.4 - Linux $KERNEL (x86_64) logs:\n```\n$TIMESTAMP - Success: Set MaxFPS to 200.\n```", + "modFilesDir": "$ROOT/data/BO3 Mod Files" + }, + "mods": { + "dxvk": false, + "enhanced": false, + "t7Patch": false + }, + "platform": "Linux", + "presets": [ + "Smooth Preset", + "Low-Latency Preset" + ], + "qol": { + "allIntros": false, + "d3dcompiler": false, + "intro": false + }, + "releaseChannel": "stable", + "steamUserId": "76561198000000000", + "t7": { + "colorCode": "", + "confExists": false, + "friendsOnly": false, + "gamertag": "", + "installed": false, + "mode": "Custom", + "networkPassword": "", + "plainName": "" + } + } + } + ] + }, + "fixture": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "6a4e31f30c7a97d9bde8fe66f1e6b89e5b6370281053bf9c09f421f9e62fc9ed", + "size": 12, + "text": "FOV = \"80\"\r\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "name": "config_append_and_crlf", + "operations": [ + { + "body": { + "comment": "fixture appended", + "key": "MaxFPS", + "value": 200 + }, + "method": "POST", + "path": "/api/config" + } + ], + "runner": "direct endpoint functions", + "schemaVersion": 1, + "sourceHashes": { + "backend/api.py": "080be5c4a3b929134298179cd3f4d1558c288a50dcdb38978ada831925a57a46", + "bo3_enhanced.py": "32a670c8a63a64216ee5c46aa000d2e3840ca348ca0fd5031c81699252cf57b8", + "dxvk_manager.py": "8e150458bc7480ac89cd9e64c558ba438f61a09611bbf24b962cba02d7e4a5e5", + "t7_patch.py": "73e66fadd1c0775742b7bb6d9c6c73a3c9df8304d3117abd8ac768dfc68cc3ea", + "utils.py": "313069c3f70b92888239c9ef15ce2379713b029e019426e76b386f088b556564" + }, + "sourceOfTruth": "Python backend on main" +} diff --git a/native/core/tests/parity/config_graphics_advanced_write.json b/native/core/tests/parity/config_graphics_advanced_write.json new file mode 100644 index 0000000..1dfde05 --- /dev/null +++ b/native/core/tests/parity/config_graphics_advanced_write.json @@ -0,0 +1,1632 @@ +{ + "expected": { + "checkpoints": [ + { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 436, + "path": "data/PatchOpsIII.log", + "sha256": "ce683c0fab28aadf089f749e1bcaec284f21a5db9e56935c11b2c7d84c0def44", + "size": 41, + "text": "$TIMESTAMP - Success: Set MaxFPS to 240.\n" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "a24ed9ad7430b8e3f6e4130a40581aa109916c18e327a544c128552684ff1896", + "size": 481, + "text": "// fixture configuration\nMaxFPS = \"240\" // fixture max fps\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 436, + "path": "data/PatchOpsIII.log", + "sha256": "af760606f4a1e70110ef950799bceb67801867b590513184f566cfbab46caf37", + "size": 79, + "text": "$TIMESTAMP - Success: Set MaxFPS to 240.\n$TIMESTAMP - Success: Set FOV to 110.\n" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "226f162a28fed3951a7df4d3b574728a41907972ac7788f2afe67b45c53522f4", + "size": 485, + "text": "// fixture configuration\nMaxFPS = \"240\" // fixture max fps\nFOV = \"110\" // fixture fov\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 436, + "path": "data/PatchOpsIII.log", + "sha256": "deaec6e85c14d457442eef071d07d854c4095549802f35c4c15919a57c607a29", + "size": 119, + "text": "$TIMESTAMP - Success: Set MaxFPS to 240.\n$TIMESTAMP - Success: Set FOV to 110.\n$TIMESTAMP - Success: Set DrawFPS to 1.\n" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "5de3b12023957cef9c91e8d39838882f93665969afa2bfd5439c3f4a2b4daf02", + "size": 492, + "text": "// fixture configuration\nMaxFPS = \"240\" // fixture max fps\nFOV = \"110\" // fixture fov\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"1\" // fixture counter\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 436, + "path": "data/PatchOpsIII.log", + "sha256": "e29c30c92ed2f927e25dccd5910155d0081d06929c7cdc127bd647f8bbb68d2b", + "size": 167, + "text": "$TIMESTAMP - Success: Set MaxFPS to 240.\n$TIMESTAMP - Success: Set FOV to 110.\n$TIMESTAMP - Success: Set DrawFPS to 1.\n$TIMESTAMP - Success: Set SerializeRender to 2.\n" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "b0107b725502ac0217bc70e0fef6638dba7e1b8bb94a5eca0f1cc530fa07821d", + "size": 495, + "text": "// fixture configuration\nMaxFPS = \"240\" // fixture max fps\nFOV = \"110\" // fixture fov\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"1\" // fixture counter\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"2\" // fixture cpu\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 436, + "path": "data/PatchOpsIII.log", + "sha256": "7f99bb8be5709868b1ef21dc26f4f1876a4af01218a14d9fa113171832aba700", + "size": 220, + "text": "$TIMESTAMP - Success: Set MaxFPS to 240.\n$TIMESTAMP - Success: Set FOV to 110.\n$TIMESTAMP - Success: Set DrawFPS to 1.\n$TIMESTAMP - Success: Set SerializeRender to 2.\n$TIMESTAMP - Success: Limited VRAM usage set to 85%.\n" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "2a28ba0d29a3047b281da49c8b3d19bd9868a0fc25fdc1758caaf07f8d7c3b98", + "size": 497, + "text": "// fixture configuration\nMaxFPS = \"240\" // fixture max fps\nFOV = \"110\" // fixture fov\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"1\" // fixture counter\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"2\" // fixture cpu\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"0.85\" // 0.75 to 1\nStreamMinResident = \"1\" // 0 or 1\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + } + ], + "filesystem": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 436, + "path": "data/PatchOpsIII.log", + "sha256": "7f99bb8be5709868b1ef21dc26f4f1876a4af01218a14d9fa113171832aba700", + "size": 220, + "text": "$TIMESTAMP - Success: Set MaxFPS to 240.\n$TIMESTAMP - Success: Set FOV to 110.\n$TIMESTAMP - Success: Set DrawFPS to 1.\n$TIMESTAMP - Success: Set SerializeRender to 2.\n$TIMESTAMP - Success: Limited VRAM usage set to 85%.\n" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "2a28ba0d29a3047b281da49c8b3d19bd9868a0fc25fdc1758caaf07f8d7c3b98", + "size": 497, + "text": "// fixture configuration\nMaxFPS = \"240\" // fixture max fps\nFOV = \"110\" // fixture fov\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"1\" // fixture counter\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"2\" // fixture cpu\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"0.85\" // 0.75 to 1\nStreamMinResident = \"1\" // 0 or 1\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "responses": [ + { + "ok": true, + "state": { + "activeLaunchProfile": "default", + "advanced": { + "configReadonly": false, + "maxFrameLatency": 1, + "reduceCpu": false, + "smoothFramerate": false, + "unlockOptions": false, + "vramLimited": false, + "vramTarget": 100 + }, + "appVersion": "v1.3.4", + "configExists": true, + "currentLaunchOptions": "", + "dxvk": { + "confExists": false, + "installed": false, + "settings": { + "enableAsync": true, + "gplAsyncCache": true, + "hudEnabled": false, + "maxFrameLatency": 1, + "maxFrameRate": 0, + "numCompilerThreads": 0, + "tearFree": "True" + } + }, + "enhanced": { + "acknowledgedAt": null, + "backupStatus": "Not created", + "detectedAt": null, + "dumpSource": "", + "filesInstalled": 0, + "installed": false, + "launchOptionsActive": false + }, + "exeSwap": { + "activeBuildDate": "", + "activeBuildId": "Unverified", + "backupAvailable": false, + "compatibleActive": false, + "compatibleAvailable": false, + "compatibleBuildDate": "Mar 3, 2023", + "compatibleBuildId": "10650222", + "currentBuildDate": "Sep 10, 2026", + "currentBuildId": "24784313", + "displayLabel": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE. T7 installs will use T7 Patch Scroptss/T7Patch.", + "enhancedActive": false, + "enhancedAvailable": false, + "enhancedBuildDate": "", + "enhancedBuildId": "Enhanced", + "enhancedExeActive": false, + "executable": "$ROOT/game/BlackOpsIII.exe", + "executableHash": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "executableName": "BlackOpsIII.exe", + "integrityMessage": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE.", + "integrityStatus": "unverified", + "latestAvailable": false, + "modeLabel": "Unknown", + "patchLabel": "T7 Patch Scroptss/T7Patch", + "profile": "", + "state": "unverified", + "trustedExecutable": false + }, + "gameDetected": true, + "gameDir": "$ROOT/game", + "graphics": { + "displayMode": 1, + "drawFps": false, + "fov": 80, + "maxFps": 240, + "refreshRate": 60.0, + "renderResolution": 100, + "resolution": "1920x1080", + "vsync": true + }, + "launchProfiles": [ + { + "active": true, + "id": "default", + "installed": true, + "label": "Default (None)", + "option": "", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "offline", + "installed": true, + "label": "Play Offline", + "option": "+set fs_game offlinemp", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "all_around", + "installed": false, + "label": "All-around Enhancement Lite", + "option": "+set fs_game 2994481309", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "ultimate", + "installed": false, + "label": "Ultimate Experience Mod", + "option": "+set fs_game 2942053577", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "reforged", + "installed": false, + "label": "BO3 Reforged", + "option": "+set fs_game 3667377161", + "path": null, + "state": "Not Subscribed", + "subscribed": false + } + ], + "logPath": "$ROOT/data/PatchOpsIII.log", + "logs": [ + { + "category": "Success", + "line": "$TIMESTAMP - Success: Set MaxFPS to 240.", + "message": "Set MaxFPS to 240." + } + ], + "maintenance": { + "logPayload": "PatchOpsIII v1.3.4 - Linux $KERNEL (x86_64) logs:\n```\n$TIMESTAMP - Success: Set MaxFPS to 240.\n```", + "modFilesDir": "$ROOT/data/BO3 Mod Files" + }, + "mods": { + "dxvk": false, + "enhanced": false, + "t7Patch": false + }, + "platform": "Linux", + "presets": [ + "Smooth Preset", + "Low-Latency Preset" + ], + "qol": { + "allIntros": false, + "d3dcompiler": false, + "intro": false + }, + "releaseChannel": "stable", + "steamUserId": "76561198000000000", + "t7": { + "colorCode": "", + "confExists": false, + "friendsOnly": false, + "gamertag": "", + "installed": false, + "mode": "Custom", + "networkPassword": "", + "plainName": "" + } + } + }, + { + "ok": true, + "state": { + "activeLaunchProfile": "default", + "advanced": { + "configReadonly": false, + "maxFrameLatency": 1, + "reduceCpu": false, + "smoothFramerate": false, + "unlockOptions": false, + "vramLimited": false, + "vramTarget": 100 + }, + "appVersion": "v1.3.4", + "configExists": true, + "currentLaunchOptions": "", + "dxvk": { + "confExists": false, + "installed": false, + "settings": { + "enableAsync": true, + "gplAsyncCache": true, + "hudEnabled": false, + "maxFrameLatency": 1, + "maxFrameRate": 0, + "numCompilerThreads": 0, + "tearFree": "True" + } + }, + "enhanced": { + "acknowledgedAt": null, + "backupStatus": "Not created", + "detectedAt": null, + "dumpSource": "", + "filesInstalled": 0, + "installed": false, + "launchOptionsActive": false + }, + "exeSwap": { + "activeBuildDate": "", + "activeBuildId": "Unverified", + "backupAvailable": false, + "compatibleActive": false, + "compatibleAvailable": false, + "compatibleBuildDate": "Mar 3, 2023", + "compatibleBuildId": "10650222", + "currentBuildDate": "Sep 10, 2026", + "currentBuildId": "24784313", + "displayLabel": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE. T7 installs will use T7 Patch Scroptss/T7Patch.", + "enhancedActive": false, + "enhancedAvailable": false, + "enhancedBuildDate": "", + "enhancedBuildId": "Enhanced", + "enhancedExeActive": false, + "executable": "$ROOT/game/BlackOpsIII.exe", + "executableHash": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "executableName": "BlackOpsIII.exe", + "integrityMessage": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE.", + "integrityStatus": "unverified", + "latestAvailable": false, + "modeLabel": "Unknown", + "patchLabel": "T7 Patch Scroptss/T7Patch", + "profile": "", + "state": "unverified", + "trustedExecutable": false + }, + "gameDetected": true, + "gameDir": "$ROOT/game", + "graphics": { + "displayMode": 1, + "drawFps": false, + "fov": 110, + "maxFps": 240, + "refreshRate": 60.0, + "renderResolution": 100, + "resolution": "1920x1080", + "vsync": true + }, + "launchProfiles": [ + { + "active": true, + "id": "default", + "installed": true, + "label": "Default (None)", + "option": "", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "offline", + "installed": true, + "label": "Play Offline", + "option": "+set fs_game offlinemp", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "all_around", + "installed": false, + "label": "All-around Enhancement Lite", + "option": "+set fs_game 2994481309", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "ultimate", + "installed": false, + "label": "Ultimate Experience Mod", + "option": "+set fs_game 2942053577", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "reforged", + "installed": false, + "label": "BO3 Reforged", + "option": "+set fs_game 3667377161", + "path": null, + "state": "Not Subscribed", + "subscribed": false + } + ], + "logPath": "$ROOT/data/PatchOpsIII.log", + "logs": [ + { + "category": "Success", + "line": "$TIMESTAMP - Success: Set MaxFPS to 240.", + "message": "Set MaxFPS to 240." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: Set FOV to 110.", + "message": "Set FOV to 110." + } + ], + "maintenance": { + "logPayload": "PatchOpsIII v1.3.4 - Linux $KERNEL (x86_64) logs:\n```\n$TIMESTAMP - Success: Set MaxFPS to 240.\n$TIMESTAMP - Success: Set FOV to 110.\n```", + "modFilesDir": "$ROOT/data/BO3 Mod Files" + }, + "mods": { + "dxvk": false, + "enhanced": false, + "t7Patch": false + }, + "platform": "Linux", + "presets": [ + "Smooth Preset", + "Low-Latency Preset" + ], + "qol": { + "allIntros": false, + "d3dcompiler": false, + "intro": false + }, + "releaseChannel": "stable", + "steamUserId": "76561198000000000", + "t7": { + "colorCode": "", + "confExists": false, + "friendsOnly": false, + "gamertag": "", + "installed": false, + "mode": "Custom", + "networkPassword": "", + "plainName": "" + } + } + }, + { + "ok": true, + "state": { + "activeLaunchProfile": "default", + "advanced": { + "configReadonly": false, + "maxFrameLatency": 1, + "reduceCpu": false, + "smoothFramerate": false, + "unlockOptions": false, + "vramLimited": false, + "vramTarget": 100 + }, + "appVersion": "v1.3.4", + "configExists": true, + "currentLaunchOptions": "", + "dxvk": { + "confExists": false, + "installed": false, + "settings": { + "enableAsync": true, + "gplAsyncCache": true, + "hudEnabled": false, + "maxFrameLatency": 1, + "maxFrameRate": 0, + "numCompilerThreads": 0, + "tearFree": "True" + } + }, + "enhanced": { + "acknowledgedAt": null, + "backupStatus": "Not created", + "detectedAt": null, + "dumpSource": "", + "filesInstalled": 0, + "installed": false, + "launchOptionsActive": false + }, + "exeSwap": { + "activeBuildDate": "", + "activeBuildId": "Unverified", + "backupAvailable": false, + "compatibleActive": false, + "compatibleAvailable": false, + "compatibleBuildDate": "Mar 3, 2023", + "compatibleBuildId": "10650222", + "currentBuildDate": "Sep 10, 2026", + "currentBuildId": "24784313", + "displayLabel": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE. T7 installs will use T7 Patch Scroptss/T7Patch.", + "enhancedActive": false, + "enhancedAvailable": false, + "enhancedBuildDate": "", + "enhancedBuildId": "Enhanced", + "enhancedExeActive": false, + "executable": "$ROOT/game/BlackOpsIII.exe", + "executableHash": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "executableName": "BlackOpsIII.exe", + "integrityMessage": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE.", + "integrityStatus": "unverified", + "latestAvailable": false, + "modeLabel": "Unknown", + "patchLabel": "T7 Patch Scroptss/T7Patch", + "profile": "", + "state": "unverified", + "trustedExecutable": false + }, + "gameDetected": true, + "gameDir": "$ROOT/game", + "graphics": { + "displayMode": 1, + "drawFps": true, + "fov": 110, + "maxFps": 240, + "refreshRate": 60.0, + "renderResolution": 100, + "resolution": "1920x1080", + "vsync": true + }, + "launchProfiles": [ + { + "active": true, + "id": "default", + "installed": true, + "label": "Default (None)", + "option": "", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "offline", + "installed": true, + "label": "Play Offline", + "option": "+set fs_game offlinemp", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "all_around", + "installed": false, + "label": "All-around Enhancement Lite", + "option": "+set fs_game 2994481309", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "ultimate", + "installed": false, + "label": "Ultimate Experience Mod", + "option": "+set fs_game 2942053577", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "reforged", + "installed": false, + "label": "BO3 Reforged", + "option": "+set fs_game 3667377161", + "path": null, + "state": "Not Subscribed", + "subscribed": false + } + ], + "logPath": "$ROOT/data/PatchOpsIII.log", + "logs": [ + { + "category": "Success", + "line": "$TIMESTAMP - Success: Set MaxFPS to 240.", + "message": "Set MaxFPS to 240." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: Set FOV to 110.", + "message": "Set FOV to 110." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: Set DrawFPS to 1.", + "message": "Set DrawFPS to 1." + } + ], + "maintenance": { + "logPayload": "PatchOpsIII v1.3.4 - Linux $KERNEL (x86_64) logs:\n```\n$TIMESTAMP - Success: Set MaxFPS to 240.\n$TIMESTAMP - Success: Set FOV to 110.\n$TIMESTAMP - Success: Set DrawFPS to 1.\n```", + "modFilesDir": "$ROOT/data/BO3 Mod Files" + }, + "mods": { + "dxvk": false, + "enhanced": false, + "t7Patch": false + }, + "platform": "Linux", + "presets": [ + "Smooth Preset", + "Low-Latency Preset" + ], + "qol": { + "allIntros": false, + "d3dcompiler": false, + "intro": false + }, + "releaseChannel": "stable", + "steamUserId": "76561198000000000", + "t7": { + "colorCode": "", + "confExists": false, + "friendsOnly": false, + "gamertag": "", + "installed": false, + "mode": "Custom", + "networkPassword": "", + "plainName": "" + } + } + }, + { + "ok": true, + "state": { + "activeLaunchProfile": "default", + "advanced": { + "configReadonly": false, + "maxFrameLatency": 1, + "reduceCpu": true, + "smoothFramerate": false, + "unlockOptions": false, + "vramLimited": false, + "vramTarget": 100 + }, + "appVersion": "v1.3.4", + "configExists": true, + "currentLaunchOptions": "", + "dxvk": { + "confExists": false, + "installed": false, + "settings": { + "enableAsync": true, + "gplAsyncCache": true, + "hudEnabled": false, + "maxFrameLatency": 1, + "maxFrameRate": 0, + "numCompilerThreads": 0, + "tearFree": "True" + } + }, + "enhanced": { + "acknowledgedAt": null, + "backupStatus": "Not created", + "detectedAt": null, + "dumpSource": "", + "filesInstalled": 0, + "installed": false, + "launchOptionsActive": false + }, + "exeSwap": { + "activeBuildDate": "", + "activeBuildId": "Unverified", + "backupAvailable": false, + "compatibleActive": false, + "compatibleAvailable": false, + "compatibleBuildDate": "Mar 3, 2023", + "compatibleBuildId": "10650222", + "currentBuildDate": "Sep 10, 2026", + "currentBuildId": "24784313", + "displayLabel": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE. T7 installs will use T7 Patch Scroptss/T7Patch.", + "enhancedActive": false, + "enhancedAvailable": false, + "enhancedBuildDate": "", + "enhancedBuildId": "Enhanced", + "enhancedExeActive": false, + "executable": "$ROOT/game/BlackOpsIII.exe", + "executableHash": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "executableName": "BlackOpsIII.exe", + "integrityMessage": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE.", + "integrityStatus": "unverified", + "latestAvailable": false, + "modeLabel": "Unknown", + "patchLabel": "T7 Patch Scroptss/T7Patch", + "profile": "", + "state": "unverified", + "trustedExecutable": false + }, + "gameDetected": true, + "gameDir": "$ROOT/game", + "graphics": { + "displayMode": 1, + "drawFps": true, + "fov": 110, + "maxFps": 240, + "refreshRate": 60.0, + "renderResolution": 100, + "resolution": "1920x1080", + "vsync": true + }, + "launchProfiles": [ + { + "active": true, + "id": "default", + "installed": true, + "label": "Default (None)", + "option": "", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "offline", + "installed": true, + "label": "Play Offline", + "option": "+set fs_game offlinemp", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "all_around", + "installed": false, + "label": "All-around Enhancement Lite", + "option": "+set fs_game 2994481309", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "ultimate", + "installed": false, + "label": "Ultimate Experience Mod", + "option": "+set fs_game 2942053577", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "reforged", + "installed": false, + "label": "BO3 Reforged", + "option": "+set fs_game 3667377161", + "path": null, + "state": "Not Subscribed", + "subscribed": false + } + ], + "logPath": "$ROOT/data/PatchOpsIII.log", + "logs": [ + { + "category": "Success", + "line": "$TIMESTAMP - Success: Set MaxFPS to 240.", + "message": "Set MaxFPS to 240." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: Set FOV to 110.", + "message": "Set FOV to 110." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: Set DrawFPS to 1.", + "message": "Set DrawFPS to 1." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: Set SerializeRender to 2.", + "message": "Set SerializeRender to 2." + } + ], + "maintenance": { + "logPayload": "PatchOpsIII v1.3.4 - Linux $KERNEL (x86_64) logs:\n```\n$TIMESTAMP - Success: Set MaxFPS to 240.\n$TIMESTAMP - Success: Set FOV to 110.\n$TIMESTAMP - Success: Set DrawFPS to 1.\n$TIMESTAMP - Success: Set SerializeRender to 2.\n```", + "modFilesDir": "$ROOT/data/BO3 Mod Files" + }, + "mods": { + "dxvk": false, + "enhanced": false, + "t7Patch": false + }, + "platform": "Linux", + "presets": [ + "Smooth Preset", + "Low-Latency Preset" + ], + "qol": { + "allIntros": false, + "d3dcompiler": false, + "intro": false + }, + "releaseChannel": "stable", + "steamUserId": "76561198000000000", + "t7": { + "colorCode": "", + "confExists": false, + "friendsOnly": false, + "gamertag": "", + "installed": false, + "mode": "Custom", + "networkPassword": "", + "plainName": "" + } + } + }, + { + "ok": true, + "state": { + "activeLaunchProfile": "default", + "advanced": { + "configReadonly": false, + "maxFrameLatency": 1, + "reduceCpu": true, + "smoothFramerate": false, + "unlockOptions": false, + "vramLimited": true, + "vramTarget": 85 + }, + "appVersion": "v1.3.4", + "configExists": true, + "currentLaunchOptions": "", + "dxvk": { + "confExists": false, + "installed": false, + "settings": { + "enableAsync": true, + "gplAsyncCache": true, + "hudEnabled": false, + "maxFrameLatency": 1, + "maxFrameRate": 0, + "numCompilerThreads": 0, + "tearFree": "True" + } + }, + "enhanced": { + "acknowledgedAt": null, + "backupStatus": "Not created", + "detectedAt": null, + "dumpSource": "", + "filesInstalled": 0, + "installed": false, + "launchOptionsActive": false + }, + "exeSwap": { + "activeBuildDate": "", + "activeBuildId": "Unverified", + "backupAvailable": false, + "compatibleActive": false, + "compatibleAvailable": false, + "compatibleBuildDate": "Mar 3, 2023", + "compatibleBuildId": "10650222", + "currentBuildDate": "Sep 10, 2026", + "currentBuildId": "24784313", + "displayLabel": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE. T7 installs will use T7 Patch Scroptss/T7Patch.", + "enhancedActive": false, + "enhancedAvailable": false, + "enhancedBuildDate": "", + "enhancedBuildId": "Enhanced", + "enhancedExeActive": false, + "executable": "$ROOT/game/BlackOpsIII.exe", + "executableHash": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "executableName": "BlackOpsIII.exe", + "integrityMessage": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE.", + "integrityStatus": "unverified", + "latestAvailable": false, + "modeLabel": "Unknown", + "patchLabel": "T7 Patch Scroptss/T7Patch", + "profile": "", + "state": "unverified", + "trustedExecutable": false + }, + "gameDetected": true, + "gameDir": "$ROOT/game", + "graphics": { + "displayMode": 1, + "drawFps": true, + "fov": 110, + "maxFps": 240, + "refreshRate": 60.0, + "renderResolution": 100, + "resolution": "1920x1080", + "vsync": true + }, + "launchProfiles": [ + { + "active": true, + "id": "default", + "installed": true, + "label": "Default (None)", + "option": "", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "offline", + "installed": true, + "label": "Play Offline", + "option": "+set fs_game offlinemp", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "all_around", + "installed": false, + "label": "All-around Enhancement Lite", + "option": "+set fs_game 2994481309", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "ultimate", + "installed": false, + "label": "Ultimate Experience Mod", + "option": "+set fs_game 2942053577", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "reforged", + "installed": false, + "label": "BO3 Reforged", + "option": "+set fs_game 3667377161", + "path": null, + "state": "Not Subscribed", + "subscribed": false + } + ], + "logPath": "$ROOT/data/PatchOpsIII.log", + "logs": [ + { + "category": "Success", + "line": "$TIMESTAMP - Success: Set MaxFPS to 240.", + "message": "Set MaxFPS to 240." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: Set FOV to 110.", + "message": "Set FOV to 110." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: Set DrawFPS to 1.", + "message": "Set DrawFPS to 1." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: Set SerializeRender to 2.", + "message": "Set SerializeRender to 2." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: Limited VRAM usage set to 85%.", + "message": "Limited VRAM usage set to 85%." + } + ], + "maintenance": { + "logPayload": "PatchOpsIII v1.3.4 - Linux $KERNEL (x86_64) logs:\n```\n$TIMESTAMP - Success: Set MaxFPS to 240.\n$TIMESTAMP - Success: Set FOV to 110.\n$TIMESTAMP - Success: Set DrawFPS to 1.\n$TIMESTAMP - Success: Set SerializeRender to 2.\n$TIMESTAMP - Success: Limited VRAM usage set to 85%.\n```", + "modFilesDir": "$ROOT/data/BO3 Mod Files" + }, + "mods": { + "dxvk": false, + "enhanced": false, + "t7Patch": false + }, + "platform": "Linux", + "presets": [ + "Smooth Preset", + "Low-Latency Preset" + ], + "qol": { + "allIntros": false, + "d3dcompiler": false, + "intro": false + }, + "releaseChannel": "stable", + "steamUserId": "76561198000000000", + "t7": { + "colorCode": "", + "confExists": false, + "friendsOnly": false, + "gamertag": "", + "installed": false, + "mode": "Custom", + "networkPassword": "", + "plainName": "" + } + } + } + ] + }, + "fixture": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "name": "config_graphics_advanced_write", + "operations": [ + { + "body": { + "comment": "fixture max fps", + "key": "MaxFPS", + "value": 240 + }, + "method": "POST", + "path": "/api/config" + }, + { + "body": { + "comment": "fixture fov", + "key": "FOV", + "value": 110 + }, + "method": "POST", + "path": "/api/config" + }, + { + "body": { + "comment": "fixture counter", + "key": "DrawFPS", + "value": 1 + }, + "method": "POST", + "path": "/api/config" + }, + { + "body": { + "comment": "fixture cpu", + "key": "SerializeRender", + "value": 2 + }, + "method": "POST", + "path": "/api/config" + }, + { + "body": { + "limited": true, + "target": 85 + }, + "method": "POST", + "path": "/api/vram-target" + } + ], + "runner": "direct endpoint functions", + "schemaVersion": 1, + "sourceHashes": { + "backend/api.py": "080be5c4a3b929134298179cd3f4d1558c288a50dcdb38978ada831925a57a46", + "bo3_enhanced.py": "32a670c8a63a64216ee5c46aa000d2e3840ca348ca0fd5031c81699252cf57b8", + "dxvk_manager.py": "8e150458bc7480ac89cd9e64c558ba438f61a09611bbf24b962cba02d7e4a5e5", + "t7_patch.py": "73e66fadd1c0775742b7bb6d9c6c73a3c9df8304d3117abd8ac768dfc68cc3ea", + "utils.py": "313069c3f70b92888239c9ef15ce2379713b029e019426e76b386f088b556564" + }, + "sourceOfTruth": "Python backend on main" +} diff --git a/native/core/tests/parity/config_missing_file.json b/native/core/tests/parity/config_missing_file.json new file mode 100644 index 0000000..05c8e9d --- /dev/null +++ b/native/core/tests/parity/config_missing_file.json @@ -0,0 +1,296 @@ +{ + "expected": { + "checkpoints": [ + { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 436, + "path": "data/PatchOpsIII.log", + "sha256": "7887d478e9b567914954cabb07c8a4f3b7590487a7a90178f41b09380b6d1140", + "size": 74, + "text": "$TIMESTAMP - Error: config.ini not found at $ROOT/game/players/config.ini\n" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + } + ], + "filesystem": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 436, + "path": "data/PatchOpsIII.log", + "sha256": "7887d478e9b567914954cabb07c8a4f3b7590487a7a90178f41b09380b6d1140", + "size": 74, + "text": "$TIMESTAMP - Error: config.ini not found at $ROOT/game/players/config.ini\n" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "responses": [ + { + "error": "config.ini not found at $ROOT/game/players/config.ini", + "ok": false + } + ] + }, + "fixture": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "name": "config_missing_file", + "operations": [ + { + "body": { + "key": "FOV", + "value": 90 + }, + "method": "POST", + "path": "/api/config" + } + ], + "runner": "direct endpoint functions", + "schemaVersion": 1, + "sourceHashes": { + "backend/api.py": "080be5c4a3b929134298179cd3f4d1558c288a50dcdb38978ada831925a57a46", + "bo3_enhanced.py": "32a670c8a63a64216ee5c46aa000d2e3840ca348ca0fd5031c81699252cf57b8", + "dxvk_manager.py": "8e150458bc7480ac89cd9e64c558ba438f61a09611bbf24b962cba02d7e4a5e5", + "t7_patch.py": "73e66fadd1c0775742b7bb6d9c6c73a3c9df8304d3117abd8ac768dfc68cc3ea", + "utils.py": "313069c3f70b92888239c9ef15ce2379713b029e019426e76b386f088b556564" + }, + "sourceOfTruth": "Python backend on main" +} diff --git a/native/core/tests/parity/config_readonly_roundtrip.json b/native/core/tests/parity/config_readonly_roundtrip.json new file mode 100644 index 0000000..1952a8c --- /dev/null +++ b/native/core/tests/parity/config_readonly_roundtrip.json @@ -0,0 +1,873 @@ +{ + "expected": { + "checkpoints": [ + { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 436, + "path": "data/PatchOpsIII.log", + "sha256": "0cbf6bdc3874ea5334e7d242b5008c0bd27467ad303be2db7b1d5f065b6d43e1", + "size": 51, + "text": "$TIMESTAMP - Success: config.ini set to read-only.\n" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 256, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 436, + "path": "data/PatchOpsIII.log", + "sha256": "27ed3c899b7c99c0dfb22609a80bc72c21e01b6c5910efb7ec06905af8e7b03f", + "size": 133, + "text": "$TIMESTAMP - Success: config.ini set to read-only.\n$TIMESTAMP - Error: [Errno 13] Permission denied: '$ROOT/game/players/config.ini'\n" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 256, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 436, + "path": "data/PatchOpsIII.log", + "sha256": "c32d57314e410d703bcf8c7c4483998639c0064fc45b9b9a3672076d815a7b52", + "size": 183, + "text": "$TIMESTAMP - Success: config.ini set to read-only.\n$TIMESTAMP - Error: [Errno 13] Permission denied: '$ROOT/game/players/config.ini'\n$TIMESTAMP - Success: config.ini set to writable.\n" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 384, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + } + ], + "filesystem": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 436, + "path": "data/PatchOpsIII.log", + "sha256": "c32d57314e410d703bcf8c7c4483998639c0064fc45b9b9a3672076d815a7b52", + "size": 183, + "text": "$TIMESTAMP - Success: config.ini set to read-only.\n$TIMESTAMP - Error: [Errno 13] Permission denied: '$ROOT/game/players/config.ini'\n$TIMESTAMP - Success: config.ini set to writable.\n" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 384, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "responses": [ + { + "ok": true, + "state": { + "activeLaunchProfile": "default", + "advanced": { + "configReadonly": true, + "maxFrameLatency": 1, + "reduceCpu": false, + "smoothFramerate": false, + "unlockOptions": false, + "vramLimited": false, + "vramTarget": 100 + }, + "appVersion": "v1.3.4", + "configExists": true, + "currentLaunchOptions": "", + "dxvk": { + "confExists": false, + "installed": false, + "settings": { + "enableAsync": true, + "gplAsyncCache": true, + "hudEnabled": false, + "maxFrameLatency": 1, + "maxFrameRate": 0, + "numCompilerThreads": 0, + "tearFree": "True" + } + }, + "enhanced": { + "acknowledgedAt": null, + "backupStatus": "Not created", + "detectedAt": null, + "dumpSource": "", + "filesInstalled": 0, + "installed": false, + "launchOptionsActive": false + }, + "exeSwap": { + "activeBuildDate": "", + "activeBuildId": "Unverified", + "backupAvailable": false, + "compatibleActive": false, + "compatibleAvailable": false, + "compatibleBuildDate": "Mar 3, 2023", + "compatibleBuildId": "10650222", + "currentBuildDate": "Sep 10, 2026", + "currentBuildId": "24784313", + "displayLabel": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE. T7 installs will use T7 Patch Scroptss/T7Patch.", + "enhancedActive": false, + "enhancedAvailable": false, + "enhancedBuildDate": "", + "enhancedBuildId": "Enhanced", + "enhancedExeActive": false, + "executable": "$ROOT/game/BlackOpsIII.exe", + "executableHash": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "executableName": "BlackOpsIII.exe", + "integrityMessage": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE.", + "integrityStatus": "unverified", + "latestAvailable": false, + "modeLabel": "Unknown", + "patchLabel": "T7 Patch Scroptss/T7Patch", + "profile": "", + "state": "unverified", + "trustedExecutable": false + }, + "gameDetected": true, + "gameDir": "$ROOT/game", + "graphics": { + "displayMode": 1, + "drawFps": false, + "fov": 80, + "maxFps": 165, + "refreshRate": 60.0, + "renderResolution": 100, + "resolution": "1920x1080", + "vsync": true + }, + "launchProfiles": [ + { + "active": true, + "id": "default", + "installed": true, + "label": "Default (None)", + "option": "", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "offline", + "installed": true, + "label": "Play Offline", + "option": "+set fs_game offlinemp", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "all_around", + "installed": false, + "label": "All-around Enhancement Lite", + "option": "+set fs_game 2994481309", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "ultimate", + "installed": false, + "label": "Ultimate Experience Mod", + "option": "+set fs_game 2942053577", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "reforged", + "installed": false, + "label": "BO3 Reforged", + "option": "+set fs_game 3667377161", + "path": null, + "state": "Not Subscribed", + "subscribed": false + } + ], + "logPath": "$ROOT/data/PatchOpsIII.log", + "logs": [ + { + "category": "Success", + "line": "$TIMESTAMP - Success: config.ini set to read-only.", + "message": "config.ini set to read-only." + } + ], + "maintenance": { + "logPayload": "PatchOpsIII v1.3.4 - Linux $KERNEL (x86_64) logs:\n```\n$TIMESTAMP - Success: config.ini set to read-only.\n```", + "modFilesDir": "$ROOT/data/BO3 Mod Files" + }, + "mods": { + "dxvk": false, + "enhanced": false, + "t7Patch": false + }, + "platform": "Linux", + "presets": [ + "Smooth Preset", + "Low-Latency Preset" + ], + "qol": { + "allIntros": false, + "d3dcompiler": false, + "intro": false + }, + "releaseChannel": "stable", + "steamUserId": "76561198000000000", + "t7": { + "colorCode": "", + "confExists": false, + "friendsOnly": false, + "gamertag": "", + "installed": false, + "mode": "Custom", + "networkPassword": "", + "plainName": "" + } + } + }, + { + "error": "[Errno 13] Permission denied: '$ROOT/game/players/config.ini'", + "ok": false + }, + { + "ok": true, + "state": { + "activeLaunchProfile": "default", + "advanced": { + "configReadonly": false, + "maxFrameLatency": 1, + "reduceCpu": false, + "smoothFramerate": false, + "unlockOptions": false, + "vramLimited": false, + "vramTarget": 100 + }, + "appVersion": "v1.3.4", + "configExists": true, + "currentLaunchOptions": "", + "dxvk": { + "confExists": false, + "installed": false, + "settings": { + "enableAsync": true, + "gplAsyncCache": true, + "hudEnabled": false, + "maxFrameLatency": 1, + "maxFrameRate": 0, + "numCompilerThreads": 0, + "tearFree": "True" + } + }, + "enhanced": { + "acknowledgedAt": null, + "backupStatus": "Not created", + "detectedAt": null, + "dumpSource": "", + "filesInstalled": 0, + "installed": false, + "launchOptionsActive": false + }, + "exeSwap": { + "activeBuildDate": "", + "activeBuildId": "Unverified", + "backupAvailable": false, + "compatibleActive": false, + "compatibleAvailable": false, + "compatibleBuildDate": "Mar 3, 2023", + "compatibleBuildId": "10650222", + "currentBuildDate": "Sep 10, 2026", + "currentBuildId": "24784313", + "displayLabel": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE. T7 installs will use T7 Patch Scroptss/T7Patch.", + "enhancedActive": false, + "enhancedAvailable": false, + "enhancedBuildDate": "", + "enhancedBuildId": "Enhanced", + "enhancedExeActive": false, + "executable": "$ROOT/game/BlackOpsIII.exe", + "executableHash": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "executableName": "BlackOpsIII.exe", + "integrityMessage": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE.", + "integrityStatus": "unverified", + "latestAvailable": false, + "modeLabel": "Unknown", + "patchLabel": "T7 Patch Scroptss/T7Patch", + "profile": "", + "state": "unverified", + "trustedExecutable": false + }, + "gameDetected": true, + "gameDir": "$ROOT/game", + "graphics": { + "displayMode": 1, + "drawFps": false, + "fov": 80, + "maxFps": 165, + "refreshRate": 60.0, + "renderResolution": 100, + "resolution": "1920x1080", + "vsync": true + }, + "launchProfiles": [ + { + "active": true, + "id": "default", + "installed": true, + "label": "Default (None)", + "option": "", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "offline", + "installed": true, + "label": "Play Offline", + "option": "+set fs_game offlinemp", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "all_around", + "installed": false, + "label": "All-around Enhancement Lite", + "option": "+set fs_game 2994481309", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "ultimate", + "installed": false, + "label": "Ultimate Experience Mod", + "option": "+set fs_game 2942053577", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "reforged", + "installed": false, + "label": "BO3 Reforged", + "option": "+set fs_game 3667377161", + "path": null, + "state": "Not Subscribed", + "subscribed": false + } + ], + "logPath": "$ROOT/data/PatchOpsIII.log", + "logs": [ + { + "category": "Success", + "line": "$TIMESTAMP - Success: config.ini set to read-only.", + "message": "config.ini set to read-only." + }, + { + "category": "Error", + "line": "$TIMESTAMP - Error: [Errno 13] Permission denied: '$ROOT/game/players/config.ini'", + "message": "[Errno 13] Permission denied: '$ROOT/game/players/config.ini'" + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: config.ini set to writable.", + "message": "config.ini set to writable." + } + ], + "maintenance": { + "logPayload": "PatchOpsIII v1.3.4 - Linux $KERNEL (x86_64) logs:\n```\n$TIMESTAMP - Success: config.ini set to read-only.\n$TIMESTAMP - Error: [Errno 13] Permission denied: '$ROOT/game/players/config.ini'\n$TIMESTAMP - Success: config.ini set to writable.\n```", + "modFilesDir": "$ROOT/data/BO3 Mod Files" + }, + "mods": { + "dxvk": false, + "enhanced": false, + "t7Patch": false + }, + "platform": "Linux", + "presets": [ + "Smooth Preset", + "Low-Latency Preset" + ], + "qol": { + "allIntros": false, + "d3dcompiler": false, + "intro": false + }, + "releaseChannel": "stable", + "steamUserId": "76561198000000000", + "t7": { + "colorCode": "", + "confExists": false, + "friendsOnly": false, + "gamertag": "", + "installed": false, + "mode": "Custom", + "networkPassword": "", + "plainName": "" + } + } + } + ] + }, + "fixture": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "name": "config_readonly_roundtrip", + "operations": [ + { + "body": { + "enabled": true + }, + "method": "POST", + "path": "/api/config-readonly" + }, + { + "body": { + "key": "FOV", + "value": 90 + }, + "method": "POST", + "path": "/api/config" + }, + { + "body": { + "enabled": false + }, + "method": "POST", + "path": "/api/config-readonly" + } + ], + "runner": "direct endpoint functions", + "schemaVersion": 1, + "sourceHashes": { + "backend/api.py": "080be5c4a3b929134298179cd3f4d1558c288a50dcdb38978ada831925a57a46", + "bo3_enhanced.py": "32a670c8a63a64216ee5c46aa000d2e3840ca348ca0fd5031c81699252cf57b8", + "dxvk_manager.py": "8e150458bc7480ac89cd9e64c558ba438f61a09611bbf24b962cba02d7e4a5e5", + "t7_patch.py": "73e66fadd1c0775742b7bb6d9c6c73a3c9df8304d3117abd8ac768dfc68cc3ea", + "utils.py": "313069c3f70b92888239c9ef15ce2379713b029e019426e76b386f088b556564" + }, + "sourceOfTruth": "Python backend on main" +} diff --git a/native/core/tests/parity/dxvk_config_write.json b/native/core/tests/parity/dxvk_config_write.json new file mode 100644 index 0000000..41c475d --- /dev/null +++ b/native/core/tests/parity/dxvk_config_write.json @@ -0,0 +1,550 @@ +{ + "expected": { + "checkpoints": [ + { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 436, + "path": "data/PatchOpsIII.log", + "sha256": "4fba4fb547a7790f00d93c27e3eac01805518dc8a1cdffdf564a91bc8cba0a42", + "size": 60, + "text": "$TIMESTAMP - Success: Updated dxvk.conf from DXVK settings.\n" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3d11.dll", + "sha256": "dc5515425bf1f3f94009300d02aaab926e2c7b60a7c11dc1bd2e7d2005fcc510", + "size": 13, + "text": "fixture-d3d11" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "base64": "AP9maXh0dXJlLWR4Z2k=", + "mode": 420, + "path": "game/dxgi.dll", + "sha256": "e7e2f73af077301aedc20be31fc767cb105d7e99b2c7969429cc5074b85e3f0f", + "size": 14 + }, + { + "mode": 420, + "path": "game/dxvk.conf", + "sha256": "bf50c7e6c4bcb2f97c849cf7d854912de81121c539094eaf55dc8ceab1e3cdde", + "size": 145, + "text": "dxvk.enableAsync=false\ndxvk.numCompilerThreads=6\ndxgi.maxFrameRate=144\ndxgi.maxFrameLatency=2\ndxvk.tearFree=Auto\ndxvk.hud=fps,frametimes,gpuload\n" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + } + ], + "filesystem": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 436, + "path": "data/PatchOpsIII.log", + "sha256": "4fba4fb547a7790f00d93c27e3eac01805518dc8a1cdffdf564a91bc8cba0a42", + "size": 60, + "text": "$TIMESTAMP - Success: Updated dxvk.conf from DXVK settings.\n" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3d11.dll", + "sha256": "dc5515425bf1f3f94009300d02aaab926e2c7b60a7c11dc1bd2e7d2005fcc510", + "size": 13, + "text": "fixture-d3d11" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "base64": "AP9maXh0dXJlLWR4Z2k=", + "mode": 420, + "path": "game/dxgi.dll", + "sha256": "e7e2f73af077301aedc20be31fc767cb105d7e99b2c7969429cc5074b85e3f0f", + "size": 14 + }, + { + "mode": 420, + "path": "game/dxvk.conf", + "sha256": "bf50c7e6c4bcb2f97c849cf7d854912de81121c539094eaf55dc8ceab1e3cdde", + "size": 145, + "text": "dxvk.enableAsync=false\ndxvk.numCompilerThreads=6\ndxgi.maxFrameRate=144\ndxgi.maxFrameLatency=2\ndxvk.tearFree=Auto\ndxvk.hud=fps,frametimes,gpuload\n" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "responses": [ + { + "ok": true, + "state": { + "activeLaunchProfile": "default", + "advanced": { + "configReadonly": false, + "maxFrameLatency": 1, + "reduceCpu": false, + "smoothFramerate": false, + "unlockOptions": false, + "vramLimited": false, + "vramTarget": 100 + }, + "appVersion": "v1.3.4", + "configExists": true, + "currentLaunchOptions": "", + "dxvk": { + "confExists": true, + "installed": true, + "settings": { + "enableAsync": false, + "gplAsyncCache": true, + "hudEnabled": true, + "maxFrameLatency": 2, + "maxFrameRate": 144, + "numCompilerThreads": 6, + "tearFree": "Auto" + } + }, + "enhanced": { + "acknowledgedAt": null, + "backupStatus": "Not created", + "detectedAt": null, + "dumpSource": "", + "filesInstalled": 0, + "installed": false, + "launchOptionsActive": false + }, + "exeSwap": { + "activeBuildDate": "", + "activeBuildId": "Unverified", + "backupAvailable": false, + "compatibleActive": false, + "compatibleAvailable": false, + "compatibleBuildDate": "Mar 3, 2023", + "compatibleBuildId": "10650222", + "currentBuildDate": "Sep 10, 2026", + "currentBuildId": "24784313", + "displayLabel": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE. T7 installs will use T7 Patch Scroptss/T7Patch.", + "enhancedActive": false, + "enhancedAvailable": false, + "enhancedBuildDate": "", + "enhancedBuildId": "Enhanced", + "enhancedExeActive": false, + "executable": "$ROOT/game/BlackOpsIII.exe", + "executableHash": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "executableName": "BlackOpsIII.exe", + "integrityMessage": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE.", + "integrityStatus": "unverified", + "latestAvailable": false, + "modeLabel": "Unknown", + "patchLabel": "T7 Patch Scroptss/T7Patch", + "profile": "", + "state": "unverified", + "trustedExecutable": false + }, + "gameDetected": true, + "gameDir": "$ROOT/game", + "graphics": { + "displayMode": 1, + "drawFps": false, + "fov": 80, + "maxFps": 165, + "refreshRate": 60.0, + "renderResolution": 100, + "resolution": "1920x1080", + "vsync": true + }, + "launchProfiles": [ + { + "active": true, + "id": "default", + "installed": true, + "label": "Default (None)", + "option": "", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "offline", + "installed": true, + "label": "Play Offline", + "option": "+set fs_game offlinemp", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "all_around", + "installed": false, + "label": "All-around Enhancement Lite", + "option": "+set fs_game 2994481309", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "ultimate", + "installed": false, + "label": "Ultimate Experience Mod", + "option": "+set fs_game 2942053577", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "reforged", + "installed": false, + "label": "BO3 Reforged", + "option": "+set fs_game 3667377161", + "path": null, + "state": "Not Subscribed", + "subscribed": false + } + ], + "logPath": "$ROOT/data/PatchOpsIII.log", + "logs": [ + { + "category": "Success", + "line": "$TIMESTAMP - Success: Updated dxvk.conf from DXVK settings.", + "message": "Updated dxvk.conf from DXVK settings." + } + ], + "maintenance": { + "logPayload": "PatchOpsIII v1.3.4 - Linux $KERNEL (x86_64) logs:\n```\n$TIMESTAMP - Success: Updated dxvk.conf from DXVK settings.\n```", + "modFilesDir": "$ROOT/data/BO3 Mod Files" + }, + "mods": { + "dxvk": true, + "enhanced": false, + "t7Patch": false + }, + "platform": "Linux", + "presets": [ + "Smooth Preset", + "Low-Latency Preset" + ], + "qol": { + "allIntros": false, + "d3dcompiler": false, + "intro": false + }, + "releaseChannel": "stable", + "steamUserId": "76561198000000000", + "t7": { + "colorCode": "", + "confExists": false, + "friendsOnly": false, + "gamertag": "", + "installed": false, + "mode": "Custom", + "networkPassword": "", + "plainName": "" + } + } + } + ] + }, + "fixture": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3d11.dll", + "sha256": "dc5515425bf1f3f94009300d02aaab926e2c7b60a7c11dc1bd2e7d2005fcc510", + "size": 13, + "text": "fixture-d3d11" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "base64": "AP9maXh0dXJlLWR4Z2k=", + "mode": 420, + "path": "game/dxgi.dll", + "sha256": "e7e2f73af077301aedc20be31fc767cb105d7e99b2c7969429cc5074b85e3f0f", + "size": 14 + }, + { + "mode": 420, + "path": "game/dxvk.conf", + "sha256": "f0a2ee076de3a6b372fe91658dea9c64d8d0a2cd86bdacbf4082d17c9c60b524", + "size": 45, + "text": "dxvk.enableAsync=true\ndxgi.maxFrameLatency=1\n" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "name": "dxvk_config_write", + "operations": [ + { + "body": { + "enableAsync": false, + "gplAsyncCache": false, + "hudEnabled": true, + "maxFrameLatency": 2, + "maxFrameRate": 144, + "numCompilerThreads": 6, + "tearFree": "Auto" + }, + "method": "POST", + "path": "/api/dxvk-config" + } + ], + "runner": "direct endpoint functions", + "schemaVersion": 1, + "sourceHashes": { + "backend/api.py": "080be5c4a3b929134298179cd3f4d1558c288a50dcdb38978ada831925a57a46", + "bo3_enhanced.py": "32a670c8a63a64216ee5c46aa000d2e3840ca348ca0fd5031c81699252cf57b8", + "dxvk_manager.py": "8e150458bc7480ac89cd9e64c558ba438f61a09611bbf24b962cba02d7e4a5e5", + "t7_patch.py": "73e66fadd1c0775742b7bb6d9c6c73a3c9df8304d3117abd8ac768dfc68cc3ea", + "utils.py": "313069c3f70b92888239c9ef15ce2379713b029e019426e76b386f088b556564" + }, + "sourceOfTruth": "Python backend on main" +} diff --git a/native/core/tests/parity/exe_preserved_enhanced_detection.json b/native/core/tests/parity/exe_preserved_enhanced_detection.json new file mode 100644 index 0000000..efb93c2 --- /dev/null +++ b/native/core/tests/parity/exe_preserved_enhanced_detection.json @@ -0,0 +1,476 @@ +{ + "expected": { + "checkpoints": [ + { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4752ef2d60db6d6142b391f2b2901592ae8cb000dbac3bed0602e86284c62e1a", + "size": 192, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\",\n \"enhanced_exe_hashes\": {\n \"$ROOT/game\": [\n \"0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2\"\n ]\n }\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.enhanced.bak", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + } + ], + "filesystem": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4752ef2d60db6d6142b391f2b2901592ae8cb000dbac3bed0602e86284c62e1a", + "size": 192, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\",\n \"enhanced_exe_hashes\": {\n \"$ROOT/game\": [\n \"0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2\"\n ]\n }\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.enhanced.bak", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "responses": [ + { + "activeLaunchProfile": "default", + "advanced": { + "configReadonly": false, + "maxFrameLatency": 1, + "reduceCpu": false, + "smoothFramerate": false, + "unlockOptions": false, + "vramLimited": false, + "vramTarget": 100 + }, + "appVersion": "v1.3.4", + "configExists": true, + "currentLaunchOptions": "", + "dxvk": { + "confExists": false, + "installed": false, + "settings": { + "enableAsync": true, + "gplAsyncCache": true, + "hudEnabled": false, + "maxFrameLatency": 1, + "maxFrameRate": 0, + "numCompilerThreads": 0, + "tearFree": "True" + } + }, + "enhanced": { + "acknowledgedAt": null, + "backupStatus": "Not created", + "detectedAt": null, + "dumpSource": "", + "filesInstalled": 0, + "installed": false, + "launchOptionsActive": false + }, + "exeSwap": { + "activeBuildDate": "", + "activeBuildId": "Enhanced", + "backupAvailable": false, + "compatibleActive": false, + "compatibleAvailable": false, + "compatibleBuildDate": "Mar 3, 2023", + "compatibleBuildId": "10650222", + "currentBuildDate": "Sep 10, 2026", + "currentBuildId": "24784313", + "displayLabel": "Executable is managed by BO3 Enhanced. T7 installs will use T7 Patch Scroptss/T7Patch.", + "enhancedActive": false, + "enhancedAvailable": true, + "enhancedBuildDate": "", + "enhancedBuildId": "Enhanced", + "enhancedExeActive": true, + "executable": "$ROOT/game/BlackOpsIII.exe", + "executableHash": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "executableName": "BlackOpsIII.exe", + "integrityMessage": "Executable is managed by BO3 Enhanced.", + "integrityStatus": "enhanced", + "latestAvailable": false, + "modeLabel": "Enhanced", + "patchLabel": "T7 Patch Scroptss/T7Patch", + "profile": "enhanced", + "state": "enhanced", + "trustedExecutable": true + }, + "gameDetected": true, + "gameDir": "$ROOT/game", + "graphics": { + "displayMode": 1, + "drawFps": false, + "fov": 80, + "maxFps": 165, + "refreshRate": 60.0, + "renderResolution": 100, + "resolution": "1920x1080", + "vsync": true + }, + "launchProfiles": [ + { + "active": true, + "id": "default", + "installed": true, + "label": "Default (None)", + "option": "", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "offline", + "installed": true, + "label": "Play Offline", + "option": "+set fs_game offlinemp", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "all_around", + "installed": false, + "label": "All-around Enhancement Lite", + "option": "+set fs_game 2994481309", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "ultimate", + "installed": false, + "label": "Ultimate Experience Mod", + "option": "+set fs_game 2942053577", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "reforged", + "installed": false, + "label": "BO3 Reforged", + "option": "+set fs_game 3667377161", + "path": null, + "state": "Not Subscribed", + "subscribed": false + } + ], + "logPath": "$ROOT/data/PatchOpsIII.log", + "logs": [], + "maintenance": { + "logPayload": "PatchOpsIII v1.3.4 - Linux $KERNEL (x86_64) logs:\n```\n(no log entries found)\n```", + "modFilesDir": "$ROOT/data/BO3 Mod Files" + }, + "mods": { + "dxvk": false, + "enhanced": false, + "t7Patch": false + }, + "platform": "Linux", + "presets": [ + "Smooth Preset", + "Low-Latency Preset" + ], + "qol": { + "allIntros": false, + "d3dcompiler": false, + "intro": false + }, + "releaseChannel": "stable", + "steamUserId": "76561198000000000", + "t7": { + "colorCode": "", + "confExists": false, + "friendsOnly": false, + "gamertag": "", + "installed": false, + "mode": "Custom", + "networkPassword": "", + "plainName": "" + } + } + ] + }, + "fixture": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4752ef2d60db6d6142b391f2b2901592ae8cb000dbac3bed0602e86284c62e1a", + "size": 192, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\",\n \"enhanced_exe_hashes\": {\n \"$ROOT/game\": [\n \"0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2\"\n ]\n }\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.enhanced.bak", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "name": "exe_preserved_enhanced_detection", + "operations": [ + { + "method": "GET", + "path": "/api/status" + } + ], + "runner": "direct endpoint functions", + "schemaVersion": 1, + "sourceHashes": { + "backend/api.py": "080be5c4a3b929134298179cd3f4d1558c288a50dcdb38978ada831925a57a46", + "bo3_enhanced.py": "32a670c8a63a64216ee5c46aa000d2e3840ca348ca0fd5031c81699252cf57b8", + "dxvk_manager.py": "8e150458bc7480ac89cd9e64c558ba438f61a09611bbf24b962cba02d7e4a5e5", + "t7_patch.py": "73e66fadd1c0775742b7bb6d9c6c73a3c9df8304d3117abd8ac768dfc68cc3ea", + "utils.py": "313069c3f70b92888239c9ef15ce2379713b029e019426e76b386f088b556564" + }, + "sourceOfTruth": "Python backend on main" +} diff --git a/native/core/tests/parity/exe_untrusted_backup_detection.json b/native/core/tests/parity/exe_untrusted_backup_detection.json new file mode 100644 index 0000000..2a1530f --- /dev/null +++ b/native/core/tests/parity/exe_untrusted_backup_detection.json @@ -0,0 +1,539 @@ +{ + "expected": { + "checkpoints": [ + { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.10650222.bak", + "sha256": "6a3302a80b8135a133456c63760088d45d72bf053d07015781f07e8b49a8dd95", + "size": 24, + "text": "not-a-trusted-executable" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.24784313.bak", + "sha256": "6a3302a80b8135a133456c63760088d45d72bf053d07015781f07e8b49a8dd95", + "size": 24, + "text": "not-a-trusted-executable" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.enhanced.bak", + "sha256": "6a3302a80b8135a133456c63760088d45d72bf053d07015781f07e8b49a8dd95", + "size": 24, + "text": "not-a-trusted-executable" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe.patchops.bak", + "sha256": "6a3302a80b8135a133456c63760088d45d72bf053d07015781f07e8b49a8dd95", + "size": 24, + "text": "not-a-trusted-executable" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + } + ], + "filesystem": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.10650222.bak", + "sha256": "6a3302a80b8135a133456c63760088d45d72bf053d07015781f07e8b49a8dd95", + "size": 24, + "text": "not-a-trusted-executable" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.24784313.bak", + "sha256": "6a3302a80b8135a133456c63760088d45d72bf053d07015781f07e8b49a8dd95", + "size": 24, + "text": "not-a-trusted-executable" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.enhanced.bak", + "sha256": "6a3302a80b8135a133456c63760088d45d72bf053d07015781f07e8b49a8dd95", + "size": 24, + "text": "not-a-trusted-executable" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe.patchops.bak", + "sha256": "6a3302a80b8135a133456c63760088d45d72bf053d07015781f07e8b49a8dd95", + "size": 24, + "text": "not-a-trusted-executable" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "responses": [ + { + "activeLaunchProfile": "default", + "advanced": { + "configReadonly": false, + "maxFrameLatency": 1, + "reduceCpu": false, + "smoothFramerate": false, + "unlockOptions": false, + "vramLimited": false, + "vramTarget": 100 + }, + "appVersion": "v1.3.4", + "configExists": true, + "currentLaunchOptions": "", + "dxvk": { + "confExists": false, + "installed": false, + "settings": { + "enableAsync": true, + "gplAsyncCache": true, + "hudEnabled": false, + "maxFrameLatency": 1, + "maxFrameRate": 0, + "numCompilerThreads": 0, + "tearFree": "True" + } + }, + "enhanced": { + "acknowledgedAt": null, + "backupStatus": "Not created", + "detectedAt": null, + "dumpSource": "", + "filesInstalled": 0, + "installed": false, + "launchOptionsActive": false + }, + "exeSwap": { + "activeBuildDate": "", + "activeBuildId": "Unverified", + "backupAvailable": false, + "compatibleActive": false, + "compatibleAvailable": false, + "compatibleBuildDate": "Mar 3, 2023", + "compatibleBuildId": "10650222", + "currentBuildDate": "Sep 10, 2026", + "currentBuildId": "24784313", + "displayLabel": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE. T7 installs will use T7 Patch Scroptss/T7Patch.", + "enhancedActive": false, + "enhancedAvailable": false, + "enhancedBuildDate": "", + "enhancedBuildId": "Enhanced", + "enhancedExeActive": false, + "executable": "$ROOT/game/BlackOpsIII.exe", + "executableHash": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "executableName": "BlackOpsIII.exe", + "integrityMessage": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE.", + "integrityStatus": "unverified", + "latestAvailable": false, + "modeLabel": "Unknown", + "patchLabel": "T7 Patch Scroptss/T7Patch", + "profile": "", + "state": "unverified", + "trustedExecutable": false + }, + "gameDetected": true, + "gameDir": "$ROOT/game", + "graphics": { + "displayMode": 1, + "drawFps": false, + "fov": 80, + "maxFps": 165, + "refreshRate": 60.0, + "renderResolution": 100, + "resolution": "1920x1080", + "vsync": true + }, + "launchProfiles": [ + { + "active": true, + "id": "default", + "installed": true, + "label": "Default (None)", + "option": "", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "offline", + "installed": true, + "label": "Play Offline", + "option": "+set fs_game offlinemp", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "all_around", + "installed": false, + "label": "All-around Enhancement Lite", + "option": "+set fs_game 2994481309", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "ultimate", + "installed": false, + "label": "Ultimate Experience Mod", + "option": "+set fs_game 2942053577", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "reforged", + "installed": false, + "label": "BO3 Reforged", + "option": "+set fs_game 3667377161", + "path": null, + "state": "Not Subscribed", + "subscribed": false + } + ], + "logPath": "$ROOT/data/PatchOpsIII.log", + "logs": [], + "maintenance": { + "logPayload": "PatchOpsIII v1.3.4 - Linux $KERNEL (x86_64) logs:\n```\n(no log entries found)\n```", + "modFilesDir": "$ROOT/data/BO3 Mod Files" + }, + "mods": { + "dxvk": false, + "enhanced": false, + "t7Patch": false + }, + "platform": "Linux", + "presets": [ + "Smooth Preset", + "Low-Latency Preset" + ], + "qol": { + "allIntros": false, + "d3dcompiler": false, + "intro": false + }, + "releaseChannel": "stable", + "steamUserId": "76561198000000000", + "t7": { + "colorCode": "", + "confExists": false, + "friendsOnly": false, + "gamertag": "", + "installed": false, + "mode": "Custom", + "networkPassword": "", + "plainName": "" + } + } + ] + }, + "fixture": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.10650222.bak", + "sha256": "6a3302a80b8135a133456c63760088d45d72bf053d07015781f07e8b49a8dd95", + "size": 24, + "text": "not-a-trusted-executable" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.24784313.bak", + "sha256": "6a3302a80b8135a133456c63760088d45d72bf053d07015781f07e8b49a8dd95", + "size": 24, + "text": "not-a-trusted-executable" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.enhanced.bak", + "sha256": "6a3302a80b8135a133456c63760088d45d72bf053d07015781f07e8b49a8dd95", + "size": 24, + "text": "not-a-trusted-executable" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe.patchops.bak", + "sha256": "6a3302a80b8135a133456c63760088d45d72bf053d07015781f07e8b49a8dd95", + "size": 24, + "text": "not-a-trusted-executable" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "name": "exe_untrusted_backup_detection", + "operations": [ + { + "method": "GET", + "path": "/api/status" + } + ], + "runner": "direct endpoint functions", + "schemaVersion": 1, + "sourceHashes": { + "backend/api.py": "080be5c4a3b929134298179cd3f4d1558c288a50dcdb38978ada831925a57a46", + "bo3_enhanced.py": "32a670c8a63a64216ee5c46aa000d2e3840ca348ca0fd5031c81699252cf57b8", + "dxvk_manager.py": "8e150458bc7480ac89cd9e64c558ba438f61a09611bbf24b962cba02d7e4a5e5", + "t7_patch.py": "73e66fadd1c0775742b7bb6d9c6c73a3c9df8304d3117abd8ac768dfc68cc3ea", + "utils.py": "313069c3f70b92888239c9ef15ce2379713b029e019426e76b386f088b556564" + }, + "sourceOfTruth": "Python backend on main" +} diff --git a/native/core/tests/parity/game_directory_validation_invalid.json b/native/core/tests/parity/game_directory_validation_invalid.json new file mode 100644 index 0000000..13f85e7 --- /dev/null +++ b/native/core/tests/parity/game_directory_validation_invalid.json @@ -0,0 +1,202 @@ +{ + "expected": { + "checkpoints": [ + { + "directories": [ + "data", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 436, + "path": "data/PatchOpsIII.log", + "sha256": "55e49116f53272369a07a704208aa87457422dab49635004e6497bc269cbdf8a", + "size": 88, + "text": "$TIMESTAMP - Error: Selected directory is not a Black Ops III install: $ROOT/not-a-game\n" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + } + ], + "filesystem": { + "directories": [ + "data", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 436, + "path": "data/PatchOpsIII.log", + "sha256": "55e49116f53272369a07a704208aa87457422dab49635004e6497bc269cbdf8a", + "size": 88, + "text": "$TIMESTAMP - Error: Selected directory is not a Black Ops III install: $ROOT/not-a-game\n" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "responses": [ + { + "error": "BlackOps3.exe or BlackOpsIII.exe was not found.", + "ok": false + } + ] + }, + "fixture": { + "directories": [ + "data", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "name": "game_directory_validation_invalid", + "operations": [ + { + "body": { + "path": "$ROOT/not-a-game" + }, + "method": "POST", + "path": "/api/game-directory" + } + ], + "runner": "direct endpoint functions", + "schemaVersion": 1, + "sourceHashes": { + "backend/api.py": "080be5c4a3b929134298179cd3f4d1558c288a50dcdb38978ada831925a57a46", + "bo3_enhanced.py": "32a670c8a63a64216ee5c46aa000d2e3840ca348ca0fd5031c81699252cf57b8", + "dxvk_manager.py": "8e150458bc7480ac89cd9e64c558ba438f61a09611bbf24b962cba02d7e4a5e5", + "t7_patch.py": "73e66fadd1c0775742b7bb6d9c6c73a3c9df8304d3117abd8ac768dfc68cc3ea", + "utils.py": "313069c3f70b92888239c9ef15ce2379713b029e019426e76b386f088b556564" + }, + "sourceOfTruth": "Python backend on main" +} diff --git a/native/core/tests/parity/game_directory_validation_valid.json b/native/core/tests/parity/game_directory_validation_valid.json new file mode 100644 index 0000000..6c2f32c --- /dev/null +++ b/native/core/tests/parity/game_directory_validation_valid.json @@ -0,0 +1,481 @@ +{ + "expected": { + "checkpoints": [ + { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 436, + "path": "data/PatchOpsIII.log", + "sha256": "0737e37545c83c8fd0201864ecef447104acf15b268c0df598b38d879cc18f27", + "size": 55, + "text": "$TIMESTAMP - Success: Game directory set to $ROOT/game\n" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + } + ], + "filesystem": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 436, + "path": "data/PatchOpsIII.log", + "sha256": "0737e37545c83c8fd0201864ecef447104acf15b268c0df598b38d879cc18f27", + "size": 55, + "text": "$TIMESTAMP - Success: Game directory set to $ROOT/game\n" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "responses": [ + { + "ok": true, + "state": { + "activeLaunchProfile": "default", + "advanced": { + "configReadonly": false, + "maxFrameLatency": 1, + "reduceCpu": false, + "smoothFramerate": false, + "unlockOptions": false, + "vramLimited": false, + "vramTarget": 100 + }, + "appVersion": "v1.3.4", + "configExists": true, + "currentLaunchOptions": "", + "dxvk": { + "confExists": false, + "installed": false, + "settings": { + "enableAsync": true, + "gplAsyncCache": true, + "hudEnabled": false, + "maxFrameLatency": 1, + "maxFrameRate": 0, + "numCompilerThreads": 0, + "tearFree": "True" + } + }, + "enhanced": { + "acknowledgedAt": null, + "backupStatus": "Not created", + "detectedAt": null, + "dumpSource": "", + "filesInstalled": 0, + "installed": false, + "launchOptionsActive": false + }, + "exeSwap": { + "activeBuildDate": "", + "activeBuildId": "Unverified", + "backupAvailable": false, + "compatibleActive": false, + "compatibleAvailable": false, + "compatibleBuildDate": "Mar 3, 2023", + "compatibleBuildId": "10650222", + "currentBuildDate": "Sep 10, 2026", + "currentBuildId": "24784313", + "displayLabel": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE. T7 installs will use T7 Patch Scroptss/T7Patch.", + "enhancedActive": false, + "enhancedAvailable": false, + "enhancedBuildDate": "", + "enhancedBuildId": "Enhanced", + "enhancedExeActive": false, + "executable": "$ROOT/game/BlackOpsIII.exe", + "executableHash": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "executableName": "BlackOpsIII.exe", + "integrityMessage": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE.", + "integrityStatus": "unverified", + "latestAvailable": false, + "modeLabel": "Unknown", + "patchLabel": "T7 Patch Scroptss/T7Patch", + "profile": "", + "state": "unverified", + "trustedExecutable": false + }, + "gameDetected": true, + "gameDir": "$ROOT/game", + "graphics": { + "displayMode": 1, + "drawFps": false, + "fov": 80, + "maxFps": 165, + "refreshRate": 60.0, + "renderResolution": 100, + "resolution": "1920x1080", + "vsync": true + }, + "launchProfiles": [ + { + "active": true, + "id": "default", + "installed": true, + "label": "Default (None)", + "option": "", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "offline", + "installed": true, + "label": "Play Offline", + "option": "+set fs_game offlinemp", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "all_around", + "installed": false, + "label": "All-around Enhancement Lite", + "option": "+set fs_game 2994481309", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "ultimate", + "installed": false, + "label": "Ultimate Experience Mod", + "option": "+set fs_game 2942053577", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "reforged", + "installed": false, + "label": "BO3 Reforged", + "option": "+set fs_game 3667377161", + "path": null, + "state": "Not Subscribed", + "subscribed": false + } + ], + "logPath": "$ROOT/data/PatchOpsIII.log", + "logs": [ + { + "category": "Success", + "line": "$TIMESTAMP - Success: Game directory set to $ROOT/game", + "message": "Game directory set to $ROOT/game" + } + ], + "maintenance": { + "logPayload": "PatchOpsIII v1.3.4 - Linux $KERNEL (x86_64) logs:\n```\n$TIMESTAMP - Success: Game directory set to $ROOT/game\n```", + "modFilesDir": "$ROOT/data/BO3 Mod Files" + }, + "mods": { + "dxvk": false, + "enhanced": false, + "t7Patch": false + }, + "platform": "Linux", + "presets": [ + "Smooth Preset", + "Low-Latency Preset" + ], + "qol": { + "allIntros": false, + "d3dcompiler": false, + "intro": false + }, + "releaseChannel": "stable", + "steamUserId": "76561198000000000", + "t7": { + "colorCode": "", + "confExists": false, + "friendsOnly": false, + "gamertag": "", + "installed": false, + "mode": "Custom", + "networkPassword": "", + "plainName": "" + } + } + } + ] + }, + "fixture": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "99e02132d604629c806d118cb06ecedc18295f28afa54906c43af1d730650210", + "size": 33, + "text": "{\n \"release_channel\": \"stable\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "name": "game_directory_validation_valid", + "operations": [ + { + "body": { + "path": "$ROOT/game" + }, + "method": "POST", + "path": "/api/game-directory" + } + ], + "runner": "direct endpoint functions", + "schemaVersion": 1, + "sourceHashes": { + "backend/api.py": "080be5c4a3b929134298179cd3f4d1558c288a50dcdb38978ada831925a57a46", + "bo3_enhanced.py": "32a670c8a63a64216ee5c46aa000d2e3840ca348ca0fd5031c81699252cf57b8", + "dxvk_manager.py": "8e150458bc7480ac89cd9e64c558ba438f61a09611bbf24b962cba02d7e4a5e5", + "t7_patch.py": "73e66fadd1c0775742b7bb6d9c6c73a3c9df8304d3117abd8ac768dfc68cc3ea", + "utils.py": "313069c3f70b92888239c9ef15ce2379713b029e019426e76b386f088b556564" + }, + "sourceOfTruth": "Python backend on main" +} diff --git a/native/core/tests/parity/launch_options_apply.json b/native/core/tests/parity/launch_options_apply.json new file mode 100644 index 0000000..abb5708 --- /dev/null +++ b/native/core/tests/parity/launch_options_apply.json @@ -0,0 +1,800 @@ +{ + "expected": { + "checkpoints": [ + { + "directories": [ + "data", + "data/backups", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 436, + "path": "data/PatchOpsIII.log", + "sha256": "5ee8d57f5546dd7782aa286df32a599208c4109b966f800c99d717489565325e", + "size": 205, + "text": "$TIMESTAMP - Success: Config backup created at $ROOT/data/backups/localconfig_backup.vdf\n$TIMESTAMP - Info: Setting launch options to: WINEDLLOVERRIDES=\"dsound=n,b\" %command% -novid +set fs_game offlinemp\n" + }, + { + "mode": 420, + "path": "data/backups/localconfig_backup.vdf", + "sha256": "0b29ee605d61e62a073f542a48a59f68274178d1b3c07318b738939682f636c8", + "size": 279, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"WINEDLLOVERRIDES=\\\"dsound=n,b\\\" %command% -novid +set fs_game oldmod\"\n }\n }\n }\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "952221cf8d0a15dfaf77b5a7f9950160bb80bec724405df1685e5100cfc52e55", + "size": 231, + "text": "\"UserLocalConfigStore\"\n{\n\t\"Software\"\n\t{\n\t\t\"Valve\"\n\t\t{\n\t\t\t\"Steam\"\n\t\t\t{\n\t\t\t\t\"apps\"\n\t\t\t\t{\n\t\t\t\t\t\"311210\"\n\t\t\t\t\t{\n\t\t\t\t\t\t\"LaunchOptions\" \"WINEDLLOVERRIDES=\\\"dsound=n,b\\\" %command% -novid +set fs_game offlinemp\"\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n" + } + ] + }, + { + "directories": [ + "data", + "data/backups", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 436, + "path": "data/PatchOpsIII.log", + "sha256": "95451fde46e60360d4f19d90ab4baa0fdbbee9c80009dfb9196dee25a742076b", + "size": 410, + "text": "$TIMESTAMP - Success: Config backup created at $ROOT/data/backups/localconfig_backup.vdf\n$TIMESTAMP - Info: Setting launch options to: WINEDLLOVERRIDES=\"dsound=n,b\" %command% -novid +set fs_game offlinemp\n$TIMESTAMP - Success: Config backup created at $ROOT/data/backups/localconfig_backup.vdf\n$TIMESTAMP - Info: Setting launch options to: WINEDLLOVERRIDES=\"dsound=n,b\" %command% -novid +set fs_game offlinemp\n" + }, + { + "mode": 420, + "path": "data/backups/localconfig_backup.vdf", + "sha256": "952221cf8d0a15dfaf77b5a7f9950160bb80bec724405df1685e5100cfc52e55", + "size": 231, + "text": "\"UserLocalConfigStore\"\n{\n\t\"Software\"\n\t{\n\t\t\"Valve\"\n\t\t{\n\t\t\t\"Steam\"\n\t\t\t{\n\t\t\t\t\"apps\"\n\t\t\t\t{\n\t\t\t\t\t\"311210\"\n\t\t\t\t\t{\n\t\t\t\t\t\t\"LaunchOptions\" \"WINEDLLOVERRIDES=\\\"dsound=n,b\\\" %command% -novid +set fs_game offlinemp\"\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "952221cf8d0a15dfaf77b5a7f9950160bb80bec724405df1685e5100cfc52e55", + "size": 231, + "text": "\"UserLocalConfigStore\"\n{\n\t\"Software\"\n\t{\n\t\t\"Valve\"\n\t\t{\n\t\t\t\"Steam\"\n\t\t\t{\n\t\t\t\t\"apps\"\n\t\t\t\t{\n\t\t\t\t\t\"311210\"\n\t\t\t\t\t{\n\t\t\t\t\t\t\"LaunchOptions\" \"WINEDLLOVERRIDES=\\\"dsound=n,b\\\" %command% -novid +set fs_game offlinemp\"\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n" + } + ] + } + ], + "filesystem": { + "directories": [ + "data", + "data/backups", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 436, + "path": "data/PatchOpsIII.log", + "sha256": "95451fde46e60360d4f19d90ab4baa0fdbbee9c80009dfb9196dee25a742076b", + "size": 410, + "text": "$TIMESTAMP - Success: Config backup created at $ROOT/data/backups/localconfig_backup.vdf\n$TIMESTAMP - Info: Setting launch options to: WINEDLLOVERRIDES=\"dsound=n,b\" %command% -novid +set fs_game offlinemp\n$TIMESTAMP - Success: Config backup created at $ROOT/data/backups/localconfig_backup.vdf\n$TIMESTAMP - Info: Setting launch options to: WINEDLLOVERRIDES=\"dsound=n,b\" %command% -novid +set fs_game offlinemp\n" + }, + { + "mode": 420, + "path": "data/backups/localconfig_backup.vdf", + "sha256": "952221cf8d0a15dfaf77b5a7f9950160bb80bec724405df1685e5100cfc52e55", + "size": 231, + "text": "\"UserLocalConfigStore\"\n{\n\t\"Software\"\n\t{\n\t\t\"Valve\"\n\t\t{\n\t\t\t\"Steam\"\n\t\t\t{\n\t\t\t\t\"apps\"\n\t\t\t\t{\n\t\t\t\t\t\"311210\"\n\t\t\t\t\t{\n\t\t\t\t\t\t\"LaunchOptions\" \"WINEDLLOVERRIDES=\\\"dsound=n,b\\\" %command% -novid +set fs_game offlinemp\"\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "952221cf8d0a15dfaf77b5a7f9950160bb80bec724405df1685e5100cfc52e55", + "size": 231, + "text": "\"UserLocalConfigStore\"\n{\n\t\"Software\"\n\t{\n\t\t\"Valve\"\n\t\t{\n\t\t\t\"Steam\"\n\t\t\t{\n\t\t\t\t\"apps\"\n\t\t\t\t{\n\t\t\t\t\t\"311210\"\n\t\t\t\t\t{\n\t\t\t\t\t\t\"LaunchOptions\" \"WINEDLLOVERRIDES=\\\"dsound=n,b\\\" %command% -novid +set fs_game offlinemp\"\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n" + } + ] + }, + "responses": [ + { + "ok": true, + "state": { + "activeLaunchProfile": "offline", + "advanced": { + "configReadonly": false, + "maxFrameLatency": 1, + "reduceCpu": false, + "smoothFramerate": false, + "unlockOptions": false, + "vramLimited": false, + "vramTarget": 100 + }, + "appVersion": "v1.3.4", + "configExists": true, + "currentLaunchOptions": "WINEDLLOVERRIDES=\"dsound=n,b\" %command% -novid +set fs_game offlinemp", + "dxvk": { + "confExists": false, + "installed": false, + "settings": { + "enableAsync": true, + "gplAsyncCache": true, + "hudEnabled": false, + "maxFrameLatency": 1, + "maxFrameRate": 0, + "numCompilerThreads": 0, + "tearFree": "True" + } + }, + "enhanced": { + "acknowledgedAt": null, + "backupStatus": "Not created", + "detectedAt": null, + "dumpSource": "", + "filesInstalled": 0, + "installed": false, + "launchOptionsActive": false + }, + "exeSwap": { + "activeBuildDate": "", + "activeBuildId": "Unverified", + "backupAvailable": false, + "compatibleActive": false, + "compatibleAvailable": false, + "compatibleBuildDate": "Mar 3, 2023", + "compatibleBuildId": "10650222", + "currentBuildDate": "Sep 10, 2026", + "currentBuildId": "24784313", + "displayLabel": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE. T7 installs will use T7 Patch Scroptss/T7Patch.", + "enhancedActive": false, + "enhancedAvailable": false, + "enhancedBuildDate": "", + "enhancedBuildId": "Enhanced", + "enhancedExeActive": false, + "executable": "$ROOT/game/BlackOpsIII.exe", + "executableHash": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "executableName": "BlackOpsIII.exe", + "integrityMessage": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE.", + "integrityStatus": "unverified", + "latestAvailable": false, + "modeLabel": "Unknown", + "patchLabel": "T7 Patch Scroptss/T7Patch", + "profile": "", + "state": "unverified", + "trustedExecutable": false + }, + "gameDetected": true, + "gameDir": "$ROOT/game", + "graphics": { + "displayMode": 1, + "drawFps": false, + "fov": 80, + "maxFps": 165, + "refreshRate": 60.0, + "renderResolution": 100, + "resolution": "1920x1080", + "vsync": true + }, + "launchProfiles": [ + { + "active": false, + "id": "default", + "installed": true, + "label": "Default (None)", + "option": "", + "state": "Ready", + "subscribed": true + }, + { + "active": true, + "id": "offline", + "installed": true, + "label": "Play Offline", + "option": "+set fs_game offlinemp", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "all_around", + "installed": false, + "label": "All-around Enhancement Lite", + "option": "+set fs_game 2994481309", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "ultimate", + "installed": false, + "label": "Ultimate Experience Mod", + "option": "+set fs_game 2942053577", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "reforged", + "installed": false, + "label": "BO3 Reforged", + "option": "+set fs_game 3667377161", + "path": null, + "state": "Not Subscribed", + "subscribed": false + } + ], + "logPath": "$ROOT/data/PatchOpsIII.log", + "logs": [ + { + "category": "Success", + "line": "$TIMESTAMP - Success: Config backup created at $ROOT/data/backups/localconfig_backup.vdf", + "message": "Config backup created at $ROOT/data/backups/localconfig_backup.vdf" + }, + { + "category": "Info", + "line": "$TIMESTAMP - Info: Setting launch options to: WINEDLLOVERRIDES=\"dsound=n,b\" %command% -novid +set fs_game offlinemp", + "message": "Setting launch options to: WINEDLLOVERRIDES=\"dsound=n,b\" %command% -novid +set fs_game offlinemp" + } + ], + "maintenance": { + "logPayload": "PatchOpsIII v1.3.4 - Linux $KERNEL (x86_64) logs:\n```\n$TIMESTAMP - Success: Config backup created at $ROOT/data/backups/localconfig_backup.vdf\n$TIMESTAMP - Info: Setting launch options to: WINEDLLOVERRIDES=\"dsound=n,b\" %command% -novid +set fs_game offlinemp\n```", + "modFilesDir": "$ROOT/data/BO3 Mod Files" + }, + "mods": { + "dxvk": false, + "enhanced": false, + "t7Patch": false + }, + "platform": "Linux", + "presets": [ + "Smooth Preset", + "Low-Latency Preset" + ], + "qol": { + "allIntros": false, + "d3dcompiler": false, + "intro": false + }, + "releaseChannel": "stable", + "steamUserId": "76561198000000000", + "t7": { + "colorCode": "", + "confExists": false, + "friendsOnly": false, + "gamertag": "", + "installed": false, + "mode": "Custom", + "networkPassword": "", + "plainName": "" + } + } + }, + { + "ok": true, + "state": { + "activeLaunchProfile": "offline", + "advanced": { + "configReadonly": false, + "maxFrameLatency": 1, + "reduceCpu": false, + "smoothFramerate": false, + "unlockOptions": false, + "vramLimited": false, + "vramTarget": 100 + }, + "appVersion": "v1.3.4", + "configExists": true, + "currentLaunchOptions": "WINEDLLOVERRIDES=\"dsound=n,b\" %command% -novid +set fs_game offlinemp", + "dxvk": { + "confExists": false, + "installed": false, + "settings": { + "enableAsync": true, + "gplAsyncCache": true, + "hudEnabled": false, + "maxFrameLatency": 1, + "maxFrameRate": 0, + "numCompilerThreads": 0, + "tearFree": "True" + } + }, + "enhanced": { + "acknowledgedAt": null, + "backupStatus": "Not created", + "detectedAt": null, + "dumpSource": "", + "filesInstalled": 0, + "installed": false, + "launchOptionsActive": false + }, + "exeSwap": { + "activeBuildDate": "", + "activeBuildId": "Unverified", + "backupAvailable": false, + "compatibleActive": false, + "compatibleAvailable": false, + "compatibleBuildDate": "Mar 3, 2023", + "compatibleBuildId": "10650222", + "currentBuildDate": "Sep 10, 2026", + "currentBuildId": "24784313", + "displayLabel": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE. T7 installs will use T7 Patch Scroptss/T7Patch.", + "enhancedActive": false, + "enhancedAvailable": false, + "enhancedBuildDate": "", + "enhancedBuildId": "Enhanced", + "enhancedExeActive": false, + "executable": "$ROOT/game/BlackOpsIII.exe", + "executableHash": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "executableName": "BlackOpsIII.exe", + "integrityMessage": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE.", + "integrityStatus": "unverified", + "latestAvailable": false, + "modeLabel": "Unknown", + "patchLabel": "T7 Patch Scroptss/T7Patch", + "profile": "", + "state": "unverified", + "trustedExecutable": false + }, + "gameDetected": true, + "gameDir": "$ROOT/game", + "graphics": { + "displayMode": 1, + "drawFps": false, + "fov": 80, + "maxFps": 165, + "refreshRate": 60.0, + "renderResolution": 100, + "resolution": "1920x1080", + "vsync": true + }, + "launchProfiles": [ + { + "active": false, + "id": "default", + "installed": true, + "label": "Default (None)", + "option": "", + "state": "Ready", + "subscribed": true + }, + { + "active": true, + "id": "offline", + "installed": true, + "label": "Play Offline", + "option": "+set fs_game offlinemp", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "all_around", + "installed": false, + "label": "All-around Enhancement Lite", + "option": "+set fs_game 2994481309", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "ultimate", + "installed": false, + "label": "Ultimate Experience Mod", + "option": "+set fs_game 2942053577", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "reforged", + "installed": false, + "label": "BO3 Reforged", + "option": "+set fs_game 3667377161", + "path": null, + "state": "Not Subscribed", + "subscribed": false + } + ], + "logPath": "$ROOT/data/PatchOpsIII.log", + "logs": [ + { + "category": "Success", + "line": "$TIMESTAMP - Success: Config backup created at $ROOT/data/backups/localconfig_backup.vdf", + "message": "Config backup created at $ROOT/data/backups/localconfig_backup.vdf" + }, + { + "category": "Info", + "line": "$TIMESTAMP - Info: Setting launch options to: WINEDLLOVERRIDES=\"dsound=n,b\" %command% -novid +set fs_game offlinemp", + "message": "Setting launch options to: WINEDLLOVERRIDES=\"dsound=n,b\" %command% -novid +set fs_game offlinemp" + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: Config backup created at $ROOT/data/backups/localconfig_backup.vdf", + "message": "Config backup created at $ROOT/data/backups/localconfig_backup.vdf" + }, + { + "category": "Info", + "line": "$TIMESTAMP - Info: Setting launch options to: WINEDLLOVERRIDES=\"dsound=n,b\" %command% -novid +set fs_game offlinemp", + "message": "Setting launch options to: WINEDLLOVERRIDES=\"dsound=n,b\" %command% -novid +set fs_game offlinemp" + } + ], + "maintenance": { + "logPayload": "PatchOpsIII v1.3.4 - Linux $KERNEL (x86_64) logs:\n```\n$TIMESTAMP - Success: Config backup created at $ROOT/data/backups/localconfig_backup.vdf\n$TIMESTAMP - Info: Setting launch options to: WINEDLLOVERRIDES=\"dsound=n,b\" %command% -novid +set fs_game offlinemp\n$TIMESTAMP - Success: Config backup created at $ROOT/data/backups/localconfig_backup.vdf\n$TIMESTAMP - Info: Setting launch options to: WINEDLLOVERRIDES=\"dsound=n,b\" %command% -novid +set fs_game offlinemp\n```", + "modFilesDir": "$ROOT/data/BO3 Mod Files" + }, + "mods": { + "dxvk": false, + "enhanced": false, + "t7Patch": false + }, + "platform": "Linux", + "presets": [ + "Smooth Preset", + "Low-Latency Preset" + ], + "qol": { + "allIntros": false, + "d3dcompiler": false, + "intro": false + }, + "releaseChannel": "stable", + "steamUserId": "76561198000000000", + "t7": { + "colorCode": "", + "confExists": false, + "friendsOnly": false, + "gamertag": "", + "installed": false, + "mode": "Custom", + "networkPassword": "", + "plainName": "" + } + } + } + ] + }, + "fixture": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "0b29ee605d61e62a073f542a48a59f68274178d1b3c07318b738939682f636c8", + "size": 279, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"WINEDLLOVERRIDES=\\\"dsound=n,b\\\" %command% -novid +set fs_game oldmod\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "name": "launch_options_apply", + "operations": [ + { + "body": { + "options": "+set fs_game offlinemp", + "preserve_fs_game": false + }, + "method": "POST", + "path": "/api/launch-options" + }, + { + "body": { + "options": "", + "preserve_fs_game": true + }, + "method": "POST", + "path": "/api/launch-options" + } + ], + "runner": "direct endpoint functions", + "schemaVersion": 1, + "sourceHashes": { + "backend/api.py": "080be5c4a3b929134298179cd3f4d1558c288a50dcdb38978ada831925a57a46", + "bo3_enhanced.py": "32a670c8a63a64216ee5c46aa000d2e3840ca348ca0fd5031c81699252cf57b8", + "dxvk_manager.py": "8e150458bc7480ac89cd9e64c558ba438f61a09611bbf24b962cba02d7e4a5e5", + "t7_patch.py": "73e66fadd1c0775742b7bb6d9c6c73a3c9df8304d3117abd8ac768dfc68cc3ea", + "utils.py": "313069c3f70b92888239c9ef15ce2379713b029e019426e76b386f088b556564" + }, + "sourceOfTruth": "Python backend on main" +} diff --git a/native/core/tests/parity/launch_options_unsupported.json b/native/core/tests/parity/launch_options_unsupported.json new file mode 100644 index 0000000..ac8ae90 --- /dev/null +++ b/native/core/tests/parity/launch_options_unsupported.json @@ -0,0 +1,482 @@ +{ + "expected": { + "checkpoints": [ + { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 436, + "path": "data/PatchOpsIII.log", + "sha256": "beb3040d711e2d595fbd43b6bf846eaa569bc71117cf351a59d8169d18d0a599", + "size": 49, + "text": "$TIMESTAMP - Warning: Unsupported launch option.\n" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + } + ], + "filesystem": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 436, + "path": "data/PatchOpsIII.log", + "sha256": "beb3040d711e2d595fbd43b6bf846eaa569bc71117cf351a59d8169d18d0a599", + "size": 49, + "text": "$TIMESTAMP - Warning: Unsupported launch option.\n" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "responses": [ + { + "error": "Unsupported launch option.", + "ok": false, + "state": { + "activeLaunchProfile": "default", + "advanced": { + "configReadonly": false, + "maxFrameLatency": 1, + "reduceCpu": false, + "smoothFramerate": false, + "unlockOptions": false, + "vramLimited": false, + "vramTarget": 100 + }, + "appVersion": "v1.3.4", + "configExists": true, + "currentLaunchOptions": "", + "dxvk": { + "confExists": false, + "installed": false, + "settings": { + "enableAsync": true, + "gplAsyncCache": true, + "hudEnabled": false, + "maxFrameLatency": 1, + "maxFrameRate": 0, + "numCompilerThreads": 0, + "tearFree": "True" + } + }, + "enhanced": { + "acknowledgedAt": null, + "backupStatus": "Not created", + "detectedAt": null, + "dumpSource": "", + "filesInstalled": 0, + "installed": false, + "launchOptionsActive": false + }, + "exeSwap": { + "activeBuildDate": "", + "activeBuildId": "Unverified", + "backupAvailable": false, + "compatibleActive": false, + "compatibleAvailable": false, + "compatibleBuildDate": "Mar 3, 2023", + "compatibleBuildId": "10650222", + "currentBuildDate": "Sep 10, 2026", + "currentBuildId": "24784313", + "displayLabel": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE. T7 installs will use T7 Patch Scroptss/T7Patch.", + "enhancedActive": false, + "enhancedAvailable": false, + "enhancedBuildDate": "", + "enhancedBuildId": "Enhanced", + "enhancedExeActive": false, + "executable": "$ROOT/game/BlackOpsIII.exe", + "executableHash": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "executableName": "BlackOpsIII.exe", + "integrityMessage": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE.", + "integrityStatus": "unverified", + "latestAvailable": false, + "modeLabel": "Unknown", + "patchLabel": "T7 Patch Scroptss/T7Patch", + "profile": "", + "state": "unverified", + "trustedExecutable": false + }, + "gameDetected": true, + "gameDir": "$ROOT/game", + "graphics": { + "displayMode": 1, + "drawFps": false, + "fov": 80, + "maxFps": 165, + "refreshRate": 60.0, + "renderResolution": 100, + "resolution": "1920x1080", + "vsync": true + }, + "launchProfiles": [ + { + "active": true, + "id": "default", + "installed": true, + "label": "Default (None)", + "option": "", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "offline", + "installed": true, + "label": "Play Offline", + "option": "+set fs_game offlinemp", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "all_around", + "installed": false, + "label": "All-around Enhancement Lite", + "option": "+set fs_game 2994481309", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "ultimate", + "installed": false, + "label": "Ultimate Experience Mod", + "option": "+set fs_game 2942053577", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "reforged", + "installed": false, + "label": "BO3 Reforged", + "option": "+set fs_game 3667377161", + "path": null, + "state": "Not Subscribed", + "subscribed": false + } + ], + "logPath": "$ROOT/data/PatchOpsIII.log", + "logs": [ + { + "category": "Warning", + "line": "$TIMESTAMP - Warning: Unsupported launch option.", + "message": "Unsupported launch option." + } + ], + "maintenance": { + "logPayload": "PatchOpsIII v1.3.4 - Linux $KERNEL (x86_64) logs:\n```\n$TIMESTAMP - Warning: Unsupported launch option.\n```", + "modFilesDir": "$ROOT/data/BO3 Mod Files" + }, + "mods": { + "dxvk": false, + "enhanced": false, + "t7Patch": false + }, + "platform": "Linux", + "presets": [ + "Smooth Preset", + "Low-Latency Preset" + ], + "qol": { + "allIntros": false, + "d3dcompiler": false, + "intro": false + }, + "releaseChannel": "stable", + "steamUserId": "76561198000000000", + "t7": { + "colorCode": "", + "confExists": false, + "friendsOnly": false, + "gamertag": "", + "installed": false, + "mode": "Custom", + "networkPassword": "", + "plainName": "" + } + } + } + ] + }, + "fixture": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "name": "launch_options_unsupported", + "operations": [ + { + "body": { + "options": "-bad" + }, + "method": "POST", + "path": "/api/launch-options" + } + ], + "runner": "direct endpoint functions", + "schemaVersion": 1, + "sourceHashes": { + "backend/api.py": "080be5c4a3b929134298179cd3f4d1558c288a50dcdb38978ada831925a57a46", + "bo3_enhanced.py": "32a670c8a63a64216ee5c46aa000d2e3840ca348ca0fd5031c81699252cf57b8", + "dxvk_manager.py": "8e150458bc7480ac89cd9e64c558ba438f61a09611bbf24b962cba02d7e4a5e5", + "t7_patch.py": "73e66fadd1c0775742b7bb6d9c6c73a3c9df8304d3117abd8ac768dfc68cc3ea", + "utils.py": "313069c3f70b92888239c9ef15ce2379713b029e019426e76b386f088b556564" + }, + "sourceOfTruth": "Python backend on main" +} diff --git a/native/core/tests/parity/qol_restore_legacy.json b/native/core/tests/parity/qol_restore_legacy.json new file mode 100644 index 0000000..ede4d1b --- /dev/null +++ b/native/core/tests/parity/qol_restore_legacy.json @@ -0,0 +1,1042 @@ +{ + "expected": { + "checkpoints": [ + { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 436, + "path": "data/PatchOpsIII.log", + "sha256": "062c04e8bec5131e82db06546ef03acd6ea19d26c9227f4ae4c277b8413e0533", + "size": 44, + "text": "$TIMESTAMP - Success: Intro video restored.\n" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll.bak", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv.bak", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 436, + "path": "data/PatchOpsIII.log", + "sha256": "c89497a06934019d2c1d332cc92208512095c0edf17c2ee9e52d0b98d63d36e7", + "size": 95, + "text": "$TIMESTAMP - Success: Intro video restored.\n$TIMESTAMP - Success: Restored d3dcompiler_46.dll.\n" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv.bak", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 436, + "path": "data/PatchOpsIII.log", + "sha256": "cc0159a731f7ad6967c5d96d394a221966ab8b21f867f24285e55dc5ec188b73", + "size": 140, + "text": "$TIMESTAMP - Success: Intro video restored.\n$TIMESTAMP - Success: Restored d3dcompiler_46.dll.\n$TIMESTAMP - Success: Intro videos restored.\n" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + } + ], + "filesystem": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 436, + "path": "data/PatchOpsIII.log", + "sha256": "cc0159a731f7ad6967c5d96d394a221966ab8b21f867f24285e55dc5ec188b73", + "size": 140, + "text": "$TIMESTAMP - Success: Intro video restored.\n$TIMESTAMP - Success: Restored d3dcompiler_46.dll.\n$TIMESTAMP - Success: Intro videos restored.\n" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "responses": [ + { + "ok": true, + "state": { + "activeLaunchProfile": "default", + "advanced": { + "configReadonly": false, + "maxFrameLatency": 1, + "reduceCpu": false, + "smoothFramerate": false, + "unlockOptions": false, + "vramLimited": false, + "vramTarget": 100 + }, + "appVersion": "v1.3.4", + "configExists": true, + "currentLaunchOptions": "", + "dxvk": { + "confExists": false, + "installed": false, + "settings": { + "enableAsync": true, + "gplAsyncCache": true, + "hudEnabled": false, + "maxFrameLatency": 1, + "maxFrameRate": 0, + "numCompilerThreads": 0, + "tearFree": "True" + } + }, + "enhanced": { + "acknowledgedAt": null, + "backupStatus": "Not created", + "detectedAt": null, + "dumpSource": "", + "filesInstalled": 0, + "installed": false, + "launchOptionsActive": false + }, + "exeSwap": { + "activeBuildDate": "", + "activeBuildId": "Unverified", + "backupAvailable": false, + "compatibleActive": false, + "compatibleAvailable": false, + "compatibleBuildDate": "Mar 3, 2023", + "compatibleBuildId": "10650222", + "currentBuildDate": "Sep 10, 2026", + "currentBuildId": "24784313", + "displayLabel": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE. T7 installs will use T7 Patch Scroptss/T7Patch.", + "enhancedActive": false, + "enhancedAvailable": false, + "enhancedBuildDate": "", + "enhancedBuildId": "Enhanced", + "enhancedExeActive": false, + "executable": "$ROOT/game/BlackOpsIII.exe", + "executableHash": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "executableName": "BlackOpsIII.exe", + "integrityMessage": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE.", + "integrityStatus": "unverified", + "latestAvailable": false, + "modeLabel": "Unknown", + "patchLabel": "T7 Patch Scroptss/T7Patch", + "profile": "", + "state": "unverified", + "trustedExecutable": false + }, + "gameDetected": true, + "gameDir": "$ROOT/game", + "graphics": { + "displayMode": 1, + "drawFps": false, + "fov": 80, + "maxFps": 165, + "refreshRate": 60.0, + "renderResolution": 100, + "resolution": "1920x1080", + "vsync": true + }, + "launchProfiles": [ + { + "active": true, + "id": "default", + "installed": true, + "label": "Default (None)", + "option": "", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "offline", + "installed": true, + "label": "Play Offline", + "option": "+set fs_game offlinemp", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "all_around", + "installed": false, + "label": "All-around Enhancement Lite", + "option": "+set fs_game 2994481309", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "ultimate", + "installed": false, + "label": "Ultimate Experience Mod", + "option": "+set fs_game 2942053577", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "reforged", + "installed": false, + "label": "BO3 Reforged", + "option": "+set fs_game 3667377161", + "path": null, + "state": "Not Subscribed", + "subscribed": false + } + ], + "logPath": "$ROOT/data/PatchOpsIII.log", + "logs": [ + { + "category": "Success", + "line": "$TIMESTAMP - Success: Intro video restored.", + "message": "Intro video restored." + } + ], + "maintenance": { + "logPayload": "PatchOpsIII v1.3.4 - Linux $KERNEL (x86_64) logs:\n```\n$TIMESTAMP - Success: Intro video restored.\n```", + "modFilesDir": "$ROOT/data/BO3 Mod Files" + }, + "mods": { + "dxvk": false, + "enhanced": false, + "t7Patch": false + }, + "platform": "Linux", + "presets": [ + "Smooth Preset", + "Low-Latency Preset" + ], + "qol": { + "allIntros": false, + "d3dcompiler": true, + "intro": false + }, + "releaseChannel": "stable", + "steamUserId": "76561198000000000", + "t7": { + "colorCode": "", + "confExists": false, + "friendsOnly": false, + "gamertag": "", + "installed": false, + "mode": "Custom", + "networkPassword": "", + "plainName": "" + } + } + }, + { + "ok": true, + "state": { + "activeLaunchProfile": "default", + "advanced": { + "configReadonly": false, + "maxFrameLatency": 1, + "reduceCpu": false, + "smoothFramerate": false, + "unlockOptions": false, + "vramLimited": false, + "vramTarget": 100 + }, + "appVersion": "v1.3.4", + "configExists": true, + "currentLaunchOptions": "", + "dxvk": { + "confExists": false, + "installed": false, + "settings": { + "enableAsync": true, + "gplAsyncCache": true, + "hudEnabled": false, + "maxFrameLatency": 1, + "maxFrameRate": 0, + "numCompilerThreads": 0, + "tearFree": "True" + } + }, + "enhanced": { + "acknowledgedAt": null, + "backupStatus": "Not created", + "detectedAt": null, + "dumpSource": "", + "filesInstalled": 0, + "installed": false, + "launchOptionsActive": false + }, + "exeSwap": { + "activeBuildDate": "", + "activeBuildId": "Unverified", + "backupAvailable": false, + "compatibleActive": false, + "compatibleAvailable": false, + "compatibleBuildDate": "Mar 3, 2023", + "compatibleBuildId": "10650222", + "currentBuildDate": "Sep 10, 2026", + "currentBuildId": "24784313", + "displayLabel": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE. T7 installs will use T7 Patch Scroptss/T7Patch.", + "enhancedActive": false, + "enhancedAvailable": false, + "enhancedBuildDate": "", + "enhancedBuildId": "Enhanced", + "enhancedExeActive": false, + "executable": "$ROOT/game/BlackOpsIII.exe", + "executableHash": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "executableName": "BlackOpsIII.exe", + "integrityMessage": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE.", + "integrityStatus": "unverified", + "latestAvailable": false, + "modeLabel": "Unknown", + "patchLabel": "T7 Patch Scroptss/T7Patch", + "profile": "", + "state": "unverified", + "trustedExecutable": false + }, + "gameDetected": true, + "gameDir": "$ROOT/game", + "graphics": { + "displayMode": 1, + "drawFps": false, + "fov": 80, + "maxFps": 165, + "refreshRate": 60.0, + "renderResolution": 100, + "resolution": "1920x1080", + "vsync": true + }, + "launchProfiles": [ + { + "active": true, + "id": "default", + "installed": true, + "label": "Default (None)", + "option": "", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "offline", + "installed": true, + "label": "Play Offline", + "option": "+set fs_game offlinemp", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "all_around", + "installed": false, + "label": "All-around Enhancement Lite", + "option": "+set fs_game 2994481309", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "ultimate", + "installed": false, + "label": "Ultimate Experience Mod", + "option": "+set fs_game 2942053577", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "reforged", + "installed": false, + "label": "BO3 Reforged", + "option": "+set fs_game 3667377161", + "path": null, + "state": "Not Subscribed", + "subscribed": false + } + ], + "logPath": "$ROOT/data/PatchOpsIII.log", + "logs": [ + { + "category": "Success", + "line": "$TIMESTAMP - Success: Intro video restored.", + "message": "Intro video restored." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: Restored d3dcompiler_46.dll.", + "message": "Restored d3dcompiler_46.dll." + } + ], + "maintenance": { + "logPayload": "PatchOpsIII v1.3.4 - Linux $KERNEL (x86_64) logs:\n```\n$TIMESTAMP - Success: Intro video restored.\n$TIMESTAMP - Success: Restored d3dcompiler_46.dll.\n```", + "modFilesDir": "$ROOT/data/BO3 Mod Files" + }, + "mods": { + "dxvk": false, + "enhanced": false, + "t7Patch": false + }, + "platform": "Linux", + "presets": [ + "Smooth Preset", + "Low-Latency Preset" + ], + "qol": { + "allIntros": false, + "d3dcompiler": false, + "intro": false + }, + "releaseChannel": "stable", + "steamUserId": "76561198000000000", + "t7": { + "colorCode": "", + "confExists": false, + "friendsOnly": false, + "gamertag": "", + "installed": false, + "mode": "Custom", + "networkPassword": "", + "plainName": "" + } + } + }, + { + "ok": true, + "state": { + "activeLaunchProfile": "default", + "advanced": { + "configReadonly": false, + "maxFrameLatency": 1, + "reduceCpu": false, + "smoothFramerate": false, + "unlockOptions": false, + "vramLimited": false, + "vramTarget": 100 + }, + "appVersion": "v1.3.4", + "configExists": true, + "currentLaunchOptions": "", + "dxvk": { + "confExists": false, + "installed": false, + "settings": { + "enableAsync": true, + "gplAsyncCache": true, + "hudEnabled": false, + "maxFrameLatency": 1, + "maxFrameRate": 0, + "numCompilerThreads": 0, + "tearFree": "True" + } + }, + "enhanced": { + "acknowledgedAt": null, + "backupStatus": "Not created", + "detectedAt": null, + "dumpSource": "", + "filesInstalled": 0, + "installed": false, + "launchOptionsActive": false + }, + "exeSwap": { + "activeBuildDate": "", + "activeBuildId": "Unverified", + "backupAvailable": false, + "compatibleActive": false, + "compatibleAvailable": false, + "compatibleBuildDate": "Mar 3, 2023", + "compatibleBuildId": "10650222", + "currentBuildDate": "Sep 10, 2026", + "currentBuildId": "24784313", + "displayLabel": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE. T7 installs will use T7 Patch Scroptss/T7Patch.", + "enhancedActive": false, + "enhancedAvailable": false, + "enhancedBuildDate": "", + "enhancedBuildId": "Enhanced", + "enhancedExeActive": false, + "executable": "$ROOT/game/BlackOpsIII.exe", + "executableHash": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "executableName": "BlackOpsIII.exe", + "integrityMessage": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE.", + "integrityStatus": "unverified", + "latestAvailable": false, + "modeLabel": "Unknown", + "patchLabel": "T7 Patch Scroptss/T7Patch", + "profile": "", + "state": "unverified", + "trustedExecutable": false + }, + "gameDetected": true, + "gameDir": "$ROOT/game", + "graphics": { + "displayMode": 1, + "drawFps": false, + "fov": 80, + "maxFps": 165, + "refreshRate": 60.0, + "renderResolution": 100, + "resolution": "1920x1080", + "vsync": true + }, + "launchProfiles": [ + { + "active": true, + "id": "default", + "installed": true, + "label": "Default (None)", + "option": "", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "offline", + "installed": true, + "label": "Play Offline", + "option": "+set fs_game offlinemp", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "all_around", + "installed": false, + "label": "All-around Enhancement Lite", + "option": "+set fs_game 2994481309", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "ultimate", + "installed": false, + "label": "Ultimate Experience Mod", + "option": "+set fs_game 2942053577", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "reforged", + "installed": false, + "label": "BO3 Reforged", + "option": "+set fs_game 3667377161", + "path": null, + "state": "Not Subscribed", + "subscribed": false + } + ], + "logPath": "$ROOT/data/PatchOpsIII.log", + "logs": [ + { + "category": "Success", + "line": "$TIMESTAMP - Success: Intro video restored.", + "message": "Intro video restored." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: Restored d3dcompiler_46.dll.", + "message": "Restored d3dcompiler_46.dll." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: Intro videos restored.", + "message": "Intro videos restored." + } + ], + "maintenance": { + "logPayload": "PatchOpsIII v1.3.4 - Linux $KERNEL (x86_64) logs:\n```\n$TIMESTAMP - Success: Intro video restored.\n$TIMESTAMP - Success: Restored d3dcompiler_46.dll.\n$TIMESTAMP - Success: Intro videos restored.\n```", + "modFilesDir": "$ROOT/data/BO3 Mod Files" + }, + "mods": { + "dxvk": false, + "enhanced": false, + "t7Patch": false + }, + "platform": "Linux", + "presets": [ + "Smooth Preset", + "Low-Latency Preset" + ], + "qol": { + "allIntros": false, + "d3dcompiler": false, + "intro": false + }, + "releaseChannel": "stable", + "steamUserId": "76561198000000000", + "t7": { + "colorCode": "", + "confExists": false, + "friendsOnly": false, + "gamertag": "", + "installed": false, + "mode": "Custom", + "networkPassword": "", + "plainName": "" + } + } + } + ] + }, + "fixture": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll.bak", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv.bak", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv.bak", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "name": "qol_restore_legacy", + "operations": [ + { + "body": { + "enabled": false + }, + "method": "POST", + "path": "/api/intro-skip" + }, + { + "body": { + "enabled": false + }, + "method": "POST", + "path": "/api/d3dcompiler" + }, + { + "body": { + "enabled": false + }, + "method": "POST", + "path": "/api/all-intros-skip" + } + ], + "runner": "direct endpoint functions", + "schemaVersion": 1, + "sourceHashes": { + "backend/api.py": "080be5c4a3b929134298179cd3f4d1558c288a50dcdb38978ada831925a57a46", + "bo3_enhanced.py": "32a670c8a63a64216ee5c46aa000d2e3840ca348ca0fd5031c81699252cf57b8", + "dxvk_manager.py": "8e150458bc7480ac89cd9e64c558ba438f61a09611bbf24b962cba02d7e4a5e5", + "t7_patch.py": "73e66fadd1c0775742b7bb6d9c6c73a3c9df8304d3117abd8ac768dfc68cc3ea", + "utils.py": "313069c3f70b92888239c9ef15ce2379713b029e019426e76b386f088b556564" + }, + "sourceOfTruth": "Python backend on main" +} diff --git a/native/core/tests/parity/qol_toggle_roundtrip.json b/native/core/tests/parity/qol_toggle_roundtrip.json new file mode 100644 index 0000000..82bd8bd --- /dev/null +++ b/native/core/tests/parity/qol_toggle_roundtrip.json @@ -0,0 +1,2845 @@ +{ + "expected": { + "checkpoints": [ + { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 436, + "path": "data/PatchOpsIII.log", + "sha256": "680528705d5426430bdafe3c54deafbe9d2548a015247eab02ad7b87c8cb9df6", + "size": 43, + "text": "$TIMESTAMP - Success: Intro video skipped.\n" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv.patchops.bak", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 436, + "path": "data/PatchOpsIII.log", + "sha256": "ce266d1b7095c11f4e7ac3f9a40ff619dfb73ac05b2017981aa5789443e047c5", + "size": 114, + "text": "$TIMESTAMP - Success: Intro video skipped.\n$TIMESTAMP - Success: Renamed d3dcompiler_46.dll to reduce stuttering.\n" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll.patchops.bak", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv.patchops.bak", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 436, + "path": "data/PatchOpsIII.log", + "sha256": "835983fb48b9162bab97965ad1c70eb442647d6684420ebbf17cdb376a895734", + "size": 162, + "text": "$TIMESTAMP - Success: Intro video skipped.\n$TIMESTAMP - Success: Renamed d3dcompiler_46.dll to reduce stuttering.\n$TIMESTAMP - Success: All intro videos skipped.\n" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll.patchops.bak", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv.patchops.bak", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv.patchops.bak", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 436, + "path": "data/PatchOpsIII.log", + "sha256": "982f487ba00ba1dd90c51fc7fc35fbae3980703344cb540ce09ca6e6ae7c67fd", + "size": 205, + "text": "$TIMESTAMP - Success: Intro video skipped.\n$TIMESTAMP - Success: Renamed d3dcompiler_46.dll to reduce stuttering.\n$TIMESTAMP - Success: All intro videos skipped.\n$TIMESTAMP - Success: Intro video skipped.\n" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll.patchops.bak", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv.patchops.bak", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv.patchops.bak", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 436, + "path": "data/PatchOpsIII.log", + "sha256": "ec98ed509c87a1b4400764cb10165b62ded7945305db0c7cbbf73b4b154d0b73", + "size": 261, + "text": "$TIMESTAMP - Success: Intro video skipped.\n$TIMESTAMP - Success: Renamed d3dcompiler_46.dll to reduce stuttering.\n$TIMESTAMP - Success: All intro videos skipped.\n$TIMESTAMP - Success: Intro video skipped.\n$TIMESTAMP - Success: Already using latest d3dcompiler.\n" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll.patchops.bak", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv.patchops.bak", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv.patchops.bak", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 436, + "path": "data/PatchOpsIII.log", + "sha256": "1f7e01f0fb2633fb2cbc948432bd1678a14f3ccc9facbe18d7551ede78ce9cb5", + "size": 318, + "text": "$TIMESTAMP - Success: Intro video skipped.\n$TIMESTAMP - Success: Renamed d3dcompiler_46.dll to reduce stuttering.\n$TIMESTAMP - Success: All intro videos skipped.\n$TIMESTAMP - Success: Intro video skipped.\n$TIMESTAMP - Success: Already using latest d3dcompiler.\n$TIMESTAMP - Success: Intro videos were already skipped.\n" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll.patchops.bak", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv.patchops.bak", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv.patchops.bak", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 436, + "path": "data/PatchOpsIII.log", + "sha256": "90bf0ac9bf3012850da5329873a982c952630cb789134e537d6dd9733db097fa", + "size": 362, + "text": "$TIMESTAMP - Success: Intro video skipped.\n$TIMESTAMP - Success: Renamed d3dcompiler_46.dll to reduce stuttering.\n$TIMESTAMP - Success: All intro videos skipped.\n$TIMESTAMP - Success: Intro video skipped.\n$TIMESTAMP - Success: Already using latest d3dcompiler.\n$TIMESTAMP - Success: Intro videos were already skipped.\n$TIMESTAMP - Success: Intro video restored.\n" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll.patchops.bak", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv.patchops.bak", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 436, + "path": "data/PatchOpsIII.log", + "sha256": "542a71b70954e8cfc9482125557264817e194286f71e2c7e7b7cb8b4e0ff40a6", + "size": 413, + "text": "$TIMESTAMP - Success: Intro video skipped.\n$TIMESTAMP - Success: Renamed d3dcompiler_46.dll to reduce stuttering.\n$TIMESTAMP - Success: All intro videos skipped.\n$TIMESTAMP - Success: Intro video skipped.\n$TIMESTAMP - Success: Already using latest d3dcompiler.\n$TIMESTAMP - Success: Intro videos were already skipped.\n$TIMESTAMP - Success: Intro video restored.\n$TIMESTAMP - Success: Restored d3dcompiler_46.dll.\n" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv.patchops.bak", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 436, + "path": "data/PatchOpsIII.log", + "sha256": "3f0851b07e23f0d5cff15aa9905bc22775bd53b83f5ca1727ed90937d05f4931", + "size": 458, + "text": "$TIMESTAMP - Success: Intro video skipped.\n$TIMESTAMP - Success: Renamed d3dcompiler_46.dll to reduce stuttering.\n$TIMESTAMP - Success: All intro videos skipped.\n$TIMESTAMP - Success: Intro video skipped.\n$TIMESTAMP - Success: Already using latest d3dcompiler.\n$TIMESTAMP - Success: Intro videos were already skipped.\n$TIMESTAMP - Success: Intro video restored.\n$TIMESTAMP - Success: Restored d3dcompiler_46.dll.\n$TIMESTAMP - Success: Intro videos restored.\n" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + } + ], + "filesystem": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 436, + "path": "data/PatchOpsIII.log", + "sha256": "3f0851b07e23f0d5cff15aa9905bc22775bd53b83f5ca1727ed90937d05f4931", + "size": 458, + "text": "$TIMESTAMP - Success: Intro video skipped.\n$TIMESTAMP - Success: Renamed d3dcompiler_46.dll to reduce stuttering.\n$TIMESTAMP - Success: All intro videos skipped.\n$TIMESTAMP - Success: Intro video skipped.\n$TIMESTAMP - Success: Already using latest d3dcompiler.\n$TIMESTAMP - Success: Intro videos were already skipped.\n$TIMESTAMP - Success: Intro video restored.\n$TIMESTAMP - Success: Restored d3dcompiler_46.dll.\n$TIMESTAMP - Success: Intro videos restored.\n" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "responses": [ + { + "ok": true, + "state": { + "activeLaunchProfile": "default", + "advanced": { + "configReadonly": false, + "maxFrameLatency": 1, + "reduceCpu": false, + "smoothFramerate": false, + "unlockOptions": false, + "vramLimited": false, + "vramTarget": 100 + }, + "appVersion": "v1.3.4", + "configExists": true, + "currentLaunchOptions": "", + "dxvk": { + "confExists": false, + "installed": false, + "settings": { + "enableAsync": true, + "gplAsyncCache": true, + "hudEnabled": false, + "maxFrameLatency": 1, + "maxFrameRate": 0, + "numCompilerThreads": 0, + "tearFree": "True" + } + }, + "enhanced": { + "acknowledgedAt": null, + "backupStatus": "Not created", + "detectedAt": null, + "dumpSource": "", + "filesInstalled": 0, + "installed": false, + "launchOptionsActive": false + }, + "exeSwap": { + "activeBuildDate": "", + "activeBuildId": "Unverified", + "backupAvailable": false, + "compatibleActive": false, + "compatibleAvailable": false, + "compatibleBuildDate": "Mar 3, 2023", + "compatibleBuildId": "10650222", + "currentBuildDate": "Sep 10, 2026", + "currentBuildId": "24784313", + "displayLabel": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE. T7 installs will use T7 Patch Scroptss/T7Patch.", + "enhancedActive": false, + "enhancedAvailable": false, + "enhancedBuildDate": "", + "enhancedBuildId": "Enhanced", + "enhancedExeActive": false, + "executable": "$ROOT/game/BlackOpsIII.exe", + "executableHash": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "executableName": "BlackOpsIII.exe", + "integrityMessage": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE.", + "integrityStatus": "unverified", + "latestAvailable": false, + "modeLabel": "Unknown", + "patchLabel": "T7 Patch Scroptss/T7Patch", + "profile": "", + "state": "unverified", + "trustedExecutable": false + }, + "gameDetected": true, + "gameDir": "$ROOT/game", + "graphics": { + "displayMode": 1, + "drawFps": false, + "fov": 80, + "maxFps": 165, + "refreshRate": 60.0, + "renderResolution": 100, + "resolution": "1920x1080", + "vsync": true + }, + "launchProfiles": [ + { + "active": true, + "id": "default", + "installed": true, + "label": "Default (None)", + "option": "", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "offline", + "installed": true, + "label": "Play Offline", + "option": "+set fs_game offlinemp", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "all_around", + "installed": false, + "label": "All-around Enhancement Lite", + "option": "+set fs_game 2994481309", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "ultimate", + "installed": false, + "label": "Ultimate Experience Mod", + "option": "+set fs_game 2942053577", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "reforged", + "installed": false, + "label": "BO3 Reforged", + "option": "+set fs_game 3667377161", + "path": null, + "state": "Not Subscribed", + "subscribed": false + } + ], + "logPath": "$ROOT/data/PatchOpsIII.log", + "logs": [ + { + "category": "Success", + "line": "$TIMESTAMP - Success: Intro video skipped.", + "message": "Intro video skipped." + } + ], + "maintenance": { + "logPayload": "PatchOpsIII v1.3.4 - Linux $KERNEL (x86_64) logs:\n```\n$TIMESTAMP - Success: Intro video skipped.\n```", + "modFilesDir": "$ROOT/data/BO3 Mod Files" + }, + "mods": { + "dxvk": false, + "enhanced": false, + "t7Patch": false + }, + "platform": "Linux", + "presets": [ + "Smooth Preset", + "Low-Latency Preset" + ], + "qol": { + "allIntros": false, + "d3dcompiler": false, + "intro": true + }, + "releaseChannel": "stable", + "steamUserId": "76561198000000000", + "t7": { + "colorCode": "", + "confExists": false, + "friendsOnly": false, + "gamertag": "", + "installed": false, + "mode": "Custom", + "networkPassword": "", + "plainName": "" + } + } + }, + { + "ok": true, + "state": { + "activeLaunchProfile": "default", + "advanced": { + "configReadonly": false, + "maxFrameLatency": 1, + "reduceCpu": false, + "smoothFramerate": false, + "unlockOptions": false, + "vramLimited": false, + "vramTarget": 100 + }, + "appVersion": "v1.3.4", + "configExists": true, + "currentLaunchOptions": "", + "dxvk": { + "confExists": false, + "installed": false, + "settings": { + "enableAsync": true, + "gplAsyncCache": true, + "hudEnabled": false, + "maxFrameLatency": 1, + "maxFrameRate": 0, + "numCompilerThreads": 0, + "tearFree": "True" + } + }, + "enhanced": { + "acknowledgedAt": null, + "backupStatus": "Not created", + "detectedAt": null, + "dumpSource": "", + "filesInstalled": 0, + "installed": false, + "launchOptionsActive": false + }, + "exeSwap": { + "activeBuildDate": "", + "activeBuildId": "Unverified", + "backupAvailable": false, + "compatibleActive": false, + "compatibleAvailable": false, + "compatibleBuildDate": "Mar 3, 2023", + "compatibleBuildId": "10650222", + "currentBuildDate": "Sep 10, 2026", + "currentBuildId": "24784313", + "displayLabel": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE. T7 installs will use T7 Patch Scroptss/T7Patch.", + "enhancedActive": false, + "enhancedAvailable": false, + "enhancedBuildDate": "", + "enhancedBuildId": "Enhanced", + "enhancedExeActive": false, + "executable": "$ROOT/game/BlackOpsIII.exe", + "executableHash": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "executableName": "BlackOpsIII.exe", + "integrityMessage": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE.", + "integrityStatus": "unverified", + "latestAvailable": false, + "modeLabel": "Unknown", + "patchLabel": "T7 Patch Scroptss/T7Patch", + "profile": "", + "state": "unverified", + "trustedExecutable": false + }, + "gameDetected": true, + "gameDir": "$ROOT/game", + "graphics": { + "displayMode": 1, + "drawFps": false, + "fov": 80, + "maxFps": 165, + "refreshRate": 60.0, + "renderResolution": 100, + "resolution": "1920x1080", + "vsync": true + }, + "launchProfiles": [ + { + "active": true, + "id": "default", + "installed": true, + "label": "Default (None)", + "option": "", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "offline", + "installed": true, + "label": "Play Offline", + "option": "+set fs_game offlinemp", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "all_around", + "installed": false, + "label": "All-around Enhancement Lite", + "option": "+set fs_game 2994481309", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "ultimate", + "installed": false, + "label": "Ultimate Experience Mod", + "option": "+set fs_game 2942053577", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "reforged", + "installed": false, + "label": "BO3 Reforged", + "option": "+set fs_game 3667377161", + "path": null, + "state": "Not Subscribed", + "subscribed": false + } + ], + "logPath": "$ROOT/data/PatchOpsIII.log", + "logs": [ + { + "category": "Success", + "line": "$TIMESTAMP - Success: Intro video skipped.", + "message": "Intro video skipped." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: Renamed d3dcompiler_46.dll to reduce stuttering.", + "message": "Renamed d3dcompiler_46.dll to reduce stuttering." + } + ], + "maintenance": { + "logPayload": "PatchOpsIII v1.3.4 - Linux $KERNEL (x86_64) logs:\n```\n$TIMESTAMP - Success: Intro video skipped.\n$TIMESTAMP - Success: Renamed d3dcompiler_46.dll to reduce stuttering.\n```", + "modFilesDir": "$ROOT/data/BO3 Mod Files" + }, + "mods": { + "dxvk": false, + "enhanced": false, + "t7Patch": false + }, + "platform": "Linux", + "presets": [ + "Smooth Preset", + "Low-Latency Preset" + ], + "qol": { + "allIntros": false, + "d3dcompiler": true, + "intro": true + }, + "releaseChannel": "stable", + "steamUserId": "76561198000000000", + "t7": { + "colorCode": "", + "confExists": false, + "friendsOnly": false, + "gamertag": "", + "installed": false, + "mode": "Custom", + "networkPassword": "", + "plainName": "" + } + } + }, + { + "ok": true, + "state": { + "activeLaunchProfile": "default", + "advanced": { + "configReadonly": false, + "maxFrameLatency": 1, + "reduceCpu": false, + "smoothFramerate": false, + "unlockOptions": false, + "vramLimited": false, + "vramTarget": 100 + }, + "appVersion": "v1.3.4", + "configExists": true, + "currentLaunchOptions": "", + "dxvk": { + "confExists": false, + "installed": false, + "settings": { + "enableAsync": true, + "gplAsyncCache": true, + "hudEnabled": false, + "maxFrameLatency": 1, + "maxFrameRate": 0, + "numCompilerThreads": 0, + "tearFree": "True" + } + }, + "enhanced": { + "acknowledgedAt": null, + "backupStatus": "Not created", + "detectedAt": null, + "dumpSource": "", + "filesInstalled": 0, + "installed": false, + "launchOptionsActive": false + }, + "exeSwap": { + "activeBuildDate": "", + "activeBuildId": "Unverified", + "backupAvailable": false, + "compatibleActive": false, + "compatibleAvailable": false, + "compatibleBuildDate": "Mar 3, 2023", + "compatibleBuildId": "10650222", + "currentBuildDate": "Sep 10, 2026", + "currentBuildId": "24784313", + "displayLabel": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE. T7 installs will use T7 Patch Scroptss/T7Patch.", + "enhancedActive": false, + "enhancedAvailable": false, + "enhancedBuildDate": "", + "enhancedBuildId": "Enhanced", + "enhancedExeActive": false, + "executable": "$ROOT/game/BlackOpsIII.exe", + "executableHash": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "executableName": "BlackOpsIII.exe", + "integrityMessage": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE.", + "integrityStatus": "unverified", + "latestAvailable": false, + "modeLabel": "Unknown", + "patchLabel": "T7 Patch Scroptss/T7Patch", + "profile": "", + "state": "unverified", + "trustedExecutable": false + }, + "gameDetected": true, + "gameDir": "$ROOT/game", + "graphics": { + "displayMode": 1, + "drawFps": false, + "fov": 80, + "maxFps": 165, + "refreshRate": 60.0, + "renderResolution": 100, + "resolution": "1920x1080", + "vsync": true + }, + "launchProfiles": [ + { + "active": true, + "id": "default", + "installed": true, + "label": "Default (None)", + "option": "", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "offline", + "installed": true, + "label": "Play Offline", + "option": "+set fs_game offlinemp", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "all_around", + "installed": false, + "label": "All-around Enhancement Lite", + "option": "+set fs_game 2994481309", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "ultimate", + "installed": false, + "label": "Ultimate Experience Mod", + "option": "+set fs_game 2942053577", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "reforged", + "installed": false, + "label": "BO3 Reforged", + "option": "+set fs_game 3667377161", + "path": null, + "state": "Not Subscribed", + "subscribed": false + } + ], + "logPath": "$ROOT/data/PatchOpsIII.log", + "logs": [ + { + "category": "Success", + "line": "$TIMESTAMP - Success: Intro video skipped.", + "message": "Intro video skipped." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: Renamed d3dcompiler_46.dll to reduce stuttering.", + "message": "Renamed d3dcompiler_46.dll to reduce stuttering." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: All intro videos skipped.", + "message": "All intro videos skipped." + } + ], + "maintenance": { + "logPayload": "PatchOpsIII v1.3.4 - Linux $KERNEL (x86_64) logs:\n```\n$TIMESTAMP - Success: Intro video skipped.\n$TIMESTAMP - Success: Renamed d3dcompiler_46.dll to reduce stuttering.\n$TIMESTAMP - Success: All intro videos skipped.\n```", + "modFilesDir": "$ROOT/data/BO3 Mod Files" + }, + "mods": { + "dxvk": false, + "enhanced": false, + "t7Patch": false + }, + "platform": "Linux", + "presets": [ + "Smooth Preset", + "Low-Latency Preset" + ], + "qol": { + "allIntros": true, + "d3dcompiler": true, + "intro": true + }, + "releaseChannel": "stable", + "steamUserId": "76561198000000000", + "t7": { + "colorCode": "", + "confExists": false, + "friendsOnly": false, + "gamertag": "", + "installed": false, + "mode": "Custom", + "networkPassword": "", + "plainName": "" + } + } + }, + { + "ok": true, + "state": { + "activeLaunchProfile": "default", + "advanced": { + "configReadonly": false, + "maxFrameLatency": 1, + "reduceCpu": false, + "smoothFramerate": false, + "unlockOptions": false, + "vramLimited": false, + "vramTarget": 100 + }, + "appVersion": "v1.3.4", + "configExists": true, + "currentLaunchOptions": "", + "dxvk": { + "confExists": false, + "installed": false, + "settings": { + "enableAsync": true, + "gplAsyncCache": true, + "hudEnabled": false, + "maxFrameLatency": 1, + "maxFrameRate": 0, + "numCompilerThreads": 0, + "tearFree": "True" + } + }, + "enhanced": { + "acknowledgedAt": null, + "backupStatus": "Not created", + "detectedAt": null, + "dumpSource": "", + "filesInstalled": 0, + "installed": false, + "launchOptionsActive": false + }, + "exeSwap": { + "activeBuildDate": "", + "activeBuildId": "Unverified", + "backupAvailable": false, + "compatibleActive": false, + "compatibleAvailable": false, + "compatibleBuildDate": "Mar 3, 2023", + "compatibleBuildId": "10650222", + "currentBuildDate": "Sep 10, 2026", + "currentBuildId": "24784313", + "displayLabel": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE. T7 installs will use T7 Patch Scroptss/T7Patch.", + "enhancedActive": false, + "enhancedAvailable": false, + "enhancedBuildDate": "", + "enhancedBuildId": "Enhanced", + "enhancedExeActive": false, + "executable": "$ROOT/game/BlackOpsIII.exe", + "executableHash": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "executableName": "BlackOpsIII.exe", + "integrityMessage": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE.", + "integrityStatus": "unverified", + "latestAvailable": false, + "modeLabel": "Unknown", + "patchLabel": "T7 Patch Scroptss/T7Patch", + "profile": "", + "state": "unverified", + "trustedExecutable": false + }, + "gameDetected": true, + "gameDir": "$ROOT/game", + "graphics": { + "displayMode": 1, + "drawFps": false, + "fov": 80, + "maxFps": 165, + "refreshRate": 60.0, + "renderResolution": 100, + "resolution": "1920x1080", + "vsync": true + }, + "launchProfiles": [ + { + "active": true, + "id": "default", + "installed": true, + "label": "Default (None)", + "option": "", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "offline", + "installed": true, + "label": "Play Offline", + "option": "+set fs_game offlinemp", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "all_around", + "installed": false, + "label": "All-around Enhancement Lite", + "option": "+set fs_game 2994481309", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "ultimate", + "installed": false, + "label": "Ultimate Experience Mod", + "option": "+set fs_game 2942053577", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "reforged", + "installed": false, + "label": "BO3 Reforged", + "option": "+set fs_game 3667377161", + "path": null, + "state": "Not Subscribed", + "subscribed": false + } + ], + "logPath": "$ROOT/data/PatchOpsIII.log", + "logs": [ + { + "category": "Success", + "line": "$TIMESTAMP - Success: Intro video skipped.", + "message": "Intro video skipped." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: Renamed d3dcompiler_46.dll to reduce stuttering.", + "message": "Renamed d3dcompiler_46.dll to reduce stuttering." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: All intro videos skipped.", + "message": "All intro videos skipped." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: Intro video skipped.", + "message": "Intro video skipped." + } + ], + "maintenance": { + "logPayload": "PatchOpsIII v1.3.4 - Linux $KERNEL (x86_64) logs:\n```\n$TIMESTAMP - Success: Intro video skipped.\n$TIMESTAMP - Success: Renamed d3dcompiler_46.dll to reduce stuttering.\n$TIMESTAMP - Success: All intro videos skipped.\n$TIMESTAMP - Success: Intro video skipped.\n```", + "modFilesDir": "$ROOT/data/BO3 Mod Files" + }, + "mods": { + "dxvk": false, + "enhanced": false, + "t7Patch": false + }, + "platform": "Linux", + "presets": [ + "Smooth Preset", + "Low-Latency Preset" + ], + "qol": { + "allIntros": true, + "d3dcompiler": true, + "intro": true + }, + "releaseChannel": "stable", + "steamUserId": "76561198000000000", + "t7": { + "colorCode": "", + "confExists": false, + "friendsOnly": false, + "gamertag": "", + "installed": false, + "mode": "Custom", + "networkPassword": "", + "plainName": "" + } + } + }, + { + "ok": true, + "state": { + "activeLaunchProfile": "default", + "advanced": { + "configReadonly": false, + "maxFrameLatency": 1, + "reduceCpu": false, + "smoothFramerate": false, + "unlockOptions": false, + "vramLimited": false, + "vramTarget": 100 + }, + "appVersion": "v1.3.4", + "configExists": true, + "currentLaunchOptions": "", + "dxvk": { + "confExists": false, + "installed": false, + "settings": { + "enableAsync": true, + "gplAsyncCache": true, + "hudEnabled": false, + "maxFrameLatency": 1, + "maxFrameRate": 0, + "numCompilerThreads": 0, + "tearFree": "True" + } + }, + "enhanced": { + "acknowledgedAt": null, + "backupStatus": "Not created", + "detectedAt": null, + "dumpSource": "", + "filesInstalled": 0, + "installed": false, + "launchOptionsActive": false + }, + "exeSwap": { + "activeBuildDate": "", + "activeBuildId": "Unverified", + "backupAvailable": false, + "compatibleActive": false, + "compatibleAvailable": false, + "compatibleBuildDate": "Mar 3, 2023", + "compatibleBuildId": "10650222", + "currentBuildDate": "Sep 10, 2026", + "currentBuildId": "24784313", + "displayLabel": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE. T7 installs will use T7 Patch Scroptss/T7Patch.", + "enhancedActive": false, + "enhancedAvailable": false, + "enhancedBuildDate": "", + "enhancedBuildId": "Enhanced", + "enhancedExeActive": false, + "executable": "$ROOT/game/BlackOpsIII.exe", + "executableHash": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "executableName": "BlackOpsIII.exe", + "integrityMessage": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE.", + "integrityStatus": "unverified", + "latestAvailable": false, + "modeLabel": "Unknown", + "patchLabel": "T7 Patch Scroptss/T7Patch", + "profile": "", + "state": "unverified", + "trustedExecutable": false + }, + "gameDetected": true, + "gameDir": "$ROOT/game", + "graphics": { + "displayMode": 1, + "drawFps": false, + "fov": 80, + "maxFps": 165, + "refreshRate": 60.0, + "renderResolution": 100, + "resolution": "1920x1080", + "vsync": true + }, + "launchProfiles": [ + { + "active": true, + "id": "default", + "installed": true, + "label": "Default (None)", + "option": "", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "offline", + "installed": true, + "label": "Play Offline", + "option": "+set fs_game offlinemp", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "all_around", + "installed": false, + "label": "All-around Enhancement Lite", + "option": "+set fs_game 2994481309", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "ultimate", + "installed": false, + "label": "Ultimate Experience Mod", + "option": "+set fs_game 2942053577", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "reforged", + "installed": false, + "label": "BO3 Reforged", + "option": "+set fs_game 3667377161", + "path": null, + "state": "Not Subscribed", + "subscribed": false + } + ], + "logPath": "$ROOT/data/PatchOpsIII.log", + "logs": [ + { + "category": "Success", + "line": "$TIMESTAMP - Success: Intro video skipped.", + "message": "Intro video skipped." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: Renamed d3dcompiler_46.dll to reduce stuttering.", + "message": "Renamed d3dcompiler_46.dll to reduce stuttering." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: All intro videos skipped.", + "message": "All intro videos skipped." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: Intro video skipped.", + "message": "Intro video skipped." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: Already using latest d3dcompiler.", + "message": "Already using latest d3dcompiler." + } + ], + "maintenance": { + "logPayload": "PatchOpsIII v1.3.4 - Linux $KERNEL (x86_64) logs:\n```\n$TIMESTAMP - Success: Intro video skipped.\n$TIMESTAMP - Success: Renamed d3dcompiler_46.dll to reduce stuttering.\n$TIMESTAMP - Success: All intro videos skipped.\n$TIMESTAMP - Success: Intro video skipped.\n$TIMESTAMP - Success: Already using latest d3dcompiler.\n```", + "modFilesDir": "$ROOT/data/BO3 Mod Files" + }, + "mods": { + "dxvk": false, + "enhanced": false, + "t7Patch": false + }, + "platform": "Linux", + "presets": [ + "Smooth Preset", + "Low-Latency Preset" + ], + "qol": { + "allIntros": true, + "d3dcompiler": true, + "intro": true + }, + "releaseChannel": "stable", + "steamUserId": "76561198000000000", + "t7": { + "colorCode": "", + "confExists": false, + "friendsOnly": false, + "gamertag": "", + "installed": false, + "mode": "Custom", + "networkPassword": "", + "plainName": "" + } + } + }, + { + "ok": true, + "state": { + "activeLaunchProfile": "default", + "advanced": { + "configReadonly": false, + "maxFrameLatency": 1, + "reduceCpu": false, + "smoothFramerate": false, + "unlockOptions": false, + "vramLimited": false, + "vramTarget": 100 + }, + "appVersion": "v1.3.4", + "configExists": true, + "currentLaunchOptions": "", + "dxvk": { + "confExists": false, + "installed": false, + "settings": { + "enableAsync": true, + "gplAsyncCache": true, + "hudEnabled": false, + "maxFrameLatency": 1, + "maxFrameRate": 0, + "numCompilerThreads": 0, + "tearFree": "True" + } + }, + "enhanced": { + "acknowledgedAt": null, + "backupStatus": "Not created", + "detectedAt": null, + "dumpSource": "", + "filesInstalled": 0, + "installed": false, + "launchOptionsActive": false + }, + "exeSwap": { + "activeBuildDate": "", + "activeBuildId": "Unverified", + "backupAvailable": false, + "compatibleActive": false, + "compatibleAvailable": false, + "compatibleBuildDate": "Mar 3, 2023", + "compatibleBuildId": "10650222", + "currentBuildDate": "Sep 10, 2026", + "currentBuildId": "24784313", + "displayLabel": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE. T7 installs will use T7 Patch Scroptss/T7Patch.", + "enhancedActive": false, + "enhancedAvailable": false, + "enhancedBuildDate": "", + "enhancedBuildId": "Enhanced", + "enhancedExeActive": false, + "executable": "$ROOT/game/BlackOpsIII.exe", + "executableHash": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "executableName": "BlackOpsIII.exe", + "integrityMessage": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE.", + "integrityStatus": "unverified", + "latestAvailable": false, + "modeLabel": "Unknown", + "patchLabel": "T7 Patch Scroptss/T7Patch", + "profile": "", + "state": "unverified", + "trustedExecutable": false + }, + "gameDetected": true, + "gameDir": "$ROOT/game", + "graphics": { + "displayMode": 1, + "drawFps": false, + "fov": 80, + "maxFps": 165, + "refreshRate": 60.0, + "renderResolution": 100, + "resolution": "1920x1080", + "vsync": true + }, + "launchProfiles": [ + { + "active": true, + "id": "default", + "installed": true, + "label": "Default (None)", + "option": "", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "offline", + "installed": true, + "label": "Play Offline", + "option": "+set fs_game offlinemp", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "all_around", + "installed": false, + "label": "All-around Enhancement Lite", + "option": "+set fs_game 2994481309", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "ultimate", + "installed": false, + "label": "Ultimate Experience Mod", + "option": "+set fs_game 2942053577", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "reforged", + "installed": false, + "label": "BO3 Reforged", + "option": "+set fs_game 3667377161", + "path": null, + "state": "Not Subscribed", + "subscribed": false + } + ], + "logPath": "$ROOT/data/PatchOpsIII.log", + "logs": [ + { + "category": "Success", + "line": "$TIMESTAMP - Success: Intro video skipped.", + "message": "Intro video skipped." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: Renamed d3dcompiler_46.dll to reduce stuttering.", + "message": "Renamed d3dcompiler_46.dll to reduce stuttering." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: All intro videos skipped.", + "message": "All intro videos skipped." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: Intro video skipped.", + "message": "Intro video skipped." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: Already using latest d3dcompiler.", + "message": "Already using latest d3dcompiler." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: Intro videos were already skipped.", + "message": "Intro videos were already skipped." + } + ], + "maintenance": { + "logPayload": "PatchOpsIII v1.3.4 - Linux $KERNEL (x86_64) logs:\n```\n$TIMESTAMP - Success: Intro video skipped.\n$TIMESTAMP - Success: Renamed d3dcompiler_46.dll to reduce stuttering.\n$TIMESTAMP - Success: All intro videos skipped.\n$TIMESTAMP - Success: Intro video skipped.\n$TIMESTAMP - Success: Already using latest d3dcompiler.\n$TIMESTAMP - Success: Intro videos were already skipped.\n```", + "modFilesDir": "$ROOT/data/BO3 Mod Files" + }, + "mods": { + "dxvk": false, + "enhanced": false, + "t7Patch": false + }, + "platform": "Linux", + "presets": [ + "Smooth Preset", + "Low-Latency Preset" + ], + "qol": { + "allIntros": true, + "d3dcompiler": true, + "intro": true + }, + "releaseChannel": "stable", + "steamUserId": "76561198000000000", + "t7": { + "colorCode": "", + "confExists": false, + "friendsOnly": false, + "gamertag": "", + "installed": false, + "mode": "Custom", + "networkPassword": "", + "plainName": "" + } + } + }, + { + "ok": true, + "state": { + "activeLaunchProfile": "default", + "advanced": { + "configReadonly": false, + "maxFrameLatency": 1, + "reduceCpu": false, + "smoothFramerate": false, + "unlockOptions": false, + "vramLimited": false, + "vramTarget": 100 + }, + "appVersion": "v1.3.4", + "configExists": true, + "currentLaunchOptions": "", + "dxvk": { + "confExists": false, + "installed": false, + "settings": { + "enableAsync": true, + "gplAsyncCache": true, + "hudEnabled": false, + "maxFrameLatency": 1, + "maxFrameRate": 0, + "numCompilerThreads": 0, + "tearFree": "True" + } + }, + "enhanced": { + "acknowledgedAt": null, + "backupStatus": "Not created", + "detectedAt": null, + "dumpSource": "", + "filesInstalled": 0, + "installed": false, + "launchOptionsActive": false + }, + "exeSwap": { + "activeBuildDate": "", + "activeBuildId": "Unverified", + "backupAvailable": false, + "compatibleActive": false, + "compatibleAvailable": false, + "compatibleBuildDate": "Mar 3, 2023", + "compatibleBuildId": "10650222", + "currentBuildDate": "Sep 10, 2026", + "currentBuildId": "24784313", + "displayLabel": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE. T7 installs will use T7 Patch Scroptss/T7Patch.", + "enhancedActive": false, + "enhancedAvailable": false, + "enhancedBuildDate": "", + "enhancedBuildId": "Enhanced", + "enhancedExeActive": false, + "executable": "$ROOT/game/BlackOpsIII.exe", + "executableHash": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "executableName": "BlackOpsIII.exe", + "integrityMessage": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE.", + "integrityStatus": "unverified", + "latestAvailable": false, + "modeLabel": "Unknown", + "patchLabel": "T7 Patch Scroptss/T7Patch", + "profile": "", + "state": "unverified", + "trustedExecutable": false + }, + "gameDetected": true, + "gameDir": "$ROOT/game", + "graphics": { + "displayMode": 1, + "drawFps": false, + "fov": 80, + "maxFps": 165, + "refreshRate": 60.0, + "renderResolution": 100, + "resolution": "1920x1080", + "vsync": true + }, + "launchProfiles": [ + { + "active": true, + "id": "default", + "installed": true, + "label": "Default (None)", + "option": "", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "offline", + "installed": true, + "label": "Play Offline", + "option": "+set fs_game offlinemp", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "all_around", + "installed": false, + "label": "All-around Enhancement Lite", + "option": "+set fs_game 2994481309", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "ultimate", + "installed": false, + "label": "Ultimate Experience Mod", + "option": "+set fs_game 2942053577", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "reforged", + "installed": false, + "label": "BO3 Reforged", + "option": "+set fs_game 3667377161", + "path": null, + "state": "Not Subscribed", + "subscribed": false + } + ], + "logPath": "$ROOT/data/PatchOpsIII.log", + "logs": [ + { + "category": "Success", + "line": "$TIMESTAMP - Success: Intro video skipped.", + "message": "Intro video skipped." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: Renamed d3dcompiler_46.dll to reduce stuttering.", + "message": "Renamed d3dcompiler_46.dll to reduce stuttering." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: All intro videos skipped.", + "message": "All intro videos skipped." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: Intro video skipped.", + "message": "Intro video skipped." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: Already using latest d3dcompiler.", + "message": "Already using latest d3dcompiler." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: Intro videos were already skipped.", + "message": "Intro videos were already skipped." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: Intro video restored.", + "message": "Intro video restored." + } + ], + "maintenance": { + "logPayload": "PatchOpsIII v1.3.4 - Linux $KERNEL (x86_64) logs:\n```\n$TIMESTAMP - Success: Intro video skipped.\n$TIMESTAMP - Success: Renamed d3dcompiler_46.dll to reduce stuttering.\n$TIMESTAMP - Success: All intro videos skipped.\n$TIMESTAMP - Success: Intro video skipped.\n$TIMESTAMP - Success: Already using latest d3dcompiler.\n$TIMESTAMP - Success: Intro videos were already skipped.\n$TIMESTAMP - Success: Intro video restored.\n```", + "modFilesDir": "$ROOT/data/BO3 Mod Files" + }, + "mods": { + "dxvk": false, + "enhanced": false, + "t7Patch": false + }, + "platform": "Linux", + "presets": [ + "Smooth Preset", + "Low-Latency Preset" + ], + "qol": { + "allIntros": false, + "d3dcompiler": true, + "intro": false + }, + "releaseChannel": "stable", + "steamUserId": "76561198000000000", + "t7": { + "colorCode": "", + "confExists": false, + "friendsOnly": false, + "gamertag": "", + "installed": false, + "mode": "Custom", + "networkPassword": "", + "plainName": "" + } + } + }, + { + "ok": true, + "state": { + "activeLaunchProfile": "default", + "advanced": { + "configReadonly": false, + "maxFrameLatency": 1, + "reduceCpu": false, + "smoothFramerate": false, + "unlockOptions": false, + "vramLimited": false, + "vramTarget": 100 + }, + "appVersion": "v1.3.4", + "configExists": true, + "currentLaunchOptions": "", + "dxvk": { + "confExists": false, + "installed": false, + "settings": { + "enableAsync": true, + "gplAsyncCache": true, + "hudEnabled": false, + "maxFrameLatency": 1, + "maxFrameRate": 0, + "numCompilerThreads": 0, + "tearFree": "True" + } + }, + "enhanced": { + "acknowledgedAt": null, + "backupStatus": "Not created", + "detectedAt": null, + "dumpSource": "", + "filesInstalled": 0, + "installed": false, + "launchOptionsActive": false + }, + "exeSwap": { + "activeBuildDate": "", + "activeBuildId": "Unverified", + "backupAvailable": false, + "compatibleActive": false, + "compatibleAvailable": false, + "compatibleBuildDate": "Mar 3, 2023", + "compatibleBuildId": "10650222", + "currentBuildDate": "Sep 10, 2026", + "currentBuildId": "24784313", + "displayLabel": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE. T7 installs will use T7 Patch Scroptss/T7Patch.", + "enhancedActive": false, + "enhancedAvailable": false, + "enhancedBuildDate": "", + "enhancedBuildId": "Enhanced", + "enhancedExeActive": false, + "executable": "$ROOT/game/BlackOpsIII.exe", + "executableHash": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "executableName": "BlackOpsIII.exe", + "integrityMessage": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE.", + "integrityStatus": "unverified", + "latestAvailable": false, + "modeLabel": "Unknown", + "patchLabel": "T7 Patch Scroptss/T7Patch", + "profile": "", + "state": "unverified", + "trustedExecutable": false + }, + "gameDetected": true, + "gameDir": "$ROOT/game", + "graphics": { + "displayMode": 1, + "drawFps": false, + "fov": 80, + "maxFps": 165, + "refreshRate": 60.0, + "renderResolution": 100, + "resolution": "1920x1080", + "vsync": true + }, + "launchProfiles": [ + { + "active": true, + "id": "default", + "installed": true, + "label": "Default (None)", + "option": "", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "offline", + "installed": true, + "label": "Play Offline", + "option": "+set fs_game offlinemp", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "all_around", + "installed": false, + "label": "All-around Enhancement Lite", + "option": "+set fs_game 2994481309", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "ultimate", + "installed": false, + "label": "Ultimate Experience Mod", + "option": "+set fs_game 2942053577", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "reforged", + "installed": false, + "label": "BO3 Reforged", + "option": "+set fs_game 3667377161", + "path": null, + "state": "Not Subscribed", + "subscribed": false + } + ], + "logPath": "$ROOT/data/PatchOpsIII.log", + "logs": [ + { + "category": "Success", + "line": "$TIMESTAMP - Success: Intro video skipped.", + "message": "Intro video skipped." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: Renamed d3dcompiler_46.dll to reduce stuttering.", + "message": "Renamed d3dcompiler_46.dll to reduce stuttering." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: All intro videos skipped.", + "message": "All intro videos skipped." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: Intro video skipped.", + "message": "Intro video skipped." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: Already using latest d3dcompiler.", + "message": "Already using latest d3dcompiler." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: Intro videos were already skipped.", + "message": "Intro videos were already skipped." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: Intro video restored.", + "message": "Intro video restored." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: Restored d3dcompiler_46.dll.", + "message": "Restored d3dcompiler_46.dll." + } + ], + "maintenance": { + "logPayload": "PatchOpsIII v1.3.4 - Linux $KERNEL (x86_64) logs:\n```\n$TIMESTAMP - Success: Intro video skipped.\n$TIMESTAMP - Success: Renamed d3dcompiler_46.dll to reduce stuttering.\n$TIMESTAMP - Success: All intro videos skipped.\n$TIMESTAMP - Success: Intro video skipped.\n$TIMESTAMP - Success: Already using latest d3dcompiler.\n$TIMESTAMP - Success: Intro videos were already skipped.\n$TIMESTAMP - Success: Intro video restored.\n$TIMESTAMP - Success: Restored d3dcompiler_46.dll.\n```", + "modFilesDir": "$ROOT/data/BO3 Mod Files" + }, + "mods": { + "dxvk": false, + "enhanced": false, + "t7Patch": false + }, + "platform": "Linux", + "presets": [ + "Smooth Preset", + "Low-Latency Preset" + ], + "qol": { + "allIntros": false, + "d3dcompiler": false, + "intro": false + }, + "releaseChannel": "stable", + "steamUserId": "76561198000000000", + "t7": { + "colorCode": "", + "confExists": false, + "friendsOnly": false, + "gamertag": "", + "installed": false, + "mode": "Custom", + "networkPassword": "", + "plainName": "" + } + } + }, + { + "ok": true, + "state": { + "activeLaunchProfile": "default", + "advanced": { + "configReadonly": false, + "maxFrameLatency": 1, + "reduceCpu": false, + "smoothFramerate": false, + "unlockOptions": false, + "vramLimited": false, + "vramTarget": 100 + }, + "appVersion": "v1.3.4", + "configExists": true, + "currentLaunchOptions": "", + "dxvk": { + "confExists": false, + "installed": false, + "settings": { + "enableAsync": true, + "gplAsyncCache": true, + "hudEnabled": false, + "maxFrameLatency": 1, + "maxFrameRate": 0, + "numCompilerThreads": 0, + "tearFree": "True" + } + }, + "enhanced": { + "acknowledgedAt": null, + "backupStatus": "Not created", + "detectedAt": null, + "dumpSource": "", + "filesInstalled": 0, + "installed": false, + "launchOptionsActive": false + }, + "exeSwap": { + "activeBuildDate": "", + "activeBuildId": "Unverified", + "backupAvailable": false, + "compatibleActive": false, + "compatibleAvailable": false, + "compatibleBuildDate": "Mar 3, 2023", + "compatibleBuildId": "10650222", + "currentBuildDate": "Sep 10, 2026", + "currentBuildId": "24784313", + "displayLabel": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE. T7 installs will use T7 Patch Scroptss/T7Patch.", + "enhancedActive": false, + "enhancedAvailable": false, + "enhancedBuildDate": "", + "enhancedBuildId": "Enhanced", + "enhancedExeActive": false, + "executable": "$ROOT/game/BlackOpsIII.exe", + "executableHash": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "executableName": "BlackOpsIII.exe", + "integrityMessage": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE.", + "integrityStatus": "unverified", + "latestAvailable": false, + "modeLabel": "Unknown", + "patchLabel": "T7 Patch Scroptss/T7Patch", + "profile": "", + "state": "unverified", + "trustedExecutable": false + }, + "gameDetected": true, + "gameDir": "$ROOT/game", + "graphics": { + "displayMode": 1, + "drawFps": false, + "fov": 80, + "maxFps": 165, + "refreshRate": 60.0, + "renderResolution": 100, + "resolution": "1920x1080", + "vsync": true + }, + "launchProfiles": [ + { + "active": true, + "id": "default", + "installed": true, + "label": "Default (None)", + "option": "", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "offline", + "installed": true, + "label": "Play Offline", + "option": "+set fs_game offlinemp", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "all_around", + "installed": false, + "label": "All-around Enhancement Lite", + "option": "+set fs_game 2994481309", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "ultimate", + "installed": false, + "label": "Ultimate Experience Mod", + "option": "+set fs_game 2942053577", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "reforged", + "installed": false, + "label": "BO3 Reforged", + "option": "+set fs_game 3667377161", + "path": null, + "state": "Not Subscribed", + "subscribed": false + } + ], + "logPath": "$ROOT/data/PatchOpsIII.log", + "logs": [ + { + "category": "Success", + "line": "$TIMESTAMP - Success: Intro video skipped.", + "message": "Intro video skipped." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: Renamed d3dcompiler_46.dll to reduce stuttering.", + "message": "Renamed d3dcompiler_46.dll to reduce stuttering." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: All intro videos skipped.", + "message": "All intro videos skipped." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: Intro video skipped.", + "message": "Intro video skipped." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: Already using latest d3dcompiler.", + "message": "Already using latest d3dcompiler." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: Intro videos were already skipped.", + "message": "Intro videos were already skipped." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: Intro video restored.", + "message": "Intro video restored." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: Restored d3dcompiler_46.dll.", + "message": "Restored d3dcompiler_46.dll." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: Intro videos restored.", + "message": "Intro videos restored." + } + ], + "maintenance": { + "logPayload": "PatchOpsIII v1.3.4 - Linux $KERNEL (x86_64) logs:\n```\n$TIMESTAMP - Success: Intro video skipped.\n$TIMESTAMP - Success: Renamed d3dcompiler_46.dll to reduce stuttering.\n$TIMESTAMP - Success: All intro videos skipped.\n$TIMESTAMP - Success: Intro video skipped.\n$TIMESTAMP - Success: Already using latest d3dcompiler.\n$TIMESTAMP - Success: Intro videos were already skipped.\n$TIMESTAMP - Success: Intro video restored.\n$TIMESTAMP - Success: Restored d3dcompiler_46.dll.\n$TIMESTAMP - Success: Intro videos restored.\n```", + "modFilesDir": "$ROOT/data/BO3 Mod Files" + }, + "mods": { + "dxvk": false, + "enhanced": false, + "t7Patch": false + }, + "platform": "Linux", + "presets": [ + "Smooth Preset", + "Low-Latency Preset" + ], + "qol": { + "allIntros": false, + "d3dcompiler": false, + "intro": false + }, + "releaseChannel": "stable", + "steamUserId": "76561198000000000", + "t7": { + "colorCode": "", + "confExists": false, + "friendsOnly": false, + "gamertag": "", + "installed": false, + "mode": "Custom", + "networkPassword": "", + "plainName": "" + } + } + } + ] + }, + "fixture": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "name": "qol_toggle_roundtrip", + "operations": [ + { + "body": { + "enabled": true + }, + "method": "POST", + "path": "/api/intro-skip" + }, + { + "body": { + "enabled": true + }, + "method": "POST", + "path": "/api/d3dcompiler" + }, + { + "body": { + "enabled": true + }, + "method": "POST", + "path": "/api/all-intros-skip" + }, + { + "body": { + "enabled": true + }, + "method": "POST", + "path": "/api/intro-skip" + }, + { + "body": { + "enabled": true + }, + "method": "POST", + "path": "/api/d3dcompiler" + }, + { + "body": { + "enabled": true + }, + "method": "POST", + "path": "/api/all-intros-skip" + }, + { + "body": { + "enabled": false + }, + "method": "POST", + "path": "/api/intro-skip" + }, + { + "body": { + "enabled": false + }, + "method": "POST", + "path": "/api/d3dcompiler" + }, + { + "body": { + "enabled": false + }, + "method": "POST", + "path": "/api/all-intros-skip" + } + ], + "runner": "direct endpoint functions", + "schemaVersion": 1, + "sourceHashes": { + "backend/api.py": "080be5c4a3b929134298179cd3f4d1558c288a50dcdb38978ada831925a57a46", + "bo3_enhanced.py": "32a670c8a63a64216ee5c46aa000d2e3840ca348ca0fd5031c81699252cf57b8", + "dxvk_manager.py": "8e150458bc7480ac89cd9e64c558ba438f61a09611bbf24b962cba02d7e4a5e5", + "t7_patch.py": "73e66fadd1c0775742b7bb6d9c6c73a3c9df8304d3117abd8ac768dfc68cc3ea", + "utils.py": "313069c3f70b92888239c9ef15ce2379713b029e019426e76b386f088b556564" + }, + "sourceOfTruth": "Python backend on main" +} diff --git a/native/core/tests/parity/qol_toggles.json b/native/core/tests/parity/qol_toggles.json new file mode 100644 index 0000000..56d6dfa --- /dev/null +++ b/native/core/tests/parity/qol_toggles.json @@ -0,0 +1,1042 @@ +{ + "expected": { + "checkpoints": [ + { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 436, + "path": "data/PatchOpsIII.log", + "sha256": "680528705d5426430bdafe3c54deafbe9d2548a015247eab02ad7b87c8cb9df6", + "size": 43, + "text": "$TIMESTAMP - Success: Intro video skipped.\n" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv.patchops.bak", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 436, + "path": "data/PatchOpsIII.log", + "sha256": "ce266d1b7095c11f4e7ac3f9a40ff619dfb73ac05b2017981aa5789443e047c5", + "size": 114, + "text": "$TIMESTAMP - Success: Intro video skipped.\n$TIMESTAMP - Success: Renamed d3dcompiler_46.dll to reduce stuttering.\n" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll.patchops.bak", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv.patchops.bak", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 436, + "path": "data/PatchOpsIII.log", + "sha256": "835983fb48b9162bab97965ad1c70eb442647d6684420ebbf17cdb376a895734", + "size": 162, + "text": "$TIMESTAMP - Success: Intro video skipped.\n$TIMESTAMP - Success: Renamed d3dcompiler_46.dll to reduce stuttering.\n$TIMESTAMP - Success: All intro videos skipped.\n" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll.patchops.bak", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv.patchops.bak", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv.patchops.bak", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + } + ], + "filesystem": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 436, + "path": "data/PatchOpsIII.log", + "sha256": "835983fb48b9162bab97965ad1c70eb442647d6684420ebbf17cdb376a895734", + "size": 162, + "text": "$TIMESTAMP - Success: Intro video skipped.\n$TIMESTAMP - Success: Renamed d3dcompiler_46.dll to reduce stuttering.\n$TIMESTAMP - Success: All intro videos skipped.\n" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll.patchops.bak", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv.patchops.bak", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv.patchops.bak", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "responses": [ + { + "ok": true, + "state": { + "activeLaunchProfile": "default", + "advanced": { + "configReadonly": false, + "maxFrameLatency": 1, + "reduceCpu": false, + "smoothFramerate": false, + "unlockOptions": false, + "vramLimited": false, + "vramTarget": 100 + }, + "appVersion": "v1.3.4", + "configExists": true, + "currentLaunchOptions": "", + "dxvk": { + "confExists": false, + "installed": false, + "settings": { + "enableAsync": true, + "gplAsyncCache": true, + "hudEnabled": false, + "maxFrameLatency": 1, + "maxFrameRate": 0, + "numCompilerThreads": 0, + "tearFree": "True" + } + }, + "enhanced": { + "acknowledgedAt": null, + "backupStatus": "Not created", + "detectedAt": null, + "dumpSource": "", + "filesInstalled": 0, + "installed": false, + "launchOptionsActive": false + }, + "exeSwap": { + "activeBuildDate": "", + "activeBuildId": "Unverified", + "backupAvailable": false, + "compatibleActive": false, + "compatibleAvailable": false, + "compatibleBuildDate": "Mar 3, 2023", + "compatibleBuildId": "10650222", + "currentBuildDate": "Sep 10, 2026", + "currentBuildId": "24784313", + "displayLabel": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE. T7 installs will use T7 Patch Scroptss/T7Patch.", + "enhancedActive": false, + "enhancedAvailable": false, + "enhancedBuildDate": "", + "enhancedBuildId": "Enhanced", + "enhancedExeActive": false, + "executable": "$ROOT/game/BlackOpsIII.exe", + "executableHash": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "executableName": "BlackOpsIII.exe", + "integrityMessage": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE.", + "integrityStatus": "unverified", + "latestAvailable": false, + "modeLabel": "Unknown", + "patchLabel": "T7 Patch Scroptss/T7Patch", + "profile": "", + "state": "unverified", + "trustedExecutable": false + }, + "gameDetected": true, + "gameDir": "$ROOT/game", + "graphics": { + "displayMode": 1, + "drawFps": false, + "fov": 80, + "maxFps": 165, + "refreshRate": 60.0, + "renderResolution": 100, + "resolution": "1920x1080", + "vsync": true + }, + "launchProfiles": [ + { + "active": true, + "id": "default", + "installed": true, + "label": "Default (None)", + "option": "", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "offline", + "installed": true, + "label": "Play Offline", + "option": "+set fs_game offlinemp", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "all_around", + "installed": false, + "label": "All-around Enhancement Lite", + "option": "+set fs_game 2994481309", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "ultimate", + "installed": false, + "label": "Ultimate Experience Mod", + "option": "+set fs_game 2942053577", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "reforged", + "installed": false, + "label": "BO3 Reforged", + "option": "+set fs_game 3667377161", + "path": null, + "state": "Not Subscribed", + "subscribed": false + } + ], + "logPath": "$ROOT/data/PatchOpsIII.log", + "logs": [ + { + "category": "Success", + "line": "$TIMESTAMP - Success: Intro video skipped.", + "message": "Intro video skipped." + } + ], + "maintenance": { + "logPayload": "PatchOpsIII v1.3.4 - Linux $KERNEL (x86_64) logs:\n```\n$TIMESTAMP - Success: Intro video skipped.\n```", + "modFilesDir": "$ROOT/data/BO3 Mod Files" + }, + "mods": { + "dxvk": false, + "enhanced": false, + "t7Patch": false + }, + "platform": "Linux", + "presets": [ + "Smooth Preset", + "Low-Latency Preset" + ], + "qol": { + "allIntros": false, + "d3dcompiler": false, + "intro": true + }, + "releaseChannel": "stable", + "steamUserId": "76561198000000000", + "t7": { + "colorCode": "", + "confExists": false, + "friendsOnly": false, + "gamertag": "", + "installed": false, + "mode": "Custom", + "networkPassword": "", + "plainName": "" + } + } + }, + { + "ok": true, + "state": { + "activeLaunchProfile": "default", + "advanced": { + "configReadonly": false, + "maxFrameLatency": 1, + "reduceCpu": false, + "smoothFramerate": false, + "unlockOptions": false, + "vramLimited": false, + "vramTarget": 100 + }, + "appVersion": "v1.3.4", + "configExists": true, + "currentLaunchOptions": "", + "dxvk": { + "confExists": false, + "installed": false, + "settings": { + "enableAsync": true, + "gplAsyncCache": true, + "hudEnabled": false, + "maxFrameLatency": 1, + "maxFrameRate": 0, + "numCompilerThreads": 0, + "tearFree": "True" + } + }, + "enhanced": { + "acknowledgedAt": null, + "backupStatus": "Not created", + "detectedAt": null, + "dumpSource": "", + "filesInstalled": 0, + "installed": false, + "launchOptionsActive": false + }, + "exeSwap": { + "activeBuildDate": "", + "activeBuildId": "Unverified", + "backupAvailable": false, + "compatibleActive": false, + "compatibleAvailable": false, + "compatibleBuildDate": "Mar 3, 2023", + "compatibleBuildId": "10650222", + "currentBuildDate": "Sep 10, 2026", + "currentBuildId": "24784313", + "displayLabel": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE. T7 installs will use T7 Patch Scroptss/T7Patch.", + "enhancedActive": false, + "enhancedAvailable": false, + "enhancedBuildDate": "", + "enhancedBuildId": "Enhanced", + "enhancedExeActive": false, + "executable": "$ROOT/game/BlackOpsIII.exe", + "executableHash": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "executableName": "BlackOpsIII.exe", + "integrityMessage": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE.", + "integrityStatus": "unverified", + "latestAvailable": false, + "modeLabel": "Unknown", + "patchLabel": "T7 Patch Scroptss/T7Patch", + "profile": "", + "state": "unverified", + "trustedExecutable": false + }, + "gameDetected": true, + "gameDir": "$ROOT/game", + "graphics": { + "displayMode": 1, + "drawFps": false, + "fov": 80, + "maxFps": 165, + "refreshRate": 60.0, + "renderResolution": 100, + "resolution": "1920x1080", + "vsync": true + }, + "launchProfiles": [ + { + "active": true, + "id": "default", + "installed": true, + "label": "Default (None)", + "option": "", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "offline", + "installed": true, + "label": "Play Offline", + "option": "+set fs_game offlinemp", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "all_around", + "installed": false, + "label": "All-around Enhancement Lite", + "option": "+set fs_game 2994481309", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "ultimate", + "installed": false, + "label": "Ultimate Experience Mod", + "option": "+set fs_game 2942053577", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "reforged", + "installed": false, + "label": "BO3 Reforged", + "option": "+set fs_game 3667377161", + "path": null, + "state": "Not Subscribed", + "subscribed": false + } + ], + "logPath": "$ROOT/data/PatchOpsIII.log", + "logs": [ + { + "category": "Success", + "line": "$TIMESTAMP - Success: Intro video skipped.", + "message": "Intro video skipped." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: Renamed d3dcompiler_46.dll to reduce stuttering.", + "message": "Renamed d3dcompiler_46.dll to reduce stuttering." + } + ], + "maintenance": { + "logPayload": "PatchOpsIII v1.3.4 - Linux $KERNEL (x86_64) logs:\n```\n$TIMESTAMP - Success: Intro video skipped.\n$TIMESTAMP - Success: Renamed d3dcompiler_46.dll to reduce stuttering.\n```", + "modFilesDir": "$ROOT/data/BO3 Mod Files" + }, + "mods": { + "dxvk": false, + "enhanced": false, + "t7Patch": false + }, + "platform": "Linux", + "presets": [ + "Smooth Preset", + "Low-Latency Preset" + ], + "qol": { + "allIntros": false, + "d3dcompiler": true, + "intro": true + }, + "releaseChannel": "stable", + "steamUserId": "76561198000000000", + "t7": { + "colorCode": "", + "confExists": false, + "friendsOnly": false, + "gamertag": "", + "installed": false, + "mode": "Custom", + "networkPassword": "", + "plainName": "" + } + } + }, + { + "ok": true, + "state": { + "activeLaunchProfile": "default", + "advanced": { + "configReadonly": false, + "maxFrameLatency": 1, + "reduceCpu": false, + "smoothFramerate": false, + "unlockOptions": false, + "vramLimited": false, + "vramTarget": 100 + }, + "appVersion": "v1.3.4", + "configExists": true, + "currentLaunchOptions": "", + "dxvk": { + "confExists": false, + "installed": false, + "settings": { + "enableAsync": true, + "gplAsyncCache": true, + "hudEnabled": false, + "maxFrameLatency": 1, + "maxFrameRate": 0, + "numCompilerThreads": 0, + "tearFree": "True" + } + }, + "enhanced": { + "acknowledgedAt": null, + "backupStatus": "Not created", + "detectedAt": null, + "dumpSource": "", + "filesInstalled": 0, + "installed": false, + "launchOptionsActive": false + }, + "exeSwap": { + "activeBuildDate": "", + "activeBuildId": "Unverified", + "backupAvailable": false, + "compatibleActive": false, + "compatibleAvailable": false, + "compatibleBuildDate": "Mar 3, 2023", + "compatibleBuildId": "10650222", + "currentBuildDate": "Sep 10, 2026", + "currentBuildId": "24784313", + "displayLabel": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE. T7 installs will use T7 Patch Scroptss/T7Patch.", + "enhancedActive": false, + "enhancedAvailable": false, + "enhancedBuildDate": "", + "enhancedBuildId": "Enhanced", + "enhancedExeActive": false, + "executable": "$ROOT/game/BlackOpsIII.exe", + "executableHash": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "executableName": "BlackOpsIII.exe", + "integrityMessage": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE.", + "integrityStatus": "unverified", + "latestAvailable": false, + "modeLabel": "Unknown", + "patchLabel": "T7 Patch Scroptss/T7Patch", + "profile": "", + "state": "unverified", + "trustedExecutable": false + }, + "gameDetected": true, + "gameDir": "$ROOT/game", + "graphics": { + "displayMode": 1, + "drawFps": false, + "fov": 80, + "maxFps": 165, + "refreshRate": 60.0, + "renderResolution": 100, + "resolution": "1920x1080", + "vsync": true + }, + "launchProfiles": [ + { + "active": true, + "id": "default", + "installed": true, + "label": "Default (None)", + "option": "", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "offline", + "installed": true, + "label": "Play Offline", + "option": "+set fs_game offlinemp", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "all_around", + "installed": false, + "label": "All-around Enhancement Lite", + "option": "+set fs_game 2994481309", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "ultimate", + "installed": false, + "label": "Ultimate Experience Mod", + "option": "+set fs_game 2942053577", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "reforged", + "installed": false, + "label": "BO3 Reforged", + "option": "+set fs_game 3667377161", + "path": null, + "state": "Not Subscribed", + "subscribed": false + } + ], + "logPath": "$ROOT/data/PatchOpsIII.log", + "logs": [ + { + "category": "Success", + "line": "$TIMESTAMP - Success: Intro video skipped.", + "message": "Intro video skipped." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: Renamed d3dcompiler_46.dll to reduce stuttering.", + "message": "Renamed d3dcompiler_46.dll to reduce stuttering." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: All intro videos skipped.", + "message": "All intro videos skipped." + } + ], + "maintenance": { + "logPayload": "PatchOpsIII v1.3.4 - Linux $KERNEL (x86_64) logs:\n```\n$TIMESTAMP - Success: Intro video skipped.\n$TIMESTAMP - Success: Renamed d3dcompiler_46.dll to reduce stuttering.\n$TIMESTAMP - Success: All intro videos skipped.\n```", + "modFilesDir": "$ROOT/data/BO3 Mod Files" + }, + "mods": { + "dxvk": false, + "enhanced": false, + "t7Patch": false + }, + "platform": "Linux", + "presets": [ + "Smooth Preset", + "Low-Latency Preset" + ], + "qol": { + "allIntros": true, + "d3dcompiler": true, + "intro": true + }, + "releaseChannel": "stable", + "steamUserId": "76561198000000000", + "t7": { + "colorCode": "", + "confExists": false, + "friendsOnly": false, + "gamertag": "", + "installed": false, + "mode": "Custom", + "networkPassword": "", + "plainName": "" + } + } + } + ] + }, + "fixture": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "name": "qol_toggles", + "operations": [ + { + "body": { + "enabled": true + }, + "method": "POST", + "path": "/api/intro-skip" + }, + { + "body": { + "enabled": true + }, + "method": "POST", + "path": "/api/d3dcompiler" + }, + { + "body": { + "enabled": true + }, + "method": "POST", + "path": "/api/all-intros-skip" + } + ], + "runner": "direct endpoint functions", + "schemaVersion": 1, + "sourceHashes": { + "backend/api.py": "080be5c4a3b929134298179cd3f4d1558c288a50dcdb38978ada831925a57a46", + "bo3_enhanced.py": "32a670c8a63a64216ee5c46aa000d2e3840ca348ca0fd5031c81699252cf57b8", + "dxvk_manager.py": "8e150458bc7480ac89cd9e64c558ba438f61a09611bbf24b962cba02d7e4a5e5", + "t7_patch.py": "73e66fadd1c0775742b7bb6d9c6c73a3c9df8304d3117abd8ac768dfc68cc3ea", + "utils.py": "313069c3f70b92888239c9ef15ce2379713b029e019426e76b386f088b556564" + }, + "sourceOfTruth": "Python backend on main" +} diff --git a/native/core/tests/parity/results.jsonl b/native/core/tests/parity/results.jsonl new file mode 100644 index 0000000..e69de29 diff --git a/native/core/tests/parity/results.provenance.json b/native/core/tests/parity/results.provenance.json new file mode 100644 index 0000000..25b85b7 --- /dev/null +++ b/native/core/tests/parity/results.provenance.json @@ -0,0 +1,43 @@ +{ + "enhancedLegacyStatusBinding": "in memory per AppState; no filesystem difference", + "expectedDiffAllowlist": { + "dxvk_config_write": [ + "data/DXVK Managed", + "data/DXVK Managed/originals", + "data/DXVK Managed/manifest.json", + "data/DXVK Managed/originals/dxvk.conf" + ] + }, + "goldensModified": false, + "mismatchCount": 0, + "rustSourcesAtReport": { + "native/core/src/app.rs": "d23b3dfd767e639473e0be4b7d2a0a2d3e7065aeb0cc88e7e34c3553d4e8e089", + "native/core/src/dxvk.rs": "2f738ea6f0f02a3c5e49ae2bc4e3b77d56d8504b1c9d23a1d10ac24ebceae36b", + "native/core/src/engine.rs": "6b7d5acc1d2fab3a49a1dc3e8dce932b088d3ab2bc1e4e978e54b3051f2332a1", + "native/core/src/enhanced.rs": "4bece67520e09fd967c9599c3acdbda4a578579b698923277d5997ea5732a3ee", + "native/core/src/exe.rs": "0237b2f2bae032806e100bf2d664a409182088a8dcb61dddb76f73b5321ae05c", + "native/core/src/fs_ops.rs": "5d588d9fa973ce64291f7ceb7b3b6295510876bf32acb58678fc4cb1d170a4e5", + "native/core/src/lib.rs": "973f5f9d453388c5d47debc895fa07eccda13baa76f6f29c17baeb34c45298e7", + "native/core/src/models.rs": "4cb649a262fd8d7f59d6c35b3c8ed1b206f37691310a785c594163a543665121", + "native/core/src/steam.rs": "b047db4b08f4bc985e8b28e1a5bc65363a858f0972d4a3834f01cf6716de9617", + "native/core/src/t7.rs": "b0174695aa50953896927fa12c94148991321cfd0a853e3009b7c41b67e97412", + "native/core/tests/parity.rs": "8e38487c85b843812a73e0b4207e2db851d966b0de49cc8b85d08d0a608cf495" + }, + "rustSourcesBeforeLaunch": { + "native/core/src/app.rs": "d23b3dfd767e639473e0be4b7d2a0a2d3e7065aeb0cc88e7e34c3553d4e8e089", + "native/core/src/dxvk.rs": "2f738ea6f0f02a3c5e49ae2bc4e3b77d56d8504b1c9d23a1d10ac24ebceae36b", + "native/core/src/engine.rs": "6b7d5acc1d2fab3a49a1dc3e8dce932b088d3ab2bc1e4e978e54b3051f2332a1", + "native/core/src/enhanced.rs": "4bece67520e09fd967c9599c3acdbda4a578579b698923277d5997ea5732a3ee", + "native/core/src/exe.rs": "0237b2f2bae032806e100bf2d664a409182088a8dcb61dddb76f73b5321ae05c", + "native/core/src/fs_ops.rs": "5d588d9fa973ce64291f7ceb7b3b6295510876bf32acb58678fc4cb1d170a4e5", + "native/core/src/lib.rs": "973f5f9d453388c5d47debc895fa07eccda13baa76f6f29c17baeb34c45298e7", + "native/core/src/models.rs": "4cb649a262fd8d7f59d6c35b3c8ed1b206f37691310a785c594163a543665121", + "native/core/src/steam.rs": "b047db4b08f4bc985e8b28e1a5bc65363a858f0972d4a3834f01cf6716de9617", + "native/core/src/t7.rs": "b0174695aa50953896927fa12c94148991321cfd0a853e3009b7c41b67e97412", + "native/core/tests/parity.rs": "8e38487c85b843812a73e0b4207e2db851d966b0de49cc8b85d08d0a608cf495" + }, + "rustSourcesChangedBetweenLaunchAndReport": [], + "summary": "test result: ok. 31 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 11.13s", + "testBinary": "native/target/debug/deps/parity-4cab83e5679c2c93", + "testBinarySha256": "0ff78ffa4cfd7c0312abe1b1b4facc7ed6b6586370af3109402c11a3984b6e93" +} diff --git a/native/core/tests/parity/status_alternate_executable.json b/native/core/tests/parity/status_alternate_executable.json new file mode 100644 index 0000000..0d04f4b --- /dev/null +++ b/native/core/tests/parity/status_alternate_executable.json @@ -0,0 +1,455 @@ +{ + "expected": { + "checkpoints": [ + { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOps3.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + } + ], + "filesystem": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOps3.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "responses": [ + { + "activeLaunchProfile": "default", + "advanced": { + "configReadonly": false, + "maxFrameLatency": 1, + "reduceCpu": false, + "smoothFramerate": false, + "unlockOptions": false, + "vramLimited": false, + "vramTarget": 100 + }, + "appVersion": "v1.3.4", + "configExists": true, + "currentLaunchOptions": "", + "dxvk": { + "confExists": false, + "installed": false, + "settings": { + "enableAsync": true, + "gplAsyncCache": true, + "hudEnabled": false, + "maxFrameLatency": 1, + "maxFrameRate": 0, + "numCompilerThreads": 0, + "tearFree": "True" + } + }, + "enhanced": { + "acknowledgedAt": null, + "backupStatus": "Not created", + "detectedAt": null, + "dumpSource": "", + "filesInstalled": 0, + "installed": false, + "launchOptionsActive": false + }, + "exeSwap": { + "activeBuildDate": "", + "activeBuildId": "Unverified", + "backupAvailable": false, + "compatibleActive": false, + "compatibleAvailable": false, + "compatibleBuildDate": "Mar 3, 2023", + "compatibleBuildId": "10650222", + "currentBuildDate": "Sep 10, 2026", + "currentBuildId": "24784313", + "displayLabel": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE. T7 installs will use T7 Patch Scroptss/T7Patch.", + "enhancedActive": false, + "enhancedAvailable": false, + "enhancedBuildDate": "", + "enhancedBuildId": "Enhanced", + "enhancedExeActive": false, + "executable": "$ROOT/game/BlackOps3.exe", + "executableHash": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "executableName": "BlackOps3.exe", + "integrityMessage": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE.", + "integrityStatus": "unverified", + "latestAvailable": false, + "modeLabel": "Unknown", + "patchLabel": "T7 Patch Scroptss/T7Patch", + "profile": "", + "state": "unverified", + "trustedExecutable": false + }, + "gameDetected": true, + "gameDir": "$ROOT/game", + "graphics": { + "displayMode": 1, + "drawFps": false, + "fov": 80, + "maxFps": 165, + "refreshRate": 60.0, + "renderResolution": 100, + "resolution": "1920x1080", + "vsync": true + }, + "launchProfiles": [ + { + "active": true, + "id": "default", + "installed": true, + "label": "Default (None)", + "option": "", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "offline", + "installed": true, + "label": "Play Offline", + "option": "+set fs_game offlinemp", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "all_around", + "installed": false, + "label": "All-around Enhancement Lite", + "option": "+set fs_game 2994481309", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "ultimate", + "installed": false, + "label": "Ultimate Experience Mod", + "option": "+set fs_game 2942053577", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "reforged", + "installed": false, + "label": "BO3 Reforged", + "option": "+set fs_game 3667377161", + "path": null, + "state": "Not Subscribed", + "subscribed": false + } + ], + "logPath": "$ROOT/data/PatchOpsIII.log", + "logs": [], + "maintenance": { + "logPayload": "PatchOpsIII v1.3.4 - Linux $KERNEL (x86_64) logs:\n```\n(no log entries found)\n```", + "modFilesDir": "$ROOT/data/BO3 Mod Files" + }, + "mods": { + "dxvk": false, + "enhanced": false, + "t7Patch": false + }, + "platform": "Linux", + "presets": [ + "Smooth Preset", + "Low-Latency Preset" + ], + "qol": { + "allIntros": false, + "d3dcompiler": false, + "intro": false + }, + "releaseChannel": "stable", + "steamUserId": "76561198000000000", + "t7": { + "colorCode": "", + "confExists": false, + "friendsOnly": false, + "gamertag": "", + "installed": false, + "mode": "Custom", + "networkPassword": "", + "plainName": "" + } + } + ] + }, + "fixture": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOps3.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "name": "status_alternate_executable", + "operations": [ + { + "method": "GET", + "path": "/api/status" + } + ], + "runner": "direct endpoint functions", + "schemaVersion": 1, + "sourceHashes": { + "backend/api.py": "080be5c4a3b929134298179cd3f4d1558c288a50dcdb38978ada831925a57a46", + "bo3_enhanced.py": "32a670c8a63a64216ee5c46aa000d2e3840ca348ca0fd5031c81699252cf57b8", + "dxvk_manager.py": "8e150458bc7480ac89cd9e64c558ba438f61a09611bbf24b962cba02d7e4a5e5", + "t7_patch.py": "73e66fadd1c0775742b7bb6d9c6c73a3c9df8304d3117abd8ac768dfc68cc3ea", + "utils.py": "313069c3f70b92888239c9ef15ce2379713b029e019426e76b386f088b556564" + }, + "sourceOfTruth": "Python backend on main" +} diff --git a/native/core/tests/parity/status_compatible_build.json b/native/core/tests/parity/status_compatible_build.json new file mode 100644 index 0000000..bb2bab2 --- /dev/null +++ b/native/core/tests/parity/status_compatible_build.json @@ -0,0 +1,459 @@ +{ + "expected": { + "checkpoints": [ + { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "logicalSha256": "66b95eb4667bd5b3b3d230e7bed1d29ccd261d48ca2699f01216c863be24ff44", + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "2ce2978309fe6dd6e7d2b964aff297c1d377ef934a442cd3a61333de90f48af9", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:compatible\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + } + ], + "filesystem": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "logicalSha256": "66b95eb4667bd5b3b3d230e7bed1d29ccd261d48ca2699f01216c863be24ff44", + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "2ce2978309fe6dd6e7d2b964aff297c1d377ef934a442cd3a61333de90f48af9", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:compatible\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "responses": [ + { + "activeLaunchProfile": "default", + "advanced": { + "configReadonly": false, + "maxFrameLatency": 1, + "reduceCpu": false, + "smoothFramerate": false, + "unlockOptions": false, + "vramLimited": false, + "vramTarget": 100 + }, + "appVersion": "v1.3.4", + "configExists": true, + "currentLaunchOptions": "", + "dxvk": { + "confExists": false, + "installed": false, + "settings": { + "enableAsync": true, + "gplAsyncCache": true, + "hudEnabled": false, + "maxFrameLatency": 1, + "maxFrameRate": 0, + "numCompilerThreads": 0, + "tearFree": "True" + } + }, + "enhanced": { + "acknowledgedAt": null, + "backupStatus": "Not created", + "detectedAt": null, + "dumpSource": "", + "filesInstalled": 0, + "installed": false, + "launchOptionsActive": false + }, + "exeSwap": { + "activeBuildDate": "Mar 3, 2023", + "activeBuildId": "10650222", + "backupAvailable": false, + "compatibleActive": true, + "compatibleAvailable": false, + "compatibleBuildDate": "Mar 3, 2023", + "compatibleBuildId": "10650222", + "currentBuildDate": "Sep 10, 2026", + "currentBuildId": "24784313", + "displayLabel": "Executable matches compatible BuildID 10650222. T7 installs will use T7 Patch 2.04.", + "enhancedActive": false, + "enhancedAvailable": false, + "enhancedBuildDate": "", + "enhancedBuildId": "Enhanced", + "enhancedExeActive": false, + "executable": "$ROOT/game/BlackOpsIII.exe", + "executableHash": "66b95eb4667bd5b3b3d230e7bed1d29ccd261d48ca2699f01216c863be24ff44", + "executableName": "BlackOpsIII.exe", + "integrityMessage": "Executable matches compatible BuildID 10650222.", + "integrityStatus": "trusted", + "latestAvailable": false, + "modeLabel": "Compatible EXE", + "patchLabel": "T7 Patch 2.04", + "profile": "compatible", + "state": "trusted", + "trustedExecutable": true + }, + "gameDetected": true, + "gameDir": "$ROOT/game", + "graphics": { + "displayMode": 1, + "drawFps": false, + "fov": 80, + "maxFps": 165, + "refreshRate": 60.0, + "renderResolution": 100, + "resolution": "1920x1080", + "vsync": true + }, + "launchProfiles": [ + { + "active": true, + "id": "default", + "installed": true, + "label": "Default (None)", + "option": "", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "offline", + "installed": true, + "label": "Play Offline", + "option": "+set fs_game offlinemp", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "all_around", + "installed": false, + "label": "All-around Enhancement Lite", + "option": "+set fs_game 2994481309", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "ultimate", + "installed": false, + "label": "Ultimate Experience Mod", + "option": "+set fs_game 2942053577", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "reforged", + "installed": false, + "label": "BO3 Reforged", + "option": "+set fs_game 3667377161", + "path": null, + "state": "Not Subscribed", + "subscribed": false + } + ], + "logPath": "$ROOT/data/PatchOpsIII.log", + "logs": [], + "maintenance": { + "logPayload": "PatchOpsIII v1.3.4 - Linux $KERNEL (x86_64) logs:\n```\n(no log entries found)\n```", + "modFilesDir": "$ROOT/data/BO3 Mod Files" + }, + "mods": { + "dxvk": false, + "enhanced": false, + "t7Patch": false + }, + "platform": "Linux", + "presets": [ + "Smooth Preset", + "Low-Latency Preset" + ], + "qol": { + "allIntros": false, + "d3dcompiler": false, + "intro": false + }, + "releaseChannel": "stable", + "steamUserId": "76561198000000000", + "t7": { + "colorCode": "", + "confExists": false, + "friendsOnly": false, + "gamertag": "", + "installed": false, + "mode": "Custom", + "networkPassword": "", + "plainName": "" + } + } + ] + }, + "fixture": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "logicalSha256": "66b95eb4667bd5b3b3d230e7bed1d29ccd261d48ca2699f01216c863be24ff44", + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "2ce2978309fe6dd6e7d2b964aff297c1d377ef934a442cd3a61333de90f48af9", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:compatible\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "name": "status_compatible_build", + "operations": [ + { + "method": "GET", + "path": "/api/status" + } + ], + "runner": "direct endpoint functions", + "rustIgnore": "fixture uses a logical SHA-256 alias because redistributing the game EXE is impossible", + "schemaVersion": 1, + "sourceHashes": { + "backend/api.py": "080be5c4a3b929134298179cd3f4d1558c288a50dcdb38978ada831925a57a46", + "bo3_enhanced.py": "32a670c8a63a64216ee5c46aa000d2e3840ca348ca0fd5031c81699252cf57b8", + "dxvk_manager.py": "8e150458bc7480ac89cd9e64c558ba438f61a09611bbf24b962cba02d7e4a5e5", + "t7_patch.py": "73e66fadd1c0775742b7bb6d9c6c73a3c9df8304d3117abd8ac768dfc68cc3ea", + "utils.py": "313069c3f70b92888239c9ef15ce2379713b029e019426e76b386f088b556564" + }, + "sourceOfTruth": "Python backend on main" +} diff --git a/native/core/tests/parity/status_config_variants.json b/native/core/tests/parity/status_config_variants.json new file mode 100644 index 0000000..403ecfe --- /dev/null +++ b/native/core/tests/parity/status_config_variants.json @@ -0,0 +1,455 @@ +{ + "expected": { + "checkpoints": [ + { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "4a2fe5a4005cd7e1eed0bbfc814e244f70b968e690de376c800cced1eb402155", + "size": 180, + "text": "// malformed and decimal input\nMaxFPS = \"144.9\"\nFOV = \"bad\"\nRefreshRate = \"143.98\"\nVideoMemory = \"0.85\"\nStreamMinResident = \"1\"\nRestrictGraphicsOptions = \"0\"\nSerializeRender = \"2\"\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + } + ], + "filesystem": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "4a2fe5a4005cd7e1eed0bbfc814e244f70b968e690de376c800cced1eb402155", + "size": 180, + "text": "// malformed and decimal input\nMaxFPS = \"144.9\"\nFOV = \"bad\"\nRefreshRate = \"143.98\"\nVideoMemory = \"0.85\"\nStreamMinResident = \"1\"\nRestrictGraphicsOptions = \"0\"\nSerializeRender = \"2\"\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "responses": [ + { + "activeLaunchProfile": "default", + "advanced": { + "configReadonly": false, + "maxFrameLatency": 1, + "reduceCpu": true, + "smoothFramerate": false, + "unlockOptions": true, + "vramLimited": true, + "vramTarget": 85 + }, + "appVersion": "v1.3.4", + "configExists": true, + "currentLaunchOptions": "", + "dxvk": { + "confExists": false, + "installed": false, + "settings": { + "enableAsync": true, + "gplAsyncCache": true, + "hudEnabled": false, + "maxFrameLatency": 1, + "maxFrameRate": 0, + "numCompilerThreads": 0, + "tearFree": "True" + } + }, + "enhanced": { + "acknowledgedAt": null, + "backupStatus": "Not created", + "detectedAt": null, + "dumpSource": "", + "filesInstalled": 0, + "installed": false, + "launchOptionsActive": false + }, + "exeSwap": { + "activeBuildDate": "", + "activeBuildId": "Unverified", + "backupAvailable": false, + "compatibleActive": false, + "compatibleAvailable": false, + "compatibleBuildDate": "Mar 3, 2023", + "compatibleBuildId": "10650222", + "currentBuildDate": "Sep 10, 2026", + "currentBuildId": "24784313", + "displayLabel": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE. T7 installs will use T7 Patch Scroptss/T7Patch.", + "enhancedActive": false, + "enhancedAvailable": false, + "enhancedBuildDate": "", + "enhancedBuildId": "Enhanced", + "enhancedExeActive": false, + "executable": "$ROOT/game/BlackOpsIII.exe", + "executableHash": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "executableName": "BlackOpsIII.exe", + "integrityMessage": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE.", + "integrityStatus": "unverified", + "latestAvailable": false, + "modeLabel": "Unknown", + "patchLabel": "T7 Patch Scroptss/T7Patch", + "profile": "", + "state": "unverified", + "trustedExecutable": false + }, + "gameDetected": true, + "gameDir": "$ROOT/game", + "graphics": { + "displayMode": 1, + "drawFps": false, + "fov": 80, + "maxFps": 144, + "refreshRate": 143.98, + "renderResolution": 100, + "resolution": "1920x1080", + "vsync": true + }, + "launchProfiles": [ + { + "active": true, + "id": "default", + "installed": true, + "label": "Default (None)", + "option": "", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "offline", + "installed": true, + "label": "Play Offline", + "option": "+set fs_game offlinemp", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "all_around", + "installed": false, + "label": "All-around Enhancement Lite", + "option": "+set fs_game 2994481309", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "ultimate", + "installed": false, + "label": "Ultimate Experience Mod", + "option": "+set fs_game 2942053577", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "reforged", + "installed": false, + "label": "BO3 Reforged", + "option": "+set fs_game 3667377161", + "path": null, + "state": "Not Subscribed", + "subscribed": false + } + ], + "logPath": "$ROOT/data/PatchOpsIII.log", + "logs": [], + "maintenance": { + "logPayload": "PatchOpsIII v1.3.4 - Linux $KERNEL (x86_64) logs:\n```\n(no log entries found)\n```", + "modFilesDir": "$ROOT/data/BO3 Mod Files" + }, + "mods": { + "dxvk": false, + "enhanced": false, + "t7Patch": false + }, + "platform": "Linux", + "presets": [ + "Smooth Preset", + "Low-Latency Preset" + ], + "qol": { + "allIntros": false, + "d3dcompiler": false, + "intro": false + }, + "releaseChannel": "stable", + "steamUserId": "76561198000000000", + "t7": { + "colorCode": "", + "confExists": false, + "friendsOnly": false, + "gamertag": "", + "installed": false, + "mode": "Custom", + "networkPassword": "", + "plainName": "" + } + } + ] + }, + "fixture": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "4a2fe5a4005cd7e1eed0bbfc814e244f70b968e690de376c800cced1eb402155", + "size": 180, + "text": "// malformed and decimal input\nMaxFPS = \"144.9\"\nFOV = \"bad\"\nRefreshRate = \"143.98\"\nVideoMemory = \"0.85\"\nStreamMinResident = \"1\"\nRestrictGraphicsOptions = \"0\"\nSerializeRender = \"2\"\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "name": "status_config_variants", + "operations": [ + { + "method": "GET", + "path": "/api/status" + } + ], + "runner": "direct endpoint functions", + "schemaVersion": 1, + "sourceHashes": { + "backend/api.py": "080be5c4a3b929134298179cd3f4d1558c288a50dcdb38978ada831925a57a46", + "bo3_enhanced.py": "32a670c8a63a64216ee5c46aa000d2e3840ca348ca0fd5031c81699252cf57b8", + "dxvk_manager.py": "8e150458bc7480ac89cd9e64c558ba438f61a09611bbf24b962cba02d7e4a5e5", + "t7_patch.py": "73e66fadd1c0775742b7bb6d9c6c73a3c9df8304d3117abd8ac768dfc68cc3ea", + "utils.py": "313069c3f70b92888239c9ef15ce2379713b029e019426e76b386f088b556564" + }, + "sourceOfTruth": "Python backend on main" +} diff --git a/native/core/tests/parity/status_dxvk_installed.json b/native/core/tests/parity/status_dxvk_installed.json new file mode 100644 index 0000000..71bc78a --- /dev/null +++ b/native/core/tests/parity/status_dxvk_installed.json @@ -0,0 +1,518 @@ +{ + "expected": { + "checkpoints": [ + { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3d11.dll", + "sha256": "dc5515425bf1f3f94009300d02aaab926e2c7b60a7c11dc1bd2e7d2005fcc510", + "size": 13, + "text": "fixture-d3d11" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "base64": "AP9maXh0dXJlLWR4Z2k=", + "mode": 420, + "path": "game/dxgi.dll", + "sha256": "e7e2f73af077301aedc20be31fc767cb105d7e99b2c7969429cc5074b85e3f0f", + "size": 14 + }, + { + "mode": 420, + "path": "game/dxvk.conf", + "sha256": "f0a2ee076de3a6b372fe91658dea9c64d8d0a2cd86bdacbf4082d17c9c60b524", + "size": 45, + "text": "dxvk.enableAsync=true\ndxgi.maxFrameLatency=1\n" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + } + ], + "filesystem": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3d11.dll", + "sha256": "dc5515425bf1f3f94009300d02aaab926e2c7b60a7c11dc1bd2e7d2005fcc510", + "size": 13, + "text": "fixture-d3d11" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "base64": "AP9maXh0dXJlLWR4Z2k=", + "mode": 420, + "path": "game/dxgi.dll", + "sha256": "e7e2f73af077301aedc20be31fc767cb105d7e99b2c7969429cc5074b85e3f0f", + "size": 14 + }, + { + "mode": 420, + "path": "game/dxvk.conf", + "sha256": "f0a2ee076de3a6b372fe91658dea9c64d8d0a2cd86bdacbf4082d17c9c60b524", + "size": 45, + "text": "dxvk.enableAsync=true\ndxgi.maxFrameLatency=1\n" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "responses": [ + { + "activeLaunchProfile": "default", + "advanced": { + "configReadonly": false, + "maxFrameLatency": 1, + "reduceCpu": false, + "smoothFramerate": false, + "unlockOptions": false, + "vramLimited": false, + "vramTarget": 100 + }, + "appVersion": "v1.3.4", + "configExists": true, + "currentLaunchOptions": "", + "dxvk": { + "confExists": true, + "installed": true, + "settings": { + "enableAsync": true, + "gplAsyncCache": true, + "hudEnabled": false, + "maxFrameLatency": 1, + "maxFrameRate": 0, + "numCompilerThreads": 0, + "tearFree": "True" + } + }, + "enhanced": { + "acknowledgedAt": null, + "backupStatus": "Not created", + "detectedAt": null, + "dumpSource": "", + "filesInstalled": 0, + "installed": false, + "launchOptionsActive": false + }, + "exeSwap": { + "activeBuildDate": "", + "activeBuildId": "Unverified", + "backupAvailable": false, + "compatibleActive": false, + "compatibleAvailable": false, + "compatibleBuildDate": "Mar 3, 2023", + "compatibleBuildId": "10650222", + "currentBuildDate": "Sep 10, 2026", + "currentBuildId": "24784313", + "displayLabel": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE. T7 installs will use T7 Patch Scroptss/T7Patch.", + "enhancedActive": false, + "enhancedAvailable": false, + "enhancedBuildDate": "", + "enhancedBuildId": "Enhanced", + "enhancedExeActive": false, + "executable": "$ROOT/game/BlackOpsIII.exe", + "executableHash": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "executableName": "BlackOpsIII.exe", + "integrityMessage": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE.", + "integrityStatus": "unverified", + "latestAvailable": false, + "modeLabel": "Unknown", + "patchLabel": "T7 Patch Scroptss/T7Patch", + "profile": "", + "state": "unverified", + "trustedExecutable": false + }, + "gameDetected": true, + "gameDir": "$ROOT/game", + "graphics": { + "displayMode": 1, + "drawFps": false, + "fov": 80, + "maxFps": 165, + "refreshRate": 60.0, + "renderResolution": 100, + "resolution": "1920x1080", + "vsync": true + }, + "launchProfiles": [ + { + "active": true, + "id": "default", + "installed": true, + "label": "Default (None)", + "option": "", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "offline", + "installed": true, + "label": "Play Offline", + "option": "+set fs_game offlinemp", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "all_around", + "installed": false, + "label": "All-around Enhancement Lite", + "option": "+set fs_game 2994481309", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "ultimate", + "installed": false, + "label": "Ultimate Experience Mod", + "option": "+set fs_game 2942053577", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "reforged", + "installed": false, + "label": "BO3 Reforged", + "option": "+set fs_game 3667377161", + "path": null, + "state": "Not Subscribed", + "subscribed": false + } + ], + "logPath": "$ROOT/data/PatchOpsIII.log", + "logs": [], + "maintenance": { + "logPayload": "PatchOpsIII v1.3.4 - Linux $KERNEL (x86_64) logs:\n```\n(no log entries found)\n```", + "modFilesDir": "$ROOT/data/BO3 Mod Files" + }, + "mods": { + "dxvk": true, + "enhanced": false, + "t7Patch": false + }, + "platform": "Linux", + "presets": [ + "Smooth Preset", + "Low-Latency Preset" + ], + "qol": { + "allIntros": false, + "d3dcompiler": false, + "intro": false + }, + "releaseChannel": "stable", + "steamUserId": "76561198000000000", + "t7": { + "colorCode": "", + "confExists": false, + "friendsOnly": false, + "gamertag": "", + "installed": false, + "mode": "Custom", + "networkPassword": "", + "plainName": "" + } + } + ] + }, + "fixture": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3d11.dll", + "sha256": "dc5515425bf1f3f94009300d02aaab926e2c7b60a7c11dc1bd2e7d2005fcc510", + "size": 13, + "text": "fixture-d3d11" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "base64": "AP9maXh0dXJlLWR4Z2k=", + "mode": 420, + "path": "game/dxgi.dll", + "sha256": "e7e2f73af077301aedc20be31fc767cb105d7e99b2c7969429cc5074b85e3f0f", + "size": 14 + }, + { + "mode": 420, + "path": "game/dxvk.conf", + "sha256": "f0a2ee076de3a6b372fe91658dea9c64d8d0a2cd86bdacbf4082d17c9c60b524", + "size": 45, + "text": "dxvk.enableAsync=true\ndxgi.maxFrameLatency=1\n" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "name": "status_dxvk_installed", + "operations": [ + { + "method": "GET", + "path": "/api/status" + } + ], + "runner": "direct endpoint functions", + "schemaVersion": 1, + "sourceHashes": { + "backend/api.py": "080be5c4a3b929134298179cd3f4d1558c288a50dcdb38978ada831925a57a46", + "bo3_enhanced.py": "32a670c8a63a64216ee5c46aa000d2e3840ca348ca0fd5031c81699252cf57b8", + "dxvk_manager.py": "8e150458bc7480ac89cd9e64c558ba438f61a09611bbf24b962cba02d7e4a5e5", + "t7_patch.py": "73e66fadd1c0775742b7bb6d9c6c73a3c9df8304d3117abd8ac768dfc68cc3ea", + "utils.py": "313069c3f70b92888239c9ef15ce2379713b029e019426e76b386f088b556564" + }, + "sourceOfTruth": "Python backend on main" +} diff --git a/native/core/tests/parity/status_empty_config.json b/native/core/tests/parity/status_empty_config.json new file mode 100644 index 0000000..9f91ec2 --- /dev/null +++ b/native/core/tests/parity/status_empty_config.json @@ -0,0 +1,455 @@ +{ + "expected": { + "checkpoints": [ + { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "size": 0, + "text": "" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + } + ], + "filesystem": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "size": 0, + "text": "" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "responses": [ + { + "activeLaunchProfile": "default", + "advanced": { + "configReadonly": false, + "maxFrameLatency": 1, + "reduceCpu": false, + "smoothFramerate": false, + "unlockOptions": false, + "vramLimited": false, + "vramTarget": 75 + }, + "appVersion": "v1.3.4", + "configExists": false, + "currentLaunchOptions": "", + "dxvk": { + "confExists": false, + "installed": false, + "settings": { + "enableAsync": true, + "gplAsyncCache": true, + "hudEnabled": false, + "maxFrameLatency": 1, + "maxFrameRate": 0, + "numCompilerThreads": 0, + "tearFree": "True" + } + }, + "enhanced": { + "acknowledgedAt": null, + "backupStatus": "Not created", + "detectedAt": null, + "dumpSource": "", + "filesInstalled": 0, + "installed": false, + "launchOptionsActive": false + }, + "exeSwap": { + "activeBuildDate": "", + "activeBuildId": "Unverified", + "backupAvailable": false, + "compatibleActive": false, + "compatibleAvailable": false, + "compatibleBuildDate": "Mar 3, 2023", + "compatibleBuildId": "10650222", + "currentBuildDate": "Sep 10, 2026", + "currentBuildId": "24784313", + "displayLabel": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE. T7 installs will use T7 Patch Scroptss/T7Patch.", + "enhancedActive": false, + "enhancedAvailable": false, + "enhancedBuildDate": "", + "enhancedBuildId": "Enhanced", + "enhancedExeActive": false, + "executable": "$ROOT/game/BlackOpsIII.exe", + "executableHash": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "executableName": "BlackOpsIII.exe", + "integrityMessage": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE.", + "integrityStatus": "unverified", + "latestAvailable": false, + "modeLabel": "Unknown", + "patchLabel": "T7 Patch Scroptss/T7Patch", + "profile": "", + "state": "unverified", + "trustedExecutable": false + }, + "gameDetected": true, + "gameDir": "$ROOT/game", + "graphics": { + "displayMode": 1, + "drawFps": false, + "fov": 80, + "maxFps": 165, + "refreshRate": 60.0, + "renderResolution": 100, + "resolution": "1920x1080", + "vsync": true + }, + "launchProfiles": [ + { + "active": true, + "id": "default", + "installed": true, + "label": "Default (None)", + "option": "", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "offline", + "installed": true, + "label": "Play Offline", + "option": "+set fs_game offlinemp", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "all_around", + "installed": false, + "label": "All-around Enhancement Lite", + "option": "+set fs_game 2994481309", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "ultimate", + "installed": false, + "label": "Ultimate Experience Mod", + "option": "+set fs_game 2942053577", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "reforged", + "installed": false, + "label": "BO3 Reforged", + "option": "+set fs_game 3667377161", + "path": null, + "state": "Not Subscribed", + "subscribed": false + } + ], + "logPath": "$ROOT/data/PatchOpsIII.log", + "logs": [], + "maintenance": { + "logPayload": "PatchOpsIII v1.3.4 - Linux $KERNEL (x86_64) logs:\n```\n(no log entries found)\n```", + "modFilesDir": "$ROOT/data/BO3 Mod Files" + }, + "mods": { + "dxvk": false, + "enhanced": false, + "t7Patch": false + }, + "platform": "Linux", + "presets": [ + "Smooth Preset", + "Low-Latency Preset" + ], + "qol": { + "allIntros": false, + "d3dcompiler": false, + "intro": false + }, + "releaseChannel": "stable", + "steamUserId": "76561198000000000", + "t7": { + "colorCode": "", + "confExists": false, + "friendsOnly": false, + "gamertag": "", + "installed": false, + "mode": "Custom", + "networkPassword": "", + "plainName": "" + } + } + ] + }, + "fixture": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "size": 0, + "text": "" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "name": "status_empty_config", + "operations": [ + { + "method": "GET", + "path": "/api/status" + } + ], + "runner": "direct endpoint functions", + "schemaVersion": 1, + "sourceHashes": { + "backend/api.py": "080be5c4a3b929134298179cd3f4d1558c288a50dcdb38978ada831925a57a46", + "bo3_enhanced.py": "32a670c8a63a64216ee5c46aa000d2e3840ca348ca0fd5031c81699252cf57b8", + "dxvk_manager.py": "8e150458bc7480ac89cd9e64c558ba438f61a09611bbf24b962cba02d7e4a5e5", + "t7_patch.py": "73e66fadd1c0775742b7bb6d9c6c73a3c9df8304d3117abd8ac768dfc68cc3ea", + "utils.py": "313069c3f70b92888239c9ef15ce2379713b029e019426e76b386f088b556564" + }, + "sourceOfTruth": "Python backend on main" +} diff --git a/native/core/tests/parity/status_enhanced_installed.json b/native/core/tests/parity/status_enhanced_installed.json new file mode 100644 index 0000000..2e2dfdd --- /dev/null +++ b/native/core/tests/parity/status_enhanced_installed.json @@ -0,0 +1,560 @@ +{ + "expected": { + "checkpoints": [ + { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/bo3_enhanced_state.json", + "sha256": "070b5e85e4b9b360468cf37790c7684f6a0b95bbbb37ed1890c1c6c96f3f433c", + "size": 206, + "text": "{\"installed\": true, \"detected_at\": \"2026-09-10T12:00:00Z\", \"acknowledged_at\": \"2026-09-10T13:00:00Z\", \"installed_files\": [\"T7InternalWS.dll\", \"T7WSBootstrapper.dll\", \"WindowsCodecs.dll\", \"steam_api65.dll\"]}" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/T7InternalWS.dll", + "sha256": "cf3a76345cf49ba68f8e1f8476b3b5566e2c80cf70b7c60eb5bd2185df31779e", + "size": 16, + "text": "fixture-enhanced" + }, + { + "mode": 420, + "path": "game/T7WSBootstrapper.dll", + "sha256": "cf3a76345cf49ba68f8e1f8476b3b5566e2c80cf70b7c60eb5bd2185df31779e", + "size": 16, + "text": "fixture-enhanced" + }, + { + "mode": 420, + "path": "game/WindowsCodecs.dll", + "sha256": "cf3a76345cf49ba68f8e1f8476b3b5566e2c80cf70b7c60eb5bd2185df31779e", + "size": 16, + "text": "fixture-enhanced" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/steam_api65.dll", + "sha256": "cf3a76345cf49ba68f8e1f8476b3b5566e2c80cf70b7c60eb5bd2185df31779e", + "size": 16, + "text": "fixture-enhanced" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + } + ], + "filesystem": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/bo3_enhanced_state.json", + "sha256": "070b5e85e4b9b360468cf37790c7684f6a0b95bbbb37ed1890c1c6c96f3f433c", + "size": 206, + "text": "{\"installed\": true, \"detected_at\": \"2026-09-10T12:00:00Z\", \"acknowledged_at\": \"2026-09-10T13:00:00Z\", \"installed_files\": [\"T7InternalWS.dll\", \"T7WSBootstrapper.dll\", \"WindowsCodecs.dll\", \"steam_api65.dll\"]}" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/T7InternalWS.dll", + "sha256": "cf3a76345cf49ba68f8e1f8476b3b5566e2c80cf70b7c60eb5bd2185df31779e", + "size": 16, + "text": "fixture-enhanced" + }, + { + "mode": 420, + "path": "game/T7WSBootstrapper.dll", + "sha256": "cf3a76345cf49ba68f8e1f8476b3b5566e2c80cf70b7c60eb5bd2185df31779e", + "size": 16, + "text": "fixture-enhanced" + }, + { + "mode": 420, + "path": "game/WindowsCodecs.dll", + "sha256": "cf3a76345cf49ba68f8e1f8476b3b5566e2c80cf70b7c60eb5bd2185df31779e", + "size": 16, + "text": "fixture-enhanced" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/steam_api65.dll", + "sha256": "cf3a76345cf49ba68f8e1f8476b3b5566e2c80cf70b7c60eb5bd2185df31779e", + "size": 16, + "text": "fixture-enhanced" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "responses": [ + { + "activeLaunchProfile": "default", + "advanced": { + "configReadonly": false, + "maxFrameLatency": 1, + "reduceCpu": false, + "smoothFramerate": false, + "unlockOptions": false, + "vramLimited": false, + "vramTarget": 100 + }, + "appVersion": "v1.3.4", + "configExists": true, + "currentLaunchOptions": "", + "dxvk": { + "confExists": false, + "installed": false, + "settings": { + "enableAsync": true, + "gplAsyncCache": true, + "hudEnabled": false, + "maxFrameLatency": 1, + "maxFrameRate": 0, + "numCompilerThreads": 0, + "tearFree": "True" + } + }, + "enhanced": { + "acknowledgedAt": "2026-09-10T13:00:00Z", + "backupStatus": "Created", + "detectedAt": "2026-09-10T12:00:00Z", + "dumpSource": "", + "filesInstalled": 4, + "installed": true, + "launchOptionsActive": false + }, + "exeSwap": { + "activeBuildDate": "", + "activeBuildId": "Enhanced", + "backupAvailable": false, + "compatibleActive": false, + "compatibleAvailable": false, + "compatibleBuildDate": "Mar 3, 2023", + "compatibleBuildId": "10650222", + "currentBuildDate": "Sep 10, 2026", + "currentBuildId": "24784313", + "displayLabel": "Executable is managed by BO3 Enhanced. T7 installs will use T7 Patch Scroptss/T7Patch.", + "enhancedActive": true, + "enhancedAvailable": true, + "enhancedBuildDate": "", + "enhancedBuildId": "Enhanced", + "enhancedExeActive": true, + "executable": "$ROOT/game/BlackOpsIII.exe", + "executableHash": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "executableName": "BlackOpsIII.exe", + "integrityMessage": "Executable is managed by BO3 Enhanced.", + "integrityStatus": "enhanced", + "latestAvailable": false, + "modeLabel": "Enhanced", + "patchLabel": "T7 Patch Scroptss/T7Patch", + "profile": "enhanced", + "state": "enhanced", + "trustedExecutable": true + }, + "gameDetected": true, + "gameDir": "$ROOT/game", + "graphics": { + "displayMode": 1, + "drawFps": false, + "fov": 80, + "maxFps": 165, + "refreshRate": 60.0, + "renderResolution": 100, + "resolution": "1920x1080", + "vsync": true + }, + "launchProfiles": [ + { + "active": true, + "id": "default", + "installed": true, + "label": "Default (None)", + "option": "", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "offline", + "installed": true, + "label": "Play Offline", + "option": "+set fs_game offlinemp", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "all_around", + "installed": false, + "label": "All-around Enhancement Lite", + "option": "+set fs_game 2994481309", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "ultimate", + "installed": false, + "label": "Ultimate Experience Mod", + "option": "+set fs_game 2942053577", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "reforged", + "installed": false, + "label": "BO3 Reforged", + "option": "+set fs_game 3667377161", + "path": null, + "state": "Not Subscribed", + "subscribed": false + } + ], + "logPath": "$ROOT/data/PatchOpsIII.log", + "logs": [], + "maintenance": { + "logPayload": "PatchOpsIII v1.3.4 - Linux $KERNEL (x86_64) logs:\n```\n(no log entries found)\n```", + "modFilesDir": "$ROOT/data/BO3 Mod Files" + }, + "mods": { + "dxvk": false, + "enhanced": true, + "t7Patch": false + }, + "platform": "Linux", + "presets": [ + "Smooth Preset", + "Low-Latency Preset" + ], + "qol": { + "allIntros": false, + "d3dcompiler": false, + "intro": false + }, + "releaseChannel": "stable", + "steamUserId": "76561198000000000", + "t7": { + "colorCode": "", + "confExists": false, + "friendsOnly": false, + "gamertag": "", + "installed": false, + "mode": "Enhanced", + "networkPassword": "", + "plainName": "" + } + } + ] + }, + "fixture": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/bo3_enhanced_state.json", + "sha256": "070b5e85e4b9b360468cf37790c7684f6a0b95bbbb37ed1890c1c6c96f3f433c", + "size": 206, + "text": "{\"installed\": true, \"detected_at\": \"2026-09-10T12:00:00Z\", \"acknowledged_at\": \"2026-09-10T13:00:00Z\", \"installed_files\": [\"T7InternalWS.dll\", \"T7WSBootstrapper.dll\", \"WindowsCodecs.dll\", \"steam_api65.dll\"]}" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/T7InternalWS.dll", + "sha256": "cf3a76345cf49ba68f8e1f8476b3b5566e2c80cf70b7c60eb5bd2185df31779e", + "size": 16, + "text": "fixture-enhanced" + }, + { + "mode": 420, + "path": "game/T7WSBootstrapper.dll", + "sha256": "cf3a76345cf49ba68f8e1f8476b3b5566e2c80cf70b7c60eb5bd2185df31779e", + "size": 16, + "text": "fixture-enhanced" + }, + { + "mode": 420, + "path": "game/WindowsCodecs.dll", + "sha256": "cf3a76345cf49ba68f8e1f8476b3b5566e2c80cf70b7c60eb5bd2185df31779e", + "size": 16, + "text": "fixture-enhanced" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/steam_api65.dll", + "sha256": "cf3a76345cf49ba68f8e1f8476b3b5566e2c80cf70b7c60eb5bd2185df31779e", + "size": 16, + "text": "fixture-enhanced" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "name": "status_enhanced_installed", + "operations": [ + { + "method": "GET", + "path": "/api/status" + } + ], + "runner": "direct endpoint functions", + "schemaVersion": 1, + "sourceHashes": { + "backend/api.py": "080be5c4a3b929134298179cd3f4d1558c288a50dcdb38978ada831925a57a46", + "bo3_enhanced.py": "32a670c8a63a64216ee5c46aa000d2e3840ca348ca0fd5031c81699252cf57b8", + "dxvk_manager.py": "8e150458bc7480ac89cd9e64c558ba438f61a09611bbf24b962cba02d7e4a5e5", + "t7_patch.py": "73e66fadd1c0775742b7bb6d9c6c73a3c9df8304d3117abd8ac768dfc68cc3ea", + "utils.py": "313069c3f70b92888239c9ef15ce2379713b029e019426e76b386f088b556564" + }, + "sourceOfTruth": "Python backend on main" +} diff --git a/native/core/tests/parity/status_no_game.json b/native/core/tests/parity/status_no_game.json new file mode 100644 index 0000000..7ebf16c --- /dev/null +++ b/native/core/tests/parity/status_no_game.json @@ -0,0 +1,341 @@ +{ + "expected": { + "checkpoints": [ + { + "directories": [ + "data", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "99e02132d604629c806d118cb06ecedc18295f28afa54906c43af1d730650210", + "size": 33, + "text": "{\n \"release_channel\": \"stable\"\n}" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + } + ], + "filesystem": { + "directories": [ + "data", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "99e02132d604629c806d118cb06ecedc18295f28afa54906c43af1d730650210", + "size": 33, + "text": "{\n \"release_channel\": \"stable\"\n}" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "responses": [ + { + "activeLaunchProfile": "default", + "advanced": { + "configReadonly": false, + "maxFrameLatency": 1, + "reduceCpu": false, + "smoothFramerate": false, + "unlockOptions": false, + "vramLimited": false, + "vramTarget": 75 + }, + "appVersion": "v1.3.4", + "configExists": false, + "currentLaunchOptions": "", + "dxvk": { + "confExists": false, + "installed": false, + "settings": { + "enableAsync": true, + "gplAsyncCache": true, + "hudEnabled": false, + "maxFrameLatency": 1, + "maxFrameRate": 0, + "numCompilerThreads": 0, + "tearFree": "True" + } + }, + "enhanced": { + "acknowledgedAt": null, + "backupStatus": "Not created", + "detectedAt": null, + "dumpSource": "", + "filesInstalled": 0, + "installed": false, + "launchOptionsActive": false + }, + "exeSwap": { + "activeBuildDate": "", + "activeBuildId": "Unknown", + "backupAvailable": false, + "compatibleActive": false, + "compatibleAvailable": false, + "compatibleBuildDate": "Mar 3, 2023", + "compatibleBuildId": "10650222", + "currentBuildDate": "Sep 10, 2026", + "currentBuildId": "24784313", + "displayLabel": "BlackOps3.exe or BlackOpsIII.exe was not found. T7 installs will use T7 Patch Scroptss/T7Patch.", + "enhancedActive": false, + "enhancedAvailable": false, + "enhancedBuildDate": "", + "enhancedBuildId": "Enhanced", + "enhancedExeActive": false, + "executable": "", + "executableHash": "", + "executableName": "", + "integrityMessage": "BlackOps3.exe or BlackOpsIII.exe was not found.", + "integrityStatus": "missing", + "latestAvailable": false, + "modeLabel": "Unknown", + "patchLabel": "T7 Patch Scroptss/T7Patch", + "profile": "", + "state": "missing", + "trustedExecutable": false + }, + "gameDetected": false, + "gameDir": null, + "graphics": { + "displayMode": 1, + "drawFps": false, + "fov": 80, + "maxFps": 165, + "refreshRate": 60.0, + "renderResolution": 100, + "resolution": "1920x1080", + "vsync": true + }, + "launchProfiles": [ + { + "active": true, + "id": "default", + "installed": true, + "label": "Default (None)", + "option": "", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "offline", + "installed": true, + "label": "Play Offline", + "option": "+set fs_game offlinemp", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "all_around", + "installed": false, + "label": "All-around Enhancement Lite", + "option": "+set fs_game 2994481309", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "ultimate", + "installed": false, + "label": "Ultimate Experience Mod", + "option": "+set fs_game 2942053577", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "reforged", + "installed": false, + "label": "BO3 Reforged", + "option": "+set fs_game 3667377161", + "path": null, + "state": "Not Subscribed", + "subscribed": false + } + ], + "logPath": "$ROOT/data/PatchOpsIII.log", + "logs": [], + "maintenance": { + "logPayload": "PatchOpsIII v1.3.4 - Linux $KERNEL (x86_64) logs:\n```\n(no log entries found)\n```", + "modFilesDir": "$ROOT/data/BO3 Mod Files" + }, + "mods": { + "dxvk": false, + "enhanced": false, + "t7Patch": false + }, + "platform": "Linux", + "presets": [ + "Smooth Preset", + "Low-Latency Preset" + ], + "qol": { + "allIntros": false, + "d3dcompiler": false, + "intro": false + }, + "releaseChannel": "stable", + "steamUserId": "76561198000000000", + "t7": { + "colorCode": "", + "confExists": false, + "friendsOnly": false, + "gamertag": "", + "installed": false, + "mode": "Unknown", + "networkPassword": "", + "plainName": "" + } + } + ] + }, + "fixture": { + "directories": [ + "data", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "99e02132d604629c806d118cb06ecedc18295f28afa54906c43af1d730650210", + "size": 33, + "text": "{\n \"release_channel\": \"stable\"\n}" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "name": "status_no_game", + "operations": [ + { + "method": "GET", + "path": "/api/status" + } + ], + "runner": "direct endpoint functions", + "schemaVersion": 1, + "sourceHashes": { + "backend/api.py": "080be5c4a3b929134298179cd3f4d1558c288a50dcdb38978ada831925a57a46", + "bo3_enhanced.py": "32a670c8a63a64216ee5c46aa000d2e3840ca348ca0fd5031c81699252cf57b8", + "dxvk_manager.py": "8e150458bc7480ac89cd9e64c558ba438f61a09611bbf24b962cba02d7e4a5e5", + "t7_patch.py": "73e66fadd1c0775742b7bb6d9c6c73a3c9df8304d3117abd8ac768dfc68cc3ea", + "utils.py": "313069c3f70b92888239c9ef15ce2379713b029e019426e76b386f088b556564" + }, + "sourceOfTruth": "Python backend on main" +} diff --git a/native/core/tests/parity/status_september_2026_current.json b/native/core/tests/parity/status_september_2026_current.json new file mode 100644 index 0000000..97350a5 --- /dev/null +++ b/native/core/tests/parity/status_september_2026_current.json @@ -0,0 +1,459 @@ +{ + "expected": { + "checkpoints": [ + { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "logicalSha256": "51ca63bbc660e0826943c60da67606f6bcb4b3b519528b5e0548c68c9423a323", + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "e9045b0361257ba833a2d0407c79a04fb795259c62df6d909c60a3d31073d66e", + "size": 26, + "text": "PATCHOPS_FAKE_EXE:current\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + } + ], + "filesystem": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "logicalSha256": "51ca63bbc660e0826943c60da67606f6bcb4b3b519528b5e0548c68c9423a323", + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "e9045b0361257ba833a2d0407c79a04fb795259c62df6d909c60a3d31073d66e", + "size": 26, + "text": "PATCHOPS_FAKE_EXE:current\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "responses": [ + { + "activeLaunchProfile": "default", + "advanced": { + "configReadonly": false, + "maxFrameLatency": 1, + "reduceCpu": false, + "smoothFramerate": false, + "unlockOptions": false, + "vramLimited": false, + "vramTarget": 100 + }, + "appVersion": "v1.3.4", + "configExists": true, + "currentLaunchOptions": "", + "dxvk": { + "confExists": false, + "installed": false, + "settings": { + "enableAsync": true, + "gplAsyncCache": true, + "hudEnabled": false, + "maxFrameLatency": 1, + "maxFrameRate": 0, + "numCompilerThreads": 0, + "tearFree": "True" + } + }, + "enhanced": { + "acknowledgedAt": null, + "backupStatus": "Not created", + "detectedAt": null, + "dumpSource": "", + "filesInstalled": 0, + "installed": false, + "launchOptionsActive": false + }, + "exeSwap": { + "activeBuildDate": "Sep 10, 2026", + "activeBuildId": "24784313", + "backupAvailable": false, + "compatibleActive": false, + "compatibleAvailable": false, + "compatibleBuildDate": "Mar 3, 2023", + "compatibleBuildId": "10650222", + "currentBuildDate": "Sep 10, 2026", + "currentBuildId": "24784313", + "displayLabel": "Executable matches latest Steam BuildID 24784313. T7 installs will use T7 Patch Scroptss/T7Patch.", + "enhancedActive": false, + "enhancedAvailable": false, + "enhancedBuildDate": "", + "enhancedBuildId": "Enhanced", + "enhancedExeActive": false, + "executable": "$ROOT/game/BlackOpsIII.exe", + "executableHash": "51ca63bbc660e0826943c60da67606f6bcb4b3b519528b5e0548c68c9423a323", + "executableName": "BlackOpsIII.exe", + "integrityMessage": "Executable matches latest Steam BuildID 24784313.", + "integrityStatus": "trusted", + "latestAvailable": false, + "modeLabel": "Current EXE", + "patchLabel": "T7 Patch Scroptss/T7Patch", + "profile": "current", + "state": "trusted", + "trustedExecutable": true + }, + "gameDetected": true, + "gameDir": "$ROOT/game", + "graphics": { + "displayMode": 1, + "drawFps": false, + "fov": 80, + "maxFps": 165, + "refreshRate": 60.0, + "renderResolution": 100, + "resolution": "1920x1080", + "vsync": true + }, + "launchProfiles": [ + { + "active": true, + "id": "default", + "installed": true, + "label": "Default (None)", + "option": "", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "offline", + "installed": true, + "label": "Play Offline", + "option": "+set fs_game offlinemp", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "all_around", + "installed": false, + "label": "All-around Enhancement Lite", + "option": "+set fs_game 2994481309", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "ultimate", + "installed": false, + "label": "Ultimate Experience Mod", + "option": "+set fs_game 2942053577", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "reforged", + "installed": false, + "label": "BO3 Reforged", + "option": "+set fs_game 3667377161", + "path": null, + "state": "Not Subscribed", + "subscribed": false + } + ], + "logPath": "$ROOT/data/PatchOpsIII.log", + "logs": [], + "maintenance": { + "logPayload": "PatchOpsIII v1.3.4 - Linux $KERNEL (x86_64) logs:\n```\n(no log entries found)\n```", + "modFilesDir": "$ROOT/data/BO3 Mod Files" + }, + "mods": { + "dxvk": false, + "enhanced": false, + "t7Patch": false + }, + "platform": "Linux", + "presets": [ + "Smooth Preset", + "Low-Latency Preset" + ], + "qol": { + "allIntros": false, + "d3dcompiler": false, + "intro": false + }, + "releaseChannel": "stable", + "steamUserId": "76561198000000000", + "t7": { + "colorCode": "", + "confExists": false, + "friendsOnly": false, + "gamertag": "", + "installed": false, + "mode": "Default", + "networkPassword": "", + "plainName": "" + } + } + ] + }, + "fixture": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "logicalSha256": "51ca63bbc660e0826943c60da67606f6bcb4b3b519528b5e0548c68c9423a323", + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "e9045b0361257ba833a2d0407c79a04fb795259c62df6d909c60a3d31073d66e", + "size": 26, + "text": "PATCHOPS_FAKE_EXE:current\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "name": "status_september_2026_current", + "operations": [ + { + "method": "GET", + "path": "/api/status" + } + ], + "runner": "direct endpoint functions", + "rustIgnore": "fixture uses a logical SHA-256 alias because redistributing the game EXE is impossible", + "schemaVersion": 1, + "sourceHashes": { + "backend/api.py": "080be5c4a3b929134298179cd3f4d1558c288a50dcdb38978ada831925a57a46", + "bo3_enhanced.py": "32a670c8a63a64216ee5c46aa000d2e3840ca348ca0fd5031c81699252cf57b8", + "dxvk_manager.py": "8e150458bc7480ac89cd9e64c558ba438f61a09611bbf24b962cba02d7e4a5e5", + "t7_patch.py": "73e66fadd1c0775742b7bb6d9c6c73a3c9df8304d3117abd8ac768dfc68cc3ea", + "utils.py": "313069c3f70b92888239c9ef15ce2379713b029e019426e76b386f088b556564" + }, + "sourceOfTruth": "Python backend on main" +} diff --git a/native/core/tests/parity/status_steam_secondary_library.json b/native/core/tests/parity/status_steam_secondary_library.json new file mode 100644 index 0000000..e391919 --- /dev/null +++ b/native/core/tests/parity/status_steam_secondary_library.json @@ -0,0 +1,458 @@ +{ + "expected": { + "checkpoints": [ + { + "directories": [ + "data", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/common", + "secondary-library/steamapps/common/Call of Duty Black Ops III", + "secondary-library/steamapps/common/Call of Duty Black Ops III/players", + "secondary-library/steamapps/common/Call of Duty Black Ops III/video", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "99e02132d604629c806d118cb06ecedc18295f28afa54906c43af1d730650210", + "size": 33, + "text": "{\n \"release_channel\": \"stable\"\n}" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/common/Call of Duty Black Ops III/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/common/Call of Duty Black Ops III/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/common/Call of Duty Black Ops III/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/common/Call of Duty Black Ops III/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/common/Call of Duty Black Ops III/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + } + ], + "filesystem": { + "directories": [ + "data", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/common", + "secondary-library/steamapps/common/Call of Duty Black Ops III", + "secondary-library/steamapps/common/Call of Duty Black Ops III/players", + "secondary-library/steamapps/common/Call of Duty Black Ops III/video", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "99e02132d604629c806d118cb06ecedc18295f28afa54906c43af1d730650210", + "size": 33, + "text": "{\n \"release_channel\": \"stable\"\n}" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/common/Call of Duty Black Ops III/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/common/Call of Duty Black Ops III/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/common/Call of Duty Black Ops III/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/common/Call of Duty Black Ops III/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/common/Call of Duty Black Ops III/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "responses": [ + { + "activeLaunchProfile": "default", + "advanced": { + "configReadonly": false, + "maxFrameLatency": 1, + "reduceCpu": false, + "smoothFramerate": false, + "unlockOptions": false, + "vramLimited": false, + "vramTarget": 100 + }, + "appVersion": "v1.3.4", + "configExists": true, + "currentLaunchOptions": "", + "dxvk": { + "confExists": false, + "installed": false, + "settings": { + "enableAsync": true, + "gplAsyncCache": true, + "hudEnabled": false, + "maxFrameLatency": 1, + "maxFrameRate": 0, + "numCompilerThreads": 0, + "tearFree": "True" + } + }, + "enhanced": { + "acknowledgedAt": null, + "backupStatus": "Not created", + "detectedAt": null, + "dumpSource": "", + "filesInstalled": 0, + "installed": false, + "launchOptionsActive": false + }, + "exeSwap": { + "activeBuildDate": "", + "activeBuildId": "Unverified", + "backupAvailable": false, + "compatibleActive": false, + "compatibleAvailable": false, + "compatibleBuildDate": "Mar 3, 2023", + "compatibleBuildId": "10650222", + "currentBuildDate": "Sep 10, 2026", + "currentBuildId": "24784313", + "displayLabel": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE. T7 installs will use T7 Patch Scroptss/T7Patch.", + "enhancedActive": false, + "enhancedAvailable": false, + "enhancedBuildDate": "", + "enhancedBuildId": "Enhanced", + "enhancedExeActive": false, + "executable": "$ROOT/secondary-library/steamapps/common/Call of Duty Black Ops III/BlackOpsIII.exe", + "executableHash": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "executableName": "BlackOpsIII.exe", + "integrityMessage": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE.", + "integrityStatus": "unverified", + "latestAvailable": false, + "modeLabel": "Unknown", + "patchLabel": "T7 Patch Scroptss/T7Patch", + "profile": "", + "state": "unverified", + "trustedExecutable": false + }, + "gameDetected": true, + "gameDir": "$ROOT/secondary-library/steamapps/common/Call of Duty Black Ops III", + "graphics": { + "displayMode": 1, + "drawFps": false, + "fov": 80, + "maxFps": 165, + "refreshRate": 60.0, + "renderResolution": 100, + "resolution": "1920x1080", + "vsync": true + }, + "launchProfiles": [ + { + "active": true, + "id": "default", + "installed": true, + "label": "Default (None)", + "option": "", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "offline", + "installed": true, + "label": "Play Offline", + "option": "+set fs_game offlinemp", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "all_around", + "installed": false, + "label": "All-around Enhancement Lite", + "option": "+set fs_game 2994481309", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "ultimate", + "installed": false, + "label": "Ultimate Experience Mod", + "option": "+set fs_game 2942053577", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "reforged", + "installed": false, + "label": "BO3 Reforged", + "option": "+set fs_game 3667377161", + "path": null, + "state": "Not Subscribed", + "subscribed": false + } + ], + "logPath": "$ROOT/data/PatchOpsIII.log", + "logs": [], + "maintenance": { + "logPayload": "PatchOpsIII v1.3.4 - Linux $KERNEL (x86_64) logs:\n```\n(no log entries found)\n```", + "modFilesDir": "$ROOT/data/BO3 Mod Files" + }, + "mods": { + "dxvk": false, + "enhanced": false, + "t7Patch": false + }, + "platform": "Linux", + "presets": [ + "Smooth Preset", + "Low-Latency Preset" + ], + "qol": { + "allIntros": false, + "d3dcompiler": false, + "intro": false + }, + "releaseChannel": "stable", + "steamUserId": "76561198000000000", + "t7": { + "colorCode": "", + "confExists": false, + "friendsOnly": false, + "gamertag": "", + "installed": false, + "mode": "Custom", + "networkPassword": "", + "plainName": "" + } + } + ] + }, + "fixture": { + "directories": [ + "data", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/common", + "secondary-library/steamapps/common/Call of Duty Black Ops III", + "secondary-library/steamapps/common/Call of Duty Black Ops III/players", + "secondary-library/steamapps/common/Call of Duty Black Ops III/video", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "99e02132d604629c806d118cb06ecedc18295f28afa54906c43af1d730650210", + "size": 33, + "text": "{\n \"release_channel\": \"stable\"\n}" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/common/Call of Duty Black Ops III/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/common/Call of Duty Black Ops III/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/common/Call of Duty Black Ops III/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/common/Call of Duty Black Ops III/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/common/Call of Duty Black Ops III/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "name": "status_steam_secondary_library", + "operations": [ + { + "method": "GET", + "path": "/api/status" + } + ], + "runner": "direct endpoint functions", + "schemaVersion": 1, + "sourceHashes": { + "backend/api.py": "080be5c4a3b929134298179cd3f4d1558c288a50dcdb38978ada831925a57a46", + "bo3_enhanced.py": "32a670c8a63a64216ee5c46aa000d2e3840ca348ca0fd5031c81699252cf57b8", + "dxvk_manager.py": "8e150458bc7480ac89cd9e64c558ba438f61a09611bbf24b962cba02d7e4a5e5", + "t7_patch.py": "73e66fadd1c0775742b7bb6d9c6c73a3c9df8304d3117abd8ac768dfc68cc3ea", + "utils.py": "313069c3f70b92888239c9ef15ce2379713b029e019426e76b386f088b556564" + }, + "sourceOfTruth": "Python backend on main" +} diff --git a/native/core/tests/parity/status_t7_installed.json b/native/core/tests/parity/status_t7_installed.json new file mode 100644 index 0000000..7cc5aa3 --- /dev/null +++ b/native/core/tests/parity/status_t7_installed.json @@ -0,0 +1,518 @@ +{ + "expected": { + "checkpoints": [ + { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/t7patch.conf", + "sha256": "1d753acbca3ca8d7bef8cb73831810f54d6d48ed990fb7e6242eb74c230fde9a", + "size": 70, + "text": "playername=^1OldName\nnetworkpassword=old\nisfriendsonly=0\nunknown=keep\n" + }, + { + "mode": 420, + "path": "game/t7patch.dll", + "sha256": "e41e48279440133a6f109cbd9b8486bd57b16b10c61a34bf7ed4547bc7f748a1", + "size": 10, + "text": "fixture-t7" + }, + { + "mode": 420, + "path": "game/t7patchloader.dll", + "sha256": "fc61197d6de766eaddc97b118f5e7aed71805be61dcc7de5ec351bbd5b94caca", + "size": 14, + "text": "fixture-loader" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + } + ], + "filesystem": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/t7patch.conf", + "sha256": "1d753acbca3ca8d7bef8cb73831810f54d6d48ed990fb7e6242eb74c230fde9a", + "size": 70, + "text": "playername=^1OldName\nnetworkpassword=old\nisfriendsonly=0\nunknown=keep\n" + }, + { + "mode": 420, + "path": "game/t7patch.dll", + "sha256": "e41e48279440133a6f109cbd9b8486bd57b16b10c61a34bf7ed4547bc7f748a1", + "size": 10, + "text": "fixture-t7" + }, + { + "mode": 420, + "path": "game/t7patchloader.dll", + "sha256": "fc61197d6de766eaddc97b118f5e7aed71805be61dcc7de5ec351bbd5b94caca", + "size": 14, + "text": "fixture-loader" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "responses": [ + { + "activeLaunchProfile": "default", + "advanced": { + "configReadonly": false, + "maxFrameLatency": 1, + "reduceCpu": false, + "smoothFramerate": false, + "unlockOptions": false, + "vramLimited": false, + "vramTarget": 100 + }, + "appVersion": "v1.3.4", + "configExists": true, + "currentLaunchOptions": "", + "dxvk": { + "confExists": false, + "installed": false, + "settings": { + "enableAsync": true, + "gplAsyncCache": true, + "hudEnabled": false, + "maxFrameLatency": 1, + "maxFrameRate": 0, + "numCompilerThreads": 0, + "tearFree": "True" + } + }, + "enhanced": { + "acknowledgedAt": null, + "backupStatus": "Not created", + "detectedAt": null, + "dumpSource": "", + "filesInstalled": 0, + "installed": false, + "launchOptionsActive": false + }, + "exeSwap": { + "activeBuildDate": "", + "activeBuildId": "Unverified", + "backupAvailable": false, + "compatibleActive": false, + "compatibleAvailable": false, + "compatibleBuildDate": "Mar 3, 2023", + "compatibleBuildId": "10650222", + "currentBuildDate": "Sep 10, 2026", + "currentBuildId": "24784313", + "displayLabel": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE. T7 installs will use T7 Patch Scroptss/T7Patch.", + "enhancedActive": false, + "enhancedAvailable": false, + "enhancedBuildDate": "", + "enhancedBuildId": "Enhanced", + "enhancedExeActive": false, + "executable": "$ROOT/game/BlackOpsIII.exe", + "executableHash": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "executableName": "BlackOpsIII.exe", + "integrityMessage": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE.", + "integrityStatus": "unverified", + "latestAvailable": false, + "modeLabel": "Unknown", + "patchLabel": "T7 Patch Scroptss/T7Patch", + "profile": "", + "state": "unverified", + "trustedExecutable": false + }, + "gameDetected": true, + "gameDir": "$ROOT/game", + "graphics": { + "displayMode": 1, + "drawFps": false, + "fov": 80, + "maxFps": 165, + "refreshRate": 60.0, + "renderResolution": 100, + "resolution": "1920x1080", + "vsync": true + }, + "launchProfiles": [ + { + "active": true, + "id": "default", + "installed": true, + "label": "Default (None)", + "option": "", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "offline", + "installed": true, + "label": "Play Offline", + "option": "+set fs_game offlinemp", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "all_around", + "installed": false, + "label": "All-around Enhancement Lite", + "option": "+set fs_game 2994481309", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "ultimate", + "installed": false, + "label": "Ultimate Experience Mod", + "option": "+set fs_game 2942053577", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "reforged", + "installed": false, + "label": "BO3 Reforged", + "option": "+set fs_game 3667377161", + "path": null, + "state": "Not Subscribed", + "subscribed": false + } + ], + "logPath": "$ROOT/data/PatchOpsIII.log", + "logs": [], + "maintenance": { + "logPayload": "PatchOpsIII v1.3.4 - Linux $KERNEL (x86_64) logs:\n```\n(no log entries found)\n```", + "modFilesDir": "$ROOT/data/BO3 Mod Files" + }, + "mods": { + "dxvk": false, + "enhanced": false, + "t7Patch": true + }, + "platform": "Linux", + "presets": [ + "Smooth Preset", + "Low-Latency Preset" + ], + "qol": { + "allIntros": false, + "d3dcompiler": false, + "intro": false + }, + "releaseChannel": "stable", + "steamUserId": "76561198000000000", + "t7": { + "colorCode": "^1", + "confExists": true, + "friendsOnly": false, + "gamertag": "^1OldName", + "installed": true, + "mode": "Custom", + "networkPassword": "old", + "plainName": "OldName" + } + } + ] + }, + "fixture": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/t7patch.conf", + "sha256": "1d753acbca3ca8d7bef8cb73831810f54d6d48ed990fb7e6242eb74c230fde9a", + "size": 70, + "text": "playername=^1OldName\nnetworkpassword=old\nisfriendsonly=0\nunknown=keep\n" + }, + { + "mode": 420, + "path": "game/t7patch.dll", + "sha256": "e41e48279440133a6f109cbd9b8486bd57b16b10c61a34bf7ed4547bc7f748a1", + "size": 10, + "text": "fixture-t7" + }, + { + "mode": 420, + "path": "game/t7patchloader.dll", + "sha256": "fc61197d6de766eaddc97b118f5e7aed71805be61dcc7de5ec351bbd5b94caca", + "size": 14, + "text": "fixture-loader" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "name": "status_t7_installed", + "operations": [ + { + "method": "GET", + "path": "/api/status" + } + ], + "runner": "direct endpoint functions", + "schemaVersion": 1, + "sourceHashes": { + "backend/api.py": "080be5c4a3b929134298179cd3f4d1558c288a50dcdb38978ada831925a57a46", + "bo3_enhanced.py": "32a670c8a63a64216ee5c46aa000d2e3840ca348ca0fd5031c81699252cf57b8", + "dxvk_manager.py": "8e150458bc7480ac89cd9e64c558ba438f61a09611bbf24b962cba02d7e4a5e5", + "t7_patch.py": "73e66fadd1c0775742b7bb6d9c6c73a3c9df8304d3117abd8ac768dfc68cc3ea", + "utils.py": "313069c3f70b92888239c9ef15ce2379713b029e019426e76b386f088b556564" + }, + "sourceOfTruth": "Python backend on main" +} diff --git a/native/core/tests/parity/status_unverified_base.json b/native/core/tests/parity/status_unverified_base.json new file mode 100644 index 0000000..3698cc2 --- /dev/null +++ b/native/core/tests/parity/status_unverified_base.json @@ -0,0 +1,455 @@ +{ + "expected": { + "checkpoints": [ + { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + } + ], + "filesystem": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "responses": [ + { + "activeLaunchProfile": "default", + "advanced": { + "configReadonly": false, + "maxFrameLatency": 1, + "reduceCpu": false, + "smoothFramerate": false, + "unlockOptions": false, + "vramLimited": false, + "vramTarget": 100 + }, + "appVersion": "v1.3.4", + "configExists": true, + "currentLaunchOptions": "", + "dxvk": { + "confExists": false, + "installed": false, + "settings": { + "enableAsync": true, + "gplAsyncCache": true, + "hudEnabled": false, + "maxFrameLatency": 1, + "maxFrameRate": 0, + "numCompilerThreads": 0, + "tearFree": "True" + } + }, + "enhanced": { + "acknowledgedAt": null, + "backupStatus": "Not created", + "detectedAt": null, + "dumpSource": "", + "filesInstalled": 0, + "installed": false, + "launchOptionsActive": false + }, + "exeSwap": { + "activeBuildDate": "", + "activeBuildId": "Unverified", + "backupAvailable": false, + "compatibleActive": false, + "compatibleAvailable": false, + "compatibleBuildDate": "Mar 3, 2023", + "compatibleBuildId": "10650222", + "currentBuildDate": "Sep 10, 2026", + "currentBuildId": "24784313", + "displayLabel": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE. T7 installs will use T7 Patch Scroptss/T7Patch.", + "enhancedActive": false, + "enhancedAvailable": false, + "enhancedBuildDate": "", + "enhancedBuildId": "Enhanced", + "enhancedExeActive": false, + "executable": "$ROOT/game/BlackOpsIII.exe", + "executableHash": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "executableName": "BlackOpsIII.exe", + "integrityMessage": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE.", + "integrityStatus": "unverified", + "latestAvailable": false, + "modeLabel": "Unknown", + "patchLabel": "T7 Patch Scroptss/T7Patch", + "profile": "", + "state": "unverified", + "trustedExecutable": false + }, + "gameDetected": true, + "gameDir": "$ROOT/game", + "graphics": { + "displayMode": 1, + "drawFps": false, + "fov": 80, + "maxFps": 165, + "refreshRate": 60.0, + "renderResolution": 100, + "resolution": "1920x1080", + "vsync": true + }, + "launchProfiles": [ + { + "active": true, + "id": "default", + "installed": true, + "label": "Default (None)", + "option": "", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "offline", + "installed": true, + "label": "Play Offline", + "option": "+set fs_game offlinemp", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "all_around", + "installed": false, + "label": "All-around Enhancement Lite", + "option": "+set fs_game 2994481309", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "ultimate", + "installed": false, + "label": "Ultimate Experience Mod", + "option": "+set fs_game 2942053577", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "reforged", + "installed": false, + "label": "BO3 Reforged", + "option": "+set fs_game 3667377161", + "path": null, + "state": "Not Subscribed", + "subscribed": false + } + ], + "logPath": "$ROOT/data/PatchOpsIII.log", + "logs": [], + "maintenance": { + "logPayload": "PatchOpsIII v1.3.4 - Linux $KERNEL (x86_64) logs:\n```\n(no log entries found)\n```", + "modFilesDir": "$ROOT/data/BO3 Mod Files" + }, + "mods": { + "dxvk": false, + "enhanced": false, + "t7Patch": false + }, + "platform": "Linux", + "presets": [ + "Smooth Preset", + "Low-Latency Preset" + ], + "qol": { + "allIntros": false, + "d3dcompiler": false, + "intro": false + }, + "releaseChannel": "stable", + "steamUserId": "76561198000000000", + "t7": { + "colorCode": "", + "confExists": false, + "friendsOnly": false, + "gamertag": "", + "installed": false, + "mode": "Custom", + "networkPassword": "", + "plainName": "" + } + } + ] + }, + "fixture": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "name": "status_unverified_base", + "operations": [ + { + "method": "GET", + "path": "/api/status" + } + ], + "runner": "direct endpoint functions", + "schemaVersion": 1, + "sourceHashes": { + "backend/api.py": "080be5c4a3b929134298179cd3f4d1558c288a50dcdb38978ada831925a57a46", + "bo3_enhanced.py": "32a670c8a63a64216ee5c46aa000d2e3840ca348ca0fd5031c81699252cf57b8", + "dxvk_manager.py": "8e150458bc7480ac89cd9e64c558ba438f61a09611bbf24b962cba02d7e4a5e5", + "t7_patch.py": "73e66fadd1c0775742b7bb6d9c6c73a3c9df8304d3117abd8ac768dfc68cc3ea", + "utils.py": "313069c3f70b92888239c9ef15ce2379713b029e019426e76b386f088b556564" + }, + "sourceOfTruth": "Python backend on main" +} diff --git a/native/core/tests/parity/t7_config_clear_and_append.json b/native/core/tests/parity/t7_config_clear_and_append.json new file mode 100644 index 0000000..f3f45b3 --- /dev/null +++ b/native/core/tests/parity/t7_config_clear_and_append.json @@ -0,0 +1,557 @@ +{ + "expected": { + "checkpoints": [ + { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 436, + "path": "data/PatchOpsIII.log", + "sha256": "af3e30337b73eb5367be9f5b723ad00af06bd465dfadd1d1b8565dc8c6d5c655", + "size": 213, + "text": "$TIMESTAMP - Success: Updated 'playername' in t7patch.conf to '^2^1ColorName'.\n$TIMESTAMP - Success: Cleared network password in t7patch.conf.\n$TIMESTAMP - Success: Updated 'isfriendsonly' in t7patch.conf to Off.\n" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/t7patch.conf", + "sha256": "68cb4086156bfe2ed6ff13a556ba781dbaf947042c602182fedb607422f7a5e3", + "size": 71, + "text": "isfriendsonly=0\nnetworkpassword=\nplayername=^2^1ColorName\nunknown=keep\n" + }, + { + "mode": 420, + "path": "game/t7patch.dll", + "sha256": "e41e48279440133a6f109cbd9b8486bd57b16b10c61a34bf7ed4547bc7f748a1", + "size": 10, + "text": "fixture-t7" + }, + { + "mode": 420, + "path": "game/t7patchloader.dll", + "sha256": "fc61197d6de766eaddc97b118f5e7aed71805be61dcc7de5ec351bbd5b94caca", + "size": 14, + "text": "fixture-loader" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + } + ], + "filesystem": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 436, + "path": "data/PatchOpsIII.log", + "sha256": "af3e30337b73eb5367be9f5b723ad00af06bd465dfadd1d1b8565dc8c6d5c655", + "size": 213, + "text": "$TIMESTAMP - Success: Updated 'playername' in t7patch.conf to '^2^1ColorName'.\n$TIMESTAMP - Success: Cleared network password in t7patch.conf.\n$TIMESTAMP - Success: Updated 'isfriendsonly' in t7patch.conf to Off.\n" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/t7patch.conf", + "sha256": "68cb4086156bfe2ed6ff13a556ba781dbaf947042c602182fedb607422f7a5e3", + "size": 71, + "text": "isfriendsonly=0\nnetworkpassword=\nplayername=^2^1ColorName\nunknown=keep\n" + }, + { + "mode": 420, + "path": "game/t7patch.dll", + "sha256": "e41e48279440133a6f109cbd9b8486bd57b16b10c61a34bf7ed4547bc7f748a1", + "size": 10, + "text": "fixture-t7" + }, + { + "mode": 420, + "path": "game/t7patchloader.dll", + "sha256": "fc61197d6de766eaddc97b118f5e7aed71805be61dcc7de5ec351bbd5b94caca", + "size": 14, + "text": "fixture-loader" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "responses": [ + { + "ok": true, + "state": { + "activeLaunchProfile": "default", + "advanced": { + "configReadonly": false, + "maxFrameLatency": 1, + "reduceCpu": false, + "smoothFramerate": false, + "unlockOptions": false, + "vramLimited": false, + "vramTarget": 100 + }, + "appVersion": "v1.3.4", + "configExists": true, + "currentLaunchOptions": "", + "dxvk": { + "confExists": false, + "installed": false, + "settings": { + "enableAsync": true, + "gplAsyncCache": true, + "hudEnabled": false, + "maxFrameLatency": 1, + "maxFrameRate": 0, + "numCompilerThreads": 0, + "tearFree": "True" + } + }, + "enhanced": { + "acknowledgedAt": null, + "backupStatus": "Not created", + "detectedAt": null, + "dumpSource": "", + "filesInstalled": 0, + "installed": false, + "launchOptionsActive": false + }, + "exeSwap": { + "activeBuildDate": "", + "activeBuildId": "Unverified", + "backupAvailable": false, + "compatibleActive": false, + "compatibleAvailable": false, + "compatibleBuildDate": "Mar 3, 2023", + "compatibleBuildId": "10650222", + "currentBuildDate": "Sep 10, 2026", + "currentBuildId": "24784313", + "displayLabel": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE. T7 installs will use T7 Patch Scroptss/T7Patch.", + "enhancedActive": false, + "enhancedAvailable": false, + "enhancedBuildDate": "", + "enhancedBuildId": "Enhanced", + "enhancedExeActive": false, + "executable": "$ROOT/game/BlackOpsIII.exe", + "executableHash": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "executableName": "BlackOpsIII.exe", + "integrityMessage": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE.", + "integrityStatus": "unverified", + "latestAvailable": false, + "modeLabel": "Unknown", + "patchLabel": "T7 Patch Scroptss/T7Patch", + "profile": "", + "state": "unverified", + "trustedExecutable": false + }, + "gameDetected": true, + "gameDir": "$ROOT/game", + "graphics": { + "displayMode": 1, + "drawFps": false, + "fov": 80, + "maxFps": 165, + "refreshRate": 60.0, + "renderResolution": 100, + "resolution": "1920x1080", + "vsync": true + }, + "launchProfiles": [ + { + "active": true, + "id": "default", + "installed": true, + "label": "Default (None)", + "option": "", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "offline", + "installed": true, + "label": "Play Offline", + "option": "+set fs_game offlinemp", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "all_around", + "installed": false, + "label": "All-around Enhancement Lite", + "option": "+set fs_game 2994481309", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "ultimate", + "installed": false, + "label": "Ultimate Experience Mod", + "option": "+set fs_game 2942053577", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "reforged", + "installed": false, + "label": "BO3 Reforged", + "option": "+set fs_game 3667377161", + "path": null, + "state": "Not Subscribed", + "subscribed": false + } + ], + "logPath": "$ROOT/data/PatchOpsIII.log", + "logs": [ + { + "category": "Success", + "line": "$TIMESTAMP - Success: Updated 'playername' in t7patch.conf to '^2^1ColorName'.", + "message": "Updated 'playername' in t7patch.conf to '^2^1ColorName'." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: Cleared network password in t7patch.conf.", + "message": "Cleared network password in t7patch.conf." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: Updated 'isfriendsonly' in t7patch.conf to Off.", + "message": "Updated 'isfriendsonly' in t7patch.conf to Off." + } + ], + "maintenance": { + "logPayload": "PatchOpsIII v1.3.4 - Linux $KERNEL (x86_64) logs:\n```\n$TIMESTAMP - Success: Updated 'playername' in t7patch.conf to '^2^1ColorName'.\n$TIMESTAMP - Success: Cleared network password in t7patch.conf.\n$TIMESTAMP - Success: Updated 'isfriendsonly' in t7patch.conf to Off.\n```", + "modFilesDir": "$ROOT/data/BO3 Mod Files" + }, + "mods": { + "dxvk": false, + "enhanced": false, + "t7Patch": true + }, + "platform": "Linux", + "presets": [ + "Smooth Preset", + "Low-Latency Preset" + ], + "qol": { + "allIntros": false, + "d3dcompiler": false, + "intro": false + }, + "releaseChannel": "stable", + "steamUserId": "76561198000000000", + "t7": { + "colorCode": "^2", + "confExists": true, + "friendsOnly": false, + "gamertag": "^2^1ColorName", + "installed": true, + "mode": "Custom", + "networkPassword": "", + "plainName": "^1ColorName" + } + } + } + ] + }, + "fixture": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/t7patch.conf", + "sha256": "e6853c6f5659890470dcee843f013d873b6783329eb38056448c07f708beb5b6", + "size": 13, + "text": "unknown=keep\n" + }, + { + "mode": 420, + "path": "game/t7patch.dll", + "sha256": "e41e48279440133a6f109cbd9b8486bd57b16b10c61a34bf7ed4547bc7f748a1", + "size": 10, + "text": "fixture-t7" + }, + { + "mode": 420, + "path": "game/t7patchloader.dll", + "sha256": "fc61197d6de766eaddc97b118f5e7aed71805be61dcc7de5ec351bbd5b94caca", + "size": 14, + "text": "fixture-loader" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "name": "t7_config_clear_and_append", + "operations": [ + { + "body": { + "colorCode": "^2", + "friendsOnly": false, + "gamertag": " ^1ColorName ", + "networkPassword": " " + }, + "method": "POST", + "path": "/api/t7-config" + } + ], + "runner": "direct endpoint functions", + "schemaVersion": 1, + "sourceHashes": { + "backend/api.py": "080be5c4a3b929134298179cd3f4d1558c288a50dcdb38978ada831925a57a46", + "bo3_enhanced.py": "32a670c8a63a64216ee5c46aa000d2e3840ca348ca0fd5031c81699252cf57b8", + "dxvk_manager.py": "8e150458bc7480ac89cd9e64c558ba438f61a09611bbf24b962cba02d7e4a5e5", + "t7_patch.py": "73e66fadd1c0775742b7bb6d9c6c73a3c9df8304d3117abd8ac768dfc68cc3ea", + "utils.py": "313069c3f70b92888239c9ef15ce2379713b029e019426e76b386f088b556564" + }, + "sourceOfTruth": "Python backend on main" +} diff --git a/native/core/tests/parity/t7_config_invalid_name.json b/native/core/tests/parity/t7_config_invalid_name.json new file mode 100644 index 0000000..6d33b65 --- /dev/null +++ b/native/core/tests/parity/t7_config_invalid_name.json @@ -0,0 +1,487 @@ +{ + "expected": { + "checkpoints": [ + { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/t7patch.conf", + "sha256": "1d753acbca3ca8d7bef8cb73831810f54d6d48ed990fb7e6242eb74c230fde9a", + "size": 70, + "text": "playername=^1OldName\nnetworkpassword=old\nisfriendsonly=0\nunknown=keep\n" + }, + { + "mode": 420, + "path": "game/t7patch.dll", + "sha256": "e41e48279440133a6f109cbd9b8486bd57b16b10c61a34bf7ed4547bc7f748a1", + "size": 10, + "text": "fixture-t7" + }, + { + "mode": 420, + "path": "game/t7patchloader.dll", + "sha256": "fc61197d6de766eaddc97b118f5e7aed71805be61dcc7de5ec351bbd5b94caca", + "size": 14, + "text": "fixture-loader" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/t7patch.conf", + "sha256": "1d753acbca3ca8d7bef8cb73831810f54d6d48ed990fb7e6242eb74c230fde9a", + "size": 70, + "text": "playername=^1OldName\nnetworkpassword=old\nisfriendsonly=0\nunknown=keep\n" + }, + { + "mode": 420, + "path": "game/t7patch.dll", + "sha256": "e41e48279440133a6f109cbd9b8486bd57b16b10c61a34bf7ed4547bc7f748a1", + "size": 10, + "text": "fixture-t7" + }, + { + "mode": 420, + "path": "game/t7patchloader.dll", + "sha256": "fc61197d6de766eaddc97b118f5e7aed71805be61dcc7de5ec351bbd5b94caca", + "size": 14, + "text": "fixture-loader" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + } + ], + "filesystem": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/t7patch.conf", + "sha256": "1d753acbca3ca8d7bef8cb73831810f54d6d48ed990fb7e6242eb74c230fde9a", + "size": 70, + "text": "playername=^1OldName\nnetworkpassword=old\nisfriendsonly=0\nunknown=keep\n" + }, + { + "mode": 420, + "path": "game/t7patch.dll", + "sha256": "e41e48279440133a6f109cbd9b8486bd57b16b10c61a34bf7ed4547bc7f748a1", + "size": 10, + "text": "fixture-t7" + }, + { + "mode": 420, + "path": "game/t7patchloader.dll", + "sha256": "fc61197d6de766eaddc97b118f5e7aed71805be61dcc7de5ec351bbd5b94caca", + "size": 14, + "text": "fixture-loader" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "responses": [ + { + "error": "Gamertag cannot be empty.", + "ok": false + }, + { + "error": "Gamertag cannot exceed 20 characters.", + "ok": false + } + ] + }, + "fixture": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/t7patch.conf", + "sha256": "1d753acbca3ca8d7bef8cb73831810f54d6d48ed990fb7e6242eb74c230fde9a", + "size": 70, + "text": "playername=^1OldName\nnetworkpassword=old\nisfriendsonly=0\nunknown=keep\n" + }, + { + "mode": 420, + "path": "game/t7patch.dll", + "sha256": "e41e48279440133a6f109cbd9b8486bd57b16b10c61a34bf7ed4547bc7f748a1", + "size": 10, + "text": "fixture-t7" + }, + { + "mode": 420, + "path": "game/t7patchloader.dll", + "sha256": "fc61197d6de766eaddc97b118f5e7aed71805be61dcc7de5ec351bbd5b94caca", + "size": 14, + "text": "fixture-loader" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "name": "t7_config_invalid_name", + "operations": [ + { + "body": { + "gamertag": " " + }, + "method": "POST", + "path": "/api/t7-config" + }, + { + "body": { + "gamertag": "aaaaaaaaaaaaaaaaaaaaa" + }, + "method": "POST", + "path": "/api/t7-config" + } + ], + "runner": "direct endpoint functions", + "schemaVersion": 1, + "sourceHashes": { + "backend/api.py": "080be5c4a3b929134298179cd3f4d1558c288a50dcdb38978ada831925a57a46", + "bo3_enhanced.py": "32a670c8a63a64216ee5c46aa000d2e3840ca348ca0fd5031c81699252cf57b8", + "dxvk_manager.py": "8e150458bc7480ac89cd9e64c558ba438f61a09611bbf24b962cba02d7e4a5e5", + "t7_patch.py": "73e66fadd1c0775742b7bb6d9c6c73a3c9df8304d3117abd8ac768dfc68cc3ea", + "utils.py": "313069c3f70b92888239c9ef15ce2379713b029e019426e76b386f088b556564" + }, + "sourceOfTruth": "Python backend on main" +} diff --git a/native/core/tests/parity/t7_config_missing.json b/native/core/tests/parity/t7_config_missing.json new file mode 100644 index 0000000..bc930ee --- /dev/null +++ b/native/core/tests/parity/t7_config_missing.json @@ -0,0 +1,302 @@ +{ + "expected": { + "checkpoints": [ + { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + } + ], + "filesystem": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "responses": [ + { + "error": "t7patch.conf was not found. Install T7 Patch before updating the gamertag.", + "ok": false + } + ] + }, + "fixture": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "name": "t7_config_missing", + "operations": [ + { + "body": { + "gamertag": "Player" + }, + "method": "POST", + "path": "/api/t7-config" + } + ], + "runner": "direct endpoint functions", + "schemaVersion": 1, + "sourceHashes": { + "backend/api.py": "080be5c4a3b929134298179cd3f4d1558c288a50dcdb38978ada831925a57a46", + "bo3_enhanced.py": "32a670c8a63a64216ee5c46aa000d2e3840ca348ca0fd5031c81699252cf57b8", + "dxvk_manager.py": "8e150458bc7480ac89cd9e64c558ba438f61a09611bbf24b962cba02d7e4a5e5", + "t7_patch.py": "73e66fadd1c0775742b7bb6d9c6c73a3c9df8304d3117abd8ac768dfc68cc3ea", + "utils.py": "313069c3f70b92888239c9ef15ce2379713b029e019426e76b386f088b556564" + }, + "sourceOfTruth": "Python backend on main" +} diff --git a/native/core/tests/parity/t7_config_write.json b/native/core/tests/parity/t7_config_write.json new file mode 100644 index 0000000..762ce48 --- /dev/null +++ b/native/core/tests/parity/t7_config_write.json @@ -0,0 +1,557 @@ +{ + "expected": { + "checkpoints": [ + { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 436, + "path": "data/PatchOpsIII.log", + "sha256": "f33a755c74594bf6f2205d2a7e8819ee536d04b4e00a88e98379966631885f28", + "size": 214, + "text": "$TIMESTAMP - Success: Updated 'playername' in t7patch.conf to '^3FixturePlayer'.\n$TIMESTAMP - Success: Updated network password in t7patch.conf.\n$TIMESTAMP - Success: Updated 'isfriendsonly' in t7patch.conf to On.\n" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/t7patch.conf", + "sha256": "7c00a8697b2a429a1cbfacf00bc3cd1d5fdaba7359ba6fa15dcfe719ced09a34", + "size": 79, + "text": "playername=^3FixturePlayer\nnetworkpassword=secret\nisfriendsonly=1\nunknown=keep\n" + }, + { + "mode": 420, + "path": "game/t7patch.dll", + "sha256": "e41e48279440133a6f109cbd9b8486bd57b16b10c61a34bf7ed4547bc7f748a1", + "size": 10, + "text": "fixture-t7" + }, + { + "mode": 420, + "path": "game/t7patchloader.dll", + "sha256": "fc61197d6de766eaddc97b118f5e7aed71805be61dcc7de5ec351bbd5b94caca", + "size": 14, + "text": "fixture-loader" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + } + ], + "filesystem": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 436, + "path": "data/PatchOpsIII.log", + "sha256": "f33a755c74594bf6f2205d2a7e8819ee536d04b4e00a88e98379966631885f28", + "size": 214, + "text": "$TIMESTAMP - Success: Updated 'playername' in t7patch.conf to '^3FixturePlayer'.\n$TIMESTAMP - Success: Updated network password in t7patch.conf.\n$TIMESTAMP - Success: Updated 'isfriendsonly' in t7patch.conf to On.\n" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/t7patch.conf", + "sha256": "7c00a8697b2a429a1cbfacf00bc3cd1d5fdaba7359ba6fa15dcfe719ced09a34", + "size": 79, + "text": "playername=^3FixturePlayer\nnetworkpassword=secret\nisfriendsonly=1\nunknown=keep\n" + }, + { + "mode": 420, + "path": "game/t7patch.dll", + "sha256": "e41e48279440133a6f109cbd9b8486bd57b16b10c61a34bf7ed4547bc7f748a1", + "size": 10, + "text": "fixture-t7" + }, + { + "mode": 420, + "path": "game/t7patchloader.dll", + "sha256": "fc61197d6de766eaddc97b118f5e7aed71805be61dcc7de5ec351bbd5b94caca", + "size": 14, + "text": "fixture-loader" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "responses": [ + { + "ok": true, + "state": { + "activeLaunchProfile": "default", + "advanced": { + "configReadonly": false, + "maxFrameLatency": 1, + "reduceCpu": false, + "smoothFramerate": false, + "unlockOptions": false, + "vramLimited": false, + "vramTarget": 100 + }, + "appVersion": "v1.3.4", + "configExists": true, + "currentLaunchOptions": "", + "dxvk": { + "confExists": false, + "installed": false, + "settings": { + "enableAsync": true, + "gplAsyncCache": true, + "hudEnabled": false, + "maxFrameLatency": 1, + "maxFrameRate": 0, + "numCompilerThreads": 0, + "tearFree": "True" + } + }, + "enhanced": { + "acknowledgedAt": null, + "backupStatus": "Not created", + "detectedAt": null, + "dumpSource": "", + "filesInstalled": 0, + "installed": false, + "launchOptionsActive": false + }, + "exeSwap": { + "activeBuildDate": "", + "activeBuildId": "Unverified", + "backupAvailable": false, + "compatibleActive": false, + "compatibleAvailable": false, + "compatibleBuildDate": "Mar 3, 2023", + "compatibleBuildId": "10650222", + "currentBuildDate": "Sep 10, 2026", + "currentBuildId": "24784313", + "displayLabel": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE. T7 installs will use T7 Patch Scroptss/T7Patch.", + "enhancedActive": false, + "enhancedAvailable": false, + "enhancedBuildDate": "", + "enhancedBuildId": "Enhanced", + "enhancedExeActive": false, + "executable": "$ROOT/game/BlackOpsIII.exe", + "executableHash": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "executableName": "BlackOpsIII.exe", + "integrityMessage": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE.", + "integrityStatus": "unverified", + "latestAvailable": false, + "modeLabel": "Unknown", + "patchLabel": "T7 Patch Scroptss/T7Patch", + "profile": "", + "state": "unverified", + "trustedExecutable": false + }, + "gameDetected": true, + "gameDir": "$ROOT/game", + "graphics": { + "displayMode": 1, + "drawFps": false, + "fov": 80, + "maxFps": 165, + "refreshRate": 60.0, + "renderResolution": 100, + "resolution": "1920x1080", + "vsync": true + }, + "launchProfiles": [ + { + "active": true, + "id": "default", + "installed": true, + "label": "Default (None)", + "option": "", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "offline", + "installed": true, + "label": "Play Offline", + "option": "+set fs_game offlinemp", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "all_around", + "installed": false, + "label": "All-around Enhancement Lite", + "option": "+set fs_game 2994481309", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "ultimate", + "installed": false, + "label": "Ultimate Experience Mod", + "option": "+set fs_game 2942053577", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "reforged", + "installed": false, + "label": "BO3 Reforged", + "option": "+set fs_game 3667377161", + "path": null, + "state": "Not Subscribed", + "subscribed": false + } + ], + "logPath": "$ROOT/data/PatchOpsIII.log", + "logs": [ + { + "category": "Success", + "line": "$TIMESTAMP - Success: Updated 'playername' in t7patch.conf to '^3FixturePlayer'.", + "message": "Updated 'playername' in t7patch.conf to '^3FixturePlayer'." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: Updated network password in t7patch.conf.", + "message": "Updated network password in t7patch.conf." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: Updated 'isfriendsonly' in t7patch.conf to On.", + "message": "Updated 'isfriendsonly' in t7patch.conf to On." + } + ], + "maintenance": { + "logPayload": "PatchOpsIII v1.3.4 - Linux $KERNEL (x86_64) logs:\n```\n$TIMESTAMP - Success: Updated 'playername' in t7patch.conf to '^3FixturePlayer'.\n$TIMESTAMP - Success: Updated network password in t7patch.conf.\n$TIMESTAMP - Success: Updated 'isfriendsonly' in t7patch.conf to On.\n```", + "modFilesDir": "$ROOT/data/BO3 Mod Files" + }, + "mods": { + "dxvk": false, + "enhanced": false, + "t7Patch": true + }, + "platform": "Linux", + "presets": [ + "Smooth Preset", + "Low-Latency Preset" + ], + "qol": { + "allIntros": false, + "d3dcompiler": false, + "intro": false + }, + "releaseChannel": "stable", + "steamUserId": "76561198000000000", + "t7": { + "colorCode": "^3", + "confExists": true, + "friendsOnly": true, + "gamertag": "^3FixturePlayer", + "installed": true, + "mode": "Custom", + "networkPassword": "secret", + "plainName": "FixturePlayer" + } + } + } + ] + }, + "fixture": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/t7patch.conf", + "sha256": "1d753acbca3ca8d7bef8cb73831810f54d6d48ed990fb7e6242eb74c230fde9a", + "size": 70, + "text": "playername=^1OldName\nnetworkpassword=old\nisfriendsonly=0\nunknown=keep\n" + }, + { + "mode": 420, + "path": "game/t7patch.dll", + "sha256": "e41e48279440133a6f109cbd9b8486bd57b16b10c61a34bf7ed4547bc7f748a1", + "size": 10, + "text": "fixture-t7" + }, + { + "mode": 420, + "path": "game/t7patchloader.dll", + "sha256": "fc61197d6de766eaddc97b118f5e7aed71805be61dcc7de5ec351bbd5b94caca", + "size": 14, + "text": "fixture-loader" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "name": "t7_config_write", + "operations": [ + { + "body": { + "colorCode": "^3", + "friendsOnly": true, + "gamertag": "FixturePlayer", + "networkPassword": "secret" + }, + "method": "POST", + "path": "/api/t7-config" + } + ], + "runner": "direct endpoint functions", + "schemaVersion": 1, + "sourceHashes": { + "backend/api.py": "080be5c4a3b929134298179cd3f4d1558c288a50dcdb38978ada831925a57a46", + "bo3_enhanced.py": "32a670c8a63a64216ee5c46aa000d2e3840ca348ca0fd5031c81699252cf57b8", + "dxvk_manager.py": "8e150458bc7480ac89cd9e64c558ba438f61a09611bbf24b962cba02d7e4a5e5", + "t7_patch.py": "73e66fadd1c0775742b7bb6d9c6c73a3c9df8304d3117abd8ac768dfc68cc3ea", + "utils.py": "313069c3f70b92888239c9ef15ce2379713b029e019426e76b386f088b556564" + }, + "sourceOfTruth": "Python backend on main" +} diff --git a/native/core/tests/parity/t7_release_asset_discovery.json b/native/core/tests/parity/t7_release_asset_discovery.json new file mode 100644 index 0000000..788db13 --- /dev/null +++ b/native/core/tests/parity/t7_release_asset_discovery.json @@ -0,0 +1,177 @@ +{ + "expected": { + "checkpoints": [], + "filesystem": { + "directories": [ + "data", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 436, + "path": "data/PatchOpsIII.log", + "sha256": "cf8ce9a988680020881fa8b5f7a2b496a83da7ee6812b1b7c8217ca173df24a2", + "size": 86, + "text": "$TIMESTAMP - Info: Using latest T7 Patch release asset T7Patch-Linux-Windows-3.0.zip.\n" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "responses": [ + [ + { + "assetKey": "compatible_archive", + "label": "versioned legacy-compatible name", + "release": { + "assets": [ + { + "browser_download_url": "https://fixtures.invalid/t7-2.05.zip", + "digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "name": "Linux.Steamdeck.and.Manual.Windows.Install.2.05.zip" + } + ] + }, + "resolved": { + "sha256": [ + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + ], + "url": "https://fixtures.invalid/t7-2.05.zip" + } + }, + { + "assetKey": "current_archive", + "label": "renamed universal current asset", + "release": { + "assets": [ + { + "browser_download_url": "https://fixtures.invalid/t7-3.0.zip", + "digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "name": "T7Patch-Linux-Windows-3.0.zip" + } + ] + }, + "resolved": { + "sha256": [ + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + ], + "url": "https://fixtures.invalid/t7-3.0.zip" + } + } + ] + ] + }, + "fixture": { + "directories": [ + "data", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "internalOperation": "t7_release_asset_discovery", + "name": "t7_release_asset_discovery", + "operations": [ + { + "method": "GET", + "path": "/api/status" + } + ], + "runner": "direct endpoint functions", + "rustIgnore": "release selection is private to the Rust install path and would otherwise download an archive", + "schemaVersion": 1, + "sourceHashes": { + "backend/api.py": "080be5c4a3b929134298179cd3f4d1558c288a50dcdb38978ada831925a57a46", + "bo3_enhanced.py": "32a670c8a63a64216ee5c46aa000d2e3840ca348ca0fd5031c81699252cf57b8", + "dxvk_manager.py": "8e150458bc7480ac89cd9e64c558ba438f61a09611bbf24b962cba02d7e4a5e5", + "t7_patch.py": "73e66fadd1c0775742b7bb6d9c6c73a3c9df8304d3117abd8ac768dfc68cc3ea", + "utils.py": "313069c3f70b92888239c9ef15ce2379713b029e019426e76b386f088b556564" + }, + "sourceOfTruth": "Python backend on main" +} diff --git a/native/core/tests/parity/vram_target_roundtrip.json b/native/core/tests/parity/vram_target_roundtrip.json new file mode 100644 index 0000000..104f731 --- /dev/null +++ b/native/core/tests/parity/vram_target_roundtrip.json @@ -0,0 +1,761 @@ +{ + "expected": { + "checkpoints": [ + { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 436, + "path": "data/PatchOpsIII.log", + "sha256": "88b8977ed20d4f1f766456c7c1056fee4ea4f1edb1b606ca1ff9716b713c751d", + "size": 54, + "text": "$TIMESTAMP - Success: Limited VRAM usage set to 100%.\n" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "48d4eca182b3265fc933d1d2399f0e7c3f38d025047ae32bc4b4ab1768771851", + "size": 475, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1.0\" // 0.75 to 1\nStreamMinResident = \"1\" // 0 or 1\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 436, + "path": "data/PatchOpsIII.log", + "sha256": "465ba17022b5798819d6181a51fcafe86bba4d19b599f6e1463e6c9a77295673", + "size": 101, + "text": "$TIMESTAMP - Success: Limited VRAM usage set to 100%.\n$TIMESTAMP - Success: Enabled full VRAM usage.\n" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "bb242c8618e86a15941c2a6dea6d990a317c94b6a90f159b7f60b5fff8f25198", + "size": 473, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // 0.75 to 1\nStreamMinResident = \"0\" // 0 or 1\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + } + ], + "filesystem": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 436, + "path": "data/PatchOpsIII.log", + "sha256": "465ba17022b5798819d6181a51fcafe86bba4d19b599f6e1463e6c9a77295673", + "size": 101, + "text": "$TIMESTAMP - Success: Limited VRAM usage set to 100%.\n$TIMESTAMP - Success: Enabled full VRAM usage.\n" + }, + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "bb242c8618e86a15941c2a6dea6d990a317c94b6a90f159b7f60b5fff8f25198", + "size": 473, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // 0.75 to 1\nStreamMinResident = \"0\" // 0 or 1\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "responses": [ + { + "ok": true, + "state": { + "activeLaunchProfile": "default", + "advanced": { + "configReadonly": false, + "maxFrameLatency": 1, + "reduceCpu": false, + "smoothFramerate": false, + "unlockOptions": false, + "vramLimited": true, + "vramTarget": 100 + }, + "appVersion": "v1.3.4", + "configExists": true, + "currentLaunchOptions": "", + "dxvk": { + "confExists": false, + "installed": false, + "settings": { + "enableAsync": true, + "gplAsyncCache": true, + "hudEnabled": false, + "maxFrameLatency": 1, + "maxFrameRate": 0, + "numCompilerThreads": 0, + "tearFree": "True" + } + }, + "enhanced": { + "acknowledgedAt": null, + "backupStatus": "Not created", + "detectedAt": null, + "dumpSource": "", + "filesInstalled": 0, + "installed": false, + "launchOptionsActive": false + }, + "exeSwap": { + "activeBuildDate": "", + "activeBuildId": "Unverified", + "backupAvailable": false, + "compatibleActive": false, + "compatibleAvailable": false, + "compatibleBuildDate": "Mar 3, 2023", + "compatibleBuildId": "10650222", + "currentBuildDate": "Sep 10, 2026", + "currentBuildId": "24784313", + "displayLabel": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE. T7 installs will use T7 Patch Scroptss/T7Patch.", + "enhancedActive": false, + "enhancedAvailable": false, + "enhancedBuildDate": "", + "enhancedBuildId": "Enhanced", + "enhancedExeActive": false, + "executable": "$ROOT/game/BlackOpsIII.exe", + "executableHash": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "executableName": "BlackOpsIII.exe", + "integrityMessage": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE.", + "integrityStatus": "unverified", + "latestAvailable": false, + "modeLabel": "Unknown", + "patchLabel": "T7 Patch Scroptss/T7Patch", + "profile": "", + "state": "unverified", + "trustedExecutable": false + }, + "gameDetected": true, + "gameDir": "$ROOT/game", + "graphics": { + "displayMode": 1, + "drawFps": false, + "fov": 80, + "maxFps": 165, + "refreshRate": 60.0, + "renderResolution": 100, + "resolution": "1920x1080", + "vsync": true + }, + "launchProfiles": [ + { + "active": true, + "id": "default", + "installed": true, + "label": "Default (None)", + "option": "", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "offline", + "installed": true, + "label": "Play Offline", + "option": "+set fs_game offlinemp", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "all_around", + "installed": false, + "label": "All-around Enhancement Lite", + "option": "+set fs_game 2994481309", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "ultimate", + "installed": false, + "label": "Ultimate Experience Mod", + "option": "+set fs_game 2942053577", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "reforged", + "installed": false, + "label": "BO3 Reforged", + "option": "+set fs_game 3667377161", + "path": null, + "state": "Not Subscribed", + "subscribed": false + } + ], + "logPath": "$ROOT/data/PatchOpsIII.log", + "logs": [ + { + "category": "Success", + "line": "$TIMESTAMP - Success: Limited VRAM usage set to 100%.", + "message": "Limited VRAM usage set to 100%." + } + ], + "maintenance": { + "logPayload": "PatchOpsIII v1.3.4 - Linux $KERNEL (x86_64) logs:\n```\n$TIMESTAMP - Success: Limited VRAM usage set to 100%.\n```", + "modFilesDir": "$ROOT/data/BO3 Mod Files" + }, + "mods": { + "dxvk": false, + "enhanced": false, + "t7Patch": false + }, + "platform": "Linux", + "presets": [ + "Smooth Preset", + "Low-Latency Preset" + ], + "qol": { + "allIntros": false, + "d3dcompiler": false, + "intro": false + }, + "releaseChannel": "stable", + "steamUserId": "76561198000000000", + "t7": { + "colorCode": "", + "confExists": false, + "friendsOnly": false, + "gamertag": "", + "installed": false, + "mode": "Custom", + "networkPassword": "", + "plainName": "" + } + } + }, + { + "ok": true, + "state": { + "activeLaunchProfile": "default", + "advanced": { + "configReadonly": false, + "maxFrameLatency": 1, + "reduceCpu": false, + "smoothFramerate": false, + "unlockOptions": false, + "vramLimited": false, + "vramTarget": 100 + }, + "appVersion": "v1.3.4", + "configExists": true, + "currentLaunchOptions": "", + "dxvk": { + "confExists": false, + "installed": false, + "settings": { + "enableAsync": true, + "gplAsyncCache": true, + "hudEnabled": false, + "maxFrameLatency": 1, + "maxFrameRate": 0, + "numCompilerThreads": 0, + "tearFree": "True" + } + }, + "enhanced": { + "acknowledgedAt": null, + "backupStatus": "Not created", + "detectedAt": null, + "dumpSource": "", + "filesInstalled": 0, + "installed": false, + "launchOptionsActive": false + }, + "exeSwap": { + "activeBuildDate": "", + "activeBuildId": "Unverified", + "backupAvailable": false, + "compatibleActive": false, + "compatibleAvailable": false, + "compatibleBuildDate": "Mar 3, 2023", + "compatibleBuildId": "10650222", + "currentBuildDate": "Sep 10, 2026", + "currentBuildId": "24784313", + "displayLabel": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE. T7 installs will use T7 Patch Scroptss/T7Patch.", + "enhancedActive": false, + "enhancedAvailable": false, + "enhancedBuildDate": "", + "enhancedBuildId": "Enhanced", + "enhancedExeActive": false, + "executable": "$ROOT/game/BlackOpsIII.exe", + "executableHash": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "executableName": "BlackOpsIII.exe", + "integrityMessage": "Active executable hash 0f96666e646d does not match latest BuildID 24784313, compatible BuildID 10650222, or a PatchOpsIII-preserved Enhanced EXE.", + "integrityStatus": "unverified", + "latestAvailable": false, + "modeLabel": "Unknown", + "patchLabel": "T7 Patch Scroptss/T7Patch", + "profile": "", + "state": "unverified", + "trustedExecutable": false + }, + "gameDetected": true, + "gameDir": "$ROOT/game", + "graphics": { + "displayMode": 1, + "drawFps": false, + "fov": 80, + "maxFps": 165, + "refreshRate": 60.0, + "renderResolution": 100, + "resolution": "1920x1080", + "vsync": true + }, + "launchProfiles": [ + { + "active": true, + "id": "default", + "installed": true, + "label": "Default (None)", + "option": "", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "offline", + "installed": true, + "label": "Play Offline", + "option": "+set fs_game offlinemp", + "state": "Ready", + "subscribed": true + }, + { + "active": false, + "id": "all_around", + "installed": false, + "label": "All-around Enhancement Lite", + "option": "+set fs_game 2994481309", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "ultimate", + "installed": false, + "label": "Ultimate Experience Mod", + "option": "+set fs_game 2942053577", + "path": null, + "state": "Not Subscribed", + "subscribed": false + }, + { + "active": false, + "id": "reforged", + "installed": false, + "label": "BO3 Reforged", + "option": "+set fs_game 3667377161", + "path": null, + "state": "Not Subscribed", + "subscribed": false + } + ], + "logPath": "$ROOT/data/PatchOpsIII.log", + "logs": [ + { + "category": "Success", + "line": "$TIMESTAMP - Success: Limited VRAM usage set to 100%.", + "message": "Limited VRAM usage set to 100%." + }, + { + "category": "Success", + "line": "$TIMESTAMP - Success: Enabled full VRAM usage.", + "message": "Enabled full VRAM usage." + } + ], + "maintenance": { + "logPayload": "PatchOpsIII v1.3.4 - Linux $KERNEL (x86_64) logs:\n```\n$TIMESTAMP - Success: Limited VRAM usage set to 100%.\n$TIMESTAMP - Success: Enabled full VRAM usage.\n```", + "modFilesDir": "$ROOT/data/BO3 Mod Files" + }, + "mods": { + "dxvk": false, + "enhanced": false, + "t7Patch": false + }, + "platform": "Linux", + "presets": [ + "Smooth Preset", + "Low-Latency Preset" + ], + "qol": { + "allIntros": false, + "d3dcompiler": false, + "intro": false + }, + "releaseChannel": "stable", + "steamUserId": "76561198000000000", + "t7": { + "colorCode": "", + "confExists": false, + "friendsOnly": false, + "gamertag": "", + "installed": false, + "mode": "Custom", + "networkPassword": "", + "plainName": "" + } + } + } + ] + }, + "fixture": { + "directories": [ + "data", + "game", + "game/players", + "game/video", + "not-a-game", + "resources", + "secondary-library", + "secondary-library/steamapps", + "secondary-library/steamapps/workshop", + "steam", + "steam/steamapps", + "steam/userdata", + "steam/userdata/76561198000000000", + "steam/userdata/76561198000000000/config" + ], + "files": [ + { + "mode": 420, + "path": "data/electron-settings.json", + "sha256": "4c07124e4037a29057b1274d3d2dbe1df207b1350e19c28ec27bf8d4ae4338b2", + "size": 61, + "text": "{\n \"release_channel\": \"stable\",\n \"game_dir\": \"$ROOT/game\"\n}" + }, + { + "mode": 420, + "path": "game/BlackOpsIII.exe", + "sha256": "0f96666e646d0a5ccbce7257d91593556a6747502ccab65a3991b7e097e9d5d2", + "size": 29, + "text": "PATCHOPS_FAKE_EXE:unverified\n" + }, + { + "mode": 420, + "path": "game/d3dcompiler_46.dll", + "sha256": "0ae6e172362d4d6866dccf7d2de3b99d9964310054626fce519bde4e35295caa", + "size": 15, + "text": "legacy-compiler" + }, + { + "mode": 420, + "path": "game/players/config.ini", + "sha256": "31e5090dff0b200154b4d7f79766d0101c796eddb073e0b3ae4a3fae3da234b4", + "size": 474, + "text": "// fixture configuration\nMaxFPS = \"165\" // original\nFOV = \"80\" // original\nFullScreenMode = \"1\" // original\nWindowSize = \"1920x1080\" // original\nRefreshRate = \"60\" // original\nResolutionPercent = \"100\" // original\nVsync = \"1\" // original\nDrawFPS = \"0\" // original\nSmoothFramerate = \"0\" // original\nRestrictGraphicsOptions = \"1\" // original\nSerializeRender = \"0\" // original\nMaxFrameLatency = \"1\" // original\nVideoMemory = \"1\" // original\nStreamMinResident = \"0\" // original\n" + }, + { + "mode": 420, + "path": "game/video/BO3_Global_Logo_LogoSequence.mkv", + "sha256": "06cd6175508d0d415bfd1944079df661fe73691ad4dd17ccc1688a298e6ed083", + "size": 9, + "text": "intro-one" + }, + { + "mode": 420, + "path": "game/video/Frontend_Intro.mkv", + "sha256": "5d3568fc674ae38d3d215ec7e9e46e02fd1c31987199c7d0aab8b7a3a41f64a2", + "size": 9, + "text": "intro-two" + }, + { + "mode": 420, + "path": "resources/presets.json", + "sha256": "e29c9752c43f216794bbe7c82739e5f6821f1debb71a734e152df2b9fb14d5f2", + "size": 5082, + "text": "{\n \"Smooth Preset\": {\n \"SmoothFramerate\": [\"1\", \"Enable smoothing at the cost of some performance\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"4\", \"Driver can queue up to 4 frames\"],\n \"SerializeRender\": [\"0\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"1\", \"Target all of the available VRAM to reduce stuttering\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"1\", \"Vertical sync enabled\"],\n \"BackbufferCount\": [\"3\", \"Triple buffering enabled\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"0\", \"No reduction in minimum asset residency in VRAM\"]\n },\n \"Low-Latency Preset\": {\n \"SmoothFramerate\": [\"0\", \"Disable smoothing to minimize frame delay\"],\n \"CorpseCount\": [\"16\", \"Maximum number of corpses is 32, 16 is selected for a balanced approach\"],\n \"MaxFrameLatency\": [\"1\", \"Driver can only queue up to 1 frame\"],\n \"SerializeRender\": [\"2\", \"0 is the default for the best performance, 1 and 2 improve latency but require a powerful CPU.\"],\n \"VideoMemory\": [\"0.75\", \"Target 75% the available VRAM to increase performance\"],\n \"RestrictGraphicsOptions\": [\"0\", \"Advanced graphics options unlocked\"],\n \"Vsync\": [\"0\", \"Vertical sync disabled for lower latency\"],\n \"BackbufferCount\": [\"2\", \"Reset Vsync to double buffer\"],\n \"MeshQuality\": [\"0\", \"Highest quality meshes\"],\n \"TextureFilter\": [\"2\", \"High anisotropic filtering\"],\n \"TextureQuality\": [\"1\", \"High texture quality (balanced)\"],\n \"TextureQualityFX\": [\"1\", \"Improved effect quality\"],\n \"TextureQualityProbes\": [\"0\", \"Default reflection quality\"],\n \"TextureQualityBakedSunShadows\": [\"1\", \"Slightly reduced baked sun shadow quality\"],\n \"TextureLowDetailResident\": [\"0\", \"Lowest detail resident streaming disabled\"],\n \"DisableDynamicLightShadows\": [\"1\", \"Dynamic light shadows disabled\"],\n \"DisableDynamicSunShadows\": [\"1\", \"Dynamic sun shadows disabled\"],\n \"SpotShadowTextureSize\": [\"1024\", \"Spot light shadow resolution set to 1024\"],\n \"OmniShadowTextureSize\": [\"256\", \"Omni (point) light shadow resolution set to 256\"],\n \"ShadowFiltering\": [\"1\", \"Multi-sampled soft shadows enabled\"],\n \"ActorShadows\": [\"0\", \"Actor shadows disabled\"],\n \"VolumetricLightingEnabled\": [\"0\", \"Volumetric lighting disabled\"],\n \"VolumetricLightingMaxSunSamples\": [\"8\", \"Max sun samples for volumetric lighting\"],\n \"VolumetricLightingMaxLightSamples\": [\"40\", \"Max light samples for volumetric lighting\"],\n \"VolumetricLightingSkipSunSamples\": [\"1\", \"Skip alternate sun samples\"],\n \"VolumetricLightingSkipLightSamples\": [\"1\", \"Skip alternate light samples\"],\n \"OIT\": [\"1\", \"Order-independent transparency enabled\"],\n \"OITLayers\": [\"8\", \"8 transparency layers set\"],\n \"SSAOTechnique\": [\"GTAO Low Quality\", \"Ambient occlusion set to GTAO Low Quality\"],\n \"AATechnique\": [\"FXAA\", \"Anti-aliasing set to FXAA\"],\n \"MotionBlur\": [\"Off\", \"Motion blur disabled\"],\n \"MotionBlurQuality\": [\"Medium\", \"Medium quality motion blur (if enabled)\"],\n \"SubsurfaceScattering\": [\"0\", \"Subsurface scattering disabled\"],\n \"StreamMinResident\": [\"1\", \"Reduce the minimum asset residency in VRAM\"]\n }\n}\n" + }, + { + "mode": 420, + "path": "secondary-library/steamapps/workshop/appworkshop_311210.acf", + "sha256": "1e41744c2d76714baeae696764a96898a65315748ed695f05aa6890dd4f7a78a", + "size": 85, + "text": "\"AppWorkshop\"\n{\n \"WorkshopItemsInstalled\"\n {\n }\n \"WorkshopItemDetails\"\n {\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/steamapps/libraryfolders.vdf", + "sha256": "1225e4effb5115a68ff774612689de1eb40580299937a428b8aa5ff8d01a864d", + "size": 153, + "text": "\"libraryfolders\"\n{\n \"0\"\n {\n \"path\" \"$ROOT/steam\"\n }\n \"1\"\n {\n \"path\" \"$ROOT/secondary-library\"\n \"apps\"\n {\n \"311210\" \"1\"\n }\n }\n}\n" + }, + { + "mode": 420, + "path": "steam/userdata/76561198000000000/config/localconfig.vdf", + "sha256": "6c7fefd9df196943bb300cec2b60d2fdd528efc08d9bad7fa4dfdc7b9b51a093", + "size": 211, + "text": "\"UserLocalConfigStore\"\n{\n \"Software\"\n {\n \"Valve\"\n {\n \"Steam\"\n {\n \"apps\"\n {\n \"311210\"\n {\n \"LaunchOptions\" \"\"\n }\n }\n }\n }\n }\n}\n" + } + ] + }, + "name": "vram_target_roundtrip", + "operations": [ + { + "body": { + "limited": true, + "target": 100 + }, + "method": "POST", + "path": "/api/vram-target" + }, + { + "body": { + "limited": false, + "target": 85 + }, + "method": "POST", + "path": "/api/vram-target" + } + ], + "runner": "direct endpoint functions", + "schemaVersion": 1, + "sourceHashes": { + "backend/api.py": "080be5c4a3b929134298179cd3f4d1558c288a50dcdb38978ada831925a57a46", + "bo3_enhanced.py": "32a670c8a63a64216ee5c46aa000d2e3840ca348ca0fd5031c81699252cf57b8", + "dxvk_manager.py": "8e150458bc7480ac89cd9e64c558ba438f61a09611bbf24b962cba02d7e4a5e5", + "t7_patch.py": "73e66fadd1c0775742b7bb6d9c6c73a3c9df8304d3117abd8ac768dfc68cc3ea", + "utils.py": "313069c3f70b92888239c9ef15ce2379713b029e019426e76b386f088b556564" + }, + "sourceOfTruth": "Python backend on main" +} diff --git a/native/gpui/Cargo.toml b/native/gpui/Cargo.toml new file mode 100644 index 0000000..de2ac99 --- /dev/null +++ b/native/gpui/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "patchopsiii-gpui" +version = "0.1.0" +edition = "2024" +publish = false +license = "MIT" + +[dependencies] +anyhow = "1" +env_logger = "0.11" +log = "0.4" +gpui = "=0.2.2" +gpui-component = "=0.5.0" +patchops-core = { path = "../core" } +serde_json = "1" diff --git a/native/gpui/README.md b/native/gpui/README.md new file mode 100644 index 0000000..9ec42e8 --- /dev/null +++ b/native/gpui/README.md @@ -0,0 +1,126 @@ +# PatchOpsIII native desktop app + +The primary Windows x64 and Linux x64 client is one Rust process: GPUI controls +and the toolkit-independent `patchops-core` library. No Python interpreter, +PyInstaller service, loopback HTTP server, Tauri, or WebView runs in the native +app. The legacy Electron client and its Python backend remain available. + +See [the architecture and migration record](../../docs/gpui-architecture.md) +for the dispatcher contract, progress hook, operation differences, and hardware +validation gates. + +## Install a release archive + +Extract `PatchOpsIII-native-windows-x64.zip` or +`PatchOpsIII-native-linux-x64.zip`. Each contains: + +```text +PatchOpsIII/ +├── patchopsiii-gpui[.exe] +├── resources/ # metadata, presets, application icons +├── LICENSE +├── THIRD-PARTY-NOTICES/Lucide.txt +├── README.md +├── NATIVE-README.md +└── docs/native-desktop.md +``` + +Run `patchopsiii-gpui.exe` on Windows. On Linux: + +```sh +chmod +x PatchOpsIII/patchopsiii-gpui +./PatchOpsIII/patchopsiii-gpui +``` + +Keep the resources directory alongside the executable. Linux needs X11 or +Wayland, fonts, and a Vulkan-capable graphics driver. Native ZIPs do not provide +an automatic installer or updater. Update checks offer the matching native ZIP; +the legacy MSI/NSIS/AppImage assets remain separate. + +## Build and run from source + +Install Rust through rustup. `native/rust-toolchain.toml` pins Rust 1.99.0 with +rustfmt and Clippy. `native/Cargo.lock` locks the workspace; GPUI 0.2.2 and GPUI +Component 0.5.0 remain pinned. The original lockfile's libc 0.2.189 is retained +for GPUI's xattr 0.2.3 dependency. + +Ubuntu 24.04 build packages: + +```sh +sudo apt-get install libfontconfig1-dev libfreetype6-dev libxkbcommon-dev \ + libxkbcommon-x11-dev libwayland-dev libvulkan-dev libx11-xcb-dev \ + libxcb1-dev libasound2-dev libclang-dev +``` + +Windows needs Visual Studio Build Tools with Desktop development with C++, a +Windows SDK, and Rust's MSVC toolchain. Rust builds do not require Python or Bun: + +```sh +cargo +1.99.0 build --locked --manifest-path native/Cargo.toml -p patchopsiii-gpui +./native/target/debug/patchopsiii-gpui +``` + +Python's standard library is sufficient for the optional convenience scripts: + +```sh +python scripts/gpui.py build +python scripts/gpui.py run +# Existing Bun aliases: bun run gpui:build / bun run gpui:run +``` + +`CARGO_TARGET_DIR` selects a custom build directory; relative paths in the +launcher resolve against the repository root. Default output is `native/target`. +`PATCHOPSIII_DATA_DIR` optionally selects isolated settings/log/cache storage, +useful for disposable tests. The old Python/backend URL environment variables +are no longer used. The normal data directory still shares +`electron-settings.json`, logs and legacy ownership state with Electron. Avoid +simultaneous mutations from both clients. + +Requests execute serially on a worker thread. Navigation remains responsive +while an operation runs. Download timeouts and integrity checks live in the +core; there is no HTTP request timeout between the UI and worker. Wait for file +operations to finish before closing: cancellation and joining the worker on +window close are not implemented. + +## Build a distributable archive + +```sh +python scripts/gpui.py build --release +python scripts/package_gpui.py \ + --executable native/target/release/patchopsiii-gpui \ + --output dist/native/PatchOpsIII-native-linux-x64.zip +# Windows: use native/target/release/patchopsiii-gpui.exe and the Windows ZIP name. +``` + +The archive contains one product executable and resources/docs. Presets and +release version are also embedded at compile time; release workflows derive the +runtime version from package.json. Native CI builds/tests the Rust workspace and +runs standard-library Python launcher/archive checks. Stable and beta release +workflows attach both native ZIPs under their existing artifact names. + +## Validation + +```sh +cargo +1.99.0 fmt --manifest-path native/Cargo.toml --all --check +cargo +1.99.0 clippy --locked --manifest-path native/Cargo.toml --workspace --all-targets -- -D warnings +cargo +1.99.0 test --locked --manifest-path native/Cargo.toml -p patchops-core +cargo +1.99.0 test --locked --manifest-path native/Cargo.toml -p patchopsiii-gpui +python -m unittest scripts/test_gpui.py +# Read-only status on a disposable fake game, without a desktop: +python scripts/smoke_gpui_linux.py --status-only --executable native/target/debug/patchopsiii-gpui +# With xvfb, openbox, xcompmgr, xdotool and a Vulkan driver: +xvfb-run -a python scripts/smoke_gpui_linux.py --start-session \ + --executable native/target/debug/patchopsiii-gpui +``` + +The binary's `--smoke-status` option starts the real worker, prints status JSON, +and exits before creating a window. The smoke script selects a temporary data +directory and fake executable/config; it never requests a game mutation. + +## Remaining validation + +Native signing/installers, automatic updates, accessibility/IME, Windows UAC, +Wayland, Steam Deck, real downloads and restoration on disposable installations +need hardware/manual checks. PR #40 uses stricter ownership and verified +transactional backups than Python; legacy payloads not provably owned may require +manual recovery. The parity fixtures document exact Python behavior separately. diff --git a/native/gpui/assets/caption/close.svg b/native/gpui/assets/caption/close.svg new file mode 100644 index 0000000..fca322d --- /dev/null +++ b/native/gpui/assets/caption/close.svg @@ -0,0 +1,3 @@ + + + diff --git a/native/gpui/assets/caption/maximize.svg b/native/gpui/assets/caption/maximize.svg new file mode 100644 index 0000000..f4631d5 --- /dev/null +++ b/native/gpui/assets/caption/maximize.svg @@ -0,0 +1,3 @@ + + + diff --git a/native/gpui/assets/caption/minimize.svg b/native/gpui/assets/caption/minimize.svg new file mode 100644 index 0000000..c90101b --- /dev/null +++ b/native/gpui/assets/caption/minimize.svg @@ -0,0 +1,3 @@ + + + diff --git a/native/gpui/assets/caption/restore.svg b/native/gpui/assets/caption/restore.svg new file mode 100644 index 0000000..c4e4b4c --- /dev/null +++ b/native/gpui/assets/caption/restore.svg @@ -0,0 +1,3 @@ + + + diff --git a/native/gpui/assets/icons/LICENSE-lucide.txt b/native/gpui/assets/icons/LICENSE-lucide.txt new file mode 100644 index 0000000..718bb3f --- /dev/null +++ b/native/gpui/assets/icons/LICENSE-lucide.txt @@ -0,0 +1,43 @@ +ISC License + +Copyright (c) 2026 Lucide Icons and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +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. + +--- + +The following Lucide icons are derived from the Feather project: + +airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out + +The MIT License (MIT) (for the icons listed above) + +Copyright (c) 2013-present Cole Bemis + +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. diff --git a/native/gpui/assets/icons/check.svg b/native/gpui/assets/icons/check.svg new file mode 100644 index 0000000..41ab05b --- /dev/null +++ b/native/gpui/assets/icons/check.svg @@ -0,0 +1,3 @@ + + + diff --git a/native/gpui/assets/icons/chevron-down.svg b/native/gpui/assets/icons/chevron-down.svg new file mode 100644 index 0000000..9c9685e --- /dev/null +++ b/native/gpui/assets/icons/chevron-down.svg @@ -0,0 +1,3 @@ + + + diff --git a/native/gpui/assets/icons/chevron-right.svg b/native/gpui/assets/icons/chevron-right.svg new file mode 100644 index 0000000..812cb6e --- /dev/null +++ b/native/gpui/assets/icons/chevron-right.svg @@ -0,0 +1,3 @@ + + + diff --git a/native/gpui/assets/icons/chevron-up.svg b/native/gpui/assets/icons/chevron-up.svg new file mode 100644 index 0000000..abf0556 --- /dev/null +++ b/native/gpui/assets/icons/chevron-up.svg @@ -0,0 +1,3 @@ + + + diff --git a/native/gpui/assets/icons/circle-check.svg b/native/gpui/assets/icons/circle-check.svg new file mode 100644 index 0000000..edf1f6a --- /dev/null +++ b/native/gpui/assets/icons/circle-check.svg @@ -0,0 +1,4 @@ + + + + diff --git a/native/gpui/assets/icons/clipboard.svg b/native/gpui/assets/icons/clipboard.svg new file mode 100644 index 0000000..f111f42 --- /dev/null +++ b/native/gpui/assets/icons/clipboard.svg @@ -0,0 +1,4 @@ + + + + diff --git a/native/gpui/assets/icons/close.svg b/native/gpui/assets/icons/close.svg new file mode 100644 index 0000000..2f94dee --- /dev/null +++ b/native/gpui/assets/icons/close.svg @@ -0,0 +1,4 @@ + + + + diff --git a/native/gpui/assets/icons/cpu.svg b/native/gpui/assets/icons/cpu.svg new file mode 100644 index 0000000..c482a2e --- /dev/null +++ b/native/gpui/assets/icons/cpu.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/native/gpui/assets/icons/download.svg b/native/gpui/assets/icons/download.svg new file mode 100644 index 0000000..86e2ccd --- /dev/null +++ b/native/gpui/assets/icons/download.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/native/gpui/assets/icons/eraser.svg b/native/gpui/assets/icons/eraser.svg new file mode 100644 index 0000000..fcacf82 --- /dev/null +++ b/native/gpui/assets/icons/eraser.svg @@ -0,0 +1,4 @@ + + + + diff --git a/native/gpui/assets/icons/external-link.svg b/native/gpui/assets/icons/external-link.svg new file mode 100644 index 0000000..23e238c --- /dev/null +++ b/native/gpui/assets/icons/external-link.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/native/gpui/assets/icons/eye-off.svg b/native/gpui/assets/icons/eye-off.svg new file mode 100644 index 0000000..bc7af7b --- /dev/null +++ b/native/gpui/assets/icons/eye-off.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/native/gpui/assets/icons/eye.svg b/native/gpui/assets/icons/eye.svg new file mode 100644 index 0000000..5731d78 --- /dev/null +++ b/native/gpui/assets/icons/eye.svg @@ -0,0 +1,4 @@ + + + + diff --git a/native/gpui/assets/icons/folder-open.svg b/native/gpui/assets/icons/folder-open.svg new file mode 100644 index 0000000..3777d2c --- /dev/null +++ b/native/gpui/assets/icons/folder-open.svg @@ -0,0 +1,3 @@ + + + diff --git a/native/gpui/assets/icons/gem.svg b/native/gpui/assets/icons/gem.svg new file mode 100644 index 0000000..43a0285 --- /dev/null +++ b/native/gpui/assets/icons/gem.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/native/gpui/assets/icons/image.svg b/native/gpui/assets/icons/image.svg new file mode 100644 index 0000000..4a6e34c --- /dev/null +++ b/native/gpui/assets/icons/image.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/native/gpui/assets/icons/key-round.svg b/native/gpui/assets/icons/key-round.svg new file mode 100644 index 0000000..324eaba --- /dev/null +++ b/native/gpui/assets/icons/key-round.svg @@ -0,0 +1,4 @@ + + + + diff --git a/native/gpui/assets/icons/layout-dashboard.svg b/native/gpui/assets/icons/layout-dashboard.svg new file mode 100644 index 0000000..c23621e --- /dev/null +++ b/native/gpui/assets/icons/layout-dashboard.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/native/gpui/assets/icons/refresh-cw.svg b/native/gpui/assets/icons/refresh-cw.svg new file mode 100644 index 0000000..191be9b --- /dev/null +++ b/native/gpui/assets/icons/refresh-cw.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/native/gpui/assets/icons/rotate-ccw.svg b/native/gpui/assets/icons/rotate-ccw.svg new file mode 100644 index 0000000..acb3eb5 --- /dev/null +++ b/native/gpui/assets/icons/rotate-ccw.svg @@ -0,0 +1,4 @@ + + + + diff --git a/native/gpui/assets/icons/save.svg b/native/gpui/assets/icons/save.svg new file mode 100644 index 0000000..1b9fc74 --- /dev/null +++ b/native/gpui/assets/icons/save.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/native/gpui/assets/icons/shield-check.svg b/native/gpui/assets/icons/shield-check.svg new file mode 100644 index 0000000..ea22670 --- /dev/null +++ b/native/gpui/assets/icons/shield-check.svg @@ -0,0 +1,4 @@ + + + + diff --git a/native/gpui/assets/icons/square-play.svg b/native/gpui/assets/icons/square-play.svg new file mode 100644 index 0000000..74098f1 --- /dev/null +++ b/native/gpui/assets/icons/square-play.svg @@ -0,0 +1,4 @@ + + + + diff --git a/native/gpui/assets/icons/trash-2.svg b/native/gpui/assets/icons/trash-2.svg new file mode 100644 index 0000000..1d16f15 --- /dev/null +++ b/native/gpui/assets/icons/trash-2.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/native/gpui/assets/icons/triangle-alert.svg b/native/gpui/assets/icons/triangle-alert.svg new file mode 100644 index 0000000..9edd41e --- /dev/null +++ b/native/gpui/assets/icons/triangle-alert.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/native/gpui/assets/icons/wrench.svg b/native/gpui/assets/icons/wrench.svg new file mode 100644 index 0000000..693acf5 --- /dev/null +++ b/native/gpui/assets/icons/wrench.svg @@ -0,0 +1,3 @@ + + + diff --git a/native/gpui/assets/icons/x.svg b/native/gpui/assets/icons/x.svg new file mode 100644 index 0000000..2f94dee --- /dev/null +++ b/native/gpui/assets/icons/x.svg @@ -0,0 +1,4 @@ + + + + diff --git a/native/gpui/assets/images/logo.png b/native/gpui/assets/images/logo.png new file mode 100644 index 0000000..b4c6a6e Binary files /dev/null and b/native/gpui/assets/images/logo.png differ diff --git a/native/gpui/src/backend.rs b/native/gpui/src/backend.rs new file mode 100644 index 0000000..2586467 --- /dev/null +++ b/native/gpui/src/backend.rs @@ -0,0 +1,235 @@ +//! In-process Rust backend hosted on one worker thread. +use anyhow::{Result, bail}; +use patchops_core::{AppState, Engine, EventCallback, models::LogEntry}; +use serde_json::Value; +use std::{sync::mpsc, thread}; + +pub use patchops_core::models::OperationProgress; + +pub struct Request { + pub path: String, + pub body: Option, +} + +#[derive(Debug)] +pub struct Reply { + pub state: Option, + pub message: String, + pub failed: bool, + pub is_status: bool, +} + +/// Streaming events emitted while a long-running operation is executing. +/// +/// The current UI obtains the same entries in the next state response. A UI +/// can subscribe to Backend::events to render logs/progress immediately. +#[derive(Clone, Debug)] +pub enum BackendEvent { + Log(LogEntry), + Progress(OperationProgress), +} + +pub struct Backend { + pub requests: mpsc::Sender, + pub replies: mpsc::Receiver, + pub events: mpsc::Receiver, +} + +pub fn decode_reply(path: &str, value: Value) -> Result { + if value.get("ok").and_then(Value::as_bool) == Some(false) + || value.get("valid").and_then(Value::as_bool) == Some(false) + { + let mut message = value["error"] + .as_str() + .or(value["message"].as_str()) + .unwrap_or("Operation failed") + .to_owned(); + if let Some(command) = value["depotCommand"].as_str() { + message.push_str(&format!("\nSteam console command: {command}")); + } + bail!("{message}"); + } + if path != "/api/status" && value.get("ok").and_then(Value::as_bool) != Some(true) { + bail!("Backend returned an invalid operation result"); + } + let state = if path == "/api/status" { + if !value["appVersion"].is_string() || !value["gameDetected"].is_boolean() { + bail!("Backend returned an invalid status document"); + } + Some(value.clone()) + } else { + value.get("state").cloned() + }; + let message = if path == "/api/status" { + "Connected to the in-process Rust backend".to_owned() + } else if let Some(update) = value.get("update") { + format!("Update check: {update}") + } else { + value["message"] + .as_str() + .unwrap_or("Operation completed") + .to_owned() + }; + Ok(Reply { + state, + message, + failed: false, + is_status: path == "/api/status", + }) +} + +fn execute(engine: &Engine, request: Request) -> Result { + let path = request.path; + let value = engine + .dispatch(&path, request.body) + .map_err(anyhow::Error::msg)?; + decode_reply(&path, value) +} + +impl Backend { + pub fn start() -> Result { + Self::start_with(|callback| AppState::for_desktop(Some(callback))) + } + + fn start_with( + factory: impl FnOnce(EventCallback) -> std::result::Result + Send + 'static, + ) -> Result { + let (requests, incoming) = mpsc::channel::(); + let (outgoing, replies) = mpsc::channel(); + let (event_sender, events) = mpsc::channel(); + thread::Builder::new() + .name("patchops-gpui-core".into()) + .spawn(move || { + let progress_sender = event_sender.clone(); + let callback = std::sync::Arc::new(move |entry| { + let _ = event_sender.send(BackendEvent::Log(entry)); + }); + let state = match factory(callback) { + Ok(state) => state, + Err(error) => { + for request in incoming { + let is_status = request.path == "/api/status"; + if outgoing + .send(Reply { + state: None, + message: error.clone(), + failed: true, + is_status, + }) + .is_err() + { + break; + } + } + return; + } + }; + state.set_progress_callback(std::sync::Arc::new(move |progress| { + let _ = progress_sender.send(BackendEvent::Progress(progress)); + })); + let engine = Engine::new(state); + for request in incoming { + let is_status = request.path == "/api/status"; + let reply = execute(&engine, request).unwrap_or_else(|error| Reply { + state: None, + message: format!("{error:#}"), + failed: true, + is_status, + }); + if outgoing.send(reply).is_err() { + break; + } + } + })?; + Ok(Self { + requests, + replies, + events, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + #[test] + fn handles_operation_failures_and_depot_instructions() { + let error = decode_reply( + "/api/exe-swap/compatible", + json!({ + "ok": false, + "error": "Download depot first", + "depotCommand": "download_depot 311210 311211 123" + }), + ) + .unwrap_err(); + assert!( + error + .to_string() + .contains("download_depot 311210 311211 123") + ); + assert!(decode_reply("/api/status", json!({"ok": true})).is_err()); + assert!(decode_reply("/api/config", json!({"state": {}})).is_err()); + assert!( + decode_reply( + "/api/enhanced-validate", + json!({"ok": true, "valid": false, "message": "Missing files"}) + ) + .is_err() + ); + } + + #[test] + fn preserves_backend_state_without_fabricating_success() { + let state = json!({"appVersion": "v1.3.4", "gameDetected": false}); + assert_eq!( + decode_reply("/api/status", state.clone()).unwrap().state, + Some(state.clone()) + ); + assert_eq!( + decode_reply("/api/config", json!({"ok": true, "state": state})) + .unwrap() + .state, + Some(state) + ); + } + + #[test] + fn worker_returns_in_process_status() { + let root = + std::env::temp_dir().join(format!("patchops-worker-test-{}", std::process::id())); + let data = root.join("data"); + let game = root.join("game"); + std::fs::create_dir_all(&data).unwrap(); + std::fs::create_dir_all(game.join("players")).unwrap(); + std::fs::write(game.join("BlackOps3.exe"), b"fake game").unwrap(); + std::fs::write(game.join("players/config.ini"), b"FOV = \"90\"\n").unwrap(); + std::fs::write( + data.join("electron-settings.json"), + json!({"game_dir": game}).to_string(), + ) + .unwrap(); + let backend = + Backend::start_with(move |callback| AppState::new(data, None, Some(callback))).unwrap(); + backend + .requests + .send(Request { + path: "/api/status".into(), + body: None, + }) + .unwrap(); + let reply = backend + .replies + .recv_timeout(std::time::Duration::from_secs(5)) + .unwrap(); + assert!(!reply.failed, "{}", reply.message); + let state = reply.state.unwrap(); + assert!(state["appVersion"].is_string()); + assert_eq!(state["gameDetected"], true); + assert_eq!(state["graphics"]["fov"], 90); + drop(backend); + std::fs::remove_dir_all(root).unwrap(); + } +} diff --git a/native/gpui/src/main.rs b/native/gpui/src/main.rs new file mode 100644 index 0000000..c7a5835 --- /dev/null +++ b/native/gpui/src/main.rs @@ -0,0 +1,72 @@ +mod backend; +mod ui; + +use anyhow::Result; +use gpui::{prelude::*, *}; +use gpui_component::{Root, Theme, ThemeMode}; + +fn main() -> Result<()> { + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("warn")).init(); + let backend = backend::Backend::start()?; + // Read-only diagnostic used by archive smoke checks, without opening a window. + if std::env::args().any(|argument| argument == "--smoke-status") { + backend.requests.send(backend::Request { + path: "/api/status".into(), + body: None, + })?; + let reply = backend + .replies + .recv_timeout(std::time::Duration::from_secs(30))?; + if reply.failed { + anyhow::bail!("{}", reply.message); + } + println!( + "{}", + reply + .state + .ok_or_else(|| anyhow::anyhow!("Missing status document"))? + ); + return Ok(()); + } + let app = Application::new().with_assets(ui::Assets); + app.run(move |cx: &mut App| { + gpui_component::init(cx); + Theme::change(ThemeMode::Dark, None, cx); + let theme = Theme::global_mut(cx); + theme.colors.primary = rgb(0xd92323).into(); + theme.colors.primary_hover = rgb(0xf03535).into(); + theme.colors.primary_active = rgb(0xa41616).into(); + theme.colors.primary_foreground = rgb(0xffffff).into(); + theme.colors.ring = rgb(0xf03535).into(); + cx.on_window_closed(|cx| { + // GPUI 0.2.2's X11 close callback holds platform state borrowed. + // Quit on a later event-loop turn, after that callback has returned. + cx.spawn(async move |cx| { + cx.background_executor() + .timer(std::time::Duration::from_millis(1)) + .await; + let _ = cx.update(|cx| { + if cx.windows().is_empty() { + cx.quit(); + } + }); + }) + .detach(); + }) + .detach(); + let bounds = Bounds::centered(None, size(px(1150.), px(820.)), cx); + ui::log_session(); + cx.open_window( + // Custom titlebar and client-side decorations where the platform + // supports them; see `ui/chrome.rs`. + ui::window_options(bounds), + move |window, cx| { + let view = cx.new(|cx| ui::ControlCenter::new(backend, window, cx)); + cx.new(|cx| Root::new(view, window, cx)) + }, + ) + .expect("Unable to open the GPUI window"); + cx.activate(true); + }); + Ok(()) +} diff --git a/native/gpui/src/ui.rs b/native/gpui/src/ui.rs new file mode 100644 index 0000000..a5fcf26 --- /dev/null +++ b/native/gpui/src/ui.rs @@ -0,0 +1,1466 @@ +//! The PatchOpsIII desktop UI. The layout mirrors the Electron renderer +//! (`src/renderer/main.tsx` + `styles/app.css`); each page lives in its own module. +mod assets; +mod chrome; +mod components; +mod dashboard; +mod enhanced; +mod exe; +mod graphics; +mod modals; +mod progress; +mod t7; +mod theme; +mod tools; + +pub use assets::Assets; +pub use chrome::{log_session, window_options}; +// The backend's event hook builds these; see `ControlCenter::on_progress`. +pub use progress::{Operation, ProgressEvent}; + +use crate::backend::{Backend, BackendEvent, Reply, Request}; +use components::{Btn, Glyph, icon}; +use gpui::{prelude::*, *}; +use gpui_component::{ + ActiveTheme, + input::{Input, InputEvent, InputState}, + scroll::ScrollableElement, + slider::{SliderEvent, SliderState}, +}; +use progress::OperationProgress; +use serde_json::{Value, json}; +use std::{ + collections::{BTreeMap, VecDeque}, + time::{Duration, Instant, SystemTime, UNIX_EPOCH}, +}; + +/// How often the UI asks the backend for a fresh status document while idle. +/// Actions refresh explicitly (see `request_refresh`), so this is only the +/// safety net for changes made outside the app; raise it freely. +const STATUS_POLL_INTERVAL: Duration = Duration::from_secs(5); +/// How often the Steam depot prompt re-checks whether the depot has arrived. +const DEPOT_POLL_INTERVAL: Duration = Duration::from_secs(5); +/// How often the UI loop drains backend replies and events. +const UI_TICK: Duration = Duration::from_millis(100); +const DEPOT_COMMAND_MARKER: &str = "Steam console command: "; + +#[derive(Clone, Copy, PartialEq, Eq)] +enum Page { + Dashboard, + T7, + Exe, + Enhanced, + Graphics, + Tools, +} + +impl Page { + const ALL: [Page; 6] = [ + Page::Dashboard, + Page::T7, + Page::Exe, + Page::Enhanced, + Page::Graphics, + Page::Tools, + ]; + + fn label(self) -> &'static str { + match self { + Page::Dashboard => "Dashboard", + Page::T7 => "T7 Patch", + Page::Exe => "EXE Swapper", + Page::Enhanced => "Enhanced", + Page::Graphics => "Graphics", + Page::Tools => "Tools", + } + } + + fn glyph(self) -> Glyph { + match self { + Page::Dashboard => Glyph::Dashboard, + Page::T7 => Glyph::Shield, + Page::Exe => Glyph::Refresh, + Page::Enhanced => Glyph::Gem, + Page::Graphics => Glyph::Image, + Page::Tools => Glyph::Wrench, + } + } +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum GraphicsTab { + Settings, + Dxvk, +} + +/// Result of the last "Validate Source" run (Enhanced page). +struct Validation { + label: String, + ok: Option, + checked_at: Option, + source: String, +} + +impl Validation { + fn not_run() -> Self { + Self { + label: "Not run".into(), + ok: None, + checked_at: None, + source: String::new(), + } + } +} + +/// The Steam console prompt shown when the compatible depot is missing. +struct Depot { + command: String, + copied: bool, + watching: bool, + last_poll: Instant, +} + +type Staged = (String, Vec); + +pub struct ControlCenter { + backend: Backend, + state: Value, + page: Page, + graphics_tab: GraphicsTab, + busy: bool, + connected: bool, + message: String, + failed: bool, + last_refresh: Instant, + /// Path of the request currently owned by the backend worker. + inflight: String, + inputs: BTreeMap<&'static str, Entity>, + sliders: BTreeMap<&'static str, Entity>, + /// An action waiting for the user's confirmation. Multi-step actions + /// (such as "Apply All") stage several requests that run in order. + pending: Option, + queue: VecDeque, + selected_profile: String, + seen_profile: String, + t7_color: String, + t7_password_enabled: bool, + t7_password_touched: bool, + show_current_password: bool, + show_new_password: bool, + validation: Validation, + depot: Option, + dxvk_draft: Value, + dxvk_synced: Value, + advanced_open: bool, + danger_open: bool, + content_scroll: ScrollHandle, + log_scroll: ScrollHandle, + log_count: usize, + /// The long operation in flight (or just finished); see `progress.rs`. + progress: Option, + /// The operation behind `inflight`, when it gets a progress strip. + inflight_op: Option, + /// Log lines streamed through `on_progress` since the last snapshot. + live_logs: Vec, + /// A status refresh is wanted as soon as the backend is idle. + refresh_pending: bool, + /// Titlebar drag in progress; see `chrome::drag_region`. + title_drag: chrome::DragArmed, + /// Whether the first presented frame has been reported; see `render`. + first_frame: bool, +} + +fn dxvk_recommended() -> Value { + json!({ + "enableAsync": true, + "gplAsyncCache": true, + "numCompilerThreads": 0, + "maxFrameRate": 0, + "maxFrameLatency": 1, + "tearFree": "True", + "hudEnabled": false, + }) +} + +/// `formatTimestamp` from the Electron renderer, without a locale database. +fn format_timestamp(value: &str) -> String { + if value.is_empty() { + return "Never".into(); + } + value + .split(['.', '+', 'Z']) + .next() + .unwrap_or(value) + .replace('T', " ") +} + +fn clock_now() -> String { + let secs = SystemTime::now() + .duration_since(UNIX_EPOCH) + .map(|elapsed| elapsed.as_secs()) + .unwrap_or_default(); + let day = secs % 86_400; + format!( + "{:02}:{:02}:{:02} UTC", + day / 3600, + day % 3600 / 60, + day % 60 + ) +} + +impl ControlCenter { + pub fn new(backend: Backend, window: &mut Window, cx: &mut Context) -> Self { + theme::apply(cx); + let inputs: BTreeMap<_, _> = [ + ("directory", "Black Ops III directory"), + ("gamertag", "Gamertag (up to 20 characters)"), + ("password", "Enter network password"), + ("dump", "Path to DUMP.zip or an extracted folder"), + ("fps", "0–1000"), + ("resolution", "1920x1080"), + ("refresh", "1–1000"), + ("latency", "0–4"), + ("vram", "75–100"), + ("dxvk-threads", "0–64"), + ("dxvk-latency", "0–16"), + ] + .into_iter() + .map(|(key, placeholder)| { + let input = cx.new(|cx| { + InputState::new(window, cx) + .placeholder(placeholder) + .masked(key == "password") + }); + (key, input) + }) + .collect(); + for input in inputs.values() { + // Drafts feed buttons and badges, so repaint while the user types. + cx.subscribe_in( + input, + window, + |_, _, event: &InputEvent, _, cx| match event { + InputEvent::Change => cx.notify(), + InputEvent::PressEnter { .. } => {} + InputEvent::Focus | InputEvent::Blur => {} + }, + ) + .detach(); + } + cx.subscribe_in( + &inputs["directory"], + window, + |view, _, event: &InputEvent, _, cx| { + if matches!(event, InputEvent::PressEnter { .. }) { + view.stage_directory(cx); + } + }, + ) + .detach(); + let sliders: BTreeMap<_, _> = [ + ("render", 50., 200., 100.), + ("fov", 65., 120., 80.), + ("dxvk-fps", 0., 360., 0.), + ] + .into_iter() + .map(|(key, min, max, value)| { + let slider = cx.new(|_| { + SliderState::new() + .min(min) + .max(max) + .step(1.) + .default_value(value) + }); + (key, slider) + }) + .collect(); + for slider in sliders.values() { + cx.subscribe_in(slider, window, |_, _, _: &SliderEvent, _, cx| cx.notify()) + .detach(); + } + // Developer knob for screenshots: PATCHOPSIII_GPUI_PAGE=t7|exe|enhanced|graphics|dxvk|tools. + let (page, graphics_tab) = match std::env::var("PATCHOPSIII_GPUI_PAGE").as_deref() { + Ok("t7") => (Page::T7, GraphicsTab::Settings), + Ok("exe") => (Page::Exe, GraphicsTab::Settings), + Ok("enhanced") => (Page::Enhanced, GraphicsTab::Settings), + Ok("graphics") => (Page::Graphics, GraphicsTab::Settings), + Ok("dxvk") => (Page::Graphics, GraphicsTab::Dxvk), + Ok("tools") => (Page::Tools, GraphicsTab::Settings), + _ => (Page::Dashboard, GraphicsTab::Settings), + }; + let mut view = Self { + backend, + state: Value::Null, + page, + graphics_tab, + busy: false, + connected: false, + message: "Connecting to the local service…".into(), + failed: false, + last_refresh: Instant::now(), + inflight: String::new(), + inputs, + sliders, + pending: None, + queue: VecDeque::new(), + selected_profile: "default".into(), + seen_profile: String::new(), + t7_color: String::new(), + t7_password_enabled: false, + t7_password_touched: false, + show_current_password: false, + show_new_password: false, + validation: Validation::not_run(), + depot: None, + dxvk_draft: dxvk_recommended(), + dxvk_synced: Value::Null, + advanced_open: false, + danger_open: false, + content_scroll: ScrollHandle::new(), + log_scroll: ScrollHandle::new(), + log_count: 0, + progress: None, + inflight_op: None, + live_logs: Vec::new(), + refresh_pending: false, + title_drag: Default::default(), + first_frame: false, + }; + // Pick the window background for the decorations the platform granted, and redo + // it if they change later; repaint when the frame (tiling, maximise) + // changes shape. + // GPUI only forwards `TitlebarOptions::title` on X11/Windows/macOS. + window.set_window_title("PatchOpsIII"); + chrome::settle(window); + cx.observe_window_appearance(window, |_, window, cx| { + chrome::settle(window); + cx.notify(); + }) + .detach(); + cx.observe_window_bounds(window, |_, _, cx| cx.notify()) + .detach(); + view.send("/api/status", None, cx); + // Drain replies and periodically refresh on GPUI's foreground executor. + // The only blocking work lives on the dedicated HTTP worker. + cx.spawn_in(window, async move |entity, cx| { + loop { + cx.background_executor().timer(UI_TICK).await; + if entity + .update_in(cx, |view, window, cx| { + // Events precede their operation's reply on the worker. + while let Ok(event) = view.backend.events.try_recv() { + view.handle_backend_event(event, cx); + } + while let Ok(reply) = view.backend.replies.try_recv() { + view.handle_reply(reply, window, cx); + } + view.poll(cx); + }) + .is_err() + { + break; + } + } + }) + .detach(); + view + } + + /// Maps worker events onto `on_progress`. Started/finished are raised by + /// the UI from the request and its reply, so only intermediate stages pass. + fn handle_backend_event(&mut self, event: BackendEvent, cx: &mut Context) { + match event { + BackendEvent::Log(entry) => self.on_progress( + ProgressEvent::log(entry.category, entry.message, Some(entry.line)), + cx, + ), + BackendEvent::Progress(progress) => { + if matches!(progress.stage.as_str(), "started" | "completed" | "failed") { + return; + } + if let Some(op) = Operation::from_path(&progress.op) { + self.on_progress( + ProgressEvent::Stage { + op, + stage: progress.stage, + fraction: progress.fraction, + }, + cx, + ); + } + } + } + } + + fn handle_reply(&mut self, reply: Reply, window: &mut Window, cx: &mut Context) { + self.busy = false; + let path = std::mem::take(&mut self.inflight); + let (is_status, carried_state) = (reply.is_status, reply.state.is_some()); + // A depot prompt is a question for the user, not a failed operation. + let failed = reply.failed && !reply.message.contains(DEPOT_COMMAND_MARKER); + if let Some(op) = self.inflight_op.take() { + let message = reply.message.lines().next().unwrap_or_default().to_owned(); + self.on_progress( + ProgressEvent::Finished { + op, + ok: !failed, + message, + }, + cx, + ); + } + if reply.failed { + self.queue.clear(); + self.failed = true; + if reply.is_status { + self.connected = false; + } + self.on_failure(&path, &reply.message); + self.message = reply.message; + } else { + let initial = self.state.is_null(); + let reconnected = !self.connected; + if let Some(state) = reply.state { + self.state = state; + self.connected = true; + if initial { + self.load_inputs(window, cx); + } else { + self.sync_drafts(window, cx); + } + } + self.on_success(&path, &reply.message, window, cx); + if !reply.is_status || reconnected || self.message.starts_with("Connecting") { + self.failed = false; + self.message = reply.message; + } + } + self.last_refresh = Instant::now(); + if carried_state { + self.prune_live_logs(); + } + self.refresh_after(is_status, failed, carried_state); + self.follow_log(); + if let Some(next) = self.queue.pop_front() { + self.send(&next.path, next.body, cx); + } + cx.notify(); + } + + /// Keep the Activity Log pinned to its newest line when entries arrive. + fn follow_log(&mut self) { + let entries = self.visible_logs().len(); + if entries != self.log_count { + self.log_count = entries; + self.log_scroll.scroll_to_bottom(); + } + } + + /// Refresh-after-action policy, in one place. A successful action normally + /// answers with the new state document, so nothing more is needed. One that + /// answers without it, or fails (the backend may have changed state and + /// logged before failing), asks for a status refresh right away instead of + /// waiting out `STATUS_POLL_INTERVAL`. + fn refresh_after(&mut self, is_status: bool, failed: bool, carried_state: bool) { + if !is_status && (failed || !carried_state) { + self.request_refresh(); + } + } + + /// Ask for a status refresh as soon as the backend is idle. Backend events + /// that mean "state changed outside a request" can call this too. + pub fn request_refresh(&mut self) { + self.refresh_pending = true; + } + + /// Per-endpoint bookkeeping for a failed request. + fn on_failure(&mut self, path: &str, message: &str) { + match path { + "/api/enhanced-validate" => { + self.validation = Validation { + label: message.to_owned(), + ok: Some(false), + checked_at: Some(clock_now()), + source: self.validation.source.clone(), + }; + } + "/api/exe-swap/compatible" => { + if let Some((_, command)) = message.split_once(DEPOT_COMMAND_MARKER) { + // The depot is not downloaded yet: this is a prompt, not an error. + let watching = self.depot.as_ref().is_some_and(|depot| depot.watching); + let copied = self.depot.as_ref().is_some_and(|depot| depot.copied); + self.depot = Some(Depot { + command: command.trim().to_owned(), + copied, + watching, + last_poll: Instant::now(), + }); + self.failed = false; + } else if let Some(depot) = self.depot.as_mut() { + depot.watching = false; + } + } + _ => {} + } + } + + /// Per-endpoint bookkeeping for a successful request. + fn on_success( + &mut self, + path: &str, + message: &str, + window: &mut Window, + cx: &mut Context, + ) { + match path { + "/api/enhanced-validate" => { + self.validation = Validation { + label: message.to_owned(), + ok: Some(true), + checked_at: Some(clock_now()), + source: self.text("dump", cx).trim().to_owned(), + }; + } + "/api/exe-swap/compatible" => self.depot = None, + "/api/logs/clear" => self.live_logs.clear(), + "/api/t7-config" => { + self.t7_password_touched = false; + self.load_t7(window, cx); + } + _ => {} + } + } + + /// Background work: status refresh and depot watching. + fn poll(&mut self, cx: &mut Context) { + self.expire_progress(cx); + if self.busy || self.pending.is_some() { + return; + } + let watching = self.depot.as_ref().is_some_and(|depot| { + depot.watching && depot.last_poll.elapsed() >= DEPOT_POLL_INTERVAL + }); + if watching { + if let Some(depot) = self.depot.as_mut() { + depot.last_poll = Instant::now(); + } + self.send("/api/exe-swap/compatible", Some(json!({})), cx); + } else if self.refresh_pending || self.last_refresh.elapsed() >= STATUS_POLL_INTERVAL { + self.refresh_pending = false; + self.send("/api/status", None, cx); + } + } + + fn send(&mut self, path: &str, body: Option, cx: &mut Context) { + if self.busy { + return; + } + match self.backend.requests.send(Request { + path: path.into(), + body, + }) { + Ok(()) => { + self.busy = true; + self.inflight = path.to_owned(); + self.last_refresh = Instant::now(); + // The depot watcher re-sends the same request every few + // seconds; that is polling, not an operation to announce. + let watching = self.depot.as_ref().is_some_and(|depot| depot.watching); + self.inflight_op = Operation::from_path(path) + .filter(|op| !(watching && *op == Operation::ExeCompatible)); + if let Some(op) = self.inflight_op { + self.on_progress(ProgressEvent::Started(op), cx); + } + } + Err(_) => { + self.failed = true; + self.connected = false; + self.message = "Local service worker stopped. Restart PatchOpsIII.".into(); + } + } + cx.notify(); + } + + fn stage(&mut self, label: String, path: String, body: Value, cx: &mut Context) { + self.stage_all( + label, + vec![Request { + path, + body: Some(body), + }], + cx, + ); + } + + fn stage_all(&mut self, label: String, requests: Vec, cx: &mut Context) { + self.pending = Some((label, requests)); + cx.notify(); + } + + fn confirm(&mut self, cx: &mut Context) { + if self.busy { + return; + } + if let Some((label, mut requests)) = self.pending.take() { + self.message = format!("Running: {label}…"); + self.failed = false; + if requests.is_empty() { + cx.notify(); + return; + } + let first = requests.remove(0); + self.queue = requests.into(); + self.send(&first.path, first.body, cx); + } + } + + fn reject(&mut self, message: impl Into, cx: &mut Context) { + self.message = message.into(); + self.failed = true; + cx.notify(); + } + + fn text(&self, key: &str, cx: &App) -> String { + self.inputs[key].read(cx).value().to_string() + } + + fn set_text(&self, key: &str, value: String, window: &mut Window, cx: &mut Context) { + self.inputs[key].update(cx, |state, cx| state.set_value(value, window, cx)); + } + + fn set_slider(&self, key: &str, value: i64, window: &mut Window, cx: &mut Context) { + self.sliders[key].update(cx, |state, cx| state.set_value(value as f32, window, cx)); + } + + fn slider_value(&self, key: &str, cx: &App) -> i64 { + self.sliders[key].read(cx).value().start().round() as i64 + } + + /// Copy every draft control from the backend state (initial load and + /// "Reload Values"). + fn load_inputs(&mut self, window: &mut Window, cx: &mut Context) { + for (key, pointer) in [ + ("directory", "/gameDir"), + ("dump", "/enhanced/dumpSource"), + ("fps", "/graphics/maxFps"), + ("resolution", "/graphics/resolution"), + ("refresh", "/graphics/refreshRate"), + ("latency", "/advanced/maxFrameLatency"), + ("vram", "/advanced/vramTarget"), + ] { + let value = self.string(pointer); + self.set_text(key, value, window, cx); + } + for (key, pointer) in [ + ("render", "/graphics/renderResolution"), + ("fov", "/graphics/fov"), + ] { + let value = self.int(pointer, 0); + self.set_slider(key, value, window, cx); + } + self.load_t7(window, cx); + self.t7_password_touched = false; + self.seen_profile.clear(); + self.dxvk_synced = Value::Null; + self.sync_drafts(window, cx); + } + + fn load_t7(&mut self, window: &mut Window, cx: &mut Context) { + let name = self.string("/t7/plainName"); + self.set_text("gamertag", name, window, cx); + self.t7_color = self.string("/t7/colorCode"); + } + + /// Drafts that follow the backend unless the user is editing them. + fn sync_drafts(&mut self, window: &mut Window, cx: &mut Context) { + let active = self.string("/activeLaunchProfile"); + if active != self.seen_profile { + self.selected_profile = if active.is_empty() || active == "custom" { + "default".into() + } else { + active.clone() + }; + self.seen_profile = active; + } + if !self.t7_password_touched { + self.t7_password_enabled = !self.string("/t7/networkPassword").is_empty(); + } + let settings = self + .state + .pointer("/dxvk/settings") + .cloned() + .unwrap_or(Value::Null); + if settings != self.dxvk_synced && settings.is_object() { + self.dxvk_draft = settings.clone(); + self.dxvk_synced = settings; + self.show_dxvk_draft(window, cx); + } + } + + fn show_dxvk_draft(&mut self, window: &mut Window, cx: &mut Context) { + let threads = self.dxvk_int("numCompilerThreads").to_string(); + let latency = self.dxvk_int("maxFrameLatency").to_string(); + let fps = self.dxvk_int("maxFrameRate"); + self.set_text("dxvk-threads", threads, window, cx); + self.set_text("dxvk-latency", latency, window, cx); + self.set_slider("dxvk-fps", fps, window, cx); + } + + fn flag(&self, pointer: &str) -> bool { + self.state + .pointer(pointer) + .and_then(Value::as_bool) + .unwrap_or(false) + } + + /// A state value as display text; absent and null values become "". + fn string(&self, pointer: &str) -> String { + match self.state.pointer(pointer) { + Some(Value::String(s)) => s.clone(), + Some(Value::Number(n)) => n + .as_f64() + .map(|n| n.to_string()) + .unwrap_or_else(|| n.to_string()), + Some(Value::Null) | None => String::new(), + Some(v) => v.to_string(), + } + } + + /// A state value as display text with a visible fallback. + fn value(&self, pointer: &str) -> String { + let value = self.string(pointer); + if value.is_empty() { + "—".into() + } else { + value + } + } + + fn int(&self, pointer: &str, default: i64) -> i64 { + self.state + .pointer(pointer) + .and_then(Value::as_f64) + .map_or(default, |n| n.round() as i64) + } + + fn dxvk_flag(&self, key: &str) -> bool { + self.dxvk_draft[key].as_bool().unwrap_or(false) + } + + fn dxvk_int(&self, key: &str) -> i64 { + self.dxvk_draft[key].as_i64().unwrap_or(0) + } + + fn can_act(&self) -> bool { + !self.busy && self.connected && self.pending.is_none() + } + + /// A click handler that asks for confirmation before sending `path`. + fn stage_click( + &self, + cx: &mut Context, + label: impl Into, + path: &str, + body: Value, + ) -> impl Fn(&ClickEvent, &mut Window, &mut App) + 'static { + let label = label.into(); + let path = path.to_owned(); + cx.listener(move |view, _: &ClickEvent, _, cx| { + view.stage(label.clone(), path.clone(), body.clone(), cx) + }) + } + + /// A button that stages one request for confirmation. + fn action( + &self, + cx: &mut Context, + id: impl Into, + label: &str, + path: &str, + body: Value, + ) -> Btn { + Btn::new(id, label.to_owned()) + .disabled(!self.can_act()) + .on_click(self.stage_click(cx, label, path, body)) + } + + fn config_body(key: &str, value: Value) -> Value { + let comment = match key { + "MaxFPS" => "Maximum FPS cap", + "FOV" => "Field of view", + "FullScreenMode" => "0=Windowed,1=Fullscreen,2=Fullscreen Windowed", + "WindowSize" => "any text", + "RefreshRate" => "1 to 240", + "ResolutionPercent" => "50 to 200", + "Vsync" => "Vertical sync", + "DrawFPS" => "FPS counter", + "SmoothFramerate" => "Frame smoothing", + "RestrictGraphicsOptions" => "Expose all graphics options", + "SerializeRender" => "Reduce CPU pressure", + "MaxFrameLatency" => "Maximum frame latency", + _ => "Managed by PatchOpsIII", + }; + json!({"key": key, "value": value, "comment": comment}) + } + + /// A text input styled like the Electron `input` elements. + fn field(&self, key: &str) -> Input { + Input::new(&self.inputs[key]) + .disabled(self.busy) + .min_h(px(theme::CONTROL_HEIGHT - 2.)) + .bg(theme::field()) + .rounded(theme::radius_card()) + .border_color(theme::border()) + } + + fn stage_directory(&mut self, cx: &mut Context) { + let path = self.text("directory", cx); + let path = path.trim().to_owned(); + if path.is_empty() { + self.reject("Select or enter a directory first.", cx); + } else if path != self.string("/gameDir") { + self.stage( + format!("Use directory {path}"), + "/api/game-directory".into(), + json!({"path": path}), + cx, + ); + } + } + + fn choose_directory(&mut self, input: &'static str, window: &Window, cx: &mut Context) { + self.choose_path(input, false, window, cx); + } + + /// Native picker for a folder or, with `file`, a single file. Platform + /// pickers can't offer both at once, so callers expose one per action. + fn choose_path( + &mut self, + input: &'static str, + file: bool, + window: &Window, + cx: &mut Context, + ) { + let paths = cx.prompt_for_paths(PathPromptOptions { + files: file, + directories: !file, + multiple: false, + prompt: Some( + if file { + "Select a file" + } else { + "Select a folder" + } + .into(), + ), + }); + cx.spawn_in(window, async move |entity, cx| match paths.await { + Ok(Ok(Some(paths))) => { + if let Some(path) = paths.first() { + let value = path.to_string_lossy().into_owned(); + let _ = entity.update_in(cx, |view, window, cx| { + view.set_text(input, value, window, cx); + view.after_pick(input, cx); + }); + } + } + Ok(Ok(None)) => {} + _ => { + let _ = entity.update(cx, |view, cx| { + view.reject("File picker unavailable. Enter the path directly.", cx); + }); + } + }) + .detach(); + } + + /// What picking a folder means for each input. + fn after_pick(&mut self, input: &str, cx: &mut Context) { + match input { + "directory" => self.stage_directory(cx), + "dump" => { + self.validation = Validation::not_run(); + cx.notify(); + } + _ => {} + } + } + + /// Latest log lines, oldest first, without the service start-up notice. + fn log_key(entry: &Value) -> String { + format!( + "{}|{}|{}", + entry["line"], entry["category"], entry["message"] + ) + } + + /// Streamed lines the status snapshot now contains no longer need keeping. + fn prune_live_logs(&mut self) { + let known: std::collections::HashSet = self.state["logs"] + .as_array() + .into_iter() + .flatten() + .map(Self::log_key) + .collect(); + self.live_logs + .retain(|entry| !known.contains(&Self::log_key(entry))); + } + + /// Snapshot entries followed by lines streamed since (`on_progress`). + fn visible_logs(&self) -> Vec<(String, String)> { + let mut seen = std::collections::HashSet::new(); + let mut entries: Vec<(String, String)> = self.state["logs"] + .as_array() + .into_iter() + .flatten() + .chain(&self.live_logs) + .filter(|entry| entry["message"] != "PatchOpsIII local API started.") + .filter(|entry| seen.insert(Self::log_key(entry))) + .map(|entry| { + ( + entry["category"].as_str().unwrap_or("Info").to_owned(), + entry["message"].as_str().unwrap_or("").to_owned(), + ) + }) + .collect(); + if entries.len() > 120 { + entries.drain(..entries.len() - 120); + } + entries + } + + fn page_view(&self, cx: &mut Context) -> AnyElement { + match self.page { + Page::Dashboard => self.dashboard_view(cx), + Page::T7 => self.t7_view(cx), + Page::Exe => self.exe_view(cx), + Page::Enhanced => self.enhanced_view(cx), + Page::Graphics => self.graphics_view(cx), + Page::Tools => self.tools_view(cx), + } + } + + /// `.titlebar`: logo, name, version (click to check for updates), the + /// connection state and the caption buttons. The whole strip is the + /// window's titlebar; everything except the buttons drags the window. + fn header( + &self, + frame: chrome::Frame, + window: &Window, + cx: &mut Context, + ) -> impl IntoElement { + let version = self.string("/appVersion"); + let version = if version.to_lowercase().starts_with('v') || version.is_empty() { + version + } else { + format!("v{version}") + }; + let can_act = self.can_act(); + let drag = &self.title_drag; + chrome::titlebar(frame) + .child( + chrome::drag_region("titlebar-brand", drag, frame) + .flex() + .items_center() + .flex_none() + .h_full() + .pl(px(10.)) + .pr(px(8.)) + .gap(px(8.)) + .child( + img("images/logo.png") + .size(px(18.)) + .flex_none() + .rounded(px(4.)), + ) + .child(div().text_size(px(theme::FONT_SM)).child("PatchOpsIII")), + ) + .child( + div() + .id("check-updates") + .flex() + .items_center() + .gap(px(5.)) + .h(px(20.)) + .px(px(8.)) + .rounded(px(6.)) + .text_size(px(11.)) + .font_weight(FontWeight::BOLD) + .text_color(theme::muted()) + .when(!can_act, |this| this.opacity(0.7).cursor_not_allowed()) + .when(can_act, |this| { + this.cursor_pointer() + .hover(|style| style.bg(theme::white(0.07)).text_color(theme::text())) + .on_click(self.stage_click( + cx, + "Check for updates", + "/api/update-check", + json!({}), + )) + }) + .child(version) + .child(icon(Glyph::Refresh, 12., theme::muted())), + ) + .child( + chrome::drag_region("titlebar-spacer", drag, frame) + .flex_1() + .h_full(), + ) + .child( + chrome::drag_region("titlebar-status", drag, frame) + .flex() + .items_center() + .h_full() + .px(px(8.)) + .child(self.connection_badge()), + ) + .child( + Btn::new("reload-values", "Reload Values") + .tiny() + .disabled(self.busy || !self.connected) + .on_click(cx.listener(|view, _: &ClickEvent, window, cx| { + view.load_inputs(window, cx); + cx.notify(); + })), + ) + .child(div().w(px(8.)).h_full().flex_none()) + .child( + Btn::new("refresh", if self.busy { "Working…" } else { "Refresh" }) + .tiny() + .icon(Glyph::Refresh) + .disabled(self.busy) + .on_click(cx.listener(|view, _: &ClickEvent, _, cx| { + view.message = "Connecting to the local service…".into(); + view.send("/api/status", None, cx); + })), + ) + .child( + chrome::drag_region("titlebar-end", drag, frame) + .flex_none() + .h_full() + .w(px(10.)), + ) + .children(chrome::caption_buttons(frame, window)) + } + + fn connection_badge(&self) -> impl IntoElement { + let (tone, label) = if self.connected { + (theme::ok(), "Connected") + } else { + (theme::danger(), "Offline") + }; + div() + .flex() + .items_center() + .gap(px(6.)) + .text_size(px(theme::FONT_XS)) + .font_weight(FontWeight::BOLD) + .text_color(tone) + .child(div().size(px(8.)).rounded_full().bg(tone)) + .child(label) + } + + /// `.directory-row`. + fn directory_row(&self, cx: &mut Context) -> impl IntoElement { + let draft = self.text("directory", cx); + let draft = draft.trim(); + let dirty = !draft.is_empty() && draft != self.string("/gameDir"); + div() + .flex() + .items_end() + .gap(px(10.)) + .flex_none() + .child( + div() + .flex() + .flex_col() + .flex_1() + .min_w_0() + .gap(px(6.)) + .child(components::field_label("Game Directory:")) + .child(self.field("directory")), + ) + .when(dirty, |row| { + row.child( + Btn::new("use-directory", "Use Directory") + .disabled(!self.can_act()) + .on_click( + cx.listener(|view, _: &ClickEvent, _, cx| view.stage_directory(cx)), + ), + ) + }) + .child( + Btn::new("browse-directory", "Browse...") + .icon(Glyph::FolderOpen) + .disabled(self.busy) + .on_click(cx.listener(|view, _: &ClickEvent, window, cx| { + view.choose_directory("directory", window, cx) + })), + ) + .child( + self.action(cx, "launch", "Launch Game", "/api/launch", json!({})) + .icon(Glyph::Play) + .primary(), + ) + } + + /// `.error-strip`. + fn error_strip(&self, cx: &mut Context) -> impl IntoElement { + div() + .flex() + .items_center() + .flex_none() + .gap(px(8.)) + .min_h(px(34.)) + .px(px(12.)) + .py(px(6.)) + .border_1() + .border_color(theme::danger_alpha(0.38)) + .rounded(px(9.)) + .bg(theme::danger_alpha(0.12)) + .text_color(theme::danger_text()) + .child(icon(Glyph::Alert, 16., theme::danger_text())) + .child(div().flex_1().min_w_0().child(self.message.clone())) + .child( + div() + .id("dismiss-error") + .flex() + .items_center() + .justify_center() + .size(px(26.)) + .rounded(px(7.)) + .cursor_pointer() + .hover(|style| style.bg(theme::white(0.12))) + .on_click(cx.listener(|view, _: &ClickEvent, _, cx| { + view.failed = false; + cx.notify(); + })) + .child(icon(Glyph::Close, 15., theme::danger_text())), + ) + } + + /// `.nav-panel`. + fn nav(&self, cx: &mut Context) -> impl IntoElement { + div() + .flex() + .flex_col() + .flex_none() + .gap(px(7.)) + .w(px(theme::NAV_WIDTH)) + .p(px(10.)) + .border_1() + .border_color(theme::border()) + .rounded(theme::radius()) + .bg(theme::white(0.045)) + .children(Page::ALL.into_iter().enumerate().map(|(index, page)| { + let active = self.page == page; + let color = if active { + theme::text() + } else { + theme::muted_strong() + }; + div() + .id(("nav", index)) + .flex() + .items_center() + .gap(px(10.)) + .min_h(px(theme::ROW_HEIGHT)) + .px(px(12.)) + .border_1() + .border_color(if active { + theme::accent_alpha(0.22) + } else { + theme::white(0.) + }) + .rounded(theme::radius_control()) + .bg(if active { + linear_gradient( + 135., + linear_color_stop(theme::accent_alpha(0.22), 0.), + linear_color_stop(theme::white(0.07), 1.), + ) + } else { + solid_background(theme::white(0.)) + }) + .text_size(px(theme::FONT_MD)) + .font_weight(FontWeight::BOLD) + .text_color(color) + .cursor_pointer() + .when(!active, |this| { + this.hover(|style| style.bg(theme::white(0.06)).text_color(theme::text())) + }) + .on_click(cx.listener(move |view, _: &ClickEvent, _, cx| { + view.page = page; + view.content_scroll.set_offset(point(px(0.), px(0.))); + cx.notify(); + })) + .child(icon(page.glyph(), 18., color)) + .child(div().truncate().child(page.label())) + })) + } + + /// `.log-panel`: the last lines of the backend's activity log. + fn log_panel(&self, height: Pixels, cx: &mut Context) -> impl IntoElement { + let entries = self.visible_logs(); + let status_color = if self.failed { + theme::warning() + } else { + theme::muted() + }; + div() + .flex() + .flex_col() + .flex_none() + .h(height) + .child( + div() + .flex() + .items_baseline() + .justify_between() + .gap(px(12.)) + .mb(px(8.)) + .child( + div() + .text_size(px(theme::FONT_LG)) + .font_weight(FontWeight::BOLD) + .child("Activity Log"), + ) + .child( + div() + .min_w_0() + .truncate() + .text_size(px(theme::FONT_XS)) + .text_color(status_color) + .child(self.status_line()), + ), + ) + .child( + div() + .flex_1() + .min_h_0() + .p(px(10.)) + .border_1() + .border_color(theme::border()) + .rounded(theme::radius()) + .bg(theme::panel()) + .child( + div() + .relative() + .size_full() + .border_1() + .border_color(theme::white(0.)) + .rounded(px(9.)) + .bg(theme::log_surface()) + .child( + div() + .id("log-scroll") + .size_full() + .overflow_y_scroll() + .track_scroll(&self.log_scroll) + .px(px(10.)) + .py(px(8.)) + .font_family(cx.theme().mono_font_family.clone()) + .text_size(px(theme::FONT_XS)) + .when(entries.is_empty(), |this| { + this.text_color(theme::muted()).child("No activity yet.") + }) + .children(entries.into_iter().map(|(category, message)| { + let tone = match category.as_str() { + "Success" => theme::ok(), + "Warning" => theme::warning(), + "Error" => theme::danger(), + _ => theme::muted(), + }; + div() + .flex() + .gap(px(10.)) + .mb(px(4.)) + .text_color(theme::text().alpha(0.76)) + .child( + div() + .w(px(72.)) + .flex_none() + .font_weight(FontWeight::BOLD) + .text_color(tone) + .child(category), + ) + .child(div().flex_1().min_w_0().child(message)) + })), + ) + .vertical_scrollbar(&self.log_scroll), + ), + ) + } + + /// `.startup-screen`, shown until the first status document arrives. + fn startup_screen(&self, cx: &mut Context) -> impl IntoElement { + let failed = self.failed; + div() + .flex() + .flex_col() + .flex_1() + .items_center() + .justify_center() + .gap(px(14.)) + .child(img("images/logo.png").size(px(64.)).rounded(px(14.))) + .child( + div() + .flex() + .flex_col() + .items_center() + .gap(px(5.)) + .max_w(px(360.)) + .text_center() + .child( + div() + .text_size(px(theme::FONT_LG)) + .font_weight(FontWeight::BOLD) + .child(if failed { + "Startup took too long" + } else { + "Opening PatchOpsIII" + }), + ) + .child( + div() + .text_size(px(theme::FONT_SM)) + .font_weight(FontWeight::SEMIBOLD) + .text_color(theme::muted()) + .child(if failed { + self.message.clone() + } else { + "Loading your settings...".to_owned() + }), + ), + ) + .when(!failed, |this| { + this.child( + div() + .w(px(132.)) + .h(px(3.)) + .rounded_full() + .bg(theme::white(0.1)) + .child(div().w(px(55.)).h_full().rounded_full().bg(theme::accent())), + ) + }) + .when(failed, |this| { + this.child( + Btn::new("retry", "Try Again") + .compact() + .icon(Glyph::Refresh) + .disabled(self.busy) + .on_click(cx.listener(|view, _: &ClickEvent, _, cx| { + view.message = "Connecting to the local service…".into(); + view.failed = false; + view.send("/api/status", None, cx); + })), + ) + }) + } +} + +impl Render for ControlCenter { + fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { + if !self.first_frame { + self.first_frame = true; + chrome::first_frame(window, cx); + } + let ready = !self.state.is_null(); + // `.log-panel` is 180px tall with a 140px floor; give the pages the + // difference on short windows so the default 820px layout fits. + let log_height = px((f32::from(window.viewport_size().height) * 0.19).clamp(140., 180.)); + let body = if ready { + div() + .flex() + .flex_col() + .flex_1() + .min_h_0() + .gap(px(theme::APP_GAP)) + .child(self.directory_row(cx)) + .when(self.failed, |this| this.child(self.error_strip(cx))) + .child( + div() + .flex() + .flex_col() + .flex_1() + .min_h_0() + .gap(px(theme::PANEL_GAP)) + .child( + div() + .flex() + .flex_1() + .min_h_0() + .gap(px(theme::PANEL_GAP)) + .child(self.nav(cx)) + .child( + div() + .relative() + .flex_1() + .min_w_0() + .child( + div() + .id("content-scroll") + .size_full() + .overflow_y_scroll() + .track_scroll(&self.content_scroll) + .flex() + .flex_col() + .gap(px(theme::PANEL_GAP)) + .pr(px(8.)) + // Content must keep its natural height (flex-shrink + // would squash it into the viewport and clip the + // cards instead of scrolling). + .child( + div() + .flex() + .flex_col() + .flex_none() + .child(self.page_view(cx)), + ), + ) + .vertical_scrollbar(&self.content_scroll), + ), + ) + .child(self.log_panel(log_height, cx)), + ) + } else { + div() + .flex() + .flex_col() + .flex_1() + .min_h_0() + .child(self.startup_screen(cx)) + }; + let frame = chrome::Frame::read(window); + let root = div() + .relative() + .flex() + .flex_col() + .size_full() + .bg(linear_gradient( + 180., + linear_color_stop(theme::bg_deep(), 0.), + linear_color_stop(theme::bg(), 1.), + )) + .font(theme::ui_font()) + .text_color(theme::text()) + .text_size(px(theme::FONT_MD)) + .child(self.header(frame, window, cx)) + .child( + div() + .flex() + .flex_col() + .flex_1() + .min_h_0() + .p(px(theme::APP_PAD)) + .child(body), + ) + .children( + self.confirm_modal(cx) + .map(|modal| frame.round_overlay(modal)), + ) + .children(self.depot_modal(cx).map(|modal| frame.round_overlay(modal))); + // Rounded corners and the 1px outline of a client-decorated window. + frame.frame(root) + } +} diff --git a/native/gpui/src/ui/assets.rs b/native/gpui/src/ui/assets.rs new file mode 100644 index 0000000..0c8cfff --- /dev/null +++ b/native/gpui/src/ui/assets.rs @@ -0,0 +1,151 @@ +//! Embedded assets: Lucide icons (the same set the Electron UI uses) and the app logo. +use anyhow::Result; +use gpui::{AssetSource, SharedString}; +use std::borrow::Cow; + +const ASSETS: &[(&str, &[u8])] = &[ + ( + "caption/close.svg", + include_bytes!("../../assets/caption/close.svg"), + ), + ( + "caption/maximize.svg", + include_bytes!("../../assets/caption/maximize.svg"), + ), + ( + "caption/minimize.svg", + include_bytes!("../../assets/caption/minimize.svg"), + ), + ( + "caption/restore.svg", + include_bytes!("../../assets/caption/restore.svg"), + ), + ( + "icons/check.svg", + include_bytes!("../../assets/icons/check.svg"), + ), + ( + "icons/chevron-down.svg", + include_bytes!("../../assets/icons/chevron-down.svg"), + ), + ( + "icons/chevron-right.svg", + include_bytes!("../../assets/icons/chevron-right.svg"), + ), + ( + "icons/chevron-up.svg", + include_bytes!("../../assets/icons/chevron-up.svg"), + ), + ( + "icons/circle-check.svg", + include_bytes!("../../assets/icons/circle-check.svg"), + ), + ( + "icons/clipboard.svg", + include_bytes!("../../assets/icons/clipboard.svg"), + ), + ( + "icons/close.svg", + include_bytes!("../../assets/icons/close.svg"), + ), + ( + "icons/cpu.svg", + include_bytes!("../../assets/icons/cpu.svg"), + ), + ( + "icons/download.svg", + include_bytes!("../../assets/icons/download.svg"), + ), + ( + "icons/eraser.svg", + include_bytes!("../../assets/icons/eraser.svg"), + ), + ( + "icons/external-link.svg", + include_bytes!("../../assets/icons/external-link.svg"), + ), + ( + "icons/eye.svg", + include_bytes!("../../assets/icons/eye.svg"), + ), + ( + "icons/eye-off.svg", + include_bytes!("../../assets/icons/eye-off.svg"), + ), + ( + "icons/folder-open.svg", + include_bytes!("../../assets/icons/folder-open.svg"), + ), + ( + "icons/gem.svg", + include_bytes!("../../assets/icons/gem.svg"), + ), + ( + "icons/image.svg", + include_bytes!("../../assets/icons/image.svg"), + ), + ( + "icons/key-round.svg", + include_bytes!("../../assets/icons/key-round.svg"), + ), + ( + "icons/layout-dashboard.svg", + include_bytes!("../../assets/icons/layout-dashboard.svg"), + ), + ( + "icons/refresh-cw.svg", + include_bytes!("../../assets/icons/refresh-cw.svg"), + ), + ( + "icons/rotate-ccw.svg", + include_bytes!("../../assets/icons/rotate-ccw.svg"), + ), + ( + "icons/save.svg", + include_bytes!("../../assets/icons/save.svg"), + ), + ( + "icons/shield-check.svg", + include_bytes!("../../assets/icons/shield-check.svg"), + ), + ( + "icons/square-play.svg", + include_bytes!("../../assets/icons/square-play.svg"), + ), + ( + "icons/trash-2.svg", + include_bytes!("../../assets/icons/trash-2.svg"), + ), + ( + "icons/triangle-alert.svg", + include_bytes!("../../assets/icons/triangle-alert.svg"), + ), + ( + "icons/wrench.svg", + include_bytes!("../../assets/icons/wrench.svg"), + ), + ("icons/x.svg", include_bytes!("../../assets/icons/x.svg")), + ( + "images/logo.png", + include_bytes!("../../assets/images/logo.png"), + ), +]; + +pub struct Assets; + +impl AssetSource for Assets { + fn load(&self, path: &str) -> Result>> { + Ok(ASSETS + .iter() + .find(|(name, _)| *name == path) + .map(|(_, bytes)| Cow::Borrowed(*bytes))) + } + + fn list(&self, path: &str) -> Result> { + Ok(ASSETS + .iter() + .filter(|(name, _)| name.starts_with(path)) + .map(|(name, _)| SharedString::from(*name)) + .collect()) + } +} diff --git a/native/gpui/src/ui/chrome.rs b/native/gpui/src/ui/chrome.rs new file mode 100644 index 0000000..5fdd136 --- /dev/null +++ b/native/gpui/src/ui/chrome.rs @@ -0,0 +1,495 @@ +//! Window chrome: the custom titlebar, caption buttons, the rounded client-side +//! frame. +//! +//! The Electron app is frameless and draws its own titlebar (`TitleBar` in +//! `src/renderer/main.tsx`, `.titlebar`/`.window-controls` in `app.css`). +//! The native window does the same with the platform's own mechanics: +//! +//! * **Windows**: the strip is the real titlebar. `window_control_area` +//! (Drag/Min/Max/Close) answers `WM_NCHITTEST`, so dragging, double-click, +//! Aero snap, snap layouts on the maximise button and the caption-button +//! clicks are all handled by the OS. +//! * **Linux (Wayland and X11)**: the strip is a client-side titlebar. Moves +//! use `start_window_move`, double-click toggles maximise, right-click opens +//! the compositor's window menu, and the caption buttons call +//! minimize/zoom/remove. Resize edges, cursors and the shadow come from +//! gpui-component's `Root`; this module adds the rounded corners and edge +//! outline, which follow `window.window_decorations()` so they disappear +//! when maximised/tiled and when the compositor insists on server-side +//! decorations (then the caption buttons are hidden too). +//! * **macOS**: traffic lights (`appears_transparent`), no caption buttons. +use super::theme; +use gpui::{prelude::*, *}; +use std::{cell::Cell, rc::Rc, sync::OnceLock}; + +pub const TITLEBAR_HEIGHT: f32 = 32.; +/// `.window-controls` is three 46px columns. +const CAPTION_WIDTH: f32 = 46.; +/// Corner radius of a client-decorated window. +const FRAME_RADIUS: f32 = 10.; + +/// Test hook: `server` asks for compositor-drawn decorations (the fallback +/// path), `client` is the default on Linux. +const DECORATIONS_ENV: &str = "PATCHOPSIII_GPUI_DECORATIONS"; +/// Test hook: set to `1` to open the window maximised. +const MAXIMIZED_ENV: &str = "PATCHOPSIII_GPUI_MAXIMIZED"; + +/// The windowing system GPUI is running on. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Session { + Windows, + MacOs, + #[cfg(any(target_os = "linux", target_os = "freebsd"))] + Wayland, + #[cfg(any(target_os = "linux", target_os = "freebsd"))] + X11, + Other, +} + +impl Session { + pub fn name(self) -> &'static str { + match self { + Self::Windows => "Windows", + Self::MacOs => "macOS", + #[cfg(any(target_os = "linux", target_os = "freebsd"))] + Self::Wayland => "Wayland", + #[cfg(any(target_os = "linux", target_os = "freebsd"))] + Self::X11 => "X11", + Self::Other => "headless", + } + } +} + +pub fn session() -> Session { + static SESSION: OnceLock = OnceLock::new(); + *SESSION.get_or_init(|| { + if cfg!(target_os = "windows") { + Session::Windows + } else if cfg!(target_os = "macos") { + Session::MacOs + } else { + #[cfg(any(target_os = "linux", target_os = "freebsd"))] + match gpui::guess_compositor() { + "Wayland" => return Session::Wayland, + "X11" => return Session::X11, + _ => {} + } + Session::Other + } + }) +} + +fn env_flag(name: &str) -> bool { + std::env::var(name).is_ok_and(|value| matches!(value.trim(), "1" | "true" | "yes" | "on")) +} + +/// The app paints every pixel opaquely. Wayland makes client-decorated +/// surfaces transparent by itself; X11 needs the flag so the rounded corners +/// and shadow are cut out instead of rendering black. +#[cfg(any(target_os = "linux", target_os = "freebsd"))] +fn background_for(session: Session, csd: bool) -> WindowBackgroundAppearance { + if session == Session::X11 && csd { + WindowBackgroundAppearance::Transparent + } else { + WindowBackgroundAppearance::Opaque + } +} + +#[cfg(not(any(target_os = "linux", target_os = "freebsd")))] +fn background_for(_: Session, _: bool) -> WindowBackgroundAppearance { + WindowBackgroundAppearance::Opaque +} + +/// The options for the main window. +pub fn window_options(bounds: Bounds) -> WindowOptions { + let session = session(); + #[cfg(any(target_os = "linux", target_os = "freebsd"))] + let linux = matches!(session, Session::Wayland | Session::X11); + #[cfg(not(any(target_os = "linux", target_os = "freebsd")))] + let linux = false; + WindowOptions { + window_bounds: Some(if env_flag(MAXIMIZED_ENV) { + WindowBounds::Maximized(bounds) + } else { + WindowBounds::Windowed(bounds) + }), + window_min_size: Some(size(px(800.), px(600.))), + titlebar: Some(TitlebarOptions { + title: Some("PatchOpsIII".into()), + // Windows/macOS: no OS titlebar; macOS keeps its traffic lights. + appears_transparent: true, + traffic_light_position: Some(point(px(12.), px(10.))), + }), + // Ask for client-side decorations; the compositor may refuse + // (`settle` reads the outcome) and X11 without a compositor falls + // back to server-side decorations by itself. + window_decorations: linux.then_some( + if std::env::var(DECORATIONS_ENV).is_ok_and(|value| value == "server") { + WindowDecorations::Server + } else { + WindowDecorations::Client + }, + ), + window_background: background_for(session, true), + ..Default::default() + } +} + +/// What the window ended up with, as read from the platform. +#[derive(Clone, Copy, Debug)] +pub struct Frame { + /// The app draws the frame (and, on Linux, the caption buttons). + pub csd: bool, + pub tiling: Tiling, + pub maximized: bool, +} + +impl Frame { + pub fn read(window: &Window) -> Self { + let (csd, tiling) = match window.window_decorations() { + Decorations::Client { tiling } => (true, tiling), + Decorations::Server => (false, Tiling::default()), + }; + Self { + csd, + tiling, + maximized: window.is_maximized(), + } + } + + /// Whether the window draws its own caption buttons. + fn caption_buttons(&self) -> bool { + match session() { + Session::Windows => true, + Session::MacOs | Session::Other => false, + #[cfg(any(target_os = "linux", target_os = "freebsd"))] + Session::Wayland | Session::X11 => self.csd, + } + } + + /// Round the corners that are not flush with a screen edge. + pub fn round(&self, mut element: E, radius: Pixels) -> E { + if !self.csd { + return element; + } + let tiling = self.tiling; + if !(tiling.top || tiling.left) { + element = element.rounded_tl(radius); + } + if !(tiling.top || tiling.right) { + element = element.rounded_tr(radius); + } + if !(tiling.bottom || tiling.left) { + element = element.rounded_bl(radius); + } + if !(tiling.bottom || tiling.right) { + element = element.rounded_br(radius); + } + element + } + + /// Rounded corners plus the 1px outline of a client-decorated window. + pub fn frame(&self, mut element: E) -> E { + if !self.csd { + return element; + } + let tiling = self.tiling; + element = self + .round(element, px(FRAME_RADIUS)) + .border_color(theme::window_edge()); + if !tiling.top { + element = element.border_t_1(); + } + if !tiling.bottom { + element = element.border_b_1(); + } + if !tiling.left { + element = element.border_l_1(); + } + if !tiling.right { + element = element.border_r_1(); + } + element + } + + /// Round a full-window overlay (a modal scrim) like the frame it covers. + pub fn round_overlay(&self, element: E) -> E { + self.round(element, px(FRAME_RADIUS - 1.)) + } +} + +/// Apply the window background for the decorations the platform actually +/// granted, and re-apply it when they change (e.g. a compositor answering a +/// client-side-decoration request with server-side). +pub fn settle(window: &mut Window) { + thread_local! { + static APPLIED: Cell> = const { Cell::new(None) }; + } + let frame = Frame::read(window); + let background = background_for(session(), frame.csd); + if APPLIED.get() == Some(background) { + return; + } + APPLIED.set(Some(background)); + window.set_background_appearance(background); + info(format_args!( + "window frame: {} decorations ({background:?})", + if frame.csd { + "client-side" + } else { + "server-side" + }, + )); +} + +/// An INFO line on stderr, shown when `RUST_LOG` enables info for this crate +/// (`RUST_LOG=info`, `debug`, `patchopsiii_gpui=info`, ...). The crate has no +/// direct `log` dependency, and the app's default filter is `warn`. +pub fn info(message: std::fmt::Arguments) { + let enabled = std::env::var("RUST_LOG").is_ok_and(|filter| { + filter.split(',').any(|directive| { + let (target, level) = directive.rsplit_once('=').unwrap_or(("", directive)); + matches!(level.trim(), "info" | "debug" | "trace") + && (target.is_empty() || "patchopsiii_gpui".starts_with(target.trim())) + }) + }); + if enabled { + eprintln!("[INFO patchopsiii_gpui::ui::chrome] {message}"); + } +} + +/// One line for the log: which windowing system GPUI picked. +pub fn log_session() { + let desktop = std::env::var("XDG_CURRENT_DESKTOP").unwrap_or_default(); + info(format_args!( + "compositor: {} (desktop: {})", + session().name(), + if desktop.is_empty() { + "unknown" + } else { + &desktop + }, + )); +} + +/// Shared between a titlebar's drag regions: set on mouse-down, consumed by +/// the first drag motion (or cleared on release), so a double-click still +/// reaches the maximise toggle. +pub type DragArmed = Rc>; + +fn toggle_maximize(window: &mut Window) { + if cfg!(target_os = "macos") { + window.titlebar_double_click(); + } else { + window.zoom_window(); + } +} + +/// An area of the titlebar that moves the window. +pub fn drag_region(id: impl Into, armed: &DragArmed, frame: Frame) -> Stateful
{ + let region = div().id(id).window_control_area(WindowControlArea::Drag); + if cfg!(target_os = "windows") { + // `WM_NCHITTEST` reports HTCAPTION: the OS owns move/double-click. + return region; + } + let csd = frame.csd; + region + .on_mouse_down(MouseButton::Left, { + let armed = armed.clone(); + move |event, window, _| { + if event.click_count >= 2 { + armed.set(false); + toggle_maximize(window); + } else { + armed.set(true); + } + } + }) + .on_mouse_move({ + let armed = armed.clone(); + move |event, window, _| { + if armed.get() && event.dragging() { + armed.set(false); + window.start_window_move(); + } + } + }) + .on_mouse_up(MouseButton::Left, { + let armed = armed.clone(); + move |_, _, _| armed.set(false) + }) + .on_mouse_up_out(MouseButton::Left, { + let armed = armed.clone(); + move |_, _, _| armed.set(false) + }) + .on_mouse_down(MouseButton::Right, move |event, window, _| { + // The compositor's own window menu (restore/move/resize/close). + if csd { + window.show_window_menu(event.position); + } + }) +} + +/// The 32px strip: `.titlebar`. +pub fn titlebar(frame: Frame) -> Div { + let mut bar = div() + .flex() + .items_center() + .flex_none() + .h(px(TITLEBAR_HEIGHT)) + .bg(theme::titlebar()) + .border_b_1() + .border_color(theme::white(0.08)) + .when(cfg!(target_os = "macos"), |this| this.pl(px(80.))); + if frame.csd { + // The strip sits inside the 1px frame outline. + let radius = px(FRAME_RADIUS - 1.); + let tiling = frame.tiling; + if !(tiling.top || tiling.left) { + bar = bar.rounded_tl(radius); + } + if !(tiling.top || tiling.right) { + bar = bar.rounded_tr(radius); + } + } + bar +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum Caption { + Minimize, + Maximize, + Restore, + Close, +} + +impl Caption { + fn path(self) -> &'static str { + match self { + Self::Minimize => "caption/minimize.svg", + Self::Maximize => "caption/maximize.svg", + Self::Restore => "caption/restore.svg", + Self::Close => "caption/close.svg", + } + } + + fn label(self) -> &'static str { + match self { + Self::Minimize => "Minimize window", + Self::Maximize => "Maximize window", + Self::Restore => "Restore window", + Self::Close => "Close window", + } + } + + fn area(self) -> WindowControlArea { + match self { + Self::Minimize => WindowControlArea::Min, + Self::Maximize | Self::Restore => WindowControlArea::Max, + Self::Close => WindowControlArea::Close, + } + } + + fn act(self, window: &mut Window) { + match self { + Self::Minimize => window.minimize_window(), + Self::Maximize | Self::Restore => window.zoom_window(), + Self::Close => window.remove_window(), + } + } +} + +/// `.window-control`: 46x32, a 10px glyph, grey until hovered, red for close. +fn caption_button(kind: Caption, frame: Frame) -> Stateful
{ + let close = kind == Caption::Close; + let (hover_bg, hover_fg): (Hsla, Hsla) = if close { + (rgb(0xc91b17).into(), rgb(0xffffff).into()) + } else { + (theme::white(0.08), theme::text()) + }; + let mut button = div() + .id(kind.label()) + .group("caption") + .window_control_area(kind.area()) + .flex() + .items_center() + .justify_center() + .flex_none() + .w(px(CAPTION_WIDTH)) + .h(px(TITLEBAR_HEIGHT)) + .hover(move |style| style.bg(hover_bg)) + .child( + svg() + .path(kind.path()) + .size(px(10.)) + .flex_none() + .text_color(theme::text().alpha(0.76)) + .group_hover("caption", move |style| style.text_color(hover_fg)), + ); + if close && frame.csd && !(frame.tiling.top || frame.tiling.right) { + // The hover fill must not poke out of the window's rounded corner. + button = button.rounded_tr(px(FRAME_RADIUS - 1.)); + } + if cfg!(target_os = "windows") { + // The OS performs the action for HTMINBUTTON/HTMAXBUTTON/HTCLOSE. + button + } else { + button + .cursor_pointer() + .on_click(move |_, window, _| kind.act(window)) + } +} + +/// `.window-controls`, when this window draws its own. +pub fn caption_buttons(frame: Frame, window: &Window) -> Option
{ + if !frame.caption_buttons() { + return None; + } + let controls = window.window_controls(); + let maximize = if frame.maximized { + Caption::Restore + } else { + Caption::Maximize + }; + Some( + div() + .flex() + .flex_none() + .h_full() + .when(controls.minimize, |this| { + this.child(caption_button(Caption::Minimize, frame)) + }) + .when(controls.maximize, |this| { + this.child(caption_button(maximize, frame)) + }) + .child(caption_button(Caption::Close, frame)), + ) +} + +/// Called from the first `render`. GPUI only draws once the compositor has +/// configured the surface and asked for a frame, so reaching this means the +/// window is mapped; the line is what `scripts/smoke_gpui_wayland.py` waits for. +/// With `PATCHOPSIII_GPUI_SMOKE_EXIT=1` the window then closes itself (a normal +/// window close, so the process exits through the usual path with status 0). +pub fn first_frame(window: &Window, cx: &mut Context) { + let viewport = window.viewport_size(); + info(format_args!( + "first frame: window mapped, {}x{} px viewport, {} decorations", + f32::from(viewport.width) as i32, + f32::from(viewport.height) as i32, + if Frame::read(window).csd { + "client-side" + } else { + "server-side" + }, + )); + if env_flag("PATCHOPSIII_GPUI_SMOKE_EXIT") { + cx.spawn_in(window, async move |_, cx| { + cx.background_executor() + .timer(std::time::Duration::from_secs(1)) + .await; + cx.update(|window, _| window.remove_window()).ok(); + }) + .detach(); + } +} diff --git a/native/gpui/src/ui/components.rs b/native/gpui/src/ui/components.rs new file mode 100644 index 0000000..ce03578 --- /dev/null +++ b/native/gpui/src/ui/components.rs @@ -0,0 +1,855 @@ +//! Small building blocks that reproduce the Electron app's controls +//! (`.tool-button`, `.panel`, `.status-row`, `.toggle`, `.check-row`, ...). +use super::theme; +use gpui::{prelude::*, *}; + +pub type ClickHandler = Box; + +pub fn handler(f: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static) -> ClickHandler { + Box::new(f) +} + +/// Lucide icons bundled in `assets/icons` (see `assets.rs`). +#[derive(Clone, Copy)] +pub enum Glyph { + Alert, + Check, + CheckCircle, + ChevronDown, + ChevronRight, + Clipboard, + Close, + Cpu, + Dashboard, + Download, + Eraser, + ExternalLink, + EyeOff, + FolderOpen, + Gem, + Image, + Key, + Play, + Refresh, + Rotate, + Save, + Shield, + Trash, + Wrench, +} + +impl Glyph { + fn path(self) -> &'static str { + match self { + Self::Alert => "icons/triangle-alert.svg", + Self::Check => "icons/check.svg", + Self::CheckCircle => "icons/circle-check.svg", + Self::ChevronDown => "icons/chevron-down.svg", + Self::ChevronRight => "icons/chevron-right.svg", + Self::Clipboard => "icons/clipboard.svg", + Self::Close => "icons/x.svg", + Self::Cpu => "icons/cpu.svg", + Self::Dashboard => "icons/layout-dashboard.svg", + Self::Download => "icons/download.svg", + Self::Eraser => "icons/eraser.svg", + Self::ExternalLink => "icons/external-link.svg", + Self::EyeOff => "icons/eye-off.svg", + Self::FolderOpen => "icons/folder-open.svg", + Self::Gem => "icons/gem.svg", + Self::Image => "icons/image.svg", + Self::Key => "icons/key-round.svg", + Self::Play => "icons/square-play.svg", + Self::Refresh => "icons/refresh-cw.svg", + Self::Rotate => "icons/rotate-ccw.svg", + Self::Save => "icons/save.svg", + Self::Shield => "icons/shield-check.svg", + Self::Trash => "icons/trash-2.svg", + Self::Wrench => "icons/wrench.svg", + } + } +} + +pub fn icon(glyph: Glyph, size: f32, color: Hsla) -> Svg { + svg() + .path(glyph.path()) + .size(px(size)) + .flex_none() + .text_color(color) +} + +pub fn text_xs(label: impl Into, color: Hsla) -> Div { + div() + .text_size(px(theme::FONT_XS)) + .font_weight(FontWeight::SEMIBOLD) + .text_color(color) + .child(label.into()) +} + +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum Tone { + Plain, + Primary, + Danger, +} + +/// `.tool-button` / `.small-button`. +#[derive(IntoElement)] +pub struct Btn { + id: ElementId, + label: SharedString, + glyph: Option, + tone: Tone, + compact: bool, + tiny: bool, + grow: bool, + disabled: bool, + on_click: Option, +} + +impl Btn { + pub fn new(id: impl Into, label: impl Into) -> Self { + Self { + id: id.into(), + label: label.into(), + glyph: None, + tone: Tone::Plain, + compact: false, + tiny: false, + grow: false, + disabled: false, + on_click: None, + } + } + + pub fn icon(mut self, glyph: Glyph) -> Self { + self.glyph = Some(glyph); + self + } + + pub fn primary(mut self) -> Self { + self.tone = Tone::Primary; + self + } + + pub fn danger(mut self) -> Self { + self.tone = Tone::Danger; + self + } + + /// The shorter `.small-button` variant. + pub fn compact(mut self) -> Self { + self.compact = true; + self + } + + /// The 24px variant used in the title bar. + pub fn tiny(mut self) -> Self { + self.tiny = true; + self + } + + /// Share the row evenly with sibling buttons. + pub fn grow(mut self) -> Self { + self.grow = true; + self + } + + pub fn disabled(mut self, disabled: bool) -> Self { + self.disabled = disabled; + self + } + + pub fn on_click( + mut self, + handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static, + ) -> Self { + self.on_click = Some(Box::new(handler)); + self + } +} + +impl RenderOnce for Btn { + fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement { + let (fill, border, hover_fill, hover_border, color) = match self.tone { + Tone::Primary => ( + linear_gradient( + 135., + linear_color_stop(theme::accent(), 0.), + linear_color_stop(theme::accent_dark(), 1.), + ), + theme::accent_alpha(0.42), + theme::accent(), + theme::accent_alpha(0.7), + theme::text(), + ), + Tone::Danger => ( + solid_background(theme::danger_alpha(0.12)), + theme::danger_alpha(0.34), + theme::danger_alpha(0.2), + theme::danger_alpha(0.5), + theme::danger_text(), + ), + Tone::Plain => ( + solid_background(theme::panel_strong()), + theme::border_strong(), + theme::white(0.12), + theme::white(0.24), + theme::text(), + ), + }; + let enabled = !self.disabled; + let handler = self.on_click.filter(|_| enabled); + div() + .id(self.id) + .flex() + .items_center() + .justify_center() + .gap(px(7.)) + .min_w_0() + .min_h(px(if self.tiny { + 24. + } else if self.compact { + 34. + } else { + theme::CONTROL_HEIGHT + })) + .px(px(if self.tiny { + 8. + } else if self.compact { + 10. + } else { + 14. + })) + .border_1() + .border_color(border) + .rounded(theme::radius_control()) + .bg(fill) + .text_color(color) + .text_size(px(if self.tiny { + theme::FONT_XS + } else if self.compact { + theme::FONT_SM + } else { + theme::FONT_MD + })) + .font_weight(FontWeight::BOLD) + .when(self.grow, |this| this.flex_1()) + .when_some(self.glyph, |this, glyph| { + this.child(icon(glyph, if self.compact { 15. } else { 16. }, color)) + }) + // `white-space: normal` on `.small-button`: when equal-width buttons + // share a narrow row the label wraps instead of being clipped. GPUI + // only wraps text inside a definite width, hence `w_full`; content- + // sized buttons keep a bare label (a `w_full` child there collapses). + .child(if self.grow && self.glyph.is_none() { + div() + .w_full() + .text_center() + .child(self.label) + .into_any_element() + } else { + self.label.into_any_element() + }) + .when(!enabled, |this| this.opacity(0.62).cursor_not_allowed()) + .when(enabled, |this| { + this.cursor_pointer() + .hover(|style| style.bg(hover_fill).border_color(hover_border)) + }) + .when_some(handler, |this, handler| this.on_click(handler)) + } +} + +/// `.icon-action`: a square icon-only button. +pub fn icon_button( + id: impl Into, + glyph: Glyph, + enabled: bool, + on_click: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static, +) -> Stateful
{ + div() + .id(id) + .flex() + .items_center() + .justify_center() + .flex_none() + .size(px(theme::CONTROL_HEIGHT)) + .border_1() + .border_color(theme::border_strong()) + .rounded(theme::radius_control()) + .bg(theme::panel_strong()) + .child(icon(glyph, 15., theme::text())) + .when(!enabled, |this| this.opacity(0.62).cursor_not_allowed()) + .when(enabled, |this| { + this.cursor_pointer() + .hover(|style| style.bg(theme::white(0.12))) + .on_click(on_click) + }) +} + +/// `.panel`: a 16px heading above a bordered body. +pub fn panel(title: impl Into, body: impl IntoElement) -> Div { + div() + .flex() + .flex_col() + .flex_none() + .min_w_0() + .child( + div() + .mb(px(8.)) + .text_size(px(theme::FONT_LG)) + .font_weight(FontWeight::BOLD) + .child(title.into()), + ) + .child( + div() + .flex() + .flex_col() + .min_w_0() + .border_1() + .border_color(theme::border()) + .rounded(theme::radius()) + .bg(theme::panel()) + .overflow_hidden() + .child(body), + ) +} + +/// A heading above a body that has no surrounding chrome (the EXE swapper, +/// Enhanced, Graphics and DXVK panels draw their own cards). +pub fn bare_panel(title: impl Into, body: impl IntoElement) -> Div { + div() + .flex() + .flex_col() + .flex_none() + .min_w_0() + .child( + div() + .mb(px(8.)) + .text_size(px(theme::FONT_LG)) + .font_weight(FontWeight::BOLD) + .child(title.into()), + ) + .child(body) +} + +/// `.enhanced-card` / `.tool-card` / `.graphics-settings-group`. +pub fn card() -> Div { + div() + .flex() + .flex_col() + .gap(px(10.)) + .min_w_0() + .p(px(12.)) + .border_1() + .border_color(theme::border()) + .rounded(theme::radius_card()) + .bg(theme::white(0.025)) +} + +pub fn card_title(glyph: Option, title: impl Into) -> Div { + div() + .flex() + .items_center() + .gap(px(9.)) + .text_size(px(theme::FONT_MD)) + .font_weight(FontWeight::BOLD) + .when_some(glyph, |this, glyph| { + this.child(icon(glyph, 16., theme::muted())) + }) + .child(title.into()) +} + +/// A row of equally sized columns that wraps when the window is narrow, +/// approximating `grid-template-columns: repeat(auto-fit, minmax(..))`. +pub fn columns() -> Div { + div().flex().flex_wrap().gap(px(theme::PANEL_GAP)).w_full() +} + +/// A flex child that participates in [`columns`]. +pub fn column(min_width: f32) -> Div { + div().flex().flex_col().flex_1().min_w(px(min_width)) +} + +pub fn hairline() -> Div { + div().h(px(1.)).w_full().flex_none().bg(theme::border()) +} + +/// `.toggle`: the 52x28 pill switch. +pub fn switch( + id: impl Into, + on: bool, + enabled: bool, + on_click: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static, +) -> Stateful
{ + div() + .id(id) + .relative() + .flex_none() + .w(px(52.)) + .h(px(28.)) + .rounded_full() + .border_1() + .border_color(if on { + theme::ok_alpha(0.38) + } else { + theme::border_strong() + }) + .bg(if on { + theme::ok_alpha(0.16) + } else { + theme::field() + }) + .child( + div() + .absolute() + .top(px(4.)) + .left(px(if on { 28. } else { 4. })) + .size(px(18.)) + .rounded_full() + .bg(if on { theme::ok() } else { theme::muted() }), + ) + .when(!enabled, |this| this.opacity(0.62).cursor_not_allowed()) + .when(enabled, |this| this.cursor_pointer().on_click(on_click)) +} + +fn check_box(checked: bool) -> Div { + div() + .flex() + .items_center() + .justify_center() + .flex_none() + .size(px(18.)) + .rounded(px(4.)) + .border_1() + .border_color(if checked { + theme::accent() + } else { + theme::white(0.5) + }) + .bg(if checked { + theme::accent() + } else { + theme::field() + }) + .when(checked, |this| { + this.child(icon(Glyph::Check, 14., theme::white(1.))) + }) +} + +/// `.check-row`. +pub fn check_row( + id: impl Into, + label: impl Into, + checked: bool, + enabled: bool, + on_click: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static, +) -> Stateful
{ + div() + .id(id) + .flex() + .items_center() + .gap(px(10.)) + .min_h(px(34.)) + .min_w_0() + .text_size(px(theme::FONT_MD)) + .font_weight(FontWeight::MEDIUM) + .child(check_box(checked)) + .child(div().flex_1().min_w_0().child(label.into())) + .when(!enabled, |this| this.opacity(0.62).cursor_not_allowed()) + .when(enabled, |this| this.cursor_pointer().on_click(on_click)) +} + +/// `.radio-row`, with the optional trailing state label (`.profile-state`). +pub fn radio_row( + id: impl Into, + label: impl Into, + selected: bool, + trailing: Option<(SharedString, Hsla)>, + on_click: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static, +) -> Stateful
{ + div() + .id(id) + .flex() + .items_center() + .gap(px(10.)) + .min_h(px(30.)) + .min_w_0() + .border_b_1() + .border_color(theme::border()) + .text_size(px(theme::FONT_MD)) + .font_weight(FontWeight::MEDIUM) + .cursor_pointer() + .on_click(on_click) + .child( + div() + .flex() + .items_center() + .justify_center() + .flex_none() + .size(px(18.)) + .rounded_full() + .border_1() + .border_color(if selected { + theme::accent() + } else { + theme::white(0.5) + }) + .bg(theme::field()) + .when(selected, |this| { + this.child(div().size(px(10.)).rounded_full().bg(theme::accent())) + }), + ) + .child(div().flex_1().min_w_0().child(label.into())) + .when_some(trailing, |this, (state, color)| { + this.child( + div() + .ml_auto() + .min_w_0() + .truncate() + .text_size(px(theme::FONT_SM)) + .text_color(color) + .child(state), + ) + }) +} + +/// `.status-row`: a label with a coloured status dot on the right. +pub fn status_row(label: &str, ok: bool, detail: Option<&str>, last: bool) -> Div { + let shown = if ok { + detail.unwrap_or("Configured") + } else { + "Not configured" + }; + let tone = if ok { theme::ok() } else { theme::muted() }; + div() + .flex() + .items_center() + .justify_between() + .gap(px(12.)) + .min_h(px(34.)) + .text_size(px(theme::FONT_MD)) + .text_color(theme::muted_strong()) + .when(!last, |this| { + this.border_b_1().border_color(theme::border()) + }) + .child(div().flex_1().min_w_0().child(label.to_owned())) + .child( + div() + .flex() + .items_center() + .gap(px(7.)) + .min_w_0() + .font_weight(FontWeight::BOLD) + .text_color(tone) + .child(div().size(px(9.)).flex_none().rounded_full().bg(if ok { + theme::ok() + } else { + theme::dot_idle() + })) + .child(div().truncate().child(shown.to_owned())), + ) +} + +/// `.module-row`: icon, title and a configured/not configured label. +pub fn module_row(glyph: Glyph, title: &str, active: bool) -> Div { + div() + .flex() + .items_center() + .gap(px(12.)) + .px(px(14.)) + .min_h(px(46.)) + .text_size(px(theme::FONT_MD)) + .child(icon(glyph, 22., theme::text())) + .child(div().flex_1().min_w_0().child(title.to_owned())) + .child( + div() + .font_weight(FontWeight::BOLD) + .text_color(if active { theme::ok() } else { theme::muted() }) + .child(if active { + "Configured" + } else { + "Not configured" + }), + ) +} + +/// `.status-pill`: a small label above a value inside a bordered box. +pub fn status_pill( + label: impl Into, + value: impl Into, + ok: bool, +) -> Div { + div() + .flex() + .flex_col() + .justify_center() + .gap(px(4.)) + .flex_1() + .min_w_0() + .min_h(px(56.)) + .px(px(14.)) + .py(px(8.)) + .border_1() + .border_color(theme::border()) + .rounded(theme::radius_control()) + .bg(theme::white(0.035)) + .child(text_xs(label, theme::muted()).truncate()) + .child( + div() + .truncate() + .text_size(px(theme::FONT_SM)) + .font_weight(FontWeight::BOLD) + .text_color(if ok { + theme::ok() + } else { + theme::muted_strong() + }) + .child(value.into()), + ) +} + +/// The borderless label-left / value-right variant used inside the T7 +/// overview and DXVK control bars. +pub fn inline_pill( + label: impl Into, + value: impl Into, + ok: bool, +) -> Div { + div() + .flex() + .items_center() + .justify_between() + .gap(px(12.)) + .min_w_0() + .px(px(14.)) + .min_h(px(54.)) + .child(text_xs(label, theme::muted()).truncate()) + .child( + div() + .truncate() + .text_size(px(theme::FONT_SM)) + .font_weight(FontWeight::BOLD) + .text_color(if ok { + theme::ok() + } else { + theme::muted_strong() + }) + .child(value.into()), + ) +} + +/// `.enhanced-metric` / `.t7-state-grid > div`: small label, value below. +pub fn metric(label: impl Into, value: impl Into, tone: Hsla) -> Div { + div() + .flex() + .flex_col() + .gap(px(4.)) + .flex_1() + .min_w_0() + .child(text_xs(label, theme::muted())) + .child( + div() + .truncate() + .text_size(px(theme::FONT_SM)) + .font_weight(FontWeight::SEMIBOLD) + .text_color(tone) + .child(value.into()), + ) +} + +/// `.t7-state-grid > div`: a [`metric`] inside its own bordered box. +pub fn boxed_metric( + label: impl Into, + value: impl Into, + tone: Hsla, +) -> Div { + metric(label, value, tone) + .p(px(10.)) + .border_1() + .border_color(theme::border()) + .rounded(theme::radius_card()) + .bg(theme::white(0.026)) +} + +/// `.tool-metric`: a muted label with its value on the same line. +pub fn tool_metric(label: &str, value: impl Into, ok: bool) -> Div { + div() + .flex() + .items_baseline() + .gap(px(10.)) + .min_w_0() + .text_size(px(theme::FONT_SM)) + .child( + div() + .w(px(96.)) + .flex_none() + .font_weight(FontWeight::SEMIBOLD) + .text_color(theme::muted()) + .child(label.to_owned()), + ) + .child( + div() + .flex_1() + .min_w_0() + .truncate() + .font_weight(FontWeight::BOLD) + .text_color(if ok { + gpui::rgb(0x24ff66).into() + } else { + theme::text() + }) + .child(value.into()), + ) +} + +/// `.segmented-control` / `.subtab-bar`: mutually exclusive options. +pub fn segmented(items: Vec<(SharedString, bool, ClickHandler)>, enabled: bool) -> Div { + div() + .flex() + .gap(px(4.)) + .p(px(4.)) + .min_w_0() + .border_1() + .border_color(theme::border()) + .rounded(theme::radius_control()) + .bg(theme::white(0.035)) + .children( + items + .into_iter() + .enumerate() + .map(|(index, (label, active, handler))| { + div() + .id(("segment", index)) + .flex() + .items_center() + .justify_center() + .flex_1() + .min_w_0() + .min_h(px(30.)) + .px(px(10.)) + .rounded(px(5.)) + .border_1() + .border_color(if active { + theme::accent_alpha(0.28) + } else { + theme::white(0.) + }) + .bg(if active { + theme::accent_alpha(0.16) + } else { + theme::white(0.) + }) + .text_size(px(theme::FONT_SM)) + .font_weight(FontWeight::BOLD) + .text_color(if active { + theme::text() + } else { + theme::muted_strong() + }) + .child(label) + .when(!enabled, |this| this.opacity(0.62).cursor_not_allowed()) + .when(enabled, |this| { + this.cursor_pointer() + .hover(|style| style.bg(theme::white(0.06))) + .on_click(handler) + }) + }), + ) +} + +/// `.color-chip` / `.recommended-chip`. +pub fn chip( + id: impl Into, + label: impl Into, + swatch: Option, + active: bool, + enabled: bool, + on_click: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static, +) -> Stateful
{ + div() + .id(id) + .flex() + .items_center() + .gap(px(5.)) + .min_w_0() + .min_h(px(32.)) + .px(px(6.)) + .border_1() + .border_color(if active { + theme::accent_alpha(0.42) + } else { + theme::border() + }) + .rounded(theme::radius_card()) + .bg(if active { + theme::accent_alpha(0.14) + } else { + theme::white(0.04) + }) + .text_size(px(theme::FONT_XS)) + .font_weight(FontWeight::BOLD) + .text_color(if active { + theme::text() + } else { + theme::muted_strong() + }) + .when_some(swatch, |this, color| { + this.child( + div() + .size(px(12.)) + .flex_none() + .rounded_full() + .border_1() + .border_color(theme::white(0.34)) + .bg(color), + ) + }) + .child(div().truncate().child(label.into())) + .when(!enabled, |this| this.opacity(0.62).cursor_not_allowed()) + .when(enabled, |this| { + this.cursor_pointer() + .hover(|style| style.bg(theme::accent_alpha(0.1))) + .on_click(on_click) + }) +} + +/// A dimmed uppercase-free field caption (`.field-grid label`). +pub fn field_label(label: impl Into) -> Div { + div() + .text_size(px(theme::FONT_XS)) + .font_weight(FontWeight::SEMIBOLD) + .text_color(theme::muted()) + .child(label.into()) +} + +/// The scrim and centring wrapper used by modal dialogs. +pub fn modal(content: impl IntoElement) -> Div { + div() + .absolute() + .top_0() + .left_0() + .size_full() + .flex() + .items_center() + .justify_center() + .p(px(28.)) + .bg(theme::scrim()) + .occlude() + .child(content) +} + +/// `.modal` surface: rounded, red glow in the corner approximated by a border. +pub fn modal_surface() -> Div { + div() + .flex() + .flex_col() + .gap(px(14.)) + .w(px(520.)) + .max_w_full() + .p(px(20.)) + .border_1() + .border_color(theme::border_strong()) + .rounded(px(16.)) + .bg(theme::modal_surface()) + .shadow_lg() +} diff --git a/native/gpui/src/ui/dashboard.rs b/native/gpui/src/ui/dashboard.rs new file mode 100644 index 0000000..fdf39c5 --- /dev/null +++ b/native/gpui/src/ui/dashboard.rs @@ -0,0 +1,271 @@ +//! Dashboard: status overview, quality-of-life options and launch options. +use super::components::{Btn, check_row, column, columns, hairline, panel, radio_row, status_row}; +use super::progress::Operation; +use super::{ControlCenter, theme}; +use crate::backend::Request; +use gpui::{prelude::*, *}; +use serde_json::{Value, json}; + +impl ControlCenter { + fn active_launch_label(&self) -> Option { + match self.string("/activeLaunchProfile").as_str() { + "default" | "" => None, + "custom" => Some("Custom".into()), + id => self.state["launchProfiles"] + .as_array() + .into_iter() + .flatten() + .find(|profile| profile["id"] == id) + .and_then(|profile| profile["label"].as_str()) + .map(str::to_owned), + } + } + + fn status_overview(&self) -> Div { + let qol_any = + self.flag("/qol/d3dcompiler") || self.flag("/qol/intro") || self.flag("/qol/allIntros"); + let launch = self.active_launch_label(); + panel( + "Status Overview", + div() + .flex() + .flex_col() + .px(px(16.)) + .py(px(10.)) + .child(status_row( + "T7 Patch", + self.flag("/mods/t7Patch"), + None, + false, + )) + .child(status_row( + "DXVK-GPLAsync", + self.flag("/mods/dxvk"), + None, + false, + )) + .child(status_row( + "BO3 Enhanced", + self.flag("/mods/enhanced"), + None, + false, + )) + .child(status_row( + "Launch Option", + self.string("/activeLaunchProfile") != "default", + launch.as_deref(), + false, + )) + .child(status_row("Quality of Life", qol_any, None, true)), + ) + } + + fn qol_panel(&self, cx: &mut Context) -> Div { + let enabled = self.can_act(); + let options = [ + ( + "d3dcompiler", + "/qol/d3dcompiler", + "/api/d3dcompiler", + "Use latest d3dcompiler (d3dcompiler_46.dll)", + "the latest d3dcompiler", + ), + ( + "intro", + "/qol/intro", + "/api/intro-skip", + "Skip Intro (BO3_Global_Logo_LogoSequence.mkv)", + "intro skipping", + ), + ( + "all-intros", + "/qol/allIntros", + "/api/all-intros-skip", + "Skip All Intros (Campaign, Zombies, etc.)", + "skipping all intros", + ), + ]; + let all = options.iter().all(|(_, pointer, ..)| self.flag(pointer)); + let apply_all = { + let next = !all; + let label = format!( + "{} all quality-of-life options", + if next { "Enable" } else { "Disable" } + ); + let paths: Vec = options + .iter() + .map(|(_, _, path, ..)| (*path).to_owned()) + .collect(); + cx.listener(move |view, _: &ClickEvent, _, cx| { + let requests = paths + .iter() + .map(|path| Request { + path: path.clone(), + body: Some(json!({"enabled": next})), + }) + .collect(); + view.stage_all(label.clone(), requests, cx); + }) + }; + panel( + "Quality of Life", + div() + .flex() + .flex_col() + .gap(px(4.)) + .p(px(14.)) + .child( + check_row("qol-all", "Apply All", all, enabled, apply_all) + .font_weight(FontWeight::BOLD) + .text_color(theme::muted_strong()), + ) + .child(hairline().mb(px(4.))) + .children( + options + .into_iter() + .map(|(id, pointer, path, label, summary)| { + let current = self.flag(pointer); + check_row( + id, + label, + current, + enabled, + self.stage_click( + cx, + format!( + "{} {summary}", + if current { "Disable" } else { "Enable" } + ), + path, + json!({"enabled": !current}), + ), + ) + }), + ), + ) + } + + fn launch_options_panel(&self, cx: &mut Context) -> Div { + let profiles: Vec = self.state["launchProfiles"] + .as_array() + .cloned() + .unwrap_or_default(); + let selected = profiles + .iter() + .find(|profile| profile["id"] == self.selected_profile.as_str()); + let installable = selected + .is_some_and(|profile| profile["id"] != "default" && profile["id"] != "offline"); + let selected_option = selected + .and_then(|profile| profile["option"].as_str()) + .unwrap_or("") + .to_owned(); + let selected_label = selected + .and_then(|profile| profile["label"].as_str()) + .unwrap_or("Default") + .to_owned(); + let can_act = self.can_act(); + panel( + "Launch Options", + div() + .flex() + .flex_col() + .gap(px(10.)) + .p(px(14.)) + .child( + div() + .flex() + .flex_col() + .children(profiles.iter().enumerate().map(|(index, profile)| { + let id = profile["id"].as_str().unwrap_or("profile").to_owned(); + let mut label = + profile["label"].as_str().unwrap_or("Profile").to_owned(); + if profile["active"] == true { + label.push_str(" · Active"); + } + let trailing = (id != "default" && id != "offline").then(|| { + let installed = profile["installed"] == true; + let subscribed = profile["subscribed"] == true; + let tone = if installed { + theme::ok() + } else if subscribed { + theme::warning() + } else { + theme::danger() + }; + ( + SharedString::from( + profile["state"].as_str().unwrap_or("").to_owned(), + ), + tone, + ) + }); + let choice = id.clone(); + radio_row( + ("profile", index), + label, + self.selected_profile == id, + trailing, + cx.listener(move |view, _: &ClickEvent, _, cx| { + view.selected_profile = choice.clone(); + cx.notify(); + }), + ) + })), + ) + .child( + div() + .flex() + .gap(px(8.)) + .child( + Btn::new("install-profile", "Install Selected Mod") + .compact() + .grow() + .disabled(!can_act || !installable) + .on_click(self.stage_click( + cx, + format!("Install {selected_label}"), + "/api/workshop-install", + json!({"profileId": self.selected_profile}), + )), + ) + .child( + Btn::new("refresh-profiles", "Refresh") + .compact() + .grow() + .disabled(self.busy) + .on_click(cx.listener(|view, _: &ClickEvent, _, cx| { + view.send("/api/status", None, cx) + })), + ) + .child( + Btn::new("apply-profile", "Apply") + .compact() + .grow() + .disabled(!can_act || selected.is_none()) + .on_click(self.stage_click( + cx, + format!("Apply launch option: {selected_label}"), + "/api/launch-options", + json!({"options": selected_option, "preserve_fs_game": false}), + )), + ), + ) + .children(self.progress_strip(&[Operation::WorkshopInstall])), + ) + } + + pub(super) fn dashboard_view(&self, cx: &mut Context) -> AnyElement { + div() + .flex() + .flex_col() + .gap(px(theme::PANEL_GAP)) + .child(self.status_overview()) + .child( + columns() + .items_start() + .child(column(300.).child(self.qol_panel(cx))) + .child(column(300.).child(self.launch_options_panel(cx))), + ) + .into_any_element() + } +} diff --git a/native/gpui/src/ui/enhanced.rs b/native/gpui/src/ui/enhanced.rs new file mode 100644 index 0000000..17dfe50 --- /dev/null +++ b/native/gpui/src/ui/enhanced.rs @@ -0,0 +1,420 @@ +//! BO3 Enhanced: dump validation, install, diagnostics and uninstall. +use super::components::{ + Btn, Glyph, bare_panel, card, card_title, column, columns, field_label, icon, metric, +}; +use super::progress::Operation; +use super::{ControlCenter, Validation, format_timestamp, theme}; +use gpui::{prelude::*, *}; +use serde_json::json; + +const DUMP_GUIDE_URL: &str = "https://youtu.be/rBZZTcSJ9_s?si=41p0r_Enten3h5AQ"; + +fn ok_tone(ok: bool) -> Hsla { + if ok { + theme::ok() + } else { + theme::muted_strong() + } +} + +fn requirement(label: &str, glyph: Glyph, color: Hsla) -> Div { + div() + .flex() + .items_center() + .gap(px(8.)) + .text_size(px(theme::FONT_SM)) + .font_weight(FontWeight::SEMIBOLD) + .text_color(theme::muted_strong()) + .child(icon(glyph, 15., color)) + .child(label.to_owned()) +} + +fn summary_line(topic: &str, detail: &str) -> Div { + div() + .flex() + .gap(px(4.)) + .child( + div() + .font_weight(FontWeight::EXTRA_BOLD) + .text_color(theme::text()) + .child(format!("{topic}:")), + ) + .child(detail.to_owned()) +} + +impl ControlCenter { + fn enhanced_status_card(&self) -> Div { + let installed = self.flag("/enhanced/installed"); + let launch = self.flag("/enhanced/launchOptionsActive"); + let detected = self.flag("/gameDetected"); + let tracked = !self.string("/enhanced/detectedAt").is_empty(); + card() + .child(card_title(Some(Glyph::Gem), "BO3 Enhanced Status")) + .child( + div() + .flex() + .flex_wrap() + .gap(px(14.)) + .child(metric( + "Status", + if installed { + "Installed" + } else { + "Not installed" + }, + ok_tone(installed), + )) + .child(metric( + "Launch override", + if launch { "Active" } else { "Inactive" }, + ok_tone(launch), + )) + .child(metric( + "Game", + if detected { "Detected" } else { "Not detected" }, + ok_tone(detected), + )) + .child(metric( + "Tracking", + if tracked { "Tracked" } else { "Not tracked" }, + ok_tone(tracked), + )), + ) + } + + fn enhanced_source_card(&self, cx: &mut Context) -> Div { + let source = self.text("dump", cx); + let source = source.trim().to_owned(); + let validated = self.validation.ok == Some(true) && self.validation.source == source; + let can_act = self.can_act(); + let validation_label = match &self.validation.checked_at { + Some(at) => format!("{} ({at})", self.validation.label), + None => self.validation.label.clone(), + }; + let validation_color = match self.validation.ok { + Some(true) => theme::ok(), + Some(false) => theme::caution(), + None => theme::muted_strong(), + }; + let drop_zone = div() + .id("dump-drop-zone") + .flex() + .flex_col() + .items_center() + .justify_center() + .gap(px(4.)) + .flex_1() + .min_w(px(260.)) + .min_h(px(120.)) + .p(px(14.)) + .border_1() + .border_dashed() + .border_color(theme::white(0.2)) + .rounded(theme::radius_card()) + .bg(theme::white(0.02)) + .text_center() + .drag_over::(|style, _, _, _| { + style + .bg(theme::accent_alpha(0.08)) + .border_color(theme::accent_alpha(0.6)) + }) + .on_drop(cx.listener(|view, paths: &ExternalPaths, window, cx| { + if let Some(path) = paths.paths().first() { + view.set_text("dump", path.to_string_lossy().into_owned(), window, cx); + view.validation = Validation::not_run(); + cx.notify(); + } + })) + .child( + div() + .font_weight(FontWeight::BOLD) + .child("Drop DUMP.zip or extracted folder here"), + ) + .child( + div() + .text_size(px(theme::FONT_XS)) + .font_weight(FontWeight::SEMIBOLD) + .text_color(theme::muted()) + .child("or browse to the dump source manually"), + ); + let controls = div() + .flex() + .flex_col() + .gap(px(10.)) + .flex_1() + .min_w(px(300.)) + .child( + div() + .flex() + .gap(px(10.)) + .child( + Btn::new("browse-dump", "Browse Folder") + .icon(Glyph::FolderOpen) + .grow() + .disabled(self.busy) + .on_click(cx.listener(|view, _: &ClickEvent, window, cx| { + view.choose_path("dump", false, window, cx) + })), + ) + .child( + Btn::new("browse-dump-zip", "Browse DUMP.zip") + .icon(Glyph::FolderOpen) + .grow() + .disabled(self.busy) + .on_click(cx.listener(|view, _: &ClickEvent, window, cx| { + view.choose_path("dump", true, window, cx) + })), + ), + ) + .child( + div().flex().child( + self.action( + cx, + "validate-dump", + "Validate Source", + "/api/enhanced-validate", + json!({"dumpSource": source}), + ) + .icon(Glyph::CheckCircle) + .grow() + .disabled(!can_act || source.is_empty()), + ), + ) + .child( + div() + .flex() + .flex_col() + .gap(px(4.)) + .child(field_label("Source")) + .child(self.field("dump")), + ) + .child( + div() + .flex() + .flex_col() + .gap(px(4.)) + .child(field_label("Validation")) + .child( + div() + .text_size(px(theme::FONT_SM)) + .font_weight(FontWeight::SEMIBOLD) + .text_color(validation_color) + .child(validation_label), + ), + ) + .child( + self.action( + cx, + "install-enhanced", + "Install / Update BO3 Enhanced", + "/api/enhanced-install", + json!({"dumpSource": source}), + ) + .icon(Glyph::Download) + .disabled(!can_act || source.is_empty() || !validated), + ) + .when(!validated, |this| { + this.child( + div() + .text_size(px(theme::FONT_XS)) + .font_weight(FontWeight::SEMIBOLD) + .text_color(theme::muted()) + .child("Install requires a valid DUMP.zip or extracted UWP dump folder."), + ) + }); + card() + .child(card_title(Some(Glyph::Download), "Install Source")) + .children( + self.progress_strip(&[Operation::EnhancedValidate, Operation::EnhancedInstall]), + ) + .child( + div() + .flex() + .flex_wrap() + .gap(px(14.)) + .child(drop_zone) + .child(controls), + ) + } + + fn enhanced_help_card(&self, cx: &mut Context) -> Div { + let detected = self.flag("/gameDetected"); + let dump_ok = self.validation.ok == Some(true); + card() + .child(card_title(Some(Glyph::Alert), "Help / Requirements")) + .child( + div() + .flex() + .flex_col() + .gap(px(7.)) + .child(requirement( + "Game detected", + Glyph::CheckCircle, + if detected { + theme::ok() + } else { + theme::caution() + }, + )) + .child(requirement( + "Dump required", + Glyph::Alert, + if dump_ok { + theme::ok() + } else { + theme::caution() + }, + )), + ) + .child( + div() + .flex() + .flex_col() + .gap(px(6.)) + .text_size(px(theme::FONT_XS)) + .font_weight(FontWeight::SEMIBOLD) + .text_color(theme::muted_strong()) + .child(summary_line("Sources", "DUMP.zip or extracted folder")) + .child(summary_line("Checks", "files, read/write, version")), + ) + .child( + div().flex().child( + Btn::new("dump-guide", "Open Dump Guide") + .icon(Glyph::ExternalLink) + .on_click( + cx.listener(|_, _: &ClickEvent, _, cx| cx.open_url(DUMP_GUIDE_URL)), + ), + ), + ) + } + + fn enhanced_details_card(&self) -> Div { + let installed = self.flag("/enhanced/installed"); + let backup = { + let backup = self.string("/enhanced/backupStatus"); + if backup.is_empty() { + "Not created".to_owned() + } else { + backup + } + }; + let game_dir = self.string("/gameDir"); + let install_path = if installed && !game_dir.is_empty() { + game_dir + } else { + "Not created".to_owned() + }; + let last_install = format_timestamp(&self.string("/enhanced/detectedAt")); + let last_validation = self + .validation + .checked_at + .clone() + .unwrap_or_else(|| "Never".into()); + let cells = [ + ("Last install", last_install), + ("Last validation", last_validation), + ( + "Files installed", + self.int("/enhanced/filesInstalled", 0).to_string(), + ), + ("Backup status", backup.clone()), + ("Install path", install_path), + ( + "Launch target", + if self.flag("/enhanced/launchOptionsActive") { + "BO3 Enhanced" + } else { + "Stock BO3" + } + .to_owned(), + ), + ( + "Config file", + if installed { "Configured" } else { "Missing" }.to_owned(), + ), + ( + "Restore point", + if backup == "Created" { + "Available" + } else { + "Not available" + } + .to_owned(), + ), + ]; + card() + .child(card_title(None, "Install Details / Diagnostics")) + .child(div().flex().flex_wrap().gap(px(14.)).children( + cells.into_iter().map(|(label, value)| { + metric(label, value, theme::muted_strong()).min_w(px(180.)) + }), + )) + } + + fn enhanced_danger_zone(&self, cx: &mut Context) -> Div { + let open = self.danger_open; + card() + .border_color(theme::accent_alpha(0.34)) + .bg(theme::accent_alpha(0.035)) + .child( + div() + .id("danger-zone") + .flex() + .items_center() + .gap(px(8.)) + .cursor_pointer() + .font_weight(FontWeight::BOLD) + .on_click(cx.listener(|view, _: &ClickEvent, _, cx| { + view.danger_open = !view.danger_open; + cx.notify(); + })) + .child(icon( + if open { + Glyph::ChevronDown + } else { + Glyph::ChevronRight + }, + 15., + theme::muted(), + )) + .child("Danger Zone"), + ) + .children(self.progress_strip(&[Operation::EnhancedUninstall])) + .when(open, |this| { + this.child( + div().flex().justify_end().child( + self.action( + cx, + "uninstall-enhanced", + "Uninstall BO3 Enhanced", + "/api/enhanced-uninstall", + json!({}), + ) + .icon(Glyph::Trash) + .danger() + .disabled(!self.can_act() || !self.flag("/enhanced/installed")), + ), + ) + }) + } + + pub(super) fn enhanced_view(&self, cx: &mut Context) -> AnyElement { + bare_panel( + "Enhanced", + div() + .flex() + .flex_col() + .gap(px(10.)) + .child(self.enhanced_status_card()) + .child( + columns() + .items_start() + .child(column(420.).child(self.enhanced_source_card(cx))) + .child(column(280.).child(self.enhanced_help_card(cx))), + ) + .child(self.enhanced_details_card()) + .child(self.enhanced_danger_zone(cx)), + ) + .into_any_element() + } +} diff --git a/native/gpui/src/ui/exe.rs b/native/gpui/src/ui/exe.rs new file mode 100644 index 0000000..b7de306 --- /dev/null +++ b/native/gpui/src/ui/exe.rs @@ -0,0 +1,336 @@ +//! EXE Swapper: the three selectable executables and their trade-offs. +use super::components::{Btn, Glyph, bare_panel, columns, icon, status_pill, text_xs}; +use super::progress::Operation; +use super::{ControlCenter, theme}; +use gpui::{prelude::*, *}; +use serde_json::json; + +#[derive(Clone, Copy)] +enum Verdict { + Pro, + Warn, + Con, +} + +fn verdict_line(verdict: Verdict, topic: &str, detail: &str) -> Div { + let (glyph, color) = match verdict { + Verdict::Pro => (Glyph::CheckCircle, theme::ok()), + Verdict::Warn => (Glyph::Alert, theme::caution()), + Verdict::Con => (Glyph::Close, theme::accent()), + }; + // One run of text with a bold lead-in so long details wrap inside the card. + let lead = format!("{topic}:"); + let bold = HighlightStyle { + font_weight: Some(FontWeight::BOLD), + ..Default::default() + }; + let text = StyledText::new(format!("{lead} {detail}")).with_highlights([(0..lead.len(), bold)]); + div() + .flex() + .items_start() + .gap(px(8.)) + .min_w_0() + .text_size(px(theme::FONT_SM)) + .child(div().pt(px(2.)).child(icon(glyph, 15., color))) + .child(div().flex_1().min_w_0().child(text)) +} + +struct ExeOption<'a> { + title: &'a str, + subtitle: String, + badge: &'a str, + active: bool, + disabled: bool, + verdicts: [(Verdict, &'a str, &'a str); 3], +} + +fn exe_option(option: ExeOption, button: Btn) -> Div { + div() + .flex() + .flex_col() + .gap(px(16.)) + .flex_1() + .min_w(px(240.)) + .p(px(16.)) + .when(option.active, |this| { + this.border_t_4() + .border_color(theme::ok()) + .bg(theme::ok_alpha(0.045)) + }) + .when(option.disabled, |this| this.opacity(0.72)) + .child( + div() + .flex() + .items_start() + .justify_between() + .gap(px(12.)) + .child( + div() + .flex_1() + .min_w_0() + .child( + div() + .text_size(px(theme::FONT_LG)) + .font_weight(FontWeight::BOLD) + .child(option.title.to_owned()), + ) + .child( + div() + .text_size(px(theme::FONT_SM)) + .font_weight(FontWeight::SEMIBOLD) + .text_color(theme::muted()) + .child(option.subtitle), + ), + ) + .child( + div() + .flex_none() + .px(px(9.)) + .py(px(4.)) + .border_1() + .border_color(if option.active { + theme::ok_alpha(0.42) + } else { + theme::border() + }) + .rounded_full() + .bg(if option.active { + theme::ok_alpha(0.13) + } else { + theme::white(0.) + }) + .text_size(px(theme::FONT_XS)) + .font_weight(FontWeight::EXTRA_BOLD) + .text_color(if option.active { + theme::ok() + } else { + theme::muted() + }) + .child(option.badge.to_owned()), + ), + ) + .child( + div().flex().flex_col().gap(px(10.)).children( + option + .verdicts + .into_iter() + .map(|(verdict, topic, detail)| verdict_line(verdict, topic, detail)), + ), + ) + .child(button) +} + +impl ControlCenter { + pub(super) fn exe_view(&self, cx: &mut Context) -> AnyElement { + let profile = self.string("/exeSwap/profile"); + let trusted = self.flag("/exeSwap/trustedExecutable"); + let detected = self.flag("/gameDetected"); + let build_id = self.value("/exeSwap/activeBuildId"); + let current_date = self.value("/exeSwap/currentBuildDate"); + let compatible_date = self.value("/exeSwap/compatibleBuildDate"); + let active_title = match (trusted, profile.as_str()) { + (true, "compatible") => format!("Compatible Build ({build_id})"), + (true, "current") => format!("Latest Build ({build_id})"), + (true, "enhanced") => format!("BO3 Enhanced ({build_id})"), + _ => "Unverified EXE".to_owned(), + }; + let enhanced_available = self.flag("/exeSwap/enhancedAvailable"); + let enhanced_value = if self.flag("/exeSwap/enhancedExeActive") { + "Active" + } else if enhanced_available { + "Available" + } else { + "Not found" + }; + let compatible_active = profile == "compatible" && trusted; + let latest_active = profile == "current" && trusted; + let enhanced_active = profile == "enhanced" && trusted; + let compatible_disabled = !detected || profile == "compatible"; + let latest_disabled = + !detected || profile == "current" || !self.flag("/exeSwap/latestAvailable"); + let enhanced_disabled = !detected || profile == "enhanced" || !enhanced_available; + let can_act = self.can_act(); + + let hero = columns() + .child( + div() + .flex() + .items_center() + .gap(px(14.)) + .flex_none() + .w(px(330.)) + .p(px(14.)) + .border_1() + .border_color(theme::border()) + .rounded(theme::radius_card()) + .bg(theme::white(0.025)) + .child(icon(Glyph::Refresh, 20., theme::accent())) + .child( + div() + .flex_1() + .min_w_0() + .child(text_xs("Active build", theme::muted())) + .child( + div() + .truncate() + .text_size(px(16.)) + .font_weight(FontWeight::EXTRA_BOLD) + .child(active_title), + ), + ), + ) + .child( + div() + .flex() + .gap(px(8.)) + .flex_1() + .min_w(px(420.)) + .child(status_pill( + format!("Latest - {current_date}"), + self.value("/exeSwap/currentBuildId"), + latest_active, + )) + .child(status_pill( + format!("Compatible - {compatible_date}"), + self.value("/exeSwap/compatibleBuildId"), + self.flag("/exeSwap/compatibleActive"), + )) + .child(status_pill("Enhanced", enhanced_value, enhanced_available)), + ); + + let options = div() + .flex() + .flex_wrap() + .border_1() + .border_color(theme::border()) + .rounded(theme::radius()) + .bg(theme::panel()) + .overflow_hidden() + .child( + exe_option( + ExeOption { + title: "Compatible Build", + subtitle: "March 3, 2023 Steam depot".into(), + badge: if compatible_active { + "Active" + } else { + "Best for mods" + }, + active: compatible_active, + disabled: compatible_disabled, + verdicts: [ + (Verdict::Pro, "Vanilla experience", "Supported"), + (Verdict::Pro, "Modded experience", "Best support"), + (Verdict::Warn, "Performance", "Standard Steam performance"), + ], + }, + self.action( + cx, + "exe-compatible", + "Use Compatible Build", + "/api/exe-swap/compatible", + json!({}), + ) + .icon(Glyph::Download) + .primary() + .disabled(!can_act || compatible_disabled), + ) + .border_r_1() + .border_color(theme::border()), + ) + .child( + exe_option( + ExeOption { + title: "Latest Build", + subtitle: format!("{current_date} Steam build"), + badge: if latest_active { + "Active" + } else { + "Steam default" + }, + active: latest_active, + disabled: latest_disabled, + verdicts: [ + (Verdict::Pro, "Vanilla experience", "Best Steam match"), + (Verdict::Warn, "Modded experience", "May or may not work"), + (Verdict::Warn, "Performance", "Standard Steam performance"), + ], + }, + self.action( + cx, + "exe-current", + "Use Latest Build", + "/api/exe-swap/current", + json!({}), + ) + .icon(Glyph::Rotate) + .disabled(!can_act || latest_disabled), + ) + .border_r_1() + .border_color(theme::border()), + ) + .child(exe_option( + ExeOption { + title: "BO3 Enhanced", + subtitle: "Windows Store build modded for Steam".into(), + badge: if enhanced_active { + "Active" + } else if enhanced_available { + "Best performance" + } else { + "Not installed" + }, + active: enhanced_active, + disabled: enhanced_disabled, + verdicts: [ + (Verdict::Pro, "Vanilla experience", "Supported"), + (Verdict::Con, "Modded experience", "Most mods unlikely"), + (Verdict::Pro, "Performance", "Highest performance"), + ], + }, + self.action( + cx, + "exe-enhanced", + "Use BO3 Enhanced", + "/api/exe-swap/enhanced", + json!({}), + ) + .icon(Glyph::Gem) + .disabled(!can_act || enhanced_disabled), + )); + + let details = div() + .flex() + .flex_col() + .gap(px(4.)) + .p(px(12.)) + .border_1() + .border_color(theme::border()) + .rounded(theme::radius_control()) + .bg(theme::scrim().alpha(0.18)) + .text_size(px(theme::FONT_XS)) + .font_weight(FontWeight::SEMIBOLD) + .text_color(theme::muted_strong()) + .child(self.value("/exeSwap/displayLabel")) + .child(self.value("/exeSwap/integrityMessage")) + .child(format!("SHA-256: {}", self.value("/exeSwap/executableHash"))) + .child("Compatible installs may need a Steam depot download. The command appears in a dialog when it is required."); + + bare_panel( + "EXE Swapper", + div() + .flex() + .flex_col() + .gap(px(theme::PANEL_GAP)) + .child(hero) + .children(self.progress_strip(&[ + Operation::ExeCompatible, + Operation::ExeCurrent, + Operation::ExeEnhanced, + ])) + .child(options) + .child(details), + ) + .into_any_element() + } +} diff --git a/native/gpui/src/ui/graphics.rs b/native/gpui/src/ui/graphics.rs new file mode 100644 index 0000000..4aa94d5 --- /dev/null +++ b/native/gpui/src/ui/graphics.rs @@ -0,0 +1,876 @@ +//! Graphics settings and DXVK. Both write `config.ini` / `dxvk.conf` through +//! the local service; every write asks for confirmation first. +use super::components::{ + Btn, Glyph, bare_panel, chip, column, columns, handler, icon, inline_pill, segmented, switch, + text_xs, +}; +use super::progress::Operation; +use super::{ControlCenter, GraphicsTab, dxvk_recommended, theme}; +use gpui::{prelude::*, *}; +use gpui_component::slider::Slider; +use serde_json::{Value, json}; + +const DISPLAY_MODES: [(i64, &str); 3] = [(0, "Windowed"), (1, "Fullscreen"), (2, "Borderless")]; + +fn dxvk_none() -> Value { + json!({ + "enableAsync": true, + "gplAsyncCache": false, + "numCompilerThreads": 0, + "maxFrameRate": 0, + "maxFrameLatency": 0, + "tearFree": "Auto", + "hudEnabled": false, + }) +} + +/// `.graphics-quick-grid .setting-row`: a bordered tile with a label and a control. +fn quick_row(label: &str, control: impl IntoElement) -> Div { + div() + .flex() + .items_center() + .gap(px(12.)) + .flex_1() + .min_w(px(300.)) + .min_h(px(50.)) + .px(px(14.)) + .py(px(6.)) + .border_1() + .border_color(theme::border()) + .rounded(theme::radius_card()) + .bg(theme::white(0.04)) + .child( + div() + .w(px(104.)) + .flex_none() + .text_size(px(theme::FONT_MD)) + .child(label.to_owned()), + ) + .child( + div() + .flex() + .items_center() + .flex_wrap() + .gap(px(8.)) + .flex_1() + .min_w_0() + .child(control), + ) +} + +/// A row inside a settings group or the advanced drawer. +fn group_row(label: &str, control: impl IntoElement) -> Div { + div() + .flex() + .items_center() + .justify_between() + .gap(px(12.)) + .min_h(px(40.)) + .px(px(14.)) + .py(px(6.)) + .child( + div() + .min_w(px(110.)) + .flex_none() + .text_size(px(theme::FONT_MD)) + .child(label.to_owned()), + ) + .child( + div() + .flex() + .items_center() + .justify_end() + .gap(px(8.)) + .flex_1() + .min_w_0() + .child(control), + ) +} + +/// `.graphics-settings-group`: a titled stack of rows separated by hairlines. +fn group(title: &str, rows: Vec
) -> Div { + let count = rows.len(); + div() + .flex() + .flex_col() + .min_w_0() + .overflow_hidden() + .border_1() + .border_color(theme::border()) + .rounded(theme::radius_card()) + .bg(theme::white(0.03)) + .child( + div() + .px(px(14.)) + .pt(px(12.)) + .pb(px(6.)) + .font_weight(FontWeight::BOLD) + .child(title.to_owned()), + ) + .children(rows.into_iter().enumerate().map(|(index, row)| { + row.when(index + 1 < count, |row| { + row.border_b_1().border_color(theme::border()) + }) + })) +} + +impl ControlCenter { + /// An input with an `Apply` button that stages a whole-number setting. + fn number_control( + &self, + cx: &mut Context, + input: &'static str, + config: &'static str, + min: i64, + max: i64, + ) -> Div { + div() + .flex() + .items_center() + .gap(px(8.)) + .flex_1() + .min_w_0() + .child(div().flex_1().min_w_0().child(self.field(input))) + .child( + Btn::new(SharedString::from(format!("apply-{input}")), "Apply") + .compact() + .disabled(!self.can_act()) + .on_click(cx.listener(move |view, _: &ClickEvent, _, cx| { + match view.text(input, cx).trim().parse::() { + Ok(value) if (min..=max).contains(&value) => view.stage( + format!("Set {config} to {value}"), + "/api/config".into(), + ControlCenter::config_body(config, json!(value)), + cx, + ), + _ => view + .reject(format!("Enter a whole number from {min} to {max}."), cx), + } + })), + ) + } + + /// A slider with its current value and an optional `Apply` button. + fn slider_control( + &self, + cx: &mut Context, + key: &'static str, + apply: Option<(&'static str, &'static str)>, + ) -> Div { + let value = self.slider_value(key, cx); + div() + .flex() + .items_center() + .gap(px(10.)) + .flex_1() + .min_w(px(160.)) + .child( + div() + .flex() + .flex_1() + .min_w(px(100.)) + .child(Slider::new(&self.sliders[key]).disabled(self.busy)), + ) + .child( + div() + .w(px(36.)) + .flex_none() + .text_right() + .font_weight(FontWeight::BOLD) + .child(value.to_string()), + ) + .when_some(apply, |this, (config, label)| { + this.child( + Btn::new(SharedString::from(format!("apply-{key}")), "Apply") + .compact() + .disabled(!self.can_act()) + .on_click(cx.listener(move |view, _: &ClickEvent, _, cx| { + let value = view.slider_value(key, cx); + view.stage( + format!("Set {label} to {value}"), + "/api/config".into(), + ControlCenter::config_body(config, json!(value)), + cx, + ); + })), + ) + }) + } + + /// A switch that writes `value` to a `config.ini` key once confirmed. + fn config_switch( + &self, + cx: &mut Context, + id: &'static str, + label: &str, + current: bool, + key: &str, + next_value: i64, + ) -> Div { + group_row( + label, + switch( + id, + current, + self.can_act(), + self.stage_click( + cx, + format!("{} {label}", if current { "Disable" } else { "Enable" }), + "/api/config", + ControlCenter::config_body(key, json!(next_value)), + ), + ), + ) + } + + fn graphics_quick_grid(&self, cx: &mut Context) -> Div { + let can_act = self.can_act(); + let presets: Vec = self.state["presets"] + .as_array() + .into_iter() + .flatten() + .filter_map(Value::as_str) + .map(str::to_owned) + .collect(); + let display_mode = self.int("/graphics/displayMode", 1); + let preset_chips = + div() + .flex() + .flex_wrap() + .gap(px(6.)) + .children(presets.into_iter().enumerate().map(|(index, name)| { + let click = self.stage_click( + cx, + format!("Apply preset {name}"), + "/api/presets/apply", + json!({"name": name}), + ); + chip(("preset", index), name, None, false, can_act, click) + })); + let modes = segmented( + DISPLAY_MODES + .iter() + .map(|(mode, label)| { + ( + SharedString::from(*label), + display_mode == *mode, + handler(self.stage_click( + cx, + format!("Set display mode to {label}"), + "/api/config", + ControlCenter::config_body("FullScreenMode", json!(mode)), + )), + ) + }) + .collect(), + can_act, + ) + .flex_1(); + columns() + .child(quick_row("Preset", preset_chips)) + .child(quick_row("Display Mode", modes)) + .child(quick_row( + "Resolution", + div() + .flex() + .items_center() + .gap(px(8.)) + .flex_1() + .min_w_0() + .child(div().flex_1().min_w_0().child(self.field("resolution"))) + .child( + Btn::new("apply-resolution", "Apply") + .compact() + .disabled(!can_act) + .on_click(cx.listener(|view, _: &ClickEvent, _, cx| { + let value = view.text("resolution", cx).trim().to_owned(); + let valid = value.split_once('x').is_some_and(|(w, h)| { + [w, h].iter().all(|n| { + n.parse::().is_ok_and(|n| (320..=16384).contains(&n)) + }) + }); + if valid { + view.stage( + format!("Set resolution to {value}"), + "/api/config".into(), + ControlCenter::config_body("WindowSize", json!(value)), + cx, + ); + } else { + view.reject( + "Enter resolution as WIDTHxHEIGHT, e.g. 1920x1080.", + cx, + ); + } + })), + ), + )) + .child(quick_row( + "Refresh Rate", + self.number_control(cx, "refresh", "RefreshRate", 1, 240), + )) + } + + fn graphics_advanced(&self, cx: &mut Context) -> Div { + let open = self.advanced_open; + let limited = self.flag("/advanced/vramLimited"); + let target = self.int("/advanced/vramTarget", 75).clamp(75, 100); + let readonly = self.flag("/advanced/configReadonly"); + let unlocked = self.flag("/advanced/unlockOptions"); + let reduce_cpu = self.flag("/advanced/reduceCpu"); + let can_act = self.can_act(); + let rows = div() + .flex() + .flex_wrap() + .border_t_1() + .border_color(theme::border()) + .child( + self.config_switch( + cx, + "smooth-framerate", + "Smooth framerate", + self.flag("/advanced/smoothFramerate"), + "SmoothFramerate", + if self.flag("/advanced/smoothFramerate") { + 0 + } else { + 1 + }, + ) + .flex_1() + .min_w(px(300.)) + .border_b_1() + .border_color(theme::border()), + ) + .child( + self.config_switch( + cx, + "unlock-graphics", + "Expose hidden graphics", + unlocked, + "RestrictGraphicsOptions", + if unlocked { 1 } else { 0 }, + ) + .flex_1() + .min_w(px(300.)) + .border_b_1() + .border_color(theme::border()), + ) + .child( + self.config_switch( + cx, + "reduce-cpu", + "Reduce CPU pressure", + reduce_cpu, + "SerializeRender", + if reduce_cpu { 0 } else { 2 }, + ) + .flex_1() + .min_w(px(300.)) + .border_b_1() + .border_color(theme::border()), + ) + .child( + group_row( + "Frame latency", + self.number_control(cx, "latency", "MaxFrameLatency", 0, 4), + ) + .flex_1() + .min_w(px(300.)) + .border_b_1() + .border_color(theme::border()), + ) + .child( + group_row( + "Limit VRAM target", + switch( + "limit-vram", + limited, + can_act, + self.stage_click( + cx, + format!( + "{} the VRAM target", + if limited { "Remove" } else { "Limit" } + ), + "/api/vram-target", + json!({"limited": !limited, "target": target}), + ), + ), + ) + .flex_1() + .min_w(px(300.)) + .border_b_1() + .border_color(theme::border()), + ) + .child( + group_row( + "VRAM target %", + div() + .flex() + .items_center() + .gap(px(8.)) + .flex_1() + .min_w_0() + .child(div().flex_1().min_w_0().child(self.field("vram"))) + .child( + Btn::new("apply-vram", "Apply") + .compact() + .disabled(!can_act) + .on_click(cx.listener(|view, _: &ClickEvent, _, cx| { + match view.text("vram", cx).trim().parse::() { + Ok(target) if (75..=100).contains(&target) => view.stage( + format!("Set VRAM target to {target}%"), + "/api/vram-target".into(), + json!({"limited": target < 100, "target": target}), + cx, + ), + _ => view.reject( + "VRAM target must be a whole number from 75 to 100.", + cx, + ), + } + })), + ), + ) + .flex_1() + .min_w(px(300.)) + .border_b_1() + .border_color(theme::border()), + ) + .child( + group_row( + "Lock config.ini", + switch( + "config-readonly", + readonly, + can_act, + self.stage_click( + cx, + format!( + "{} config.ini read-only mode", + if readonly { "Disable" } else { "Enable" } + ), + "/api/config-readonly", + json!({"enabled": !readonly}), + ), + ), + ) + .flex_1() + .min_w(px(300.)) + .border_b_1() + .border_color(theme::border()), + ); + div() + .flex() + .flex_col() + .overflow_hidden() + .border_1() + .border_color(theme::border()) + .rounded(theme::radius_card()) + .bg(theme::white(0.025)) + .child( + div() + .id("advanced-drawer") + .flex() + .items_center() + .justify_between() + .min_h(px(46.)) + .px(px(14.)) + .cursor_pointer() + .on_click(cx.listener(|view, _: &ClickEvent, _, cx| { + view.advanced_open = !view.advanced_open; + cx.notify(); + })) + .child( + div() + .flex() + .flex_col() + .gap(px(2.)) + .child(div().font_weight(FontWeight::BOLD).child("Advanced")) + .child(text_xs( + "Config, CPU, latency, and VRAM controls", + theme::muted(), + )), + ) + .child(icon( + if open { + Glyph::ChevronDown + } else { + Glyph::ChevronRight + }, + 16., + theme::muted(), + )), + ) + .when(open, |this| this.child(rows)) + } + + fn graphics_settings(&self, cx: &mut Context) -> Div { + let vsync = self.flag("/graphics/vsync"); + let draw_fps = self.flag("/graphics/drawFps"); + bare_panel( + "Graphics Settings", + div() + .flex() + .flex_col() + .gap(px(theme::PANEL_GAP)) + .child(self.graphics_quick_grid(cx)) + .child( + columns() + .items_start() + .child(column(340.).child(group( + "Performance", + vec![ + group_row( + "Max FPS", + self.number_control(cx, "fps", "MaxFPS", 0, 1000), + ), + self.config_switch( + cx, + "vsync", + "Vertical sync", + vsync, + "Vsync", + if vsync { 0 } else { 1 }, + ), + self.config_switch( + cx, + "draw-fps", + "FPS counter", + draw_fps, + "DrawFPS", + if draw_fps { 0 } else { 1 }, + ), + ], + ))) + .child(column(340.).child(group( + "Quality", + vec![ + group_row( + "Render Resolution", + self.slider_control( + cx, + "render", + Some(("ResolutionPercent", "render resolution")), + ), + ), + group_row( + "Field of View", + self.slider_control(cx, "fov", Some(("FOV", "field of view"))), + ), + ], + ))), + ) + .child(self.graphics_advanced(cx)), + ) + } + + /// The current DXVK draft with the number inputs and slider folded in. + fn dxvk_payload(&self, cx: &App) -> Result { + let parse = |key: &str, label: &str, max: i64| -> Result { + self.text(key, cx) + .trim() + .parse::() + .ok() + .filter(|value| (0..=max).contains(value)) + .ok_or_else(|| format!("{label} must be a whole number from 0 to {max}.")) + }; + let mut payload = self.dxvk_draft.clone(); + payload["numCompilerThreads"] = json!(parse("dxvk-threads", "Compiler threads", 64)?); + payload["maxFrameLatency"] = json!(parse("dxvk-latency", "Frame latency", 16)?); + payload["maxFrameRate"] = json!(self.slider_value("dxvk-fps", cx)); + Ok(payload) + } + + /// Stage a DXVK install/apply after folding the draft controls in. + fn dxvk_click( + &self, + cx: &mut Context, + label: &'static str, + path: &'static str, + ) -> impl Fn(&ClickEvent, &mut Window, &mut App) + 'static { + cx.listener( + move |view, _: &ClickEvent, _, cx| match view.dxvk_payload(cx) { + Ok(payload) => view.stage(label.into(), path.into(), payload, cx), + Err(message) => view.reject(message, cx), + }, + ) + } + + fn dxvk_toggle( + &self, + cx: &mut Context, + id: &'static str, + label: &str, + key: &'static str, + ) -> Div { + group_row( + label, + switch( + id, + self.dxvk_flag(key), + !self.busy, + cx.listener(move |view, _: &ClickEvent, _, cx| { + let next = !view.dxvk_flag(key); + view.dxvk_draft[key] = json!(next); + cx.notify(); + }), + ), + ) + } + + fn dxvk_view(&self, cx: &mut Context) -> Div { + let installed = self.flag("/dxvk/installed"); + let conf = self.flag("/dxvk/confExists"); + let can_act = self.can_act(); + let detected_threads = std::thread::available_parallelism() + .ok() + .map(|cores| cores.get().saturating_sub(2).max(1)); + let pill = |label: &str, value: &str, ok: bool| { + div() + .flex_1() + .min_w(px(180.)) + .min_h(px(58.)) + .border_1() + .border_color(theme::border()) + .rounded(theme::radius_card()) + .bg(theme::white(0.04)) + .child(inline_pill(label.to_owned(), value.to_owned(), ok)) + }; + let control_bar = div() + .flex() + .flex_wrap() + .gap(px(theme::PANEL_GAP)) + .child(pill( + "Status", + if installed { + "Installed" + } else { + "Not installed" + }, + installed, + )) + .child(pill( + "dxvk.conf", + if conf { "Configured" } else { "Missing" }, + conf, + )) + .child( + div() + .flex() + .items_center() + .gap(px(8.)) + .flex_1() + .min_w(px(300.)) + .min_h(px(58.)) + .p(px(8.)) + .border_1() + .border_color(theme::border()) + .rounded(theme::radius_card()) + .bg(theme::white(0.04)) + .child( + Btn::new("dxvk-install", "Install") + .compact() + .icon(Glyph::Download) + .grow() + .disabled(!can_act) + .on_click(self.dxvk_click(cx, "Install DXVK", "/api/dxvk-install")), + ) + .child( + self.action( + cx, + "dxvk-uninstall", + "Uninstall", + "/api/dxvk-uninstall", + json!({}), + ) + .compact() + .icon(Glyph::Trash) + .grow() + .disabled(!can_act || !installed), + ) + .child( + Btn::new("dxvk-apply", "Apply") + .compact() + .icon(Glyph::Save) + .grow() + .disabled(!can_act) + .on_click(self.dxvk_click( + cx, + "Apply DXVK settings", + "/api/dxvk-config", + )), + ), + ); + let presets = div().flex().gap(px(6.)).children( + [("Recommended", dxvk_recommended()), ("None", dxvk_none())] + .into_iter() + .enumerate() + .map(|(index, (name, preset))| { + chip( + ("dxvk-preset", index), + name, + None, + self.dxvk_draft == preset, + !self.busy, + cx.listener(move |view, _: &ClickEvent, window, cx| { + view.dxvk_draft = preset.clone(); + view.show_dxvk_draft(window, cx); + cx.notify(); + }), + ) + }), + ); + let threads = div() + .flex() + .flex_wrap() + .items_center() + .gap(px(8.)) + .flex_1() + .min_w_0() + .child(div().w(px(80.)).child(self.field("dxvk-threads"))) + .child(chip( + "threads-auto", + "Recommended: Auto (0)", + None, + false, + !self.busy, + cx.listener(|view, _: &ClickEvent, window, cx| { + view.set_text("dxvk-threads", "0".into(), window, cx) + }), + )) + .when_some(detected_threads, |this, threads| { + this.child(chip( + "threads-manual", + format!("Manual: {threads}"), + None, + false, + !self.busy, + cx.listener(move |view, _: &ClickEvent, window, cx| { + view.set_text("dxvk-threads", threads.to_string(), window, cx) + }), + )) + }); + let tear_free = segmented( + ["Auto", "True", "False"] + .into_iter() + .map(|mode| { + ( + SharedString::from(mode), + self.dxvk_draft["tearFree"] == mode, + handler(cx.listener(move |view, _: &ClickEvent, _, cx| { + view.dxvk_draft["tearFree"] = json!(mode); + cx.notify(); + })), + ) + }) + .collect(), + !self.busy, + ) + .flex_1(); + let fps = self.slider_value("dxvk-fps", cx); + bare_panel( + "DXVK-GPLAsync", + div() + .flex() + .flex_col() + .gap(px(theme::PANEL_GAP)) + .child(control_bar) + .children(self.progress_strip(&[Operation::DxvkInstall, Operation::DxvkUninstall])) + .child( + columns() + .child(quick_row("Preset", presets)) + .child(quick_row("Compiler threads", threads)), + ) + .child( + columns() + .items_start() + .child(column(340.).child(group( + "Shader Pipeline", + vec![ + self.dxvk_toggle( + cx, + "dxvk-async", + "Async shader compilation", + "enableAsync", + ), + self.dxvk_toggle( + cx, + "dxvk-gpl", + "GPL async cache", + "gplAsyncCache", + ), + self.dxvk_toggle(cx, "dxvk-hud", "FPS/GPU HUD", "hudEnabled"), + ], + ))) + .child(column(340.).child(group( + "Frame Pacing", + vec![ + group_row( + "Frame rate cap", + div() + .flex() + .items_center() + .gap(px(10.)) + .flex_1() + .min_w(px(160.)) + .child( + div().flex().flex_1().min_w(px(100.)).child( + Slider::new(&self.sliders["dxvk-fps"]) + .disabled(self.busy), + ), + ) + .child( + div() + .w(px(36.)) + .flex_none() + .text_right() + .font_weight(FontWeight::BOLD) + .child(fps.to_string()), + ), + ), + group_row( + "Frame latency", + div().w(px(120.)).child(self.field("dxvk-latency")), + ), + group_row("Tear Free", tear_free), + ], + ))), + ), + ) + } + + pub(super) fn graphics_view(&self, cx: &mut Context) -> AnyElement { + let tab = self.graphics_tab; + let tabs = segmented( + [ + (GraphicsTab::Settings, "Graphics Settings"), + (GraphicsTab::Dxvk, "DXVK"), + ] + .into_iter() + .map(|(target, label)| { + ( + SharedString::from(label), + tab == target, + handler(cx.listener(move |view, _: &ClickEvent, _, cx| { + view.graphics_tab = target; + cx.notify(); + })), + ) + }) + .collect(), + true, + ); + div() + .flex() + .flex_col() + .gap(px(theme::PANEL_GAP)) + .child(tabs) + .child(match tab { + GraphicsTab::Settings => self.graphics_settings(cx), + GraphicsTab::Dxvk => self.dxvk_view(cx), + }) + .into_any_element() + } +} diff --git a/native/gpui/src/ui/modals.rs b/native/gpui/src/ui/modals.rs new file mode 100644 index 0000000..3bba448 --- /dev/null +++ b/native/gpui/src/ui/modals.rs @@ -0,0 +1,150 @@ +//! Confirmation and Steam-depot dialogs (`.modal-backdrop`, `.depot-modal`). +use super::components::{Btn, Glyph, icon, modal, modal_surface}; +use super::{ControlCenter, theme}; +use gpui::{prelude::*, *}; +use std::time::Instant; + +fn code_box(text: impl Into) -> Div { + div() + .p(px(12.)) + .border_1() + .border_color(theme::border()) + .rounded(theme::radius_card()) + .bg(theme::scrim().alpha(0.35)) + .text_size(px(theme::FONT_SM)) + .child(text.into()) +} + +impl ControlCenter { + /// Every staged action asks for confirmation before it reaches the service. + pub(super) fn confirm_modal(&self, cx: &mut Context) -> Option
{ + let (label, requests) = self.pending.as_ref()?; + let steps = requests.len(); + let target = self.value("/gameDir"); + Some(modal( + modal_surface() + .child( + div() + .text_size(px(theme::FONT_LG + 2.)) + .font_weight(FontWeight::BOLD) + .child("Confirm operation"), + ) + .child(div().text_color(theme::muted_strong()).child(if steps > 1 { + format!("{label} ({steps} steps)") + } else { + label.clone() + })) + .child(code_box(format!("Target: {target}"))) + .child( + div() + .flex() + .justify_end() + .gap(px(10.)) + .child(Btn::new("cancel", "Cancel").on_click(cx.listener( + |view, _: &ClickEvent, _, cx| { + view.pending = None; + cx.notify(); + }, + ))) + .child( + Btn::new("confirm", "Confirm") + .primary() + .disabled(self.busy) + .on_click( + cx.listener(|view, _: &ClickEvent, _, cx| view.confirm(cx)), + ), + ), + ), + )) + } + + /// Shown when the compatible EXE needs the Steam depot first. + pub(super) fn depot_modal(&self, cx: &mut Context) -> Option
{ + let depot = self.depot.as_ref()?; + let watching = depot.watching; + let copied = depot.copied; + Some(modal( + modal_surface() + .child( + div() + .flex() + .items_center() + .justify_center() + .size(px(54.)) + .border_1() + .border_color(theme::border()) + .rounded(theme::radius_card()) + .child(icon(Glyph::Clipboard, 28., theme::accent())), + ) + .child( + div() + .flex() + .flex_col() + .gap(px(10.)) + .child( + div() + .text_size(px(theme::FONT_LG + 2.)) + .font_weight(FontWeight::BOLD) + .child("Copy this"), + ) + .child( + div().child( + "Run this command in the Steam console to download the compatible build.", + ), + ) + .child(code_box(depot.command.clone())) + .when(watching, |this| { + this.child( + div() + .font_weight(FontWeight::SEMIBOLD) + .text_color(theme::ok()) + .child( + "Watching for the depot. PatchOpsIII will install it when ready.", + ), + ) + }), + ) + .child( + div() + .flex() + .gap(px(10.)) + .child( + Btn::new("depot-cancel", "Cancel").grow().on_click(cx.listener( + |view, _: &ClickEvent, _, cx| { + view.depot = None; + cx.notify(); + }, + )), + ) + .child( + Btn::new("depot-copy", if copied { "Copied" } else { "Copy" }) + .grow() + .icon(Glyph::Clipboard) + .on_click(cx.listener(|view, _: &ClickEvent, _, cx| { + if let Some(depot) = view.depot.as_mut() { + depot.copied = true; + cx.write_to_clipboard(ClipboardItem::new_string( + depot.command.clone(), + )); + } + cx.notify(); + })), + ) + .child( + Btn::new("depot-continue", "Continue") + .grow() + .primary() + .icon(Glyph::ExternalLink) + .on_click(cx.listener(|view, _: &ClickEvent, _, cx| { + cx.open_url("steam://open/console"); + if let Some(depot) = view.depot.as_mut() { + depot.watching = true; + depot.last_poll = Instant::now(); + } + cx.notify(); + })), + ), + ), + )) + } +} diff --git a/native/gpui/src/ui/progress.rs b/native/gpui/src/ui/progress.rs new file mode 100644 index 0000000..b165dea --- /dev/null +++ b/native/gpui/src/ui/progress.rs @@ -0,0 +1,457 @@ +//! Long-running operation feedback: the model behind the progress strips on +//! each page and the entry point the backend's event hook feeds. +//! +//! PR #40 (`migrate/tauri-in-process-rust`) has exactly one backend event, +//! `patchops-log` carrying `{category, message, line}`, and a frontend-only +//! `busy` id per command (`t7-install`, `dxvk-install`, `enhanced-install`, +//! `workshop-install`, ...). It renders no progress bars: buttons are disabled +//! while `busy` is set and log lines are appended as they stream in. This +//! module keeps that contract (`ProgressEvent::Log`, [`Operation::id`]) and +//! adds an optional stage/fraction channel for when the core can report one. +use super::components::{Glyph, icon}; +use super::{ControlCenter, theme}; +use gpui::{prelude::*, *}; +use serde_json::json; +use std::time::{Duration, Instant}; + +/// How long a finished operation stays visible before its strip disappears. +const OUTCOME_LINGER: Duration = Duration::from_secs(4); +/// Live log lines kept between status snapshots. +const LIVE_LOG_LIMIT: usize = 300; + +/// A long-running operation. [`Operation::id`] matches PR #40's `busy` ids. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Operation { + T7Install, + T7Uninstall, + DxvkInstall, + DxvkUninstall, + EnhancedValidate, + EnhancedInstall, + EnhancedUninstall, + WorkshopInstall, + ExeCompatible, + ExeCurrent, + ExeEnhanced, + UpdateCheck, + ResetStock, + ClearModFiles, +} + +impl Operation { + /// The operation a request path starts, if it is worth a progress strip. + pub fn from_path(path: &str) -> Option { + Some(match path { + "/api/t7-install" => Self::T7Install, + "/api/t7-uninstall" => Self::T7Uninstall, + "/api/dxvk-install" => Self::DxvkInstall, + "/api/dxvk-uninstall" => Self::DxvkUninstall, + "/api/enhanced-validate" => Self::EnhancedValidate, + "/api/enhanced-install" => Self::EnhancedInstall, + "/api/enhanced-uninstall" => Self::EnhancedUninstall, + "/api/workshop-install" => Self::WorkshopInstall, + "/api/exe-swap/compatible" => Self::ExeCompatible, + "/api/exe-swap/current" => Self::ExeCurrent, + "/api/exe-swap/enhanced" => Self::ExeEnhanced, + "/api/update-check" => Self::UpdateCheck, + "/api/reset-stock" => Self::ResetStock, + "/api/mod-files/clear" => Self::ClearModFiles, + _ => return None, + }) + } + + /// The frontend `busy` id used by PR #40. + #[allow(dead_code)] + pub fn id(self) -> &'static str { + match self { + Self::T7Install => "t7-install", + Self::T7Uninstall => "t7-uninstall", + Self::DxvkInstall => "dxvk-install", + Self::DxvkUninstall => "dxvk-uninstall", + Self::EnhancedValidate => "enhanced-validate", + Self::EnhancedInstall => "enhanced-install", + Self::EnhancedUninstall => "enhanced-uninstall", + Self::WorkshopInstall => "workshop-install", + Self::ExeCompatible => "exe-compatible", + Self::ExeCurrent => "exe-current", + Self::ExeEnhanced => "exe-enhanced", + Self::UpdateCheck => "update-check", + Self::ResetStock => "reset-stock", + Self::ClearModFiles => "clear-mod-files", + } + } + + /// Inverse of [`Operation::id`], for events that carry the `busy` id. + #[allow(dead_code)] + pub fn from_id(id: &str) -> Option { + const ALL: [Operation; 14] = [ + Operation::T7Install, + Operation::T7Uninstall, + Operation::DxvkInstall, + Operation::DxvkUninstall, + Operation::EnhancedValidate, + Operation::EnhancedInstall, + Operation::EnhancedUninstall, + Operation::WorkshopInstall, + Operation::ExeCompatible, + Operation::ExeCurrent, + Operation::ExeEnhanced, + Operation::UpdateCheck, + Operation::ResetStock, + Operation::ClearModFiles, + ]; + ALL.into_iter().find(|op| op.id() == id) + } + + fn title(self) -> &'static str { + match self { + Self::T7Install => "Installing T7 Patch", + Self::T7Uninstall => "Uninstalling T7 Patch", + Self::DxvkInstall => "Installing DXVK-GPLAsync", + Self::DxvkUninstall => "Uninstalling DXVK-GPLAsync", + Self::EnhancedValidate => "Validating dump source", + Self::EnhancedInstall => "Installing BO3 Enhanced", + Self::EnhancedUninstall => "Uninstalling BO3 Enhanced", + Self::WorkshopInstall => "Installing workshop mod", + Self::ExeCompatible => "Switching to the Compatible build", + Self::ExeCurrent => "Switching to the Latest build", + Self::ExeEnhanced => "Switching to BO3 Enhanced", + Self::UpdateCheck => "Checking for updates", + Self::ResetStock => "Resetting to stock", + Self::ClearModFiles => "Clearing mod files", + } + } + + /// The stage a streamed log line implies, matching the messages PR #40's + /// core logs ("Downloading DXVK-GPLAsync...", "Fetching latest BO3 + /// Enhanced release...", "Installing BO3 Enhanced files..."). + fn stage_for_log(message: &str) -> Option<&'static str> { + let lower = message.to_lowercase(); + [ + ("download", "Downloading"), + ("fetching", "Fetching release"), + ("extract", "Extracting"), + ("verif", "Verifying"), + ("validat", "Validating"), + ("back", "Backing up"), + ("install", "Installing"), + ("remov", "Removing files"), + ("restor", "Restoring"), + ] + .into_iter() + .find(|(needle, _)| lower.contains(needle)) + .map(|(_, stage)| stage) + } +} + +/// What the UI shows for the operation in flight (or just finished). +pub struct OperationProgress { + pub op: Operation, + /// Current stage, e.g. "Downloading". Empty until the first stage is known. + pub stage: String, + /// `Some(0.0..=1.0)` draws a determinate bar, `None` an indeterminate one. + pub fraction: Option, + /// Latest log line while running; the outcome text once finished. + pub message: String, + /// `None` while running, `Some(success)` once finished. + pub outcome: Option, + started: Instant, + finished: Option, +} + +impl OperationProgress { + fn new(op: Operation) -> Self { + Self { + op, + stage: String::new(), + fraction: None, + message: String::new(), + outcome: None, + started: Instant::now(), + finished: None, + } + } + + fn running(&self) -> bool { + self.outcome.is_none() + } + + /// The one-line summary used by the strip and the Activity Log header. + pub fn headline(&self) -> String { + match self.outcome { + Some(true) => "Done".to_owned(), + Some(false) => "Failed".to_owned(), + None if self.stage.is_empty() => format!("{}…", self.op.title()), + None => format!("{} · {}…", self.op.title(), self.stage), + } + } +} + +/// An event from the backend (or the UI itself) about a running operation. +#[derive(Clone, Debug)] +pub enum ProgressEvent { + /// An operation began. The UI raises this itself from `send`. + Started(Operation), + /// A new stage and, when the core can measure it, how far along it is. + Stage { + op: Operation, + stage: String, + fraction: Option, + }, + /// One activity-log line, i.e. PR #40's `patchops-log` payload. + Log { + category: String, + message: String, + /// The timestamped line (`"
Dashboard
Dashboard