diff --git a/.gitignore b/.gitignore index 242f55e4..e28a7b7b 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ dist/ # Editor .vscode/ .idea/ +.DS_Store *.sw? *.iml @@ -31,6 +32,8 @@ debug/ target/ **/*.rs.bk *.pdb +src-tauri/binaries/* +!src-tauri/binaries/.gitkeep # ts-rs default output (real bindings go to src/lib/bindings/) src-tauri/bindings/ @@ -39,6 +42,7 @@ src-tauri/src/lib/bindings/ # AI local settings .claude/ .mcp.json +plans/ # Windows nul diff --git a/Cargo.lock b/Cargo.lock index 265aada6..4609d6dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2763,6 +2763,17 @@ dependencies = [ "imgref", ] +[[package]] +name = "ltk-macos-patcher" +version = "1.9.0" +dependencies = [ + "cc", + "libc", + "serde", + "serde_json", + "tempfile", +] + [[package]] name = "ltk-manager" version = "1.9.0" @@ -2775,6 +2786,7 @@ dependencies = [ "filetime", "flate2", "image", + "libc", "libloading 0.9.0", "ltk_fantome", "ltk_file", diff --git a/Cargo.toml b/Cargo.toml index 5322fc26..81b4c449 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,3 @@ [workspace] -members = ["src-tauri"] +members = ["src-tauri", "macos-patcher"] resolver = "2" diff --git a/README.md b/README.md index 31924c34..12e301c3 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ The next-generation mod manager for League of Legends, built by the [League Tool [![Releases](https://img.shields.io/github/v/release/LeagueToolkit/ltk-manager?style=for-the-badge)](https://github.com/LeagueToolkit/ltk-manager/releases) [![License: MIT/Apache-2.0](https://img.shields.io/badge/License-MIT%2FApache--2.0-blue?style=for-the-badge)](https://github.com/LeagueToolkit/ltk-manager) [![Windows 10+](https://img.shields.io/badge/Windows-10+-0078D4?style=for-the-badge&logo=windows)](https://www.microsoft.com/windows) +[![macOS 13+ ARM64](https://img.shields.io/badge/macOS-13%2B_ARM64-000000?style=for-the-badge&logo=apple)](docs/DEVELOPMENT.md#apple-silicon-development) [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FLeagueToolkit%2Fltk-manager.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2FLeagueToolkit%2Fltk-manager?ref=badge_shield) --- @@ -40,7 +41,9 @@ The next-generation mod manager for League of Legends, built by the [League Tool ### Prerequisites -- **Windows 10 or 11** (64-bit). macOS and Linux support is planned. +- **Windows 10 or 11** (64-bit) for released installers. +- **macOS 13 or newer on Apple Silicon** for local source builds. Public macOS packages, Intel + support, notarization, and auto-updates are not currently provided. - **League of Legends** — a valid game installation. ### Installation @@ -56,6 +59,10 @@ The next-generation mod manager for League of Legends, built by the [League Tool 2. Drag and drop the file onto the LTK Manager window, or use the install button. 3. Enable the mod in your library and click **Run** to start the patcher. +Apple Silicon developers should use `pnpm macos:dev`, which builds the native ARM64 helper before +starting Tauri. See the [Apple Silicon development guide](docs/DEVELOPMENT.md#apple-silicon-development) +for the required local approval and preflight workflow. + --- ## ⚖️ License & Reuse @@ -75,6 +82,13 @@ If you are a developer looking to reuse this DLL in your own launcher or tool, y For full terms, see [LICENSE-CSLOL.md](LICENSE-CSLOL.md). +### macOS helper + +The native macOS patcher is a separate MIT-licensed process under `macos-patcher/`. Its ARM64 +Mach-O scanner and WAD redirection mechanism are adapted from the separately MIT-licensed +`cslol-tools` subtree of cslol-manager. See `macos-patcher/NOTICE.md` for the pinned source commit +and attribution. + --- ## ⚠️ Disclaimer diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index dc15b9d7..1bac1c3d 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -15,10 +15,19 @@ This guide covers how to build LTK Manager from source and contribute to the pro sudo apt-get install -y libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev ``` -### Windows / macOS +### Windows No additional system dependencies required beyond Rust and Node.js. +### macOS + +- macOS 13 or newer +- Apple Silicon (`arm64`) +- Xcode Command Line Tools (`xcode-select --install`) +- SIP enabled + +The initial native patcher does not support Intel Macs or an x86_64 League process under Rosetta. + ## Getting Started ```bash @@ -37,6 +46,119 @@ pnpm tauri dev pnpm dev ``` +## Apple Silicon Development + +The macOS patcher is a separate executable. LTK Manager stays unprivileged; starting a patcher +session launches only the small helper through a macOS administrator approval prompt. The helper +connects back to an owner-only Unix socket, authenticates with a random session token, validates +the configured League bundle and overlay paths, and exits when the patcher stops. + +### Build and run + +```bash +pnpm install +pnpm macos:dev +``` + +`pnpm macos:dev` builds and ad-hoc signs +`src-tauri/binaries/ltk-macos-patcher-aarch64-apple-darwin`, then starts +`pnpm tauri dev --target aarch64-apple-darwin`. + +For a local ARM64 application bundle: + +```bash +pnpm macos:build +``` + +This produces `target/aarch64-apple-darwin/release/bundle/macos/LTK Manager.app`. +The build command ad-hoc signs the helper and final app bundle, then runs strict local signature +verification. +Developer ID signing, notarization, DMG packaging, updater artifacts, Intel builds, and universal +binaries are intentionally outside the local workflow. + +### Configure League + +LTK Manager accepts any of these selections and resolves them to one canonical installation: + +- `/Applications/League of Legends.app` +- `League of Legends.app/Contents/LoL` +- `League of Legends.app/Contents/LoL/Game` +- A path inside `Game/LeagueofLegends.app` + +Auto-detection checks common application locations and running `LeagueClient` or +`LeagueofLegends` process paths. `LTK_LEAGUE_PATH` can be set to override detection during local +development. + +### Verify compatibility + +Run a read-only dry scan before starting the patcher: + +```bash +pnpm macos:preflight -- "/Applications/League of Legends.app" +``` + +A successful response reports `compatible`, architecture `arm64`, and the current signature ID. +The helper requires exactly one validated patch signature and fails before opening or writing +target process memory when the League build is unknown. + +To inspect the installed executable directly: + +```bash +file "/Applications/League of Legends.app/Contents/LoL/Game/LeagueofLegends.app/Contents/MacOS/LeagueofLegends" +``` + +The selected slice must be ARM64. Rosetta/x86_64 execution is not supported. + +### End-to-end run + +1. Keep SIP enabled. +2. Start LTK Manager with `pnpm macos:dev`. +3. Select or auto-detect the League application. +4. Install and enable a harmless, visually obvious test mod. +5. Run Diagnostics and confirm the native helper and ARM64 signature checks pass. +6. Click Run and approve the macOS administrator prompt for the helper. +7. Launch Practice Tool and verify the mod loads from the generated overlay. +8. Stop the patcher, launch another Practice Tool session, and verify unmodified assets are used. + +Repeat the patched and unpatched cycle after every League update. A signature mismatch is a hard +compatibility failure and must not be bypassed. + +### Stop, repair, and remove + +The privilege mechanism is one-shot: no launch daemon or persistent root helper is installed. +Stopping the patcher sends a protocol stop request and waits for the elevated helper to exit. + +Rebuild a missing or version-mismatched helper: + +```bash +pnpm macos:helper +``` + +Remove local helper artifacts: + +```bash +rm -f src-tauri/binaries/ltk-macos-patcher-aarch64-apple-darwin +cargo clean -p ltk-macos-patcher +``` + +If macOS App Translocation or quarantine prevents helper startup, move the locally built app to +`/Applications`, verify its source, and use the Diagnostics report before changing any extended +attributes. Do not disable SIP and do not run the full Tauri application with `sudo`. + +### Patch maintenance + +The patch-day smoke sequence is: + +1. Rebuild the helper. +2. Run `pnpm macos:preflight`. +3. Build an overlay from the fixed harmless test mod. +4. Verify one patched Practice Tool launch. +5. Stop patching and verify one unmodified launch. +6. Repeat after restarting LTK Manager. + +Update the versioned native signature only with an ARM64 executable fixture and a unique validated +match. The helper must continue to reject zero or multiple matches. + ### Verbose Backend Logging ```bash @@ -52,8 +174,8 @@ pnpm format # Prettier (auto-fix) pnpm check # All three (typecheck + lint + format:check) # Rust -cargo clippy -p ltk-manager -cargo fmt -p ltk-manager +cargo clippy --workspace --all-targets +cargo fmt --all ``` ## Production Build @@ -138,4 +260,5 @@ ltk-manager/ Logs are written to disk automatically and are useful for debugging: - **Windows:** `%APPDATA%\dev.leaguetoolkit.manager\logs\ltk-manager.log` -- **Linux / macOS:** `~/.local/share/dev.leaguetoolkit.manager/logs/ltk-manager.log` +- **Linux:** `~/.local/share/dev.leaguetoolkit.manager/logs/ltk-manager.log` +- **macOS:** `~/Library/Logs/dev.leaguetoolkit.manager/ltk-manager.*.log` diff --git a/macos-patcher/Cargo.toml b/macos-patcher/Cargo.toml new file mode 100644 index 00000000..8735279c --- /dev/null +++ b/macos-patcher/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "ltk-macos-patcher" +version = "1.9.0" +edition = "2021" +license = "MIT" +publish = false + +[dependencies] +libc = "0.2" +serde = { version = "1", features = ["derive"] } +serde_json = "1" + +[build-dependencies] +cc = "1" + +[dev-dependencies] +tempfile = "3" diff --git a/macos-patcher/LICENSE b/macos-patcher/LICENSE new file mode 100644 index 00000000..3a138086 --- /dev/null +++ b/macos-patcher/LICENSE @@ -0,0 +1,19 @@ +Copyright 2026 League Toolkit contributors + +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/macos-patcher/NOTICE.md b/macos-patcher/NOTICE.md new file mode 100644 index 00000000..6d0cc3c2 --- /dev/null +++ b/macos-patcher/NOTICE.md @@ -0,0 +1,10 @@ +# Native patcher notice + +The ARM64 Mach-O scanner and in-process WAD redirection mechanism are adapted +from the `cslol-tools` subtree of LeagueToolkit/cslol-manager, commit +`23f230858bc2359ce279e07ed129d482fe3b00bf`. That subtree carries an MIT +license. The protocol, validation, lifecycle, and privilege boundary in this +directory are specific to LTK Manager. + +This helper is intentionally a separate process. It must not be linked into or +loaded by the Tauri WebView process. diff --git a/macos-patcher/build.rs b/macos-patcher/build.rs new file mode 100644 index 00000000..5c03104c --- /dev/null +++ b/macos-patcher/build.rs @@ -0,0 +1,15 @@ +fn main() { + println!("cargo:rerun-if-changed=native/patcher.cpp"); + println!("cargo:rerun-if-changed=native/macho.hpp"); + + let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default(); + let target_arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default(); + if target_os == "macos" && target_arch == "aarch64" { + cc::Build::new() + .cpp(true) + .std("c++20") + .warnings(true) + .file("native/patcher.cpp") + .compile("ltk_macos_patch_engine"); + } +} diff --git a/macos-patcher/native/macho.hpp b/macos-patcher/native/macho.hpp new file mode 100644 index 00000000..4d23bcd3 --- /dev/null +++ b/macos-patcher/native/macho.hpp @@ -0,0 +1,507 @@ +// Adapted from the MIT-licensed cslol-tools Mach-O parser at +// LeagueToolkit/cslol-manager commit 23f230858bc2359ce279e07ed129d482fe3b00bf. +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace lol { + struct MachO { + using data_t = unsigned char const*; + + struct Header { + std::uint32_t magic; + std::uint32_t cputype; + std::uint32_t cpusubtype; + std::uint32_t filetype; + std::uint32_t ncmds; + std::uint32_t sizeofcmds; + std::uint32_t flags; + std::uint32_t reserved; + }; + + struct Command { + std::uint32_t cmd; + std::uint32_t cmdsize; + }; + + struct Segment { + char segname[16]; + std::uint64_t vmaddr; + std::uint64_t vmsize; + std::uint64_t fileoff; + std::uint64_t filesize; + std::int32_t maxprot; + std::int32_t initprot; + std::uint32_t nsects; + std::uint32_t flags; + }; + + struct Section { + char sectname[16]; + char segname[16]; + std::uint64_t addr; + std::uint64_t size; + std::uint32_t offset; + std::uint32_t align; + std::uint32_t reloff; + std::uint32_t nreloc; + std::uint32_t flags; + std::uint32_t reserved1; + std::uint32_t reserved2; + std::uint32_t reserved3; + }; + + struct SegSec : Segment { + std::vector
sections; + }; + + struct DyldInfo { + std::uint32_t rebase_off; + std::uint32_t rebase_size; + std::uint32_t bind_off; + std::uint32_t bind_size; + std::uint32_t weak_bind_off; + std::uint32_t weak_bind_size; + std::uint32_t lazy_bind_off; + std::uint32_t lazy_bind_size; + std::uint32_t export_off; + std::uint32_t export_size; + }; + + struct Symtab { + std::uint32_t symoff; + std::uint32_t nsyms; + std::uint32_t stroff; + std::uint32_t strsize; + }; + + struct Export { + struct ReExport { + std::string name; + std::uint64_t ordinal; + }; + struct StubAndResolver { + std::uint64_t stub_offset; + std::uint64_t resolver_offset; + }; + std::optional symbol_offset; + std::optional reexport; + std::optional stub_and_resolver; + }; + + struct Symbol { + std::uint32_t n_strx; + std::uint8_t n_type; + std::uint8_t n_sect; + std::int16_t n_desc; + std::uint64_t n_value; + }; + + struct BindingLazy { + std::uint8_t binding_type = 0; + std::uint8_t sym_flags = 0; + std::uint8_t segment = 0; + std::int64_t lib_ord = 0; + std::uint64_t address = 0; + }; + + void parse_data_amd64(data_t data, size_t size) { parse_data(data, size, 0x1000007); } + void parse_data_arm64(data_t data, size_t size) { parse_data(data, size, 0x100000C); } + void parse_data(data_t data, size_t size, std::uint32_t cputype); + + void parse_file_amd64(std::filesystem::path const& filename) { parse_file(filename, 0x1000007); } + void parse_file_arm64(std::filesystem::path const& filename) { parse_file(filename, 0x100000C); } + void parse_file(std::filesystem::path const& filename, std::uint32_t cputype); + std::uint64_t find_export(char const* func_name) const; + std::uint64_t find_import_ptr(char const* func_name) const; + std::uint64_t find_stub_refs(std::uint64_t address) const; + std::tuple find_section(std::string_view name) const; + std::pair image_vm_range() const; + + private: + struct Stream; + void read_impl(data_t data, size_t size); + void read_stubs(Stream const& org_stream, std::uint64_t address); + void read_exports(Stream const& org_stream); + void read_binding_lazy(Stream const& org_stream); + + data_t data_ = {}; + size_t size_ = {}; + std::uint32_t cputype_ = {}; + std::vector segments; + std::unordered_map exports; + std::unordered_map binding_lazy; + std::unordered_map stub_refs; + }; +} + +/*************************************************************************************************/ +/* Implementation */ +/*************************************************************************************************/ + +namespace lol { + struct MachO::Stream { + data_t data_ = {}; + size_t size_ = {}; + + inline bool empty() { return !size_; } + + template + inline T read_raw() { + if (size_ < sizeof(T)) { + throw std::runtime_error("Failed to read raw: no more data!"); + } + unsigned char raw[sizeof(T)]; + memcpy(raw, data_, sizeof(T)); + T result; + memcpy(&result, raw, sizeof(T)); + data_ = data_ + sizeof(T); + size_ -= sizeof(T); + return result; + } + + inline uint32_t read_uint32_endian(bool big) { + auto const raw = read_raw(); + if (!big) return raw; + return raw >> 24 | ((raw & 0x00FF0000) >> 8) | ((raw & 0x0000FF00) << 8) | (raw << 24); + } + + inline std::uint64_t read_uleb64() { + std::uint64_t result = 0; + for (int shift = 0; true; shift += 7) { + if (shift + 7 > 63) { + throw std::runtime_error("Failed to read uleb64: value too big!"); + } + auto const c = read_raw(); + result |= ((std::uint64_t)(c & 0x7f) << shift); + if (!(c & 0x80)) { + break; + } + } + return result; + } + + inline std::int64_t read_sleb64() { + std::int64_t result = 0; + for (int shift = 0; true; shift += 7) { + if (shift + 7 > 63) { + throw std::runtime_error("Failed to read uleb64: value too big!"); + } + auto const c = read_raw(); + result |= ((std::uint64_t)(c & 0x7f) << shift); + if (!(c & 0x80)) { + result <<= 64 - (shift + 7); + result >>= 64 - (shift + 7); + break; + } + } + return result; + } + + inline std::string read_zstring() { + auto result = std::string{}; + while (auto const c = read_raw()) { + result.push_back(c); + } + return result; + } + + inline Stream copy(std::size_t offset = 0, std::size_t count = (std::size_t)-1) const { + if (offset > size_) { + throw std::runtime_error("Failed to seek: out of range!"); + } + if (auto const remain_data = size_ - offset; count == (std::size_t)-1) { + count = remain_data; + } else { + if (remain_data < count) { + throw std::runtime_error("Failed to seek: out of range!"); + } + } + return Stream{data_ + offset, count}; + } + + inline void skip(std::size_t count) { + if (count > size_) { + throw std::runtime_error("Failed to skip: out of range!"); + } + data_ += count; + size_ -= count; + } + }; + + inline void MachO::parse_data(data_t data, size_t size, std::uint32_t cputype) { + data_ = data; + size_ = size; + cputype_ = cputype; + segments.clear(); + exports.clear(); + binding_lazy.clear(); + stub_refs.clear(); + + auto stream = Stream{data, size}; + auto const magic = stream.read_raw(); + if (magic != 0xBEBAFECA && magic != 0xCAFEBABE) { + return read_impl(data, size); + } + + auto const flip_endians = (magic != 0xCAFEBABE); + auto nfat_arch = stream.read_uint32_endian(flip_endians); + for (auto i = 0u; i != nfat_arch; ++i) { + auto const arch_cputype = stream.read_uint32_endian(flip_endians); + auto const arch_cpusubtype = stream.read_uint32_endian(flip_endians); + auto const arch_offset = stream.read_uint32_endian(flip_endians); + auto const arch_size = stream.read_uint32_endian(flip_endians); + auto const arch_align = stream.read_uint32_endian(flip_endians); + + (void)arch_cpusubtype; // ignore for now + (void)arch_align; // ignore for now + if (arch_cputype == cputype) { + if (arch_offset > size || arch_size > size - arch_offset) { + throw std::runtime_error("Invalid Mach-O slice: out of range!"); + } + return read_impl(data + arch_offset, arch_size); + } + } + throw std::runtime_error("Failed to find Mach-O slice for the specified architecture!"); + } + + inline void MachO::read_impl(data_t data, size_t data_size) { + this->data_ = data; + this->size_ = data_size; + auto const file_stream = Stream{data, data_size}; + auto stream = file_stream.copy(); + auto const header = stream.read_raw
(); + for (auto count = header.ncmds; count; --count) { + auto cmd_stream = stream.copy(); + auto const command = cmd_stream.read_raw(); + if (command.cmd == 0x19) { + auto const segment = cmd_stream.read_raw(); + auto& segsec = this->segments.emplace_back(segment); + for (auto count = segment.nsects; count; --count) { + auto const section = cmd_stream.read_raw
(); + if (std::string_view{section.sectname} == "__stubs") { + read_stubs(file_stream.copy(segment.fileoff + section.offset, section.size), section.addr); + } + segsec.sections.push_back(section); + } + } + if ((command.cmd & ~0x80000000u) == 0x22) { + auto const info = cmd_stream.read_raw(); + read_exports(file_stream.copy(info.export_off, info.export_size)); + read_binding_lazy(file_stream.copy(info.lazy_bind_off, info.lazy_bind_size)); + } + stream.skip(command.cmdsize); + } + } + + inline void MachO::parse_file(std::filesystem::path const& filename, std::uint32_t cputype) { + auto result = std::vector{}; + if (auto file = std::ifstream(filename, std::ios::binary)) { + auto beg = file.tellg(); + file.seekg(std::ios::end); + auto end = file.tellg(); + file.seekg(std::ios::beg); + result.resize((std::size_t)(end - beg)); + file.read((char*)result.data(), end - beg); + } + parse_data(result.data(), result.size(), cputype); + } + + inline std::pair MachO::image_vm_range() const { + if (segments.empty()) { + throw std::runtime_error("Mach-O image has no segments"); + } + + auto begin = segments.front().vmaddr; + auto end = begin; + for (auto const& segment : segments) { + if (segment.vmsize > UINT64_MAX - segment.vmaddr) { + throw std::runtime_error("Mach-O segment range overflows"); + } + begin = std::min(begin, segment.vmaddr); + end = std::max(end, segment.vmaddr + segment.vmsize); + } + return {begin, end}; + } + + inline void MachO::read_stubs(const Stream& org_stream, std::uint64_t address) { + auto stream = org_stream.copy(); + while (!stream.empty()) { + if (cputype_ == 0x1000007) { + auto const opcode = stream.read_raw(); + if (opcode != 0x25FF) { + // throw error here + } + auto const relative = stream.read_raw(); + stub_refs[(address + 6) + relative] = address; + address += 6; + } else if (cputype_ == 0x100000C) { + auto const adrp = stream.read_raw(); + auto const ldr = stream.read_raw(); + auto const br = stream.read_raw(); + if ((adrp & 0x9F00001F) != 0x90000010 || + (ldr & 0xFFC003FF) != 0xF9400210 || br != 0xD61F0200) { + throw std::runtime_error("Unsupported ARM64 import stub"); + } + + // ADRP: Extract 21-bit signed page offset + auto const immlo = (adrp & 0x60000000) >> 29; // Bits 30:29 + auto const immhi = (adrp & 0x00FFFFE0) >> 5; // Bits 23:5 + auto const imm = (immhi << 2) | immlo; // Combine into 21-bit value + auto const sign = (imm & 0x100000) ? 0xFFFFFFFFFFE00000ULL : 0; // Sign extend from bit 20 + auto const relative = (sign | imm) << 12; // Convert page offset to byte offset + auto const page = (address + relative) & ~0xFFF; + + // LDR: Extract scaled immediate offset + auto const page_offset = ((ldr >> 10) & 0xFFF) << 3; // Bits 21:10, scaled by 8 + auto const final_address = page + page_offset; + stub_refs[final_address] = address; + address += 12; + } else { + throw std::runtime_error("Unsupported CPU type for stubs!"); + } + } + } + + inline void MachO::read_exports(Stream const& org_stream) { + for (auto stack = std::vector>{{"", 0ull}}; !stack.empty();) { + auto const [sym_name, offset] = stack.back(); + stack.pop_back(); + auto stream = org_stream.copy(offset); + if (stream.read_uleb64()) { + if (auto const flags = stream.read_uleb64(); flags & 0x08) { + auto const ordinal = stream.read_uleb64(); + auto const name = stream.read_zstring(); + exports[sym_name] = Export{.reexport = Export::ReExport{ + .name = name, + .ordinal = ordinal, + }}; + } else if (flags & 0x10) { + auto const stub_offset = stream.read_uleb64(); + auto const resolver_offset = stream.read_uleb64(); + exports[sym_name] = Export{.stub_and_resolver = Export::StubAndResolver{ + .stub_offset = stub_offset, + .resolver_offset = resolver_offset, + }}; + } else { + auto const symbol_offset = stream.read_uleb64(); + exports[sym_name] = Export{.symbol_offset = symbol_offset}; + } + } + for (auto child_count = (std::uint32_t)stream.read_raw(); child_count; --child_count) { + auto const child_name = sym_name + stream.read_zstring(); + auto const child_offset = stream.read_uleb64(); + stack.emplace_back(child_name, child_offset); + } + } + } + + inline void MachO::read_binding_lazy(Stream const& org_stream) { + std::string sym_name = ""; + std::uint8_t binding_type = 0; + std::uint8_t segment = 0; + std::uint8_t sym_flags = 0; + std::int64_t lib_ord = 0; + std::uint64_t address = 0; + for (auto stream = org_stream; !stream.empty();) { + auto const raw_opcode = stream.read_raw(); + auto const opcode = raw_opcode & 0xF0; + auto const immediate = raw_opcode & 0x0F; + switch (opcode) { + case 0x00: + sym_name = ""; + binding_type = 1; + segment = 0; + sym_flags = 0; + lib_ord = 0; + address = 0; + break; + case 0x10: + lib_ord = immediate; + break; + case 0x20: + lib_ord = stream.read_uleb64(); + break; + case 0x30: + if (immediate) { + lib_ord = immediate - std::int64_t{16}; + } else { + lib_ord = 0; + } + break; + case 0x40: + sym_name = stream.read_zstring(); + sym_flags = immediate; + break; + case 0x50: + binding_type = immediate; + break; + case 0x70: + address = stream.read_uleb64(); + segment = immediate; + break; + case 0x90: + binding_lazy[sym_name] = MachO::BindingLazy{ + .binding_type = binding_type, + .sym_flags = sym_flags, + .segment = segment, + .lib_ord = lib_ord, + .address = address, + }; + break; + default: + throw std::runtime_error("Unknown symbol binding opcode"); + } + } + } + + inline std::uint64_t MachO::find_export(char const* func_name) const { + if (auto i = exports.find(func_name); i != exports.end()) { + if (i->second.symbol_offset) { + return segments.at(0).vmsize + *i->second.symbol_offset; + } + } + return {}; + } + + inline std::uint64_t MachO::find_import_ptr(char const* func_name) const { + if (auto i = binding_lazy.find(func_name); i != binding_lazy.end()) { + if (i->second.binding_type == 1) { + return segments.at(i->second.segment).vmaddr + i->second.address; + } + } + return {}; + } + + inline std::uint64_t MachO::find_stub_refs(std::uint64_t address) const { + if (auto i = stub_refs.find(address); i != stub_refs.end()) { + return i->second; + } + return {}; + } + + inline std::tuple MachO::find_section(std::string_view name) const { + auto stream = Stream{data_, size_}; + for (const auto& segment : this->segments) { + for (const auto& section : segment.sections) { + if (std::string_view{section.sectname} != name) { + continue; + } + const auto [data, size] = stream.copy(segment.fileoff + section.offset, section.size); + return {section.addr, data, size}; + } + } + return {}; + } +} diff --git a/macos-patcher/native/patcher.cpp b/macos-patcher/native/patcher.cpp new file mode 100644 index 00000000..eb98c267 --- /dev/null +++ b/macos-patcher/native/patcher.cpp @@ -0,0 +1,714 @@ +// The ARM64 patch mechanism in this file is adapted from the MIT-licensed +// cslol-tools subtree at LeagueToolkit/cslol-manager commit +// 23f230858bc2359ce279e07ed129d482fe3b00bf. + +#if defined(__APPLE__) && (defined(__aarch64__) || defined(__arm64__)) + +#include "macho.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using EventCallback = void (*)(void*, const char*, std::uint32_t, const char*); +using StopCallback = bool (*)(void*); +using PtrStorage = std::uint64_t; + +constexpr const char* SIGNATURE_ID = "mac-arm64-pattern-v1"; +constexpr std::uint32_t BTI_C = 0xD503245F; +constexpr auto IMPORT_RESOLUTION_TIMEOUT = std::chrono::seconds(10); + +void set_error(char* buffer, std::size_t buffer_len, std::string_view message) { + if (!buffer || buffer_len == 0) { + return; + } + const auto count = std::min(buffer_len - 1, message.size()); + std::memcpy(buffer, message.data(), count); + buffer[count] = '\0'; +} + +std::filesystem::path canonical_path(const std::filesystem::path& path) { + std::error_code error; + auto result = std::filesystem::weakly_canonical(path, error); + if (error) { + throw std::runtime_error("Failed to canonicalize path: " + error.message()); + } + return result; +} + +class Process { +public: + Process() = default; + Process(mach_port_t task, std::uint32_t pid) : task_(task), pid_(pid) {} + Process(const Process&) = delete; + Process& operator=(const Process&) = delete; + + Process(Process&& other) noexcept { + std::swap(task_, other.task_); + std::swap(pid_, other.pid_); + std::swap(base_, other.base_); + } + + ~Process() { + if (task_ != MACH_PORT_NULL) { + mach_port_deallocate(mach_task_self(), task_); + } + } + + static std::uint32_t find_pid(const std::filesystem::path& exact_path) { + std::array pids{}; + const int bytes = + proc_listpids(PROC_ALL_PIDS, 0, pids.data(), static_cast(sizeof(pids))); + if (bytes <= 0) { + return 0; + } + + const auto expected = exact_path.generic_string(); + const int count = bytes / static_cast(sizeof(pid_t)); + std::array path{}; + for (int index = 0; index < count; ++index) { + if (pids[index] <= 0) { + continue; + } + const int length = + proc_pidpath(pids[index], path.data(), static_cast(path.size())); + if (length <= 0) { + continue; + } + if (std::string_view(path.data(), static_cast(length)) == expected) { + return static_cast(pids[index]); + } + } + return 0; + } + + static Process open(std::uint32_t pid) { + mach_port_t task = MACH_PORT_NULL; + const auto result = task_for_pid(mach_task_self(), static_cast(pid), &task); + if (result != KERN_SUCCESS) { + throw std::runtime_error("task_for_pid failed with code " + std::to_string(result)); + } + return Process(task, pid); + } + + bool exited() const { + std::array path{}; + return proc_pidpath(static_cast(pid_), path.data(), + static_cast(path.size())) <= 0; + } + + PtrStorage base() { + if (base_ != 0) { + return base_; + } + + vm_map_offset_t offset = 0; + vm_map_size_t size = 0; + natural_t depth = 0; + vm_region_submap_info_data_64_t info{}; + mach_msg_type_number_t count = VM_REGION_SUBMAP_INFO_COUNT_64; + const auto result = mach_vm_region_recurse( + task_, &offset, &size, &depth, reinterpret_cast(&info), + &count); + if (result != KERN_SUCCESS) { + throw std::runtime_error("mach_vm_region_recurse failed with code " + + std::to_string(result)); + } + if (offset < 0x100000000ULL) { + throw std::runtime_error("Unexpected Mach-O image base"); + } + base_ = offset - 0x100000000ULL; + return base_; + } + + void* allocate(std::size_t size) const { + mach_vm_address_t address = 0; + const auto result = mach_vm_allocate(task_, &address, size, VM_FLAGS_ANYWHERE); + if (result != KERN_SUCCESS) { + throw std::runtime_error("mach_vm_allocate failed with code " + + std::to_string(result)); + } + return reinterpret_cast(address); + } + + void write(void* address, const void* source, std::size_t size) const { + const auto result = mach_vm_write( + task_, reinterpret_cast(address), + reinterpret_cast(const_cast(source)), + static_cast(size)); + if (result != KERN_SUCCESS) { + throw std::runtime_error("mach_vm_write failed with code " + + std::to_string(result)); + } + } + + void read(const void* address, void* destination, std::size_t size) const { + mach_vm_size_t bytes_read = 0; + const auto result = mach_vm_read_overwrite( + task_, reinterpret_cast(address), size, + reinterpret_cast(destination), &bytes_read); + if (result != KERN_SUCCESS || bytes_read != size) { + throw std::runtime_error("mach_vm_read_overwrite failed with code " + + std::to_string(result)); + } + } + + void write_verified(void* address, const void* source, std::size_t size) const { + write(address, source, size); + std::vector actual(size); + read(address, actual.data(), size); + if (std::memcmp(actual.data(), source, size) != 0) { + throw std::runtime_error("Target memory verification failed after patch write"); + } + } + + void writable(void* address, std::size_t size) const { + const auto result = + mach_vm_protect(task_, reinterpret_cast(address), size, FALSE, + VM_PROT_READ | VM_PROT_WRITE | VM_PROT_COPY); + if (result != KERN_SUCCESS) { + throw std::runtime_error("mach_vm_protect writable failed with code " + + std::to_string(result)); + } + } + + void executable(void* address, std::size_t size) const { + const auto result = + mach_vm_protect(task_, reinterpret_cast(address), size, FALSE, + VM_PROT_READ | VM_PROT_EXECUTE); + if (result != KERN_SUCCESS) { + throw std::runtime_error("mach_vm_protect executable failed with code " + + std::to_string(result)); + } + + auto cache_operation = MATTR_VAL_CACHE_SYNC; + auto cache_result = mach_vm_machine_attribute( + task_, reinterpret_cast(address), size, MATTR_CACHE, + &cache_operation); + if (cache_result != KERN_SUCCESS) { + cache_operation = MATTR_VAL_CACHE_FLUSH; + cache_result = mach_vm_machine_attribute( + task_, reinterpret_cast(address), size, MATTR_CACHE, + &cache_operation); + } + if (cache_result != KERN_SUCCESS) { + throw std::runtime_error("Failed to synchronize target instruction cache with code " + + std::to_string(cache_result)); + } + } + + void suspend() const { + const auto result = task_suspend(task_); + if (result != KERN_SUCCESS) { + throw std::runtime_error("task_suspend failed with code " + + std::to_string(result)); + } + } + + void resume() const noexcept { + (void)task_resume(task_); + } + + void verify_arm64() { + mach_header_64 header{}; + const auto image_address = base() + 0x100000000ULL; + read(reinterpret_cast(image_address), &header, sizeof(header)); + if (header.magic != MH_MAGIC_64 || header.cputype != CPU_TYPE_ARM64) { + throw std::runtime_error("League game process is not running as ARM64"); + } + } + +private: + mach_port_t task_ = MACH_PORT_NULL; + std::uint32_t pid_ = 0; + PtrStorage base_ = 0; +}; + +class TaskSuspension { +public: + explicit TaskSuspension(const Process& process) : process_(process) { + process_.suspend(); + } + TaskSuspension(const TaskSuspension&) = delete; + TaskSuspension& operator=(const TaskSuspension&) = delete; + ~TaskSuspension() { + process_.resume(); + } + +private: + const Process& process_; +}; + +__asm__(R"( +.text + +.global _fopen_hook_shellcode_beg +.global _fopen_hook_shellcode_end +.global _fopen_hook_original_ptr +.global _fopen_hook_prefix + +.set buffer_size, 0x200 + +filename .req x19 +mode .req x20 +filename_len .req x21 +fopen_org .req x22 + +.p2align 8 +_fopen_hook_shellcode_beg: + bti c + stp fp, lr, [sp, #-16]! + mov fp, sp + stp filename, mode, [sp, #-16]! + stp filename_len, fopen_org, [sp, #-16]! + sub sp, sp, #buffer_size + + mov filename, x0 + mov mode, x1 + + adr fopen_org, Lfopen_org_ref + ldr fopen_org, [fopen_org] + +Lcheck_args_not_null: + cbz filename, Lcall_with_filename + cbz mode, Lcall_with_filename + +Lcheck_mode_eq_rb: + ldrb w2, [mode] + cmp w2, #'r' + b.ne Lcall_with_filename + ldrb w2, [mode, #1] + cmp w2, #'b' + b.ne Lcall_with_filename + ldrb w2, [mode, #2] + cbnz w2, Lcall_with_filename + +Lget_filename_length: + mov filename_len, #0 + mov x0, filename +Lget_filename_length_continue: + ldrb w2, [x0], #1 + cbz w2, Lget_filename_length_break + add filename_len, filename_len, #1 + cmp filename_len, 0x80 + b.ge Lcall_with_filename + b Lget_filename_length_continue +Lget_filename_length_break: + +Lcheck_suffix: + cmp filename_len, #7 + b.lt Lcall_with_filename + add x0, filename, filename_len + sub x0, x0, #7 + ldr x2, [x0] + movz x3, #0x632E, lsl #0 + movk x3, #0x696C, lsl #16 + movk x3, #0x6E65, lsl #32 + movk x3, #0x0074, lsl #48 + cmp x2, x3 + b.ne Lcall_with_filename + +Lwrite_prefix: + mov x0, sp + adr x1, Lprefix +Lwrite_prefix_continue: + ldrb w2, [x1], #1 + strb w2, [x0], #1 + cbnz w2, Lwrite_prefix_continue + +Lwrite_filename: + sub x0, x0, #1 + mov x1, filename +Lwrite_filename_continue: + ldrb w2, [x1], #1 + strb w2, [x0], #1 + cbnz w2, Lwrite_filename_continue + +Lcall_with_buffer: + mov x0, sp + mov x1, mode + blr fopen_org + cbnz x0, Lreturn + +Lcall_with_filename: + mov x0, filename + mov x1, mode + blr fopen_org + +Lreturn: + add sp, sp, #buffer_size + ldp filename_len, fopen_org, [sp], #16 + ldp filename, mode, [sp], #16 + ldp fp, lr, [sp], #16 + ret + +.p2align 8 +_fopen_hook_shellcode_end: +Lfopen_org_ref: +_fopen_hook_original_ptr: + .quad 0x11223344556677 + .word 0x11223344 +.p2align 4 +Lprefix: +_fopen_hook_prefix: + .quad 0x11223344556677 +)"); + +extern "C" { +extern unsigned char fopen_hook_shellcode_beg[]; +extern unsigned char fopen_hook_shellcode_end[]; +extern unsigned char fopen_hook_original_ptr[]; +extern unsigned char fopen_hook_prefix[]; +} + +struct PayloadWadVerify { + unsigned char return_true[0x8] = { + 0x20, 0x00, 0x80, 0xD2, 0xC0, 0x03, 0x5F, 0xD6, + }; +}; + +struct PayloadFopenHook { + unsigned char fopen_hook[0x100]{}; + PtrStorage fopen_org_ptr{}; + std::uint8_t padding[0x8]{}; + char prefix[0x100]{}; +}; + +static_assert(offsetof(PayloadFopenHook, fopen_org_ptr) == 0x100); +static_assert(offsetof(PayloadFopenHook, prefix) == 0x110); + +std::uint64_t find_unique_wad_verify(const std::uint8_t* begin, const std::uint8_t* end, + std::uint64_t text_address) { + constexpr std::array pattern = { + 0xC3, 0x24, 0x80, 0x52, 0x04, 0x20, 0x80, 0x52, + }; + std::vector matches; + auto cursor = begin; + while (cursor < end) { + const auto match = std::search(cursor, end, pattern.begin(), pattern.end()); + if (match == end) { + break; + } + matches.push_back(match); + cursor = match + 1; + } + if (matches.size() != 1) { + throw std::runtime_error("wad_verify signature must have exactly one match; found " + + std::to_string(matches.size())); + } + + const auto* instruction_bytes = matches.front() + pattern.size(); + if (instruction_bytes + sizeof(std::uint32_t) > end) { + throw std::runtime_error("wad_verify signature is truncated"); + } + std::uint32_t instruction = 0; + std::memcpy(&instruction, instruction_bytes, sizeof(instruction)); + const auto opcode = instruction & 0xFC000000; + if (opcode != 0x94000000 && opcode != 0x14000000) { + throw std::runtime_error("wad_verify signature has an invalid branch instruction"); + } + const auto relative = static_cast(instruction << 6) >> 6; + const auto branch_pc = + text_address + static_cast(instruction_bytes - begin); + return branch_pc + static_cast(relative) * 4; +} + +struct ScanResult { + std::uint64_t wad_verify{}; + std::uint64_t fopen_ptr{}; + std::uint64_t image_begin{}; + std::uint64_t image_end{}; +}; + +ScanResult scan_executable(const std::filesystem::path& executable) { + std::ifstream file(executable, std::ios::binary); + if (!file) { + throw std::runtime_error("Failed to open League executable"); + } + file.seekg(0, std::ios::end); + const auto size = file.tellg(); + if (size <= 0) { + throw std::runtime_error("League executable is empty"); + } + file.seekg(0, std::ios::beg); + std::vector data(static_cast(size)); + file.read(reinterpret_cast(data.data()), size); + if (!file) { + throw std::runtime_error("Failed to read League executable"); + } + + auto macho = lol::MachO{}; + macho.parse_data_arm64(data.data(), data.size()); + const auto [text_address, text_data, text_size] = macho.find_section("__text"); + if (!text_data || text_size == 0) { + throw std::runtime_error("Failed to find ARM64 __text section"); + } + + ScanResult result; + result.wad_verify = + find_unique_wad_verify(text_data, text_data + text_size, text_address); + result.fopen_ptr = macho.find_import_ptr("_fopen"); + if (result.fopen_ptr == 0) { + throw std::runtime_error("Failed to find fopen import pointer"); + } + if (macho.find_stub_refs(result.fopen_ptr) == 0) { + throw std::runtime_error("Failed to find fopen import stub"); + } + std::tie(result.image_begin, result.image_end) = macho.image_vm_range(); + return result; +} + +void wait_for_fopen_resolution(Process& process, const ScanResult& scan) { + const auto slide = process.base(); + const auto image_begin = slide + scan.image_begin; + const auto image_end = slide + scan.image_end; + const auto fopen_ptr = slide + scan.fopen_ptr; + const auto deadline = std::chrono::steady_clock::now() + IMPORT_RESOLUTION_TIMEOUT; + + while (std::chrono::steady_clock::now() < deadline) { + if (process.exited()) { + throw std::runtime_error("League exited before the fopen import was resolved"); + } + + PtrStorage target = 0; + process.read(reinterpret_cast(fopen_ptr), &target, sizeof(target)); + if (target != 0 && (target < image_begin || target >= image_end)) { + return; + } + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + + throw std::runtime_error( + "Timed out waiting for dyld to resolve the League fopen import"); +} + +void patch_process(Process& process, const ScanResult& scan, + const std::filesystem::path& overlay) { + wait_for_fopen_resolution(process, scan); + const TaskSuspension suspension(process); + process.verify_arm64(); + + auto prefix = canonical_path(overlay).generic_string(); + if (!prefix.ends_with('/')) { + prefix.push_back('/'); + } + if (prefix.size() >= sizeof(PayloadFopenHook::prefix)) { + throw std::runtime_error("Overlay prefix path exceeds 255 bytes"); + } + if (fopen_hook_shellcode_end - fopen_hook_shellcode_beg != + static_cast(sizeof(PayloadFopenHook::fopen_hook))) { + throw std::runtime_error("ARM64 fopen hook payload has an unexpected size"); + } + if (fopen_hook_original_ptr - fopen_hook_shellcode_beg != + static_cast(offsetof(PayloadFopenHook, fopen_org_ptr)) || + fopen_hook_prefix - fopen_hook_shellcode_beg != + static_cast(offsetof(PayloadFopenHook, prefix))) { + throw std::runtime_error("ARM64 fopen hook data layout does not match its payload"); + } + std::uint32_t hook_landing_instruction = 0; + std::memcpy(&hook_landing_instruction, fopen_hook_shellcode_beg, + sizeof(hook_landing_instruction)); + if (hook_landing_instruction != BTI_C) { + throw std::runtime_error("ARM64 fopen hook is missing its BTI landing instruction"); + } + + const auto fopen_hook = process.allocate(sizeof(PayloadFopenHook)); + const auto wad_verify = reinterpret_cast(process.base() + scan.wad_verify); + const auto fopen_ptr = process.base() + scan.fopen_ptr; + PtrStorage fopen_org = 0; + process.read(reinterpret_cast(fopen_ptr), &fopen_org, sizeof(fopen_org)); + + PayloadFopenHook fopen_payload{}; + fopen_payload.fopen_org_ptr = fopen_org; + std::memcpy(fopen_payload.fopen_hook, fopen_hook_shellcode_beg, + sizeof(fopen_payload.fopen_hook)); + std::memcpy(fopen_payload.prefix, prefix.c_str(), prefix.size() + 1); + + PayloadWadVerify verify_payload{}; + const auto fopen_hook_ptr = reinterpret_cast(fopen_hook); + + process.writable(fopen_hook, sizeof(fopen_payload)); + process.write_verified(fopen_hook, &fopen_payload, sizeof(fopen_payload)); + process.executable(fopen_hook, sizeof(fopen_payload)); + + process.writable(wad_verify, sizeof(verify_payload)); + process.write_verified(wad_verify, &verify_payload, sizeof(verify_payload)); + process.executable(wad_verify, sizeof(verify_payload)); + + process.write_verified(reinterpret_cast(fopen_ptr), &fopen_hook_ptr, + sizeof(fopen_hook_ptr)); +} + +bool should_stop(void* context, StopCallback callback) { + return callback && callback(context); +} + +void emit(void* context, EventCallback callback, const char* event, std::uint32_t pid = 0, + const char* detail = "") { + if (callback) { + callback(context, event, pid, detail); + } +} + +} // namespace + +extern "C" int ltk_macos_preflight(const char* game_executable, char* error, + std::size_t error_len) { + try { + if (!game_executable || !*game_executable) { + throw std::runtime_error("Game executable path is empty"); + } + const auto executable = canonical_path(game_executable); + if (!std::filesystem::is_regular_file(executable)) { + throw std::runtime_error("Game executable does not exist"); + } + (void)scan_executable(executable); + return 0; + } catch (const std::exception& exception) { + set_error(error, error_len, exception.what()); + return -1; + } +} + +extern "C" int ltk_macos_test_find_unique_wad_verify( + const std::uint8_t* text, std::size_t text_len, std::uint64_t text_address, + std::uint64_t* result, char* error, std::size_t error_len) { + try { + if (!text || !result) { + throw std::runtime_error("Text bytes and result pointer are required"); + } + *result = find_unique_wad_verify(text, text + text_len, text_address); + return 0; + } catch (const std::exception& exception) { + set_error(error, error_len, exception.what()); + return -1; + } +} + +extern "C" int ltk_macos_test_parse_arm64_text( + const std::uint8_t* data, std::size_t data_len, std::uint64_t* address, + std::size_t* size, char* error, std::size_t error_len) { + try { + if (!data || !address || !size) { + throw std::runtime_error("Mach-O bytes, address, and size are required"); + } + auto macho = lol::MachO{}; + macho.parse_data_arm64(data, data_len); + const auto [text_address, text_data, text_size] = macho.find_section("__text"); + if (!text_data || text_size == 0) { + throw std::runtime_error("Failed to find ARM64 __text fixture section"); + } + *address = text_address; + *size = text_size; + return 0; + } catch (const std::exception& exception) { + set_error(error, error_len, exception.what()); + return -1; + } +} + +extern "C" int ltk_macos_run(const char* overlay, const char* game_executable, void* context, + EventCallback event_callback, StopCallback stop_callback, + char* error, std::size_t error_len) { + try { + if (!overlay || !game_executable) { + throw std::runtime_error("Overlay and game executable paths are required"); + } + const auto overlay_path = canonical_path(overlay); + const auto executable_path = canonical_path(game_executable); + const auto executable_string = executable_path.generic_string(); + if (!executable_string.ends_with( + "/LeagueofLegends.app/Contents/MacOS/LeagueofLegends")) { + throw std::runtime_error("Configured executable is not the macOS League game process"); + } + + const auto scan = scan_executable(executable_path); + emit(context, event_callback, "ready", 0, SIGNATURE_ID); + bool waiting_emitted = false; + + while (!should_stop(context, stop_callback)) { + const auto pid = Process::find_pid(executable_path); + if (pid == 0) { + if (!waiting_emitted) { + emit(context, event_callback, "waitingForGame"); + waiting_emitted = true; + } + for (int count = 0; count < 10 && !should_stop(context, stop_callback); ++count) { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + continue; + } + + waiting_emitted = false; + emit(context, event_callback, "gameFound", pid); + auto process = Process::open(pid); + emit(context, event_callback, "scanning", pid, SIGNATURE_ID); + patch_process(process, scan, overlay_path); + emit(context, event_callback, "patched", pid, SIGNATURE_ID); + + while (!process.exited() && !should_stop(context, stop_callback)) { + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + } + if (!should_stop(context, stop_callback)) { + emit(context, event_callback, "gameExited", pid); + } + } + return 0; + } catch (const std::exception& exception) { + set_error(error, error_len, exception.what()); + return -1; + } +} + +#else + +#include +#include +#include +#include + +namespace { +void set_error(char* buffer, std::size_t buffer_len, std::string_view message) { + if (!buffer || buffer_len == 0) { + return; + } + const auto count = std::min(buffer_len - 1, message.size()); + std::memcpy(buffer, message.data(), count); + buffer[count] = '\0'; +} +} // namespace + +extern "C" int ltk_macos_preflight(const char*, char* error, std::size_t error_len) { + set_error(error, error_len, "helper requires macOS on Apple Silicon"); + return -1; +} + +extern "C" int ltk_macos_run(const char*, const char*, void*, void (*)(void*, const char*, + unsigned int, const char*), + bool (*)(void*), char* error, std::size_t error_len) { + set_error(error, error_len, "helper requires macOS on Apple Silicon"); + return -1; +} + +#endif diff --git a/macos-patcher/src/main.rs b/macos-patcher/src/main.rs new file mode 100644 index 00000000..1a60a292 --- /dev/null +++ b/macos-patcher/src/main.rs @@ -0,0 +1,788 @@ +use serde::{Deserialize, Serialize}; +use std::ffi::{c_char, c_int, c_void, CStr, CString}; +use std::fs; +use std::io::{BufRead, BufReader, Read, Write}; +use std::os::unix::fs::{FileTypeExt, MetadataExt}; +use std::os::unix::net::UnixStream; +use std::path::{Path, PathBuf}; +use std::process::Command; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, Mutex}; + +const PROTOCOL_VERSION: u32 = 1; +const HELPER_VERSION: &str = env!("CARGO_PKG_VERSION"); +const MAX_REQUEST_BYTES: u64 = 64 * 1024; + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +struct StartRequest { + version: u32, + command: String, + token: String, + overlay: PathBuf, + allowed_root: PathBuf, + game_bundle: PathBuf, + game_executable: PathBuf, + client_uid: u32, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +struct ControlRequest { + version: u32, + command: String, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +struct Event<'a> { + version: u32, + event: &'a str, + #[serde(skip_serializing_if = "Option::is_none")] + code: Option<&'a str>, + #[serde(skip_serializing_if = "Option::is_none")] + detail: Option<&'a str>, + #[serde(skip_serializing_if = "Option::is_none")] + signature: Option<&'a str>, + #[serde(skip_serializing_if = "Option::is_none")] + architecture: Option<&'a str>, + #[serde(skip_serializing_if = "Option::is_none")] + pid: Option, + #[serde(skip_serializing_if = "Option::is_none")] + helper_version: Option<&'a str>, + #[serde(skip_serializing_if = "Option::is_none")] + token: Option<&'a str>, +} + +#[cfg(all(target_os = "macos", target_arch = "aarch64"))] +extern "C" { + fn ltk_macos_preflight( + game_executable: *const c_char, + error: *mut c_char, + error_len: usize, + ) -> c_int; + fn ltk_macos_run( + overlay: *const c_char, + game_executable: *const c_char, + context: *mut c_void, + event_callback: extern "C" fn(*mut c_void, *const c_char, u32, *const c_char), + stop_callback: extern "C" fn(*mut c_void) -> bool, + error: *mut c_char, + error_len: usize, + ) -> c_int; + #[cfg(test)] + fn ltk_macos_test_find_unique_wad_verify( + text: *const u8, + text_len: usize, + text_address: u64, + result: *mut u64, + error: *mut c_char, + error_len: usize, + ) -> c_int; + #[cfg(test)] + fn ltk_macos_test_parse_arm64_text( + data: *const u8, + data_len: usize, + address: *mut u64, + size: *mut usize, + error: *mut c_char, + error_len: usize, + ) -> c_int; +} + +struct CallbackContext { + writer: Arc>, + stop: Arc, +} + +fn main() { + if let Err(error) = run() { + eprintln!("{error}"); + std::process::exit(1); + } +} + +fn run() -> Result<(), String> { + let args: Vec = std::env::args().collect(); + match args.as_slice() { + [_, flag, executable] if flag == "--preflight" => run_preflight(Path::new(executable)), + [_, socket_flag, socket, token_flag, token] + if socket_flag == "--socket" && token_flag == "--token" => + { + run_socket(Path::new(socket), token) + } + _ => Err( + "usage: ltk-macos-patcher --preflight | --socket --token " + .into(), + ), + } +} + +fn run_preflight(executable: &Path) -> Result<(), String> { + let result = preflight(executable); + let (event, code, detail, signature) = match &result { + Ok(signature) => ("compatible", None, None, Some(signature.as_str())), + Err(error) => ( + "error", + Some("UNSUPPORTED_GAME_BUILD"), + Some(error.as_str()), + None, + ), + }; + let payload = Event { + version: PROTOCOL_VERSION, + event, + code, + detail, + signature, + architecture: Some("arm64"), + pid: None, + helper_version: Some(HELPER_VERSION), + token: None, + }; + println!( + "{}", + serde_json::to_string(&payload).map_err(|error| error.to_string())? + ); + result.map(|_| ()) +} + +fn run_socket(socket_path: &Path, expected_token: &str) -> Result<(), String> { + let socket_metadata = + fs::metadata(socket_path).map_err(|error| format!("invalid socket path: {error}"))?; + if !socket_metadata.file_type().is_socket() { + return Err("control path is not a Unix socket".into()); + } + + let stream = + UnixStream::connect(socket_path).map_err(|error| format!("connect failed: {error}"))?; + let writer = Arc::new(Mutex::new( + stream + .try_clone() + .map_err(|error| format!("socket clone failed: {error}"))?, + )); + send_event( + &writer, + Event { + version: PROTOCOL_VERSION, + event: "hello", + code: None, + detail: None, + signature: None, + architecture: Some(std::env::consts::ARCH), + pid: None, + helper_version: Some(HELPER_VERSION), + token: Some(expected_token), + }, + )?; + + let mut reader = BufReader::new(stream); + let request: StartRequest = match read_json_line(&mut reader) { + Ok(request) => request, + Err(error) => { + send_event( + &writer, + Event { + version: PROTOCOL_VERSION, + event: "error", + code: Some("HELPER_PROTOCOL_ERROR"), + detail: Some(&error), + signature: None, + architecture: Some("arm64"), + pid: None, + helper_version: Some(HELPER_VERSION), + token: None, + }, + )?; + return Err(error); + } + }; + validate_start_request(&request, expected_token, socket_metadata.uid())?; + + let stop = Arc::new(AtomicBool::new(false)); + let stop_reader = stop.clone(); + std::thread::spawn(move || loop { + match read_json_line::(&mut reader) { + Ok(request) if request.version == PROTOCOL_VERSION && request.command == "stop" => { + stop_reader.store(true, Ordering::SeqCst); + return; + } + Ok(_) => {} + Err(_) => { + stop_reader.store(true, Ordering::SeqCst); + return; + } + } + }); + + let overlay = path_to_cstring(&request.overlay)?; + let executable = path_to_cstring(&request.game_executable)?; + let mut callback_context = CallbackContext { + writer: writer.clone(), + stop, + }; + let mut error = vec![0_i8; 2048]; + + #[cfg(all(target_os = "macos", target_arch = "aarch64"))] + let result = unsafe { + ltk_macos_run( + overlay.as_ptr(), + executable.as_ptr(), + (&mut callback_context as *mut CallbackContext).cast(), + native_event_callback, + native_stop_callback, + error.as_mut_ptr(), + error.len(), + ) + }; + #[cfg(not(all(target_os = "macos", target_arch = "aarch64")))] + let result = { + write_error(&mut error, "helper requires macOS on Apple Silicon"); + -1 + }; + + if result != 0 { + let detail = c_buffer_to_string(&error); + let code = classify_native_error(&detail); + send_event( + &writer, + Event { + version: PROTOCOL_VERSION, + event: "error", + code: Some(code), + detail: Some(&detail), + signature: None, + architecture: Some("arm64"), + pid: None, + helper_version: Some(HELPER_VERSION), + token: None, + }, + )?; + return Err(detail); + } + + send_event( + &writer, + Event { + version: PROTOCOL_VERSION, + event: "stopped", + code: None, + detail: None, + signature: None, + architecture: Some("arm64"), + pid: None, + helper_version: Some(HELPER_VERSION), + token: None, + }, + ) +} + +fn validate_start_request( + request: &StartRequest, + expected_token: &str, + socket_uid: u32, +) -> Result<(), String> { + let game_executable = + validate_start_request_authorization(request, expected_token, socket_uid, unsafe { + libc::geteuid() + })?; + preflight(&game_executable)?; + Ok(()) +} + +fn validate_start_request_authorization( + request: &StartRequest, + expected_token: &str, + socket_uid: u32, + effective_uid: u32, +) -> Result { + if request.version != PROTOCOL_VERSION { + return Err(format!("unsupported protocol version {}", request.version)); + } + if request.command != "start" { + return Err("first command must be start".into()); + } + if request.token != expected_token { + return Err("authentication token mismatch".into()); + } + if request.client_uid != socket_uid { + return Err("socket owner does not match requesting user".into()); + } + if effective_uid != 0 { + return Err( + "patcher helper must be elevated, but the Tauri app must remain unprivileged".into(), + ); + } + + let allowed_root = canonical_directory(&request.allowed_root, "allowed root")?; + let overlay = canonical_directory(&request.overlay, "overlay")?; + if !overlay.starts_with(&allowed_root) { + return Err("overlay is outside the approved LTK data directory".into()); + } + if fs::metadata(&allowed_root) + .map_err(|error| format!("cannot inspect allowed root: {error}"))? + .uid() + != request.client_uid + { + return Err("approved LTK data directory is not owned by the requesting user".into()); + } + + let game_bundle = canonical_directory(&request.game_bundle, "game bundle")?; + if game_bundle.extension().and_then(|ext| ext.to_str()) != Some("app") { + return Err("game bundle is not an application bundle".into()); + } + let game_executable = fs::canonicalize(&request.game_executable) + .map_err(|error| format!("invalid game executable: {error}"))?; + if !game_executable.is_file() || !game_executable.starts_with(&game_bundle) { + return Err("game executable is outside the configured League bundle".into()); + } + if fs::metadata(&game_executable) + .map_err(|error| format!("cannot inspect game executable: {error}"))? + .uid() + != request.client_uid + { + return Err("game executable is not owned by the requesting user".into()); + } + Ok(game_executable) +} + +fn canonical_directory(path: &Path, label: &str) -> Result { + let path = fs::canonicalize(path).map_err(|error| format!("invalid {label}: {error}"))?; + if !path.is_dir() { + return Err(format!("{label} is not a directory")); + } + Ok(path) +} + +fn read_json_line Deserialize<'de>, R: BufRead>(reader: &mut R) -> Result { + let mut line = String::new(); + reader + .take(MAX_REQUEST_BYTES) + .read_line(&mut line) + .map_err(|error| format!("failed to read request: {error}"))?; + if line.is_empty() || !line.ends_with('\n') { + return Err("request is empty, oversized, or not newline terminated".into()); + } + serde_json::from_str(&line).map_err(|error| format!("malformed request: {error}")) +} + +fn preflight(executable: &Path) -> Result { + let executable = fs::canonicalize(executable) + .map_err(|error| format!("invalid game executable: {error}"))?; + validate_riot_code_signature(&executable)?; + let executable = path_to_cstring(&executable)?; + let mut error = vec![0_i8; 2048]; + + #[cfg(all(target_os = "macos", target_arch = "aarch64"))] + let result = + unsafe { ltk_macos_preflight(executable.as_ptr(), error.as_mut_ptr(), error.len()) }; + #[cfg(not(all(target_os = "macos", target_arch = "aarch64")))] + let result = { + write_error(&mut error, "helper requires macOS on Apple Silicon"); + -1 + }; + + if result == 0 { + Ok("mac-arm64-pattern-v1".into()) + } else { + Err(c_buffer_to_string(&error)) + } +} + +fn validate_riot_code_signature(executable: &Path) -> Result<(), String> { + #[cfg(target_os = "macos")] + { + let verification = Command::new("/usr/bin/codesign") + .args(["--verify", "--strict", "--ignore-resources", "--verbose=1"]) + .arg(executable) + .output() + .map_err(|error| format!("failed to verify League code signature: {error}"))?; + if !verification.status.success() { + return Err(format!( + "League executable code signature is invalid: {}", + String::from_utf8_lossy(&verification.stderr).trim() + )); + } + + let details = Command::new("/usr/bin/codesign") + .args(["-dv", "--verbose=4"]) + .arg(executable) + .output() + .map_err(|error| format!("failed to inspect League code signature: {error}"))?; + let details = String::from_utf8_lossy(&details.stderr); + if !details.contains("Identifier=com.riotgames.LeagueofLegends.GameClient") + || !details.contains("TeamIdentifier=K832E2UXV7") + { + return Err("League executable is not signed with the expected Riot identity".into()); + } + Ok(()) + } + #[cfg(not(target_os = "macos"))] + { + let _ = executable; + Err("helper requires macOS on Apple Silicon".into()) + } +} + +fn path_to_cstring(path: &Path) -> Result { + CString::new(path.as_os_str().as_encoded_bytes()) + .map_err(|_| format!("path contains a NUL byte: {}", path.display())) +} + +fn c_buffer_to_string(buffer: &[c_char]) -> String { + unsafe { CStr::from_ptr(buffer.as_ptr()) } + .to_string_lossy() + .into_owned() +} + +fn classify_native_error(detail: &str) -> &'static str { + if detail.contains("task_for_pid") { + "PROCESS_ACCESS_DENIED" + } else if detail.contains("ARM64") || detail.contains("architecture") { + "UNSUPPORTED_ARCHITECTURE" + } else if detail.contains("signature") + || detail.contains("wad_verify") + || detail.contains("fopen") + { + "UNSUPPORTED_GAME_BUILD" + } else { + "PATCHER_HELPER_FAILED" + } +} + +fn send_event(writer: &Arc>, event: Event<'_>) -> Result<(), String> { + let mut writer = writer + .lock() + .map_err(|_| "event writer lock is poisoned".to_string())?; + serde_json::to_writer(&mut *writer, &event).map_err(|error| error.to_string())?; + writer + .write_all(b"\n") + .and_then(|_| writer.flush()) + .map_err(|error| format!("event write failed: {error}")) +} + +extern "C" fn native_event_callback( + context: *mut c_void, + event: *const c_char, + pid: u32, + detail: *const c_char, +) { + if context.is_null() || event.is_null() { + return; + } + let context = unsafe { &*(context.cast::()) }; + let event = unsafe { CStr::from_ptr(event) }.to_string_lossy(); + let detail = if detail.is_null() { + None + } else { + Some(unsafe { CStr::from_ptr(detail) }.to_string_lossy()) + }; + let _ = send_event( + &context.writer, + Event { + version: PROTOCOL_VERSION, + event: &event, + code: None, + detail: detail.as_deref(), + signature: (event == "patched").then_some("mac-arm64-pattern-v1"), + architecture: (event == "gameFound").then_some("arm64"), + pid: (pid != 0).then_some(pid), + helper_version: Some(HELPER_VERSION), + token: None, + }, + ); +} + +extern "C" fn native_stop_callback(context: *mut c_void) -> bool { + if context.is_null() { + return true; + } + unsafe { &*(context.cast::()) } + .stop + .load(Ordering::SeqCst) +} + +#[cfg(not(all(target_os = "macos", target_arch = "aarch64")))] +fn write_error(buffer: &mut [c_char], message: &str) { + let bytes = message.as_bytes(); + let count = bytes.len().min(buffer.len().saturating_sub(1)); + for (target, source) in buffer.iter_mut().zip(bytes.iter()).take(count) { + *target = *source as c_char; + } + if let Some(terminator) = buffer.get_mut(count) { + *terminator = 0; + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::io::Cursor; + use std::os::unix::fs::{symlink, MetadataExt}; + + fn request_fixture(root: &Path) -> StartRequest { + let allowed_root = root.join("data"); + let overlay = allowed_root.join("overlay"); + let game_bundle = root.join("League of Legends.app"); + let game_executable = game_bundle + .join("Contents/LoL/Game/LeagueofLegends.app/Contents/MacOS/LeagueofLegends"); + fs::create_dir_all(&overlay).unwrap(); + fs::create_dir_all(game_executable.parent().unwrap()).unwrap(); + fs::write(&game_executable, b"fixture").unwrap(); + let client_uid = fs::metadata(&allowed_root).unwrap().uid(); + StartRequest { + version: PROTOCOL_VERSION, + command: "start".into(), + token: "secret".into(), + overlay, + allowed_root, + game_bundle, + game_executable, + client_uid, + } + } + + fn append_u32(bytes: &mut Vec, value: u32) { + bytes.extend_from_slice(&value.to_le_bytes()); + } + + fn append_u64(bytes: &mut Vec, value: u64) { + bytes.extend_from_slice(&value.to_le_bytes()); + } + + fn append_name(bytes: &mut Vec, value: &str) { + let mut name = [0_u8; 16]; + name[..value.len()].copy_from_slice(value.as_bytes()); + bytes.extend_from_slice(&name); + } + + fn thin_arm64_macho() -> Vec { + const HEADER_SIZE: u32 = 32; + const SEGMENT_COMMAND_SIZE: u32 = 8 + 64 + 80; + const TEXT_SIZE: u64 = 16; + let text_offset = HEADER_SIZE + SEGMENT_COMMAND_SIZE; + let mut bytes = Vec::new(); + + append_u32(&mut bytes, 0xFEED_FACF); + append_u32(&mut bytes, 0x0100_000C); + append_u32(&mut bytes, 0); + append_u32(&mut bytes, 2); + append_u32(&mut bytes, 1); + append_u32(&mut bytes, SEGMENT_COMMAND_SIZE); + append_u32(&mut bytes, 0); + append_u32(&mut bytes, 0); + + append_u32(&mut bytes, 0x19); + append_u32(&mut bytes, SEGMENT_COMMAND_SIZE); + append_name(&mut bytes, "__TEXT"); + append_u64(&mut bytes, 0x1_0000_0000); + append_u64(&mut bytes, 0x1000); + append_u64(&mut bytes, 0); + append_u64(&mut bytes, u64::from(text_offset) + TEXT_SIZE); + append_u32(&mut bytes, 5); + append_u32(&mut bytes, 5); + append_u32(&mut bytes, 1); + append_u32(&mut bytes, 0); + + append_name(&mut bytes, "__text"); + append_name(&mut bytes, "__TEXT"); + append_u64(&mut bytes, 0x1_0000_1000); + append_u64(&mut bytes, TEXT_SIZE); + append_u32(&mut bytes, text_offset); + append_u32(&mut bytes, 2); + append_u32(&mut bytes, 0); + append_u32(&mut bytes, 0); + append_u32(&mut bytes, 0); + append_u32(&mut bytes, 0); + append_u32(&mut bytes, 0); + append_u32(&mut bytes, 0); + + bytes.extend_from_slice(&[0xAA; TEXT_SIZE as usize]); + bytes + } + + fn universal_with_arm64_slice(thin: &[u8]) -> Vec { + const FAT_HEADER_SIZE: usize = 8 + 20; + let mut bytes = Vec::new(); + bytes.extend_from_slice(&0xCAFE_BABE_u32.to_be_bytes()); + bytes.extend_from_slice(&1_u32.to_be_bytes()); + bytes.extend_from_slice(&0x0100_000C_u32.to_be_bytes()); + bytes.extend_from_slice(&0_u32.to_be_bytes()); + bytes.extend_from_slice(&(FAT_HEADER_SIZE as u32).to_be_bytes()); + bytes.extend_from_slice(&(thin.len() as u32).to_be_bytes()); + bytes.extend_from_slice(&0_u32.to_be_bytes()); + bytes.extend_from_slice(thin); + bytes + } + + #[test] + fn classifies_native_failures() { + assert_eq!( + classify_native_error("task_for_pid failed"), + "PROCESS_ACCESS_DENIED" + ); + assert_eq!( + classify_native_error("wad_verify signature not unique"), + "UNSUPPORTED_GAME_BUILD" + ); + } + + #[test] + fn authorization_accepts_owned_paths_inside_approved_roots() { + let directory = tempfile::tempdir().unwrap(); + let request = request_fixture(directory.path()); + let executable = + validate_start_request_authorization(&request, "secret", request.client_uid, 0) + .unwrap(); + assert_eq!( + executable, + fs::canonicalize(&request.game_executable).unwrap() + ); + } + + #[test] + fn authorization_rejects_token_uid_and_non_root_helper() { + let directory = tempfile::tempdir().unwrap(); + let request = request_fixture(directory.path()); + assert!( + validate_start_request_authorization(&request, "wrong", request.client_uid, 0) + .unwrap_err() + .contains("token") + ); + assert!(validate_start_request_authorization( + &request, + "secret", + request.client_uid + 1, + 0 + ) + .unwrap_err() + .contains("socket owner")); + assert!(validate_start_request_authorization( + &request, + "secret", + request.client_uid, + request.client_uid.max(1) + ) + .unwrap_err() + .contains("must be elevated")); + } + + #[test] + fn authorization_rejects_overlay_symlink_escape() { + let directory = tempfile::tempdir().unwrap(); + let request = request_fixture(directory.path()); + let outside = directory.path().join("outside"); + fs::create_dir(&outside).unwrap(); + fs::remove_dir(&request.overlay).unwrap(); + symlink(&outside, &request.overlay).unwrap(); + assert!( + validate_start_request_authorization(&request, "secret", request.client_uid, 0) + .unwrap_err() + .contains("outside") + ); + } + + #[test] + fn authorization_rejects_executable_outside_bundle() { + let directory = tempfile::tempdir().unwrap(); + let mut request = request_fixture(directory.path()); + request.game_executable = directory.path().join("LeagueofLegends"); + fs::write(&request.game_executable, b"fixture").unwrap(); + assert!( + validate_start_request_authorization(&request, "secret", request.client_uid, 0) + .unwrap_err() + .contains("outside the configured League bundle") + ); + } + + #[test] + fn protocol_rejects_malformed_and_oversized_requests() { + let mut malformed = Cursor::new(b"{not-json}\n".to_vec()); + assert!(read_json_line::(&mut malformed) + .unwrap_err() + .contains("malformed")); + + let mut oversized = Cursor::new(vec![b'x'; MAX_REQUEST_BYTES as usize + 1]); + assert!(read_json_line::(&mut oversized) + .unwrap_err() + .contains("oversized")); + } + + #[test] + fn stop_callback_observes_cancellation() { + let (stream, _peer) = UnixStream::pair().unwrap(); + let context = CallbackContext { + writer: Arc::new(Mutex::new(stream)), + stop: Arc::new(AtomicBool::new(false)), + }; + assert!(!native_stop_callback( + (&context as *const CallbackContext).cast_mut().cast() + )); + context.stop.store(true, Ordering::SeqCst); + assert!(native_stop_callback( + (&context as *const CallbackContext).cast_mut().cast() + )); + } + + #[cfg(all(target_os = "macos", target_arch = "aarch64"))] + #[test] + fn native_signature_matcher_requires_exactly_one_valid_match() { + const PATTERN: [u8; 8] = [0xC3, 0x24, 0x80, 0x52, 0x04, 0x20, 0x80, 0x52]; + let mut text = [0_u8; 32]; + text[4..12].copy_from_slice(&PATTERN); + text[12..16].copy_from_slice(&0x14000002_u32.to_le_bytes()); + let mut result = 0_u64; + let mut error = vec![0_i8; 256]; + let status = unsafe { + ltk_macos_test_find_unique_wad_verify( + text.as_ptr(), + text.len(), + 0x1000, + &mut result, + error.as_mut_ptr(), + error.len(), + ) + }; + assert_eq!(status, 0); + assert_eq!(result, 0x1014); + + text[20..28].copy_from_slice(&PATTERN); + let status = unsafe { + ltk_macos_test_find_unique_wad_verify( + text.as_ptr(), + text.len(), + 0x1000, + &mut result, + error.as_mut_ptr(), + error.len(), + ) + }; + assert_ne!(status, 0); + assert!(c_buffer_to_string(&error).contains("exactly one match")); + } + + #[cfg(all(target_os = "macos", target_arch = "aarch64"))] + #[test] + fn native_macho_parser_selects_thin_and_universal_arm64_text() { + let thin = thin_arm64_macho(); + let universal = universal_with_arm64_slice(&thin); + for fixture in [&thin, &universal] { + let mut address = 0_u64; + let mut size = 0_usize; + let mut error = vec![0_i8; 256]; + let status = unsafe { + ltk_macos_test_parse_arm64_text( + fixture.as_ptr(), + fixture.len(), + &mut address, + &mut size, + error.as_mut_ptr(), + error.len(), + ) + }; + assert_eq!(status, 0, "{}", c_buffer_to_string(&error)); + assert_eq!(address, 0x1_0000_1000); + assert_eq!(size, 16); + } + } +} diff --git a/package.json b/package.json index 05ad48f0..6658784e 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,10 @@ "build": "tsc && vite build", "preview": "vite preview", "tauri": "tauri", + "macos:helper": "bash scripts/build-macos-patcher.sh debug", + "macos:dev": "pnpm macos:helper && pnpm tauri dev --target aarch64-apple-darwin", + "macos:build": "bash scripts/build-macos-patcher.sh release && pnpm tauri build --target aarch64-apple-darwin --bundles app && bash scripts/sign-macos-app.sh", + "macos:preflight": "bash scripts/macos-preflight.sh", "typecheck": "tsc --noEmit", "lint": "eslint .", "lint:fix": "eslint . --fix", diff --git a/scripts/build-macos-patcher.sh b/scripts/build-macos-patcher.sh new file mode 100755 index 00000000..974f0dd6 --- /dev/null +++ b/scripts/build-macos-patcher.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ "$(uname -s)" != "Darwin" || "$(uname -m)" != "arm64" ]]; then + echo "The macOS patcher can only be built on Apple Silicon." >&2 + exit 1 +fi + +profile="${1:-debug}" +case "$profile" in + debug) + target_profile="debug" + cargo build -p ltk-macos-patcher --target "aarch64-apple-darwin" + ;; + release) + target_profile="release" + cargo build -p ltk-macos-patcher --target "aarch64-apple-darwin" --release + ;; + *) + echo "Usage: $0 [debug|release]" >&2 + exit 1 + ;; +esac + +target="aarch64-apple-darwin" + +source_path="target/$target/$target_profile/ltk-macos-patcher" +destination="src-tauri/binaries/ltk-macos-patcher-$target" +mkdir -p "$(dirname "$destination")" +install -m 0755 "$source_path" "$destination" + +# An ad-hoc signature keeps local app and helper builds deterministic without +# introducing Developer ID or notarization requirements. +/usr/bin/codesign --force --sign - --timestamp=none "$destination" + +echo "Built $destination" diff --git a/scripts/macos-preflight.sh b/scripts/macos-preflight.sh new file mode 100755 index 00000000..6777a1a2 --- /dev/null +++ b/scripts/macos-preflight.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ "${1:-}" == "--" ]]; then + shift +fi + +if [[ $# -ne 1 ]]; then + echo "Usage: pnpm macos:preflight -- '/Applications/League of Legends.app'" >&2 + exit 1 +fi + +bundle="$1" +executable="$bundle/Contents/LoL/Game/LeagueofLegends.app/Contents/MacOS/LeagueofLegends" +helper="src-tauri/binaries/ltk-macos-patcher-aarch64-apple-darwin" + +if [[ ! -x "$helper" ]]; then + bash scripts/build-macos-patcher.sh debug +fi + +"$helper" --preflight "$executable" diff --git a/scripts/sign-macos-app.sh b/scripts/sign-macos-app.sh new file mode 100755 index 00000000..f76c354a --- /dev/null +++ b/scripts/sign-macos-app.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +set -euo pipefail + +app="target/aarch64-apple-darwin/release/bundle/macos/LTK Manager.app" +helper="$app/Contents/MacOS/ltk-macos-patcher" + +if [[ ! -d "$app" || ! -x "$helper" ]]; then + echo "Build the ARM64 app bundle before signing: pnpm macos:build" >&2 + exit 1 +fi + +/usr/bin/codesign --force --sign - --timestamp=none "$helper" +/usr/bin/codesign --force --sign - --timestamp=none "$app" +/usr/bin/codesign --verify --deep --strict --verbose=2 "$app" + +echo "Signed $app" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 187dcbb1..19e39847 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -39,11 +39,10 @@ semver = "1.0" toml = "0.8" tokio = { version = "1", features = ["full"] } -libloading = "0.9.0" - serde = { version = "1", features = ["derive"] } serde_json = "1" regex = "1" +libc = "0.2" ts-rs = { version = "12", features = ["chrono-impl", "serde-json-impl"] } @@ -76,6 +75,7 @@ tracing-appender = "0.2" tempfile = "3" [target.'cfg(windows)'.dependencies] +libloading = "0.9.0" windows-sys = { version = "0.59", features = [ "Win32_Foundation", "Win32_Security", diff --git a/src-tauri/binaries/.gitkeep b/src-tauri/binaries/.gitkeep new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/src-tauri/binaries/.gitkeep @@ -0,0 +1 @@ + diff --git a/src-tauri/gen/schemas/macOS-schema.json b/src-tauri/gen/schemas/macOS-schema.json new file mode 100644 index 00000000..cbaa7825 --- /dev/null +++ b/src-tauri/gen/schemas/macOS-schema.json @@ -0,0 +1,6542 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CapabilityFile", + "description": "Capability formats accepted in a capability file.", + "anyOf": [ + { + "description": "A single capability.", + "allOf": [ + { + "$ref": "#/definitions/Capability" + } + ] + }, + { + "description": "A list of capabilities.", + "type": "array", + "items": { + "$ref": "#/definitions/Capability" + } + }, + { + "description": "A list of capabilities.", + "type": "object", + "required": [ + "capabilities" + ], + "properties": { + "capabilities": { + "description": "The list of capabilities.", + "type": "array", + "items": { + "$ref": "#/definitions/Capability" + } + } + } + } + ], + "definitions": { + "Capability": { + "description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows' and webviews' fine grained access to the Tauri core, application, or plugin commands. If a webview or its window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programmatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, ], \"platforms\": [\"macOS\",\"windows\"] } ```", + "type": "object", + "required": [ + "identifier", + "permissions" + ], + "properties": { + "identifier": { + "description": "Identifier of the capability.\n\n## Example\n\n`main-user-files-write`", + "type": "string" + }, + "description": { + "description": "Description of what the capability is intended to allow on associated windows.\n\nIt should contain a description of what the grouped permissions should allow.\n\n## Example\n\nThis capability allows the `main` window access to `filesystem` write related commands and `dialog` commands to enable programmatic access to files selected by the user.", + "default": "", + "type": "string" + }, + "remote": { + "description": "Configure remote URLs that can use the capability permissions.\n\nThis setting is optional and defaults to not being set, as our default use case is that the content is served from our local application.\n\n:::caution Make sure you understand the security implications of providing remote sources with local system access. :::\n\n## Example\n\n```json { \"urls\": [\"https://*.mydomain.dev\"] } ```", + "anyOf": [ + { + "$ref": "#/definitions/CapabilityRemote" + }, + { + "type": "null" + } + ] + }, + "local": { + "description": "Whether this capability is enabled for local app URLs or not. Defaults to `true`.", + "default": true, + "type": "boolean" + }, + "windows": { + "description": "List of windows that are affected by this capability. Can be a glob pattern.\n\nIf a window label matches any of the patterns in this list, the capability will be enabled on all the webviews of that window, regardless of the value of [`Self::webviews`].\n\nOn multiwebview windows, prefer specifying [`Self::webviews`] and omitting [`Self::windows`] for a fine grained access control.\n\n## Example\n\n`[\"main\"]`", + "type": "array", + "items": { + "type": "string" + } + }, + "webviews": { + "description": "List of webviews that are affected by this capability. Can be a glob pattern.\n\nThe capability will be enabled on all the webviews whose label matches any of the patterns in this list, regardless of whether the webview's window label matches a pattern in [`Self::windows`].\n\n## Example\n\n`[\"sub-webview-one\", \"sub-webview-two\"]`", + "type": "array", + "items": { + "type": "string" + } + }, + "permissions": { + "description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ] ```", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionEntry" + }, + "uniqueItems": true + }, + "platforms": { + "description": "Limit which target platforms this capability applies to.\n\nBy default all platforms are targeted.\n\n## Example\n\n`[\"macOS\",\"windows\"]`", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Target" + } + } + } + }, + "CapabilityRemote": { + "description": "Configuration for remote URLs that are associated with the capability.", + "type": "object", + "required": [ + "urls" + ], + "properties": { + "urls": { + "description": "Remote domains this capability refers to using the [URLPattern standard](https://urlpattern.spec.whatwg.org/).\n\n## Examples\n\n- \"https://*.mydomain.dev\": allows subdomains of mydomain.dev - \"https://mydomain.dev/api/*\": allows any subpath of mydomain.dev/api", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "PermissionEntry": { + "description": "An entry for a permission value in a [`Capability`] can be either a raw permission [`Identifier`] or an object that references a permission and extends its scope.", + "anyOf": [ + { + "description": "Reference a permission or permission set by identifier.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + }, + { + "description": "Reference a permission or permission set by identifier and extends its scope.", + "type": "object", + "allOf": [ + { + "if": { + "properties": { + "identifier": { + "anyOf": [ + { + "description": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n#### This default permission set includes:\n\n- `create-app-specific-dirs`\n- `read-app-specific-dirs-recursive`\n- `deny-default`", + "type": "string", + "const": "fs:default", + "markdownDescription": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n#### This default permission set includes:\n\n- `create-app-specific-dirs`\n- `read-app-specific-dirs-recursive`\n- `deny-default`" + }, + { + "description": "This allows non-recursive read access to metadata of the application folders, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-app-index`", + "type": "string", + "const": "fs:allow-app-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the application folders, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-app-index`" + }, + { + "description": "This allows full recursive read access to metadata of the application folders, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-app-recursive`", + "type": "string", + "const": "fs:allow-app-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the application folders, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-app-recursive`" + }, + { + "description": "This allows non-recursive read access to the application folders.\n#### This permission set includes:\n\n- `read-all`\n- `scope-app`", + "type": "string", + "const": "fs:allow-app-read", + "markdownDescription": "This allows non-recursive read access to the application folders.\n#### This permission set includes:\n\n- `read-all`\n- `scope-app`" + }, + { + "description": "This allows full recursive read access to the complete application folders, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-app-recursive`", + "type": "string", + "const": "fs:allow-app-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete application folders, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-app-recursive`" + }, + { + "description": "This allows non-recursive write access to the application folders.\n#### This permission set includes:\n\n- `write-all`\n- `scope-app`", + "type": "string", + "const": "fs:allow-app-write", + "markdownDescription": "This allows non-recursive write access to the application folders.\n#### This permission set includes:\n\n- `write-all`\n- `scope-app`" + }, + { + "description": "This allows full recursive write access to the complete application folders, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-app-recursive`", + "type": "string", + "const": "fs:allow-app-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete application folders, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-app-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appcache-index`", + "type": "string", + "const": "fs:allow-appcache-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appcache-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appcache-recursive`", + "type": "string", + "const": "fs:allow-appcache-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appcache-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPCACHE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appcache`", + "type": "string", + "const": "fs:allow-appcache-read", + "markdownDescription": "This allows non-recursive read access to the `$APPCACHE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appcache`" + }, + { + "description": "This allows full recursive read access to the complete `$APPCACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appcache-recursive`", + "type": "string", + "const": "fs:allow-appcache-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPCACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appcache-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPCACHE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appcache`", + "type": "string", + "const": "fs:allow-appcache-write", + "markdownDescription": "This allows non-recursive write access to the `$APPCACHE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appcache`" + }, + { + "description": "This allows full recursive write access to the complete `$APPCACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appcache-recursive`", + "type": "string", + "const": "fs:allow-appcache-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPCACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appcache-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appconfig-index`", + "type": "string", + "const": "fs:allow-appconfig-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appconfig-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appconfig-recursive`", + "type": "string", + "const": "fs:allow-appconfig-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appconfig-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPCONFIG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appconfig`", + "type": "string", + "const": "fs:allow-appconfig-read", + "markdownDescription": "This allows non-recursive read access to the `$APPCONFIG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appconfig`" + }, + { + "description": "This allows full recursive read access to the complete `$APPCONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appconfig-recursive`", + "type": "string", + "const": "fs:allow-appconfig-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPCONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appconfig-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPCONFIG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appconfig`", + "type": "string", + "const": "fs:allow-appconfig-write", + "markdownDescription": "This allows non-recursive write access to the `$APPCONFIG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appconfig`" + }, + { + "description": "This allows full recursive write access to the complete `$APPCONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appconfig-recursive`", + "type": "string", + "const": "fs:allow-appconfig-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPCONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appconfig-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appdata-index`", + "type": "string", + "const": "fs:allow-appdata-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appdata-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appdata-recursive`", + "type": "string", + "const": "fs:allow-appdata-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appdata-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appdata`", + "type": "string", + "const": "fs:allow-appdata-read", + "markdownDescription": "This allows non-recursive read access to the `$APPDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appdata`" + }, + { + "description": "This allows full recursive read access to the complete `$APPDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appdata-recursive`", + "type": "string", + "const": "fs:allow-appdata-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appdata-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appdata`", + "type": "string", + "const": "fs:allow-appdata-write", + "markdownDescription": "This allows non-recursive write access to the `$APPDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appdata`" + }, + { + "description": "This allows full recursive write access to the complete `$APPDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appdata-recursive`", + "type": "string", + "const": "fs:allow-appdata-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appdata-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applocaldata-index`", + "type": "string", + "const": "fs:allow-applocaldata-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applocaldata-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applocaldata-recursive`", + "type": "string", + "const": "fs:allow-applocaldata-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applocaldata-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPLOCALDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applocaldata`", + "type": "string", + "const": "fs:allow-applocaldata-read", + "markdownDescription": "This allows non-recursive read access to the `$APPLOCALDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applocaldata`" + }, + { + "description": "This allows full recursive read access to the complete `$APPLOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applocaldata-recursive`", + "type": "string", + "const": "fs:allow-applocaldata-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPLOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applocaldata-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPLOCALDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applocaldata`", + "type": "string", + "const": "fs:allow-applocaldata-write", + "markdownDescription": "This allows non-recursive write access to the `$APPLOCALDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applocaldata`" + }, + { + "description": "This allows full recursive write access to the complete `$APPLOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applocaldata-recursive`", + "type": "string", + "const": "fs:allow-applocaldata-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPLOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applocaldata-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applog-index`", + "type": "string", + "const": "fs:allow-applog-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applog-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applog-recursive`", + "type": "string", + "const": "fs:allow-applog-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applog-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPLOG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applog`", + "type": "string", + "const": "fs:allow-applog-read", + "markdownDescription": "This allows non-recursive read access to the `$APPLOG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applog`" + }, + { + "description": "This allows full recursive read access to the complete `$APPLOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applog-recursive`", + "type": "string", + "const": "fs:allow-applog-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPLOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applog-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPLOG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applog`", + "type": "string", + "const": "fs:allow-applog-write", + "markdownDescription": "This allows non-recursive write access to the `$APPLOG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applog`" + }, + { + "description": "This allows full recursive write access to the complete `$APPLOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applog-recursive`", + "type": "string", + "const": "fs:allow-applog-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPLOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applog-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-audio-index`", + "type": "string", + "const": "fs:allow-audio-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-audio-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-audio-recursive`", + "type": "string", + "const": "fs:allow-audio-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-audio-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$AUDIO` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-audio`", + "type": "string", + "const": "fs:allow-audio-read", + "markdownDescription": "This allows non-recursive read access to the `$AUDIO` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-audio`" + }, + { + "description": "This allows full recursive read access to the complete `$AUDIO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-audio-recursive`", + "type": "string", + "const": "fs:allow-audio-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$AUDIO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-audio-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$AUDIO` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-audio`", + "type": "string", + "const": "fs:allow-audio-write", + "markdownDescription": "This allows non-recursive write access to the `$AUDIO` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-audio`" + }, + { + "description": "This allows full recursive write access to the complete `$AUDIO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-audio-recursive`", + "type": "string", + "const": "fs:allow-audio-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$AUDIO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-audio-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-cache-index`", + "type": "string", + "const": "fs:allow-cache-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-cache-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-cache-recursive`", + "type": "string", + "const": "fs:allow-cache-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-cache-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$CACHE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-cache`", + "type": "string", + "const": "fs:allow-cache-read", + "markdownDescription": "This allows non-recursive read access to the `$CACHE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-cache`" + }, + { + "description": "This allows full recursive read access to the complete `$CACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-cache-recursive`", + "type": "string", + "const": "fs:allow-cache-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$CACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-cache-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$CACHE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-cache`", + "type": "string", + "const": "fs:allow-cache-write", + "markdownDescription": "This allows non-recursive write access to the `$CACHE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-cache`" + }, + { + "description": "This allows full recursive write access to the complete `$CACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-cache-recursive`", + "type": "string", + "const": "fs:allow-cache-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$CACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-cache-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-config-index`", + "type": "string", + "const": "fs:allow-config-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-config-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-config-recursive`", + "type": "string", + "const": "fs:allow-config-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-config-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$CONFIG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-config`", + "type": "string", + "const": "fs:allow-config-read", + "markdownDescription": "This allows non-recursive read access to the `$CONFIG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-config`" + }, + { + "description": "This allows full recursive read access to the complete `$CONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-config-recursive`", + "type": "string", + "const": "fs:allow-config-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$CONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-config-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$CONFIG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-config`", + "type": "string", + "const": "fs:allow-config-write", + "markdownDescription": "This allows non-recursive write access to the `$CONFIG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-config`" + }, + { + "description": "This allows full recursive write access to the complete `$CONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-config-recursive`", + "type": "string", + "const": "fs:allow-config-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$CONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-config-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$DATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-data-index`", + "type": "string", + "const": "fs:allow-data-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$DATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-data-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$DATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-data-recursive`", + "type": "string", + "const": "fs:allow-data-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$DATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-data-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$DATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-data`", + "type": "string", + "const": "fs:allow-data-read", + "markdownDescription": "This allows non-recursive read access to the `$DATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-data`" + }, + { + "description": "This allows full recursive read access to the complete `$DATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-data-recursive`", + "type": "string", + "const": "fs:allow-data-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$DATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-data-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$DATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-data`", + "type": "string", + "const": "fs:allow-data-write", + "markdownDescription": "This allows non-recursive write access to the `$DATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-data`" + }, + { + "description": "This allows full recursive write access to the complete `$DATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-data-recursive`", + "type": "string", + "const": "fs:allow-data-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$DATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-data-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-desktop-index`", + "type": "string", + "const": "fs:allow-desktop-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-desktop-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-desktop-recursive`", + "type": "string", + "const": "fs:allow-desktop-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-desktop-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$DESKTOP` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-desktop`", + "type": "string", + "const": "fs:allow-desktop-read", + "markdownDescription": "This allows non-recursive read access to the `$DESKTOP` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-desktop`" + }, + { + "description": "This allows full recursive read access to the complete `$DESKTOP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-desktop-recursive`", + "type": "string", + "const": "fs:allow-desktop-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$DESKTOP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-desktop-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$DESKTOP` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-desktop`", + "type": "string", + "const": "fs:allow-desktop-write", + "markdownDescription": "This allows non-recursive write access to the `$DESKTOP` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-desktop`" + }, + { + "description": "This allows full recursive write access to the complete `$DESKTOP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-desktop-recursive`", + "type": "string", + "const": "fs:allow-desktop-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$DESKTOP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-desktop-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-document-index`", + "type": "string", + "const": "fs:allow-document-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-document-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-document-recursive`", + "type": "string", + "const": "fs:allow-document-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-document-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$DOCUMENT` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-document`", + "type": "string", + "const": "fs:allow-document-read", + "markdownDescription": "This allows non-recursive read access to the `$DOCUMENT` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-document`" + }, + { + "description": "This allows full recursive read access to the complete `$DOCUMENT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-document-recursive`", + "type": "string", + "const": "fs:allow-document-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$DOCUMENT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-document-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$DOCUMENT` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-document`", + "type": "string", + "const": "fs:allow-document-write", + "markdownDescription": "This allows non-recursive write access to the `$DOCUMENT` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-document`" + }, + { + "description": "This allows full recursive write access to the complete `$DOCUMENT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-document-recursive`", + "type": "string", + "const": "fs:allow-document-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$DOCUMENT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-document-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-download-index`", + "type": "string", + "const": "fs:allow-download-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-download-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-download-recursive`", + "type": "string", + "const": "fs:allow-download-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-download-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$DOWNLOAD` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-download`", + "type": "string", + "const": "fs:allow-download-read", + "markdownDescription": "This allows non-recursive read access to the `$DOWNLOAD` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-download`" + }, + { + "description": "This allows full recursive read access to the complete `$DOWNLOAD` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-download-recursive`", + "type": "string", + "const": "fs:allow-download-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$DOWNLOAD` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-download-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$DOWNLOAD` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-download`", + "type": "string", + "const": "fs:allow-download-write", + "markdownDescription": "This allows non-recursive write access to the `$DOWNLOAD` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-download`" + }, + { + "description": "This allows full recursive write access to the complete `$DOWNLOAD` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-download-recursive`", + "type": "string", + "const": "fs:allow-download-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$DOWNLOAD` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-download-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$EXE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-exe-index`", + "type": "string", + "const": "fs:allow-exe-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$EXE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-exe-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$EXE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-exe-recursive`", + "type": "string", + "const": "fs:allow-exe-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$EXE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-exe-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$EXE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-exe`", + "type": "string", + "const": "fs:allow-exe-read", + "markdownDescription": "This allows non-recursive read access to the `$EXE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-exe`" + }, + { + "description": "This allows full recursive read access to the complete `$EXE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-exe-recursive`", + "type": "string", + "const": "fs:allow-exe-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$EXE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-exe-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$EXE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-exe`", + "type": "string", + "const": "fs:allow-exe-write", + "markdownDescription": "This allows non-recursive write access to the `$EXE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-exe`" + }, + { + "description": "This allows full recursive write access to the complete `$EXE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-exe-recursive`", + "type": "string", + "const": "fs:allow-exe-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$EXE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-exe-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$FONT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-font-index`", + "type": "string", + "const": "fs:allow-font-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$FONT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-font-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$FONT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-font-recursive`", + "type": "string", + "const": "fs:allow-font-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$FONT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-font-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$FONT` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-font`", + "type": "string", + "const": "fs:allow-font-read", + "markdownDescription": "This allows non-recursive read access to the `$FONT` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-font`" + }, + { + "description": "This allows full recursive read access to the complete `$FONT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-font-recursive`", + "type": "string", + "const": "fs:allow-font-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$FONT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-font-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$FONT` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-font`", + "type": "string", + "const": "fs:allow-font-write", + "markdownDescription": "This allows non-recursive write access to the `$FONT` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-font`" + }, + { + "description": "This allows full recursive write access to the complete `$FONT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-font-recursive`", + "type": "string", + "const": "fs:allow-font-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$FONT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-font-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$HOME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-home-index`", + "type": "string", + "const": "fs:allow-home-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$HOME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-home-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$HOME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-home-recursive`", + "type": "string", + "const": "fs:allow-home-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$HOME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-home-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$HOME` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-home`", + "type": "string", + "const": "fs:allow-home-read", + "markdownDescription": "This allows non-recursive read access to the `$HOME` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-home`" + }, + { + "description": "This allows full recursive read access to the complete `$HOME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-home-recursive`", + "type": "string", + "const": "fs:allow-home-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$HOME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-home-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$HOME` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-home`", + "type": "string", + "const": "fs:allow-home-write", + "markdownDescription": "This allows non-recursive write access to the `$HOME` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-home`" + }, + { + "description": "This allows full recursive write access to the complete `$HOME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-home-recursive`", + "type": "string", + "const": "fs:allow-home-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$HOME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-home-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-localdata-index`", + "type": "string", + "const": "fs:allow-localdata-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-localdata-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-localdata-recursive`", + "type": "string", + "const": "fs:allow-localdata-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-localdata-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$LOCALDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-localdata`", + "type": "string", + "const": "fs:allow-localdata-read", + "markdownDescription": "This allows non-recursive read access to the `$LOCALDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-localdata`" + }, + { + "description": "This allows full recursive read access to the complete `$LOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-localdata-recursive`", + "type": "string", + "const": "fs:allow-localdata-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$LOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-localdata-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$LOCALDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-localdata`", + "type": "string", + "const": "fs:allow-localdata-write", + "markdownDescription": "This allows non-recursive write access to the `$LOCALDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-localdata`" + }, + { + "description": "This allows full recursive write access to the complete `$LOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-localdata-recursive`", + "type": "string", + "const": "fs:allow-localdata-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$LOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-localdata-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$LOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-log-index`", + "type": "string", + "const": "fs:allow-log-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$LOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-log-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$LOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-log-recursive`", + "type": "string", + "const": "fs:allow-log-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$LOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-log-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$LOG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-log`", + "type": "string", + "const": "fs:allow-log-read", + "markdownDescription": "This allows non-recursive read access to the `$LOG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-log`" + }, + { + "description": "This allows full recursive read access to the complete `$LOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-log-recursive`", + "type": "string", + "const": "fs:allow-log-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$LOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-log-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$LOG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-log`", + "type": "string", + "const": "fs:allow-log-write", + "markdownDescription": "This allows non-recursive write access to the `$LOG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-log`" + }, + { + "description": "This allows full recursive write access to the complete `$LOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-log-recursive`", + "type": "string", + "const": "fs:allow-log-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$LOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-log-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-picture-index`", + "type": "string", + "const": "fs:allow-picture-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-picture-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-picture-recursive`", + "type": "string", + "const": "fs:allow-picture-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-picture-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$PICTURE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-picture`", + "type": "string", + "const": "fs:allow-picture-read", + "markdownDescription": "This allows non-recursive read access to the `$PICTURE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-picture`" + }, + { + "description": "This allows full recursive read access to the complete `$PICTURE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-picture-recursive`", + "type": "string", + "const": "fs:allow-picture-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$PICTURE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-picture-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$PICTURE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-picture`", + "type": "string", + "const": "fs:allow-picture-write", + "markdownDescription": "This allows non-recursive write access to the `$PICTURE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-picture`" + }, + { + "description": "This allows full recursive write access to the complete `$PICTURE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-picture-recursive`", + "type": "string", + "const": "fs:allow-picture-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$PICTURE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-picture-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-public-index`", + "type": "string", + "const": "fs:allow-public-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-public-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-public-recursive`", + "type": "string", + "const": "fs:allow-public-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-public-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$PUBLIC` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-public`", + "type": "string", + "const": "fs:allow-public-read", + "markdownDescription": "This allows non-recursive read access to the `$PUBLIC` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-public`" + }, + { + "description": "This allows full recursive read access to the complete `$PUBLIC` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-public-recursive`", + "type": "string", + "const": "fs:allow-public-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$PUBLIC` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-public-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$PUBLIC` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-public`", + "type": "string", + "const": "fs:allow-public-write", + "markdownDescription": "This allows non-recursive write access to the `$PUBLIC` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-public`" + }, + { + "description": "This allows full recursive write access to the complete `$PUBLIC` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-public-recursive`", + "type": "string", + "const": "fs:allow-public-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$PUBLIC` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-public-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-resource-index`", + "type": "string", + "const": "fs:allow-resource-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-resource-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-resource-recursive`", + "type": "string", + "const": "fs:allow-resource-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-resource-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$RESOURCE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-resource`", + "type": "string", + "const": "fs:allow-resource-read", + "markdownDescription": "This allows non-recursive read access to the `$RESOURCE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-resource`" + }, + { + "description": "This allows full recursive read access to the complete `$RESOURCE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-resource-recursive`", + "type": "string", + "const": "fs:allow-resource-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$RESOURCE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-resource-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$RESOURCE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-resource`", + "type": "string", + "const": "fs:allow-resource-write", + "markdownDescription": "This allows non-recursive write access to the `$RESOURCE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-resource`" + }, + { + "description": "This allows full recursive write access to the complete `$RESOURCE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-resource-recursive`", + "type": "string", + "const": "fs:allow-resource-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$RESOURCE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-resource-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-runtime-index`", + "type": "string", + "const": "fs:allow-runtime-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-runtime-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-runtime-recursive`", + "type": "string", + "const": "fs:allow-runtime-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-runtime-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$RUNTIME` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-runtime`", + "type": "string", + "const": "fs:allow-runtime-read", + "markdownDescription": "This allows non-recursive read access to the `$RUNTIME` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-runtime`" + }, + { + "description": "This allows full recursive read access to the complete `$RUNTIME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-runtime-recursive`", + "type": "string", + "const": "fs:allow-runtime-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$RUNTIME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-runtime-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$RUNTIME` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-runtime`", + "type": "string", + "const": "fs:allow-runtime-write", + "markdownDescription": "This allows non-recursive write access to the `$RUNTIME` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-runtime`" + }, + { + "description": "This allows full recursive write access to the complete `$RUNTIME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-runtime-recursive`", + "type": "string", + "const": "fs:allow-runtime-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$RUNTIME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-runtime-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-temp-index`", + "type": "string", + "const": "fs:allow-temp-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-temp-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-temp-recursive`", + "type": "string", + "const": "fs:allow-temp-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-temp-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$TEMP` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-temp`", + "type": "string", + "const": "fs:allow-temp-read", + "markdownDescription": "This allows non-recursive read access to the `$TEMP` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-temp`" + }, + { + "description": "This allows full recursive read access to the complete `$TEMP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-temp-recursive`", + "type": "string", + "const": "fs:allow-temp-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$TEMP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-temp-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$TEMP` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-temp`", + "type": "string", + "const": "fs:allow-temp-write", + "markdownDescription": "This allows non-recursive write access to the `$TEMP` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-temp`" + }, + { + "description": "This allows full recursive write access to the complete `$TEMP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-temp-recursive`", + "type": "string", + "const": "fs:allow-temp-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$TEMP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-temp-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-template-index`", + "type": "string", + "const": "fs:allow-template-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-template-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-template-recursive`", + "type": "string", + "const": "fs:allow-template-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-template-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$TEMPLATE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-template`", + "type": "string", + "const": "fs:allow-template-read", + "markdownDescription": "This allows non-recursive read access to the `$TEMPLATE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-template`" + }, + { + "description": "This allows full recursive read access to the complete `$TEMPLATE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-template-recursive`", + "type": "string", + "const": "fs:allow-template-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$TEMPLATE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-template-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$TEMPLATE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-template`", + "type": "string", + "const": "fs:allow-template-write", + "markdownDescription": "This allows non-recursive write access to the `$TEMPLATE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-template`" + }, + { + "description": "This allows full recursive write access to the complete `$TEMPLATE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-template-recursive`", + "type": "string", + "const": "fs:allow-template-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$TEMPLATE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-template-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-video-index`", + "type": "string", + "const": "fs:allow-video-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-video-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-video-recursive`", + "type": "string", + "const": "fs:allow-video-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-video-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$VIDEO` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-video`", + "type": "string", + "const": "fs:allow-video-read", + "markdownDescription": "This allows non-recursive read access to the `$VIDEO` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-video`" + }, + { + "description": "This allows full recursive read access to the complete `$VIDEO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-video-recursive`", + "type": "string", + "const": "fs:allow-video-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$VIDEO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-video-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$VIDEO` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-video`", + "type": "string", + "const": "fs:allow-video-write", + "markdownDescription": "This allows non-recursive write access to the `$VIDEO` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-video`" + }, + { + "description": "This allows full recursive write access to the complete `$VIDEO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-video-recursive`", + "type": "string", + "const": "fs:allow-video-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$VIDEO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-video-recursive`" + }, + { + "description": "This denies access to dangerous Tauri relevant files and folders by default.\n#### This permission set includes:\n\n- `deny-webview-data-linux`\n- `deny-webview-data-windows`", + "type": "string", + "const": "fs:deny-default", + "markdownDescription": "This denies access to dangerous Tauri relevant files and folders by default.\n#### This permission set includes:\n\n- `deny-webview-data-linux`\n- `deny-webview-data-windows`" + }, + { + "description": "Enables the copy_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-copy-file", + "markdownDescription": "Enables the copy_file command without any pre-configured scope." + }, + { + "description": "Enables the create command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-create", + "markdownDescription": "Enables the create command without any pre-configured scope." + }, + { + "description": "Enables the exists command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-exists", + "markdownDescription": "Enables the exists command without any pre-configured scope." + }, + { + "description": "Enables the fstat command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-fstat", + "markdownDescription": "Enables the fstat command without any pre-configured scope." + }, + { + "description": "Enables the ftruncate command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-ftruncate", + "markdownDescription": "Enables the ftruncate command without any pre-configured scope." + }, + { + "description": "Enables the lstat command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-lstat", + "markdownDescription": "Enables the lstat command without any pre-configured scope." + }, + { + "description": "Enables the mkdir command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-mkdir", + "markdownDescription": "Enables the mkdir command without any pre-configured scope." + }, + { + "description": "Enables the open command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-open", + "markdownDescription": "Enables the open command without any pre-configured scope." + }, + { + "description": "Enables the read command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read", + "markdownDescription": "Enables the read command without any pre-configured scope." + }, + { + "description": "Enables the read_dir command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-dir", + "markdownDescription": "Enables the read_dir command without any pre-configured scope." + }, + { + "description": "Enables the read_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-file", + "markdownDescription": "Enables the read_file command without any pre-configured scope." + }, + { + "description": "Enables the read_text_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-text-file", + "markdownDescription": "Enables the read_text_file command without any pre-configured scope." + }, + { + "description": "Enables the read_text_file_lines command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-text-file-lines", + "markdownDescription": "Enables the read_text_file_lines command without any pre-configured scope." + }, + { + "description": "Enables the read_text_file_lines_next command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-text-file-lines-next", + "markdownDescription": "Enables the read_text_file_lines_next command without any pre-configured scope." + }, + { + "description": "Enables the remove command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-remove", + "markdownDescription": "Enables the remove command without any pre-configured scope." + }, + { + "description": "Enables the rename command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-rename", + "markdownDescription": "Enables the rename command without any pre-configured scope." + }, + { + "description": "Enables the seek command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-seek", + "markdownDescription": "Enables the seek command without any pre-configured scope." + }, + { + "description": "Enables the size command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-size", + "markdownDescription": "Enables the size command without any pre-configured scope." + }, + { + "description": "Enables the start_accessing_security_scoped_resource command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-start-accessing-security-scoped-resource", + "markdownDescription": "Enables the start_accessing_security_scoped_resource command without any pre-configured scope." + }, + { + "description": "Enables the stat command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-stat", + "markdownDescription": "Enables the stat command without any pre-configured scope." + }, + { + "description": "Enables the stop_accessing_security_scoped_resource command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-stop-accessing-security-scoped-resource", + "markdownDescription": "Enables the stop_accessing_security_scoped_resource command without any pre-configured scope." + }, + { + "description": "Enables the truncate command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-truncate", + "markdownDescription": "Enables the truncate command without any pre-configured scope." + }, + { + "description": "Enables the unwatch command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-unwatch", + "markdownDescription": "Enables the unwatch command without any pre-configured scope." + }, + { + "description": "Enables the watch command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-watch", + "markdownDescription": "Enables the watch command without any pre-configured scope." + }, + { + "description": "Enables the write command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-write", + "markdownDescription": "Enables the write command without any pre-configured scope." + }, + { + "description": "Enables the write_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-write-file", + "markdownDescription": "Enables the write_file command without any pre-configured scope." + }, + { + "description": "Enables the write_text_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-write-text-file", + "markdownDescription": "Enables the write_text_file command without any pre-configured scope." + }, + { + "description": "This permissions allows to create the application specific directories.\n", + "type": "string", + "const": "fs:create-app-specific-dirs", + "markdownDescription": "This permissions allows to create the application specific directories.\n" + }, + { + "description": "Denies the copy_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-copy-file", + "markdownDescription": "Denies the copy_file command without any pre-configured scope." + }, + { + "description": "Denies the create command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-create", + "markdownDescription": "Denies the create command without any pre-configured scope." + }, + { + "description": "Denies the exists command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-exists", + "markdownDescription": "Denies the exists command without any pre-configured scope." + }, + { + "description": "Denies the fstat command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-fstat", + "markdownDescription": "Denies the fstat command without any pre-configured scope." + }, + { + "description": "Denies the ftruncate command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-ftruncate", + "markdownDescription": "Denies the ftruncate command without any pre-configured scope." + }, + { + "description": "Denies the lstat command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-lstat", + "markdownDescription": "Denies the lstat command without any pre-configured scope." + }, + { + "description": "Denies the mkdir command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-mkdir", + "markdownDescription": "Denies the mkdir command without any pre-configured scope." + }, + { + "description": "Denies the open command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-open", + "markdownDescription": "Denies the open command without any pre-configured scope." + }, + { + "description": "Denies the read command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read", + "markdownDescription": "Denies the read command without any pre-configured scope." + }, + { + "description": "Denies the read_dir command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-dir", + "markdownDescription": "Denies the read_dir command without any pre-configured scope." + }, + { + "description": "Denies the read_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-file", + "markdownDescription": "Denies the read_file command without any pre-configured scope." + }, + { + "description": "Denies the read_text_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-text-file", + "markdownDescription": "Denies the read_text_file command without any pre-configured scope." + }, + { + "description": "Denies the read_text_file_lines command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-text-file-lines", + "markdownDescription": "Denies the read_text_file_lines command without any pre-configured scope." + }, + { + "description": "Denies the read_text_file_lines_next command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-text-file-lines-next", + "markdownDescription": "Denies the read_text_file_lines_next command without any pre-configured scope." + }, + { + "description": "Denies the remove command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-remove", + "markdownDescription": "Denies the remove command without any pre-configured scope." + }, + { + "description": "Denies the rename command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-rename", + "markdownDescription": "Denies the rename command without any pre-configured scope." + }, + { + "description": "Denies the seek command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-seek", + "markdownDescription": "Denies the seek command without any pre-configured scope." + }, + { + "description": "Denies the size command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-size", + "markdownDescription": "Denies the size command without any pre-configured scope." + }, + { + "description": "Denies the start_accessing_security_scoped_resource command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-start-accessing-security-scoped-resource", + "markdownDescription": "Denies the start_accessing_security_scoped_resource command without any pre-configured scope." + }, + { + "description": "Denies the stat command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-stat", + "markdownDescription": "Denies the stat command without any pre-configured scope." + }, + { + "description": "Denies the stop_accessing_security_scoped_resource command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-stop-accessing-security-scoped-resource", + "markdownDescription": "Denies the stop_accessing_security_scoped_resource command without any pre-configured scope." + }, + { + "description": "Denies the truncate command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-truncate", + "markdownDescription": "Denies the truncate command without any pre-configured scope." + }, + { + "description": "Denies the unwatch command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-unwatch", + "markdownDescription": "Denies the unwatch command without any pre-configured scope." + }, + { + "description": "Denies the watch command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-watch", + "markdownDescription": "Denies the watch command without any pre-configured scope." + }, + { + "description": "This denies read access to the\n`$APPLOCALDATA` folder on linux as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered.", + "type": "string", + "const": "fs:deny-webview-data-linux", + "markdownDescription": "This denies read access to the\n`$APPLOCALDATA` folder on linux as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered." + }, + { + "description": "This denies read access to the\n`$APPLOCALDATA/EBWebView` folder on windows as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered.", + "type": "string", + "const": "fs:deny-webview-data-windows", + "markdownDescription": "This denies read access to the\n`$APPLOCALDATA/EBWebView` folder on windows as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered." + }, + { + "description": "Denies the write command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-write", + "markdownDescription": "Denies the write command without any pre-configured scope." + }, + { + "description": "Denies the write_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-write-file", + "markdownDescription": "Denies the write_file command without any pre-configured scope." + }, + { + "description": "Denies the write_text_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-write-text-file", + "markdownDescription": "Denies the write_text_file command without any pre-configured scope." + }, + { + "description": "This enables all read related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:read-all", + "markdownDescription": "This enables all read related commands without any pre-configured accessible paths." + }, + { + "description": "This permission allows recursive read functionality on the application\nspecific base directories. \n", + "type": "string", + "const": "fs:read-app-specific-dirs-recursive", + "markdownDescription": "This permission allows recursive read functionality on the application\nspecific base directories. \n" + }, + { + "description": "This enables directory read and file metadata related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:read-dirs", + "markdownDescription": "This enables directory read and file metadata related commands without any pre-configured accessible paths." + }, + { + "description": "This enables file read related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:read-files", + "markdownDescription": "This enables file read related commands without any pre-configured accessible paths." + }, + { + "description": "This enables all index or metadata related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:read-meta", + "markdownDescription": "This enables all index or metadata related commands without any pre-configured accessible paths." + }, + { + "description": "An empty permission you can use to modify the global scope.\n\n## Example\n\n```json\n{\n \"identifier\": \"read-documents\",\n \"windows\": [\"main\"],\n \"permissions\": [\n \"fs:allow-read\",\n {\n \"identifier\": \"fs:scope\",\n \"allow\": [\n \"$APPDATA/documents/**/*\"\n ],\n \"deny\": [\n \"$APPDATA/documents/secret.txt\"\n ]\n }\n ]\n}\n```\n", + "type": "string", + "const": "fs:scope", + "markdownDescription": "An empty permission you can use to modify the global scope.\n\n## Example\n\n```json\n{\n \"identifier\": \"read-documents\",\n \"windows\": [\"main\"],\n \"permissions\": [\n \"fs:allow-read\",\n {\n \"identifier\": \"fs:scope\",\n \"allow\": [\n \"$APPDATA/documents/**/*\"\n ],\n \"deny\": [\n \"$APPDATA/documents/secret.txt\"\n ]\n }\n ]\n}\n```\n" + }, + { + "description": "This scope permits access to all files and list content of top level directories in the application folders.", + "type": "string", + "const": "fs:scope-app", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the application folders." + }, + { + "description": "This scope permits to list all files and folders in the application directories.", + "type": "string", + "const": "fs:scope-app-index", + "markdownDescription": "This scope permits to list all files and folders in the application directories." + }, + { + "description": "This scope permits recursive access to the complete application folders, including sub directories and files.", + "type": "string", + "const": "fs:scope-app-recursive", + "markdownDescription": "This scope permits recursive access to the complete application folders, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPCACHE` folder.", + "type": "string", + "const": "fs:scope-appcache", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPCACHE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPCACHE`folder.", + "type": "string", + "const": "fs:scope-appcache-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPCACHE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPCACHE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-appcache-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPCACHE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPCONFIG` folder.", + "type": "string", + "const": "fs:scope-appconfig", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPCONFIG` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPCONFIG`folder.", + "type": "string", + "const": "fs:scope-appconfig-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPCONFIG`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPCONFIG` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-appconfig-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPCONFIG` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPDATA` folder.", + "type": "string", + "const": "fs:scope-appdata", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPDATA` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPDATA`folder.", + "type": "string", + "const": "fs:scope-appdata-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPDATA`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPDATA` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-appdata-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPDATA` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPLOCALDATA` folder.", + "type": "string", + "const": "fs:scope-applocaldata", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPLOCALDATA` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPLOCALDATA`folder.", + "type": "string", + "const": "fs:scope-applocaldata-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPLOCALDATA`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPLOCALDATA` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-applocaldata-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPLOCALDATA` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPLOG` folder.", + "type": "string", + "const": "fs:scope-applog", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPLOG` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPLOG`folder.", + "type": "string", + "const": "fs:scope-applog-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPLOG`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPLOG` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-applog-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPLOG` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$AUDIO` folder.", + "type": "string", + "const": "fs:scope-audio", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$AUDIO` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$AUDIO`folder.", + "type": "string", + "const": "fs:scope-audio-index", + "markdownDescription": "This scope permits to list all files and folders in the `$AUDIO`folder." + }, + { + "description": "This scope permits recursive access to the complete `$AUDIO` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-audio-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$AUDIO` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$CACHE` folder.", + "type": "string", + "const": "fs:scope-cache", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$CACHE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$CACHE`folder.", + "type": "string", + "const": "fs:scope-cache-index", + "markdownDescription": "This scope permits to list all files and folders in the `$CACHE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$CACHE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-cache-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$CACHE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$CONFIG` folder.", + "type": "string", + "const": "fs:scope-config", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$CONFIG` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$CONFIG`folder.", + "type": "string", + "const": "fs:scope-config-index", + "markdownDescription": "This scope permits to list all files and folders in the `$CONFIG`folder." + }, + { + "description": "This scope permits recursive access to the complete `$CONFIG` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-config-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$CONFIG` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$DATA` folder.", + "type": "string", + "const": "fs:scope-data", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$DATA` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$DATA`folder.", + "type": "string", + "const": "fs:scope-data-index", + "markdownDescription": "This scope permits to list all files and folders in the `$DATA`folder." + }, + { + "description": "This scope permits recursive access to the complete `$DATA` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-data-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$DATA` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$DESKTOP` folder.", + "type": "string", + "const": "fs:scope-desktop", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$DESKTOP` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$DESKTOP`folder.", + "type": "string", + "const": "fs:scope-desktop-index", + "markdownDescription": "This scope permits to list all files and folders in the `$DESKTOP`folder." + }, + { + "description": "This scope permits recursive access to the complete `$DESKTOP` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-desktop-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$DESKTOP` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$DOCUMENT` folder.", + "type": "string", + "const": "fs:scope-document", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$DOCUMENT` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$DOCUMENT`folder.", + "type": "string", + "const": "fs:scope-document-index", + "markdownDescription": "This scope permits to list all files and folders in the `$DOCUMENT`folder." + }, + { + "description": "This scope permits recursive access to the complete `$DOCUMENT` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-document-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$DOCUMENT` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$DOWNLOAD` folder.", + "type": "string", + "const": "fs:scope-download", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$DOWNLOAD` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$DOWNLOAD`folder.", + "type": "string", + "const": "fs:scope-download-index", + "markdownDescription": "This scope permits to list all files and folders in the `$DOWNLOAD`folder." + }, + { + "description": "This scope permits recursive access to the complete `$DOWNLOAD` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-download-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$DOWNLOAD` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$EXE` folder.", + "type": "string", + "const": "fs:scope-exe", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$EXE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$EXE`folder.", + "type": "string", + "const": "fs:scope-exe-index", + "markdownDescription": "This scope permits to list all files and folders in the `$EXE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$EXE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-exe-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$EXE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$FONT` folder.", + "type": "string", + "const": "fs:scope-font", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$FONT` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$FONT`folder.", + "type": "string", + "const": "fs:scope-font-index", + "markdownDescription": "This scope permits to list all files and folders in the `$FONT`folder." + }, + { + "description": "This scope permits recursive access to the complete `$FONT` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-font-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$FONT` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$HOME` folder.", + "type": "string", + "const": "fs:scope-home", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$HOME` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$HOME`folder.", + "type": "string", + "const": "fs:scope-home-index", + "markdownDescription": "This scope permits to list all files and folders in the `$HOME`folder." + }, + { + "description": "This scope permits recursive access to the complete `$HOME` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-home-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$HOME` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$LOCALDATA` folder.", + "type": "string", + "const": "fs:scope-localdata", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$LOCALDATA` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$LOCALDATA`folder.", + "type": "string", + "const": "fs:scope-localdata-index", + "markdownDescription": "This scope permits to list all files and folders in the `$LOCALDATA`folder." + }, + { + "description": "This scope permits recursive access to the complete `$LOCALDATA` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-localdata-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$LOCALDATA` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$LOG` folder.", + "type": "string", + "const": "fs:scope-log", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$LOG` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$LOG`folder.", + "type": "string", + "const": "fs:scope-log-index", + "markdownDescription": "This scope permits to list all files and folders in the `$LOG`folder." + }, + { + "description": "This scope permits recursive access to the complete `$LOG` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-log-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$LOG` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$PICTURE` folder.", + "type": "string", + "const": "fs:scope-picture", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$PICTURE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$PICTURE`folder.", + "type": "string", + "const": "fs:scope-picture-index", + "markdownDescription": "This scope permits to list all files and folders in the `$PICTURE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$PICTURE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-picture-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$PICTURE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$PUBLIC` folder.", + "type": "string", + "const": "fs:scope-public", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$PUBLIC` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$PUBLIC`folder.", + "type": "string", + "const": "fs:scope-public-index", + "markdownDescription": "This scope permits to list all files and folders in the `$PUBLIC`folder." + }, + { + "description": "This scope permits recursive access to the complete `$PUBLIC` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-public-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$PUBLIC` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$RESOURCE` folder.", + "type": "string", + "const": "fs:scope-resource", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$RESOURCE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$RESOURCE`folder.", + "type": "string", + "const": "fs:scope-resource-index", + "markdownDescription": "This scope permits to list all files and folders in the `$RESOURCE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$RESOURCE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-resource-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$RESOURCE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$RUNTIME` folder.", + "type": "string", + "const": "fs:scope-runtime", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$RUNTIME` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$RUNTIME`folder.", + "type": "string", + "const": "fs:scope-runtime-index", + "markdownDescription": "This scope permits to list all files and folders in the `$RUNTIME`folder." + }, + { + "description": "This scope permits recursive access to the complete `$RUNTIME` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-runtime-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$RUNTIME` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$TEMP` folder.", + "type": "string", + "const": "fs:scope-temp", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$TEMP` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$TEMP`folder.", + "type": "string", + "const": "fs:scope-temp-index", + "markdownDescription": "This scope permits to list all files and folders in the `$TEMP`folder." + }, + { + "description": "This scope permits recursive access to the complete `$TEMP` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-temp-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$TEMP` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$TEMPLATE` folder.", + "type": "string", + "const": "fs:scope-template", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$TEMPLATE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$TEMPLATE`folder.", + "type": "string", + "const": "fs:scope-template-index", + "markdownDescription": "This scope permits to list all files and folders in the `$TEMPLATE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$TEMPLATE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-template-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$TEMPLATE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$VIDEO` folder.", + "type": "string", + "const": "fs:scope-video", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$VIDEO` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$VIDEO`folder.", + "type": "string", + "const": "fs:scope-video-index", + "markdownDescription": "This scope permits to list all files and folders in the `$VIDEO`folder." + }, + { + "description": "This scope permits recursive access to the complete `$VIDEO` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-video-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$VIDEO` folder, including sub directories and files." + }, + { + "description": "This enables all write related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:write-all", + "markdownDescription": "This enables all write related commands without any pre-configured accessible paths." + }, + { + "description": "This enables all file write related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:write-files", + "markdownDescription": "This enables all file write related commands without any pre-configured accessible paths." + } + ] + } + } + }, + "then": { + "properties": { + "allow": { + "items": { + "title": "FsScopeEntry", + "description": "FS scope entry.", + "anyOf": [ + { + "description": "A path that can be accessed by the webview when using the fs APIs. FS scope path pattern.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", + "type": "string" + }, + { + "type": "object", + "required": [ + "path" + ], + "properties": { + "path": { + "description": "A path that can be accessed by the webview when using the fs APIs.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", + "type": "string" + } + } + } + ] + } + }, + "deny": { + "items": { + "title": "FsScopeEntry", + "description": "FS scope entry.", + "anyOf": [ + { + "description": "A path that can be accessed by the webview when using the fs APIs. FS scope path pattern.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", + "type": "string" + }, + { + "type": "object", + "required": [ + "path" + ], + "properties": { + "path": { + "description": "A path that can be accessed by the webview when using the fs APIs.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", + "type": "string" + } + } + } + ] + } + } + } + }, + "properties": { + "identifier": { + "description": "Identifier of the permission or permission set.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + } + } + }, + { + "if": { + "properties": { + "identifier": { + "anyOf": [ + { + "description": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n\n#### This default permission set includes:\n\n- `allow-open`", + "type": "string", + "const": "shell:default", + "markdownDescription": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n\n#### This default permission set includes:\n\n- `allow-open`" + }, + { + "description": "Enables the execute command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-execute", + "markdownDescription": "Enables the execute command without any pre-configured scope." + }, + { + "description": "Enables the kill command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-kill", + "markdownDescription": "Enables the kill command without any pre-configured scope." + }, + { + "description": "Enables the open command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-open", + "markdownDescription": "Enables the open command without any pre-configured scope." + }, + { + "description": "Enables the spawn command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-spawn", + "markdownDescription": "Enables the spawn command without any pre-configured scope." + }, + { + "description": "Enables the stdin_write command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-stdin-write", + "markdownDescription": "Enables the stdin_write command without any pre-configured scope." + }, + { + "description": "Denies the execute command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-execute", + "markdownDescription": "Denies the execute command without any pre-configured scope." + }, + { + "description": "Denies the kill command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-kill", + "markdownDescription": "Denies the kill command without any pre-configured scope." + }, + { + "description": "Denies the open command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-open", + "markdownDescription": "Denies the open command without any pre-configured scope." + }, + { + "description": "Denies the spawn command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-spawn", + "markdownDescription": "Denies the spawn command without any pre-configured scope." + }, + { + "description": "Denies the stdin_write command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-stdin-write", + "markdownDescription": "Denies the stdin_write command without any pre-configured scope." + } + ] + } + } + }, + "then": { + "properties": { + "allow": { + "items": { + "title": "ShellScopeEntry", + "description": "Shell scope entry.", + "anyOf": [ + { + "type": "object", + "required": [ + "cmd", + "name" + ], + "properties": { + "args": { + "description": "The allowed arguments for the command execution.", + "allOf": [ + { + "$ref": "#/definitions/ShellScopeEntryAllowedArgs" + } + ] + }, + "cmd": { + "description": "The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", + "type": "string" + }, + "name": { + "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "name", + "sidecar" + ], + "properties": { + "args": { + "description": "The allowed arguments for the command execution.", + "allOf": [ + { + "$ref": "#/definitions/ShellScopeEntryAllowedArgs" + } + ] + }, + "name": { + "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", + "type": "string" + }, + "sidecar": { + "description": "If this command is a sidecar command.", + "type": "boolean" + } + }, + "additionalProperties": false + } + ] + } + }, + "deny": { + "items": { + "title": "ShellScopeEntry", + "description": "Shell scope entry.", + "anyOf": [ + { + "type": "object", + "required": [ + "cmd", + "name" + ], + "properties": { + "args": { + "description": "The allowed arguments for the command execution.", + "allOf": [ + { + "$ref": "#/definitions/ShellScopeEntryAllowedArgs" + } + ] + }, + "cmd": { + "description": "The command name. It can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", + "type": "string" + }, + "name": { + "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "name", + "sidecar" + ], + "properties": { + "args": { + "description": "The allowed arguments for the command execution.", + "allOf": [ + { + "$ref": "#/definitions/ShellScopeEntryAllowedArgs" + } + ] + }, + "name": { + "description": "The name for this allowed shell command configuration.\n\nThis name will be used inside of the webview API to call this command along with any specified arguments.", + "type": "string" + }, + "sidecar": { + "description": "If this command is a sidecar command.", + "type": "boolean" + } + }, + "additionalProperties": false + } + ] + } + } + } + }, + "properties": { + "identifier": { + "description": "Identifier of the permission or permission set.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + } + } + }, + { + "properties": { + "identifier": { + "description": "Identifier of the permission or permission set.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + }, + "allow": { + "description": "Data that defines what is allowed by the scope.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + }, + "deny": { + "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + } + } + } + ], + "required": [ + "identifier" + ] + } + ] + }, + "Identifier": { + "description": "Permission identifier", + "oneOf": [ + { + "description": "This permission set configures if your\napplication can enable or disable auto\nstarting the application on boot.\n\n#### Granted Permissions\n\nIt allows all to check, enable and\ndisable the automatic start on boot.\n\n\n#### This default permission set includes:\n\n- `allow-enable`\n- `allow-disable`\n- `allow-is-enabled`", + "type": "string", + "const": "autostart:default", + "markdownDescription": "This permission set configures if your\napplication can enable or disable auto\nstarting the application on boot.\n\n#### Granted Permissions\n\nIt allows all to check, enable and\ndisable the automatic start on boot.\n\n\n#### This default permission set includes:\n\n- `allow-enable`\n- `allow-disable`\n- `allow-is-enabled`" + }, + { + "description": "Enables the disable command without any pre-configured scope.", + "type": "string", + "const": "autostart:allow-disable", + "markdownDescription": "Enables the disable command without any pre-configured scope." + }, + { + "description": "Enables the enable command without any pre-configured scope.", + "type": "string", + "const": "autostart:allow-enable", + "markdownDescription": "Enables the enable command without any pre-configured scope." + }, + { + "description": "Enables the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "autostart:allow-is-enabled", + "markdownDescription": "Enables the is_enabled command without any pre-configured scope." + }, + { + "description": "Denies the disable command without any pre-configured scope.", + "type": "string", + "const": "autostart:deny-disable", + "markdownDescription": "Denies the disable command without any pre-configured scope." + }, + { + "description": "Denies the enable command without any pre-configured scope.", + "type": "string", + "const": "autostart:deny-enable", + "markdownDescription": "Denies the enable command without any pre-configured scope." + }, + { + "description": "Denies the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "autostart:deny-is-enabled", + "markdownDescription": "Denies the is_enabled command without any pre-configured scope." + }, + { + "description": "Default core plugins set.\n#### This default permission set includes:\n\n- `core:path:default`\n- `core:event:default`\n- `core:window:default`\n- `core:webview:default`\n- `core:app:default`\n- `core:image:default`\n- `core:resources:default`\n- `core:menu:default`\n- `core:tray:default`", + "type": "string", + "const": "core:default", + "markdownDescription": "Default core plugins set.\n#### This default permission set includes:\n\n- `core:path:default`\n- `core:event:default`\n- `core:window:default`\n- `core:webview:default`\n- `core:app:default`\n- `core:image:default`\n- `core:resources:default`\n- `core:menu:default`\n- `core:tray:default`" + }, + { + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`\n- `allow-supports-multiple-windows`", + "type": "string", + "const": "core:app:default", + "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`\n- `allow-supports-multiple-windows`" + }, + { + "description": "Enables the app_hide command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-app-hide", + "markdownDescription": "Enables the app_hide command without any pre-configured scope." + }, + { + "description": "Enables the app_show command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-app-show", + "markdownDescription": "Enables the app_show command without any pre-configured scope." + }, + { + "description": "Enables the bundle_type command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-bundle-type", + "markdownDescription": "Enables the bundle_type command without any pre-configured scope." + }, + { + "description": "Enables the default_window_icon command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-default-window-icon", + "markdownDescription": "Enables the default_window_icon command without any pre-configured scope." + }, + { + "description": "Enables the fetch_data_store_identifiers command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-fetch-data-store-identifiers", + "markdownDescription": "Enables the fetch_data_store_identifiers command without any pre-configured scope." + }, + { + "description": "Enables the identifier command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-identifier", + "markdownDescription": "Enables the identifier command without any pre-configured scope." + }, + { + "description": "Enables the name command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-name", + "markdownDescription": "Enables the name command without any pre-configured scope." + }, + { + "description": "Enables the register_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-register-listener", + "markdownDescription": "Enables the register_listener command without any pre-configured scope." + }, + { + "description": "Enables the remove_data_store command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-remove-data-store", + "markdownDescription": "Enables the remove_data_store command without any pre-configured scope." + }, + { + "description": "Enables the remove_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-remove-listener", + "markdownDescription": "Enables the remove_listener command without any pre-configured scope." + }, + { + "description": "Enables the set_app_theme command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-set-app-theme", + "markdownDescription": "Enables the set_app_theme command without any pre-configured scope." + }, + { + "description": "Enables the set_dock_visibility command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-set-dock-visibility", + "markdownDescription": "Enables the set_dock_visibility command without any pre-configured scope." + }, + { + "description": "Enables the supports_multiple_windows command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-supports-multiple-windows", + "markdownDescription": "Enables the supports_multiple_windows command without any pre-configured scope." + }, + { + "description": "Enables the tauri_version command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-tauri-version", + "markdownDescription": "Enables the tauri_version command without any pre-configured scope." + }, + { + "description": "Enables the version command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-version", + "markdownDescription": "Enables the version command without any pre-configured scope." + }, + { + "description": "Denies the app_hide command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-app-hide", + "markdownDescription": "Denies the app_hide command without any pre-configured scope." + }, + { + "description": "Denies the app_show command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-app-show", + "markdownDescription": "Denies the app_show command without any pre-configured scope." + }, + { + "description": "Denies the bundle_type command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-bundle-type", + "markdownDescription": "Denies the bundle_type command without any pre-configured scope." + }, + { + "description": "Denies the default_window_icon command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-default-window-icon", + "markdownDescription": "Denies the default_window_icon command without any pre-configured scope." + }, + { + "description": "Denies the fetch_data_store_identifiers command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-fetch-data-store-identifiers", + "markdownDescription": "Denies the fetch_data_store_identifiers command without any pre-configured scope." + }, + { + "description": "Denies the identifier command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-identifier", + "markdownDescription": "Denies the identifier command without any pre-configured scope." + }, + { + "description": "Denies the name command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-name", + "markdownDescription": "Denies the name command without any pre-configured scope." + }, + { + "description": "Denies the register_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-register-listener", + "markdownDescription": "Denies the register_listener command without any pre-configured scope." + }, + { + "description": "Denies the remove_data_store command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-remove-data-store", + "markdownDescription": "Denies the remove_data_store command without any pre-configured scope." + }, + { + "description": "Denies the remove_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-remove-listener", + "markdownDescription": "Denies the remove_listener command without any pre-configured scope." + }, + { + "description": "Denies the set_app_theme command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-set-app-theme", + "markdownDescription": "Denies the set_app_theme command without any pre-configured scope." + }, + { + "description": "Denies the set_dock_visibility command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-set-dock-visibility", + "markdownDescription": "Denies the set_dock_visibility command without any pre-configured scope." + }, + { + "description": "Denies the supports_multiple_windows command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-supports-multiple-windows", + "markdownDescription": "Denies the supports_multiple_windows command without any pre-configured scope." + }, + { + "description": "Denies the tauri_version command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-tauri-version", + "markdownDescription": "Denies the tauri_version command without any pre-configured scope." + }, + { + "description": "Denies the version command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-version", + "markdownDescription": "Denies the version command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-listen`\n- `allow-unlisten`\n- `allow-emit`\n- `allow-emit-to`", + "type": "string", + "const": "core:event:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-listen`\n- `allow-unlisten`\n- `allow-emit`\n- `allow-emit-to`" + }, + { + "description": "Enables the emit command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-emit", + "markdownDescription": "Enables the emit command without any pre-configured scope." + }, + { + "description": "Enables the emit_to command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-emit-to", + "markdownDescription": "Enables the emit_to command without any pre-configured scope." + }, + { + "description": "Enables the listen command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-listen", + "markdownDescription": "Enables the listen command without any pre-configured scope." + }, + { + "description": "Enables the unlisten command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-unlisten", + "markdownDescription": "Enables the unlisten command without any pre-configured scope." + }, + { + "description": "Denies the emit command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-emit", + "markdownDescription": "Denies the emit command without any pre-configured scope." + }, + { + "description": "Denies the emit_to command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-emit-to", + "markdownDescription": "Denies the emit_to command without any pre-configured scope." + }, + { + "description": "Denies the listen command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-listen", + "markdownDescription": "Denies the listen command without any pre-configured scope." + }, + { + "description": "Denies the unlisten command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-unlisten", + "markdownDescription": "Denies the unlisten command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-from-bytes`\n- `allow-from-path`\n- `allow-rgba`\n- `allow-size`", + "type": "string", + "const": "core:image:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-from-bytes`\n- `allow-from-path`\n- `allow-rgba`\n- `allow-size`" + }, + { + "description": "Enables the from_bytes command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-from-bytes", + "markdownDescription": "Enables the from_bytes command without any pre-configured scope." + }, + { + "description": "Enables the from_path command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-from-path", + "markdownDescription": "Enables the from_path command without any pre-configured scope." + }, + { + "description": "Enables the new command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-new", + "markdownDescription": "Enables the new command without any pre-configured scope." + }, + { + "description": "Enables the rgba command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-rgba", + "markdownDescription": "Enables the rgba command without any pre-configured scope." + }, + { + "description": "Enables the size command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-size", + "markdownDescription": "Enables the size command without any pre-configured scope." + }, + { + "description": "Denies the from_bytes command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-from-bytes", + "markdownDescription": "Denies the from_bytes command without any pre-configured scope." + }, + { + "description": "Denies the from_path command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-from-path", + "markdownDescription": "Denies the from_path command without any pre-configured scope." + }, + { + "description": "Denies the new command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-new", + "markdownDescription": "Denies the new command without any pre-configured scope." + }, + { + "description": "Denies the rgba command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-rgba", + "markdownDescription": "Denies the rgba command without any pre-configured scope." + }, + { + "description": "Denies the size command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-size", + "markdownDescription": "Denies the size command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-append`\n- `allow-prepend`\n- `allow-insert`\n- `allow-remove`\n- `allow-remove-at`\n- `allow-items`\n- `allow-get`\n- `allow-popup`\n- `allow-create-default`\n- `allow-set-as-app-menu`\n- `allow-set-as-window-menu`\n- `allow-text`\n- `allow-set-text`\n- `allow-is-enabled`\n- `allow-set-enabled`\n- `allow-set-accelerator`\n- `allow-set-as-windows-menu-for-nsapp`\n- `allow-set-as-help-menu-for-nsapp`\n- `allow-is-checked`\n- `allow-set-checked`\n- `allow-set-icon`", + "type": "string", + "const": "core:menu:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-append`\n- `allow-prepend`\n- `allow-insert`\n- `allow-remove`\n- `allow-remove-at`\n- `allow-items`\n- `allow-get`\n- `allow-popup`\n- `allow-create-default`\n- `allow-set-as-app-menu`\n- `allow-set-as-window-menu`\n- `allow-text`\n- `allow-set-text`\n- `allow-is-enabled`\n- `allow-set-enabled`\n- `allow-set-accelerator`\n- `allow-set-as-windows-menu-for-nsapp`\n- `allow-set-as-help-menu-for-nsapp`\n- `allow-is-checked`\n- `allow-set-checked`\n- `allow-set-icon`" + }, + { + "description": "Enables the append command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-append", + "markdownDescription": "Enables the append command without any pre-configured scope." + }, + { + "description": "Enables the create_default command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-create-default", + "markdownDescription": "Enables the create_default command without any pre-configured scope." + }, + { + "description": "Enables the get command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-get", + "markdownDescription": "Enables the get command without any pre-configured scope." + }, + { + "description": "Enables the insert command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-insert", + "markdownDescription": "Enables the insert command without any pre-configured scope." + }, + { + "description": "Enables the is_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-is-checked", + "markdownDescription": "Enables the is_checked command without any pre-configured scope." + }, + { + "description": "Enables the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-is-enabled", + "markdownDescription": "Enables the is_enabled command without any pre-configured scope." + }, + { + "description": "Enables the items command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-items", + "markdownDescription": "Enables the items command without any pre-configured scope." + }, + { + "description": "Enables the new command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-new", + "markdownDescription": "Enables the new command without any pre-configured scope." + }, + { + "description": "Enables the popup command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-popup", + "markdownDescription": "Enables the popup command without any pre-configured scope." + }, + { + "description": "Enables the prepend command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-prepend", + "markdownDescription": "Enables the prepend command without any pre-configured scope." + }, + { + "description": "Enables the remove command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-remove", + "markdownDescription": "Enables the remove command without any pre-configured scope." + }, + { + "description": "Enables the remove_at command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-remove-at", + "markdownDescription": "Enables the remove_at command without any pre-configured scope." + }, + { + "description": "Enables the set_accelerator command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-accelerator", + "markdownDescription": "Enables the set_accelerator command without any pre-configured scope." + }, + { + "description": "Enables the set_as_app_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-app-menu", + "markdownDescription": "Enables the set_as_app_menu command without any pre-configured scope." + }, + { + "description": "Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-help-menu-for-nsapp", + "markdownDescription": "Enables the set_as_help_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Enables the set_as_window_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-window-menu", + "markdownDescription": "Enables the set_as_window_menu command without any pre-configured scope." + }, + { + "description": "Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-windows-menu-for-nsapp", + "markdownDescription": "Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Enables the set_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-checked", + "markdownDescription": "Enables the set_checked command without any pre-configured scope." + }, + { + "description": "Enables the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-enabled", + "markdownDescription": "Enables the set_enabled command without any pre-configured scope." + }, + { + "description": "Enables the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-icon", + "markdownDescription": "Enables the set_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-text", + "markdownDescription": "Enables the set_text command without any pre-configured scope." + }, + { + "description": "Enables the text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-text", + "markdownDescription": "Enables the text command without any pre-configured scope." + }, + { + "description": "Denies the append command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-append", + "markdownDescription": "Denies the append command without any pre-configured scope." + }, + { + "description": "Denies the create_default command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-create-default", + "markdownDescription": "Denies the create_default command without any pre-configured scope." + }, + { + "description": "Denies the get command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-get", + "markdownDescription": "Denies the get command without any pre-configured scope." + }, + { + "description": "Denies the insert command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-insert", + "markdownDescription": "Denies the insert command without any pre-configured scope." + }, + { + "description": "Denies the is_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-is-checked", + "markdownDescription": "Denies the is_checked command without any pre-configured scope." + }, + { + "description": "Denies the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-is-enabled", + "markdownDescription": "Denies the is_enabled command without any pre-configured scope." + }, + { + "description": "Denies the items command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-items", + "markdownDescription": "Denies the items command without any pre-configured scope." + }, + { + "description": "Denies the new command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-new", + "markdownDescription": "Denies the new command without any pre-configured scope." + }, + { + "description": "Denies the popup command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-popup", + "markdownDescription": "Denies the popup command without any pre-configured scope." + }, + { + "description": "Denies the prepend command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-prepend", + "markdownDescription": "Denies the prepend command without any pre-configured scope." + }, + { + "description": "Denies the remove command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-remove", + "markdownDescription": "Denies the remove command without any pre-configured scope." + }, + { + "description": "Denies the remove_at command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-remove-at", + "markdownDescription": "Denies the remove_at command without any pre-configured scope." + }, + { + "description": "Denies the set_accelerator command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-accelerator", + "markdownDescription": "Denies the set_accelerator command without any pre-configured scope." + }, + { + "description": "Denies the set_as_app_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-app-menu", + "markdownDescription": "Denies the set_as_app_menu command without any pre-configured scope." + }, + { + "description": "Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-help-menu-for-nsapp", + "markdownDescription": "Denies the set_as_help_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Denies the set_as_window_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-window-menu", + "markdownDescription": "Denies the set_as_window_menu command without any pre-configured scope." + }, + { + "description": "Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-windows-menu-for-nsapp", + "markdownDescription": "Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Denies the set_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-checked", + "markdownDescription": "Denies the set_checked command without any pre-configured scope." + }, + { + "description": "Denies the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-enabled", + "markdownDescription": "Denies the set_enabled command without any pre-configured scope." + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-icon", + "markdownDescription": "Denies the set_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-text", + "markdownDescription": "Denies the set_text command without any pre-configured scope." + }, + { + "description": "Denies the text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-text", + "markdownDescription": "Denies the text command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-resolve-directory`\n- `allow-resolve`\n- `allow-normalize`\n- `allow-join`\n- `allow-dirname`\n- `allow-extname`\n- `allow-basename`\n- `allow-is-absolute`", + "type": "string", + "const": "core:path:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-resolve-directory`\n- `allow-resolve`\n- `allow-normalize`\n- `allow-join`\n- `allow-dirname`\n- `allow-extname`\n- `allow-basename`\n- `allow-is-absolute`" + }, + { + "description": "Enables the basename command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-basename", + "markdownDescription": "Enables the basename command without any pre-configured scope." + }, + { + "description": "Enables the dirname command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-dirname", + "markdownDescription": "Enables the dirname command without any pre-configured scope." + }, + { + "description": "Enables the extname command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-extname", + "markdownDescription": "Enables the extname command without any pre-configured scope." + }, + { + "description": "Enables the is_absolute command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-is-absolute", + "markdownDescription": "Enables the is_absolute command without any pre-configured scope." + }, + { + "description": "Enables the join command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-join", + "markdownDescription": "Enables the join command without any pre-configured scope." + }, + { + "description": "Enables the normalize command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-normalize", + "markdownDescription": "Enables the normalize command without any pre-configured scope." + }, + { + "description": "Enables the resolve command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-resolve", + "markdownDescription": "Enables the resolve command without any pre-configured scope." + }, + { + "description": "Enables the resolve_directory command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-resolve-directory", + "markdownDescription": "Enables the resolve_directory command without any pre-configured scope." + }, + { + "description": "Denies the basename command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-basename", + "markdownDescription": "Denies the basename command without any pre-configured scope." + }, + { + "description": "Denies the dirname command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-dirname", + "markdownDescription": "Denies the dirname command without any pre-configured scope." + }, + { + "description": "Denies the extname command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-extname", + "markdownDescription": "Denies the extname command without any pre-configured scope." + }, + { + "description": "Denies the is_absolute command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-is-absolute", + "markdownDescription": "Denies the is_absolute command without any pre-configured scope." + }, + { + "description": "Denies the join command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-join", + "markdownDescription": "Denies the join command without any pre-configured scope." + }, + { + "description": "Denies the normalize command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-normalize", + "markdownDescription": "Denies the normalize command without any pre-configured scope." + }, + { + "description": "Denies the resolve command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-resolve", + "markdownDescription": "Denies the resolve command without any pre-configured scope." + }, + { + "description": "Denies the resolve_directory command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-resolve-directory", + "markdownDescription": "Denies the resolve_directory command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-close`", + "type": "string", + "const": "core:resources:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-close`" + }, + { + "description": "Enables the close command without any pre-configured scope.", + "type": "string", + "const": "core:resources:allow-close", + "markdownDescription": "Enables the close command without any pre-configured scope." + }, + { + "description": "Denies the close command without any pre-configured scope.", + "type": "string", + "const": "core:resources:deny-close", + "markdownDescription": "Denies the close command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-get-by-id`\n- `allow-remove-by-id`\n- `allow-set-icon`\n- `allow-set-menu`\n- `allow-set-tooltip`\n- `allow-set-title`\n- `allow-set-visible`\n- `allow-set-temp-dir-path`\n- `allow-set-icon-as-template`\n- `allow-set-icon-with-as-template`\n- `allow-set-show-menu-on-left-click`", + "type": "string", + "const": "core:tray:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-get-by-id`\n- `allow-remove-by-id`\n- `allow-set-icon`\n- `allow-set-menu`\n- `allow-set-tooltip`\n- `allow-set-title`\n- `allow-set-visible`\n- `allow-set-temp-dir-path`\n- `allow-set-icon-as-template`\n- `allow-set-icon-with-as-template`\n- `allow-set-show-menu-on-left-click`" + }, + { + "description": "Enables the get_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-get-by-id", + "markdownDescription": "Enables the get_by_id command without any pre-configured scope." + }, + { + "description": "Enables the new command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-new", + "markdownDescription": "Enables the new command without any pre-configured scope." + }, + { + "description": "Enables the remove_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-remove-by-id", + "markdownDescription": "Enables the remove_by_id command without any pre-configured scope." + }, + { + "description": "Enables the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-icon", + "markdownDescription": "Enables the set_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_icon_as_template command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-icon-as-template", + "markdownDescription": "Enables the set_icon_as_template command without any pre-configured scope." + }, + { + "description": "Enables the set_icon_with_as_template command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-icon-with-as-template", + "markdownDescription": "Enables the set_icon_with_as_template command without any pre-configured scope." + }, + { + "description": "Enables the set_menu command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-menu", + "markdownDescription": "Enables the set_menu command without any pre-configured scope." + }, + { + "description": "Enables the set_show_menu_on_left_click command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-show-menu-on-left-click", + "markdownDescription": "Enables the set_show_menu_on_left_click command without any pre-configured scope." + }, + { + "description": "Enables the set_temp_dir_path command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-temp-dir-path", + "markdownDescription": "Enables the set_temp_dir_path command without any pre-configured scope." + }, + { + "description": "Enables the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-title", + "markdownDescription": "Enables the set_title command without any pre-configured scope." + }, + { + "description": "Enables the set_tooltip command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-tooltip", + "markdownDescription": "Enables the set_tooltip command without any pre-configured scope." + }, + { + "description": "Enables the set_visible command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-visible", + "markdownDescription": "Enables the set_visible command without any pre-configured scope." + }, + { + "description": "Denies the get_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-get-by-id", + "markdownDescription": "Denies the get_by_id command without any pre-configured scope." + }, + { + "description": "Denies the new command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-new", + "markdownDescription": "Denies the new command without any pre-configured scope." + }, + { + "description": "Denies the remove_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-remove-by-id", + "markdownDescription": "Denies the remove_by_id command without any pre-configured scope." + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-icon", + "markdownDescription": "Denies the set_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_icon_as_template command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-icon-as-template", + "markdownDescription": "Denies the set_icon_as_template command without any pre-configured scope." + }, + { + "description": "Denies the set_icon_with_as_template command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-icon-with-as-template", + "markdownDescription": "Denies the set_icon_with_as_template command without any pre-configured scope." + }, + { + "description": "Denies the set_menu command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-menu", + "markdownDescription": "Denies the set_menu command without any pre-configured scope." + }, + { + "description": "Denies the set_show_menu_on_left_click command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-show-menu-on-left-click", + "markdownDescription": "Denies the set_show_menu_on_left_click command without any pre-configured scope." + }, + { + "description": "Denies the set_temp_dir_path command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-temp-dir-path", + "markdownDescription": "Denies the set_temp_dir_path command without any pre-configured scope." + }, + { + "description": "Denies the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-title", + "markdownDescription": "Denies the set_title command without any pre-configured scope." + }, + { + "description": "Denies the set_tooltip command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-tooltip", + "markdownDescription": "Denies the set_tooltip command without any pre-configured scope." + }, + { + "description": "Denies the set_visible command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-visible", + "markdownDescription": "Denies the set_visible command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-webviews`\n- `allow-webview-position`\n- `allow-webview-size`\n- `allow-internal-toggle-devtools`", + "type": "string", + "const": "core:webview:default", + "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-webviews`\n- `allow-webview-position`\n- `allow-webview-size`\n- `allow-internal-toggle-devtools`" + }, + { + "description": "Enables the clear_all_browsing_data command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-clear-all-browsing-data", + "markdownDescription": "Enables the clear_all_browsing_data command without any pre-configured scope." + }, + { + "description": "Enables the create_webview command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-create-webview", + "markdownDescription": "Enables the create_webview command without any pre-configured scope." + }, + { + "description": "Enables the create_webview_window command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-create-webview-window", + "markdownDescription": "Enables the create_webview_window command without any pre-configured scope." + }, + { + "description": "Enables the get_all_webviews command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-get-all-webviews", + "markdownDescription": "Enables the get_all_webviews command without any pre-configured scope." + }, + { + "description": "Enables the internal_toggle_devtools command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-internal-toggle-devtools", + "markdownDescription": "Enables the internal_toggle_devtools command without any pre-configured scope." + }, + { + "description": "Enables the print command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-print", + "markdownDescription": "Enables the print command without any pre-configured scope." + }, + { + "description": "Enables the reparent command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-reparent", + "markdownDescription": "Enables the reparent command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_auto_resize command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-auto-resize", + "markdownDescription": "Enables the set_webview_auto_resize command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-background-color", + "markdownDescription": "Enables the set_webview_background_color command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_focus command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-focus", + "markdownDescription": "Enables the set_webview_focus command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-position", + "markdownDescription": "Enables the set_webview_position command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-size", + "markdownDescription": "Enables the set_webview_size command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_zoom command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-zoom", + "markdownDescription": "Enables the set_webview_zoom command without any pre-configured scope." + }, + { + "description": "Enables the webview_close command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-close", + "markdownDescription": "Enables the webview_close command without any pre-configured scope." + }, + { + "description": "Enables the webview_hide command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-hide", + "markdownDescription": "Enables the webview_hide command without any pre-configured scope." + }, + { + "description": "Enables the webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-position", + "markdownDescription": "Enables the webview_position command without any pre-configured scope." + }, + { + "description": "Enables the webview_show command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-show", + "markdownDescription": "Enables the webview_show command without any pre-configured scope." + }, + { + "description": "Enables the webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-size", + "markdownDescription": "Enables the webview_size command without any pre-configured scope." + }, + { + "description": "Denies the clear_all_browsing_data command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-clear-all-browsing-data", + "markdownDescription": "Denies the clear_all_browsing_data command without any pre-configured scope." + }, + { + "description": "Denies the create_webview command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-create-webview", + "markdownDescription": "Denies the create_webview command without any pre-configured scope." + }, + { + "description": "Denies the create_webview_window command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-create-webview-window", + "markdownDescription": "Denies the create_webview_window command without any pre-configured scope." + }, + { + "description": "Denies the get_all_webviews command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-get-all-webviews", + "markdownDescription": "Denies the get_all_webviews command without any pre-configured scope." + }, + { + "description": "Denies the internal_toggle_devtools command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-internal-toggle-devtools", + "markdownDescription": "Denies the internal_toggle_devtools command without any pre-configured scope." + }, + { + "description": "Denies the print command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-print", + "markdownDescription": "Denies the print command without any pre-configured scope." + }, + { + "description": "Denies the reparent command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-reparent", + "markdownDescription": "Denies the reparent command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_auto_resize command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-auto-resize", + "markdownDescription": "Denies the set_webview_auto_resize command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-background-color", + "markdownDescription": "Denies the set_webview_background_color command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_focus command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-focus", + "markdownDescription": "Denies the set_webview_focus command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-position", + "markdownDescription": "Denies the set_webview_position command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-size", + "markdownDescription": "Denies the set_webview_size command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_zoom command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-zoom", + "markdownDescription": "Denies the set_webview_zoom command without any pre-configured scope." + }, + { + "description": "Denies the webview_close command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-close", + "markdownDescription": "Denies the webview_close command without any pre-configured scope." + }, + { + "description": "Denies the webview_hide command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-hide", + "markdownDescription": "Denies the webview_hide command without any pre-configured scope." + }, + { + "description": "Denies the webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-position", + "markdownDescription": "Denies the webview_position command without any pre-configured scope." + }, + { + "description": "Denies the webview_show command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-show", + "markdownDescription": "Denies the webview_show command without any pre-configured scope." + }, + { + "description": "Denies the webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-size", + "markdownDescription": "Denies the webview_size command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-windows`\n- `allow-scale-factor`\n- `allow-inner-position`\n- `allow-outer-position`\n- `allow-inner-size`\n- `allow-outer-size`\n- `allow-is-fullscreen`\n- `allow-is-minimized`\n- `allow-is-maximized`\n- `allow-is-focused`\n- `allow-is-decorated`\n- `allow-is-resizable`\n- `allow-is-maximizable`\n- `allow-is-minimizable`\n- `allow-is-closable`\n- `allow-is-visible`\n- `allow-is-enabled`\n- `allow-title`\n- `allow-current-monitor`\n- `allow-primary-monitor`\n- `allow-monitor-from-point`\n- `allow-available-monitors`\n- `allow-cursor-position`\n- `allow-theme`\n- `allow-is-always-on-top`\n- `allow-activity-name`\n- `allow-scene-identifier`\n- `allow-internal-toggle-maximize`", + "type": "string", + "const": "core:window:default", + "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-windows`\n- `allow-scale-factor`\n- `allow-inner-position`\n- `allow-outer-position`\n- `allow-inner-size`\n- `allow-outer-size`\n- `allow-is-fullscreen`\n- `allow-is-minimized`\n- `allow-is-maximized`\n- `allow-is-focused`\n- `allow-is-decorated`\n- `allow-is-resizable`\n- `allow-is-maximizable`\n- `allow-is-minimizable`\n- `allow-is-closable`\n- `allow-is-visible`\n- `allow-is-enabled`\n- `allow-title`\n- `allow-current-monitor`\n- `allow-primary-monitor`\n- `allow-monitor-from-point`\n- `allow-available-monitors`\n- `allow-cursor-position`\n- `allow-theme`\n- `allow-is-always-on-top`\n- `allow-activity-name`\n- `allow-scene-identifier`\n- `allow-internal-toggle-maximize`" + }, + { + "description": "Enables the activity_name command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-activity-name", + "markdownDescription": "Enables the activity_name command without any pre-configured scope." + }, + { + "description": "Enables the available_monitors command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-available-monitors", + "markdownDescription": "Enables the available_monitors command without any pre-configured scope." + }, + { + "description": "Enables the center command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-center", + "markdownDescription": "Enables the center command without any pre-configured scope." + }, + { + "description": "Enables the close command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-close", + "markdownDescription": "Enables the close command without any pre-configured scope." + }, + { + "description": "Enables the create command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-create", + "markdownDescription": "Enables the create command without any pre-configured scope." + }, + { + "description": "Enables the current_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-current-monitor", + "markdownDescription": "Enables the current_monitor command without any pre-configured scope." + }, + { + "description": "Enables the cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-cursor-position", + "markdownDescription": "Enables the cursor_position command without any pre-configured scope." + }, + { + "description": "Enables the destroy command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-destroy", + "markdownDescription": "Enables the destroy command without any pre-configured scope." + }, + { + "description": "Enables the get_all_windows command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-get-all-windows", + "markdownDescription": "Enables the get_all_windows command without any pre-configured scope." + }, + { + "description": "Enables the hide command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-hide", + "markdownDescription": "Enables the hide command without any pre-configured scope." + }, + { + "description": "Enables the inner_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-inner-position", + "markdownDescription": "Enables the inner_position command without any pre-configured scope." + }, + { + "description": "Enables the inner_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-inner-size", + "markdownDescription": "Enables the inner_size command without any pre-configured scope." + }, + { + "description": "Enables the internal_toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-internal-toggle-maximize", + "markdownDescription": "Enables the internal_toggle_maximize command without any pre-configured scope." + }, + { + "description": "Enables the is_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-always-on-top", + "markdownDescription": "Enables the is_always_on_top command without any pre-configured scope." + }, + { + "description": "Enables the is_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-closable", + "markdownDescription": "Enables the is_closable command without any pre-configured scope." + }, + { + "description": "Enables the is_decorated command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-decorated", + "markdownDescription": "Enables the is_decorated command without any pre-configured scope." + }, + { + "description": "Enables the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-enabled", + "markdownDescription": "Enables the is_enabled command without any pre-configured scope." + }, + { + "description": "Enables the is_focused command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-focused", + "markdownDescription": "Enables the is_focused command without any pre-configured scope." + }, + { + "description": "Enables the is_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-fullscreen", + "markdownDescription": "Enables the is_fullscreen command without any pre-configured scope." + }, + { + "description": "Enables the is_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-maximizable", + "markdownDescription": "Enables the is_maximizable command without any pre-configured scope." + }, + { + "description": "Enables the is_maximized command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-maximized", + "markdownDescription": "Enables the is_maximized command without any pre-configured scope." + }, + { + "description": "Enables the is_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-minimizable", + "markdownDescription": "Enables the is_minimizable command without any pre-configured scope." + }, + { + "description": "Enables the is_minimized command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-minimized", + "markdownDescription": "Enables the is_minimized command without any pre-configured scope." + }, + { + "description": "Enables the is_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-resizable", + "markdownDescription": "Enables the is_resizable command without any pre-configured scope." + }, + { + "description": "Enables the is_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-visible", + "markdownDescription": "Enables the is_visible command without any pre-configured scope." + }, + { + "description": "Enables the maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-maximize", + "markdownDescription": "Enables the maximize command without any pre-configured scope." + }, + { + "description": "Enables the minimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-minimize", + "markdownDescription": "Enables the minimize command without any pre-configured scope." + }, + { + "description": "Enables the monitor_from_point command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-monitor-from-point", + "markdownDescription": "Enables the monitor_from_point command without any pre-configured scope." + }, + { + "description": "Enables the outer_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-outer-position", + "markdownDescription": "Enables the outer_position command without any pre-configured scope." + }, + { + "description": "Enables the outer_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-outer-size", + "markdownDescription": "Enables the outer_size command without any pre-configured scope." + }, + { + "description": "Enables the primary_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-primary-monitor", + "markdownDescription": "Enables the primary_monitor command without any pre-configured scope." + }, + { + "description": "Enables the request_user_attention command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-request-user-attention", + "markdownDescription": "Enables the request_user_attention command without any pre-configured scope." + }, + { + "description": "Enables the scale_factor command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-scale-factor", + "markdownDescription": "Enables the scale_factor command without any pre-configured scope." + }, + { + "description": "Enables the scene_identifier command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-scene-identifier", + "markdownDescription": "Enables the scene_identifier command without any pre-configured scope." + }, + { + "description": "Enables the set_always_on_bottom command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-always-on-bottom", + "markdownDescription": "Enables the set_always_on_bottom command without any pre-configured scope." + }, + { + "description": "Enables the set_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-always-on-top", + "markdownDescription": "Enables the set_always_on_top command without any pre-configured scope." + }, + { + "description": "Enables the set_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-background-color", + "markdownDescription": "Enables the set_background_color command without any pre-configured scope." + }, + { + "description": "Enables the set_badge_count command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-badge-count", + "markdownDescription": "Enables the set_badge_count command without any pre-configured scope." + }, + { + "description": "Enables the set_badge_label command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-badge-label", + "markdownDescription": "Enables the set_badge_label command without any pre-configured scope." + }, + { + "description": "Enables the set_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-closable", + "markdownDescription": "Enables the set_closable command without any pre-configured scope." + }, + { + "description": "Enables the set_content_protected command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-content-protected", + "markdownDescription": "Enables the set_content_protected command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_grab command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-grab", + "markdownDescription": "Enables the set_cursor_grab command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-icon", + "markdownDescription": "Enables the set_cursor_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-position", + "markdownDescription": "Enables the set_cursor_position command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-visible", + "markdownDescription": "Enables the set_cursor_visible command without any pre-configured scope." + }, + { + "description": "Enables the set_decorations command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-decorations", + "markdownDescription": "Enables the set_decorations command without any pre-configured scope." + }, + { + "description": "Enables the set_effects command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-effects", + "markdownDescription": "Enables the set_effects command without any pre-configured scope." + }, + { + "description": "Enables the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-enabled", + "markdownDescription": "Enables the set_enabled command without any pre-configured scope." + }, + { + "description": "Enables the set_focus command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-focus", + "markdownDescription": "Enables the set_focus command without any pre-configured scope." + }, + { + "description": "Enables the set_focusable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-focusable", + "markdownDescription": "Enables the set_focusable command without any pre-configured scope." + }, + { + "description": "Enables the set_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-fullscreen", + "markdownDescription": "Enables the set_fullscreen command without any pre-configured scope." + }, + { + "description": "Enables the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-icon", + "markdownDescription": "Enables the set_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_ignore_cursor_events command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-ignore-cursor-events", + "markdownDescription": "Enables the set_ignore_cursor_events command without any pre-configured scope." + }, + { + "description": "Enables the set_max_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-max-size", + "markdownDescription": "Enables the set_max_size command without any pre-configured scope." + }, + { + "description": "Enables the set_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-maximizable", + "markdownDescription": "Enables the set_maximizable command without any pre-configured scope." + }, + { + "description": "Enables the set_min_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-min-size", + "markdownDescription": "Enables the set_min_size command without any pre-configured scope." + }, + { + "description": "Enables the set_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-minimizable", + "markdownDescription": "Enables the set_minimizable command without any pre-configured scope." + }, + { + "description": "Enables the set_overlay_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-overlay-icon", + "markdownDescription": "Enables the set_overlay_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-position", + "markdownDescription": "Enables the set_position command without any pre-configured scope." + }, + { + "description": "Enables the set_progress_bar command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-progress-bar", + "markdownDescription": "Enables the set_progress_bar command without any pre-configured scope." + }, + { + "description": "Enables the set_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-resizable", + "markdownDescription": "Enables the set_resizable command without any pre-configured scope." + }, + { + "description": "Enables the set_shadow command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-shadow", + "markdownDescription": "Enables the set_shadow command without any pre-configured scope." + }, + { + "description": "Enables the set_simple_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-simple-fullscreen", + "markdownDescription": "Enables the set_simple_fullscreen command without any pre-configured scope." + }, + { + "description": "Enables the set_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-size", + "markdownDescription": "Enables the set_size command without any pre-configured scope." + }, + { + "description": "Enables the set_size_constraints command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-size-constraints", + "markdownDescription": "Enables the set_size_constraints command without any pre-configured scope." + }, + { + "description": "Enables the set_skip_taskbar command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-skip-taskbar", + "markdownDescription": "Enables the set_skip_taskbar command without any pre-configured scope." + }, + { + "description": "Enables the set_theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-theme", + "markdownDescription": "Enables the set_theme command without any pre-configured scope." + }, + { + "description": "Enables the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-title", + "markdownDescription": "Enables the set_title command without any pre-configured scope." + }, + { + "description": "Enables the set_title_bar_style command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-title-bar-style", + "markdownDescription": "Enables the set_title_bar_style command without any pre-configured scope." + }, + { + "description": "Enables the set_visible_on_all_workspaces command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-visible-on-all-workspaces", + "markdownDescription": "Enables the set_visible_on_all_workspaces command without any pre-configured scope." + }, + { + "description": "Enables the show command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-show", + "markdownDescription": "Enables the show command without any pre-configured scope." + }, + { + "description": "Enables the start_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-start-dragging", + "markdownDescription": "Enables the start_dragging command without any pre-configured scope." + }, + { + "description": "Enables the start_resize_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-start-resize-dragging", + "markdownDescription": "Enables the start_resize_dragging command without any pre-configured scope." + }, + { + "description": "Enables the theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-theme", + "markdownDescription": "Enables the theme command without any pre-configured scope." + }, + { + "description": "Enables the title command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-title", + "markdownDescription": "Enables the title command without any pre-configured scope." + }, + { + "description": "Enables the toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-toggle-maximize", + "markdownDescription": "Enables the toggle_maximize command without any pre-configured scope." + }, + { + "description": "Enables the unmaximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-unmaximize", + "markdownDescription": "Enables the unmaximize command without any pre-configured scope." + }, + { + "description": "Enables the unminimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-unminimize", + "markdownDescription": "Enables the unminimize command without any pre-configured scope." + }, + { + "description": "Denies the activity_name command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-activity-name", + "markdownDescription": "Denies the activity_name command without any pre-configured scope." + }, + { + "description": "Denies the available_monitors command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-available-monitors", + "markdownDescription": "Denies the available_monitors command without any pre-configured scope." + }, + { + "description": "Denies the center command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-center", + "markdownDescription": "Denies the center command without any pre-configured scope." + }, + { + "description": "Denies the close command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-close", + "markdownDescription": "Denies the close command without any pre-configured scope." + }, + { + "description": "Denies the create command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-create", + "markdownDescription": "Denies the create command without any pre-configured scope." + }, + { + "description": "Denies the current_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-current-monitor", + "markdownDescription": "Denies the current_monitor command without any pre-configured scope." + }, + { + "description": "Denies the cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-cursor-position", + "markdownDescription": "Denies the cursor_position command without any pre-configured scope." + }, + { + "description": "Denies the destroy command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-destroy", + "markdownDescription": "Denies the destroy command without any pre-configured scope." + }, + { + "description": "Denies the get_all_windows command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-get-all-windows", + "markdownDescription": "Denies the get_all_windows command without any pre-configured scope." + }, + { + "description": "Denies the hide command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-hide", + "markdownDescription": "Denies the hide command without any pre-configured scope." + }, + { + "description": "Denies the inner_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-inner-position", + "markdownDescription": "Denies the inner_position command without any pre-configured scope." + }, + { + "description": "Denies the inner_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-inner-size", + "markdownDescription": "Denies the inner_size command without any pre-configured scope." + }, + { + "description": "Denies the internal_toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-internal-toggle-maximize", + "markdownDescription": "Denies the internal_toggle_maximize command without any pre-configured scope." + }, + { + "description": "Denies the is_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-always-on-top", + "markdownDescription": "Denies the is_always_on_top command without any pre-configured scope." + }, + { + "description": "Denies the is_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-closable", + "markdownDescription": "Denies the is_closable command without any pre-configured scope." + }, + { + "description": "Denies the is_decorated command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-decorated", + "markdownDescription": "Denies the is_decorated command without any pre-configured scope." + }, + { + "description": "Denies the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-enabled", + "markdownDescription": "Denies the is_enabled command without any pre-configured scope." + }, + { + "description": "Denies the is_focused command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-focused", + "markdownDescription": "Denies the is_focused command without any pre-configured scope." + }, + { + "description": "Denies the is_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-fullscreen", + "markdownDescription": "Denies the is_fullscreen command without any pre-configured scope." + }, + { + "description": "Denies the is_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-maximizable", + "markdownDescription": "Denies the is_maximizable command without any pre-configured scope." + }, + { + "description": "Denies the is_maximized command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-maximized", + "markdownDescription": "Denies the is_maximized command without any pre-configured scope." + }, + { + "description": "Denies the is_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-minimizable", + "markdownDescription": "Denies the is_minimizable command without any pre-configured scope." + }, + { + "description": "Denies the is_minimized command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-minimized", + "markdownDescription": "Denies the is_minimized command without any pre-configured scope." + }, + { + "description": "Denies the is_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-resizable", + "markdownDescription": "Denies the is_resizable command without any pre-configured scope." + }, + { + "description": "Denies the is_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-visible", + "markdownDescription": "Denies the is_visible command without any pre-configured scope." + }, + { + "description": "Denies the maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-maximize", + "markdownDescription": "Denies the maximize command without any pre-configured scope." + }, + { + "description": "Denies the minimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-minimize", + "markdownDescription": "Denies the minimize command without any pre-configured scope." + }, + { + "description": "Denies the monitor_from_point command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-monitor-from-point", + "markdownDescription": "Denies the monitor_from_point command without any pre-configured scope." + }, + { + "description": "Denies the outer_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-outer-position", + "markdownDescription": "Denies the outer_position command without any pre-configured scope." + }, + { + "description": "Denies the outer_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-outer-size", + "markdownDescription": "Denies the outer_size command without any pre-configured scope." + }, + { + "description": "Denies the primary_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-primary-monitor", + "markdownDescription": "Denies the primary_monitor command without any pre-configured scope." + }, + { + "description": "Denies the request_user_attention command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-request-user-attention", + "markdownDescription": "Denies the request_user_attention command without any pre-configured scope." + }, + { + "description": "Denies the scale_factor command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-scale-factor", + "markdownDescription": "Denies the scale_factor command without any pre-configured scope." + }, + { + "description": "Denies the scene_identifier command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-scene-identifier", + "markdownDescription": "Denies the scene_identifier command without any pre-configured scope." + }, + { + "description": "Denies the set_always_on_bottom command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-always-on-bottom", + "markdownDescription": "Denies the set_always_on_bottom command without any pre-configured scope." + }, + { + "description": "Denies the set_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-always-on-top", + "markdownDescription": "Denies the set_always_on_top command without any pre-configured scope." + }, + { + "description": "Denies the set_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-background-color", + "markdownDescription": "Denies the set_background_color command without any pre-configured scope." + }, + { + "description": "Denies the set_badge_count command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-badge-count", + "markdownDescription": "Denies the set_badge_count command without any pre-configured scope." + }, + { + "description": "Denies the set_badge_label command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-badge-label", + "markdownDescription": "Denies the set_badge_label command without any pre-configured scope." + }, + { + "description": "Denies the set_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-closable", + "markdownDescription": "Denies the set_closable command without any pre-configured scope." + }, + { + "description": "Denies the set_content_protected command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-content-protected", + "markdownDescription": "Denies the set_content_protected command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_grab command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-grab", + "markdownDescription": "Denies the set_cursor_grab command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-icon", + "markdownDescription": "Denies the set_cursor_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-position", + "markdownDescription": "Denies the set_cursor_position command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-visible", + "markdownDescription": "Denies the set_cursor_visible command without any pre-configured scope." + }, + { + "description": "Denies the set_decorations command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-decorations", + "markdownDescription": "Denies the set_decorations command without any pre-configured scope." + }, + { + "description": "Denies the set_effects command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-effects", + "markdownDescription": "Denies the set_effects command without any pre-configured scope." + }, + { + "description": "Denies the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-enabled", + "markdownDescription": "Denies the set_enabled command without any pre-configured scope." + }, + { + "description": "Denies the set_focus command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-focus", + "markdownDescription": "Denies the set_focus command without any pre-configured scope." + }, + { + "description": "Denies the set_focusable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-focusable", + "markdownDescription": "Denies the set_focusable command without any pre-configured scope." + }, + { + "description": "Denies the set_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-fullscreen", + "markdownDescription": "Denies the set_fullscreen command without any pre-configured scope." + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-icon", + "markdownDescription": "Denies the set_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_ignore_cursor_events command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-ignore-cursor-events", + "markdownDescription": "Denies the set_ignore_cursor_events command without any pre-configured scope." + }, + { + "description": "Denies the set_max_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-max-size", + "markdownDescription": "Denies the set_max_size command without any pre-configured scope." + }, + { + "description": "Denies the set_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-maximizable", + "markdownDescription": "Denies the set_maximizable command without any pre-configured scope." + }, + { + "description": "Denies the set_min_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-min-size", + "markdownDescription": "Denies the set_min_size command without any pre-configured scope." + }, + { + "description": "Denies the set_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-minimizable", + "markdownDescription": "Denies the set_minimizable command without any pre-configured scope." + }, + { + "description": "Denies the set_overlay_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-overlay-icon", + "markdownDescription": "Denies the set_overlay_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-position", + "markdownDescription": "Denies the set_position command without any pre-configured scope." + }, + { + "description": "Denies the set_progress_bar command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-progress-bar", + "markdownDescription": "Denies the set_progress_bar command without any pre-configured scope." + }, + { + "description": "Denies the set_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-resizable", + "markdownDescription": "Denies the set_resizable command without any pre-configured scope." + }, + { + "description": "Denies the set_shadow command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-shadow", + "markdownDescription": "Denies the set_shadow command without any pre-configured scope." + }, + { + "description": "Denies the set_simple_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-simple-fullscreen", + "markdownDescription": "Denies the set_simple_fullscreen command without any pre-configured scope." + }, + { + "description": "Denies the set_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-size", + "markdownDescription": "Denies the set_size command without any pre-configured scope." + }, + { + "description": "Denies the set_size_constraints command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-size-constraints", + "markdownDescription": "Denies the set_size_constraints command without any pre-configured scope." + }, + { + "description": "Denies the set_skip_taskbar command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-skip-taskbar", + "markdownDescription": "Denies the set_skip_taskbar command without any pre-configured scope." + }, + { + "description": "Denies the set_theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-theme", + "markdownDescription": "Denies the set_theme command without any pre-configured scope." + }, + { + "description": "Denies the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-title", + "markdownDescription": "Denies the set_title command without any pre-configured scope." + }, + { + "description": "Denies the set_title_bar_style command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-title-bar-style", + "markdownDescription": "Denies the set_title_bar_style command without any pre-configured scope." + }, + { + "description": "Denies the set_visible_on_all_workspaces command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-visible-on-all-workspaces", + "markdownDescription": "Denies the set_visible_on_all_workspaces command without any pre-configured scope." + }, + { + "description": "Denies the show command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-show", + "markdownDescription": "Denies the show command without any pre-configured scope." + }, + { + "description": "Denies the start_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-start-dragging", + "markdownDescription": "Denies the start_dragging command without any pre-configured scope." + }, + { + "description": "Denies the start_resize_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-start-resize-dragging", + "markdownDescription": "Denies the start_resize_dragging command without any pre-configured scope." + }, + { + "description": "Denies the theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-theme", + "markdownDescription": "Denies the theme command without any pre-configured scope." + }, + { + "description": "Denies the title command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-title", + "markdownDescription": "Denies the title command without any pre-configured scope." + }, + { + "description": "Denies the toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-toggle-maximize", + "markdownDescription": "Denies the toggle_maximize command without any pre-configured scope." + }, + { + "description": "Denies the unmaximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-unmaximize", + "markdownDescription": "Denies the unmaximize command without any pre-configured scope." + }, + { + "description": "Denies the unminimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-unminimize", + "markdownDescription": "Denies the unminimize command without any pre-configured scope." + }, + { + "description": "Allows reading the opened deep link via the get_current command\n#### This default permission set includes:\n\n- `allow-get-current`", + "type": "string", + "const": "deep-link:default", + "markdownDescription": "Allows reading the opened deep link via the get_current command\n#### This default permission set includes:\n\n- `allow-get-current`" + }, + { + "description": "Enables the get_current command without any pre-configured scope.", + "type": "string", + "const": "deep-link:allow-get-current", + "markdownDescription": "Enables the get_current command without any pre-configured scope." + }, + { + "description": "Enables the is_registered command without any pre-configured scope.", + "type": "string", + "const": "deep-link:allow-is-registered", + "markdownDescription": "Enables the is_registered command without any pre-configured scope." + }, + { + "description": "Enables the register command without any pre-configured scope.", + "type": "string", + "const": "deep-link:allow-register", + "markdownDescription": "Enables the register command without any pre-configured scope." + }, + { + "description": "Enables the unregister command without any pre-configured scope.", + "type": "string", + "const": "deep-link:allow-unregister", + "markdownDescription": "Enables the unregister command without any pre-configured scope." + }, + { + "description": "Denies the get_current command without any pre-configured scope.", + "type": "string", + "const": "deep-link:deny-get-current", + "markdownDescription": "Denies the get_current command without any pre-configured scope." + }, + { + "description": "Denies the is_registered command without any pre-configured scope.", + "type": "string", + "const": "deep-link:deny-is-registered", + "markdownDescription": "Denies the is_registered command without any pre-configured scope." + }, + { + "description": "Denies the register command without any pre-configured scope.", + "type": "string", + "const": "deep-link:deny-register", + "markdownDescription": "Denies the register command without any pre-configured scope." + }, + { + "description": "Denies the unregister command without any pre-configured scope.", + "type": "string", + "const": "deep-link:deny-unregister", + "markdownDescription": "Denies the unregister command without any pre-configured scope." + }, + { + "description": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-message`\n- `allow-save`\n- `allow-open`", + "type": "string", + "const": "dialog:default", + "markdownDescription": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-message`\n- `allow-save`\n- `allow-open`" + }, + { + "description": "Enables the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)", + "type": "string", + "const": "dialog:allow-ask", + "markdownDescription": "Enables the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)" + }, + { + "description": "Enables the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)", + "type": "string", + "const": "dialog:allow-confirm", + "markdownDescription": "Enables the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)" + }, + { + "description": "Enables the message command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-message", + "markdownDescription": "Enables the message command without any pre-configured scope." + }, + { + "description": "Enables the open command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-open", + "markdownDescription": "Enables the open command without any pre-configured scope." + }, + { + "description": "Enables the save command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-save", + "markdownDescription": "Enables the save command without any pre-configured scope." + }, + { + "description": "Denies the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)", + "type": "string", + "const": "dialog:deny-ask", + "markdownDescription": "Denies the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)" + }, + { + "description": "Denies the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)", + "type": "string", + "const": "dialog:deny-confirm", + "markdownDescription": "Denies the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)" + }, + { + "description": "Denies the message command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-message", + "markdownDescription": "Denies the message command without any pre-configured scope." + }, + { + "description": "Denies the open command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-open", + "markdownDescription": "Denies the open command without any pre-configured scope." + }, + { + "description": "Denies the save command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-save", + "markdownDescription": "Denies the save command without any pre-configured scope." + }, + { + "description": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n#### This default permission set includes:\n\n- `create-app-specific-dirs`\n- `read-app-specific-dirs-recursive`\n- `deny-default`", + "type": "string", + "const": "fs:default", + "markdownDescription": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n#### This default permission set includes:\n\n- `create-app-specific-dirs`\n- `read-app-specific-dirs-recursive`\n- `deny-default`" + }, + { + "description": "This allows non-recursive read access to metadata of the application folders, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-app-index`", + "type": "string", + "const": "fs:allow-app-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the application folders, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-app-index`" + }, + { + "description": "This allows full recursive read access to metadata of the application folders, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-app-recursive`", + "type": "string", + "const": "fs:allow-app-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the application folders, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-app-recursive`" + }, + { + "description": "This allows non-recursive read access to the application folders.\n#### This permission set includes:\n\n- `read-all`\n- `scope-app`", + "type": "string", + "const": "fs:allow-app-read", + "markdownDescription": "This allows non-recursive read access to the application folders.\n#### This permission set includes:\n\n- `read-all`\n- `scope-app`" + }, + { + "description": "This allows full recursive read access to the complete application folders, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-app-recursive`", + "type": "string", + "const": "fs:allow-app-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete application folders, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-app-recursive`" + }, + { + "description": "This allows non-recursive write access to the application folders.\n#### This permission set includes:\n\n- `write-all`\n- `scope-app`", + "type": "string", + "const": "fs:allow-app-write", + "markdownDescription": "This allows non-recursive write access to the application folders.\n#### This permission set includes:\n\n- `write-all`\n- `scope-app`" + }, + { + "description": "This allows full recursive write access to the complete application folders, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-app-recursive`", + "type": "string", + "const": "fs:allow-app-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete application folders, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-app-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appcache-index`", + "type": "string", + "const": "fs:allow-appcache-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appcache-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appcache-recursive`", + "type": "string", + "const": "fs:allow-appcache-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appcache-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPCACHE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appcache`", + "type": "string", + "const": "fs:allow-appcache-read", + "markdownDescription": "This allows non-recursive read access to the `$APPCACHE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appcache`" + }, + { + "description": "This allows full recursive read access to the complete `$APPCACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appcache-recursive`", + "type": "string", + "const": "fs:allow-appcache-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPCACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appcache-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPCACHE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appcache`", + "type": "string", + "const": "fs:allow-appcache-write", + "markdownDescription": "This allows non-recursive write access to the `$APPCACHE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appcache`" + }, + { + "description": "This allows full recursive write access to the complete `$APPCACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appcache-recursive`", + "type": "string", + "const": "fs:allow-appcache-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPCACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appcache-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appconfig-index`", + "type": "string", + "const": "fs:allow-appconfig-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appconfig-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appconfig-recursive`", + "type": "string", + "const": "fs:allow-appconfig-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appconfig-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPCONFIG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appconfig`", + "type": "string", + "const": "fs:allow-appconfig-read", + "markdownDescription": "This allows non-recursive read access to the `$APPCONFIG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appconfig`" + }, + { + "description": "This allows full recursive read access to the complete `$APPCONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appconfig-recursive`", + "type": "string", + "const": "fs:allow-appconfig-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPCONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appconfig-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPCONFIG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appconfig`", + "type": "string", + "const": "fs:allow-appconfig-write", + "markdownDescription": "This allows non-recursive write access to the `$APPCONFIG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appconfig`" + }, + { + "description": "This allows full recursive write access to the complete `$APPCONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appconfig-recursive`", + "type": "string", + "const": "fs:allow-appconfig-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPCONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appconfig-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appdata-index`", + "type": "string", + "const": "fs:allow-appdata-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appdata-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appdata-recursive`", + "type": "string", + "const": "fs:allow-appdata-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appdata-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appdata`", + "type": "string", + "const": "fs:allow-appdata-read", + "markdownDescription": "This allows non-recursive read access to the `$APPDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appdata`" + }, + { + "description": "This allows full recursive read access to the complete `$APPDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appdata-recursive`", + "type": "string", + "const": "fs:allow-appdata-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appdata-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appdata`", + "type": "string", + "const": "fs:allow-appdata-write", + "markdownDescription": "This allows non-recursive write access to the `$APPDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appdata`" + }, + { + "description": "This allows full recursive write access to the complete `$APPDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appdata-recursive`", + "type": "string", + "const": "fs:allow-appdata-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appdata-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applocaldata-index`", + "type": "string", + "const": "fs:allow-applocaldata-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applocaldata-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applocaldata-recursive`", + "type": "string", + "const": "fs:allow-applocaldata-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applocaldata-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPLOCALDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applocaldata`", + "type": "string", + "const": "fs:allow-applocaldata-read", + "markdownDescription": "This allows non-recursive read access to the `$APPLOCALDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applocaldata`" + }, + { + "description": "This allows full recursive read access to the complete `$APPLOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applocaldata-recursive`", + "type": "string", + "const": "fs:allow-applocaldata-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPLOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applocaldata-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPLOCALDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applocaldata`", + "type": "string", + "const": "fs:allow-applocaldata-write", + "markdownDescription": "This allows non-recursive write access to the `$APPLOCALDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applocaldata`" + }, + { + "description": "This allows full recursive write access to the complete `$APPLOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applocaldata-recursive`", + "type": "string", + "const": "fs:allow-applocaldata-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPLOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applocaldata-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applog-index`", + "type": "string", + "const": "fs:allow-applog-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applog-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applog-recursive`", + "type": "string", + "const": "fs:allow-applog-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applog-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPLOG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applog`", + "type": "string", + "const": "fs:allow-applog-read", + "markdownDescription": "This allows non-recursive read access to the `$APPLOG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applog`" + }, + { + "description": "This allows full recursive read access to the complete `$APPLOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applog-recursive`", + "type": "string", + "const": "fs:allow-applog-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPLOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applog-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$APPLOG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applog`", + "type": "string", + "const": "fs:allow-applog-write", + "markdownDescription": "This allows non-recursive write access to the `$APPLOG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applog`" + }, + { + "description": "This allows full recursive write access to the complete `$APPLOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applog-recursive`", + "type": "string", + "const": "fs:allow-applog-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPLOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applog-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-audio-index`", + "type": "string", + "const": "fs:allow-audio-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-audio-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-audio-recursive`", + "type": "string", + "const": "fs:allow-audio-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-audio-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$AUDIO` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-audio`", + "type": "string", + "const": "fs:allow-audio-read", + "markdownDescription": "This allows non-recursive read access to the `$AUDIO` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-audio`" + }, + { + "description": "This allows full recursive read access to the complete `$AUDIO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-audio-recursive`", + "type": "string", + "const": "fs:allow-audio-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$AUDIO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-audio-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$AUDIO` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-audio`", + "type": "string", + "const": "fs:allow-audio-write", + "markdownDescription": "This allows non-recursive write access to the `$AUDIO` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-audio`" + }, + { + "description": "This allows full recursive write access to the complete `$AUDIO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-audio-recursive`", + "type": "string", + "const": "fs:allow-audio-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$AUDIO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-audio-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-cache-index`", + "type": "string", + "const": "fs:allow-cache-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-cache-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-cache-recursive`", + "type": "string", + "const": "fs:allow-cache-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-cache-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$CACHE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-cache`", + "type": "string", + "const": "fs:allow-cache-read", + "markdownDescription": "This allows non-recursive read access to the `$CACHE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-cache`" + }, + { + "description": "This allows full recursive read access to the complete `$CACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-cache-recursive`", + "type": "string", + "const": "fs:allow-cache-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$CACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-cache-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$CACHE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-cache`", + "type": "string", + "const": "fs:allow-cache-write", + "markdownDescription": "This allows non-recursive write access to the `$CACHE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-cache`" + }, + { + "description": "This allows full recursive write access to the complete `$CACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-cache-recursive`", + "type": "string", + "const": "fs:allow-cache-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$CACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-cache-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-config-index`", + "type": "string", + "const": "fs:allow-config-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-config-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-config-recursive`", + "type": "string", + "const": "fs:allow-config-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-config-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$CONFIG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-config`", + "type": "string", + "const": "fs:allow-config-read", + "markdownDescription": "This allows non-recursive read access to the `$CONFIG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-config`" + }, + { + "description": "This allows full recursive read access to the complete `$CONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-config-recursive`", + "type": "string", + "const": "fs:allow-config-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$CONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-config-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$CONFIG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-config`", + "type": "string", + "const": "fs:allow-config-write", + "markdownDescription": "This allows non-recursive write access to the `$CONFIG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-config`" + }, + { + "description": "This allows full recursive write access to the complete `$CONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-config-recursive`", + "type": "string", + "const": "fs:allow-config-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$CONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-config-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$DATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-data-index`", + "type": "string", + "const": "fs:allow-data-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$DATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-data-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$DATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-data-recursive`", + "type": "string", + "const": "fs:allow-data-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$DATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-data-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$DATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-data`", + "type": "string", + "const": "fs:allow-data-read", + "markdownDescription": "This allows non-recursive read access to the `$DATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-data`" + }, + { + "description": "This allows full recursive read access to the complete `$DATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-data-recursive`", + "type": "string", + "const": "fs:allow-data-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$DATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-data-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$DATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-data`", + "type": "string", + "const": "fs:allow-data-write", + "markdownDescription": "This allows non-recursive write access to the `$DATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-data`" + }, + { + "description": "This allows full recursive write access to the complete `$DATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-data-recursive`", + "type": "string", + "const": "fs:allow-data-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$DATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-data-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-desktop-index`", + "type": "string", + "const": "fs:allow-desktop-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-desktop-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-desktop-recursive`", + "type": "string", + "const": "fs:allow-desktop-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-desktop-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$DESKTOP` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-desktop`", + "type": "string", + "const": "fs:allow-desktop-read", + "markdownDescription": "This allows non-recursive read access to the `$DESKTOP` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-desktop`" + }, + { + "description": "This allows full recursive read access to the complete `$DESKTOP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-desktop-recursive`", + "type": "string", + "const": "fs:allow-desktop-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$DESKTOP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-desktop-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$DESKTOP` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-desktop`", + "type": "string", + "const": "fs:allow-desktop-write", + "markdownDescription": "This allows non-recursive write access to the `$DESKTOP` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-desktop`" + }, + { + "description": "This allows full recursive write access to the complete `$DESKTOP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-desktop-recursive`", + "type": "string", + "const": "fs:allow-desktop-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$DESKTOP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-desktop-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-document-index`", + "type": "string", + "const": "fs:allow-document-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-document-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-document-recursive`", + "type": "string", + "const": "fs:allow-document-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-document-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$DOCUMENT` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-document`", + "type": "string", + "const": "fs:allow-document-read", + "markdownDescription": "This allows non-recursive read access to the `$DOCUMENT` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-document`" + }, + { + "description": "This allows full recursive read access to the complete `$DOCUMENT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-document-recursive`", + "type": "string", + "const": "fs:allow-document-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$DOCUMENT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-document-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$DOCUMENT` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-document`", + "type": "string", + "const": "fs:allow-document-write", + "markdownDescription": "This allows non-recursive write access to the `$DOCUMENT` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-document`" + }, + { + "description": "This allows full recursive write access to the complete `$DOCUMENT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-document-recursive`", + "type": "string", + "const": "fs:allow-document-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$DOCUMENT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-document-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-download-index`", + "type": "string", + "const": "fs:allow-download-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-download-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-download-recursive`", + "type": "string", + "const": "fs:allow-download-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-download-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$DOWNLOAD` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-download`", + "type": "string", + "const": "fs:allow-download-read", + "markdownDescription": "This allows non-recursive read access to the `$DOWNLOAD` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-download`" + }, + { + "description": "This allows full recursive read access to the complete `$DOWNLOAD` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-download-recursive`", + "type": "string", + "const": "fs:allow-download-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$DOWNLOAD` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-download-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$DOWNLOAD` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-download`", + "type": "string", + "const": "fs:allow-download-write", + "markdownDescription": "This allows non-recursive write access to the `$DOWNLOAD` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-download`" + }, + { + "description": "This allows full recursive write access to the complete `$DOWNLOAD` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-download-recursive`", + "type": "string", + "const": "fs:allow-download-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$DOWNLOAD` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-download-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$EXE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-exe-index`", + "type": "string", + "const": "fs:allow-exe-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$EXE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-exe-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$EXE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-exe-recursive`", + "type": "string", + "const": "fs:allow-exe-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$EXE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-exe-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$EXE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-exe`", + "type": "string", + "const": "fs:allow-exe-read", + "markdownDescription": "This allows non-recursive read access to the `$EXE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-exe`" + }, + { + "description": "This allows full recursive read access to the complete `$EXE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-exe-recursive`", + "type": "string", + "const": "fs:allow-exe-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$EXE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-exe-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$EXE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-exe`", + "type": "string", + "const": "fs:allow-exe-write", + "markdownDescription": "This allows non-recursive write access to the `$EXE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-exe`" + }, + { + "description": "This allows full recursive write access to the complete `$EXE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-exe-recursive`", + "type": "string", + "const": "fs:allow-exe-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$EXE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-exe-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$FONT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-font-index`", + "type": "string", + "const": "fs:allow-font-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$FONT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-font-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$FONT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-font-recursive`", + "type": "string", + "const": "fs:allow-font-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$FONT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-font-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$FONT` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-font`", + "type": "string", + "const": "fs:allow-font-read", + "markdownDescription": "This allows non-recursive read access to the `$FONT` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-font`" + }, + { + "description": "This allows full recursive read access to the complete `$FONT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-font-recursive`", + "type": "string", + "const": "fs:allow-font-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$FONT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-font-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$FONT` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-font`", + "type": "string", + "const": "fs:allow-font-write", + "markdownDescription": "This allows non-recursive write access to the `$FONT` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-font`" + }, + { + "description": "This allows full recursive write access to the complete `$FONT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-font-recursive`", + "type": "string", + "const": "fs:allow-font-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$FONT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-font-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$HOME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-home-index`", + "type": "string", + "const": "fs:allow-home-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$HOME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-home-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$HOME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-home-recursive`", + "type": "string", + "const": "fs:allow-home-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$HOME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-home-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$HOME` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-home`", + "type": "string", + "const": "fs:allow-home-read", + "markdownDescription": "This allows non-recursive read access to the `$HOME` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-home`" + }, + { + "description": "This allows full recursive read access to the complete `$HOME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-home-recursive`", + "type": "string", + "const": "fs:allow-home-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$HOME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-home-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$HOME` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-home`", + "type": "string", + "const": "fs:allow-home-write", + "markdownDescription": "This allows non-recursive write access to the `$HOME` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-home`" + }, + { + "description": "This allows full recursive write access to the complete `$HOME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-home-recursive`", + "type": "string", + "const": "fs:allow-home-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$HOME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-home-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-localdata-index`", + "type": "string", + "const": "fs:allow-localdata-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-localdata-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-localdata-recursive`", + "type": "string", + "const": "fs:allow-localdata-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-localdata-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$LOCALDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-localdata`", + "type": "string", + "const": "fs:allow-localdata-read", + "markdownDescription": "This allows non-recursive read access to the `$LOCALDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-localdata`" + }, + { + "description": "This allows full recursive read access to the complete `$LOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-localdata-recursive`", + "type": "string", + "const": "fs:allow-localdata-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$LOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-localdata-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$LOCALDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-localdata`", + "type": "string", + "const": "fs:allow-localdata-write", + "markdownDescription": "This allows non-recursive write access to the `$LOCALDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-localdata`" + }, + { + "description": "This allows full recursive write access to the complete `$LOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-localdata-recursive`", + "type": "string", + "const": "fs:allow-localdata-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$LOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-localdata-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$LOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-log-index`", + "type": "string", + "const": "fs:allow-log-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$LOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-log-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$LOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-log-recursive`", + "type": "string", + "const": "fs:allow-log-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$LOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-log-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$LOG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-log`", + "type": "string", + "const": "fs:allow-log-read", + "markdownDescription": "This allows non-recursive read access to the `$LOG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-log`" + }, + { + "description": "This allows full recursive read access to the complete `$LOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-log-recursive`", + "type": "string", + "const": "fs:allow-log-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$LOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-log-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$LOG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-log`", + "type": "string", + "const": "fs:allow-log-write", + "markdownDescription": "This allows non-recursive write access to the `$LOG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-log`" + }, + { + "description": "This allows full recursive write access to the complete `$LOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-log-recursive`", + "type": "string", + "const": "fs:allow-log-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$LOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-log-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-picture-index`", + "type": "string", + "const": "fs:allow-picture-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-picture-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-picture-recursive`", + "type": "string", + "const": "fs:allow-picture-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-picture-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$PICTURE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-picture`", + "type": "string", + "const": "fs:allow-picture-read", + "markdownDescription": "This allows non-recursive read access to the `$PICTURE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-picture`" + }, + { + "description": "This allows full recursive read access to the complete `$PICTURE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-picture-recursive`", + "type": "string", + "const": "fs:allow-picture-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$PICTURE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-picture-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$PICTURE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-picture`", + "type": "string", + "const": "fs:allow-picture-write", + "markdownDescription": "This allows non-recursive write access to the `$PICTURE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-picture`" + }, + { + "description": "This allows full recursive write access to the complete `$PICTURE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-picture-recursive`", + "type": "string", + "const": "fs:allow-picture-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$PICTURE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-picture-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-public-index`", + "type": "string", + "const": "fs:allow-public-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-public-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-public-recursive`", + "type": "string", + "const": "fs:allow-public-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-public-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$PUBLIC` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-public`", + "type": "string", + "const": "fs:allow-public-read", + "markdownDescription": "This allows non-recursive read access to the `$PUBLIC` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-public`" + }, + { + "description": "This allows full recursive read access to the complete `$PUBLIC` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-public-recursive`", + "type": "string", + "const": "fs:allow-public-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$PUBLIC` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-public-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$PUBLIC` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-public`", + "type": "string", + "const": "fs:allow-public-write", + "markdownDescription": "This allows non-recursive write access to the `$PUBLIC` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-public`" + }, + { + "description": "This allows full recursive write access to the complete `$PUBLIC` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-public-recursive`", + "type": "string", + "const": "fs:allow-public-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$PUBLIC` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-public-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-resource-index`", + "type": "string", + "const": "fs:allow-resource-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-resource-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-resource-recursive`", + "type": "string", + "const": "fs:allow-resource-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-resource-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$RESOURCE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-resource`", + "type": "string", + "const": "fs:allow-resource-read", + "markdownDescription": "This allows non-recursive read access to the `$RESOURCE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-resource`" + }, + { + "description": "This allows full recursive read access to the complete `$RESOURCE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-resource-recursive`", + "type": "string", + "const": "fs:allow-resource-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$RESOURCE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-resource-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$RESOURCE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-resource`", + "type": "string", + "const": "fs:allow-resource-write", + "markdownDescription": "This allows non-recursive write access to the `$RESOURCE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-resource`" + }, + { + "description": "This allows full recursive write access to the complete `$RESOURCE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-resource-recursive`", + "type": "string", + "const": "fs:allow-resource-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$RESOURCE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-resource-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-runtime-index`", + "type": "string", + "const": "fs:allow-runtime-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-runtime-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-runtime-recursive`", + "type": "string", + "const": "fs:allow-runtime-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-runtime-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$RUNTIME` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-runtime`", + "type": "string", + "const": "fs:allow-runtime-read", + "markdownDescription": "This allows non-recursive read access to the `$RUNTIME` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-runtime`" + }, + { + "description": "This allows full recursive read access to the complete `$RUNTIME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-runtime-recursive`", + "type": "string", + "const": "fs:allow-runtime-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$RUNTIME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-runtime-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$RUNTIME` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-runtime`", + "type": "string", + "const": "fs:allow-runtime-write", + "markdownDescription": "This allows non-recursive write access to the `$RUNTIME` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-runtime`" + }, + { + "description": "This allows full recursive write access to the complete `$RUNTIME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-runtime-recursive`", + "type": "string", + "const": "fs:allow-runtime-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$RUNTIME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-runtime-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-temp-index`", + "type": "string", + "const": "fs:allow-temp-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-temp-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-temp-recursive`", + "type": "string", + "const": "fs:allow-temp-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-temp-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$TEMP` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-temp`", + "type": "string", + "const": "fs:allow-temp-read", + "markdownDescription": "This allows non-recursive read access to the `$TEMP` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-temp`" + }, + { + "description": "This allows full recursive read access to the complete `$TEMP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-temp-recursive`", + "type": "string", + "const": "fs:allow-temp-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$TEMP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-temp-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$TEMP` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-temp`", + "type": "string", + "const": "fs:allow-temp-write", + "markdownDescription": "This allows non-recursive write access to the `$TEMP` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-temp`" + }, + { + "description": "This allows full recursive write access to the complete `$TEMP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-temp-recursive`", + "type": "string", + "const": "fs:allow-temp-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$TEMP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-temp-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-template-index`", + "type": "string", + "const": "fs:allow-template-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-template-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-template-recursive`", + "type": "string", + "const": "fs:allow-template-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-template-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$TEMPLATE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-template`", + "type": "string", + "const": "fs:allow-template-read", + "markdownDescription": "This allows non-recursive read access to the `$TEMPLATE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-template`" + }, + { + "description": "This allows full recursive read access to the complete `$TEMPLATE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-template-recursive`", + "type": "string", + "const": "fs:allow-template-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$TEMPLATE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-template-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$TEMPLATE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-template`", + "type": "string", + "const": "fs:allow-template-write", + "markdownDescription": "This allows non-recursive write access to the `$TEMPLATE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-template`" + }, + { + "description": "This allows full recursive write access to the complete `$TEMPLATE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-template-recursive`", + "type": "string", + "const": "fs:allow-template-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$TEMPLATE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-template-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-video-index`", + "type": "string", + "const": "fs:allow-video-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-video-index`" + }, + { + "description": "This allows full recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-video-recursive`", + "type": "string", + "const": "fs:allow-video-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-video-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$VIDEO` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-video`", + "type": "string", + "const": "fs:allow-video-read", + "markdownDescription": "This allows non-recursive read access to the `$VIDEO` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-video`" + }, + { + "description": "This allows full recursive read access to the complete `$VIDEO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-video-recursive`", + "type": "string", + "const": "fs:allow-video-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$VIDEO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-video-recursive`" + }, + { + "description": "This allows non-recursive write access to the `$VIDEO` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-video`", + "type": "string", + "const": "fs:allow-video-write", + "markdownDescription": "This allows non-recursive write access to the `$VIDEO` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-video`" + }, + { + "description": "This allows full recursive write access to the complete `$VIDEO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-video-recursive`", + "type": "string", + "const": "fs:allow-video-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$VIDEO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-video-recursive`" + }, + { + "description": "This denies access to dangerous Tauri relevant files and folders by default.\n#### This permission set includes:\n\n- `deny-webview-data-linux`\n- `deny-webview-data-windows`", + "type": "string", + "const": "fs:deny-default", + "markdownDescription": "This denies access to dangerous Tauri relevant files and folders by default.\n#### This permission set includes:\n\n- `deny-webview-data-linux`\n- `deny-webview-data-windows`" + }, + { + "description": "Enables the copy_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-copy-file", + "markdownDescription": "Enables the copy_file command without any pre-configured scope." + }, + { + "description": "Enables the create command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-create", + "markdownDescription": "Enables the create command without any pre-configured scope." + }, + { + "description": "Enables the exists command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-exists", + "markdownDescription": "Enables the exists command without any pre-configured scope." + }, + { + "description": "Enables the fstat command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-fstat", + "markdownDescription": "Enables the fstat command without any pre-configured scope." + }, + { + "description": "Enables the ftruncate command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-ftruncate", + "markdownDescription": "Enables the ftruncate command without any pre-configured scope." + }, + { + "description": "Enables the lstat command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-lstat", + "markdownDescription": "Enables the lstat command without any pre-configured scope." + }, + { + "description": "Enables the mkdir command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-mkdir", + "markdownDescription": "Enables the mkdir command without any pre-configured scope." + }, + { + "description": "Enables the open command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-open", + "markdownDescription": "Enables the open command without any pre-configured scope." + }, + { + "description": "Enables the read command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read", + "markdownDescription": "Enables the read command without any pre-configured scope." + }, + { + "description": "Enables the read_dir command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-dir", + "markdownDescription": "Enables the read_dir command without any pre-configured scope." + }, + { + "description": "Enables the read_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-file", + "markdownDescription": "Enables the read_file command without any pre-configured scope." + }, + { + "description": "Enables the read_text_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-text-file", + "markdownDescription": "Enables the read_text_file command without any pre-configured scope." + }, + { + "description": "Enables the read_text_file_lines command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-text-file-lines", + "markdownDescription": "Enables the read_text_file_lines command without any pre-configured scope." + }, + { + "description": "Enables the read_text_file_lines_next command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-read-text-file-lines-next", + "markdownDescription": "Enables the read_text_file_lines_next command without any pre-configured scope." + }, + { + "description": "Enables the remove command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-remove", + "markdownDescription": "Enables the remove command without any pre-configured scope." + }, + { + "description": "Enables the rename command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-rename", + "markdownDescription": "Enables the rename command without any pre-configured scope." + }, + { + "description": "Enables the seek command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-seek", + "markdownDescription": "Enables the seek command without any pre-configured scope." + }, + { + "description": "Enables the size command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-size", + "markdownDescription": "Enables the size command without any pre-configured scope." + }, + { + "description": "Enables the start_accessing_security_scoped_resource command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-start-accessing-security-scoped-resource", + "markdownDescription": "Enables the start_accessing_security_scoped_resource command without any pre-configured scope." + }, + { + "description": "Enables the stat command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-stat", + "markdownDescription": "Enables the stat command without any pre-configured scope." + }, + { + "description": "Enables the stop_accessing_security_scoped_resource command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-stop-accessing-security-scoped-resource", + "markdownDescription": "Enables the stop_accessing_security_scoped_resource command without any pre-configured scope." + }, + { + "description": "Enables the truncate command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-truncate", + "markdownDescription": "Enables the truncate command without any pre-configured scope." + }, + { + "description": "Enables the unwatch command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-unwatch", + "markdownDescription": "Enables the unwatch command without any pre-configured scope." + }, + { + "description": "Enables the watch command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-watch", + "markdownDescription": "Enables the watch command without any pre-configured scope." + }, + { + "description": "Enables the write command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-write", + "markdownDescription": "Enables the write command without any pre-configured scope." + }, + { + "description": "Enables the write_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-write-file", + "markdownDescription": "Enables the write_file command without any pre-configured scope." + }, + { + "description": "Enables the write_text_file command without any pre-configured scope.", + "type": "string", + "const": "fs:allow-write-text-file", + "markdownDescription": "Enables the write_text_file command without any pre-configured scope." + }, + { + "description": "This permissions allows to create the application specific directories.\n", + "type": "string", + "const": "fs:create-app-specific-dirs", + "markdownDescription": "This permissions allows to create the application specific directories.\n" + }, + { + "description": "Denies the copy_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-copy-file", + "markdownDescription": "Denies the copy_file command without any pre-configured scope." + }, + { + "description": "Denies the create command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-create", + "markdownDescription": "Denies the create command without any pre-configured scope." + }, + { + "description": "Denies the exists command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-exists", + "markdownDescription": "Denies the exists command without any pre-configured scope." + }, + { + "description": "Denies the fstat command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-fstat", + "markdownDescription": "Denies the fstat command without any pre-configured scope." + }, + { + "description": "Denies the ftruncate command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-ftruncate", + "markdownDescription": "Denies the ftruncate command without any pre-configured scope." + }, + { + "description": "Denies the lstat command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-lstat", + "markdownDescription": "Denies the lstat command without any pre-configured scope." + }, + { + "description": "Denies the mkdir command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-mkdir", + "markdownDescription": "Denies the mkdir command without any pre-configured scope." + }, + { + "description": "Denies the open command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-open", + "markdownDescription": "Denies the open command without any pre-configured scope." + }, + { + "description": "Denies the read command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read", + "markdownDescription": "Denies the read command without any pre-configured scope." + }, + { + "description": "Denies the read_dir command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-dir", + "markdownDescription": "Denies the read_dir command without any pre-configured scope." + }, + { + "description": "Denies the read_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-file", + "markdownDescription": "Denies the read_file command without any pre-configured scope." + }, + { + "description": "Denies the read_text_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-text-file", + "markdownDescription": "Denies the read_text_file command without any pre-configured scope." + }, + { + "description": "Denies the read_text_file_lines command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-text-file-lines", + "markdownDescription": "Denies the read_text_file_lines command without any pre-configured scope." + }, + { + "description": "Denies the read_text_file_lines_next command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-read-text-file-lines-next", + "markdownDescription": "Denies the read_text_file_lines_next command without any pre-configured scope." + }, + { + "description": "Denies the remove command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-remove", + "markdownDescription": "Denies the remove command without any pre-configured scope." + }, + { + "description": "Denies the rename command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-rename", + "markdownDescription": "Denies the rename command without any pre-configured scope." + }, + { + "description": "Denies the seek command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-seek", + "markdownDescription": "Denies the seek command without any pre-configured scope." + }, + { + "description": "Denies the size command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-size", + "markdownDescription": "Denies the size command without any pre-configured scope." + }, + { + "description": "Denies the start_accessing_security_scoped_resource command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-start-accessing-security-scoped-resource", + "markdownDescription": "Denies the start_accessing_security_scoped_resource command without any pre-configured scope." + }, + { + "description": "Denies the stat command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-stat", + "markdownDescription": "Denies the stat command without any pre-configured scope." + }, + { + "description": "Denies the stop_accessing_security_scoped_resource command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-stop-accessing-security-scoped-resource", + "markdownDescription": "Denies the stop_accessing_security_scoped_resource command without any pre-configured scope." + }, + { + "description": "Denies the truncate command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-truncate", + "markdownDescription": "Denies the truncate command without any pre-configured scope." + }, + { + "description": "Denies the unwatch command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-unwatch", + "markdownDescription": "Denies the unwatch command without any pre-configured scope." + }, + { + "description": "Denies the watch command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-watch", + "markdownDescription": "Denies the watch command without any pre-configured scope." + }, + { + "description": "This denies read access to the\n`$APPLOCALDATA` folder on linux as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered.", + "type": "string", + "const": "fs:deny-webview-data-linux", + "markdownDescription": "This denies read access to the\n`$APPLOCALDATA` folder on linux as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered." + }, + { + "description": "This denies read access to the\n`$APPLOCALDATA/EBWebView` folder on windows as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered.", + "type": "string", + "const": "fs:deny-webview-data-windows", + "markdownDescription": "This denies read access to the\n`$APPLOCALDATA/EBWebView` folder on windows as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered." + }, + { + "description": "Denies the write command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-write", + "markdownDescription": "Denies the write command without any pre-configured scope." + }, + { + "description": "Denies the write_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-write-file", + "markdownDescription": "Denies the write_file command without any pre-configured scope." + }, + { + "description": "Denies the write_text_file command without any pre-configured scope.", + "type": "string", + "const": "fs:deny-write-text-file", + "markdownDescription": "Denies the write_text_file command without any pre-configured scope." + }, + { + "description": "This enables all read related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:read-all", + "markdownDescription": "This enables all read related commands without any pre-configured accessible paths." + }, + { + "description": "This permission allows recursive read functionality on the application\nspecific base directories. \n", + "type": "string", + "const": "fs:read-app-specific-dirs-recursive", + "markdownDescription": "This permission allows recursive read functionality on the application\nspecific base directories. \n" + }, + { + "description": "This enables directory read and file metadata related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:read-dirs", + "markdownDescription": "This enables directory read and file metadata related commands without any pre-configured accessible paths." + }, + { + "description": "This enables file read related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:read-files", + "markdownDescription": "This enables file read related commands without any pre-configured accessible paths." + }, + { + "description": "This enables all index or metadata related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:read-meta", + "markdownDescription": "This enables all index or metadata related commands without any pre-configured accessible paths." + }, + { + "description": "An empty permission you can use to modify the global scope.\n\n## Example\n\n```json\n{\n \"identifier\": \"read-documents\",\n \"windows\": [\"main\"],\n \"permissions\": [\n \"fs:allow-read\",\n {\n \"identifier\": \"fs:scope\",\n \"allow\": [\n \"$APPDATA/documents/**/*\"\n ],\n \"deny\": [\n \"$APPDATA/documents/secret.txt\"\n ]\n }\n ]\n}\n```\n", + "type": "string", + "const": "fs:scope", + "markdownDescription": "An empty permission you can use to modify the global scope.\n\n## Example\n\n```json\n{\n \"identifier\": \"read-documents\",\n \"windows\": [\"main\"],\n \"permissions\": [\n \"fs:allow-read\",\n {\n \"identifier\": \"fs:scope\",\n \"allow\": [\n \"$APPDATA/documents/**/*\"\n ],\n \"deny\": [\n \"$APPDATA/documents/secret.txt\"\n ]\n }\n ]\n}\n```\n" + }, + { + "description": "This scope permits access to all files and list content of top level directories in the application folders.", + "type": "string", + "const": "fs:scope-app", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the application folders." + }, + { + "description": "This scope permits to list all files and folders in the application directories.", + "type": "string", + "const": "fs:scope-app-index", + "markdownDescription": "This scope permits to list all files and folders in the application directories." + }, + { + "description": "This scope permits recursive access to the complete application folders, including sub directories and files.", + "type": "string", + "const": "fs:scope-app-recursive", + "markdownDescription": "This scope permits recursive access to the complete application folders, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPCACHE` folder.", + "type": "string", + "const": "fs:scope-appcache", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPCACHE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPCACHE`folder.", + "type": "string", + "const": "fs:scope-appcache-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPCACHE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPCACHE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-appcache-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPCACHE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPCONFIG` folder.", + "type": "string", + "const": "fs:scope-appconfig", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPCONFIG` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPCONFIG`folder.", + "type": "string", + "const": "fs:scope-appconfig-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPCONFIG`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPCONFIG` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-appconfig-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPCONFIG` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPDATA` folder.", + "type": "string", + "const": "fs:scope-appdata", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPDATA` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPDATA`folder.", + "type": "string", + "const": "fs:scope-appdata-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPDATA`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPDATA` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-appdata-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPDATA` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPLOCALDATA` folder.", + "type": "string", + "const": "fs:scope-applocaldata", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPLOCALDATA` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPLOCALDATA`folder.", + "type": "string", + "const": "fs:scope-applocaldata-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPLOCALDATA`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPLOCALDATA` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-applocaldata-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPLOCALDATA` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPLOG` folder.", + "type": "string", + "const": "fs:scope-applog", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPLOG` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPLOG`folder.", + "type": "string", + "const": "fs:scope-applog-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPLOG`folder." + }, + { + "description": "This scope permits recursive access to the complete `$APPLOG` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-applog-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPLOG` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$AUDIO` folder.", + "type": "string", + "const": "fs:scope-audio", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$AUDIO` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$AUDIO`folder.", + "type": "string", + "const": "fs:scope-audio-index", + "markdownDescription": "This scope permits to list all files and folders in the `$AUDIO`folder." + }, + { + "description": "This scope permits recursive access to the complete `$AUDIO` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-audio-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$AUDIO` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$CACHE` folder.", + "type": "string", + "const": "fs:scope-cache", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$CACHE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$CACHE`folder.", + "type": "string", + "const": "fs:scope-cache-index", + "markdownDescription": "This scope permits to list all files and folders in the `$CACHE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$CACHE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-cache-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$CACHE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$CONFIG` folder.", + "type": "string", + "const": "fs:scope-config", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$CONFIG` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$CONFIG`folder.", + "type": "string", + "const": "fs:scope-config-index", + "markdownDescription": "This scope permits to list all files and folders in the `$CONFIG`folder." + }, + { + "description": "This scope permits recursive access to the complete `$CONFIG` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-config-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$CONFIG` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$DATA` folder.", + "type": "string", + "const": "fs:scope-data", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$DATA` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$DATA`folder.", + "type": "string", + "const": "fs:scope-data-index", + "markdownDescription": "This scope permits to list all files and folders in the `$DATA`folder." + }, + { + "description": "This scope permits recursive access to the complete `$DATA` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-data-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$DATA` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$DESKTOP` folder.", + "type": "string", + "const": "fs:scope-desktop", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$DESKTOP` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$DESKTOP`folder.", + "type": "string", + "const": "fs:scope-desktop-index", + "markdownDescription": "This scope permits to list all files and folders in the `$DESKTOP`folder." + }, + { + "description": "This scope permits recursive access to the complete `$DESKTOP` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-desktop-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$DESKTOP` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$DOCUMENT` folder.", + "type": "string", + "const": "fs:scope-document", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$DOCUMENT` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$DOCUMENT`folder.", + "type": "string", + "const": "fs:scope-document-index", + "markdownDescription": "This scope permits to list all files and folders in the `$DOCUMENT`folder." + }, + { + "description": "This scope permits recursive access to the complete `$DOCUMENT` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-document-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$DOCUMENT` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$DOWNLOAD` folder.", + "type": "string", + "const": "fs:scope-download", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$DOWNLOAD` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$DOWNLOAD`folder.", + "type": "string", + "const": "fs:scope-download-index", + "markdownDescription": "This scope permits to list all files and folders in the `$DOWNLOAD`folder." + }, + { + "description": "This scope permits recursive access to the complete `$DOWNLOAD` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-download-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$DOWNLOAD` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$EXE` folder.", + "type": "string", + "const": "fs:scope-exe", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$EXE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$EXE`folder.", + "type": "string", + "const": "fs:scope-exe-index", + "markdownDescription": "This scope permits to list all files and folders in the `$EXE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$EXE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-exe-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$EXE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$FONT` folder.", + "type": "string", + "const": "fs:scope-font", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$FONT` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$FONT`folder.", + "type": "string", + "const": "fs:scope-font-index", + "markdownDescription": "This scope permits to list all files and folders in the `$FONT`folder." + }, + { + "description": "This scope permits recursive access to the complete `$FONT` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-font-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$FONT` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$HOME` folder.", + "type": "string", + "const": "fs:scope-home", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$HOME` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$HOME`folder.", + "type": "string", + "const": "fs:scope-home-index", + "markdownDescription": "This scope permits to list all files and folders in the `$HOME`folder." + }, + { + "description": "This scope permits recursive access to the complete `$HOME` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-home-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$HOME` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$LOCALDATA` folder.", + "type": "string", + "const": "fs:scope-localdata", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$LOCALDATA` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$LOCALDATA`folder.", + "type": "string", + "const": "fs:scope-localdata-index", + "markdownDescription": "This scope permits to list all files and folders in the `$LOCALDATA`folder." + }, + { + "description": "This scope permits recursive access to the complete `$LOCALDATA` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-localdata-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$LOCALDATA` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$LOG` folder.", + "type": "string", + "const": "fs:scope-log", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$LOG` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$LOG`folder.", + "type": "string", + "const": "fs:scope-log-index", + "markdownDescription": "This scope permits to list all files and folders in the `$LOG`folder." + }, + { + "description": "This scope permits recursive access to the complete `$LOG` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-log-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$LOG` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$PICTURE` folder.", + "type": "string", + "const": "fs:scope-picture", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$PICTURE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$PICTURE`folder.", + "type": "string", + "const": "fs:scope-picture-index", + "markdownDescription": "This scope permits to list all files and folders in the `$PICTURE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$PICTURE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-picture-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$PICTURE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$PUBLIC` folder.", + "type": "string", + "const": "fs:scope-public", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$PUBLIC` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$PUBLIC`folder.", + "type": "string", + "const": "fs:scope-public-index", + "markdownDescription": "This scope permits to list all files and folders in the `$PUBLIC`folder." + }, + { + "description": "This scope permits recursive access to the complete `$PUBLIC` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-public-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$PUBLIC` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$RESOURCE` folder.", + "type": "string", + "const": "fs:scope-resource", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$RESOURCE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$RESOURCE`folder.", + "type": "string", + "const": "fs:scope-resource-index", + "markdownDescription": "This scope permits to list all files and folders in the `$RESOURCE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$RESOURCE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-resource-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$RESOURCE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$RUNTIME` folder.", + "type": "string", + "const": "fs:scope-runtime", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$RUNTIME` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$RUNTIME`folder.", + "type": "string", + "const": "fs:scope-runtime-index", + "markdownDescription": "This scope permits to list all files and folders in the `$RUNTIME`folder." + }, + { + "description": "This scope permits recursive access to the complete `$RUNTIME` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-runtime-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$RUNTIME` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$TEMP` folder.", + "type": "string", + "const": "fs:scope-temp", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$TEMP` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$TEMP`folder.", + "type": "string", + "const": "fs:scope-temp-index", + "markdownDescription": "This scope permits to list all files and folders in the `$TEMP`folder." + }, + { + "description": "This scope permits recursive access to the complete `$TEMP` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-temp-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$TEMP` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$TEMPLATE` folder.", + "type": "string", + "const": "fs:scope-template", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$TEMPLATE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$TEMPLATE`folder.", + "type": "string", + "const": "fs:scope-template-index", + "markdownDescription": "This scope permits to list all files and folders in the `$TEMPLATE`folder." + }, + { + "description": "This scope permits recursive access to the complete `$TEMPLATE` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-template-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$TEMPLATE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$VIDEO` folder.", + "type": "string", + "const": "fs:scope-video", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$VIDEO` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$VIDEO`folder.", + "type": "string", + "const": "fs:scope-video-index", + "markdownDescription": "This scope permits to list all files and folders in the `$VIDEO`folder." + }, + { + "description": "This scope permits recursive access to the complete `$VIDEO` folder, including sub directories and files.", + "type": "string", + "const": "fs:scope-video-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$VIDEO` folder, including sub directories and files." + }, + { + "description": "This enables all write related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:write-all", + "markdownDescription": "This enables all write related commands without any pre-configured accessible paths." + }, + { + "description": "This enables all file write related commands without any pre-configured accessible paths.", + "type": "string", + "const": "fs:write-files", + "markdownDescription": "This enables all file write related commands without any pre-configured accessible paths." + }, + { + "description": "No features are enabled by default, as we believe\nthe shortcuts can be inherently dangerous and it is\napplication specific if specific shortcuts should be\nregistered or unregistered.\n", + "type": "string", + "const": "global-shortcut:default", + "markdownDescription": "No features are enabled by default, as we believe\nthe shortcuts can be inherently dangerous and it is\napplication specific if specific shortcuts should be\nregistered or unregistered.\n" + }, + { + "description": "Enables the is_registered command without any pre-configured scope.", + "type": "string", + "const": "global-shortcut:allow-is-registered", + "markdownDescription": "Enables the is_registered command without any pre-configured scope." + }, + { + "description": "Enables the register command without any pre-configured scope.", + "type": "string", + "const": "global-shortcut:allow-register", + "markdownDescription": "Enables the register command without any pre-configured scope." + }, + { + "description": "Enables the register_all command without any pre-configured scope.", + "type": "string", + "const": "global-shortcut:allow-register-all", + "markdownDescription": "Enables the register_all command without any pre-configured scope." + }, + { + "description": "Enables the unregister command without any pre-configured scope.", + "type": "string", + "const": "global-shortcut:allow-unregister", + "markdownDescription": "Enables the unregister command without any pre-configured scope." + }, + { + "description": "Enables the unregister_all command without any pre-configured scope.", + "type": "string", + "const": "global-shortcut:allow-unregister-all", + "markdownDescription": "Enables the unregister_all command without any pre-configured scope." + }, + { + "description": "Denies the is_registered command without any pre-configured scope.", + "type": "string", + "const": "global-shortcut:deny-is-registered", + "markdownDescription": "Denies the is_registered command without any pre-configured scope." + }, + { + "description": "Denies the register command without any pre-configured scope.", + "type": "string", + "const": "global-shortcut:deny-register", + "markdownDescription": "Denies the register command without any pre-configured scope." + }, + { + "description": "Denies the register_all command without any pre-configured scope.", + "type": "string", + "const": "global-shortcut:deny-register-all", + "markdownDescription": "Denies the register_all command without any pre-configured scope." + }, + { + "description": "Denies the unregister command without any pre-configured scope.", + "type": "string", + "const": "global-shortcut:deny-unregister", + "markdownDescription": "Denies the unregister command without any pre-configured scope." + }, + { + "description": "Denies the unregister_all command without any pre-configured scope.", + "type": "string", + "const": "global-shortcut:deny-unregister-all", + "markdownDescription": "Denies the unregister_all command without any pre-configured scope." + }, + { + "description": "This permission set configures which\nprocess features are by default exposed.\n\n#### Granted Permissions\n\nThis enables to quit via `allow-exit` and restart via `allow-restart`\nthe application.\n\n#### This default permission set includes:\n\n- `allow-exit`\n- `allow-restart`", + "type": "string", + "const": "process:default", + "markdownDescription": "This permission set configures which\nprocess features are by default exposed.\n\n#### Granted Permissions\n\nThis enables to quit via `allow-exit` and restart via `allow-restart`\nthe application.\n\n#### This default permission set includes:\n\n- `allow-exit`\n- `allow-restart`" + }, + { + "description": "Enables the exit command without any pre-configured scope.", + "type": "string", + "const": "process:allow-exit", + "markdownDescription": "Enables the exit command without any pre-configured scope." + }, + { + "description": "Enables the restart command without any pre-configured scope.", + "type": "string", + "const": "process:allow-restart", + "markdownDescription": "Enables the restart command without any pre-configured scope." + }, + { + "description": "Denies the exit command without any pre-configured scope.", + "type": "string", + "const": "process:deny-exit", + "markdownDescription": "Denies the exit command without any pre-configured scope." + }, + { + "description": "Denies the restart command without any pre-configured scope.", + "type": "string", + "const": "process:deny-restart", + "markdownDescription": "Denies the restart command without any pre-configured scope." + }, + { + "description": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n\n#### This default permission set includes:\n\n- `allow-open`", + "type": "string", + "const": "shell:default", + "markdownDescription": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n\n#### This default permission set includes:\n\n- `allow-open`" + }, + { + "description": "Enables the execute command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-execute", + "markdownDescription": "Enables the execute command without any pre-configured scope." + }, + { + "description": "Enables the kill command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-kill", + "markdownDescription": "Enables the kill command without any pre-configured scope." + }, + { + "description": "Enables the open command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-open", + "markdownDescription": "Enables the open command without any pre-configured scope." + }, + { + "description": "Enables the spawn command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-spawn", + "markdownDescription": "Enables the spawn command without any pre-configured scope." + }, + { + "description": "Enables the stdin_write command without any pre-configured scope.", + "type": "string", + "const": "shell:allow-stdin-write", + "markdownDescription": "Enables the stdin_write command without any pre-configured scope." + }, + { + "description": "Denies the execute command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-execute", + "markdownDescription": "Denies the execute command without any pre-configured scope." + }, + { + "description": "Denies the kill command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-kill", + "markdownDescription": "Denies the kill command without any pre-configured scope." + }, + { + "description": "Denies the open command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-open", + "markdownDescription": "Denies the open command without any pre-configured scope." + }, + { + "description": "Denies the spawn command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-spawn", + "markdownDescription": "Denies the spawn command without any pre-configured scope." + }, + { + "description": "Denies the stdin_write command without any pre-configured scope.", + "type": "string", + "const": "shell:deny-stdin-write", + "markdownDescription": "Denies the stdin_write command without any pre-configured scope." + }, + { + "description": "This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n\n#### This default permission set includes:\n\n- `allow-check`\n- `allow-download`\n- `allow-install`\n- `allow-download-and-install`", + "type": "string", + "const": "updater:default", + "markdownDescription": "This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n\n#### This default permission set includes:\n\n- `allow-check`\n- `allow-download`\n- `allow-install`\n- `allow-download-and-install`" + }, + { + "description": "Enables the check command without any pre-configured scope.", + "type": "string", + "const": "updater:allow-check", + "markdownDescription": "Enables the check command without any pre-configured scope." + }, + { + "description": "Enables the download command without any pre-configured scope.", + "type": "string", + "const": "updater:allow-download", + "markdownDescription": "Enables the download command without any pre-configured scope." + }, + { + "description": "Enables the download_and_install command without any pre-configured scope.", + "type": "string", + "const": "updater:allow-download-and-install", + "markdownDescription": "Enables the download_and_install command without any pre-configured scope." + }, + { + "description": "Enables the install command without any pre-configured scope.", + "type": "string", + "const": "updater:allow-install", + "markdownDescription": "Enables the install command without any pre-configured scope." + }, + { + "description": "Denies the check command without any pre-configured scope.", + "type": "string", + "const": "updater:deny-check", + "markdownDescription": "Denies the check command without any pre-configured scope." + }, + { + "description": "Denies the download command without any pre-configured scope.", + "type": "string", + "const": "updater:deny-download", + "markdownDescription": "Denies the download command without any pre-configured scope." + }, + { + "description": "Denies the download_and_install command without any pre-configured scope.", + "type": "string", + "const": "updater:deny-download-and-install", + "markdownDescription": "Denies the download_and_install command without any pre-configured scope." + }, + { + "description": "Denies the install command without any pre-configured scope.", + "type": "string", + "const": "updater:deny-install", + "markdownDescription": "Denies the install command without any pre-configured scope." + }, + { + "description": "This permission set configures what kind of\noperations are available from the window state plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n\n#### This default permission set includes:\n\n- `allow-filename`\n- `allow-restore-state`\n- `allow-save-window-state`", + "type": "string", + "const": "window-state:default", + "markdownDescription": "This permission set configures what kind of\noperations are available from the window state plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n\n#### This default permission set includes:\n\n- `allow-filename`\n- `allow-restore-state`\n- `allow-save-window-state`" + }, + { + "description": "Enables the filename command without any pre-configured scope.", + "type": "string", + "const": "window-state:allow-filename", + "markdownDescription": "Enables the filename command without any pre-configured scope." + }, + { + "description": "Enables the restore_state command without any pre-configured scope.", + "type": "string", + "const": "window-state:allow-restore-state", + "markdownDescription": "Enables the restore_state command without any pre-configured scope." + }, + { + "description": "Enables the save_window_state command without any pre-configured scope.", + "type": "string", + "const": "window-state:allow-save-window-state", + "markdownDescription": "Enables the save_window_state command without any pre-configured scope." + }, + { + "description": "Denies the filename command without any pre-configured scope.", + "type": "string", + "const": "window-state:deny-filename", + "markdownDescription": "Denies the filename command without any pre-configured scope." + }, + { + "description": "Denies the restore_state command without any pre-configured scope.", + "type": "string", + "const": "window-state:deny-restore-state", + "markdownDescription": "Denies the restore_state command without any pre-configured scope." + }, + { + "description": "Denies the save_window_state command without any pre-configured scope.", + "type": "string", + "const": "window-state:deny-save-window-state", + "markdownDescription": "Denies the save_window_state command without any pre-configured scope." + } + ] + }, + "Value": { + "description": "All supported ACL values.", + "anyOf": [ + { + "description": "Represents a null JSON value.", + "type": "null" + }, + { + "description": "Represents a [`bool`].", + "type": "boolean" + }, + { + "description": "Represents a valid ACL [`Number`].", + "allOf": [ + { + "$ref": "#/definitions/Number" + } + ] + }, + { + "description": "Represents a [`String`].", + "type": "string" + }, + { + "description": "Represents a list of other [`Value`]s.", + "type": "array", + "items": { + "$ref": "#/definitions/Value" + } + }, + { + "description": "Represents a map of [`String`] keys to [`Value`]s.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Value" + } + } + ] + }, + "Number": { + "description": "A valid ACL number.", + "anyOf": [ + { + "description": "Represents an [`i64`].", + "type": "integer", + "format": "int64" + }, + { + "description": "Represents a [`f64`].", + "type": "number", + "format": "double" + } + ] + }, + "Target": { + "description": "Platform target.", + "oneOf": [ + { + "description": "MacOS.", + "type": "string", + "enum": [ + "macOS" + ] + }, + { + "description": "Windows.", + "type": "string", + "enum": [ + "windows" + ] + }, + { + "description": "Linux.", + "type": "string", + "enum": [ + "linux" + ] + }, + { + "description": "Android.", + "type": "string", + "enum": [ + "android" + ] + }, + { + "description": "iOS.", + "type": "string", + "enum": [ + "iOS" + ] + } + ] + }, + "ShellScopeEntryAllowedArg": { + "description": "A command argument allowed to be executed by the webview API.", + "anyOf": [ + { + "description": "A non-configurable argument that is passed to the command in the order it was specified.", + "type": "string" + }, + { + "description": "A variable that is set while calling the command from the webview API.", + "type": "object", + "required": [ + "validator" + ], + "properties": { + "raw": { + "description": "Marks the validator as a raw regex, meaning the plugin should not make any modification at runtime.\n\nThis means the regex will not match on the entire string by default, which might be exploited if your regex allow unexpected input to be considered valid. When using this option, make sure your regex is correct.", + "default": false, + "type": "boolean" + }, + "validator": { + "description": "[regex] validator to require passed values to conform to an expected input.\n\nThis will require the argument value passed to this variable to match the `validator` regex before it will be executed.\n\nThe regex string is by default surrounded by `^...$` to match the full string. For example the `https?://\\w+` regex would be registered as `^https?://\\w+$`.\n\n[regex]: ", + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "ShellScopeEntryAllowedArgs": { + "description": "A set of command arguments allowed to be executed by the webview API.\n\nA value of `true` will allow any arguments to be passed to the command. `false` will disable all arguments. A list of [`ShellScopeEntryAllowedArg`] will set those arguments as the only valid arguments to be passed to the attached command configuration.", + "anyOf": [ + { + "description": "Use a simple boolean to allow all or disable all arguments to this command configuration.", + "type": "boolean" + }, + { + "description": "A specific set of [`ShellScopeEntryAllowedArg`] that are valid to call for the command configuration.", + "type": "array", + "items": { + "$ref": "#/definitions/ShellScopeEntryAllowedArg" + } + } + ] + } + } +} \ No newline at end of file diff --git a/src-tauri/src/commands/diagnostics.rs b/src-tauri/src/commands/diagnostics.rs index 39f0986a..e41378e1 100644 --- a/src-tauri/src/commands/diagnostics.rs +++ b/src-tauri/src/commands/diagnostics.rs @@ -7,14 +7,22 @@ use crate::diagnostics::{run_all, CheckCtx, DiagnosticReport}; use crate::error::{AppError, AppResult, IpcResult, MutexResultExt}; +#[cfg(target_os = "windows")] use crate::legacy_patcher::api::PATCHER_DLL_NAME; +#[cfg(target_os = "macos")] +use crate::patcher::backend::resolve_helper_path; +use crate::platform::LeagueInstall; use crate::state::{get_app_data_dir, SettingsState}; +#[cfg(target_os = "windows")] use std::path::PathBuf; -use tauri::{AppHandle, Manager, State}; +#[cfg(target_os = "windows")] +use tauri::Manager; +use tauri::{AppHandle, State}; /// Same lookup chain as `commands::patcher::resolve_patcher_dll_path`, but /// returns `None` instead of an error so we can still report the rest of the /// diagnostics when the DLL is missing. +#[cfg(target_os = "windows")] fn resolve_patcher_dll(app_handle: &AppHandle) -> Option { if let Ok(dir) = app_handle.path().resource_dir() { let p = dir.join(PATCHER_DLL_NAME); @@ -144,9 +152,16 @@ fn run_diagnostics_inner( .or_else(|| get_app_data_dir(app_handle)); let ctx = CheckCtx { league_path: snapshot.league_path.clone(), + league_install: snapshot + .league_path + .as_ref() + .and_then(|path| LeagueInstall::resolve(path).ok()), mod_storage_path, mod_storage_is_default: storage_is_default, + #[cfg(target_os = "windows")] patcher_dll_path: resolve_patcher_dll(app_handle), + #[cfg(target_os = "macos")] + patcher_helper_path: resolve_helper_path(app_handle), manager_exe: std::env::current_exe().ok(), }; let checks = run_all(&ctx); diff --git a/src-tauri/src/commands/hotkeys.rs b/src-tauri/src/commands/hotkeys.rs index fa166bb9..c12ed9cb 100644 --- a/src-tauri/src/commands/hotkeys.rs +++ b/src-tauri/src/commands/hotkeys.rs @@ -2,7 +2,9 @@ use crate::error::{AppError, AppResult, IpcResult, MutexResultExt}; use crate::hotkeys::{HotkeyAction, HotkeyManager}; use crate::mods::ModLibraryState; use crate::patcher::PatcherState; +use crate::platform::LeagueInstall; use crate::state::{save_settings_to_disk, SettingsState}; +#[cfg(target_os = "macos")] use std::path::Path; use std::process::Command; use std::sync::atomic::Ordering; @@ -42,7 +44,14 @@ pub(crate) fn execute_hot_reload(app_handle: &AppHandle) -> AppResult<()> { } wait_for_patcher_stop(&patcher_state)?; - kill_league_process(); + let league_install = { + let settings = settings_state.0.lock().mutex_err()?; + settings + .league_path + .as_ref() + .and_then(|path| LeagueInstall::resolve(path).ok()) + }; + kill_league_process(league_install.as_ref()); std::thread::sleep(std::time::Duration::from_millis(500)); let workshop_projects = config.workshop_projects.clone(); @@ -64,12 +73,14 @@ pub(crate) fn execute_hot_reload(app_handle: &AppHandle) -> AppResult<()> { )?; // Best-effort LCU reconnect (in background — retries take time) - let league_path = { + let league_install = { let s = settings_state.0.lock().mutex_err()?; - s.league_path.clone() + s.league_path + .as_ref() + .and_then(|path| LeagueInstall::resolve(path).ok()) }; - if let Some(path) = league_path { - std::thread::spawn(move || try_lcu_reconnect(&path)); + if let Some(install) = league_install { + std::thread::spawn(move || try_lcu_reconnect(&install)); } // Emit workshop project paths so frontend can re-sync testing state @@ -97,7 +108,14 @@ pub(crate) fn execute_kill_league(app_handle: &AppHandle) -> AppResult<()> { wait_for_patcher_stop(&patcher_state)?; } - kill_league_process(); + let league_install = { + let settings = settings_state.0.lock().mutex_err()?; + settings + .league_path + .as_ref() + .and_then(|path| LeagueInstall::resolve(path).ok()) + }; + kill_league_process(league_install.as_ref()); Ok(()) } @@ -220,7 +238,14 @@ fn hot_reload_mods_inner( } wait_for_patcher_stop(state)?; - kill_league_process(); + let league_install = { + let settings = settings.0.lock().mutex_err()?; + settings + .league_path + .as_ref() + .and_then(|path| LeagueInstall::resolve(path).ok()) + }; + kill_league_process(league_install.as_ref()); std::thread::sleep(std::time::Duration::from_millis(500)); let patcher_config = PatcherConfig { @@ -234,12 +259,14 @@ fn hot_reload_mods_inner( start_patcher_inner(patcher_config, app_handle, state, settings, library)?; // Best-effort LCU reconnect (in background — retries take time) - let league_path = { + let league_install = { let s = settings.0.lock().mutex_err()?; - s.league_path.clone() + s.league_path + .as_ref() + .and_then(|path| LeagueInstall::resolve(path).ok()) }; - if let Some(path) = league_path { - std::thread::spawn(move || try_lcu_reconnect(&path)); + if let Some(install) = league_install { + std::thread::spawn(move || try_lcu_reconnect(&install)); } Ok(()) @@ -270,7 +297,14 @@ fn kill_league_inner( wait_for_patcher_stop(state)?; } - kill_league_process(); + let league_install = { + let settings = settings.0.lock().mutex_err()?; + settings + .league_path + .as_ref() + .and_then(|path| LeagueInstall::resolve(path).ok()) + }; + kill_league_process(league_install.as_ref()); Ok(()) } @@ -305,13 +339,15 @@ struct LockfileData { /// Read and parse the League Client lockfile. /// Format: `LeagueClient:pid:port:password:https` (5-part) or `process:port:password:protocol` (4-part). -fn read_lockfile(league_path: &Path) -> Option { - let lockfile_path = league_path.join("lockfile"); - - let content = match std::fs::read_to_string(&lockfile_path) { +fn read_lockfile(install: &LeagueInstall) -> Option { + let content = match std::fs::read_to_string(&install.client_lockfile) { Ok(s) => s, Err(e) => { - tracing::debug!("Could not read lockfile at {:?}: {}", lockfile_path, e); + tracing::debug!( + "Could not read lockfile at {:?}: {}", + install.client_lockfile, + e + ); return None; } }; @@ -353,8 +389,8 @@ fn read_lockfile(league_path: &Path) -> Option { /// Attempt to reconnect to League via the LCU API (best-effort, non-fatal). /// Retries several times with delays to give the client time to process the game exit. -fn try_lcu_reconnect(league_path: &Path) { - let lockfile = match read_lockfile(league_path) { +fn try_lcu_reconnect(install: &LeagueInstall) { + let lockfile = match read_lockfile(install) { Some(data) => data, None => { tracing::debug!("No lockfile found, skipping LCU reconnect"); @@ -445,7 +481,7 @@ fn try_lcu_reconnect_once(client: &reqwest::blocking::Client, lockfile: &Lockfil } /// Kill the League of Legends game process. -fn kill_league_process() { +fn kill_league_process(_install: Option<&LeagueInstall>) { tracing::info!("Killing League of Legends process"); #[cfg(target_os = "windows")] @@ -454,9 +490,19 @@ fn kill_league_process() { .spawn(); #[cfg(target_os = "macos")] - let result = Command::new("pkill") - .args(["-f", "League of Legends"]) - .spawn(); + let result = _install + .and_then(find_exact_macos_game_pid) + .map(|pid| { + Command::new("/bin/kill") + .args(["-KILL", &pid.to_string()]) + .spawn() + }) + .unwrap_or_else(|| { + Err(std::io::Error::new( + std::io::ErrorKind::NotFound, + "The configured League game process is not running", + )) + }); #[cfg(target_os = "linux")] let result = Command::new("pkill") @@ -489,3 +535,27 @@ fn kill_league_process() { } } } + +#[cfg(target_os = "macos")] +fn find_exact_macos_game_pid(install: &LeagueInstall) -> Option { + let expected = std::fs::canonicalize(&install.game_executable) + .unwrap_or_else(|_| install.game_executable.clone()); + let output = Command::new("/bin/ps") + .args(["-axo", "pid=,comm="]) + .output() + .ok()?; + if !output.status.success() { + return None; + } + String::from_utf8_lossy(&output.stdout) + .lines() + .find_map(|line| { + let line = line.trim(); + let split = line.find(char::is_whitespace)?; + let pid = line[..split].trim().parse::().ok()?; + let executable = Path::new(line[split..].trim()); + let executable = + std::fs::canonicalize(executable).unwrap_or_else(|_| executable.to_path_buf()); + (executable == expected).then_some(pid) + }) +} diff --git a/src-tauri/src/commands/patcher.rs b/src-tauri/src/commands/patcher.rs index a0404215..59476d0e 100644 --- a/src-tauri/src/commands/patcher.rs +++ b/src-tauri/src/commands/patcher.rs @@ -1,116 +1,77 @@ use crate::error::{AppError, AppErrorResponse, AppResult, IpcResult, MutexResultExt}; -use crate::legacy_patcher::api::PATCHER_DLL_NAME; -use crate::legacy_patcher::runner::{ - run_legacy_patcher_loop, LegacyPatcherLoopError, DEFAULT_HOOK_TIMEOUT_MS, -}; use crate::mods::ModLibraryState; +use crate::patcher::backend::{ + selected_backend, BackendError, BackendEvent, PatcherContext, PatcherEventSink, + PatcherPreflight, +}; use crate::patcher::{PatcherPhase, PatcherState, StoredPatcherConfig}; +use crate::platform::LeagueInstall; use crate::state::SettingsState; use serde::{Deserialize, Serialize}; use std::path::PathBuf; use std::sync::atomic::Ordering; use std::sync::Arc; use std::thread; -use tauri::{AppHandle, Emitter, Manager, State}; +use tauri::{AppHandle, Emitter, State}; use ts_rs::TS; -/// Configuration for starting the patcher. +const DEFAULT_PATCHER_TIMEOUT_MS: u32 = 300_000; + #[derive(Debug, Clone, Deserialize, TS)] #[ts(export)] #[serde(rename_all = "camelCase")] pub struct PatcherConfig { - /// Optional log file path. #[ts(optional)] pub log_file: Option, - /// Timeout in milliseconds for hook initialization. Defaults to 5 minutes. #[ts(optional)] pub timeout_ms: Option, - /// Optional legacy patcher flags (matches `cslol_set_flags`). - /// - /// If not provided, defaults to 0 (equivalent to `--opts:none` in cslol-tools). #[ts(optional, type = "number")] pub flags: Option, - /// Absolute paths to workshop project directories to include in the overlay. - /// - /// These are loaded directly from disk via `FsModContent` and prepended to - /// the enabled mod list (highest priority). #[ts(optional)] pub workshop_projects: Option>, } -/// Current status of the patcher. #[derive(Debug, Clone, Serialize, TS)] #[ts(export)] #[serde(rename_all = "camelCase")] pub struct PatcherStatus { - /// Whether the patcher is currently running. pub running: bool, - /// The config path the patcher was started with. pub config_path: Option, - /// Current phase of the patcher lifecycle. pub phase: PatcherPhase, + pub backend: Option, + pub message: Option, } -/// Resolve the path to the patcher DLL from bundled resources. -fn resolve_patcher_dll_path(app_handle: &AppHandle) -> AppResult { - let resource_path = app_handle - .path() - .resource_dir() - .map_err(|e| AppError::Other(format!("Failed to get resource directory: {}", e)))? - .join(PATCHER_DLL_NAME); - - if resource_path.exists() { - tracing::info!( - "Resolved patcher DLL from resource_dir: {}", - resource_path.display() - ); - return Ok(resource_path); - } - - // Fallback for development: check next to executable - let dev_path = std::env::current_exe() - .ok() - .and_then(|p| p.parent().map(|p| p.to_path_buf())) - .map(|p| p.join(PATCHER_DLL_NAME)); - - if let Some(ref path) = dev_path { - if path.exists() { - tracing::info!( - "Resolved patcher DLL next to executable: {}", - path.display() - ); - return Ok(path.clone()); - } - } - - // Fallback for `tauri dev`: use the checked-in resources folder from the crate. - // (`resource_dir()` during dev often points at `target/debug/`, but resources may not be copied there.) - let manifest_resource_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("resources") - .join(PATCHER_DLL_NAME); - if manifest_resource_path.exists() { - tracing::info!( - "Resolved patcher DLL from CARGO_MANIFEST_DIR resources: {}", - manifest_resource_path.display() - ); - return Ok(manifest_resource_path); - } +#[tauri::command] +pub fn preflight_patcher( + app_handle: AppHandle, + settings: State, + library: State, +) -> IpcResult { + preflight_patcher_inner(&app_handle, &settings, &library).into() +} - Err(AppError::Other(format!( - "Patcher DLL not found. Tried:\n - {}\n - {}\n - {}", - resource_path.display(), - dev_path - .as_ref() - .map(|p| p.display().to_string()) - .unwrap_or_else(|| "".to_string()), - manifest_resource_path.display(), - ))) +fn preflight_patcher_inner( + app_handle: &AppHandle, + settings: &State, + library: &State, +) -> AppResult { + let settings = settings.0.lock().mutex_err()?.clone(); + let league_path = settings.league_path.as_ref().ok_or_else(|| { + AppError::ValidationFailed("League installation path is not configured".into()) + })?; + let league_install = LeagueInstall::resolve(league_path)?; + let allowed_root = library.0.storage_dir(&settings)?; + selected_backend(app_handle).preflight(&PatcherContext { + overlay_root: allowed_root.clone(), + allowed_root, + league_install, + log_file: None, + timeout_ms: DEFAULT_PATCHER_TIMEOUT_MS, + flags: 0, + }) } -/// Start the patcher with the given configuration. -/// -/// Returns immediately after spawning a background thread that builds the overlay -/// and then runs the patcher loop. Progress is reported via events. #[tauri::command] pub fn start_patcher( config: PatcherConfig, @@ -120,8 +81,8 @@ pub fn start_patcher( library: State, ) -> IpcResult<()> { let result = start_patcher_inner(config, &app_handle, &state, &settings, &library); - if let Err(ref e) = result { - tracing::error!(error = ?e, "Start patcher failed"); + if let Err(ref error) = result { + tracing::error!(error = ?error, "Start patcher failed"); } result.into() } @@ -133,229 +94,255 @@ pub(crate) fn start_patcher_inner( settings: &State, library: &State, ) -> AppResult<()> { - if cfg!(not(target_os = "windows")) { - return Err(AppError::Other( - "The patcher is not yet available on this platform".to_string(), - )); + reap_finished_thread(state)?; + + let backend = selected_backend(app_handle); + let availability = backend.availability(); + if !availability.supported || !availability.ready { + return Err(AppError::PatcherBackend { + code: if availability.supported { + "BACKEND_NOT_READY" + } else { + "UNSUPPORTED_PLATFORM" + } + .into(), + detail: availability + .reason + .unwrap_or_else(|| "The patcher backend is not ready".into()), + }); } - // Lock briefly: check state, set phase, clone what we need for the thread + let settings_snapshot = settings.0.lock().mutex_err()?.clone(); + let league_path = settings_snapshot.league_path.as_ref().ok_or_else(|| { + AppError::ValidationFailed("League installation path is not configured".into()) + })?; + let league_install = LeagueInstall::resolve(league_path)?; + let allowed_root = library.0.storage_dir(&settings_snapshot)?; + let (stop_flag, state_arc) = { let mut patcher_state = state.0.lock().mutex_err()?; - if patcher_state.is_running() { - return Err(AppError::Other("Patcher is already running".to_string())); + return Err(AppError::Other("Patcher is already running".into())); } - patcher_state.stop_flag.store(false, Ordering::SeqCst); patcher_state.phase = PatcherPhase::Building; - - (Arc::clone(&patcher_state.stop_flag), Arc::clone(&state.0)) - }; - - tracing::info!("Start patcher requested (legacy DLL mode)"); - - // Resolve DLL path and snapshot settings - let dll_path = resolve_patcher_dll_path(app_handle)?; - tracing::info!("Using patcher DLL: {}", dll_path.display()); - - // Stash config for hot-reload - { - let mut patcher_state = state.0.lock().mutex_err()?; + patcher_state.backend = Some(backend.name().into()); + patcher_state.message = Some("Building overlay".into()); patcher_state.last_config = Some(StoredPatcherConfig { log_file: config.log_file.clone(), timeout_ms: config.timeout_ms, flags: config.flags, workshop_projects: config.workshop_projects.clone(), }); - } - - let log_file = config.log_file.clone(); - let timeout_ms = config.timeout_ms.unwrap_or(DEFAULT_HOOK_TIMEOUT_MS); - let flags = config.flags.unwrap_or(0); + (Arc::clone(&patcher_state.stop_flag), Arc::clone(&state.0)) + }; - // tray: we see if we are loading Workshop or Library based on the config let is_workshop = config .workshop_projects .as_ref() - .map(|v| !v.is_empty()) - .unwrap_or(false); - + .is_some_and(|projects| !projects.is_empty()); let workshop_paths: Vec = config .workshop_projects .unwrap_or_default() - .iter() + .into_iter() .map(PathBuf::from) .collect(); - - let settings_snapshot = settings.0.lock().mutex_err()?.clone(); - tracing::info!( - "Settings snapshot: league_path={} mod_storage_path={}", - settings_snapshot - .league_path - .as_ref() - .map(|p| p.display().to_string()) - .unwrap_or_else(|| "".to_string()), - settings_snapshot - .mod_storage_path - .as_ref() - .map(|p| p.display().to_string()) - .unwrap_or_else(|| "".to_string()) - ); + let log_file = config.log_file; + let timeout_ms = config.timeout_ms.unwrap_or(DEFAULT_PATCHER_TIMEOUT_MS); + let flags = config.flags.unwrap_or(0); let library_clone = library.0.clone(); - - // tray: clone the app handle so we can pass it into the background thread let app_handle_thread = app_handle.clone(); - // tray: set initial LOADING state before thread starts - let initial_state = if is_workshop { + let initial_tray_state = if is_workshop { crate::tray::AppTrayState::WorkshopLoading } else { crate::tray::AppTrayState::LibraryLoading }; - let _ = crate::tray::set_tray_state(app_handle.clone(), initial_state); + let _ = crate::tray::set_tray_state(app_handle.clone(), initial_tray_state); let handle = thread::spawn(move || { - // Phase 1: Build overlay (the slow part) - let overlay_root = match library_clone.ensure_overlay(&settings_snapshot, &workshop_paths) { - Ok(root) => root, - Err(e) => { - tracing::error!(error = ?e, "Overlay build failed"); - let error_response: AppErrorResponse = e.into(); - let _ = library_clone - .app_handle() - .emit("patcher-error", &error_response); - if let Ok(mut s) = state_arc.lock() { - s.phase = PatcherPhase::Idle; - } - // TRAY: Reset to default on error - let _ = crate::tray::set_tray_state( - app_handle_thread.clone(), - crate::tray::AppTrayState::Default, - ); - return; + let result = (|| -> AppResult<()> { + let overlay_root = library_clone.ensure_overlay(&settings_snapshot, &workshop_paths)?; + if stop_flag.load(Ordering::SeqCst) { + return Ok(()); } - }; - // Check stop flag between build and patcher loop - if stop_flag.load(Ordering::SeqCst) { - tracing::info!("Stop requested after overlay build, exiting"); - if let Ok(mut s) = state_arc.lock() { - s.phase = PatcherPhase::Idle; + let context = PatcherContext { + overlay_root: overlay_root.clone(), + allowed_root, + league_install, + log_file, + timeout_ms, + flags, + }; + let preflight = backend.preflight(&context)?; + if !preflight.compatible { + return Err(AppError::PatcherBackend { + code: "UNSUPPORTED_GAME_BUILD".into(), + detail: preflight.reason.unwrap_or_else(|| { + "The installed League build is not compatible with this patcher".into() + }), + }); } - // tray: R$reset to default on early stop - let _ = crate::tray::set_tray_state( - app_handle_thread.clone(), - crate::tray::AppTrayState::Default, - ); - return; - } - - tracing::info!("Using overlay root: {}", overlay_root.display()); - - let mut overlay_root_str = overlay_root.display().to_string(); - if !overlay_root_str.ends_with(std::path::MAIN_SEPARATOR) { - overlay_root_str.push(std::path::MAIN_SEPARATOR); - } - // Phase 2: Run patcher loop - { - if let Ok(mut s) = state_arc.lock() { - s.phase = PatcherPhase::Patching; - s.config_path = Some(overlay_root_str.clone()); + { + let mut patcher_state = state_arc.lock().map_err(|_| AppError::MutexLockFailed)?; + patcher_state.phase = PatcherPhase::WaitingForGame; + patcher_state.config_path = Some(overlay_root.display().to_string()); + patcher_state.message = Some("Waiting for League game process".into()); } - } + let active_tray_state = if is_workshop { + crate::tray::AppTrayState::WorkshopOn + } else { + crate::tray::AppTrayState::LibraryOn + }; + let _ = crate::tray::set_tray_state(app_handle_thread.clone(), active_tray_state); + + let event_state = Arc::clone(&state_arc); + let event_app = app_handle_thread.clone(); + let event_sink: PatcherEventSink = Arc::new(move |event: BackendEvent| { + tracing::info!( + event = %event.event, + pid = ?event.pid, + architecture = ?event.architecture, + signature = ?event.signature, + detail = ?event.detail, + "Patcher backend event" + ); + if let Ok(mut patcher_state) = event_state.lock() { + patcher_state.phase = match event.event.as_str() { + "waitingForGame" | "ready" | "gameExited" => PatcherPhase::WaitingForGame, + "gameFound" | "scanning" | "patched" => PatcherPhase::Patching, + _ => patcher_state.phase, + }; + patcher_state.message = Some(backend_event_message(&event)); + } + let _ = event_app.emit("patcher-backend-event", &event); + }); - // tray: overlay is built, we are now Patching - let on_state = if is_workshop { - crate::tray::AppTrayState::WorkshopOn - } else { - crate::tray::AppTrayState::LibraryOn - }; - let _ = crate::tray::set_tray_state(app_handle_thread.clone(), on_state); + match backend.run(context, Arc::clone(&stop_flag), event_sink) { + Ok(()) | Err(BackendError::Stopped) => Ok(()), + Err(BackendError::Failed { code, detail }) => { + Err(AppError::PatcherBackend { code, detail }) + } + } + })(); - // This blocks until the game closes or the patcher is stopped - match run_legacy_patcher_loop( - &dll_path, - &overlay_root_str, - log_file.as_deref(), - timeout_ms, - flags, - &stop_flag, - ) { - Ok(()) => tracing::info!("Patcher loop completed successfully"), - Err(LegacyPatcherLoopError::Stopped) => tracing::info!("Patcher stopped by request"), - Err(e) => tracing::error!("Patcher loop error: {}", e), + if let Err(error) = result { + tracing::error!(error = ?error, "Patcher session failed"); + let response: AppErrorResponse = error.into(); + let _ = app_handle_thread.emit("patcher-error", &response); } - - // Cleanup Phase - if let Ok(mut s) = state_arc.lock() { - s.phase = PatcherPhase::Idle; - s.config_path = None; + if let Ok(mut patcher_state) = state_arc.lock() { + patcher_state.phase = PatcherPhase::Idle; + patcher_state.config_path = None; + patcher_state.backend = None; + patcher_state.message = None; } - - // tray: game closed or patcher stopped, revert to default icon let _ = crate::tray::set_tray_state(app_handle_thread, crate::tray::AppTrayState::Default); - - tracing::info!("Patcher thread exiting"); }); - // Store thread handle - let mut patcher_state = state.0.lock().mutex_err()?; - patcher_state.thread_handle = Some(handle); - + state.0.lock().mutex_err()?.thread_handle = Some(handle); Ok(()) } -/// Stop the running patcher. #[tauri::command] pub fn stop_patcher(state: State) -> IpcResult<()> { stop_patcher_inner(&state).into() } -pub(crate) fn stop_patcher_inner(state: &State) -> AppResult<()> { - let patcher_state = state.0.lock().mutex_err()?; - +pub(crate) fn stop_patcher_inner(state: &PatcherState) -> AppResult<()> { + let mut patcher_state = state.0.lock().mutex_err()?; if !patcher_state.is_running() { - return Err(AppError::Other("Patcher is not running".to_string())); + return Err(AppError::Other("Patcher is not running".into())); } - - tracing::info!("Stopping patcher..."); - + tracing::info!("Stopping patcher"); patcher_state.stop_flag.store(true, Ordering::SeqCst); - + patcher_state.message = Some("Stopping patcher".into()); Ok(()) } -/// Get the current status of the patcher. #[tauri::command] pub fn get_patcher_status(state: State) -> IpcResult { get_patcher_status_inner(&state).into() } fn get_patcher_status_inner(state: &State) -> AppResult { - let mut patcher_state = state.0.lock().mutex_err()?; - + reap_finished_thread(state)?; + let patcher_state = state.0.lock().mutex_err()?; let running = patcher_state.is_running(); - - // Defensive reset: if the thread has died but phase wasn't reset (e.g. panic), - // correct it so the UI doesn't get stuck. - if !running && patcher_state.phase != PatcherPhase::Idle { - tracing::warn!( - "Patcher thread dead but phase was {:?}, resetting to Idle", - patcher_state.phase - ); - patcher_state.phase = PatcherPhase::Idle; - patcher_state.config_path = None; - } - Ok(PatcherStatus { running, - config_path: if running { - patcher_state.config_path.clone() + config_path: running.then(|| patcher_state.config_path.clone()).flatten(), + phase: if running { + patcher_state.phase } else { - None + PatcherPhase::Idle }, - phase: patcher_state.phase, + backend: patcher_state.backend.clone(), + message: patcher_state.message.clone(), }) } + +fn reap_finished_thread(state: &PatcherState) -> AppResult<()> { + let handle = { + let mut patcher_state = state.0.lock().mutex_err()?; + if patcher_state + .thread_handle + .as_ref() + .is_some_and(|handle| handle.is_finished()) + { + patcher_state.thread_handle.take() + } else { + None + } + }; + if let Some(handle) = handle { + if handle.join().is_err() { + return Err(AppError::Other("Patcher thread panicked".into())); + } + } + Ok(()) +} + +fn backend_event_message(event: &BackendEvent) -> String { + match event.event.as_str() { + "ready" | "waitingForGame" => "Waiting for League game process".into(), + "gameFound" => event + .pid + .map(|pid| format!("Found League game process ({pid})")) + .unwrap_or_else(|| "Found League game process".into()), + "scanning" => "Validating League patch signature".into(), + "patched" => "League process patched".into(), + "gameExited" => "League exited; waiting for the next match".into(), + other => event.detail.clone().unwrap_or_else(|| other.into()), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::time::{Duration, Instant}; + + #[test] + fn stop_patcher_signals_without_joining_worker() { + let state = PatcherState::new(); + let worker = thread::spawn(|| thread::sleep(Duration::from_millis(300))); + { + let mut inner = state.0.lock().unwrap(); + inner.thread_handle = Some(worker); + } + + let started = Instant::now(); + stop_patcher_inner(&state).unwrap(); + assert!(started.elapsed() < Duration::from_millis(100)); + + let worker = { + let mut inner = state.0.lock().unwrap(); + assert!(inner.stop_flag.load(Ordering::SeqCst)); + assert_eq!(inner.message.as_deref(), Some("Stopping patcher")); + inner.thread_handle.take().unwrap() + }; + worker.join().unwrap(); + } +} diff --git a/src-tauri/src/commands/platform.rs b/src-tauri/src/commands/platform.rs index f4966064..f0fee1c8 100644 --- a/src-tauri/src/commands/platform.rs +++ b/src-tauri/src/commands/platform.rs @@ -1,22 +1,41 @@ use crate::error::IpcResult; +use crate::patcher::backend::{selected_backend, PatcherAvailability}; use serde::Serialize; +use tauri::AppHandle; use ts_rs::TS; +#[derive(Debug, Serialize, TS)] +#[ts(export)] +#[serde(rename_all = "camelCase")] +pub struct HotkeyAvailability { + pub supported: bool, + pub accessibility_permission_required: bool, + pub reason: Option, +} + #[derive(Debug, Serialize, TS)] #[ts(export)] #[serde(rename_all = "camelCase")] pub struct PlatformSupport { pub os: String, - pub patcher_available: bool, - pub hotkeys_available: bool, + pub architecture: String, + pub patcher: PatcherAvailability, + pub hotkeys: HotkeyAvailability, } -/// Get platform-specific feature flags. #[tauri::command] -pub fn get_platform_support() -> IpcResult { +pub fn get_platform_support(app_handle: AppHandle) -> IpcResult { + let patcher = selected_backend(&app_handle).availability(); + let hotkeys_supported = cfg!(any(target_os = "windows", target_os = "macos")); IpcResult::ok(PlatformSupport { os: std::env::consts::OS.to_string(), - patcher_available: cfg!(target_os = "windows"), - hotkeys_available: cfg!(target_os = "windows"), + architecture: std::env::consts::ARCH.to_string(), + patcher, + hotkeys: HotkeyAvailability { + supported: hotkeys_supported, + accessibility_permission_required: false, + reason: (!hotkeys_supported) + .then(|| "Global shortcuts are not supported on this operating system".into()), + }, }) } diff --git a/src-tauri/src/commands/settings.rs b/src-tauri/src/commands/settings.rs index e9fa0dc0..e7fcc1b0 100644 --- a/src-tauri/src/commands/settings.rs +++ b/src-tauri/src/commands/settings.rs @@ -1,4 +1,5 @@ use crate::error::{AppResult, IpcResult, MutexResultExt}; +use crate::platform::LeagueInstall; use crate::state::{save_settings_to_disk, Settings, SettingsState}; use crate::utils::game::{list_game_wads, resolve_game_dir}; use std::path::PathBuf; @@ -54,28 +55,15 @@ pub fn auto_detect_league_path() -> IpcResult> { } fn auto_detect_league_path_inner() -> Option { - let exe_path = ltk_mod_core::auto_detect_league_path()?; - let path = std::path::Path::new(&exe_path); - - // Navigate from "Game/League of Legends.exe" to installation root - let install_root = path.parent()?.parent()?; - - tracing::info!("Found League installation at: {:?}", install_root); - Some(install_root.to_path_buf()) + let install = LeagueInstall::auto_detect()?; + tracing::info!("Found League installation at: {:?}", install.install_root); + Some(install.configured_root()) } /// Validate a League installation path. #[tauri::command] pub fn validate_league_path(path: PathBuf) -> IpcResult { - let valid = if cfg!(target_os = "macos") { - // Path points to the .app bundle (e.g. /Applications/League of Legends.app) - path.join("Contents").join("LoL").join("Game").exists() - // Path points to the LoL root inside the bundle - || path.join("Game").join("League of Legends.app").exists() - } else { - path.join("Game").join("League of Legends.exe").exists() - }; - IpcResult::ok(valid) + IpcResult::ok(LeagueInstall::resolve(path).is_ok()) } /// List every WAD filename under the configured League install's `DATA` directory. diff --git a/src-tauri/src/diagnostics/macos.rs b/src-tauri/src/diagnostics/macos.rs new file mode 100644 index 00000000..314c16be --- /dev/null +++ b/src-tauri/src/diagnostics/macos.rs @@ -0,0 +1,321 @@ +use super::{check, check_ok, Category, Check, CheckCtx, CheckDetail, Severity}; +use serde::Deserialize; +use std::process::Command; + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct PreflightEvent { + event: String, + code: Option, + detail: Option, + signature: Option, + architecture: Option, + helper_version: Option, +} + +pub fn system_checks(ctx: &CheckCtx) -> Vec { + vec![ + check_version(), + check_architecture(), + check_sip(), + check_translocation(ctx), + check_quarantine(ctx), + ] +} + +pub fn patcher_checks(ctx: &CheckCtx) -> Vec { + vec![ + check_helper(ctx), + check_game_architecture_and_signature(ctx), + check_process_access_model(), + ] +} + +fn check_version() -> Check { + let version = command_output("/usr/bin/sw_vers", &["-productVersion"]); + let Some(version) = version else { + return check( + "macos.version", + "macOS version", + Category::System, + Severity::Warn, + "Could not determine macOS version", + ); + }; + let major = version + .split('.') + .next() + .and_then(|value| value.parse::().ok()); + if major.is_some_and(|major| major >= 13) { + check_ok( + "macos.version", + "macOS version", + Category::System, + &format!("macOS {version}"), + ) + } else { + let mut result = check( + "macos.version", + "macOS version", + Category::System, + Severity::Bad, + format!("macOS {version} is below the supported minimum"), + ); + result.suggestion = Some("Upgrade this Mac to macOS 13 or newer.".into()); + result + } +} + +fn check_architecture() -> Check { + if std::env::consts::ARCH == "aarch64" { + check_ok( + "macos.architecture", + "Manager architecture", + Category::System, + "Apple Silicon (ARM64)", + ) + } else { + check( + "macos.architecture", + "Manager architecture", + Category::System, + Severity::Bad, + format!( + "{} is unsupported; this implementation requires ARM64", + std::env::consts::ARCH + ), + ) + } +} + +fn check_sip() -> Check { + let output = command_output("/usr/bin/csrutil", &["status"]); + match output { + Some(status) if status.to_ascii_lowercase().contains("enabled") => check_ok( + "macos.sip", + "System Integrity Protection", + Category::System, + "Enabled", + ), + Some(status) if status.to_ascii_lowercase().contains("disabled") => { + let mut result = check( + "macos.sip", + "System Integrity Protection", + Category::System, + Severity::Bad, + "Disabled", + ); + result.suggestion = Some( + "LTK Manager is designed to work with SIP enabled. Re-enable SIP before using the patcher." + .into(), + ); + result + } + _ => check( + "macos.sip", + "System Integrity Protection", + Category::System, + Severity::Warn, + "Could not determine SIP status", + ), + } +} + +fn check_translocation(ctx: &CheckCtx) -> Check { + let translocated = ctx + .manager_exe + .as_ref() + .is_some_and(|path| path.to_string_lossy().contains("/AppTranslocation/")); + if translocated { + let mut result = check( + "macos.translocation", + "App Translocation", + Category::Manager, + Severity::Bad, + "LTK Manager is running from an App Translocation path", + ); + result.suggestion = Some( + "Move LTK Manager into /Applications and launch it from there before setting up the helper." + .into(), + ); + result + } else { + check_ok( + "macos.translocation", + "App Translocation", + Category::Manager, + "Not translocated", + ) + } +} + +fn check_quarantine(ctx: &CheckCtx) -> Check { + let Some(executable) = ctx.manager_exe.as_ref() else { + return check( + "macos.quarantine", + "Quarantine attribute", + Category::Manager, + Severity::Info, + "Manager executable path unavailable", + ); + }; + let output = Command::new("/usr/bin/xattr") + .args(["-p", "com.apple.quarantine"]) + .arg(executable) + .output(); + match output { + Ok(output) if output.status.success() => { + let mut result = check( + "macos.quarantine", + "Quarantine attribute", + Category::Manager, + Severity::Warn, + "Present", + ); + result.suggestion = Some( + "If helper setup or launch fails, move the app to /Applications and clear quarantine only after verifying the build source." + .into(), + ); + result + } + _ => check_ok( + "macos.quarantine", + "Quarantine attribute", + Category::Manager, + "Not present", + ), + } +} + +fn check_helper(ctx: &CheckCtx) -> Check { + let Some(helper) = ctx.patcher_helper_path.as_ref() else { + let mut result = check( + "macos.helper.present", + "Native patcher helper", + Category::Patcher, + Severity::Bad, + "Missing", + ); + result.suggestion = Some("Run `pnpm macos:helper`, then restart LTK Manager.".into()); + return result; + }; + let mut result = check_ok( + "macos.helper.present", + "Native patcher helper", + Category::Patcher, + "Bundled and executable", + ); + result + .details + .push(CheckDetail::new("path", helper.display().to_string())); + result +} + +fn check_game_architecture_and_signature(ctx: &CheckCtx) -> Check { + let (Some(helper), Some(install)) = ( + ctx.patcher_helper_path.as_ref(), + ctx.league_install.as_ref(), + ) else { + return check( + "macos.patcher.preflight", + "ARM64 patch signature", + Category::Patcher, + Severity::Info, + "Helper or League installation unavailable", + ); + }; + let output = Command::new(helper) + .arg("--preflight") + .arg(&install.game_executable) + .output(); + let Ok(output) = output else { + return check( + "macos.patcher.preflight", + "ARM64 patch signature", + Category::Patcher, + Severity::Bad, + "Failed to execute helper preflight", + ); + }; + let event = serde_json::from_slice::(&output.stdout); + match event { + Ok(event) if output.status.success() && event.event == "compatible" => { + let mut result = check_ok( + "macos.patcher.preflight", + "ARM64 patch signature", + Category::Patcher, + "Compatible", + ); + if let Some(architecture) = event.architecture { + result + .details + .push(CheckDetail::new("architecture", architecture)); + } + if let Some(signature) = event.signature { + result + .details + .push(CheckDetail::new("signature", signature)); + } + if let Some(version) = event.helper_version { + result + .details + .push(CheckDetail::new("helper_version", version)); + } + result + } + Ok(event) => { + let mut result = check( + "macos.patcher.preflight", + "ARM64 patch signature", + Category::Patcher, + Severity::Bad, + event + .detail + .unwrap_or_else(|| "Current League build is unsupported".into()), + ); + if let Some(code) = event.code { + result.details.push(CheckDetail::new("code", code)); + } + result.suggestion = Some( + "Do not start patching until a signature update supports this League build.".into(), + ); + result + } + Err(error) => { + let mut result = check( + "macos.patcher.preflight", + "ARM64 patch signature", + Category::Patcher, + Severity::Bad, + "Helper returned an invalid preflight response", + ); + result + .details + .push(CheckDetail::new("error", error.to_string())); + result + } + } +} + +fn check_process_access_model() -> Check { + let mut result = check( + "macos.patcher.authorization", + "Process access authorization", + Category::Patcher, + Severity::Info, + "Administrator approval is requested per patcher session", + ); + result.suggestion = Some( + "Approve the macOS prompt when starting the patcher. LTK Manager itself remains unprivileged." + .into(), + ); + result +} + +fn command_output(program: &str, arguments: &[&str]) -> Option { + let output = Command::new(program).args(arguments).output().ok()?; + output + .status + .success() + .then(|| String::from_utf8_lossy(&output.stdout).trim().to_string()) +} diff --git a/src-tauri/src/diagnostics/mod.rs b/src-tauri/src/diagnostics/mod.rs index 98f7d4d2..702511e5 100644 --- a/src-tauri/src/diagnostics/mod.rs +++ b/src-tauri/src/diagnostics/mod.rs @@ -10,12 +10,17 @@ use serde::Serialize; use std::path::PathBuf; use ts_rs::TS; +#[cfg(target_os = "windows")] mod compat_flags; mod library_index; +#[cfg(target_os = "macos")] +mod macos; +#[cfg(target_os = "windows")] mod patcher_dll; mod paths; mod processes; mod storage_medium; +#[cfg(target_os = "windows")] mod windows; #[cfg(target_os = "windows")] @@ -123,6 +128,8 @@ pub struct DiagnosticReport { pub(crate) struct CheckCtx { /// League install root (e.g. `C:\Riot Games\League of Legends`). pub league_path: Option, + /// Canonical, validated League installation. + pub league_install: Option, /// Resolved mod storage directory — the path the rest of the app actually /// uses, with the `app_data_dir` fallback already applied. Only `None` if /// even the fallback could not be resolved (no Tauri app-data dir). @@ -131,7 +138,11 @@ pub(crate) struct CheckCtx { /// configured a custom path in Settings). pub mod_storage_is_default: bool, /// Resource directory containing `cslol-dll.dll`. None if it could not be resolved. + #[cfg(target_os = "windows")] pub patcher_dll_path: Option, + /// Native macOS helper path. + #[cfg(target_os = "macos")] + pub patcher_helper_path: Option, /// Manager executable path. Unused by phase-1 checks but kept for the /// future handle-leak / signature checks on the manager itself. #[allow(dead_code)] @@ -177,28 +188,44 @@ pub(crate) fn check( /// at this layer — checks that fail to gather data report a `Warn` or `Bad` /// severity rather than propagating an error. pub fn run_all(ctx: &CheckCtx) -> Vec { - vec![ - // System + let mut checks = Vec::new(); + + #[cfg(target_os = "windows")] + checks.extend([ windows::check_version(), windows::check_long_paths_enabled(), windows::check_uac_enabled(), - // Manager - processes::check_manager_not_admin(), - // League - paths::check_league_path(ctx), - paths::check_league_writability(ctx), - compat_flags::check_compat_flags(), - // Storage + ]); + #[cfg(target_os = "macos")] + checks.extend(macos::system_checks(ctx)); + + checks.push(processes::check_manager_not_admin()); + checks.push(paths::check_league_path(ctx)); + #[cfg(target_os = "windows")] + { + checks.push(paths::check_league_writability(ctx)); + checks.push(compat_flags::check_compat_flags()); + } + #[cfg(target_os = "macos")] + checks.push(paths::check_league_readability(ctx)); + + checks.extend([ paths::check_storage_path(ctx), paths::check_storage_writability(ctx), paths::check_storage_in_league(ctx), paths::check_free_space(ctx), storage_medium::check_storage_medium(ctx), - // Patcher + ]); + + #[cfg(target_os = "windows")] + checks.extend([ patcher_dll::check_dll_present(ctx), patcher_dll::check_dll_signature(ctx), patcher_dll::check_dll_not_locked(ctx), - // Library - library_index::check_library_index(ctx), - ] + ]); + #[cfg(target_os = "macos")] + checks.extend(macos::patcher_checks(ctx)); + + checks.push(library_index::check_library_index(ctx)); + checks } diff --git a/src-tauri/src/diagnostics/paths.rs b/src-tauri/src/diagnostics/paths.rs index e75a1ed3..05656fa0 100644 --- a/src-tauri/src/diagnostics/paths.rs +++ b/src-tauri/src/diagnostics/paths.rs @@ -75,8 +75,18 @@ fn free_disk_bytes(path: &Path) -> Option { } #[cfg(not(target_os = "windows"))] -fn free_disk_bytes(_path: &Path) -> Option { - None +fn free_disk_bytes(path: &Path) -> Option { + use std::ffi::CString; + use std::os::unix::ffi::OsStrExt; + + let path = CString::new(path.as_os_str().as_bytes()).ok()?; + let mut stats = std::mem::MaybeUninit::::uninit(); + let result = unsafe { libc::statvfs(path.as_ptr(), stats.as_mut_ptr()) }; + if result != 0 { + return None; + } + let stats = unsafe { stats.assume_init() }; + Some((stats.f_bavail as u64).saturating_mul(stats.f_frsize)) } #[cfg(not(target_os = "windows"))] @@ -123,10 +133,7 @@ pub fn check_league_path(ctx: &CheckCtx) -> Check { "Not configured", ); }; - let game_exe = p.join("Game").join("League of Legends.exe"); - let mac_path = p.join("Contents").join("LoL").join("Game"); - let exists = game_exe.exists() || mac_path.exists(); - if !exists { + let Some(install) = ctx.league_install.as_ref() else { let mut c = check( "paths.league.exists", "League installation path", @@ -141,12 +148,12 @@ pub fn check_league_path(ctx: &CheckCtx) -> Check { .into(), ); return c; - } + }; let mut c = check_ok( "paths.league.exists", "League installation path", Category::League, - &p.display().to_string(), + &install.game_dir.display().to_string(), ); let len = p.display().to_string().len(); if len > PATH_LEN_WARN { @@ -179,6 +186,55 @@ pub fn check_league_path(ctx: &CheckCtx) -> Check { c } +#[cfg(target_os = "macos")] +pub fn check_league_readability(ctx: &CheckCtx) -> Check { + let Some(install) = ctx.league_install.as_ref() else { + return check( + "paths.league.readable", + "League game data is readable", + Category::League, + Severity::Info, + "League path is not configured or invalid", + ); + }; + let data = install.game_dir.join("DATA"); + match std::fs::read_dir(&data) { + Ok(_) => { + let mut result = check_ok( + "paths.league.readable", + "League game data is readable", + Category::League, + "Readable without modifying Riot's files", + ); + result + .details + .push(CheckDetail::new("path", data.display().to_string())); + result + } + Err(error) => { + let mut result = check( + "paths.league.readable", + "League game data is readable", + Category::League, + Severity::Bad, + "Cannot read League's DATA directory", + ); + result + .details + .push(CheckDetail::new("path", data.display().to_string())); + result + .details + .push(CheckDetail::new("error", error.to_string())); + result.suggestion = Some( + "Confirm the selected application is a complete Riot League installation and that the current user can read it." + .into(), + ); + result + } + } +} + +#[cfg(target_os = "windows")] pub fn check_league_writability(ctx: &CheckCtx) -> Check { let Some(p) = ctx.league_path.as_ref() else { return check( diff --git a/src-tauri/src/diagnostics/processes.rs b/src-tauri/src/diagnostics/processes.rs index 04754ff6..374c30a6 100644 --- a/src-tauri/src/diagnostics/processes.rs +++ b/src-tauri/src/diagnostics/processes.rs @@ -34,7 +34,32 @@ pub fn check_manager_not_admin() -> Check { } } -#[cfg(not(target_os = "windows"))] +#[cfg(target_os = "macos")] +pub fn check_manager_not_admin() -> Check { + if unsafe { libc::geteuid() } == 0 { + let mut result = check( + "process.manager_not_admin", + "LTK Manager not running as root", + Category::Manager, + Severity::Bad, + "LTK Manager is running as root", + ); + result.suggestion = Some( + "Close LTK Manager and launch it normally. Only the one-shot patcher helper should receive administrator privileges." + .into(), + ); + result + } else { + super::check_ok( + "process.manager_not_admin", + "LTK Manager not running as root", + Category::Manager, + "Not elevated", + ) + } +} + +#[cfg(not(any(target_os = "windows", target_os = "macos")))] pub fn check_manager_not_admin() -> Check { check( "process.manager_not_admin", diff --git a/src-tauri/src/error.rs b/src-tauri/src/error.rs index 652fce87..59d59b3c 100644 --- a/src-tauri/src/error.rs +++ b/src-tauri/src/error.rs @@ -44,6 +44,8 @@ pub enum ErrorCode { Wad, /// Operation blocked because the patcher is running PatcherRunning, + /// Platform patcher backend or helper failure + PatcherBackend, /// ZIP error Zip, /// Library index was written by a newer app version @@ -208,6 +210,9 @@ pub enum AppError { #[error("Cannot modify mods while the patcher is running")] PatcherRunning, + #[error("{code}: {detail}")] + PatcherBackend { code: String, detail: String }, + #[error("ZIP error: {0}")] ZipError(#[from] zip::result::ZipError), @@ -288,6 +293,11 @@ impl From for AppErrorResponse { "Stop the patcher before modifying mods", ), + AppError::PatcherBackend { code, detail } => { + AppErrorResponse::new(ErrorCode::PatcherBackend, detail.clone()) + .with_context(serde_json::json!({ "backendCode": code, "detail": detail })) + } + AppError::ZipError(e) => AppErrorResponse::new(ErrorCode::Zip, e.to_string()), AppError::SchemaVersionTooNew { file_version, max_supported } => AppErrorResponse::new( @@ -405,6 +415,7 @@ mod tests { ErrorCode::Fantome, ErrorCode::Wad, ErrorCode::PatcherRunning, + ErrorCode::PatcherBackend, ErrorCode::Zip, ErrorCode::SchemaVersionTooNew, ErrorCode::Workshop, diff --git a/src-tauri/src/logging.rs b/src-tauri/src/logging.rs index 88ac50f2..23435254 100644 --- a/src-tauri/src/logging.rs +++ b/src-tauri/src/logging.rs @@ -1,4 +1,5 @@ use std::path::{Path, PathBuf}; +#[cfg(debug_assertions)] use std::sync::{Arc, OnceLock}; use std::time::{Duration, SystemTime}; use tracing_appender::{non_blocking, non_blocking::WorkerGuard, rolling}; diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index fa56ffb3..4d7941af 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -8,6 +8,7 @@ mod deep_link; mod diagnostics; mod error; mod hotkeys; +#[cfg(target_os = "windows")] mod legacy_patcher; #[cfg(debug_assertions)] mod log_layer; @@ -15,6 +16,7 @@ mod logging; mod mods; mod overlay; pub mod patcher; +mod platform; mod setup; mod state; mod storage; @@ -90,6 +92,7 @@ fn main() { commands::start_patcher, commands::stop_patcher, commands::get_patcher_status, + commands::preflight_patcher, // Hotkeys commands::pause_hotkeys, commands::resume_hotkeys, diff --git a/src-tauri/src/overlay/mod.rs b/src-tauri/src/overlay/mod.rs index 10760072..933086b1 100644 --- a/src-tauri/src/overlay/mod.rs +++ b/src-tauri/src/overlay/mod.rs @@ -7,6 +7,9 @@ use tauri::{Emitter, Manager}; const SCRIPTS_WAD: &str = "scripts.wad.client"; const TFT_WAD: &str = "map22.wad.client"; +const MACOS_PLATFORM_WADS: &[&str] = + &["bootstrap.macos.wad.client", "shadercache.metal.wad.client"]; + #[derive(Clone, serde::Serialize, ts_rs::TS)] #[ts(export)] #[serde(rename_all = "camelCase")] @@ -224,6 +227,12 @@ pub(crate) fn resolve_blocked_wads(settings: &Settings, available_wads: &[String blocked.push(TFT_WAD.to_string()); } + if cfg!(target_os = "macos") { + for wad in MACOS_PLATFORM_WADS { + blocked.push(wad.to_string()); + } + } + blocked.sort(); blocked.dedup(); blocked @@ -247,6 +256,19 @@ mod tests { assert!(result.contains(&"map22.wad.client".to_string())); } + #[cfg(target_os = "macos")] + #[test] + fn resolve_blocked_wads_includes_macos_platform_wads() { + let settings = Settings { + block_scripts_wad: false, + patch_tft: true, + ..Settings::default() + }; + let result = resolve_blocked_wads(&settings, &[]); + assert!(result.contains(&"bootstrap.macos.wad.client".to_string())); + assert!(result.contains(&"shadercache.metal.wad.client".to_string())); + } + #[test] fn resolve_blocked_wads_regex_expanded_against_available() { let settings = Settings { @@ -264,13 +286,10 @@ mod tests { "aatrox.wad.client".to_string(), ]; let result = resolve_blocked_wads(&settings, &available); - assert_eq!( - result, - vec![ - "map11.en_us.wad.client".to_string(), - "map22.en_us.wad.client".to_string(), - ] - ); + assert!(result.contains(&"map11.en_us.wad.client".to_string())); + assert!(result.contains(&"map22.en_us.wad.client".to_string())); + assert!(!result.contains(&"map12.wad.client".to_string())); + assert!(!result.contains(&"aatrox.wad.client".to_string())); } #[test] @@ -289,7 +308,7 @@ mod tests { ..Settings::default() }; let result = resolve_blocked_wads(&settings, &[]); - assert_eq!(result, vec!["keeper.wad.client".to_string()]); + assert!(result.contains(&"keeper.wad.client".to_string())); } #[test] @@ -309,7 +328,9 @@ mod tests { }; let available = vec!["scripts.wad.client".to_string()]; let result = resolve_blocked_wads(&settings, &available); - assert_eq!(result, vec!["scripts.wad.client".to_string()]); + assert!(result.contains(&"scripts.wad.client".to_string())); + let scripts_count = result.iter().filter(|w| *w == "scripts.wad.client").count(); + assert_eq!(scripts_count, 1); } #[test] diff --git a/src-tauri/src/patcher/backend/macos.rs b/src-tauri/src/patcher/backend/macos.rs new file mode 100644 index 00000000..3a46e0ca --- /dev/null +++ b/src-tauri/src/patcher/backend/macos.rs @@ -0,0 +1,704 @@ +use super::{ + BackendError, BackendEvent, BackendResult, PatcherAvailability, PatcherBackend, PatcherContext, + PatcherEventSink, PatcherPreflight, +}; +use crate::error::{AppError, AppResult}; +use serde::{Deserialize, Serialize}; +use std::fs; +use std::io::{ErrorKind, Read, Write}; +use std::ops::{Deref, DerefMut}; +use std::os::unix::ffi::OsStrExt; +use std::os::unix::fs::PermissionsExt; +use std::os::unix::net::{UnixListener, UnixStream}; +use std::path::{Path, PathBuf}; +use std::process::{Child, Command, Stdio}; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; +use std::time::{Duration, Instant}; +use tauri::{AppHandle, Manager}; +use uuid::Uuid; + +pub const MACOS_HELPER_VERSION: &str = env!("CARGO_PKG_VERSION"); +const HELPER_NAME: &str = "ltk-macos-patcher"; +const HELPER_TARGET_NAME: &str = "ltk-macos-patcher-aarch64-apple-darwin"; +const PROTOCOL_VERSION: u32 = 1; +const STARTUP_TIMEOUT: Duration = Duration::from_secs(90); +const STOP_TIMEOUT: Duration = Duration::from_secs(5); +const IO_POLL_INTERVAL: Duration = Duration::from_millis(250); +const MACOS_UNIX_SOCKET_PATH_MAX: usize = 103; +const MAX_HELPER_EVENT_BYTES: usize = 64 * 1024; + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +struct HelperEvent { + version: u32, + event: String, + code: Option, + detail: Option, + signature: Option, + architecture: Option, + pid: Option, + helper_version: Option, + token: Option, +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct StartRequest<'a> { + version: u32, + command: &'static str, + token: &'a str, + overlay: &'a Path, + allowed_root: &'a Path, + game_bundle: &'a Path, + game_executable: &'a Path, + client_uid: u32, +} + +#[derive(Serialize)] +struct StopRequest { + version: u32, + command: &'static str, +} + +pub struct MacOsBackend { + app_handle: AppHandle, +} + +impl MacOsBackend { + pub fn new(app_handle: AppHandle) -> Self { + Self { app_handle } + } + + fn helper_path(&self) -> AppResult { + resolve_helper_path(&self.app_handle).ok_or_else(|| { + AppError::Other( + "macOS patcher helper is missing. Run `pnpm macos:helper` and restart LTK Manager." + .into(), + ) + }) + } +} + +impl PatcherBackend for MacOsBackend { + fn name(&self) -> &'static str { + "macos-arm64-helper" + } + + fn availability(&self) -> PatcherAvailability { + if std::env::consts::ARCH != "aarch64" { + return PatcherAvailability::unsupported( + "The initial macOS patcher supports Apple Silicon and an ARM64 League process only", + ); + } + match self.helper_path() { + Ok(_) => PatcherAvailability { + supported: true, + ready: true, + reason: Some( + "macOS will request administrator approval when a patcher session starts" + .into(), + ), + requires_setup: false, + permission_required: true, + helper_version: Some(MACOS_HELPER_VERSION.into()), + }, + Err(error) => PatcherAvailability { + supported: true, + ready: false, + reason: Some(error.to_string()), + requires_setup: true, + permission_required: true, + helper_version: None, + }, + } + } + + fn preflight(&self, context: &PatcherContext) -> AppResult { + let helper = self.helper_path()?; + let output = Command::new(helper) + .arg("--preflight") + .arg(&context.league_install.game_executable) + .output() + .map_err(|error| { + AppError::Other(format!("Failed to run macOS helper preflight: {error}")) + })?; + let line = String::from_utf8_lossy(&output.stdout); + let event: HelperEvent = serde_json::from_str(line.trim()).map_err(|error| { + AppError::Other(format!("Invalid macOS helper preflight response: {error}")) + })?; + Ok(PatcherPreflight { + compatible: output.status.success() && event.event == "compatible", + backend: self.name().into(), + architecture: event + .architecture + .unwrap_or_else(|| std::env::consts::ARCH.into()), + signature: event.signature, + reason: event.detail, + }) + } + + fn run( + &self, + context: PatcherContext, + stop: Arc, + events: PatcherEventSink, + ) -> BackendResult<()> { + let helper = self.helper_path().map_err(|error| BackendError::Failed { + code: "HELPER_MISSING".into(), + detail: error.to_string(), + })?; + let game_bundle = &context.league_install.install_root; + + let uid = unsafe { libc::getuid() }; + let session_dir = helper_session_dir(uid); + fs::create_dir(&session_dir).map_err(|error| BackendError::Failed { + code: "HELPER_SESSION_FAILED".into(), + detail: format!("Failed to create helper session directory: {error}"), + })?; + fs::set_permissions(&session_dir, fs::Permissions::from_mode(0o700)).map_err(|error| { + BackendError::Failed { + code: "HELPER_SESSION_FAILED".into(), + detail: format!("Failed to secure helper session directory: {error}"), + } + })?; + let _cleanup = SessionCleanup(session_dir.clone()); + + let socket_path = helper_socket_path(&session_dir)?; + let listener = UnixListener::bind(&socket_path).map_err(|error| BackendError::Failed { + code: "HELPER_SESSION_FAILED".into(), + detail: format!("Failed to create helper control socket: {error}"), + })?; + fs::set_permissions(&socket_path, fs::Permissions::from_mode(0o600)).map_err(|error| { + BackendError::Failed { + code: "HELPER_SESSION_FAILED".into(), + detail: format!("Failed to secure helper control socket: {error}"), + } + })?; + listener + .set_nonblocking(true) + .map_err(|error| BackendError::Failed { + code: "HELPER_SESSION_FAILED".into(), + detail: format!("Failed to configure helper control socket: {error}"), + })?; + + let token = Uuid::new_v4().to_string(); + let mut child = ChildGuard::new(launch_elevated_helper(&helper, &socket_path, &token)?); + let stream = accept_helper(&listener, &mut child, &stop)?; + stream + .set_read_timeout(Some(IO_POLL_INTERVAL)) + .map_err(|error| BackendError::Failed { + code: "HELPER_SESSION_FAILED".into(), + detail: format!("Failed to configure helper socket timeout: {error}"), + })?; + let mut writer = stream.try_clone().map_err(|error| BackendError::Failed { + code: "HELPER_SESSION_FAILED".into(), + detail: format!("Failed to clone helper socket: {error}"), + })?; + let mut reader = HelperEventReader::new(stream); + + let hello = read_helper_hello(&mut reader, &mut child, &stop)?; + if hello.version != PROTOCOL_VERSION + || hello.event != "hello" + || hello.token.as_deref() != Some(token.as_str()) + || hello.helper_version.as_deref() != Some(MACOS_HELPER_VERSION) + || hello.architecture.as_deref() != Some("aarch64") + { + return Err(BackendError::Failed { + code: "HELPER_VERSION_MISMATCH".into(), + detail: + "The bundled helper failed protocol, token, version, or architecture validation" + .into(), + }); + } + + write_request( + &mut writer, + &StartRequest { + version: PROTOCOL_VERSION, + command: "start", + token: &token, + overlay: &context.overlay_root, + allowed_root: &context.allowed_root, + game_bundle, + game_executable: &context.league_install.game_executable, + client_uid: uid, + }, + )?; + + let mut stop_sent = false; + let mut stop_deadline = None; + loop { + if stop.load(Ordering::SeqCst) && !stop_sent { + write_request( + &mut writer, + &StopRequest { + version: PROTOCOL_VERSION, + command: "stop", + }, + )?; + stop_sent = true; + stop_deadline = Some(Instant::now() + STOP_TIMEOUT); + } + if stop_deadline.is_some_and(|deadline| Instant::now() >= deadline) { + tracing::warn!("Elevated macOS helper did not acknowledge stop before timeout"); + return Err(BackendError::Stopped); + } + + match reader.read_event() { + Ok(Some(event)) => { + if event.version != PROTOCOL_VERSION { + return Err(BackendError::Failed { + code: "HELPER_PROTOCOL_ERROR".into(), + detail: format!( + "Unsupported helper protocol version {}", + event.version + ), + }); + } + if event.event == "error" { + return Err(BackendError::Failed { + code: event.code.unwrap_or_else(|| "PATCHER_HELPER_FAILED".into()), + detail: event + .detail + .unwrap_or_else(|| "The macOS helper reported an error".into()), + }); + } + events(BackendEvent { + event: event.event.clone(), + pid: event.pid, + architecture: event.architecture, + signature: event.signature, + detail: event.detail, + }); + if event.event == "stopped" { + child.wait_bounded(); + return if stop_sent { + Err(BackendError::Stopped) + } else { + Ok(()) + }; + } + } + Ok(None) => { + if reader.is_eof() { + return Err(BackendError::Failed { + code: "HELPER_DISCONNECTED".into(), + detail: "The elevated helper disconnected before reporting completion" + .into(), + }); + } + if let Ok(Some(status)) = child.try_wait() { + return Err(BackendError::Failed { + code: "HELPER_EXITED".into(), + detail: format!( + "The elevated helper exited unexpectedly with status {status}" + ), + }); + } + } + Err(error) => return Err(error), + } + } + } +} + +fn helper_session_dir(uid: u32) -> PathBuf { + PathBuf::from("/tmp").join(format!("ltk-{uid}-{}", Uuid::new_v4().simple())) +} + +fn helper_socket_path(session_dir: &Path) -> BackendResult { + let socket_path = session_dir.join("c"); + let path_len = socket_path.as_os_str().as_bytes().len(); + if path_len > MACOS_UNIX_SOCKET_PATH_MAX { + return Err(BackendError::Failed { + code: "HELPER_SESSION_FAILED".into(), + detail: format!( + "Helper control socket path is {path_len} bytes; macOS allows at most {MACOS_UNIX_SOCKET_PATH_MAX}" + ), + }); + } + Ok(socket_path) +} + +pub fn resolve_helper_path(app_handle: &AppHandle) -> Option { + let mut candidates = Vec::new(); + if let Ok(current_exe) = std::env::current_exe() { + if let Some(directory) = current_exe.parent() { + candidates.push(directory.join(HELPER_NAME)); + candidates.push(directory.join(HELPER_TARGET_NAME)); + } + } + if let Ok(resource_dir) = app_handle.path().resource_dir() { + candidates.push(resource_dir.join(HELPER_NAME)); + candidates.push(resource_dir.join(HELPER_TARGET_NAME)); + candidates.push(resource_dir.join("binaries").join(HELPER_NAME)); + candidates.push(resource_dir.join("binaries").join(HELPER_TARGET_NAME)); + } + candidates.push( + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("binaries") + .join(HELPER_TARGET_NAME), + ); + candidates.push( + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("..") + .join("target") + .join("debug") + .join(HELPER_NAME), + ); + + candidates + .into_iter() + .find(|path| path.is_file() && is_executable(path)) +} + +fn is_executable(path: &Path) -> bool { + fs::metadata(path) + .map(|metadata| metadata.permissions().mode() & 0o111 != 0) + .unwrap_or(false) +} + +fn launch_elevated_helper(helper: &Path, socket: &Path, token: &str) -> BackendResult { + if unsafe { libc::geteuid() } == 0 + || std::env::var_os("LTK_MACOS_PATCHER_NO_ELEVATION").is_some() + { + return Command::new(helper) + .args(["--socket"]) + .arg(socket) + .args(["--token", token]) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::piped()) + .spawn() + .map_err(|error| BackendError::Failed { + code: "HELPER_LAUNCH_FAILED".into(), + detail: format!("Failed to launch macOS helper: {error}"), + }); + } + + let command = format!( + "{} --socket {} --token {}", + shell_quote(helper.as_os_str().to_string_lossy().as_ref()), + shell_quote(socket.as_os_str().to_string_lossy().as_ref()), + shell_quote(token) + ); + let apple_script = format!( + "do shell script \"{}\" with administrator privileges", + command.replace('\\', "\\\\").replace('"', "\\\"") + ); + Command::new("/usr/bin/osascript") + .args(["-e", &apple_script]) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::piped()) + .spawn() + .map_err(|error| BackendError::Failed { + code: "HELPER_LAUNCH_FAILED".into(), + detail: format!("Failed to request macOS administrator approval: {error}"), + }) +} + +fn shell_quote(value: &str) -> String { + format!("'{}'", value.replace('\'', "'\\''")) +} + +fn accept_helper( + listener: &UnixListener, + child: &mut Child, + stop: &AtomicBool, +) -> BackendResult { + let deadline = Instant::now() + STARTUP_TIMEOUT; + loop { + if stop.load(Ordering::SeqCst) { + let _ = child.kill(); + return Err(BackendError::Stopped); + } + match listener.accept() { + Ok((stream, _)) => return Ok(stream), + Err(error) if error.kind() == ErrorKind::WouldBlock => {} + Err(error) => { + return Err(BackendError::Failed { + code: "HELPER_SESSION_FAILED".into(), + detail: format!("Failed to accept helper connection: {error}"), + }); + } + } + if let Ok(Some(status)) = child.try_wait() { + return Err(BackendError::Failed { + code: "HELPER_AUTHORIZATION_DENIED".into(), + detail: format!( + "The helper did not start. Administrator approval may have been cancelled ({status})." + ), + }); + } + if Instant::now() >= deadline { + let _ = child.kill(); + return Err(BackendError::Failed { + code: "HELPER_START_TIMEOUT".into(), + detail: "Timed out waiting for the elevated macOS helper".into(), + }); + } + std::thread::sleep(Duration::from_millis(100)); + } +} + +fn read_helper_hello( + reader: &mut HelperEventReader, + child: &mut Child, + stop: &AtomicBool, +) -> BackendResult { + let deadline = Instant::now() + STARTUP_TIMEOUT; + loop { + if stop.load(Ordering::SeqCst) { + let _ = child.kill(); + return Err(BackendError::Stopped); + } + if let Some(event) = reader.read_event()? { + return Ok(event); + } + if reader.is_eof() { + return Err(BackendError::Failed { + code: "HELPER_PROTOCOL_ERROR".into(), + detail: "Helper disconnected before authentication".into(), + }); + } + if let Ok(Some(status)) = child.try_wait() { + return Err(BackendError::Failed { + code: "HELPER_EXITED".into(), + detail: format!("The elevated helper exited before authentication ({status})"), + }); + } + if Instant::now() >= deadline { + let _ = child.kill(); + return Err(BackendError::Failed { + code: "HELPER_START_TIMEOUT".into(), + detail: "Timed out waiting for the helper authentication frame".into(), + }); + } + } +} + +struct HelperEventReader { + stream: UnixStream, + buffer: Vec, + eof: bool, +} + +impl HelperEventReader { + fn new(stream: UnixStream) -> Self { + Self { + stream, + buffer: Vec::new(), + eof: false, + } + } + + fn is_eof(&self) -> bool { + self.eof + } + + fn read_event(&mut self) -> BackendResult> { + loop { + if let Some(newline) = self.buffer.iter().position(|byte| *byte == b'\n') { + let mut frame = self.buffer.drain(..=newline).collect::>(); + frame.pop(); + if frame.last() == Some(&b'\r') { + frame.pop(); + } + if frame.is_empty() { + continue; + } + return serde_json::from_slice(&frame).map(Some).map_err(|error| { + BackendError::Failed { + code: "HELPER_PROTOCOL_ERROR".into(), + detail: format!("Malformed helper event: {error}"), + } + }); + } + + if self.buffer.len() > MAX_HELPER_EVENT_BYTES { + return Err(BackendError::Failed { + code: "HELPER_PROTOCOL_ERROR".into(), + detail: "Helper response exceeded 64 KiB".into(), + }); + } + if self.eof { + if self.buffer.is_empty() { + return Ok(None); + } + return Err(BackendError::Failed { + code: "HELPER_PROTOCOL_ERROR".into(), + detail: "Helper disconnected with an incomplete event".into(), + }); + } + + let mut chunk = [0_u8; 4096]; + match self.stream.read(&mut chunk) { + Ok(0) => self.eof = true, + Ok(count) => { + self.buffer.extend_from_slice(&chunk[..count]); + if self.buffer.len() > MAX_HELPER_EVENT_BYTES { + return Err(BackendError::Failed { + code: "HELPER_PROTOCOL_ERROR".into(), + detail: "Helper response exceeded 64 KiB".into(), + }); + } + } + Err(error) + if matches!(error.kind(), ErrorKind::WouldBlock | ErrorKind::TimedOut) => + { + return Ok(None); + } + Err(error) => { + return Err(BackendError::Failed { + code: "HELPER_PROTOCOL_ERROR".into(), + detail: format!("Failed to read helper event: {error}"), + }); + } + } + } + } +} + +fn write_request(writer: &mut UnixStream, request: &impl Serialize) -> BackendResult<()> { + serde_json::to_writer(&mut *writer, request).map_err(|error| BackendError::Failed { + code: "HELPER_PROTOCOL_ERROR".into(), + detail: format!("Failed to encode helper request: {error}"), + })?; + writer + .write_all(b"\n") + .and_then(|_| writer.flush()) + .map_err(|error| BackendError::Failed { + code: "HELPER_PROTOCOL_ERROR".into(), + detail: format!("Failed to send helper request: {error}"), + }) +} + +fn wait_for_child(child: &mut Child) { + let deadline = Instant::now() + Duration::from_secs(5); + while Instant::now() < deadline { + if child.try_wait().ok().flatten().is_some() { + return; + } + std::thread::sleep(Duration::from_millis(50)); + } + let _ = child.kill(); + let _ = child.wait(); +} + +struct ChildGuard { + child: Child, + reaped: bool, +} + +impl ChildGuard { + fn new(child: Child) -> Self { + Self { + child, + reaped: false, + } + } + + fn wait_bounded(&mut self) { + wait_for_child(&mut self.child); + self.reaped = true; + } +} + +impl Deref for ChildGuard { + type Target = Child; + + fn deref(&self) -> &Self::Target { + &self.child + } +} + +impl DerefMut for ChildGuard { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.child + } +} + +impl Drop for ChildGuard { + fn drop(&mut self) { + if self.reaped { + return; + } + if matches!(self.child.try_wait(), Ok(None)) { + let _ = self.child.kill(); + } + let _ = self.child.wait(); + } +} + +struct SessionCleanup(PathBuf); + +impl Drop for SessionCleanup { + fn drop(&mut self) { + let _ = fs::remove_dir_all(&self.0); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn shell_quoting_handles_apostrophes() { + assert_eq!(shell_quote("/tmp/a'b"), "'/tmp/a'\\''b'"); + } + + #[test] + fn helper_control_socket_uses_a_short_bindable_path() { + let session_dir = helper_session_dir(501); + let socket_path = helper_socket_path(&session_dir).unwrap(); + assert!(socket_path.as_os_str().as_bytes().len() <= MACOS_UNIX_SOCKET_PATH_MAX); + + fs::create_dir(&session_dir).unwrap(); + let _cleanup = SessionCleanup(session_dir); + UnixListener::bind(socket_path).unwrap(); + } + + #[test] + fn helper_control_socket_rejects_overlong_paths_before_bind() { + let session_dir = PathBuf::from("/tmp").join("x".repeat(MACOS_UNIX_SOCKET_PATH_MAX)); + assert!(matches!( + helper_socket_path(&session_dir), + Err(BackendError::Failed { code, .. }) if code == "HELPER_SESSION_FAILED" + )); + } + + #[test] + fn helper_event_reader_preserves_partial_frames_across_timeouts() { + let (reader_stream, mut writer) = UnixStream::pair().unwrap(); + reader_stream + .set_read_timeout(Some(Duration::from_millis(10))) + .unwrap(); + let mut reader = HelperEventReader::new(reader_stream); + + writer.write_all(br#"{"version":1,"event":"wait"#).unwrap(); + assert!(reader.read_event().unwrap().is_none()); + + writer.write_all(b"ingForGame\"}\n").unwrap(); + let event = reader.read_event().unwrap().unwrap(); + assert_eq!(event.event, "waitingForGame"); + } + + #[test] + fn helper_event_reader_returns_buffered_frames_one_at_a_time() { + let (reader_stream, mut writer) = UnixStream::pair().unwrap(); + let mut reader = HelperEventReader::new(reader_stream); + writer + .write_all( + b"{\"version\":1,\"event\":\"ready\"}\n{\"version\":1,\"event\":\"patched\",\"pid\":42}\n", + ) + .unwrap(); + + let ready = reader.read_event().unwrap().unwrap(); + let patched = reader.read_event().unwrap().unwrap(); + assert_eq!(ready.event, "ready"); + assert_eq!(patched.event, "patched"); + assert_eq!(patched.pid, Some(42)); + } +} diff --git a/src-tauri/src/patcher/backend/mod.rs b/src-tauri/src/patcher/backend/mod.rs new file mode 100644 index 00000000..20d52746 --- /dev/null +++ b/src-tauri/src/patcher/backend/mod.rs @@ -0,0 +1,215 @@ +use crate::error::AppResult; +use crate::platform::LeagueInstall; +use serde::Serialize; +use std::path::PathBuf; +use std::sync::atomic::AtomicBool; +use std::sync::Arc; +use tauri::AppHandle; +use ts_rs::TS; + +#[cfg(target_os = "macos")] +mod macos; +#[cfg(not(any(target_os = "windows", target_os = "macos")))] +mod unsupported; +#[cfg(target_os = "windows")] +mod windows; + +#[cfg(target_os = "macos")] +pub use macos::{resolve_helper_path, MacOsBackend, MACOS_HELPER_VERSION}; +#[cfg(not(any(target_os = "windows", target_os = "macos")))] +use unsupported::UnsupportedBackend; +#[cfg(target_os = "windows")] +use windows::WindowsDllBackend; + +#[derive(Debug, Clone)] +pub struct PatcherContext { + pub overlay_root: PathBuf, + pub allowed_root: PathBuf, + pub league_install: LeagueInstall, + pub log_file: Option, + pub timeout_ms: u32, + pub flags: u64, +} + +#[derive(Debug, Clone, Serialize, TS)] +#[ts(export)] +#[serde(rename_all = "camelCase")] +pub struct PatcherAvailability { + pub supported: bool, + pub ready: bool, + pub reason: Option, + pub requires_setup: bool, + pub permission_required: bool, + pub helper_version: Option, +} + +impl PatcherAvailability { + pub fn unsupported(reason: impl Into) -> Self { + Self { + supported: false, + ready: false, + reason: Some(reason.into()), + requires_setup: false, + permission_required: false, + helper_version: None, + } + } +} + +#[derive(Debug, Clone, Serialize, TS)] +#[ts(export)] +#[serde(rename_all = "camelCase")] +pub struct PatcherPreflight { + pub compatible: bool, + pub backend: String, + pub architecture: String, + pub signature: Option, + pub reason: Option, +} + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BackendEvent { + pub event: String, + pub pid: Option, + pub architecture: Option, + pub signature: Option, + pub detail: Option, +} + +#[derive(Debug, thiserror::Error)] +pub enum BackendError { + #[error("Patcher stopped by request")] + Stopped, + #[error("{code}: {detail}")] + Failed { code: String, detail: String }, +} + +pub type BackendResult = Result; +pub type PatcherEventSink = Arc; + +pub trait PatcherBackend: Send + Sync { + fn name(&self) -> &'static str; + fn availability(&self) -> PatcherAvailability; + fn preflight(&self, context: &PatcherContext) -> AppResult; + fn run( + &self, + context: PatcherContext, + stop: Arc, + events: PatcherEventSink, + ) -> BackendResult<()>; +} + +pub fn selected_backend(app_handle: &AppHandle) -> Box { + #[cfg(target_os = "windows")] + { + Box::new(WindowsDllBackend::new(app_handle.clone())) + } + #[cfg(target_os = "macos")] + { + Box::new(MacOsBackend::new(app_handle.clone())) + } + #[cfg(not(any(target_os = "windows", target_os = "macos")))] + { + let _ = app_handle; + Box::new(UnsupportedBackend) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::platform::league_install::LeaguePlatform; + use std::sync::atomic::Ordering; + use std::sync::Mutex; + + struct FakeBackend; + + impl PatcherBackend for FakeBackend { + fn name(&self) -> &'static str { + "fake" + } + + fn availability(&self) -> PatcherAvailability { + PatcherAvailability { + supported: true, + ready: true, + reason: None, + requires_setup: false, + permission_required: false, + helper_version: Some("test".into()), + } + } + + fn preflight(&self, _context: &PatcherContext) -> AppResult { + Ok(PatcherPreflight { + compatible: true, + backend: self.name().into(), + architecture: "test".into(), + signature: Some("fixture".into()), + reason: None, + }) + } + + fn run( + &self, + _context: PatcherContext, + stop: Arc, + events: PatcherEventSink, + ) -> BackendResult<()> { + events(BackendEvent { + event: "waitingForGame".into(), + pid: None, + architecture: Some("test".into()), + signature: None, + detail: None, + }); + if stop.load(Ordering::SeqCst) { + Err(BackendError::Stopped) + } else { + Ok(()) + } + } + } + + fn context() -> PatcherContext { + PatcherContext { + overlay_root: PathBuf::from("/tmp/overlay"), + allowed_root: PathBuf::from("/tmp"), + league_install: LeagueInstall { + configured_path: PathBuf::from("/game"), + install_root: PathBuf::from("/game"), + game_dir: PathBuf::from("/game/Game"), + client_lockfile: PathBuf::from("/game/lockfile"), + game_executable: PathBuf::from("/game/Game/game"), + game_bundle: None, + platform: LeaguePlatform::Windows, + }, + log_file: None, + timeout_ms: 100, + flags: 0, + } + } + + #[test] + fn fake_backend_emits_events_and_honors_cancellation() { + let backend = FakeBackend; + assert!(backend.preflight(&context()).unwrap().compatible); + + let events = Arc::new(Mutex::new(Vec::new())); + let captured = Arc::clone(&events); + let sink: PatcherEventSink = Arc::new(move |event| { + captured.lock().unwrap().push(event.event); + }); + let stop = Arc::new(AtomicBool::new(true)); + + assert!(matches!( + backend.run(context(), stop, sink), + Err(BackendError::Stopped) + )); + assert_eq!( + events.lock().unwrap().as_slice(), + ["waitingForGame".to_string()] + ); + } +} diff --git a/src-tauri/src/patcher/backend/unsupported.rs b/src-tauri/src/patcher/backend/unsupported.rs new file mode 100644 index 00000000..0631a2a7 --- /dev/null +++ b/src-tauri/src/patcher/backend/unsupported.rs @@ -0,0 +1,37 @@ +use super::{ + BackendError, BackendResult, PatcherAvailability, PatcherBackend, PatcherContext, + PatcherEventSink, PatcherPreflight, +}; +use crate::error::{AppError, AppResult}; +use std::sync::atomic::AtomicBool; +use std::sync::Arc; + +pub struct UnsupportedBackend; + +impl PatcherBackend for UnsupportedBackend { + fn name(&self) -> &'static str { + "unsupported" + } + + fn availability(&self) -> PatcherAvailability { + PatcherAvailability::unsupported("Live patching is not supported on this operating system") + } + + fn preflight(&self, _context: &PatcherContext) -> AppResult { + Err(AppError::Other( + "Live patching is not supported on this operating system".into(), + )) + } + + fn run( + &self, + _context: PatcherContext, + _stop: Arc, + _events: PatcherEventSink, + ) -> BackendResult<()> { + Err(BackendError::Failed { + code: "UNSUPPORTED_PLATFORM".into(), + detail: "Live patching is not supported on this operating system".into(), + }) + } +} diff --git a/src-tauri/src/patcher/backend/windows.rs b/src-tauri/src/patcher/backend/windows.rs new file mode 100644 index 00000000..e51f3e85 --- /dev/null +++ b/src-tauri/src/patcher/backend/windows.rs @@ -0,0 +1,129 @@ +use super::{ + BackendError, BackendResult, PatcherAvailability, PatcherBackend, PatcherContext, + PatcherEventSink, PatcherPreflight, +}; +use crate::error::{AppError, AppResult}; +use crate::legacy_patcher::api::PATCHER_DLL_NAME; +use crate::legacy_patcher::runner::{run_legacy_patcher_loop, LegacyPatcherLoopError}; +use std::path::PathBuf; +use std::sync::atomic::AtomicBool; +use std::sync::Arc; +use tauri::{AppHandle, Manager}; + +pub struct WindowsDllBackend { + app_handle: AppHandle, +} + +impl WindowsDllBackend { + pub fn new(app_handle: AppHandle) -> Self { + Self { app_handle } + } + + fn resolve_dll(&self) -> AppResult { + let resource_path = self + .app_handle + .path() + .resource_dir() + .map_err(|error| AppError::Other(format!("Failed to get resource directory: {error}")))? + .join(PATCHER_DLL_NAME); + if resource_path.exists() { + return Ok(resource_path); + } + + if let Some(path) = std::env::current_exe() + .ok() + .and_then(|path| path.parent().map(|parent| parent.join(PATCHER_DLL_NAME))) + .filter(|path| path.exists()) + { + return Ok(path); + } + + let manifest_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("resources") + .join(PATCHER_DLL_NAME); + if manifest_path.exists() { + return Ok(manifest_path); + } + + Err(AppError::Other(format!( + "Patcher DLL not found at {}", + manifest_path.display() + ))) + } +} + +impl PatcherBackend for WindowsDllBackend { + fn name(&self) -> &'static str { + "windows-dll" + } + + fn availability(&self) -> PatcherAvailability { + match self.resolve_dll() { + Ok(_) => PatcherAvailability { + supported: true, + ready: true, + reason: None, + requires_setup: false, + permission_required: false, + helper_version: None, + }, + Err(error) => PatcherAvailability { + supported: true, + ready: false, + reason: Some(error.to_string()), + requires_setup: true, + permission_required: false, + helper_version: None, + }, + } + } + + fn preflight(&self, _context: &PatcherContext) -> AppResult { + self.resolve_dll()?; + Ok(PatcherPreflight { + compatible: true, + backend: self.name().into(), + architecture: std::env::consts::ARCH.into(), + signature: None, + reason: None, + }) + } + + fn run( + &self, + context: PatcherContext, + stop: Arc, + events: PatcherEventSink, + ) -> BackendResult<()> { + let dll = self.resolve_dll().map_err(|error| BackendError::Failed { + code: "PATCHER_DLL_MISSING".into(), + detail: error.to_string(), + })?; + let mut overlay = context.overlay_root.display().to_string(); + if !overlay.ends_with(std::path::MAIN_SEPARATOR) { + overlay.push(std::path::MAIN_SEPARATOR); + } + events(super::BackendEvent { + event: "waitingForGame".into(), + pid: None, + architecture: Some(std::env::consts::ARCH.into()), + signature: None, + detail: None, + }); + match run_legacy_patcher_loop( + &dll, + &overlay, + context.log_file.as_deref(), + context.timeout_ms, + context.flags, + &stop, + ) { + Ok(()) => Ok(()), + Err(LegacyPatcherLoopError::Stopped) => Err(BackendError::Stopped), + Err(error) => Err(BackendError::Failed { + code: "WINDOWS_PATCHER_FAILED".into(), + detail: error.to_string(), + }), + } + } +} diff --git a/src-tauri/src/patcher/mod.rs b/src-tauri/src/patcher/mod.rs index 379f11d2..404bb07b 100644 --- a/src-tauri/src/patcher/mod.rs +++ b/src-tauri/src/patcher/mod.rs @@ -1,4 +1,7 @@ +#[cfg(target_os = "windows")] pub mod api; +pub mod backend; +#[cfg(target_os = "windows")] pub mod runner; use std::sync::atomic::AtomicBool; @@ -15,6 +18,7 @@ use ts_rs::TS; pub enum PatcherPhase { Idle, Building, + WaitingForGame, Patching, } @@ -52,6 +56,10 @@ pub struct PatcherStateInner { pub phase: PatcherPhase, /// Last patcher config used, for hot-reload. pub last_config: Option, + /// Active platform backend. + pub backend: Option, + /// Human-readable backend state. + pub message: Option, } impl PatcherStateInner { @@ -62,6 +70,8 @@ impl PatcherStateInner { config_path: None, phase: PatcherPhase::Idle, last_config: None, + backend: None, + message: None, } } @@ -107,6 +117,10 @@ mod tests { serde_json::to_string(&PatcherPhase::Building).unwrap(), "\"building\"" ); + assert_eq!( + serde_json::to_string(&PatcherPhase::WaitingForGame).unwrap(), + "\"waitingForGame\"" + ); assert_eq!( serde_json::to_string(&PatcherPhase::Patching).unwrap(), "\"patching\"" diff --git a/src-tauri/src/platform/league_install.rs b/src-tauri/src/platform/league_install.rs new file mode 100644 index 00000000..89c05b90 --- /dev/null +++ b/src-tauri/src/platform/league_install.rs @@ -0,0 +1,318 @@ +use crate::error::{AppError, AppResult}; +use std::path::{Path, PathBuf}; +#[cfg(target_os = "macos")] +use std::process::Command; + +const WINDOWS_GAME_EXE: &str = "League of Legends.exe"; +const MAC_GAME_APP: &str = "LeagueofLegends.app"; +const MAC_GAME_EXE: &str = "LeagueofLegends"; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum LeaguePlatform { + Windows, + MacOs, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct LeagueInstall { + pub configured_path: PathBuf, + pub install_root: PathBuf, + pub game_dir: PathBuf, + pub client_lockfile: PathBuf, + pub game_executable: PathBuf, + pub game_bundle: Option, + pub platform: LeaguePlatform, +} + +impl LeagueInstall { + pub fn resolve(path: impl AsRef) -> AppResult { + let configured_path = path.as_ref(); + + if let Some(install) = resolve_windows(configured_path) { + return Ok(install); + } + if let Some(install) = resolve_macos(configured_path) { + return Ok(install); + } + + Err(AppError::ValidationFailed(format!( + "League path is not a supported installation, Game directory, app bundle, or game executable: {}", + configured_path.display() + ))) + } + + pub fn auto_detect() -> Option { + if let Some(path) = std::env::var_os("LTK_LEAGUE_PATH") { + if let Ok(install) = Self::resolve(PathBuf::from(path)) { + return Some(install); + } + } + + #[cfg(target_os = "macos")] + { + for path in macos_common_paths() { + if let Ok(install) = Self::resolve(path) { + return Some(install); + } + } + + if let Some(path) = detect_macos_running_process() { + if let Ok(install) = Self::resolve(path) { + return Some(install); + } + } + } + + #[cfg(target_os = "windows")] + { + if let Some(exe_path) = ltk_mod_core::auto_detect_league_path() { + if let Ok(install) = Self::resolve(exe_path.as_str()) { + return Some(install); + } + } + } + + None + } + + pub fn configured_root(&self) -> PathBuf { + self.install_root.clone() + } +} + +fn canonical_or_original(path: PathBuf) -> PathBuf { + std::fs::canonicalize(&path).unwrap_or(path) +} + +fn resolve_windows(input: &Path) -> Option { + let mut candidates = Vec::new(); + if input.file_name().and_then(|name| name.to_str()) == Some(WINDOWS_GAME_EXE) { + candidates.push(input.parent()?.to_path_buf()); + } + candidates.push(input.to_path_buf()); + candidates.push(input.join("Game")); + + for game_dir in candidates { + let game_executable = game_dir.join(WINDOWS_GAME_EXE); + if !game_executable.is_file() { + continue; + } + + let game_dir = canonical_or_original(game_dir); + let install_root = game_dir.parent()?.to_path_buf(); + return Some(LeagueInstall { + configured_path: input.to_path_buf(), + client_lockfile: install_root.join("lockfile"), + install_root, + game_executable: canonical_or_original(game_executable), + game_dir, + game_bundle: None, + platform: LeaguePlatform::Windows, + }); + } + + None +} + +fn resolve_macos(input: &Path) -> Option { + let search_start = if input.is_file() { + input.parent()? + } else { + input + }; + + let mut game_candidates = vec![ + search_start.to_path_buf(), + search_start.join("Game"), + search_start.join("Contents").join("LoL").join("Game"), + ]; + game_candidates.extend( + search_start + .ancestors() + .take(8) + .filter(|path| path.file_name().and_then(|name| name.to_str()) == Some("Game")) + .map(Path::to_path_buf), + ); + game_candidates.extend( + search_start + .ancestors() + .take(10) + .filter(|path| path.extension().and_then(|ext| ext.to_str()) == Some("app")) + .map(|bundle| bundle.join("Contents").join("LoL").join("Game")), + ); + + for game_dir in game_candidates { + let game_bundle = game_dir.join(MAC_GAME_APP); + let game_executable = game_bundle + .join("Contents") + .join("MacOS") + .join(MAC_GAME_EXE); + if !game_executable.is_file() { + continue; + } + + let lol_root = game_dir.parent()?.to_path_buf(); + let outer_bundle = outer_macos_bundle(&lol_root).unwrap_or_else(|| lol_root.clone()); + return Some(LeagueInstall { + configured_path: input.to_path_buf(), + install_root: canonical_or_original(outer_bundle), + game_dir: canonical_or_original(game_dir), + client_lockfile: canonical_or_original(lol_root).join("lockfile"), + game_executable: canonical_or_original(game_executable), + game_bundle: Some(canonical_or_original(game_bundle)), + platform: LeaguePlatform::MacOs, + }); + } + + None +} + +fn outer_macos_bundle(lol_root: &Path) -> Option { + if lol_root.file_name().and_then(|name| name.to_str()) != Some("LoL") { + return None; + } + let contents = lol_root.parent()?; + if contents.file_name().and_then(|name| name.to_str()) != Some("Contents") { + return None; + } + let bundle = contents.parent()?; + bundle + .extension() + .and_then(|ext| ext.to_str()) + .filter(|ext| ext.eq_ignore_ascii_case("app"))?; + Some(bundle.to_path_buf()) +} + +#[cfg(target_os = "macos")] +fn macos_common_paths() -> Vec { + let mut paths = vec![ + PathBuf::from("/Applications/League of Legends.app"), + PathBuf::from("/Applications/LeagueofLegends.app"), + PathBuf::from("/Users/Shared/Riot Games/League of Legends.app"), + ]; + if let Some(home) = std::env::var_os("HOME") { + paths.push( + PathBuf::from(home) + .join("Applications") + .join("League of Legends.app"), + ); + } + paths +} + +#[cfg(target_os = "macos")] +fn detect_macos_running_process() -> Option { + let output = Command::new("/bin/ps") + .args(["-axo", "comm="]) + .output() + .ok()?; + if !output.status.success() { + return None; + } + + String::from_utf8_lossy(&output.stdout) + .lines() + .map(str::trim) + .filter(|line| { + line.contains("LeagueClient.app/Contents/MacOS/LeagueClient") + || line.contains("LeagueofLegends.app/Contents/MacOS/LeagueofLegends") + }) + .find_map(|line| LeagueInstall::resolve(line).ok()) + .map(|install| install.install_root) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn create_windows_install(root: &Path) -> PathBuf { + let game = root.join("Game"); + std::fs::create_dir_all(&game).unwrap(); + std::fs::write(game.join(WINDOWS_GAME_EXE), b"fixture").unwrap(); + root.to_path_buf() + } + + fn create_macos_install(root: &Path) -> PathBuf { + let bundle = root.join("League of Legends.app"); + let game = bundle.join("Contents").join("LoL").join("Game"); + let executable = game + .join(MAC_GAME_APP) + .join("Contents") + .join("MacOS") + .join(MAC_GAME_EXE); + std::fs::create_dir_all(executable.parent().unwrap()).unwrap(); + std::fs::write(executable, b"fixture").unwrap(); + bundle + } + + #[test] + fn resolves_windows_root_game_dir_and_executable() { + let temp = tempfile::tempdir().unwrap(); + let root = create_windows_install(temp.path()); + let game = root.join("Game"); + let exe = game.join(WINDOWS_GAME_EXE); + let canonical_root = std::fs::canonicalize(&root).unwrap(); + let canonical_game = std::fs::canonicalize(&game).unwrap(); + let canonical_exe = std::fs::canonicalize(&exe).unwrap(); + + for input in [&root, &game, &exe] { + let install = LeagueInstall::resolve(input).unwrap(); + assert_eq!(install.platform, LeaguePlatform::Windows); + assert_eq!(install.install_root, canonical_root); + assert_eq!(install.game_dir, canonical_game); + assert_eq!(install.game_executable, canonical_exe); + assert_eq!(install.client_lockfile, canonical_root.join("lockfile")); + } + } + + #[test] + fn resolves_macos_bundle_lol_root_game_dir_and_nested_selection() { + let temp = tempfile::tempdir().unwrap(); + let bundle = create_macos_install(temp.path()); + let lol_root = bundle.join("Contents").join("LoL"); + let game = lol_root.join("Game"); + let nested_bundle = game.join(MAC_GAME_APP); + let executable = nested_bundle + .join("Contents") + .join("MacOS") + .join(MAC_GAME_EXE); + let client_executable = bundle + .join("Contents") + .join("LoL") + .join("LeagueClient.app") + .join("Contents") + .join("MacOS") + .join("LeagueClient"); + std::fs::create_dir_all(client_executable.parent().unwrap()).unwrap(); + std::fs::write(&client_executable, b"fixture").unwrap(); + let canonical_bundle = std::fs::canonicalize(&bundle).unwrap(); + let canonical_lol_root = std::fs::canonicalize(&lol_root).unwrap(); + let canonical_game = std::fs::canonicalize(&game).unwrap(); + let canonical_nested_bundle = std::fs::canonicalize(&nested_bundle).unwrap(); + let canonical_executable = std::fs::canonicalize(&executable).unwrap(); + + for input in [ + &bundle, + &lol_root, + &game, + &nested_bundle, + &executable, + &client_executable, + ] { + let install = LeagueInstall::resolve(input).unwrap(); + assert_eq!(install.platform, LeaguePlatform::MacOs); + assert_eq!(install.install_root, canonical_bundle); + assert_eq!(install.game_dir, canonical_game); + assert_eq!(install.game_executable, canonical_executable); + assert_eq!(install.game_bundle.as_ref(), Some(&canonical_nested_bundle)); + assert_eq!(install.client_lockfile, canonical_lol_root.join("lockfile")); + } + } + + #[test] + fn rejects_incomplete_layouts() { + let temp = tempfile::tempdir().unwrap(); + std::fs::create_dir_all(temp.path().join("Game")).unwrap(); + assert!(LeagueInstall::resolve(temp.path()).is_err()); + } +} diff --git a/src-tauri/src/platform/mod.rs b/src-tauri/src/platform/mod.rs new file mode 100644 index 00000000..4fb5a29c --- /dev/null +++ b/src-tauri/src/platform/mod.rs @@ -0,0 +1,3 @@ +pub mod league_install; + +pub use league_install::LeagueInstall; diff --git a/src-tauri/src/setup.rs b/src-tauri/src/setup.rs index 7193be82..ce5b37f2 100644 --- a/src-tauri/src/setup.rs +++ b/src-tauri/src/setup.rs @@ -5,6 +5,7 @@ use tauri_plugin_deep_link::DeepLinkExt; use crate::deep_link::DeepLinkState; use crate::mods::{ModLibrary, ModLibraryState, WadReportState}; use crate::patcher::PatcherState; +use crate::platform::LeagueInstall; use crate::state::SettingsState; use crate::workshop::{Workshop, WorkshopState}; @@ -115,17 +116,13 @@ fn initialize_first_run(app_handle: &tauri::AppHandle, settings_state: &Settings tracing::info!("Attempting auto-detection of League installation..."); - if let Some(exe_path) = ltk_mod_core::auto_detect_league_path() { - let path = std::path::Path::new(exe_path.as_str()); + if let Some(install) = LeagueInstall::auto_detect() { + tracing::info!("Auto-detected League at: {:?}", install.install_root); + settings.league_path = Some(install.configured_root()); + settings.first_run_complete = true; - if let Some(install_root) = path.parent().and_then(|p| p.parent()) { - tracing::info!("Auto-detected League at: {:?}", install_root); - settings.league_path = Some(install_root.to_path_buf()); - settings.first_run_complete = true; - - if let Err(e) = crate::state::save_settings_to_disk(app_handle, &settings) { - tracing::error!("Failed to save auto-detected settings: {}", e); - } + if let Err(e) = crate::state::save_settings_to_disk(app_handle, &settings) { + tracing::error!("Failed to save auto-detected settings: {}", e); } } else { tracing::info!("Auto-detection did not find League installation"); diff --git a/src-tauri/src/utils/game.rs b/src-tauri/src/utils/game.rs index 625516c7..a850e9b7 100644 --- a/src-tauri/src/utils/game.rs +++ b/src-tauri/src/utils/game.rs @@ -1,31 +1,19 @@ //! Read-only utilities for resolving and inspecting a League game directory. use crate::error::{AppError, AppResult}; +use crate::platform::LeagueInstall; use crate::state::Settings; use std::path::{Path, PathBuf}; /// Resolve the game directory (the one containing `DATA`) from settings. /// -/// Users may configure either the install root (`…/League of Legends`) or the -/// `Game` subdirectory directly; both are accepted. +/// Users may configure any path accepted by the platform install resolver. pub(crate) fn resolve_game_dir(settings: &Settings) -> AppResult { let league_root = settings .league_path - .clone() + .as_ref() .ok_or_else(|| AppError::ValidationFailed("League path is not configured".to_string()))?; - - let game_dir = league_root.join("Game"); - if game_dir.exists() { - return Ok(game_dir); - } - if league_root.join("DATA").exists() { - return Ok(league_root); - } - - Err(AppError::ValidationFailed(format!( - "League path does not look like an install root or a Game directory: {}", - league_root.display() - ))) + Ok(LeagueInstall::resolve(league_root)?.game_dir) } /// Enumerate every `.wad` / `.wad.client` filename under the game's `DATA` directory. @@ -120,6 +108,11 @@ mod tests { fn resolve_game_dir_with_game_subdir() { let dir = tempfile::tempdir().unwrap(); std::fs::create_dir_all(dir.path().join("Game")).unwrap(); + std::fs::write( + dir.path().join("Game").join("League of Legends.exe"), + b"fixture", + ) + .unwrap(); let settings = Settings { league_path: Some(dir.path().to_path_buf()), @@ -133,13 +126,14 @@ mod tests { fn resolve_game_dir_with_data_dir() { let dir = tempfile::tempdir().unwrap(); std::fs::create_dir_all(dir.path().join("DATA")).unwrap(); + std::fs::write(dir.path().join("League of Legends.exe"), b"fixture").unwrap(); let settings = Settings { league_path: Some(dir.path().to_path_buf()), ..Settings::default() }; let result = resolve_game_dir(&settings).unwrap(); - assert_eq!(result, dir.path().to_path_buf()); + assert_eq!(result, std::fs::canonicalize(dir.path()).unwrap()); } #[test] diff --git a/src-tauri/src/utils/mod.rs b/src-tauri/src/utils/mod.rs index 4d82f5ea..26f6894e 100644 --- a/src-tauri/src/utils/mod.rs +++ b/src-tauri/src/utils/mod.rs @@ -1,2 +1,3 @@ pub mod game; +#[cfg(target_os = "windows")] pub mod native; diff --git a/src-tauri/tauri.macos.conf.json b/src-tauri/tauri.macos.conf.json new file mode 100644 index 00000000..fbcd495d --- /dev/null +++ b/src-tauri/tauri.macos.conf.json @@ -0,0 +1,10 @@ +{ + "bundle": { + "externalBin": ["binaries/ltk-macos-patcher"], + "resources": [], + "createUpdaterArtifacts": false, + "macOS": { + "minimumSystemVersion": "13.0" + } + } +} diff --git a/src/__tests__/lib/tauri.test.ts b/src/__tests__/lib/tauri.test.ts index df3f7a31..95f2ece4 100644 --- a/src/__tests__/lib/tauri.test.ts +++ b/src/__tests__/lib/tauri.test.ts @@ -91,6 +91,22 @@ describe("api", () => { await api.stopPatcher(); expect(mockInvoke).toHaveBeenCalledWith("stop_patcher", undefined); }); + + it("preflightPatcher invokes the preflight command", async () => { + mockInvoke.mockResolvedValueOnce({ + ok: true, + value: { + compatible: true, + backend: "macos-arm64-helper", + architecture: "arm64", + signature: "mac-arm64-pattern-v1", + reason: null, + }, + }); + + await api.preflightPatcher(); + expect(mockInvoke).toHaveBeenCalledWith("preflight_patcher", undefined); + }); }); describe("error handling", () => { diff --git a/src/components/Kbd.test.tsx b/src/components/Kbd.test.tsx new file mode 100644 index 00000000..41f17b56 --- /dev/null +++ b/src/components/Kbd.test.tsx @@ -0,0 +1,37 @@ +import { render, screen } from "@testing-library/react"; + +import { Kbd } from "./Kbd"; + +describe("Kbd", () => { + const platform = Object.getOwnPropertyDescriptor(navigator, "platform"); + + afterEach(() => { + if (platform) { + Object.defineProperty(navigator, "platform", platform); + } else { + Reflect.deleteProperty(navigator, "platform"); + } + }); + + it("uses macOS modifier labels for portable shortcuts", () => { + Object.defineProperty(navigator, "platform", { + configurable: true, + value: "MacIntel", + }); + render(); + + expect(screen.getByText("⌘")).toBeInTheDocument(); + expect(screen.getByText("⌥")).toBeInTheDocument(); + expect(screen.getByText("⇧")).toBeInTheDocument(); + }); + + it("uses Ctrl outside macOS", () => { + Object.defineProperty(navigator, "platform", { + configurable: true, + value: "Win32", + }); + render(); + + expect(screen.getByText("Ctrl")).toBeInTheDocument(); + }); +}); diff --git a/src/components/Kbd.tsx b/src/components/Kbd.tsx index 3829f99d..0fe235e0 100644 --- a/src/components/Kbd.tsx +++ b/src/components/Kbd.tsx @@ -13,7 +13,16 @@ const sizeClasses = { export const Kbd = forwardRef( ({ shortcut, size = "sm", className, ...props }, ref) => { - const keys = shortcut.split("+"); + const isMac = + typeof navigator !== "undefined" && /Mac|iPhone|iPad|iPod/.test(navigator.platform); + const keys = shortcut.split("+").map((key) => { + if (key === "CommandOrControl") return isMac ? "⌘" : "Ctrl"; + if (!isMac) return key; + if (key === "Ctrl" || key === "Super" || key === "Cmd") return "⌘"; + if (key === "Alt") return "⌥"; + if (key === "Shift") return "⇧"; + return key; + }); return ( invokeResult("start_patcher", { config }), stopPatcher: () => invokeResult("stop_patcher"), getPatcherStatus: () => invokeResult("get_patcher_status"), + preflightPatcher: () => invokeResult("preflight_patcher"), // Hotkeys pauseHotkeys: () => invokeResult("pause_hotkeys"), diff --git a/src/modules/patcher/components/PatcherStatusPill.tsx b/src/modules/patcher/components/PatcherStatusPill.tsx index 9c92609d..77dff18c 100644 --- a/src/modules/patcher/components/PatcherStatusPill.tsx +++ b/src/modules/patcher/components/PatcherStatusPill.tsx @@ -36,13 +36,15 @@ export function PatcherStatusPill() { ? `${testingProjects.length} projects` : null; - const label = building - ? testLabel - ? `Building ${testLabel}…` - : "Building overlay…" - : testLabel - ? `Testing ${testLabel}` - : "Patcher running"; + const label = status?.message + ? status.message + : building + ? testLabel + ? `Building ${testLabel}…` + : "Building overlay…" + : testLabel + ? `Testing ${testLabel}` + : "Patcher running"; const tone = building ? "accent" : "running"; diff --git a/src/modules/patcher/components/PatcherUnsupported.test.tsx b/src/modules/patcher/components/PatcherUnsupported.test.tsx new file mode 100644 index 00000000..33b70c64 --- /dev/null +++ b/src/modules/patcher/components/PatcherUnsupported.test.tsx @@ -0,0 +1,56 @@ +import { render, screen } from "@testing-library/react"; + +import type { PlatformSupport } from "@/lib/tauri"; + +import { PatcherUnsupported } from "./PatcherUnsupported"; + +function platform(patcher: Partial): PlatformSupport { + return { + os: "macos", + architecture: "aarch64", + patcher: { + supported: true, + ready: false, + reason: null, + requiresSetup: false, + permissionRequired: true, + helperVersion: null, + ...patcher, + }, + hotkeys: { + supported: true, + accessibilityPermissionRequired: false, + reason: null, + }, + }; +} + +describe("PatcherUnsupported", () => { + it("shows helper setup state separately from unsupported platforms", () => { + render( + , + ); + + expect(screen.getByText("macOS patcher helper is not ready")).toBeInTheDocument(); + expect(screen.getByText("Run pnpm macos:helper")).toBeInTheDocument(); + }); + + it("shows unsupported platform state", () => { + render( + , + ); + + expect(screen.getByText("Patcher not available on this platform")).toBeInTheDocument(); + }); +}); diff --git a/src/modules/patcher/components/PatcherUnsupported.tsx b/src/modules/patcher/components/PatcherUnsupported.tsx index ce291aaf..6d21e5e3 100644 --- a/src/modules/patcher/components/PatcherUnsupported.tsx +++ b/src/modules/patcher/components/PatcherUnsupported.tsx @@ -1,16 +1,29 @@ -import { Monitor } from "lucide-react"; +import { Monitor, ShieldAlert, Wrench } from "lucide-react"; + +import type { PlatformSupport } from "@/lib/tauri"; + +export function PatcherUnsupported({ platform }: { platform?: PlatformSupport }) { + const patcher = platform?.patcher; + const Icon = patcher?.requiresSetup + ? Wrench + : patcher?.permissionRequired + ? ShieldAlert + : Monitor; + const title = !patcher?.supported + ? "Patcher not available on this platform" + : patcher.requiresSetup + ? "macOS patcher helper is not ready" + : "Patcher setup is required"; + const detail = + patcher?.reason ?? + "Mod management works normally, but this platform cannot run the live overlay patcher."; -export function PatcherUnsupported() { return (
- +
- - Patcher not available on this platform - - - Mod management works normally. The overlay patcher requires Windows. - + {title} + {detail}
); diff --git a/src/modules/settings/components/HotkeySection.tsx b/src/modules/settings/components/HotkeySection.tsx index d65b24c9..f535d752 100644 --- a/src/modules/settings/components/HotkeySection.tsx +++ b/src/modules/settings/components/HotkeySection.tsx @@ -92,10 +92,9 @@ function HotkeyInput({ label, description, value, onSet }: HotkeyInputProps) { } const keys: string[] = []; - if (e.ctrlKey) keys.push("Ctrl"); + if (e.ctrlKey || e.metaKey) keys.push("CommandOrControl"); if (e.altKey) keys.push("Alt"); if (e.shiftKey) keys.push("Shift"); - if (e.metaKey) keys.push("Super"); const mainKey = e.key; // Ignore standalone modifier keys @@ -103,7 +102,10 @@ function HotkeyInput({ label, description, value, onSet }: HotkeyInputProps) { // Require at least one modifier if (keys.length === 0) { - toast.warning("Hotkey must include a modifier", "Use Ctrl, Alt, Shift, or Super with a key."); + toast.warning( + "Hotkey must include a modifier", + "Use Command/Ctrl, Alt/Option, or Shift with a key.", + ); return; } diff --git a/src/modules/settings/components/LeaguePathSection.tsx b/src/modules/settings/components/LeaguePathSection.tsx index 769290cd..e465c0cc 100644 --- a/src/modules/settings/components/LeaguePathSection.tsx +++ b/src/modules/settings/components/LeaguePathSection.tsx @@ -102,8 +102,9 @@ export function LeaguePathSection({ settings, onSave }: SettingsSectionProps) { {leaguePathValid === false && settings.leaguePath && (

- Could not find League of Legends at this path. Make sure it points to the folder - containing the Game directory. + Could not find League of Legends at this path. Select the League application bundle, its{" "} + Contents/LoL directory, or the + resolved Game directory.

)} diff --git a/src/modules/settings/components/PatchingSection.test.tsx b/src/modules/settings/components/PatchingSection.test.tsx new file mode 100644 index 00000000..8a1ab494 --- /dev/null +++ b/src/modules/settings/components/PatchingSection.test.tsx @@ -0,0 +1,108 @@ +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { vi } from "vitest"; + +import { api, type PlatformSupport, type Settings } from "@/lib/tauri"; + +import { PatchingSection } from "./PatchingSection"; + +const mocks = vi.hoisted(() => ({ + usePlatformSupport: vi.fn(), + refetch: vi.fn(), +})); + +vi.mock("@/hooks", () => ({ + usePlatformSupport: mocks.usePlatformSupport, +})); + +vi.mock("./WadBlocklistEditor", () => ({ + WadBlocklistEditor: () => null, +})); + +function platform(patcher: Partial = {}): PlatformSupport { + return { + os: "macos", + architecture: "aarch64", + patcher: { + supported: true, + ready: true, + reason: "Administrator approval is requested per session", + requiresSetup: false, + permissionRequired: true, + helperVersion: "1.9.0", + ...patcher, + }, + hotkeys: { + supported: true, + accessibilityPermissionRequired: false, + reason: null, + }, + }; +} + +function mockPlatform(data: PlatformSupport, isFetching = false) { + mocks.usePlatformSupport.mockReturnValue({ + data, + isFetching, + refetch: mocks.refetch, + }); +} + +const settings = { + leaguePath: "/Applications/League of Legends.app", + patchTft: true, + blockScriptsWad: true, +} as Settings; + +describe("PatchingSection macOS states", () => { + beforeEach(() => { + vi.restoreAllMocks(); + mocks.refetch.mockReset(); + mocks.usePlatformSupport.mockReset(); + }); + + it("shows setup-required state and allows helper recheck without a League path", async () => { + mockPlatform( + platform({ + ready: false, + requiresSetup: true, + reason: "Native helper is missing", + helperVersion: null, + }), + ); + render(); + + expect(screen.getByText("Native helper missing")).toBeInTheDocument(); + const button = screen.getByRole("button", { name: "Check helper again" }); + expect(button).toBeEnabled(); + await userEvent.click(button); + expect(mocks.refetch).toHaveBeenCalledOnce(); + }); + + it("shows ready and updating states", () => { + mockPlatform(platform(), true); + render(); + + expect(screen.getByText("Native helper ready")).toBeInTheDocument(); + expect(screen.getByText("Helper version 1.9.0")).toBeInTheDocument(); + expect(screen.getByRole("button", { name: "Check game compatibility" })).toBeDisabled(); + }); + + it("shows an incompatible dry-scan result", async () => { + mockPlatform(platform()); + vi.spyOn(api, "preflightPatcher").mockResolvedValue({ + ok: true, + value: { + compatible: false, + backend: "macos-arm64-helper", + architecture: "arm64", + signature: null, + reason: "No unique signature match", + }, + }); + render(); + + await userEvent.click(screen.getByRole("button", { name: "Check game compatibility" })); + expect(await screen.findByText("No unique signature match")).toBeInTheDocument(); + }); +}); diff --git a/src/modules/settings/components/PatchingSection.tsx b/src/modules/settings/components/PatchingSection.tsx index c2e7a938..62fb2547 100644 --- a/src/modules/settings/components/PatchingSection.tsx +++ b/src/modules/settings/components/PatchingSection.tsx @@ -1,7 +1,9 @@ -import { AlertTriangle, ShieldAlert } from "lucide-react"; +import { AlertTriangle, CheckCircle2, ScanSearch, ShieldAlert, Wrench } from "lucide-react"; +import { useState } from "react"; -import { SectionCard, Switch } from "@/components"; -import type { Settings } from "@/lib/tauri"; +import { Button, SectionCard, Switch } from "@/components"; +import { usePlatformSupport } from "@/hooks"; +import { api, isErr, type PatcherPreflight, type Settings } from "@/lib/tauri"; import { WadBlocklistEditor } from "./WadBlocklistEditor"; @@ -11,8 +13,100 @@ interface PatchingSectionProps { } export function PatchingSection({ settings, onSave }: PatchingSectionProps) { + const platform = usePlatformSupport(); + const [preflight, setPreflight] = useState(null); + const [preflightError, setPreflightError] = useState(null); + const [checking, setChecking] = useState(false); + const macSupport = platform.data?.os === "macos" ? platform.data : null; + + async function runPreflight() { + setChecking(true); + setPreflightError(null); + const result = await api.preflightPatcher(); + if (isErr(result)) { + setPreflight(null); + setPreflightError(result.error.message); + } else { + setPreflight(result.value); + } + setChecking(false); + } + return (
+ {macSupport && ( + }> +
+
+
+ + {macSupport.patcher.ready ? "Native helper ready" : "Native helper missing"} + + + {macSupport.patcher.reason ?? + "The ARM64 helper is bundled separately from the unprivileged app."} + + {macSupport.patcher.helperVersion && ( + + Helper version {macSupport.patcher.helperVersion} + + )} +
+ {macSupport.patcher.ready ? ( + + ) : ( + + )} +
+ + {!macSupport.patcher.ready && ( +
+ pnpm macos:helper +
+ )} + + {macSupport.patcher.permissionRequired && ( +

+ macOS asks for administrator approval when each patcher session starts. The main LTK + Manager process remains unprivileged. +

+ )} + +
+ + {preflight?.compatible && ( + + ARM64 signature {preflight.signature ?? "recognized"} + + )} +
+ + {(preflightError || (preflight && !preflight.compatible)) && ( +

+ {preflightError ?? preflight?.reason ?? "This League build is not supported."} +

+ )} +
+
+ )} + }>