mirror of
https://github.com/drakmor/ShadowMountPlus.git
synced 2026-10-19 21:29:58 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
24a421b350 | ||
|
|
599d5d04f3 | ||
|
|
f5c7870583 | ||
|
|
718f02017b | ||
|
|
3f49f60cec | ||
|
|
5ff85005bf | ||
|
|
b00536960d | ||
|
|
a4a35af5f0 | ||
|
|
3bff288a86 | ||
|
|
9485f22ddc | ||
|
|
75b310ee25 | ||
|
|
496ed48e33 | ||
|
|
4f48237d93 | ||
|
|
8d888c5a25 | ||
|
|
3c484e68ab | ||
|
|
7da387fc12 | ||
|
|
a393737423 | ||
|
|
6f64551b42 | ||
|
|
6e58123275 | ||
|
|
3aa62a3728 | ||
|
|
38bb6790db | ||
|
|
be92afa71d | ||
|
|
fa8423d427 | ||
|
|
3d8d248336 | ||
|
|
22bacfe168 | ||
|
|
4085fcd8ca | ||
|
|
eb3c611ebf | ||
|
|
9f0242044d | ||
|
|
c4e9839fe5 | ||
|
|
73b26ba3fb | ||
|
|
5691f25ece | ||
|
|
9cda9717ea | ||
|
|
e24a444147 | ||
|
|
03ad6d2e45 | ||
|
|
6d59555b66 | ||
|
|
7300dc1830 | ||
|
|
ad5afc5689 | ||
|
|
6d8622c829 | ||
|
|
9f85a3b421 | ||
|
|
0fb241cba5 | ||
|
|
09589b7ae2 | ||
|
|
8566c0294c | ||
|
|
56dd338abd | ||
|
|
090723879c | ||
|
|
16873e01f2 | ||
|
|
f20b97db65 | ||
|
|
9d6ab61369 | ||
|
|
25182da557 | ||
|
|
72cf4460a9 | ||
|
|
cc0f4e0684 | ||
|
|
8cdcd112f5 | ||
|
|
c41c3eeba8 | ||
|
|
3d6a300742 | ||
|
|
51d01011ff | ||
|
|
9e803cff7f | ||
|
|
dee175e2bd | ||
|
|
860e9de3b1 | ||
|
|
e0b41b3ca6 | ||
|
|
5c4470cdcd | ||
|
|
5b6be76272 | ||
|
|
c66bff6d2a | ||
|
|
2df61d7e2d | ||
|
|
e960fc8079 | ||
|
|
d1c6978e78 | ||
|
|
4eadb06f57 | ||
|
|
78f536b41a | ||
|
|
99e000eed9 | ||
|
|
8622d8757f | ||
|
|
bcffa18997 | ||
|
|
e00dda0075 | ||
|
|
315265f5db | ||
|
|
30b04358f4 | ||
|
|
a6ad4922a9 | ||
|
|
ebd649e0ea | ||
|
|
4072ced15d | ||
|
|
fee1029b0a | ||
|
|
cbd0fce4f9 | ||
|
|
9fa6ad94de | ||
|
|
d4c4cda739 | ||
|
|
954b3a7d17 | ||
|
|
3652f506c0 | ||
|
|
6c188a03bb | ||
|
|
2680ed28d7 | ||
|
|
f401660113 | ||
|
|
11a135cf1c | ||
|
|
146942c138 | ||
|
|
a8f1a216bd | ||
|
|
c37088543d | ||
|
|
059f8b40f1 | ||
|
|
6f61e8d29e | ||
|
|
a5471da456 | ||
|
|
0abfecd3e7 | ||
|
|
9f1786905c | ||
|
|
fa714f868f | ||
|
|
17bb561e8d | ||
|
|
0efeec16fa | ||
|
|
a584121d76 | ||
|
|
45da514549 | ||
|
|
e272c9736c | ||
|
|
902a6917df | ||
|
|
6df8a472e3 | ||
|
|
4e9afa7885 | ||
|
|
40967215b4 | ||
|
|
a23822a26f | ||
|
|
97ffedf0df | ||
|
|
bdd3083d65 | ||
|
|
bcb906e5b6 | ||
|
|
47a033b50b | ||
|
|
47bded61da | ||
|
|
5d0ca6d5d1 | ||
|
|
ebfe0376c6 | ||
|
|
a6a5437a42 | ||
|
|
8c1ebbdf28 | ||
|
|
853ff28b85 | ||
|
|
e9098631c1 | ||
|
|
bf24df419b | ||
|
|
ff70cb1bc8 | ||
|
|
9254525a6c | ||
|
|
26ba93c9aa | ||
|
|
43d61dce37 | ||
|
|
7759b83293 | ||
|
|
0eca3fa138 | ||
|
|
e30a296a53 | ||
|
|
b374c3163e | ||
|
|
a71a03d0db | ||
|
|
56e8ba2185 | ||
|
|
18a97ecca9 | ||
|
|
270264cc75 | ||
|
|
ef814ad56b | ||
|
|
cec94656c6 | ||
|
|
4ff9a3dffe | ||
|
|
fe7dad099c | ||
|
|
c02f237ddf | ||
|
|
d318b5e607 | ||
|
|
6f97cde602 | ||
|
|
edcac6bdd7 | ||
|
|
27feba2085 | ||
|
|
82603fce7d | ||
|
|
01b64346c8 | ||
|
|
7e48103149 | ||
|
|
124e757199 | ||
|
|
0fcc970a20 | ||
|
|
7684e8431d | ||
|
|
3166434db1 | ||
|
|
c785daaddf | ||
|
|
2db6b49439 | ||
|
|
aff2fc0e6c | ||
|
|
ae36dd5871 | ||
|
|
cc3e07d201 | ||
|
|
2c03ede25c | ||
|
|
a212a883b3 | ||
|
|
99a2956c28 | ||
|
|
93071924b5 | ||
|
|
47e47477d1 | ||
|
|
31c28e1896 | ||
|
|
c1f34ffa22 |
No files matched your search
@@ -0,0 +1,2 @@
|
||||
custom:
|
||||
- https://github.com/drakmor/shadowMountPlus#support-development
|
||||
@@ -0,0 +1,15 @@
|
||||
# Files included in the release archive.
|
||||
# One path per line, relative to the repository root.
|
||||
|
||||
shadowmountplus.elf
|
||||
config.ini.example
|
||||
LICENSE
|
||||
make_image.bat
|
||||
mkexfat.sh
|
||||
mkexfat_macos.sh
|
||||
mkufs2.sh
|
||||
New-OsfExfatImage.ps1
|
||||
README.md
|
||||
docs/socket-api.md
|
||||
docs/openapi.yaml
|
||||
tools/api_test.py
|
||||
@@ -0,0 +1,150 @@
|
||||
name: PS5 Build and Release
|
||||
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- "**/*.md"
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- "**/*.md"
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
outputs:
|
||||
archive_name: ${{ steps.package.outputs.archive_name }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Payload
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Checkout pacbrew-repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: ps5-payload-dev/pacbrew-repo
|
||||
path: pacbrew-repo
|
||||
|
||||
- name: Checkout SDK stubs
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: ps5-payload-dev/sdk
|
||||
path: ps5-sdk
|
||||
|
||||
- name: Setup build environment
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install -y \
|
||||
autoconf \
|
||||
automake \
|
||||
build-essential \
|
||||
clang-18 \
|
||||
cmake \
|
||||
curl \
|
||||
git \
|
||||
libarchive-tools \
|
||||
libtool \
|
||||
lld-18 \
|
||||
makepkg \
|
||||
meson \
|
||||
pacman-package-manager \
|
||||
pkg-config \
|
||||
python3 \
|
||||
python3-yaml \
|
||||
xxd \
|
||||
zip
|
||||
|
||||
- name: Build PS5 payload SDK
|
||||
working-directory: pacbrew-repo/sdk
|
||||
run: |
|
||||
makepkg -c -f
|
||||
sudo pacman --noconfirm -U ./ps5-payload-*.pkg.tar.gz
|
||||
|
||||
- name: Build PS5 openlibm package
|
||||
working-directory: pacbrew-repo/openlibm
|
||||
run: |
|
||||
makepkg -c -f
|
||||
sudo pacman --noconfirm -U ./ps5-payload-*.pkg.tar.gz
|
||||
|
||||
- name: Build PS5 json-c package
|
||||
working-directory: pacbrew-repo/json-c
|
||||
run: |
|
||||
makepkg -c -f
|
||||
sudo pacman --noconfirm -U ./ps5-payload-*.pkg.tar.gz
|
||||
|
||||
- name: Build PS5 sqlite package
|
||||
working-directory: pacbrew-repo/sqlite
|
||||
run: |
|
||||
makepkg -c -f
|
||||
sudo pacman --noconfirm -U ./ps5-payload-*.pkg.tar.gz
|
||||
|
||||
- name: Check API tools and manifest
|
||||
run: |
|
||||
python3 -m py_compile tools/api_test.py
|
||||
python3 -c 'import pathlib, yaml; doc = yaml.safe_load(pathlib.Path("docs/openapi.yaml").read_text()); assert doc["openapi"] == "3.1.2" and len(doc["paths"]) == 5'
|
||||
|
||||
- name: Build payload
|
||||
run: |
|
||||
make clean all PS5_SCE_STUBS_DIR="$GITHUB_WORKSPACE/ps5-sdk/sce_stubs"
|
||||
|
||||
- name: Create release archive
|
||||
id: package
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
version_tag="$(git describe --abbrev=6 --dirty --always --tags 2>/dev/null || echo "${GITHUB_SHA::7}")"
|
||||
archive_name="ShadowMountPlus_${version_tag}.zip"
|
||||
manifest_file=".github/release-files.txt"
|
||||
|
||||
mapfile -t release_files < <(grep -Ev '^[[:space:]]*(#|$)' "${manifest_file}")
|
||||
|
||||
if [ "${#release_files[@]}" -eq 0 ]; then
|
||||
echo "Release manifest is empty: ${manifest_file}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for release_file in "${release_files[@]}"; do
|
||||
if [ ! -f "${release_file}" ]; then
|
||||
echo "Missing release file: ${release_file}" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
zip -9 "${archive_name}" "${release_files[@]}"
|
||||
|
||||
echo "archive_name=${archive_name}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ steps.package.outputs.archive_name }}
|
||||
path: ${{ steps.package.outputs.archive_name }}
|
||||
if-no-files-found: error
|
||||
|
||||
release:
|
||||
needs: build
|
||||
permissions:
|
||||
contents: write
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download build artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ${{ needs.build.outputs.archive_name }}
|
||||
path: dist
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: ${{ github.ref_name }}
|
||||
generate_release_notes: true
|
||||
files: dist/${{ needs.build.outputs.archive_name }}
|
||||
@@ -1,5 +1,11 @@
|
||||
*.o
|
||||
*.elf
|
||||
*.so
|
||||
*.pyc
|
||||
__pycache__/
|
||||
daemon.lock
|
||||
debug.log
|
||||
notify.txt
|
||||
src/notify_icon_asset.c
|
||||
refs/*
|
||||
docs/*
|
||||
@@ -1,22 +1,69 @@
|
||||
PS5_PAYLOAD_SDK ?= /opt/ps5-payload-sdk
|
||||
include $(PS5_PAYLOAD_SDK)/toolchain/prospero.mk
|
||||
|
||||
# Standard Flags (No extra libraries)
|
||||
CFLAGS := -O2 -Wall -D_BSD_SOURCE -std=gnu11 -Isrc
|
||||
VERSION_TAG := $(shell git describe --abbrev=6 --dirty --always --tags 2>/dev/null || echo unknown)
|
||||
BUILD_TIME := $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
|
||||
|
||||
# Compiler and dependency flags
|
||||
JSON_C_ROOT := $(PS5_PAYLOAD_SDK)/target/user/homebrew
|
||||
JSON_CFLAGS := -I$(JSON_C_ROOT)/include
|
||||
CFLAGS := -O3 -flto=thin -DNDEBUG -ffunction-sections -fdata-sections -Wall -Wextra -Wstrict-prototypes -Wmissing-prototypes -Werror=strict-prototypes -Werror=missing-prototypes -D_BSD_SOURCE -std=gnu11 -Iinclude -Isrc
|
||||
CFLAGS += -DSHADOWMOUNT_VERSION=\"$(VERSION_TAG)\"
|
||||
|
||||
# Linker
|
||||
LDFLAGS :=
|
||||
LDFLAGS := -flto=thin -Wl,--gc-sections
|
||||
|
||||
# Standard Libraries Only
|
||||
LIBS := -lkernel_sys -lSceSystemService -lSceUserService -lSceAppInstUtil -lsqlite3
|
||||
# Libraries
|
||||
LIBS := -lSceNotification -lSceSystemService -lSceUserService -lSceAppInstUtil -lSceNet -lSceSsl -lSceHttp2 -lsqlite3 $(JSON_C_ROOT)/lib/libjson-c.a -lm
|
||||
PS5_SCE_STUBS_DIR ?= $(PS5_PAYLOAD_SDK)/src/sce_stubs
|
||||
KERNEL_SYS_STUB_SO := src/libkernel_sys_ext.so
|
||||
KERNEL_SYS_STUB_SRCS := $(PS5_SCE_STUBS_DIR)/libkernel_sys.c src/libkernel_sys_ext.c
|
||||
|
||||
ASSET_SRCS := src/notify_icon_asset.c src/config_ini_example_asset.c
|
||||
SRCS := src/main.c $(wildcard src/sm_*.c) $(ASSET_SRCS)
|
||||
ASM_SRCS := src/sm_shellcore_bridge.S
|
||||
OBJS := $(SRCS:.c=.o) $(ASM_SRCS:.S=.o)
|
||||
HEADERS := $(wildcard include/*.h)
|
||||
L10N_CATALOGS := $(wildcard include/lang/*.inc)
|
||||
|
||||
# Targets
|
||||
all: shadowmountplus.elf
|
||||
|
||||
usb-info.elf: tools/usb_info.c
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< -lSceNotification
|
||||
$(PS5_PAYLOAD_SDK)/bin/prospero-strip --strip-all $@
|
||||
|
||||
# Build Daemon
|
||||
shadowmountplus.elf: src/main.c
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< $(LIBS)
|
||||
$(PS5_PAYLOAD_SDK)/bin/prospero-strip --strip-debug $@
|
||||
shadowmountplus.elf: $(OBJS) $(KERNEL_SYS_STUB_SO)
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(OBJS) $(KERNEL_SYS_STUB_SO) $(LIBS)
|
||||
$(PS5_PAYLOAD_SDK)/bin/prospero-strip --strip-all $@
|
||||
rm -f src/notify_icon_asset.c src/config_ini_example_asset.c
|
||||
|
||||
$(KERNEL_SYS_STUB_SO): $(KERNEL_SYS_STUB_SRCS)
|
||||
test -f "$(PS5_SCE_STUBS_DIR)/libkernel_sys.c"
|
||||
$(CC) -shared -Wl,-soname=libkernel_sys.sprx -o $@ $^
|
||||
|
||||
src/notify_icon_asset.c: smp_icon.png
|
||||
xxd -i $< > $@
|
||||
|
||||
src/config_ini_example_asset.c: config.ini.example
|
||||
xxd -i $< > $@
|
||||
|
||||
src/sm_api_service.o: CFLAGS += $(JSON_CFLAGS)
|
||||
src/main.o src/sm_image_index.o: CFLAGS += -DSHADOWMOUNT_BUILD_TIME=\"$(BUILD_TIME)\"
|
||||
src/main.o src/sm_image_index.o: FORCE
|
||||
|
||||
src/sm_l10n.o: $(L10N_CATALOGS)
|
||||
src/sm_ampr_updater.o: src/sm_ampr_ca.inc
|
||||
|
||||
.PHONY: FORCE
|
||||
FORCE:
|
||||
|
||||
src/%.o: src/%.c $(HEADERS)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
src/%.o: src/%.S
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
clean:
|
||||
rm -f shadowmountplus.elf kill.elf src/*.o
|
||||
rm -f shadowmountplus.elf usb-info.elf api-test.elf kill.elf src/*.o $(KERNEL_SYS_STUB_SO) src/notify_icon_asset.c src/config_ini_example_asset.c
|
||||
+5
-21
@@ -46,9 +46,9 @@
|
||||
-----
|
||||
- This script does NOT auto-elevate. Start PowerShell as Administrator.
|
||||
- Filesystem is always exFAT.
|
||||
- Cluster size is auto-selected:
|
||||
- large-file sets: 65536
|
||||
- small/mixed-file sets: 32768
|
||||
- Cluster size is fixed at 65536 bytes for the LVD type-5/BFS sdimg path.
|
||||
- Keep ShadowMount's LVD exFAT logical sector at 512 bytes; 65536 is the
|
||||
filesystem allocation unit and LVD secondary unit, not sector_size.
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
@@ -197,22 +197,6 @@ function Get-LogicalDriveFileSystem([string]$driveLetter) {
|
||||
return ""
|
||||
}
|
||||
|
||||
function Get-OptimalExfatClusterSize([string]$dir) {
|
||||
[Int64]$largeFileThreshold = 1MB
|
||||
|
||||
$files = @(Get-ChildItem -LiteralPath $dir -Recurse -File -Force)
|
||||
if ($files.Count -eq 0) { return 32768 }
|
||||
|
||||
[Int64]$rawFileBytes = 0
|
||||
foreach ($f in $files) {
|
||||
$rawFileBytes += [Int64]$f.Length
|
||||
}
|
||||
|
||||
[Int64]$avgFileBytes = [Int64]($rawFileBytes / [Int64]$files.Count)
|
||||
if ($avgFileBytes -ge $largeFileThreshold) { return 65536 }
|
||||
return 32768
|
||||
}
|
||||
|
||||
function Format-AllocationUnitArg([int]$clusterSize) {
|
||||
if ($clusterSize -ge 1MB -and ($clusterSize % 1MB) -eq 0) {
|
||||
return "{0}M" -f ($clusterSize / 1MB)
|
||||
@@ -308,7 +292,7 @@ if (Test-Path $ImagePath) {
|
||||
|
||||
[Int64]$expectedBytes = 0
|
||||
[string]$osfSizeArg = $null
|
||||
[int]$ExfatClusterSize = Get-OptimalExfatClusterSize -dir $SourceDir
|
||||
[int]$ExfatClusterSize = 65536
|
||||
[bool]$sizeProvided = -not [string]::IsNullOrWhiteSpace($Size)
|
||||
[int]$TargetClusterSize = $ExfatClusterSize
|
||||
|
||||
@@ -354,7 +338,7 @@ try {
|
||||
if ($CreateEmptyAndMount) {
|
||||
Write-Host "[2/2] Done. Empty image is mounted at $dest."
|
||||
Write-Host "Manual steps:"
|
||||
Write-Host " 1) Format $dest as exFAT (recommended cluster: 64KB for large-file sets, 32KB for small/mixed sets)."
|
||||
Write-Host " 1) Format $dest as exFAT with a 64KB allocation unit (required for the LVD/BFS fast-path profile)."
|
||||
Write-Host " 2) Copy contents of '$SourceDir' to $dest."
|
||||
Write-Host " 3) Dismount: `"$osf`" -d -m $MountPoint"
|
||||
return
|
||||
|
||||
@@ -1,16 +1,26 @@
|
||||
# ShadowMountPlus (PS5)
|
||||
|
||||
**Version:** `1.6beta1`
|
||||
|
||||
**Repository:** https://github.com/drakmor/shadowMountPlus
|
||||
|
||||
**Discord:** https://discord.gg/x2Ppvzwjhm
|
||||
|
||||
|
||||
**Warning! Mounting images can cause shutdown problems and data corruption on internal drives! This depends on many factors, but is more common with older firmware versions. Please take this into account when testing.**
|
||||
|
||||
|
||||
**ShadowMountPlus** is a fully automated, background "Auto-Mounter" payload for Jailbroken PlayStation 5 consoles. It streamlines the game mounting process by eliminating the need for manual configuration or external tools (such as DumpRunner or Itemzflow). ShadowMountPlus automatically detects, mounts, and installs game dumps from both **internal and external storage**.
|
||||
|
||||
|
||||
**Compatibility:** Supports all Jailbroken PS5 firmwares running **Kstuff v1.6.7**.
|
||||
**Compatibility:** Supports all Jailbroken PS5 firmwares running **[Kstuff-lite v1.07+](https://github.com/EchoStretch/kstuff-lite)**.
|
||||
|
||||
|
||||
## 💜 Support Development
|
||||
|
||||
If you want to support this project, you can donate
|
||||
- USDT (TRC-20): **`TKaUGEwMm9KBXzEoiaaKYBX2yCHAKASW3p`**
|
||||
- USDT (ERC-20): **`0x313dD245dBA957A5560618eA882d08e66aaFb430`**
|
||||
- USDC (Solana): **`5kv7j2RbUGaSP1kU1cZWj9jHH7d6rfvxmK6YXTYbH4um`**
|
||||
|
||||
|
||||
|
||||
## Current image support
|
||||
@@ -19,42 +29,119 @@
|
||||
|
||||
| Extension | Mounted FS | Attach backend | Status |
|
||||
| --- | --- | --- | --- |
|
||||
| `.exfat` | `exfatfs` | `LVD` or `MD` (configurable) | Optimal |
|
||||
| `.ffpkg` | `ufs` | `LVD` or `MD` (configurable) | Legacy |
|
||||
| `.ffpkg` | `ufs` | `LVD` or `MD` (configurable) | Recommended |
|
||||
| `.exfat` | `exfatfs` | `LVD` or `MD` (configurable) | Compatibility / external-drive-only titles |
|
||||
| `.ffpfs` | `pfs` | `LVD` | Experimental |
|
||||
| `.ffpfsc` | `pfs` container | `LVD` | Experimental container for nested images |
|
||||
|
||||
Notes:
|
||||
- Backend, read-only mode, and sector size can be configured via `/data/shadowmount/config.ini`.
|
||||
- Debug logging is enabled by default (`debug=1`) and writes to console plus `/data/shadowmount/debug.log` (set `debug=0` to disable).
|
||||
- **exFAT is the preferred image filesystem, including on 4.xx firmware, with no known reboot/shutdown issues in typical use.**
|
||||
- **UFS (`.ffpkg`) is the recommended image format for normal use.**
|
||||
- **Use exFAT (`.exfat`) only for titles that need external-drive-style compatibility.**
|
||||
- **When building exFAT images manually, keep the cluster size at `64 KB`; smaller clusters can reduce performance.**
|
||||
|
||||
## Recommended FS choice
|
||||
|
||||
- Prefer **exFAT (`.exfat`)** in most cases: it is generally more performant, case-insensitive, and does not have reboot/shutdown issues in typical use.
|
||||
- Use **UFS (`.ffpkg`)** only when specifically needed for compatibility with your game/setup.
|
||||
- Prefer **UFS (`.ffpkg`)** in most cases: this is the recommended default image format for ShadowMountPlus.
|
||||
- Use **exFAT (`.exfat`)** only for games that do not work correctly unless they are handled like external-drive content.
|
||||
- If you create an **exFAT (`.exfat`)** image manually, use a **`64 KB` cluster size**. Smaller clusters can cause a noticeable performance loss.
|
||||
|
||||
## Runtime config (`/data/shadowmount/config.ini`)
|
||||
|
||||
This file is optional. If it does not exist, built-in defaults are used.
|
||||
This file is optional. If it does not exist, ShadowMountPlus creates it from the bundled `config.ini.example` template on startup and uses built-in defaults until you uncomment overrides.
|
||||
|
||||
Supported keys (all optional):
|
||||
- `debug=1|0` (`1` enables `log_debug` output to console + `/data/shadowmount/debug.log`; default is `1`)
|
||||
- `quiet_mode=1|0` (`1` suppresses plain informational popups but keeps rich toasts; default is `0`)
|
||||
- `language=auto|<locale>` (notification language; `auto` uses the console system language, e.g. `en-US`, `ru-RU`; default: `auto`)
|
||||
- `api_bind_address=<IPv4>` (HTTP/JSON API bind address; default: `127.0.0.1`; use `0.0.0.0` for network access)
|
||||
- `api_port=<1..65535>` (HTTP/JSON API port; default: `10101`)
|
||||
- `mount_read_only=1|0` (default: `1`)
|
||||
- `force_mount=1|0` (mounting even damaged file systems; default: `0`)
|
||||
- `persistent_image_mounts=1|0` (`1` keeps discovered images mounted between game launches; default: `0`)
|
||||
- `app_install_all=1|0` (`1` stages new titles and submits them through the stock batch `sceAppInstUtilAppInstallAll`; default: `0` on every firmware. On FW `12.00+`, the default per-title path is provided by the SceShellCore TitleDir bridge.)
|
||||
- `auto_remove_missing_games=1|0` (`1` automatically removes games from the system library when their source is no longer available; default: `0`)
|
||||
- `auto_remove_games_with_dlc=1|0` (`1` allows automatic removal of games that have installed DLC; default: `0`)
|
||||
- `auto_remove_missing_delay_seconds=<1..86400>` (how long a source must remain unavailable before removal; default: `300`)
|
||||
- `image_ro=<image_filename>` (repeatable; force read-only mode for this image filename)
|
||||
- `image_rw=<image_filename>` (repeatable; force read-write mode for this image filename)
|
||||
- `recursive_scan=1|0` (`0` = scan only first-level subfolders, `1` = recursive scan without depth limit; default: `0`)
|
||||
- `backports_path=<absolute_path>` (default: `/data/backports`; if `<backports_path>/<TITLE_ID>` exists, it is overlaid on mounted image via `unionfs`)
|
||||
- `scan_interval_seconds=<1..3600>` (full scan loop interval; default: `10`)
|
||||
- `image_sector=<image_filename>:<sector_size>` (repeatable; force the logical device sector size, not the filesystem cluster size)
|
||||
- `scan_depth=<1..2>` (`1` = scan only first-level subfolders, `2` = also scan one additional nested level; default: `1`)
|
||||
- `recursive_scan=1|0` (deprecated compatibility key; `1` forces `scan_depth=2`)
|
||||
- `scan_interval_seconds=<1..3600>` (full scan loop interval; default: `15`)
|
||||
- `stability_wait_seconds=<0..3600>` (minimum source age before processing; default: `10`)
|
||||
- `exfat_backend=lvd|md` (default: `lvd`)
|
||||
- `ufs_backend=lvd|md` (default: `lvd`)
|
||||
- `legacy_mount_ufs=1|0` (`.ffpkg`; `1` selects the version 1.6 mount profile; default: `1`)
|
||||
- `legacy_mount_exfat=1|0` (`.exfat`; `1` selects the version 1.6 mount profile; default: `1`)
|
||||
- `legacy_mount_pfs=1|0` (`.ffpfs` and nested `pfs_image.dat`; `1` selects the version 1.6 mount profile; default: `1`)
|
||||
- `legacy_mount_pfsc=1|0` (outer `.ffpfsc`; `1` selects the version 1.6 mount profile; default: `1`)
|
||||
- `legacy_gddr5_cache=1|0` (request the PFS compressed-offset cache for nested images in legacy mode; default: `0`; optimized mode always requests it)
|
||||
- `backport_fakelib=1|0` (`1` mounts sandbox `fakelib` overlays for running games; default: `1`)
|
||||
- `update_emulators=1|0` (`1` updates all emulators with matching files in a game's own fakelib; default: `1`)
|
||||
- `emulators_path=<absolute_path>` (folder containing emulator update files; default: `/data/shadowmount/emus`)
|
||||
- `auto_update_ampr=1|0` (check for a new `libSceAmpr.sprx` 30 seconds after startup and every four hours; default: `0`)
|
||||
- `ampr_update_url=<http_or_https_url>` (AMPR emulator download URL; default: `https://github.com/drakmor/ampr_emu/releases/latest/download/libSceAmpr.sprx`)
|
||||
- `global_fakelib=1|0` (`1` enables the global fakelib overlay when the folder exists; default: `1`)
|
||||
- `global_fakelib_path=<absolute_path>` (global fakelib folder; default: `/data/shadowmount/fakelib`)
|
||||
- `global_fakelib_priority=game|global` (overlay priority when both global and game fakelib exist; default: `game`)
|
||||
- `global_fakelib_exclude=<TITLE_ID>` (repeatable; disables the global fakelib overlay for matching titles)
|
||||
- `kstuff_game_auto_toggle=1|0` (`1` pauses kstuff after tracked game launches and resumes it on stop; default: `1`)
|
||||
- `kstuff_crash_detection=1|0` (`1` enables crash monitoring and pause-delay autotune updates; default: `1`)
|
||||
- `fan_target_temperature=system|<50..91>` (automatic fan-controller target in degrees Celsius, applied when a game starts; `system` leaves fan control to the console and is the default)
|
||||
- `kstuff_pause_delay_image_seconds=<0..3600>` (delay before pausing kstuff for image-backed launches; default: `25`)
|
||||
- `kstuff_pause_delay_direct_seconds=<0..3600>` (delay before pausing kstuff for direct/non-image launches; default: `15`)
|
||||
- `kstuff_no_pause=<TITLE_ID>` (repeatable; keeps kstuff enabled for matching titles)
|
||||
- `kstuff_delay=<TITLE_ID>:<0..3600>` (repeatable; per-title pause delay override, last matching rule wins)
|
||||
- `/data/shadowmount/autotune.ini` may also provide per-title pause-delay overrides with highest priority:
|
||||
- `kstuff_delay=<TITLE_ID>:<0..3600>`
|
||||
- `<TITLE_ID>=<0..3600>`
|
||||
- `image_sector=<image_filename>:<sector_size>`
|
||||
- `scanpath=<absolute_path>` (can be repeated on multiple lines; default: built-in scan path list below)
|
||||
- `lvd_exfat_sector_size=<value>` (default: `512`)
|
||||
- `lvd_ufs_sector_size=<value>` (default: `4096`)
|
||||
- `lvd_pfs_sector_size=<value>` (default: `32768`)
|
||||
- `lvd_pfs_sector_size=<value>` (default: `4096`; the optimized profile uses a `65536`-byte LVD mapping unit)
|
||||
- `md_exfat_sector_size=<value>` (default: `512`)
|
||||
- `md_ufs_sector_size=<value>` (default: `512`)
|
||||
- `md_ufs_sector_size=<value>` (default: `512` with the legacy UFS profile, `4096` with the optimized profile)
|
||||
|
||||
Supported notification languages:
|
||||
|
||||
| Language | Locale |
|
||||
| --- | --- |
|
||||
| Arabic (Saudi Arabia) | `ar-SA` |
|
||||
| Chinese (Simplified) | `zh-CN` |
|
||||
| Chinese (Traditional) | `zh-TW` |
|
||||
| Czech | `cs-CZ` |
|
||||
| Danish | `da-DK` |
|
||||
| Dutch | `nl-NL` |
|
||||
| English (United Kingdom) | `en-GB` |
|
||||
| English (United States) | `en-US` |
|
||||
| Finnish | `fi-FI` |
|
||||
| French (Canada) | `fr-CA` |
|
||||
| French (France) | `fr-FR` |
|
||||
| German | `de-DE` |
|
||||
| Greek | `el-GR` |
|
||||
| Hungarian | `hu-HU` |
|
||||
| Indonesian | `id-ID` |
|
||||
| Italian | `it-IT` |
|
||||
| Japanese | `ja-JP` |
|
||||
| Korean | `ko-KR` |
|
||||
| Norwegian | `no-NO` |
|
||||
| Polish | `pl-PL` |
|
||||
| Portuguese (Brazil) | `pt-BR` |
|
||||
| Portuguese (Portugal) | `pt-PT` |
|
||||
| Romanian | `ro-RO` |
|
||||
| Russian | `ru-RU` |
|
||||
| Spanish (Mexico) | `es-MX` |
|
||||
| Spanish (Spain) | `es-ES` |
|
||||
| Swedish | `sv-SE` |
|
||||
| Thai | `th-TH` |
|
||||
| Turkish | `tr-TR` |
|
||||
| Vietnamese | `vi-VN` |
|
||||
|
||||
The public API routes and JSON schemas are documented in [docs/socket-api.md](docs/socket-api.md)
|
||||
and available as an [OpenAPI manifest](docs/openapi.yaml).
|
||||
|
||||
Per-image mode override behavior:
|
||||
- Match is done by image file name (without path).
|
||||
@@ -67,19 +154,67 @@ mount_read_only=1
|
||||
image_rw=PPSA1234-my-image.ffpfs
|
||||
image_rw=MYGame 123.exfat
|
||||
image_ro=legacy_dump.ffpkg
|
||||
image_sector=legacy-512-sector.exfat:512
|
||||
```
|
||||
|
||||
Persistent image mount behavior:
|
||||
- With `persistent_image_mounts=0` (default), backing images are mounted when a game starts and released after it exits.
|
||||
- With `persistent_image_mounts=1`, images discovered during scans remain mounted between launches. Per-title runtime and backport layers are still mounted only when needed.
|
||||
- USB-backed images are always released during suspend and mounted again by the resume scan.
|
||||
- Persistent mode consumes one backing device per mounted image layer and is intended for libraries that fit the available LVD/MD device capacity.
|
||||
|
||||
Per-image sector override behavior:
|
||||
- Match is done by image file name (without path).
|
||||
- `image_sector` in `/data/shadowmount/autotune.ini` has the highest priority for matching image files.
|
||||
- If no per-image rule matches, the backend-specific global sector size defaults are used.
|
||||
- When image validation fails because the mounted file-system cluster size is smaller than the selected device sector size, ShadowMountPlus writes `image_sector=<image_filename>:<cluster_size>` into `/data/shadowmount/autotune.ini` and asks you to try mounting again.
|
||||
- For the exFAT LVD/BFS fast path, leave the logical sector at its default `512`. The required `65536` values are the exFAT allocation unit and the internal LVD `secondary_unit`; do not set `image_sector=...:65536` to request this mode.
|
||||
|
||||
Scan path behavior:
|
||||
- If at least one `scanpath=...` is present, only those custom paths are used.
|
||||
- `/data/imgmnt/{ufsmnt,exfatmnt,pfsmnt}` are always added automatically, even with custom paths.
|
||||
- With `recursive_scan=0` (default), only first-level subfolders are checked.
|
||||
- With `recursive_scan=1`, subfolders are scanned recursively.
|
||||
- Full scan loop runs every `scan_interval_seconds` (default: `10`).
|
||||
- `/mnt/shadowmnt/pfsc` and `/mnt/shadowmnt` are always added automatically, even with custom paths.
|
||||
- With `scan_depth=1` (default), only first-level subfolders are checked.
|
||||
- With `scan_depth=2`, one additional nested level is checked.
|
||||
- If `recursive_scan=1` is set, ShadowMount+ forces `scan_depth=2`.
|
||||
- Full scan loop runs every `scan_interval_seconds` (default: `15`).
|
||||
- Sources newer than `stability_wait_seconds` are deferred until stable (default: `10`).
|
||||
- With `auto_remove_missing_games=1`, a game is removed from the system library only after its source remains unavailable for `auto_remove_missing_delay_seconds`. If the source becomes available again before removal, the pending removal is cancelled.
|
||||
- Direct folder installs use `<game>/sce_sys` for this check; image and backport sources use the target path itself.
|
||||
|
||||
Backport overlay behavior:
|
||||
- If `${backports_path}/${TITLE_ID}` exists, ShadowMount+ mounts it over the game root with `unionfs`.
|
||||
- Files from backport folder override files from the mounted image.
|
||||
- For each `scanpath`, use:
|
||||
- `<scanpath>/backports/<TITLE_ID>/`
|
||||
- The `backports` folder is ignored during normal game scanning.
|
||||
- A backport is applied automatically to the matching mounted game from any configured scan path.
|
||||
- If multiple scan paths provide the same title backport, the game's own scan path wins; otherwise scan path order is used.
|
||||
- ShadowMount+ checks the selected backport for `fakelib2` and then `fakelib`; if neither exists, it uses only `fakelib` from the original game source. The selected directory is mounted into the running game's sandbox `common/lib`.
|
||||
- If `update_emulators=1`, matching files from `emulators_path` replace files in the selected game fakelib. The cache is refreshed when its sources change and expires after seven days without a game launch.
|
||||
- With `auto_update_ampr=1`, ShadowMount+ checks for AMPR updates 30 seconds after startup and every four hours. It downloads a missing or newer emulator and displays a notification after a successful update.
|
||||
- The backport notification adds `Emulators updated` when emulator files are updated for the launched game.
|
||||
- If both global and per-game fakelib exist, they are combined in the game cache according to `global_fakelib_priority`. Without a per-game fakelib, the global folder is mounted directly.
|
||||
- Use repeatable `global_fakelib_exclude=<TITLE_ID>` entries to skip the global fakelib for specific games without disabling per-game fakelib.
|
||||
- `backport_fakelib=0` disables the sandbox `fakelib` watcher, including global fakelib and emulator updates.
|
||||
- For `backport_fakelib` to work correctly, the standalone `BackPork` payload must be disabled. Running both at the same time will conflict.
|
||||
|
||||
Kstuff game lifecycle behavior:
|
||||
- When `kstuff_game_auto_toggle=1`, ShadowMount watches game `exec/exit` events in the background.
|
||||
- Image-backed launches use `kstuff_pause_delay_image_seconds`; direct/non-image launches use `kstuff_pause_delay_direct_seconds`.
|
||||
- `kstuff_crash_detection=0` disables crash monitoring and the automatic pause-delay tuning logic, while leaving normal kstuff auto-pause/auto-resume behavior intact.
|
||||
- `kstuff_no_pause` skips auto-pause entirely for matching title IDs.
|
||||
- `kstuff_delay` overrides the pause delay for matching title IDs, regardless of image/direct launch type.
|
||||
- `/data/shadowmount/autotune.ini` overrides both `config.ini` and `autopause.txt` for matching title IDs.
|
||||
- `/data/shadowmount/autotune.ini` also overrides `image_sector` rules from `config.ini` for matching image file names.
|
||||
- A game source folder may optionally contain `autopause.txt`; it is read once at launch time.
|
||||
- Priority order is: `autotune.ini` -> `kstuff_delay` from `config.ini` -> `autopause.txt` -> global direct/image default delay.
|
||||
- If `autopause.txt` contains only a number, that value is used for direct launches and doubled for image-backed launches.
|
||||
- `autopause.txt` may also use:
|
||||
- `direct=<seconds>`
|
||||
- `image=<seconds>`
|
||||
- If both kinds of rule target the same title, `kstuff_no_pause` takes priority.
|
||||
- When crash monitoring detects an app crash before kstuff was paused, ShadowMountPlus only notifies that the app crashed.
|
||||
- When crash monitoring detects an app crash within 2 minutes after kstuff auto-pause, ShadowMountPlus doubles the applied pause delay for that title and upserts it into `/data/shadowmount/autotune.ini` (up to `3600` seconds), then prompts you to launch the game again.
|
||||
- When the last tracked game stops, ShadowMount immediately enables kstuff again if it was the component that disabled it.
|
||||
|
||||
|
||||
Validation:
|
||||
- See `config.ini.example` for a ready-to-use template.
|
||||
@@ -88,9 +223,11 @@ Validation:
|
||||
|
||||
Image mountpoints are created under:
|
||||
|
||||
`/data/imgmnt/<fs>/<image_name>`
|
||||
`/mnt/shadowmnt/<image_name>_<hash>`
|
||||
|
||||
Where `<fs>` is one of: `ufsmnt`, `exfatmnt`, `pfsmnt`.
|
||||
PFSC container mountpoints are created under:
|
||||
|
||||
`/mnt/shadowmnt/pfsc/<image_name>_<hash>`
|
||||
|
||||
Image layout requirement (`.ffpkg`, `.exfat`, `.ffpfs`):
|
||||
- Game files must be placed at the image root.
|
||||
@@ -98,6 +235,92 @@ Image layout requirement (`.ffpkg`, `.exfat`, `.ffpfs`):
|
||||
- Valid example: `/sce_sys/param.json` exists directly from image root.
|
||||
- Invalid example: `/GAME_FOLDER/sce_sys/param.json` (extra nesting level).
|
||||
|
||||
PFSC container layout requirement (`.ffpfsc`):
|
||||
- Do not place game files directly in the container root.
|
||||
- Place supported nested image files inside the container; ShadowMountPlus mounts those nested images and scans them for the game.
|
||||
- A nested `pfs_image.dat` file inside a PFSC container is treated as a PFS image.
|
||||
- `.ffpfsc` uses the nested outer PFS profile (`img_type=0x02`); `.ffpfs` and `pfs_image.dat` files mounted from inside it use the nested inner profile (`img_type=0x82`). Signature verification remains disabled for unsigned images; nested inner images request the PFSC compressed-offset cache before LVD attach.
|
||||
|
||||
## Compressed PFS containers (`.ffpfsc`)
|
||||
|
||||
Compressed PFS mode is intended only for nested images. During packing, data is
|
||||
zero-padded to a `64 KB` sector boundary, so the compressed PFS should be used
|
||||
as an outer container for another image rather than as a direct game-file
|
||||
layout.
|
||||
|
||||
Recommended layouts:
|
||||
- exFAT image inside compressed PFS.
|
||||
- Uncompressed PFS image inside compressed PFS.
|
||||
|
||||
MkPFS uses zLib compression. Decompression is hardware-assisted, but throughput
|
||||
is limited to roughly `150-250 MB/s`. This is about one third of the speed of an
|
||||
external USB drive, FFPKG/exFAT images, or about one tenth of the internal drive
|
||||
speed. Keep this in mind when choosing which games to pack. Games that read large
|
||||
amounts of data or stream textures continuously may stutter.
|
||||
|
||||
Use the official [PSBrew/MkPFS](https://github.com/PSBrew/MkPFS) tool to pack
|
||||
PFS images.
|
||||
|
||||
### Packing an uncompressed PFS image into compressed PFS
|
||||
|
||||
First create an uncompressed nested PFS image:
|
||||
|
||||
```bash
|
||||
mkpfs pack folder --verify --no-compress --no-adjust-output-file-extension --version PS5 --inode-bits 32 \
|
||||
'./PPSA07923/PPSA07923-app' \
|
||||
'./pfs_image.dat'
|
||||
```
|
||||
|
||||
Then pack the nested image (**pfs_image.dat**) into a compressed PFS container:
|
||||
|
||||
```bash
|
||||
mkpfs pack file --verify --version PS5 --inode-bits 32 \
|
||||
'./pfs_image.dat' \
|
||||
'./PPSA12345.ffpfsc'
|
||||
```
|
||||
|
||||
After successful packing, the temporary nested image can be removed:
|
||||
|
||||
```bash
|
||||
rm './pfs_image.dat'
|
||||
```
|
||||
|
||||
### Packing an exFAT image into compressed PFS
|
||||
|
||||
First create a normal exFAT image using one of the methods from
|
||||
`Creating an exFAT image`. The nested exFAT image name must keep the `.exfat`
|
||||
extension, for example `PPSA12345.exfat`.
|
||||
|
||||
Linux:
|
||||
|
||||
```bash
|
||||
chmod +x mkexfat.sh
|
||||
./mkexfat.sh ./PPSA12345-app ./PPSA12345.exfat
|
||||
```
|
||||
|
||||
Windows:
|
||||
|
||||
```cmd
|
||||
make_image.bat "C:\images\PPSA12345.exfat" "C:\payload\PPSA12345-app"
|
||||
```
|
||||
|
||||
The exFAT image must contain the game files at the image root, without an extra
|
||||
top-level folder.
|
||||
|
||||
Then pack the exFAT image into a compressed PFS container:
|
||||
|
||||
```bash
|
||||
mkpfs pack file --verify --version PS5 --inode-bits 32 \
|
||||
'./PPSA12345.exfat' \
|
||||
'./PPSA12345.ffpfsc'
|
||||
```
|
||||
|
||||
After successful packing, the temporary exFAT image can be removed:
|
||||
|
||||
```bash
|
||||
rm './PPSA12345.exfat'
|
||||
```
|
||||
|
||||
## Scan paths
|
||||
|
||||
Default scan locations:
|
||||
@@ -112,22 +335,102 @@ Default scan locations:
|
||||
- `/mnt/usb0` .. `/mnt/usb7`
|
||||
- `/mnt/ext0`
|
||||
- `/mnt/ext1`
|
||||
- `/data/imgmnt/ufsmnt`, `/data/imgmnt/exfatmnt`, `/data/imgmnt/pfsmnt` (mounted image content scan)
|
||||
- `/mnt/shadowmnt/pfsc` (mounted PFSC container scan)
|
||||
- `/mnt/shadowmnt` (mounted image content scan)
|
||||
|
||||
You can override scan roots with `scanpath=...` entries in `/data/shadowmount/config.ini`.
|
||||
|
||||
## Manual install list
|
||||
|
||||
For games that should not live under the normal scan paths, ShadowMountPlus also
|
||||
checks:
|
||||
|
||||
`/data/shadowmount/manual.lst`
|
||||
|
||||
Add one source per line:
|
||||
- Path to a game folder, where `sce_sys/param.json` exists inside that folder.
|
||||
- Path to a supported image file: `.ffpkg`, `.exfat`, `.ffpfs`, or `.ffpfsc`.
|
||||
- Empty lines and lines starting with `#` are ignored.
|
||||
|
||||
Example:
|
||||
```text
|
||||
/mnt/usb0/MyGames/PPSA12345
|
||||
/mnt/usb0/images/PPSA54321.ffpkg
|
||||
# /mnt/usb0/disabled/PPSA00000
|
||||
```
|
||||
|
||||
`manual.lst` is watched for changes. When you add a new line, ShadowMountPlus
|
||||
rescans shortly after the write, mounts images when needed, and installs the
|
||||
game through the same pipeline as normal scan path discoveries.
|
||||
|
||||
Manual install state is tracked in:
|
||||
|
||||
`/data/shadowmount/manual.status`
|
||||
|
||||
This status file is managed by ShadowMountPlus. If a manually installed game is
|
||||
later removed by the user and disappears from `app.db`, ShadowMountPlus marks it
|
||||
as deleted in `manual.status` and removes the matching source line from
|
||||
`manual.lst` so it is not installed again automatically. If you later add the
|
||||
same source path back to `manual.lst`, it will be processed again and the status
|
||||
will be updated after installation.
|
||||
|
||||
You can use **Dump Installer** to install or prepare game dumps for this manual
|
||||
workflow, then add the resulting game folder path or image file path to
|
||||
`manual.lst`.
|
||||
|
||||
Recommended folder structure:
|
||||
- Default mode (`recursive_scan=0`):
|
||||
- Default mode (`scan_depth=1`):
|
||||
- `/data/homebrew/<TITLE_ID>/`
|
||||
- `/data/etaHEN/games/<TITLE_ID>/`
|
||||
- `/data/homebrew/backports/<TITLE_ID>/`
|
||||
- `/data/etaHEN/games/backports/<TITLE_ID>/`
|
||||
|
||||
- Recursive mode (`recursive_scan=1`):
|
||||
- Nested mode (`scan_depth=2`):
|
||||
- `/data/homebrew/PS5/<AnyFolder>/<TITLE_ID>/`
|
||||
- `/mnt/ext0/etaHEN/games/<Collection>/<TITLE_ID>/`
|
||||
- `/mnt/ext0/etaHEN/games/backports/<TITLE_ID>/`
|
||||
|
||||
|
||||
## Creating an exFAT image
|
||||
|
||||
Recommended only for titles that need external-drive-style compatibility. For general use, prefer `.ffpkg`.
|
||||
|
||||
### LVD type-5 / BFS fast-path requirements
|
||||
|
||||
With `legacy_mount_exfat=0`, ShadowMount attaches `.exfat` through LVD as
|
||||
`img_type=5 (Sv)`, with a 512-byte
|
||||
logical sector and a 64 KiB `secondary_unit`. This lets the LVD worker collect
|
||||
up to 31 queued, 64-KiB-aligned requests of exactly 64 KiB and
|
||||
submit them through `bfs_iosession_rw_sdimg`/`BfsSdimg`. Non-matching requests
|
||||
continue through the normal vnode path.
|
||||
|
||||
All of the following are required for the kernel to consider that fast path:
|
||||
|
||||
- Use `exfat_backend=lvd` (the default). `/dev/mdctl` has no LVD image type or
|
||||
BFS sdimg batching.
|
||||
- Set `legacy_mount_exfat=0`; the default version 1.6 profile uses
|
||||
`img_type=0` and does not request this fast path.
|
||||
- Store the final `.exfat` file directly on the internal BFS, normally below
|
||||
`/data`. An image on USB/UFS/exFAT/PFS, or an exFAT image nested in compressed
|
||||
PFS, uses the normal path.
|
||||
- Format exFAT with a fixed 64 KiB allocation unit. The supplied Linux, macOS,
|
||||
and Windows builders now always use this value, including small-file images.
|
||||
- Keep `lvd_exfat_sector_size=512` and do not add an
|
||||
`image_sector=<name>.exfat:65536` override. A logical sector and an exFAT
|
||||
allocation unit are different geometries.
|
||||
- Make the image size a multiple of 64 KiB; the supplied builders round it to
|
||||
1 MiB and therefore satisfy this automatically.
|
||||
- Copy the completed image to BFS in one sequential operation when practical.
|
||||
This does not control eligibility, but avoids needless fragmentation of the
|
||||
backing file. The default read-only mount is preferred when writes are not
|
||||
required.
|
||||
|
||||
The 64 KiB cluster improves eligibility but cannot force every access through
|
||||
the batch path: exFAT metadata and small or unaligned application I/O still use
|
||||
the safe fallback. In the ShadowMount debug log, an eligible profile request is
|
||||
shown by `img=5 sec=512 sec2=65536`; the kernel still makes the final BFS and
|
||||
platform checks internally.
|
||||
|
||||
Linux (Ubuntu/Debian):
|
||||
- Required components installation:
|
||||
- `sudo apt-get update && sudo apt-get install -y exfatprogs exfat-fuse fuse3 rsync`
|
||||
@@ -139,9 +442,15 @@ Linux (Ubuntu/Debian):
|
||||
- Notes:
|
||||
- Source folder must be the game root and contain `eboot.bin`.
|
||||
- Auto-calculates image size using rounded file allocation + metadata + safety margin.
|
||||
- Automatically selects exFAT cluster profile:
|
||||
- Large-file profile: `64K`
|
||||
- Small/mixed-file profile: `32K`
|
||||
- Always formats with `mkfs.exfat -c 64K` for the LVD/BFS profile.
|
||||
- For a manual build, use an image size divisible by 64 KiB and run `mkfs.exfat -c 64K <image>`.
|
||||
|
||||
macOS:
|
||||
|
||||
- Script: `mkexfat_macos.sh` (uses the built-in exFAT, disk-image and `rsync` tools).
|
||||
- Example: `chmod +x mkexfat_macos.sh && ./mkexfat_macos.sh ./APPXXXX ./PPSA12345.exfat`.
|
||||
- The script attaches the raw image as a device and formats it with
|
||||
`newfs_exfat -b 65536` before copying the game-root contents.
|
||||
|
||||
Windows:
|
||||
- Recommended: use `make_image.bat` (wrapper for `New-OsfExfatImage.ps1` + OSFMount).
|
||||
@@ -149,12 +458,14 @@ Windows:
|
||||
- Install OSFMount: https://www.osforensics.com/tools/mount-disk-images.html.
|
||||
- Keep `make_image.bat` and `New-OsfExfatImage.ps1` in the same folder.
|
||||
- Run `cmd.exe` as Administrator.
|
||||
- If you build an exFAT image manually, format it with a `64K` allocation unit, for example `format X: /FS:exFAT /A:64K /Q`.
|
||||
- Usage:
|
||||
- `make_image.bat "C:\images\game.exfat" "C:\payload\APPXXXX"`
|
||||
- Behavior:
|
||||
- Auto-sizes the image to fit source content.
|
||||
- Source folder must be the game root and contain `eboot.bin`.
|
||||
- Formats and copies source folder contents into image root.
|
||||
- Uses a fixed 64 KiB allocation unit for both large-file and small/mixed-file images.
|
||||
- Optional (fixed size): run PowerShell script directly:
|
||||
- `powershell.exe -ExecutionPolicy Bypass -File .\New-OsfExfatImage.ps1 -ImagePath "C:\images\game.exfat" -SourceDir "C:\payload\APPXXXX" -Size 8G -ForceOverwrite`
|
||||
|
||||
@@ -170,14 +481,20 @@ FreeBSD:
|
||||
- Source folder must be the game root and contain `eboot.bin`.
|
||||
- The script auto-calculates image size using rounded file allocation + metadata + safety margin.
|
||||
- Recommended `newfs` parameters for UFS2:
|
||||
- Large-file profile: `newfs -O 2 -b 65536 -f 4096 -m 0 -i 262144`
|
||||
- Small/mixed-file profile: `newfs -O 2 -b 32768 -f 4096 -m 0 -i 262144`
|
||||
- `mkufs2.sh` selects between these two profiles automatically (based on average file size).
|
||||
- `newfs -O 2 -b 65536 -f 65536 -m 0 -S 4096`
|
||||
- `mkufs2.sh` keeps this fixed block/fragment/sector profile and auto-tunes `-i` based on source file/directory count.
|
||||
- Rough manual `-i` estimate for manual builds:
|
||||
- `target_inodes ~= file_count + dir_count + 2048`
|
||||
- `bytes_per_inode ~= image_size_bytes / target_inodes`
|
||||
- Round `bytes_per_inode` down to a multiple of `4096`, then keep it in the practical range `65536..262144`.
|
||||
- Practical rule of thumb: use `262144` for normal game dumps, `131072` for tens of thousands of files, and `65536` only for very file-dense images.
|
||||
- Example: for an `8 GiB` image with `60000` files and `4000` directories, `-i ~= 8*1024^3 / (60000 + 4000 + 2048) ~= 130312`, so use `-i 131072`.
|
||||
|
||||
Windows:
|
||||
- You can create UFS2 images with **UFS2Tool** https://github.com/SvenGDK/UFS2Tool.
|
||||
- Example:
|
||||
- `UFS2Tool.exe newfs -O 2 -b 32768 -f 4096 -m 0 -i 262144 -D ./APPXXXX ./PPSA12345.ffpkg`
|
||||
- `UFS2Tool.exe newfs -O 2 -b 65536 -f 65536 -m 0 -S 4096 -i 262144 -D ./APPXXXX ./PPSA12345.ffpkg`
|
||||
- For manual builds, use `-i 262144` as the baseline and lower it for images with many small files.
|
||||
|
||||
|
||||
## Installation and usage
|
||||
@@ -186,23 +503,16 @@ Windows:
|
||||
### Method 1: Manual Payload Injection (Port 9021)
|
||||
Use a payload sender (such as NetCat GUI or a web-based loader) to send the files to **Port 9021**.
|
||||
|
||||
1. Send `notify.elf` (Optional).
|
||||
* *Only send this if you want graphical pop-ups. Skip if you prefer standard notifications.*
|
||||
2. Send `shadowmountplus.elf`.
|
||||
3. Wait for the notification: *"ShadowMount+"*.
|
||||
1. Send `shadowmountplus.elf`.
|
||||
2. Wait for the notification: *"ShadowMount+"*.
|
||||
|
||||
### Method 2: PLK Autoloader (Recommended)
|
||||
Add ShadowMountPlus to your `autoload.txt` for **plk-autoloader** to ensure it starts automatically on every boot.
|
||||
|
||||
**Sample Configuration:**
|
||||
```ini
|
||||
!1000
|
||||
notify.elf ; Optional - Remove this line if you do not want Rich Toasts
|
||||
!1000
|
||||
shadowmountplus.elf
|
||||
!3000
|
||||
ps5-backpork.elf
|
||||
!1000
|
||||
kstuff.elf
|
||||
```
|
||||
|
||||
@@ -215,16 +525,18 @@ If a game is not mounted:
|
||||
- Check `/data/shadowmount/debug.log` and system notifications from ShadowMount+.
|
||||
- Verify scan roots:
|
||||
- if `scanpath=...` is set, only these paths are scanned;
|
||||
- `/data/imgmnt/{ufsmnt,exfatmnt,pfsmnt}` are always scanned.
|
||||
- `/mnt/shadowmnt/pfsc` and `/mnt/shadowmnt` are always scanned.
|
||||
- Verify scan depth:
|
||||
- `recursive_scan=0` scans only first-level subfolders;
|
||||
- `recursive_scan=1` scans recursively.
|
||||
- `scan_depth=1` scans only first-level subfolders;
|
||||
- `scan_depth=2` scans one additional nested level;
|
||||
- `recursive_scan=1` is treated as deprecated compatibility mode and forces `scan_depth=2`.
|
||||
- If logs show `source not stable yet`, adjust `stability_wait_seconds` (or wait for source copy/write to finish).
|
||||
- Verify game structure:
|
||||
- folder game: `<GAME_DIR>/sce_sys/param.json`;
|
||||
- image game (`.ffpkg` / `.exfat` / `.ffpfs`): `sce_sys/param.json` must be at image root (no extra top-level folder).
|
||||
- If you see `missing/invalid param.json` for an image, check via FTP that files are present under `/data/imgmnt/<fs>/<image_name>/` and include `sce_sys/param.json`.
|
||||
- If you see image mount failure, check image integrity and filesystem type (`.ffpkg`=UFS, `.exfat`=exFAT, `.ffpfs`=PFS).
|
||||
- image game (`.ffpkg` / `.exfat` / `.ffpfs`): `sce_sys/param.json` must be at image root (no extra top-level folder);
|
||||
- PFSC container (`.ffpfsc`): nested supported image files are scanned; direct game files inside the container are ignored.
|
||||
- If you see `missing/invalid param.json` for an image, check via FTP that files are present under `/mnt/shadowmnt/<image_name>_<hash>/` and include `sce_sys/param.json`.
|
||||
- If you see image mount failure, check image integrity and filesystem type (`.ffpkg`=UFS, `.exfat`=exFAT, `.ffpfs`=PFS, `.ffpfsc`=PFS container).
|
||||
- If you see duplicate titleId notification, keep only one source per `<TITLE_ID>`.
|
||||
|
||||
If a game is mounted but does not start:
|
||||
@@ -240,10 +552,12 @@ If a game is mounted but does not start:
|
||||
* **Drakmor** - Evolution of ShadowMount to ShadowMountPlus
|
||||
|
||||
* **Special Thanks:**
|
||||
* VoidWhisper for ShadowMount [](https://ko-fi.com/voidwhisper)
|
||||
* EchoStretch
|
||||
* VoidWhisper for ShadowMount
|
||||
* BestPig for BackPort
|
||||
* EchoStretch for kstuff-toggle and etc
|
||||
* Gezine
|
||||
* earthonion
|
||||
* LightningMods
|
||||
* john-tornblom
|
||||
* RenanGBarreto for his excellent https://github.com/PSBrew/MkPFS
|
||||
* john-tornblom for SDK
|
||||
* PS5 R&D Community
|
||||
+211
-29
@@ -1,61 +1,243 @@
|
||||
# ShadowMount runtime config example
|
||||
# Place as: /data/shadowmount/config.ini
|
||||
#
|
||||
# All keys are optional.
|
||||
# If a key is missing, built-in defaults are used.
|
||||
# This file is a template.
|
||||
# Nothing below is active until you remove the leading '#'.
|
||||
#
|
||||
# How to use this file:
|
||||
# 1. Uncomment only the parameters you really want to override.
|
||||
# 2. Lines that start with '# key=value' are real parameters you can uncomment directly.
|
||||
# 3. Lines that start with '##' are examples/templates only. Edit them before use.
|
||||
|
||||
# Debug logging:
|
||||
# 1/true/yes/on -> enable log_debug output to console and /data/shadowmount/debug.log
|
||||
# 0/false/no/off -> disable log_debug output
|
||||
debug=1
|
||||
# Default: 1
|
||||
# debug=1
|
||||
|
||||
# Quiet mode:
|
||||
# 1/true/yes/on -> only show the startup version popup and runtime errors
|
||||
# 0/false/no/off -> also show normal informational popups
|
||||
# Default: 0
|
||||
# quiet_mode=0
|
||||
|
||||
# Notification language:
|
||||
# auto -> use the console system language
|
||||
# Or set a supported locale, for example: en-US, ru-RU, de-DE, fr-FR, es-ES
|
||||
# Default: auto
|
||||
# language=auto
|
||||
|
||||
# Public HTTP/JSON API listener:
|
||||
# The loopback default accepts connections only from software running on the PS5.
|
||||
# Use 0.0.0.0 explicitly if remote devices on the network must connect.
|
||||
# Defaults: 127.0.0.1:10101
|
||||
# api_bind_address=127.0.0.1
|
||||
# api_port=10101
|
||||
|
||||
# Global mount mode:
|
||||
# 1/true/yes/on/ro -> read-only mounts
|
||||
# 0/false/no/off/rw -> read-write mounts
|
||||
mount_read_only=1
|
||||
# Default: 1
|
||||
# mount_read_only=1
|
||||
|
||||
# Mounting even damaged file systems
|
||||
# Mounting even damaged file systems:
|
||||
# 1/true/yes/on -> include force option
|
||||
# 0/false/no/off -> default mount options (default)
|
||||
force_mount=0
|
||||
# 0/false/no/off -> default mount options
|
||||
# Default: 0
|
||||
# force_mount=0
|
||||
|
||||
# Per-image mount mode override by file name (can be repeated many times):
|
||||
# image_ro=<image_filename>
|
||||
# image_rw=<image_filename>
|
||||
# image_ro=PPSA12345.ffpfs
|
||||
# image_rw=My Game Dump.exfat
|
||||
# Keep discovered images mounted between game launches:
|
||||
# 1/true/yes/on -> mount during scans and keep images mounted
|
||||
# 0/false/no/off -> mount images on demand for each game (default)
|
||||
# USB-backed images are still unmounted during suspend.
|
||||
# Default: 0
|
||||
# persistent_image_mounts=0
|
||||
|
||||
# Recursive scan mode:
|
||||
# 1/true/yes/on -> scan subdirectories recursively (no depth limit)
|
||||
# 0/false/no/off -> scan only first-level subdirectories (default)
|
||||
recursive_scan=0
|
||||
# Use batch registration for newly staged titles:
|
||||
# 1/true/yes/on -> submit queued installs through sceAppInstUtilAppInstallAll
|
||||
# 0/false/no/off -> register each title directly
|
||||
# Default: 0 on FW below 12.00, forced to 1 on FW 12.00 and newer
|
||||
# app_install_all=0
|
||||
|
||||
# Optional backport overlay root:
|
||||
# If "<backports_path>/<TITLE_ID>" exists, it is mounted over image root via unionfs.
|
||||
backports_path=/data/backports
|
||||
# Automatically remove games from the system library when their source
|
||||
# is no longer available:
|
||||
# 1/true/yes/on -> remove after the configured missing delay
|
||||
# 0/false/no/off -> keep the registration (default)
|
||||
# Default: 0
|
||||
# auto_remove_missing_games=0
|
||||
|
||||
# Allow automatic removal of games that have installed DLC:
|
||||
# 1/true/yes/on -> allow removal
|
||||
# 0/false/no/off -> keep games with installed DLC (default)
|
||||
# Default: 0
|
||||
# auto_remove_games_with_dlc=0
|
||||
|
||||
# How long a source must remain unavailable before removal (seconds),
|
||||
# range: 1..86400
|
||||
# Default: 300
|
||||
# auto_remove_missing_delay_seconds=300
|
||||
|
||||
# Scan depth relative to each scan root:
|
||||
# 1 -> scan only first-level subdirectories
|
||||
# 2 -> also scan one additional nested level
|
||||
# Default: 1
|
||||
# scan_depth=1
|
||||
|
||||
# Legacy compatibility:
|
||||
# recursive_scan=1 forces scan_depth=2
|
||||
# Default: disabled
|
||||
# recursive_scan=0
|
||||
|
||||
# Full scan loop interval (seconds), range: 1..3600
|
||||
scan_interval_seconds=10
|
||||
# Default: 15
|
||||
# scan_interval_seconds=15
|
||||
|
||||
# Source stabilization wait window (seconds), range: 0..3600
|
||||
# If source mtime is newer than this window, it will be deferred to next scan.
|
||||
stability_wait_seconds=10
|
||||
# If source mtime/ctime is newer than this window, it will be deferred to next scan.
|
||||
# Default: 10
|
||||
# stability_wait_seconds=10
|
||||
|
||||
# Backend selection per filesystem:
|
||||
# lvd -> /dev/lvdctl -> /dev/lvdN
|
||||
# md -> /dev/mdctl -> /dev/mdN
|
||||
exfat_backend=lvd
|
||||
ufs_backend=lvd
|
||||
# Defaults: exfat=lvd, ufs=lvd
|
||||
# LVD is required for the exFAT img_type=5/BFS sdimg fast path. The image file
|
||||
# must also reside on internal BFS and be formatted with a 64 KiB cluster.
|
||||
# exfat_backend=lvd
|
||||
# ufs_backend=lvd
|
||||
|
||||
# Backend-specific overrides (optional):
|
||||
# Mount profile per image type:
|
||||
# 1/true/yes/on -> use the version 1.6 attach/nmount profile (default)
|
||||
# 0/false/no/off -> use the optimized version 1.7 profile
|
||||
# Changes apply the next time an image is mounted; active mounts are not reset.
|
||||
# ufs = .ffpkg
|
||||
# exfat = .exfat
|
||||
# pfs = .ffpfs and nested pfs_image.dat
|
||||
# pfsc = outer .ffpfsc container
|
||||
# legacy_mount_ufs=1
|
||||
# legacy_mount_exfat=1
|
||||
# legacy_mount_pfs=1
|
||||
# legacy_mount_pfsc=1
|
||||
|
||||
# PFS "GDDR5" compressed-offset cache for nested images in legacy mode:
|
||||
# 1/true/yes/on -> request the cache before attaching a nested image
|
||||
# 0/false/no/off -> keep the version 1.6 behavior (default)
|
||||
# The optimized profile always requests this cache when supported.
|
||||
# legacy_gddr5_cache=0
|
||||
|
||||
# Sandbox fakelib backport watcher:
|
||||
# 1/true/yes/on -> mount app0/fakelib2 when present, otherwise app0/fakelib, into common/lib
|
||||
# 0/false/no/off -> disable fakelib watcher
|
||||
# Default: 1
|
||||
# backport_fakelib=1
|
||||
|
||||
# Updates for multiple emulators in the game fakelib:
|
||||
# 1/true/yes/on -> cache the game's fakelib before launch and replace matching
|
||||
# cached files from emulators_path
|
||||
# 0/false/no/off -> disable emulator updates
|
||||
# Default: 1
|
||||
# update_emulators=1
|
||||
#
|
||||
# Folder containing emulator update files (absolute path):
|
||||
# emulators_path=/data/shadowmount/emus
|
||||
# Cache: /data/shadowmount/cache/<TITLE_ID>/fakelib
|
||||
# Unused caches expire seven days after the most recent game launch.
|
||||
|
||||
# Automatic libSceAmpr emulator download:
|
||||
# 1/true/yes/on -> check 30 seconds after startup and then every four hours
|
||||
# 0/false/no/off -> do not access the network (default)
|
||||
# A missing local emulator is downloaded directly. Otherwise a HEAD request
|
||||
# compares the server Last-Modified time with the local file.
|
||||
# Current Sectigo and Let's Encrypt trust chains used by GitHub releases are
|
||||
# bundled; HTTPS certificate and hostname verification remains enabled.
|
||||
# Otherwise, the SPRX is downloaded only when the server file is newer, then
|
||||
# replaces emulators_path/libSceAmpr.sprx only when its contents differ.
|
||||
# Downloads are deferred while a game mount is active.
|
||||
# Default: 0
|
||||
# auto_update_ampr=0
|
||||
#
|
||||
# HTTP or HTTPS download URL:
|
||||
# ampr_update_url=https://github.com/drakmor/ampr_emu/releases/latest/download/libSceAmpr.sprx
|
||||
|
||||
# Global fakelib overlay:
|
||||
# 1/true/yes/on -> use global fakelib for every tracked game when present
|
||||
# 0/false/no/off -> do not use the global fakelib folder
|
||||
# Default: 1
|
||||
# global_fakelib=1
|
||||
#
|
||||
# Default global fakelib folder:
|
||||
# global_fakelib_path=/data/shadowmount/fakelib
|
||||
#
|
||||
# Priority when both global and per-game fakelib exist in the combined cache:
|
||||
# game -> game-specific files win
|
||||
# global -> global files win
|
||||
# Default: game
|
||||
# global_fakelib_priority=game
|
||||
#
|
||||
## Exclude specific title IDs from the global fakelib overlay (repeatable):
|
||||
## global_fakelib_exclude=PPSA12345
|
||||
|
||||
# Auto-manage kstuff around game launches:
|
||||
# 1/true/yes/on -> pause kstuff after game launch, resume immediately on stop
|
||||
# 0/false/no/off -> leave kstuff state untouched
|
||||
# Default: 1
|
||||
# kstuff_game_auto_toggle=1
|
||||
|
||||
# Detect crash-candidate state and autotune pause delay:
|
||||
# 1/true/yes/on -> enable crash monitoring and autotune updates
|
||||
# 0/false/no/off -> disable crash detection/autotune logic
|
||||
# Default: 1
|
||||
# kstuff_crash_detection=1
|
||||
|
||||
# Automatic fan-controller target for games:
|
||||
# system -> leave the system-managed target unchanged (default)
|
||||
# 50..91 -> apply this target when a game starts
|
||||
# The stock automatic fan controller remains enabled.
|
||||
# Default: system
|
||||
# fan_target_temperature=system
|
||||
|
||||
# Delay before pausing kstuff for image-backed titles.
|
||||
# Default: 25
|
||||
# kstuff_pause_delay_image_seconds=25
|
||||
|
||||
# Delay before pausing kstuff for direct/non-image launches.
|
||||
# Default: 15
|
||||
# kstuff_pause_delay_direct_seconds=15
|
||||
|
||||
# Backend-specific sector size overrides:
|
||||
# Defaults: lvd_exfat=512, lvd_ufs=4096, lvd_pfs=4096, md_exfat=512
|
||||
# md_ufs defaults to 512 in legacy mode and 4096 in optimized mode.
|
||||
# Keep lvd_exfat at 512 for the BFS fast path. 65536 is the exFAT cluster and
|
||||
# LVD secondary unit in the optimized profile, not the logical sector size.
|
||||
# lvd_exfat_sector_size=512
|
||||
# lvd_ufs_sector_size=4096
|
||||
# lvd_pfs_sector_size=32768
|
||||
# lvd_pfs_sector_size=4096
|
||||
# md_exfat_sector_size=512
|
||||
# md_ufs_sector_size=512
|
||||
# md_ufs_sector_size=4096
|
||||
|
||||
# Optional custom scan roots (can be repeated):
|
||||
# If at least one scanpath=... is present, only these paths are used.
|
||||
# "/data/imgmnt/{ufsmnt,exfatmnt,pfsmnt}" are always added automatically.
|
||||
# "/mnt/shadowmnt/pfsc" and "/mnt/shadowmnt" are always added automatically.
|
||||
# Default: use built-in scan path list
|
||||
# scanpath=/data/homebrew
|
||||
# scanpath=/data/etaHEN/games
|
||||
|
||||
## Per-image mount mode override by file name (repeatable):
|
||||
## image_ro=<image_filename>
|
||||
## image_rw=<image_filename>
|
||||
## image_sector=<image_filename>:<sector_size>
|
||||
##
|
||||
## Examples:
|
||||
## image_ro=PPSA12345.ffpfs
|
||||
## image_rw=My Game Dump.exfat
|
||||
## image_sector=Legacy 512 Sector.exfat:512
|
||||
|
||||
## Keep kstuff enabled for specific title IDs (repeatable):
|
||||
## kstuff_no_pause=PPSA12345
|
||||
|
||||
## Override pause delay for specific title IDs (repeatable):
|
||||
## Format: TITLEID:SECONDS
|
||||
## kstuff_delay=PPSA12345:30
|
||||
|
||||
## More custom scan root examples:
|
||||
## scanpath=/data/homebrew
|
||||
## scanpath=/data/etaHEN/games
|
||||
@@ -0,0 +1,570 @@
|
||||
openapi: 3.1.2
|
||||
info:
|
||||
title: ShadowMount HTTP API
|
||||
version: "1"
|
||||
description: |
|
||||
HTTP/JSON API exposed by the ShadowMountPlus payload. The service has no
|
||||
authentication and should only be exposed on a trusted network.
|
||||
servers:
|
||||
- url: http://127.0.0.1:10101
|
||||
description: Default loopback endpoint on the PS5
|
||||
- url: http://{address}:{port}
|
||||
description: Configured network endpoint
|
||||
variables:
|
||||
address:
|
||||
default: 192.168.1.50
|
||||
description: PS5 IPv4 address or hostname
|
||||
port:
|
||||
default: "10101"
|
||||
description: Value of api_port
|
||||
security: []
|
||||
tags:
|
||||
- name: Service
|
||||
- name: Images
|
||||
- name: Games
|
||||
|
||||
paths:
|
||||
/api/v1/version:
|
||||
options:
|
||||
operationId: preflightVersion
|
||||
summary: CORS preflight
|
||||
tags: [Service]
|
||||
responses:
|
||||
"204":
|
||||
$ref: "#/components/responses/Preflight"
|
||||
post:
|
||||
operationId: getVersion
|
||||
summary: Get API version and capabilities
|
||||
tags: [Service]
|
||||
requestBody:
|
||||
$ref: "#/components/requestBodies/EmptyRequest"
|
||||
responses:
|
||||
"200":
|
||||
$ref: "#/components/responses/VersionSuccess"
|
||||
"400":
|
||||
$ref: "#/components/responses/BadRequest"
|
||||
"411":
|
||||
$ref: "#/components/responses/LengthRequired"
|
||||
"413":
|
||||
$ref: "#/components/responses/PayloadTooLarge"
|
||||
"415":
|
||||
$ref: "#/components/responses/UnsupportedMediaType"
|
||||
"431":
|
||||
$ref: "#/components/responses/HeadersTooLarge"
|
||||
"500":
|
||||
$ref: "#/components/responses/InternalError"
|
||||
"501":
|
||||
$ref: "#/components/responses/NotImplemented"
|
||||
|
||||
/api/v1/images:
|
||||
options:
|
||||
operationId: preflightImages
|
||||
summary: CORS preflight
|
||||
tags: [Images]
|
||||
responses:
|
||||
"204":
|
||||
$ref: "#/components/responses/Preflight"
|
||||
post:
|
||||
operationId: listImages
|
||||
summary: Get the complete image snapshot
|
||||
tags: [Images]
|
||||
requestBody:
|
||||
$ref: "#/components/requestBodies/EmptyRequest"
|
||||
responses:
|
||||
"200":
|
||||
$ref: "#/components/responses/ImagesSuccess"
|
||||
"400":
|
||||
$ref: "#/components/responses/BadRequest"
|
||||
"411":
|
||||
$ref: "#/components/responses/LengthRequired"
|
||||
"413":
|
||||
$ref: "#/components/responses/PayloadTooLarge"
|
||||
"415":
|
||||
$ref: "#/components/responses/UnsupportedMediaType"
|
||||
"431":
|
||||
$ref: "#/components/responses/HeadersTooLarge"
|
||||
"500":
|
||||
$ref: "#/components/responses/InternalError"
|
||||
"501":
|
||||
$ref: "#/components/responses/NotImplemented"
|
||||
|
||||
/api/v1/games:
|
||||
options:
|
||||
operationId: preflightGames
|
||||
summary: CORS preflight
|
||||
tags: [Games]
|
||||
responses:
|
||||
"204":
|
||||
$ref: "#/components/responses/Preflight"
|
||||
post:
|
||||
operationId: listGames
|
||||
summary: Get the complete game snapshot
|
||||
tags: [Games]
|
||||
requestBody:
|
||||
$ref: "#/components/requestBodies/EmptyRequest"
|
||||
responses:
|
||||
"200":
|
||||
$ref: "#/components/responses/GamesSuccess"
|
||||
"400":
|
||||
$ref: "#/components/responses/BadRequest"
|
||||
"411":
|
||||
$ref: "#/components/responses/LengthRequired"
|
||||
"413":
|
||||
$ref: "#/components/responses/PayloadTooLarge"
|
||||
"415":
|
||||
$ref: "#/components/responses/UnsupportedMediaType"
|
||||
"431":
|
||||
$ref: "#/components/responses/HeadersTooLarge"
|
||||
"500":
|
||||
$ref: "#/components/responses/InternalError"
|
||||
"501":
|
||||
$ref: "#/components/responses/NotImplemented"
|
||||
|
||||
/api/v1/games/mount:
|
||||
options:
|
||||
operationId: preflightMountGame
|
||||
summary: CORS preflight
|
||||
tags: [Games]
|
||||
responses:
|
||||
"204":
|
||||
$ref: "#/components/responses/Preflight"
|
||||
post:
|
||||
operationId: mountGame
|
||||
summary: Mount a managed game
|
||||
tags: [Games]
|
||||
requestBody:
|
||||
$ref: "#/components/requestBodies/TitleRequest"
|
||||
responses:
|
||||
"200":
|
||||
$ref: "#/components/responses/MountSuccess"
|
||||
"400":
|
||||
$ref: "#/components/responses/BadRequest"
|
||||
"404":
|
||||
$ref: "#/components/responses/NotFound"
|
||||
"409":
|
||||
$ref: "#/components/responses/Busy"
|
||||
"411":
|
||||
$ref: "#/components/responses/LengthRequired"
|
||||
"413":
|
||||
$ref: "#/components/responses/PayloadTooLarge"
|
||||
"415":
|
||||
$ref: "#/components/responses/UnsupportedMediaType"
|
||||
"431":
|
||||
$ref: "#/components/responses/HeadersTooLarge"
|
||||
"500":
|
||||
$ref: "#/components/responses/InternalError"
|
||||
"501":
|
||||
$ref: "#/components/responses/NotImplemented"
|
||||
|
||||
/api/v1/games/unmount:
|
||||
options:
|
||||
operationId: preflightUnmountGame
|
||||
summary: CORS preflight
|
||||
tags: [Games]
|
||||
responses:
|
||||
"204":
|
||||
$ref: "#/components/responses/Preflight"
|
||||
post:
|
||||
operationId: unmountGame
|
||||
summary: Unmount a managed game
|
||||
tags: [Games]
|
||||
requestBody:
|
||||
$ref: "#/components/requestBodies/TitleRequest"
|
||||
responses:
|
||||
"200":
|
||||
$ref: "#/components/responses/UnmountSuccess"
|
||||
"400":
|
||||
$ref: "#/components/responses/BadRequest"
|
||||
"404":
|
||||
$ref: "#/components/responses/NotFound"
|
||||
"409":
|
||||
$ref: "#/components/responses/Busy"
|
||||
"411":
|
||||
$ref: "#/components/responses/LengthRequired"
|
||||
"413":
|
||||
$ref: "#/components/responses/PayloadTooLarge"
|
||||
"415":
|
||||
$ref: "#/components/responses/UnsupportedMediaType"
|
||||
"431":
|
||||
$ref: "#/components/responses/HeadersTooLarge"
|
||||
"500":
|
||||
$ref: "#/components/responses/InternalError"
|
||||
"501":
|
||||
$ref: "#/components/responses/NotImplemented"
|
||||
|
||||
components:
|
||||
requestBodies:
|
||||
EmptyRequest:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
example: {}
|
||||
TitleRequest:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/TitleRequest"
|
||||
|
||||
headers:
|
||||
AllowOrigin:
|
||||
description: Browser origins allowed to access the API
|
||||
schema:
|
||||
type: string
|
||||
const: "*"
|
||||
AllowMethods:
|
||||
description: Methods allowed by CORS preflight
|
||||
schema:
|
||||
type: string
|
||||
const: POST, OPTIONS
|
||||
AllowHeaders:
|
||||
description: Request headers allowed by CORS preflight
|
||||
schema:
|
||||
type: string
|
||||
const: Content-Type
|
||||
AllowPrivateNetwork:
|
||||
description: Private Network Access permission
|
||||
schema:
|
||||
type: string
|
||||
const: "true"
|
||||
MaxAge:
|
||||
description: Number of seconds for which the preflight may be cached
|
||||
schema:
|
||||
type: integer
|
||||
const: 86400
|
||||
|
||||
responses:
|
||||
Preflight:
|
||||
description: CORS preflight accepted
|
||||
headers:
|
||||
Access-Control-Allow-Origin:
|
||||
$ref: "#/components/headers/AllowOrigin"
|
||||
Access-Control-Allow-Methods:
|
||||
$ref: "#/components/headers/AllowMethods"
|
||||
Access-Control-Allow-Headers:
|
||||
$ref: "#/components/headers/AllowHeaders"
|
||||
Access-Control-Allow-Private-Network:
|
||||
$ref: "#/components/headers/AllowPrivateNetwork"
|
||||
Access-Control-Max-Age:
|
||||
$ref: "#/components/headers/MaxAge"
|
||||
|
||||
VersionSuccess:
|
||||
description: API version and capabilities
|
||||
headers:
|
||||
Access-Control-Allow-Origin:
|
||||
$ref: "#/components/headers/AllowOrigin"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/VersionResponse"
|
||||
|
||||
ImagesSuccess:
|
||||
description: Complete image snapshot
|
||||
headers:
|
||||
Access-Control-Allow-Origin:
|
||||
$ref: "#/components/headers/AllowOrigin"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ImagesResponse"
|
||||
|
||||
GamesSuccess:
|
||||
description: Complete game snapshot
|
||||
headers:
|
||||
Access-Control-Allow-Origin:
|
||||
$ref: "#/components/headers/AllowOrigin"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/GamesResponse"
|
||||
|
||||
MountSuccess:
|
||||
description: Game mounted
|
||||
headers:
|
||||
Access-Control-Allow-Origin:
|
||||
$ref: "#/components/headers/AllowOrigin"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/MountResponse"
|
||||
|
||||
UnmountSuccess:
|
||||
description: Game unmounted
|
||||
headers:
|
||||
Access-Control-Allow-Origin:
|
||||
$ref: "#/components/headers/AllowOrigin"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/UnmountResponse"
|
||||
|
||||
BadRequest:
|
||||
description: Invalid HTTP request, JSON body or title ID
|
||||
headers:
|
||||
Access-Control-Allow-Origin:
|
||||
$ref: "#/components/headers/AllowOrigin"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorResponse"
|
||||
NotFound:
|
||||
description: Managed title tracker was not found
|
||||
headers:
|
||||
Access-Control-Allow-Origin:
|
||||
$ref: "#/components/headers/AllowOrigin"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorResponse"
|
||||
Busy:
|
||||
description: Runtime state does not currently permit the operation
|
||||
headers:
|
||||
Access-Control-Allow-Origin:
|
||||
$ref: "#/components/headers/AllowOrigin"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorResponse"
|
||||
example:
|
||||
status: 16
|
||||
error: Device busy
|
||||
LengthRequired:
|
||||
description: Content-Length is required
|
||||
headers:
|
||||
Access-Control-Allow-Origin:
|
||||
$ref: "#/components/headers/AllowOrigin"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorResponse"
|
||||
PayloadTooLarge:
|
||||
description: JSON request body exceeds 4096 bytes
|
||||
headers:
|
||||
Access-Control-Allow-Origin:
|
||||
$ref: "#/components/headers/AllowOrigin"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorResponse"
|
||||
UnsupportedMediaType:
|
||||
description: Content-Type is not application/json
|
||||
headers:
|
||||
Access-Control-Allow-Origin:
|
||||
$ref: "#/components/headers/AllowOrigin"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorResponse"
|
||||
HeadersTooLarge:
|
||||
description: HTTP headers exceed 8192 bytes
|
||||
headers:
|
||||
Access-Control-Allow-Origin:
|
||||
$ref: "#/components/headers/AllowOrigin"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorResponse"
|
||||
InternalError:
|
||||
description: Internal failure
|
||||
headers:
|
||||
Access-Control-Allow-Origin:
|
||||
$ref: "#/components/headers/AllowOrigin"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorResponse"
|
||||
NotImplemented:
|
||||
description: Unsupported transfer encoding or runtime operation
|
||||
headers:
|
||||
Access-Control-Allow-Origin:
|
||||
$ref: "#/components/headers/AllowOrigin"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorResponse"
|
||||
|
||||
schemas:
|
||||
TitleId:
|
||||
type: string
|
||||
pattern: "^(CUSA|PPSA)[0-9]{5}$"
|
||||
examples: [PPSA12345]
|
||||
|
||||
TitleRequest:
|
||||
type: object
|
||||
required: [title_id]
|
||||
properties:
|
||||
title_id:
|
||||
$ref: "#/components/schemas/TitleId"
|
||||
|
||||
VersionResponse:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required: [status, api_version, shadowmount_version, capabilities]
|
||||
properties:
|
||||
status:
|
||||
type: integer
|
||||
const: 0
|
||||
api_version:
|
||||
type: integer
|
||||
const: 1
|
||||
shadowmount_version:
|
||||
type: string
|
||||
capabilities:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
enum: [list_images, list_games, mount_game, unmount_game]
|
||||
example: [list_images, list_games, mount_game, unmount_game]
|
||||
|
||||
Image:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- path
|
||||
- mount_point
|
||||
- size
|
||||
- mtime_sec
|
||||
- mtime_nsec
|
||||
- unit_id
|
||||
- backend
|
||||
- complete
|
||||
- source_available
|
||||
- mapped
|
||||
- mounted
|
||||
properties:
|
||||
path:
|
||||
type: string
|
||||
mount_point:
|
||||
type: string
|
||||
size:
|
||||
type: integer
|
||||
format: int64
|
||||
minimum: 0
|
||||
mtime_sec:
|
||||
type: integer
|
||||
format: int64
|
||||
mtime_nsec:
|
||||
type: integer
|
||||
minimum: 0
|
||||
maximum: 999999999
|
||||
unit_id:
|
||||
type: integer
|
||||
minimum: -1
|
||||
backend:
|
||||
type: string
|
||||
enum: [LVD, MD, UNKNOWN]
|
||||
complete:
|
||||
type: boolean
|
||||
source_available:
|
||||
type: boolean
|
||||
mapped:
|
||||
type: boolean
|
||||
mounted:
|
||||
type: boolean
|
||||
|
||||
ImagesResponse:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required: [status, count, images]
|
||||
properties:
|
||||
status:
|
||||
type: integer
|
||||
const: 0
|
||||
count:
|
||||
type: integer
|
||||
minimum: 0
|
||||
images:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/Image"
|
||||
|
||||
Game:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- path
|
||||
- image_path
|
||||
- title_id
|
||||
- title_name
|
||||
- installed
|
||||
- managed
|
||||
- mounted
|
||||
- image_backed
|
||||
- source_available
|
||||
properties:
|
||||
path:
|
||||
type: string
|
||||
image_path:
|
||||
type: string
|
||||
title_id:
|
||||
$ref: "#/components/schemas/TitleId"
|
||||
title_name:
|
||||
type: string
|
||||
installed:
|
||||
type: boolean
|
||||
managed:
|
||||
type: boolean
|
||||
mounted:
|
||||
type: boolean
|
||||
image_backed:
|
||||
type: boolean
|
||||
source_available:
|
||||
type: boolean
|
||||
|
||||
GamesResponse:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required: [status, count, games]
|
||||
properties:
|
||||
status:
|
||||
type: integer
|
||||
const: 0
|
||||
count:
|
||||
type: integer
|
||||
minimum: 0
|
||||
games:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/Game"
|
||||
|
||||
MountResponse:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required: [status, title_id, mounted]
|
||||
properties:
|
||||
status:
|
||||
type: integer
|
||||
const: 0
|
||||
title_id:
|
||||
$ref: "#/components/schemas/TitleId"
|
||||
mounted:
|
||||
type: boolean
|
||||
const: true
|
||||
|
||||
UnmountResponse:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required: [status, title_id, mounted]
|
||||
properties:
|
||||
status:
|
||||
type: integer
|
||||
const: 0
|
||||
title_id:
|
||||
$ref: "#/components/schemas/TitleId"
|
||||
mounted:
|
||||
type: boolean
|
||||
const: false
|
||||
|
||||
ErrorResponse:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required: [status, error]
|
||||
properties:
|
||||
status:
|
||||
type: integer
|
||||
minimum: 1
|
||||
description: Positive errno value
|
||||
error:
|
||||
type: string
|
||||
@@ -0,0 +1,97 @@
|
||||
# ShadowMount HTTP/JSON API v1
|
||||
|
||||
The machine-readable OpenAPI 3.1 description is available in
|
||||
[`openapi.yaml`](openapi.yaml).
|
||||
|
||||
ShadowMount exposes its public API over HTTP/TCP. The default endpoint is
|
||||
`127.0.0.1:10101`, so only software running on the PS5 can connect. Set
|
||||
`api_bind_address=0.0.0.0` to allow clients on the network, or select a
|
||||
specific PS5 IPv4 address. `api_port` accepts values from 1 through 65535.
|
||||
Both settings are applied on runtime config reload.
|
||||
|
||||
The API has no authentication. Bind it to a network-facing address only on a
|
||||
trusted network.
|
||||
|
||||
The internal `/system_tmp/shadowmount.sock` Unix socket remains private to the
|
||||
SceShellCore bridge and must not be used by third-party applications.
|
||||
|
||||
The HTTP service owns and monitors its listener. If initial `socket/bind/listen`
|
||||
or a later `accept` fails, it retries with an interruptible 250 ms to 5 s
|
||||
backoff. Only the transition into recovery and the successful recovery are
|
||||
logged. Shutdown and config reload interrupt the wait immediately.
|
||||
|
||||
Every API operation must use `POST`, include `Content-Type: application/json`
|
||||
and contain one JSON object. Every response is a JSON object. Connections are
|
||||
closed after one response; chunked request bodies are not supported. The
|
||||
maximum request body is 4096 bytes.
|
||||
|
||||
All responses include `Access-Control-Allow-Origin: *`. Browser preflight
|
||||
requests using `OPTIONS` are supported and allow `POST`, the `Content-Type`
|
||||
header and Private Network Access. This permits calls from any browser origin;
|
||||
the listener still needs `api_bind_address=0.0.0.0` for access from another
|
||||
device.
|
||||
|
||||
## Routes
|
||||
|
||||
| Route | Request | Purpose |
|
||||
| --- | --- | --- |
|
||||
| `/api/v1/version` | `{}` | API and ShadowMount versions plus capabilities |
|
||||
| `/api/v1/images` | `{}` | Complete image snapshot |
|
||||
| `/api/v1/games` | `{}` | Complete game snapshot |
|
||||
| `/api/v1/games/mount` | `{"title_id":"PPSA12345"}` | Mount a managed game |
|
||||
| `/api/v1/games/unmount` | `{"title_id":"PPSA12345"}` | Unmount a managed game |
|
||||
|
||||
Example:
|
||||
|
||||
```sh
|
||||
curl -sS http://127.0.0.1:10101/api/v1/version \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{}'
|
||||
|
||||
curl -sS http://127.0.0.1:10101/api/v1/games \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{}'
|
||||
```
|
||||
|
||||
## Responses
|
||||
|
||||
Every response contains numeric `status`. It is zero on success, or a positive
|
||||
errno value on failure. Error responses also contain `error` and use an
|
||||
appropriate HTTP status such as 400, 404, 409, 413 or 415.
|
||||
|
||||
A successful version response has this shape:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": 0,
|
||||
"api_version": 1,
|
||||
"shadowmount_version": "1.7",
|
||||
"capabilities": ["list_images", "list_games", "mount_game", "unmount_game"]
|
||||
}
|
||||
```
|
||||
|
||||
List responses contain `count` and the complete `images` or `games` array.
|
||||
Image items expose `path`, `mount_point`, `size`, modification time, `unit_id`,
|
||||
`backend`, `complete`, `source_available`, `mapped` and `mounted`. Game items
|
||||
expose `path`, `image_path`, `title_id`, `title_name`, `installed`, `managed`,
|
||||
`mounted`, `image_backed` and `source_available`.
|
||||
|
||||
Mount mutations remain conservative. They return HTTP 409 with `status` set to
|
||||
`EBUSY` while a game is active, while ShellCore owns another prepared title,
|
||||
during suspend, or while a conflicting mount/release is in progress. Clients
|
||||
must not retry this response in a tight loop. HTTP 404 with `ENOENT` means the
|
||||
title has no ShadowMount `mount.lnk` tracker.
|
||||
With `persistent_image_mounts=1`, unmounting a game releases its per-title
|
||||
runtime layers but keeps the backing image mounted.
|
||||
|
||||
## Test client
|
||||
|
||||
Run the host-side Python client while ShadowMount is active:
|
||||
|
||||
```sh
|
||||
python3 tools/api_test.py 192.168.1.50 10101
|
||||
```
|
||||
|
||||
It validates CORS preflight, the version response and complete image/game
|
||||
snapshots, then selects the first safe managed game for a mount/unmount cycle.
|
||||
The mutation test is skipped if there is no candidate or the runtime is busy.
|
||||
@@ -0,0 +1,88 @@
|
||||
static const char *const g_ar_sa[SM_L10N_COUNT] = {
|
||||
/* English: Go to Debug Settings */
|
||||
[SM_L10N_ACTION_DEBUG_SETTINGS] = "الانتقال إلى إعدادات التصحيح",
|
||||
/* English: ShadowMount+ v%s exFAT/UFS/PFS */
|
||||
[SM_L10N_STARTUP] = "ShadowMount+ v%s exFAT/UFS/PFS",
|
||||
/* English: ShadowMount+: recursive_scan=1 is deprecated, using scan_depth=2. */
|
||||
[SM_L10N_RECURSIVE_SCAN_DEPRECATED] = "شادوماونت+: لم يعد الفحص التكراري مدعوماً، وسيتم استخدام عمق الفحص ٢.",
|
||||
/* English: ShadowMount+: scan depth %u enabled. */
|
||||
[SM_L10N_SCAN_DEPTH_ENABLED] = "شادوماونت+: تم تفعيل عمق الفحص %u.",
|
||||
/* English: Backport overlay failed: %s\n%s\n0x%08X */
|
||||
[SM_L10N_BACKPORT_OVERLAY_FAILED] = "فشل تركيب طبقة التوافق مع الإصدارات الأقدم: %s\n%s\n0x%08X",
|
||||
/* English: Backport applied to game: %s */
|
||||
[SM_L10N_GAME_BACKPORTED] = "طُبّق دعم الإصدار الأقدم على اللعبة: %s",
|
||||
/* English: Backport applied to game: %s\nEmulators updated */
|
||||
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "طُبّق دعم الإصدار الأقدم على اللعبة: %s\nتم تحديث المحاكيات",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "تم رفض تحميل الصورة:\n%s\nحجم الكتلة صغير جداً (%llu < %u).\nتم حفظ قيمة حجم القطاع الجديدة: %u.\nحاول التحميل مرة أخرى.",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "تم رفض تحميل الصورة:\n%s\nحجم الكتلة صغير جداً (%llu < %u).\nاستخدم حجماً أكبر للكتلة في الصورة أو حجماً أصغر للقطاع في الإعدادات.",
|
||||
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
|
||||
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "فشل التسجيل: %s (%s)\nمجلد تثبيت التطبيقات غير متاح",
|
||||
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
|
||||
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "تعذر التسجيل: %s (%s)\nجسر TitleDir غير متاح",
|
||||
/* English: Register failed: %s (%s)\ncode=0x%08X */
|
||||
[SM_L10N_REGISTER_FAILED_CODE] = "فشل التسجيل: %s (%s)\nالرمز=0x%08X",
|
||||
/* English: Installing: %s (%s)... */
|
||||
[SM_L10N_INSTALLING] = "جارٍ التثبيت: %s (%s)...",
|
||||
/* English: KStuff active again */
|
||||
[SM_L10N_KSTUFF_ACTIVE] = "أصبح KStuff نشطًا من جديد",
|
||||
/* English: KStuff paused while the game is running */
|
||||
[SM_L10N_KSTUFF_PAUSED] = "أُوقف KStuff مؤقتًا أثناء تشغيل اللعبة",
|
||||
/* English: Installed: %s */
|
||||
[SM_L10N_INSTALLED] = "تم التثبيت: %s",
|
||||
/* English: Unknown */
|
||||
[SM_L10N_UNKNOWN] = "غير معروف",
|
||||
/* English: Image mount failed: 0x%08X (%s)\n%s */
|
||||
[SM_L10N_IMAGE_MOUNT_FAILED] = "فشل تحميل الصورة: 0x%08X (%s)\n%s",
|
||||
/* English: Filesystem cluster size (%llu) is smaller than device sector size (%u) for %s */
|
||||
[SM_L10N_FS_CLUSTER_SMALL] = "حجم كتلة نظام الملفات (%llu) أصغر من حجم قطاع الجهاز (%u) لـ %s",
|
||||
/* English: Mounted image is unreadable or damaged */
|
||||
[SM_L10N_IMAGE_UNREADABLE_DAMAGED] = "الصورة المحمّلة غير قابلة للقراءة أو تالفة",
|
||||
/* English: Image cache full (%u entries), cannot track mount %s */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_TRACK] = "ذاكرة الصور المؤقتة ممتلئة (%u إدخالًا)، ولا يمكن تتبع التركيب %s",
|
||||
/* English: Image cache full (%u entries), rolling back mount */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_ROLLBACK] = "ذاكرة الصور المؤقتة ممتلئة (%u إدخالًا)، جارٍ التراجع عن التركيب",
|
||||
/* English: Install failed: %s (%s)\nTimed out after 5 minutes */
|
||||
[SM_L10N_INSTALL_FAILED_TIMEOUT] = "فشل التثبيت: %s (%s)\nانتهت المهلة بعد 5 دقائق",
|
||||
/* English: Batch install queued (%d): */
|
||||
[SM_L10N_BATCH_INSTALL_QUEUED] = "تمت إضافة التثبيت الجماعي إلى قائمة الانتظار (%d):",
|
||||
/* English: \n... and %d more */
|
||||
[SM_L10N_BATCH_INSTALL_AND_MORE] = "\n... و %d أخرى",
|
||||
/* English: Batch install failed for %d app(s).\ncode=0x%08X */
|
||||
[SM_L10N_BATCH_INSTALL_FAILED] = "فشل التثبيت الجماعي لـ %d تطبيق.\nالرمز=0x%08X",
|
||||
/* English: %s crashed before KStuff was paused. KStuff is not the cause. */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF] = "تعطّل التطبيق %s قبل إيقاف KStuff مؤقتًا. لم يكن KStuff هو السبب.",
|
||||
/* English: %s crashed before KStuff pause:\n%s */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF_FATAL] = "تعطل %s قبل إيقاف KStuff مؤقتًا:\n%s",
|
||||
/* English: %s crashed after KStuff was paused. Automatic tuning skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_SKIPPED] = "تعطّل التطبيق %s بعد إيقاف KStuff مؤقتًا. تم تخطي الضبط التلقائي.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nAutotune skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_FATAL_SKIPPED] = "تعطل %s بعد إيقاف KStuff مؤقتًا:\n%s\nتم تخطي الضبط التلقائي.",
|
||||
/* English: Can't load module after KStuff pause */
|
||||
[SM_L10N_RTLD_AFTER_KSTUFF_FALLBACK] = "تعذر تحميل الوحدة بعد إيقاف كايستاف مؤقتاً",
|
||||
/* English: can't load module %.*s after KStuff pause */
|
||||
[SM_L10N_RTLD_MODULE_AFTER_KSTUFF] = "تعذر تحميل الوحدة %.*s بعد إيقاف KStuff مؤقتًا",
|
||||
/* English: %s: %s. Delay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_DELAY_INCREASED_RELAUNCH] = "%s: %s. تمت زيادة التأخير إلى %u ثانية.\nشغّل اللعبة مرة أخرى.",
|
||||
/* English: Crash detected after KStuff pause: pause delay for %s increased to %us. Launch the game again. */
|
||||
[SM_L10N_CRASH_DELAY_INCREASED] = "تم اكتشاف تعطل بعد إيقاف كايستاف مؤقتاً: تمت زيادة مدة الإيقاف الخاصة بـ %s إلى %u ثانية. شغّل اللعبة مرة أخرى.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nDelay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_INCREASED] = "تعطل %s بعد إيقاف KStuff مؤقتًا:\n%s\nتمت زيادة التأخير إلى %u ث.\nشغّل اللعبة مجددًا.",
|
||||
/* English: Crash detected after KStuff pause: %s. Failed to update autotune delay. */
|
||||
[SM_L10N_CRASH_DELAY_UPDATE_FAILED] = "تم اكتشاف تعطل بعد إيقاف كايستاف مؤقتاً: %s. تعذر تحديث مدة التحسين التلقائي.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nFailed to update autotune delay. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_UPDATE_FAILED] = "تعطل %s بعد إيقاف KStuff مؤقتًا:\n%s\nتعذر تحديث تأخير الضبط التلقائي.",
|
||||
/* English: Missing/invalid param.json:\n%s */
|
||||
[SM_L10N_MISSING_PARAM] = "ملف param.json مفقود أو غير صالح:\n%s",
|
||||
/* English: Found %d new games. Processing... */
|
||||
[SM_L10N_FOUND_NEW_GAMES] = "عُثر على %d ألعاب جديدة. جارٍ معالجتها...",
|
||||
/* English: Library synchronized.\nFound %d games. */
|
||||
[SM_L10N_LIBRARY_SYNCED] = "تمت مزامنة المكتبة.\nتم العثور على %d ألعاب.",
|
||||
/* English: ShadowMount+: config reloaded. */
|
||||
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: أُعيد تحميل الإعدادات.",
|
||||
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
|
||||
[SM_L10N_DUPLICATE_IGNORED] = "تم تجاهل %s المكرر:\n%s\nالموجود مسبقًا: %s",
|
||||
/* English: AMPR emulator updated */
|
||||
[SM_L10N_AMPR_EMULATOR_UPDATED] = "تم تحديث محاكي AMPR",
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
static const char *const g_cs_cz[SM_L10N_COUNT] = {
|
||||
/* English: Go to Debug Settings */
|
||||
[SM_L10N_ACTION_DEBUG_SETTINGS] = "Přejít do nastavení ladění",
|
||||
/* English: ShadowMount+ v%s exFAT/UFS/PFS */
|
||||
[SM_L10N_STARTUP] = "ShadowMount+ v%s exFAT/UFS/PFS",
|
||||
/* English: ShadowMount+: recursive_scan=1 is deprecated, using scan_depth=2. */
|
||||
[SM_L10N_RECURSIVE_SCAN_DEPRECATED] = "ShadowMount+: recursive_scan=1 je zastaralé, používá se scan_depth=2.",
|
||||
/* English: ShadowMount+: scan depth %u enabled. */
|
||||
[SM_L10N_SCAN_DEPTH_ENABLED] = "ShadowMount+: povolena hloubka prohledávání %u.",
|
||||
/* English: Backport overlay failed: %s\n%s\n0x%08X */
|
||||
[SM_L10N_BACKPORT_OVERLAY_FAILED] = "Backport overlay selhal: %s\n%s\n0x%08X",
|
||||
/* English: Backport applied to game: %s */
|
||||
[SM_L10N_GAME_BACKPORTED] = "Backport hry použit: %s",
|
||||
/* English: Backport applied to game: %s\nEmulators updated */
|
||||
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Backport hry použit: %s\nEmulátory aktualizovány",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Připojení obrazu odmítnuto:\n%s\nVelikost clusteru je příliš malá (%llu < %u).\nUloženo přepsání image_sector: %u.\nZkuste připojit znovu.",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "Připojení obrazu odmítnuto:\n%s\nVelikost clusteru je příliš malá (%llu < %u).\nPoužijte v obrazu větší cluster nebo v konfiguraci menší velikost sektoru.",
|
||||
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
|
||||
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "Registrace selhala: %s (%s)\nAppInstallTitleDir není dostupné",
|
||||
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
|
||||
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "Registrace selhala: %s (%s)\nMost TitleDir není dostupný",
|
||||
/* English: Register failed: %s (%s)\ncode=0x%08X */
|
||||
[SM_L10N_REGISTER_FAILED_CODE] = "Registrace selhala: %s (%s)\nkód=0x%08X",
|
||||
/* English: Installing: %s (%s)... */
|
||||
[SM_L10N_INSTALLING] = "Instalace: %s (%s)...",
|
||||
/* English: KStuff active again */
|
||||
[SM_L10N_KSTUFF_ACTIVE] = "KStuff je znovu aktivní",
|
||||
/* English: KStuff paused while the game is running */
|
||||
[SM_L10N_KSTUFF_PAUSED] = "KStuff pozastaven, zatímco hra běží",
|
||||
/* English: Installed: %s */
|
||||
[SM_L10N_INSTALLED] = "Nainstalováno: %s",
|
||||
/* English: Unknown */
|
||||
[SM_L10N_UNKNOWN] = "Neznámé",
|
||||
/* English: Image mount failed: 0x%08X (%s)\n%s */
|
||||
[SM_L10N_IMAGE_MOUNT_FAILED] = "Připojení obrazu selhalo: 0x%08X (%s)\n%s",
|
||||
/* English: Filesystem cluster size (%llu) is smaller than device sector size (%u) for %s */
|
||||
[SM_L10N_FS_CLUSTER_SMALL] = "Velikost clusteru souborového systému (%llu) je menší než velikost sektoru zařízení (%u) pro %s",
|
||||
/* English: Mounted image is unreadable or damaged */
|
||||
[SM_L10N_IMAGE_UNREADABLE_DAMAGED] = "Připojený obraz je nečitelný nebo poškozený",
|
||||
/* English: Image cache full (%u entries), cannot track mount %s */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_TRACK] = "Mezipaměť obrazů je plná (%u položek), připojení %s nelze sledovat",
|
||||
/* English: Image cache full (%u entries), rolling back mount */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_ROLLBACK] = "Mezipaměť obrazů je plná (%u položek), připojení se vrací zpět",
|
||||
/* English: Install failed: %s (%s)\nTimed out after 5 minutes */
|
||||
[SM_L10N_INSTALL_FAILED_TIMEOUT] = "Instalace selhala: %s (%s)\nČasový limit po 5 minutách",
|
||||
/* English: Batch install queued (%d): */
|
||||
[SM_L10N_BATCH_INSTALL_QUEUED] = "Dávková instalace ve frontě (%d):",
|
||||
/* English: \n... and %d more */
|
||||
[SM_L10N_BATCH_INSTALL_AND_MORE] = "\n... a dalších %d",
|
||||
/* English: Batch install failed for %d app(s).\ncode=0x%08X */
|
||||
[SM_L10N_BATCH_INSTALL_FAILED] = "Dávková instalace selhala pro %d aplikací.\nkód=0x%08X",
|
||||
/* English: %s crashed before KStuff was paused. KStuff is not the cause. */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF] = "Aplikace %s selhala před pozastavením KStuff. Příčinou není KStuff.",
|
||||
/* English: %s crashed before KStuff pause:\n%s */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF_FATAL] = "Aplikace %s selhala před pozastavením KStuff:\n%s",
|
||||
/* English: %s crashed after KStuff was paused. Automatic tuning skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_SKIPPED] = "Aplikace %s selhala po pozastavení KStuff. Automatické ladění bylo přeskočeno.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nAutotune skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_FATAL_SKIPPED] = "Aplikace %s selhala po pozastavení KStuff:\n%s\nAutomatické ladění bylo přeskočeno.",
|
||||
/* English: Can't load module after KStuff pause */
|
||||
[SM_L10N_RTLD_AFTER_KSTUFF_FALLBACK] = "Nelze načíst modul po pauze KStuff",
|
||||
/* English: can't load module %.*s after KStuff pause */
|
||||
[SM_L10N_RTLD_MODULE_AFTER_KSTUFF] = "po pozastavení KStuff nelze načíst modul %.*s",
|
||||
/* English: %s: %s. Delay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_DELAY_INCREASED_RELAUNCH] = "%s: %s. Zpoždění zvýšeno na %u s.\nSpusťte hru znovu.",
|
||||
/* English: Crash detected after KStuff pause: pause delay for %s increased to %us. Launch the game again. */
|
||||
[SM_L10N_CRASH_DELAY_INCREASED] = "Po pozastavení KStuff byla zjištěna chyba: prodleva pro %s byla zvýšena na %u s. Spusťte hru znovu.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nDelay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_INCREASED] = "Aplikace %s selhala po pozastavení KStuff:\n%s\nProdleva byla zvýšena na %u s.\nSpusťte hru znovu.",
|
||||
/* English: Crash detected after KStuff pause: %s. Failed to update autotune delay. */
|
||||
[SM_L10N_CRASH_DELAY_UPDATE_FAILED] = "Po pozastavení KStuff byla zjištěna chyba: %s. Prodlevu automatického ladění se nepodařilo aktualizovat.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nFailed to update autotune delay. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_UPDATE_FAILED] = "Aplikace %s selhala po pozastavení KStuff:\n%s\nProdlevu automatického ladění se nepodařilo aktualizovat.",
|
||||
/* English: Missing/invalid param.json:\n%s */
|
||||
[SM_L10N_MISSING_PARAM] = "Chybí nebo je neplatný param.json:\n%s",
|
||||
/* English: Found %d new games. Processing... */
|
||||
[SM_L10N_FOUND_NEW_GAMES] = "Nalezeno %d nových her. Zpracovávám...",
|
||||
/* English: Library synchronized.\nFound %d games. */
|
||||
[SM_L10N_LIBRARY_SYNCED] = "Knihovna synchronizována.\nNalezeno %d her.",
|
||||
/* English: ShadowMount+: config reloaded. */
|
||||
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: konfigurace byla znovu načtena.",
|
||||
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
|
||||
[SM_L10N_DUPLICATE_IGNORED] = "Duplicitní %s ignorováno:\n%s\nexistující: %s",
|
||||
/* English: AMPR emulator updated */
|
||||
[SM_L10N_AMPR_EMULATOR_UPDATED] = "Emulátor AMPR byl aktualizován",
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
static const char *const g_da_dk[SM_L10N_COUNT] = {
|
||||
/* English: Go to Debug Settings */
|
||||
[SM_L10N_ACTION_DEBUG_SETTINGS] = "Gå til fejlfindingsindstillinger",
|
||||
/* English: ShadowMount+ v%s exFAT/UFS/PFS */
|
||||
[SM_L10N_STARTUP] = "ShadowMount+ v%s exFAT/UFS/PFS",
|
||||
/* English: ShadowMount+: recursive_scan=1 is deprecated, using scan_depth=2. */
|
||||
[SM_L10N_RECURSIVE_SCAN_DEPRECATED] = "ShadowMount+: recursive_scan=1 er forældet, bruger scan_depth=2.",
|
||||
/* English: ShadowMount+: scan depth %u enabled. */
|
||||
[SM_L10N_SCAN_DEPTH_ENABLED] = "ShadowMount+: scanningsdybde %u aktiveret.",
|
||||
/* English: Backport overlay failed: %s\n%s\n0x%08X */
|
||||
[SM_L10N_BACKPORT_OVERLAY_FAILED] = "Backport-overlay mislykkedes: %s\n%s\n0x%08X",
|
||||
/* English: Backport applied to game: %s */
|
||||
[SM_L10N_GAME_BACKPORTED] = "Spil backportet: %s",
|
||||
/* English: Backport applied to game: %s\nEmulators updated */
|
||||
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Spil backportet: %s\nEmulatorer opdateret",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Montering af image afvist:\n%s\nKlyngestørrelsen er for lille (%llu < %u).\nimage_sector-tilsidesættelse gemt: %u.\nPrøv at montere igen.",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "Montering af image afvist:\n%s\nKlyngestørrelsen er for lille (%llu < %u).\nBrug en større klyngestørrelse i imaget eller en mindre sektorstørrelse i konfigurationen.",
|
||||
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
|
||||
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "Registrering mislykkedes: %s (%s)\nAppInstallTitleDir ikke tilgængelig",
|
||||
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
|
||||
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "Registrering mislykkedes: %s (%s)\nTitleDir-broen er ikke tilgængelig",
|
||||
/* English: Register failed: %s (%s)\ncode=0x%08X */
|
||||
[SM_L10N_REGISTER_FAILED_CODE] = "Registrering mislykkedes: %s (%s)\nkode=0x%08X",
|
||||
/* English: Installing: %s (%s)... */
|
||||
[SM_L10N_INSTALLING] = "Installerer: %s (%s)...",
|
||||
/* English: KStuff active again */
|
||||
[SM_L10N_KSTUFF_ACTIVE] = "KStuff er aktiv igen",
|
||||
/* English: KStuff paused while the game is running */
|
||||
[SM_L10N_KSTUFF_PAUSED] = "KStuff pauset mens spillet kører",
|
||||
/* English: Installed: %s */
|
||||
[SM_L10N_INSTALLED] = "Installeret: %s",
|
||||
/* English: Unknown */
|
||||
[SM_L10N_UNKNOWN] = "Ukendt",
|
||||
/* English: Image mount failed: 0x%08X (%s)\n%s */
|
||||
[SM_L10N_IMAGE_MOUNT_FAILED] = "Montering af image mislykkedes: 0x%08X (%s)\n%s",
|
||||
/* English: Filesystem cluster size (%llu) is smaller than device sector size (%u) for %s */
|
||||
[SM_L10N_FS_CLUSTER_SMALL] = "Filsystemets klyngestørrelse (%llu) er mindre end enhedens sektorstørrelse (%u) for %s",
|
||||
/* English: Mounted image is unreadable or damaged */
|
||||
[SM_L10N_IMAGE_UNREADABLE_DAMAGED] = "Mountet image er ulæseligt eller beskadiget",
|
||||
/* English: Image cache full (%u entries), cannot track mount %s */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_TRACK] = "Imagecachen er fuld (%u poster), monteringen %s kan ikke spores",
|
||||
/* English: Image cache full (%u entries), rolling back mount */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_ROLLBACK] = "Imagecachen er fuld (%u poster), monteringen rulles tilbage",
|
||||
/* English: Install failed: %s (%s)\nTimed out after 5 minutes */
|
||||
[SM_L10N_INSTALL_FAILED_TIMEOUT] = "Installation mislykkedes: %s (%s)\nTimeout efter 5 minutter",
|
||||
/* English: Batch install queued (%d): */
|
||||
[SM_L10N_BATCH_INSTALL_QUEUED] = "Batchinstallation i kø (%d):",
|
||||
/* English: \n... and %d more */
|
||||
[SM_L10N_BATCH_INSTALL_AND_MORE] = "\n... og %d mere",
|
||||
/* English: Batch install failed for %d app(s).\ncode=0x%08X */
|
||||
[SM_L10N_BATCH_INSTALL_FAILED] = "Batchinstallationen mislykkedes for %d apps.\nkode=0x%08X",
|
||||
/* English: %s crashed before KStuff was paused. KStuff is not the cause. */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF] = "Appen %s gik ned, før KStuff blev sat på pause. KStuff er ikke årsagen.",
|
||||
/* English: %s crashed before KStuff pause:\n%s */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF_FATAL] = "Appen %s gik ned, før KStuff blev sat på pause:\n%s",
|
||||
/* English: %s crashed after KStuff was paused. Automatic tuning skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_SKIPPED] = "Appen %s gik ned, efter KStuff blev sat på pause. Automatisk justering blev sprunget over.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nAutotune skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_FATAL_SKIPPED] = "Appen %s gik ned, efter KStuff blev sat på pause:\n%s\nAutomatisk justering blev sprunget over.",
|
||||
/* English: Can't load module after KStuff pause */
|
||||
[SM_L10N_RTLD_AFTER_KSTUFF_FALLBACK] = "Kan ikke indlæse modul efter KStuff-pause",
|
||||
/* English: can't load module %.*s after KStuff pause */
|
||||
[SM_L10N_RTLD_MODULE_AFTER_KSTUFF] = "modulet %.*s kan ikke indlæses efter KStuff-pausen",
|
||||
/* English: %s: %s. Delay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_DELAY_INCREASED_RELAUNCH] = "%s: %s. Forsinkelse øget til %u s.\nStart spillet igen.",
|
||||
/* English: Crash detected after KStuff pause: pause delay for %s increased to %us. Launch the game again. */
|
||||
[SM_L10N_CRASH_DELAY_INCREASED] = "Nedbrud registreret efter KStuff-pausen: pauseforsinkelsen for %s blev øget til %u s. Start spillet igen.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nDelay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_INCREASED] = "%s gik ned efter KStuff blev sat på pause:\n%s\nForsinkelsen blev øget til %u s.\nStart spillet igen.",
|
||||
/* English: Crash detected after KStuff pause: %s. Failed to update autotune delay. */
|
||||
[SM_L10N_CRASH_DELAY_UPDATE_FAILED] = "Nedbrud registreret efter KStuff-pausen: %s. Forsinkelsen for automatisk justering kunne ikke opdateres.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nFailed to update autotune delay. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_UPDATE_FAILED] = "%s gik ned efter KStuff blev sat på pause:\n%s\nForsinkelsen for automatisk justering kunne ikke opdateres.",
|
||||
/* English: Missing/invalid param.json:\n%s */
|
||||
[SM_L10N_MISSING_PARAM] = "param.json mangler eller er ugyldig:\n%s",
|
||||
/* English: Found %d new games. Processing... */
|
||||
[SM_L10N_FOUND_NEW_GAMES] = "Fandt %d nye spil. Behandler...",
|
||||
/* English: Library synchronized.\nFound %d games. */
|
||||
[SM_L10N_LIBRARY_SYNCED] = "Bibliotek synkroniseret.\nFandt %d spil.",
|
||||
/* English: ShadowMount+: config reloaded. */
|
||||
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: konfigurationen blev genindlæst.",
|
||||
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
|
||||
[SM_L10N_DUPLICATE_IGNORED] = "Dublet %s ignoreret:\n%s\neksisterende: %s",
|
||||
/* English: AMPR emulator updated */
|
||||
[SM_L10N_AMPR_EMULATOR_UPDATED] = "AMPR-emulator opdateret",
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
static const char *const g_de_de[SM_L10N_COUNT] = {
|
||||
/* English: Go to Debug Settings */
|
||||
[SM_L10N_ACTION_DEBUG_SETTINGS] = "Zu den Debug-Einstellungen",
|
||||
/* English: ShadowMount+ v%s exFAT/UFS/PFS */
|
||||
[SM_L10N_STARTUP] = "ShadowMount+ v%s exFAT/UFS/PFS",
|
||||
/* English: ShadowMount+: recursive_scan=1 is deprecated, using scan_depth=2. */
|
||||
[SM_L10N_RECURSIVE_SCAN_DEPRECATED] = "ShadowMount+: recursive_scan=1 ist veraltet, scan_depth=2 wird verwendet.",
|
||||
/* English: ShadowMount+: scan depth %u enabled. */
|
||||
[SM_L10N_SCAN_DEPTH_ENABLED] = "ShadowMount+: Scan-Tiefe %u ist aktiviert.",
|
||||
/* English: Backport overlay failed: %s\n%s\n0x%08X */
|
||||
[SM_L10N_BACKPORT_OVERLAY_FAILED] = "Backport-Overlay fehlgeschlagen: %s\n%s\n0x%08X",
|
||||
/* English: Backport applied to game: %s */
|
||||
[SM_L10N_GAME_BACKPORTED] = "Spiel backportiert: %s",
|
||||
/* English: Backport applied to game: %s\nEmulators updated */
|
||||
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Spiel backportiert: %s\nEmulatoren aktualisiert",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Einbindung des Images abgelehnt:\n%s\nDie Clustergröße ist zu klein (%llu < %u).\nimage_sector-Überschreibung gespeichert: %u.\nVersuche es erneut.",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "Einbindung des Images abgelehnt:\n%s\nDie Clustergröße ist zu klein (%llu < %u).\nVerwende im Image eine größere Clustergröße oder in der Konfiguration eine kleinere Sektorgröße.",
|
||||
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
|
||||
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "Registrierung fehlgeschlagen: %s (%s)\nAppInstallTitleDir nicht verfügbar",
|
||||
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
|
||||
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "Registrierung fehlgeschlagen: %s (%s)\nTitleDir-Bridge nicht verfügbar",
|
||||
/* English: Register failed: %s (%s)\ncode=0x%08X */
|
||||
[SM_L10N_REGISTER_FAILED_CODE] = "Registrierung fehlgeschlagen: %s (%s)\nCode=0x%08X",
|
||||
/* English: Installing: %s (%s)... */
|
||||
[SM_L10N_INSTALLING] = "Installiere: %s (%s)...",
|
||||
/* English: KStuff active again */
|
||||
[SM_L10N_KSTUFF_ACTIVE] = "KStuff ist wieder aktiv",
|
||||
/* English: KStuff paused while the game is running */
|
||||
[SM_L10N_KSTUFF_PAUSED] = "KStuff pausiert, während das Spiel läuft",
|
||||
/* English: Installed: %s */
|
||||
[SM_L10N_INSTALLED] = "Installiert: %s",
|
||||
/* English: Unknown */
|
||||
[SM_L10N_UNKNOWN] = "Unbekannt",
|
||||
/* English: Image mount failed: 0x%08X (%s)\n%s */
|
||||
[SM_L10N_IMAGE_MOUNT_FAILED] = "Einbindung des Images fehlgeschlagen: 0x%08X (%s)\n%s",
|
||||
/* English: Filesystem cluster size (%llu) is smaller than device sector size (%u) for %s */
|
||||
[SM_L10N_FS_CLUSTER_SMALL] = "Dateisystem-Clustergröße (%llu) ist kleiner als Gerätesektorgröße (%u) für %s",
|
||||
/* English: Mounted image is unreadable or damaged */
|
||||
[SM_L10N_IMAGE_UNREADABLE_DAMAGED] = "Gemountetes Image ist nicht lesbar oder beschädigt",
|
||||
/* English: Image cache full (%u entries), cannot track mount %s */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_TRACK] = "Image-Cache voll (%u Einträge), Einbindung %s kann nicht verfolgt werden",
|
||||
/* English: Image cache full (%u entries), rolling back mount */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_ROLLBACK] = "Image-Cache voll (%u Einträge), Einbindung wird rückgängig gemacht",
|
||||
/* English: Install failed: %s (%s)\nTimed out after 5 minutes */
|
||||
[SM_L10N_INSTALL_FAILED_TIMEOUT] = "Installation fehlgeschlagen: %s (%s)\nZeitüberschreitung nach 5 Minuten",
|
||||
/* English: Batch install queued (%d): */
|
||||
[SM_L10N_BATCH_INSTALL_QUEUED] = "Batch-Installation eingereiht (%d):",
|
||||
/* English: \n... and %d more */
|
||||
[SM_L10N_BATCH_INSTALL_AND_MORE] = "\n... und %d weitere",
|
||||
/* English: Batch install failed for %d app(s).\ncode=0x%08X */
|
||||
[SM_L10N_BATCH_INSTALL_FAILED] = "Batch-Installation für %d Apps fehlgeschlagen.\nCode=0x%08X",
|
||||
/* English: %s crashed before KStuff was paused. KStuff is not the cause. */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF] = "%s ist abgestürzt, bevor KStuff pausiert wurde. KStuff ist nicht die Ursache.",
|
||||
/* English: %s crashed before KStuff pause:\n%s */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF_FATAL] = "%s ist vor der KStuff-Pause abgestürzt:\n%s",
|
||||
/* English: %s crashed after KStuff was paused. Automatic tuning skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_SKIPPED] = "%s ist abgestürzt, nachdem KStuff pausiert wurde. Die automatische Abstimmung wurde übersprungen.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nAutotune skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_FATAL_SKIPPED] = "%s ist nach der KStuff-Pause abgestürzt:\n%s\nAutomatische Abstimmung übersprungen.",
|
||||
/* English: Can't load module after KStuff pause */
|
||||
[SM_L10N_RTLD_AFTER_KSTUFF_FALLBACK] = "Modul kann nach KStuff-Pause nicht geladen werden",
|
||||
/* English: can't load module %.*s after KStuff pause */
|
||||
[SM_L10N_RTLD_MODULE_AFTER_KSTUFF] = "Modul %.*s kann nach der KStuff-Pause nicht geladen werden",
|
||||
/* English: %s: %s. Delay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_DELAY_INCREASED_RELAUNCH] = "%s: %s. Verzögerung auf %u s erhöht.\nStarte das Spiel erneut.",
|
||||
/* English: Crash detected after KStuff pause: pause delay for %s increased to %us. Launch the game again. */
|
||||
[SM_L10N_CRASH_DELAY_INCREASED] = "Absturz nach der KStuff-Pause erkannt: Die Pausenverzögerung für %s wurde auf %u s erhöht. Starte das Spiel erneut.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nDelay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_INCREASED] = "%s ist nach der KStuff-Pause abgestürzt:\n%s\nVerzögerung auf %u s erhöht.\nStarte das Spiel erneut.",
|
||||
/* English: Crash detected after KStuff pause: %s. Failed to update autotune delay. */
|
||||
[SM_L10N_CRASH_DELAY_UPDATE_FAILED] = "Absturz nach der KStuff-Pause erkannt: %s. Die Verzögerung der automatischen Abstimmung konnte nicht aktualisiert werden.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nFailed to update autotune delay. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_UPDATE_FAILED] = "%s ist nach der KStuff-Pause abgestürzt:\n%s\nVerzögerung der automatischen Abstimmung konnte nicht aktualisiert werden.",
|
||||
/* English: Missing/invalid param.json:\n%s */
|
||||
[SM_L10N_MISSING_PARAM] = "param.json fehlt oder ist ungültig:\n%s",
|
||||
/* English: Found %d new games. Processing... */
|
||||
[SM_L10N_FOUND_NEW_GAMES] = "%d neue Spiele gefunden. Verarbeitung läuft...",
|
||||
/* English: Library synchronized.\nFound %d games. */
|
||||
[SM_L10N_LIBRARY_SYNCED] = "Bibliothek synchronisiert.\n%d Spiele gefunden.",
|
||||
/* English: ShadowMount+: config reloaded. */
|
||||
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: Konfiguration neu geladen.",
|
||||
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
|
||||
[SM_L10N_DUPLICATE_IGNORED] = "Duplikat %s ignoriert:\n%s\nvorhanden: %s",
|
||||
/* English: AMPR emulator updated */
|
||||
[SM_L10N_AMPR_EMULATOR_UPDATED] = "AMPR-Emulator aktualisiert",
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
static const char *const g_el_gr[SM_L10N_COUNT] = {
|
||||
/* English: Go to Debug Settings */
|
||||
[SM_L10N_ACTION_DEBUG_SETTINGS] = "Μετάβαση στις ρυθμίσεις αποσφαλμάτωσης",
|
||||
/* English: ShadowMount+ v%s exFAT/UFS/PFS */
|
||||
[SM_L10N_STARTUP] = "ShadowMount+ v%s exFAT/UFS/PFS",
|
||||
/* English: ShadowMount+: recursive_scan=1 is deprecated, using scan_depth=2. */
|
||||
[SM_L10N_RECURSIVE_SCAN_DEPRECATED] = "ShadowMount+: το recursive_scan=1 είναι παρωχημένο, χρησιμοποιείται scan_depth=2.",
|
||||
/* English: ShadowMount+: scan depth %u enabled. */
|
||||
[SM_L10N_SCAN_DEPTH_ENABLED] = "ShadowMount+: ενεργό βάθος σάρωσης %u.",
|
||||
/* English: Backport overlay failed: %s\n%s\n0x%08X */
|
||||
[SM_L10N_BACKPORT_OVERLAY_FAILED] = "Αποτυχία backport overlay: %s\n%s\n0x%08X",
|
||||
/* English: Backport applied to game: %s */
|
||||
[SM_L10N_GAME_BACKPORTED] = "Εφαρμόστηκε backport στο παιχνίδι: %s",
|
||||
/* English: Backport applied to game: %s\nEmulators updated */
|
||||
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Εφαρμόστηκε backport στο παιχνίδι: %s\nΟι εξομοιωτές ενημερώθηκαν",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Η προσάρτηση του ειδώλου απορρίφθηκε:\n%s\nΤο μέγεθος συστοιχίας είναι πολύ μικρό (%llu < %u).\nΑποθηκεύτηκε η τιμή image_sector: %u.\nΔοκιμάστε ξανά την προσάρτηση.",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "Η προσάρτηση του ειδώλου απορρίφθηκε:\n%s\nΤο μέγεθος συστοιχίας είναι πολύ μικρό (%llu < %u).\nΧρησιμοποιήστε μεγαλύτερη συστοιχία στο είδωλο ή μικρότερο μέγεθος τομέα στις ρυθμίσεις.",
|
||||
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
|
||||
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "Αποτυχία εγγραφής: %s (%s)\nΤο AppInstallTitleDir δεν είναι διαθέσιμο",
|
||||
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
|
||||
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "Η εγγραφή απέτυχε: %s (%s)\nΗ γέφυρα TitleDir δεν είναι διαθέσιμη",
|
||||
/* English: Register failed: %s (%s)\ncode=0x%08X */
|
||||
[SM_L10N_REGISTER_FAILED_CODE] = "Αποτυχία εγγραφής: %s (%s)\nκωδικός=0x%08X",
|
||||
/* English: Installing: %s (%s)... */
|
||||
[SM_L10N_INSTALLING] = "Εγκατάσταση: %s (%s)...",
|
||||
/* English: KStuff active again */
|
||||
[SM_L10N_KSTUFF_ACTIVE] = "Το KStuff είναι ξανά ενεργό",
|
||||
/* English: KStuff paused while the game is running */
|
||||
[SM_L10N_KSTUFF_PAUSED] = "Το KStuff παύθηκε όσο τρέχει το παιχνίδι",
|
||||
/* English: Installed: %s */
|
||||
[SM_L10N_INSTALLED] = "Εγκαταστάθηκε: %s",
|
||||
/* English: Unknown */
|
||||
[SM_L10N_UNKNOWN] = "Άγνωστο",
|
||||
/* English: Image mount failed: 0x%08X (%s)\n%s */
|
||||
[SM_L10N_IMAGE_MOUNT_FAILED] = "Η προσάρτηση του ειδώλου απέτυχε: 0x%08X (%s)\n%s",
|
||||
/* English: Filesystem cluster size (%llu) is smaller than device sector size (%u) for %s */
|
||||
[SM_L10N_FS_CLUSTER_SMALL] = "Το μέγεθος cluster του συστήματος αρχείων (%llu) είναι μικρότερο από το μέγεθος sector της συσκευής (%u) για %s",
|
||||
/* English: Mounted image is unreadable or damaged */
|
||||
[SM_L10N_IMAGE_UNREADABLE_DAMAGED] = "Το mounted image δεν διαβάζεται ή είναι κατεστραμμένο",
|
||||
/* English: Image cache full (%u entries), cannot track mount %s */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_TRACK] = "Η προσωρινή μνήμη ειδώλων είναι πλήρης (%u εγγραφές), δεν είναι δυνατή η παρακολούθηση της προσάρτησης %s",
|
||||
/* English: Image cache full (%u entries), rolling back mount */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_ROLLBACK] = "Η προσωρινή μνήμη ειδώλων είναι πλήρης (%u εγγραφές), γίνεται αναίρεση της προσάρτησης",
|
||||
/* English: Install failed: %s (%s)\nTimed out after 5 minutes */
|
||||
[SM_L10N_INSTALL_FAILED_TIMEOUT] = "Αποτυχία εγκατάστασης: %s (%s)\nTimeout μετά από 5 λεπτά",
|
||||
/* English: Batch install queued (%d): */
|
||||
[SM_L10N_BATCH_INSTALL_QUEUED] = "Μαζική εγκατάσταση στην ουρά (%d):",
|
||||
/* English: \n... and %d more */
|
||||
[SM_L10N_BATCH_INSTALL_AND_MORE] = "\n... και %d ακόμα",
|
||||
/* English: Batch install failed for %d app(s).\ncode=0x%08X */
|
||||
[SM_L10N_BATCH_INSTALL_FAILED] = "Η μαζική εγκατάσταση απέτυχε για %d app(s).\nκωδικός=0x%08X",
|
||||
/* English: %s crashed before KStuff was paused. KStuff is not the cause. */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF] = "Η εφαρμογή %s κατέρρευσε πριν από την παύση του KStuff. Δεν ευθύνεται το KStuff.",
|
||||
/* English: %s crashed before KStuff pause:\n%s */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF_FATAL] = "Το %s κατέρρευσε πριν από την παύση του KStuff:\n%s",
|
||||
/* English: %s crashed after KStuff was paused. Automatic tuning skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_SKIPPED] = "Η εφαρμογή %s κατέρρευσε μετά την παύση του KStuff. Η αυτόματη ρύθμιση παραλείφθηκε.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nAutotune skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_FATAL_SKIPPED] = "Το %s κατέρρευσε μετά την παύση του KStuff:\n%s\nΗ αυτόματη ρύθμιση παραλείφθηκε.",
|
||||
/* English: Can't load module after KStuff pause */
|
||||
[SM_L10N_RTLD_AFTER_KSTUFF_FALLBACK] = "Δεν μπορεί να φορτωθεί module μετά την παύση KStuff",
|
||||
/* English: can't load module %.*s after KStuff pause */
|
||||
[SM_L10N_RTLD_MODULE_AFTER_KSTUFF] = "δεν είναι δυνατή η φόρτωση της μονάδας %.*s μετά την παύση του KStuff",
|
||||
/* English: %s: %s. Delay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_DELAY_INCREASED_RELAUNCH] = "%s: %s. Η καθυστέρηση αυξήθηκε σε %u s.\nΕκκινήστε ξανά το παιχνίδι.",
|
||||
/* English: Crash detected after KStuff pause: pause delay for %s increased to %us. Launch the game again. */
|
||||
[SM_L10N_CRASH_DELAY_INCREASED] = "Εντοπίστηκε κατάρρευση μετά την παύση του KStuff: η καθυστέρηση για το %s αυξήθηκε σε %u δ. Εκκινήστε ξανά το παιχνίδι.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nDelay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_INCREASED] = "Το %s κατέρρευσε μετά την παύση του KStuff:\n%s\nΗ καθυστέρηση αυξήθηκε σε %u δ.\nΕκκινήστε ξανά το παιχνίδι.",
|
||||
/* English: Crash detected after KStuff pause: %s. Failed to update autotune delay. */
|
||||
[SM_L10N_CRASH_DELAY_UPDATE_FAILED] = "Εντοπίστηκε κατάρρευση μετά την παύση του KStuff: %s. Αποτυχία ενημέρωσης της καθυστέρησης αυτόματης ρύθμισης.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nFailed to update autotune delay. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_UPDATE_FAILED] = "Το %s κατέρρευσε μετά την παύση του KStuff:\n%s\nΑποτυχία ενημέρωσης της καθυστέρησης αυτόματης ρύθμισης.",
|
||||
/* English: Missing/invalid param.json:\n%s */
|
||||
[SM_L10N_MISSING_PARAM] = "Λείπει ή είναι άκυρο το param.json:\n%s",
|
||||
/* English: Found %d new games. Processing... */
|
||||
[SM_L10N_FOUND_NEW_GAMES] = "Βρέθηκαν %d νέα παιχνίδια. Γίνεται επεξεργασία...",
|
||||
/* English: Library synchronized.\nFound %d games. */
|
||||
[SM_L10N_LIBRARY_SYNCED] = "Η βιβλιοθήκη συγχρονίστηκε.\nΒρέθηκαν %d παιχνίδια.",
|
||||
/* English: ShadowMount+: config reloaded. */
|
||||
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: οι ρυθμίσεις επαναφορτώθηκαν.",
|
||||
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
|
||||
[SM_L10N_DUPLICATE_IGNORED] = "Το διπλότυπο %s αγνοήθηκε:\n%s\nυπάρχον: %s",
|
||||
/* English: AMPR emulator updated */
|
||||
[SM_L10N_AMPR_EMULATOR_UPDATED] = "Ο εξομοιωτής AMPR ενημερώθηκε",
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
static const char *const g_en_gb[SM_L10N_COUNT] = {
|
||||
/* English: Go to Debug Settings */
|
||||
[SM_L10N_ACTION_DEBUG_SETTINGS] = "Go to Debug Settings",
|
||||
/* English: ShadowMount+ v%s exFAT/UFS/PFS */
|
||||
[SM_L10N_STARTUP] = "ShadowMount+ v%s exFAT/UFS/PFS",
|
||||
/* English: ShadowMount+: recursive_scan=1 is deprecated, using scan_depth=2. */
|
||||
[SM_L10N_RECURSIVE_SCAN_DEPRECATED] = "ShadowMount+: recursive_scan=1 is deprecated, using scan_depth=2.",
|
||||
/* English: ShadowMount+: scan depth %u enabled. */
|
||||
[SM_L10N_SCAN_DEPTH_ENABLED] = "ShadowMount+: scan depth %u enabled.",
|
||||
/* English: Backport overlay failed: %s\n%s\n0x%08X */
|
||||
[SM_L10N_BACKPORT_OVERLAY_FAILED] = "Backport overlay failed: %s\n%s\n0x%08X",
|
||||
/* English: Backport applied to game: %s */
|
||||
[SM_L10N_GAME_BACKPORTED] = "Backport applied to game: %s",
|
||||
/* English: Backport applied to game: %s\nEmulators updated */
|
||||
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Backport applied to game: %s\nEmulators updated",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again.",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config.",
|
||||
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
|
||||
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "Register failed: %s (%s)\nAppInstallTitleDir unavailable",
|
||||
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
|
||||
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "Register failed: %s (%s)\nTitleDir bridge unavailable",
|
||||
/* English: Register failed: %s (%s)\ncode=0x%08X */
|
||||
[SM_L10N_REGISTER_FAILED_CODE] = "Register failed: %s (%s)\ncode=0x%08X",
|
||||
/* English: Installing: %s (%s)... */
|
||||
[SM_L10N_INSTALLING] = "Installing: %s (%s)...",
|
||||
/* English: KStuff active again */
|
||||
[SM_L10N_KSTUFF_ACTIVE] = "KStuff active again",
|
||||
/* English: KStuff paused while the game is running */
|
||||
[SM_L10N_KSTUFF_PAUSED] = "KStuff paused while the game is running",
|
||||
/* English: Installed: %s */
|
||||
[SM_L10N_INSTALLED] = "Installed: %s",
|
||||
/* English: Unknown */
|
||||
[SM_L10N_UNKNOWN] = "Unknown",
|
||||
/* English: Image mount failed: 0x%08X (%s)\n%s */
|
||||
[SM_L10N_IMAGE_MOUNT_FAILED] = "Image mount failed: 0x%08X (%s)\n%s",
|
||||
/* English: Filesystem cluster size (%llu) is smaller than device sector size (%u) for %s */
|
||||
[SM_L10N_FS_CLUSTER_SMALL] = "Filesystem cluster size (%llu) is smaller than device sector size (%u) for %s",
|
||||
/* English: Mounted image is unreadable or damaged */
|
||||
[SM_L10N_IMAGE_UNREADABLE_DAMAGED] = "Mounted image is unreadable or damaged",
|
||||
/* English: Image cache full (%u entries), cannot track mount %s */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_TRACK] = "Image cache full (%u entries), cannot track mount %s",
|
||||
/* English: Image cache full (%u entries), rolling back mount */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_ROLLBACK] = "Image cache full (%u entries), rolling back mount",
|
||||
/* English: Install failed: %s (%s)\nTimed out after 5 minutes */
|
||||
[SM_L10N_INSTALL_FAILED_TIMEOUT] = "Install failed: %s (%s)\nTimed out after 5 minutes",
|
||||
/* English: Batch install queued (%d): */
|
||||
[SM_L10N_BATCH_INSTALL_QUEUED] = "Batch install queued (%d):",
|
||||
/* English: \n... and %d more */
|
||||
[SM_L10N_BATCH_INSTALL_AND_MORE] = "\n... and %d more",
|
||||
/* English: Batch install failed for %d app(s).\ncode=0x%08X */
|
||||
[SM_L10N_BATCH_INSTALL_FAILED] = "Batch install failed for %d app(s).\ncode=0x%08X",
|
||||
/* English: %s crashed before KStuff was paused. KStuff is not the cause. */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF] = "%s crashed before KStuff was paused. KStuff is not the cause.",
|
||||
/* English: %s crashed before KStuff pause:\n%s */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF_FATAL] = "%s crashed before KStuff pause:\n%s",
|
||||
/* English: %s crashed after KStuff was paused. Automatic tuning skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_SKIPPED] = "%s crashed after KStuff was paused. Automatic tuning skipped.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nAutotune skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_FATAL_SKIPPED] = "%s crashed after KStuff pause:\n%s\nAutotune skipped.",
|
||||
/* English: Can't load module after KStuff pause */
|
||||
[SM_L10N_RTLD_AFTER_KSTUFF_FALLBACK] = "Can't load module after KStuff pause",
|
||||
/* English: can't load module %.*s after KStuff pause */
|
||||
[SM_L10N_RTLD_MODULE_AFTER_KSTUFF] = "can't load module %.*s after KStuff pause",
|
||||
/* English: %s: %s. Delay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_DELAY_INCREASED_RELAUNCH] = "%s: %s. Delay increased to %us.\nLaunch the game again.",
|
||||
/* English: Crash detected after KStuff pause: pause delay for %s increased to %us. Launch the game again. */
|
||||
[SM_L10N_CRASH_DELAY_INCREASED] = "Crash detected after KStuff pause: pause delay for %s increased to %us. Launch the game again.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nDelay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_INCREASED] = "%s crashed after KStuff pause:\n%s\nDelay increased to %us.\nLaunch the game again.",
|
||||
/* English: Crash detected after KStuff pause: %s. Failed to update autotune delay. */
|
||||
[SM_L10N_CRASH_DELAY_UPDATE_FAILED] = "Crash detected after KStuff pause: %s. Failed to update autotune delay.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nFailed to update autotune delay. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_UPDATE_FAILED] = "%s crashed after KStuff pause:\n%s\nFailed to update autotune delay.",
|
||||
/* English: Missing/invalid param.json:\n%s */
|
||||
[SM_L10N_MISSING_PARAM] = "Missing/invalid param.json:\n%s",
|
||||
/* English: Found %d new games. Processing... */
|
||||
[SM_L10N_FOUND_NEW_GAMES] = "Found %d new games. Processing...",
|
||||
/* English: Library synchronized.\nFound %d games. */
|
||||
[SM_L10N_LIBRARY_SYNCED] = "Library synchronised.\nFound %d games.",
|
||||
/* English: ShadowMount+: config reloaded. */
|
||||
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: config reloaded.",
|
||||
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
|
||||
[SM_L10N_DUPLICATE_IGNORED] = "Duplicate %s ignored:\n%s\nexisting: %s",
|
||||
/* English: AMPR emulator updated */
|
||||
[SM_L10N_AMPR_EMULATOR_UPDATED] = "AMPR emulator updated",
|
||||
};
|
||||
@@ -0,0 +1,75 @@
|
||||
static const char *const g_en_us[SM_L10N_COUNT] = {
|
||||
[SM_L10N_ACTION_DEBUG_SETTINGS] = "Go to Debug Settings",
|
||||
[SM_L10N_STARTUP] = "ShadowMount+ v%s exFAT/UFS/PFS",
|
||||
[SM_L10N_RECURSIVE_SCAN_DEPRECATED] =
|
||||
"ShadowMount+: recursive_scan=1 is deprecated, using scan_depth=2.",
|
||||
[SM_L10N_SCAN_DEPTH_ENABLED] = "ShadowMount+: scan depth %u enabled.",
|
||||
[SM_L10N_BACKPORT_OVERLAY_FAILED] =
|
||||
"Backport overlay failed: %s\n%s\n0x%08X",
|
||||
[SM_L10N_GAME_BACKPORTED] = "Backport applied to game: %s",
|
||||
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] =
|
||||
"Backport applied to game: %s\nEmulators updated",
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] =
|
||||
"Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\n"
|
||||
"Saved image_sector override: %u.\nTry mounting again.",
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] =
|
||||
"Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\n"
|
||||
"Use a larger cluster size in the image or lower sector size in config.",
|
||||
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] =
|
||||
"Register failed: %s (%s)\nAppInstallTitleDir unavailable",
|
||||
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] =
|
||||
"Register failed: %s (%s)\nTitleDir bridge unavailable",
|
||||
[SM_L10N_REGISTER_FAILED_CODE] = "Register failed: %s (%s)\ncode=0x%08X",
|
||||
[SM_L10N_INSTALLING] = "Installing: %s (%s)...",
|
||||
[SM_L10N_KSTUFF_ACTIVE] = "KStuff active again",
|
||||
[SM_L10N_KSTUFF_PAUSED] = "KStuff paused while the game is running",
|
||||
[SM_L10N_INSTALLED] = "Installed: %s",
|
||||
[SM_L10N_UNKNOWN] = "Unknown",
|
||||
[SM_L10N_IMAGE_MOUNT_FAILED] = "Image mount failed: 0x%08X (%s)\n%s",
|
||||
[SM_L10N_FS_CLUSTER_SMALL] =
|
||||
"Filesystem cluster size (%llu) is smaller than device sector size "
|
||||
"(%u) for %s",
|
||||
[SM_L10N_IMAGE_UNREADABLE_DAMAGED] =
|
||||
"Mounted image is unreadable or damaged",
|
||||
[SM_L10N_IMAGE_CACHE_FULL_TRACK] =
|
||||
"Image cache full (%u entries), cannot track mount %s",
|
||||
[SM_L10N_IMAGE_CACHE_FULL_ROLLBACK] =
|
||||
"Image cache full (%u entries), rolling back mount",
|
||||
[SM_L10N_INSTALL_FAILED_TIMEOUT] =
|
||||
"Install failed: %s (%s)\nTimed out after 5 minutes",
|
||||
[SM_L10N_BATCH_INSTALL_QUEUED] = "Batch install queued (%d):",
|
||||
[SM_L10N_BATCH_INSTALL_AND_MORE] = "\n... and %d more",
|
||||
[SM_L10N_BATCH_INSTALL_FAILED] =
|
||||
"Batch install failed for %d app(s).\ncode=0x%08X",
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF] =
|
||||
"%s crashed before KStuff was paused. KStuff is not the cause.",
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF_FATAL] =
|
||||
"%s crashed before KStuff pause:\n%s",
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_SKIPPED] =
|
||||
"%s crashed after KStuff was paused. Automatic tuning skipped.",
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_FATAL_SKIPPED] =
|
||||
"%s crashed after KStuff pause:\n%s\nAutotune skipped.",
|
||||
[SM_L10N_RTLD_AFTER_KSTUFF_FALLBACK] =
|
||||
"Can't load module after KStuff pause",
|
||||
[SM_L10N_RTLD_MODULE_AFTER_KSTUFF] =
|
||||
"can't load module %.*s after KStuff pause",
|
||||
[SM_L10N_DELAY_INCREASED_RELAUNCH] =
|
||||
"%s: %s. Delay increased to %us.\nLaunch the game again.",
|
||||
[SM_L10N_CRASH_DELAY_INCREASED] =
|
||||
"Crash detected after KStuff pause: pause delay for %s increased to "
|
||||
"%us. Launch the game again.",
|
||||
[SM_L10N_CRASH_FATAL_DELAY_INCREASED] =
|
||||
"%s crashed after KStuff pause:\n%s\nDelay increased to %us.\n"
|
||||
"Launch the game again.",
|
||||
[SM_L10N_CRASH_DELAY_UPDATE_FAILED] =
|
||||
"Crash detected after KStuff pause: %s. Failed to update autotune delay.",
|
||||
[SM_L10N_CRASH_FATAL_DELAY_UPDATE_FAILED] =
|
||||
"%s crashed after KStuff pause:\n%s\nFailed to update autotune delay.",
|
||||
[SM_L10N_MISSING_PARAM] = "Missing/invalid param.json:\n%s",
|
||||
[SM_L10N_FOUND_NEW_GAMES] = "Found %d new games. Processing...",
|
||||
[SM_L10N_LIBRARY_SYNCED] = "Library synchronized.\nFound %d games.",
|
||||
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: config reloaded.",
|
||||
[SM_L10N_DUPLICATE_IGNORED] =
|
||||
"Duplicate %s ignored:\n%s\nexisting: %s",
|
||||
[SM_L10N_AMPR_EMULATOR_UPDATED] = "AMPR emulator updated",
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
static const char *const g_es_es[SM_L10N_COUNT] = {
|
||||
/* English: Go to Debug Settings */
|
||||
[SM_L10N_ACTION_DEBUG_SETTINGS] = "Ir a ajustes de depuración",
|
||||
/* English: ShadowMount+ v%s exFAT/UFS/PFS */
|
||||
[SM_L10N_STARTUP] = "ShadowMount+ v%s exFAT/UFS/PFS",
|
||||
/* English: ShadowMount+: recursive_scan=1 is deprecated, using scan_depth=2. */
|
||||
[SM_L10N_RECURSIVE_SCAN_DEPRECATED] = "ShadowMount+: recursive_scan=1 está obsoleto, se usará scan_depth=2.",
|
||||
/* English: ShadowMount+: scan depth %u enabled. */
|
||||
[SM_L10N_SCAN_DEPTH_ENABLED] = "ShadowMount+: profundidad de escaneo %u activada.",
|
||||
/* English: Backport overlay failed: %s\n%s\n0x%08X */
|
||||
[SM_L10N_BACKPORT_OVERLAY_FAILED] = "Error en overlay de backport: %s\n%s\n0x%08X",
|
||||
/* English: Backport applied to game: %s */
|
||||
[SM_L10N_GAME_BACKPORTED] = "Juego con backport aplicado: %s",
|
||||
/* English: Backport applied to game: %s\nEmulators updated */
|
||||
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Juego con backport aplicado: %s\nEmuladores actualizados",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Montaje de imagen rechazado:\n%s\nEl tamaño de clúster es demasiado pequeño (%llu < %u).\nSe guardó el valor de image_sector: %u.\nIntente montar de nuevo.",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "Montaje de imagen rechazado:\n%s\nEl tamaño del clúster es demasiado pequeño (%llu < %u).\nUse un clúster mayor en la imagen o un tamaño de sector menor en la configuración.",
|
||||
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
|
||||
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "Error al registrar: %s (%s)\nAppInstallTitleDir no disponible",
|
||||
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
|
||||
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "Error de registro: %s (%s)\nEl puente TitleDir no está disponible",
|
||||
/* English: Register failed: %s (%s)\ncode=0x%08X */
|
||||
[SM_L10N_REGISTER_FAILED_CODE] = "Error al registrar: %s (%s)\ncódigo=0x%08X",
|
||||
/* English: Installing: %s (%s)... */
|
||||
[SM_L10N_INSTALLING] = "Instalando: %s (%s)...",
|
||||
/* English: KStuff active again */
|
||||
[SM_L10N_KSTUFF_ACTIVE] = "KStuff activo de nuevo",
|
||||
/* English: KStuff paused while the game is running */
|
||||
[SM_L10N_KSTUFF_PAUSED] = "KStuff pausado mientras el juego está en ejecución",
|
||||
/* English: Installed: %s */
|
||||
[SM_L10N_INSTALLED] = "Instalado: %s",
|
||||
/* English: Unknown */
|
||||
[SM_L10N_UNKNOWN] = "Desconocido",
|
||||
/* English: Image mount failed: 0x%08X (%s)\n%s */
|
||||
[SM_L10N_IMAGE_MOUNT_FAILED] = "Error al montar imagen: 0x%08X (%s)\n%s",
|
||||
/* English: Filesystem cluster size (%llu) is smaller than device sector size (%u) for %s */
|
||||
[SM_L10N_FS_CLUSTER_SMALL] = "El tamaño de clúster del sistema de archivos (%llu) es menor que el tamaño de sector del dispositivo (%u) para %s",
|
||||
/* English: Mounted image is unreadable or damaged */
|
||||
[SM_L10N_IMAGE_UNREADABLE_DAMAGED] = "La imagen montada es ilegible o está dañada",
|
||||
/* English: Image cache full (%u entries), cannot track mount %s */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_TRACK] = "La caché de imágenes está llena (%u entradas); no se puede supervisar el montaje %s",
|
||||
/* English: Image cache full (%u entries), rolling back mount */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_ROLLBACK] = "La caché de imágenes está llena (%u entradas); se deshará el montaje",
|
||||
/* English: Install failed: %s (%s)\nTimed out after 5 minutes */
|
||||
[SM_L10N_INSTALL_FAILED_TIMEOUT] = "Error de instalación: %s (%s)\nTiempo agotado tras 5 minutos",
|
||||
/* English: Batch install queued (%d): */
|
||||
[SM_L10N_BATCH_INSTALL_QUEUED] = "Instalación por lotes en cola (%d):",
|
||||
/* English: \n... and %d more */
|
||||
[SM_L10N_BATCH_INSTALL_AND_MORE] = "\n... y %d más",
|
||||
/* English: Batch install failed for %d app(s).\ncode=0x%08X */
|
||||
[SM_L10N_BATCH_INSTALL_FAILED] = "La instalación por lotes falló para %d aplicaciones.\ncódigo=0x%08X",
|
||||
/* English: %s crashed before KStuff was paused. KStuff is not the cause. */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF] = "%s se bloqueó antes de pausar KStuff. KStuff no es la causa.",
|
||||
/* English: %s crashed before KStuff pause:\n%s */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF_FATAL] = "%s se bloqueó antes de pausar KStuff:\n%s",
|
||||
/* English: %s crashed after KStuff was paused. Automatic tuning skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_SKIPPED] = "%s se bloqueó después de pausar KStuff. Se omitió el ajuste automático.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nAutotune skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_FATAL_SKIPPED] = "%s se bloqueó después de pausar KStuff:\n%s\nSe omitió el ajuste automático.",
|
||||
/* English: Can't load module after KStuff pause */
|
||||
[SM_L10N_RTLD_AFTER_KSTUFF_FALLBACK] = "No se puede cargar el módulo tras la pausa de KStuff",
|
||||
/* English: can't load module %.*s after KStuff pause */
|
||||
[SM_L10N_RTLD_MODULE_AFTER_KSTUFF] = "no se puede cargar el módulo %.*s después de pausar KStuff",
|
||||
/* English: %s: %s. Delay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_DELAY_INCREASED_RELAUNCH] = "%s: %s. Retardo aumentado a %u s.\nInicie el juego de nuevo.",
|
||||
/* English: Crash detected after KStuff pause: pause delay for %s increased to %us. Launch the game again. */
|
||||
[SM_L10N_CRASH_DELAY_INCREASED] = "Se detectó un bloqueo después de pausar KStuff: el retardo de pausa de %s aumentó a %u s. Inicie el juego de nuevo.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nDelay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_INCREASED] = "%s se bloqueó después de pausar KStuff:\n%s\nEl retraso aumentó a %u s.\nInicie el juego de nuevo.",
|
||||
/* English: Crash detected after KStuff pause: %s. Failed to update autotune delay. */
|
||||
[SM_L10N_CRASH_DELAY_UPDATE_FAILED] = "Se detectó un bloqueo después de pausar KStuff: %s. No se pudo actualizar el retardo del ajuste automático.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nFailed to update autotune delay. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_UPDATE_FAILED] = "%s se bloqueó después de pausar KStuff:\n%s\nNo se pudo actualizar el retraso del ajuste automático.",
|
||||
/* English: Missing/invalid param.json:\n%s */
|
||||
[SM_L10N_MISSING_PARAM] = "param.json ausente o no válido:\n%s",
|
||||
/* English: Found %d new games. Processing... */
|
||||
[SM_L10N_FOUND_NEW_GAMES] = "Se encontraron %d juegos nuevos. Procesando...",
|
||||
/* English: Library synchronized.\nFound %d games. */
|
||||
[SM_L10N_LIBRARY_SYNCED] = "Biblioteca sincronizada.\nSe encontraron %d juegos.",
|
||||
/* English: ShadowMount+: config reloaded. */
|
||||
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: configuración recargada.",
|
||||
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
|
||||
[SM_L10N_DUPLICATE_IGNORED] = "Duplicado %s ignorado:\n%s\nexistente: %s",
|
||||
/* English: AMPR emulator updated */
|
||||
[SM_L10N_AMPR_EMULATOR_UPDATED] = "Emulador AMPR actualizado",
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
static const char *const g_es_mx[SM_L10N_COUNT] = {
|
||||
/* English: Go to Debug Settings */
|
||||
[SM_L10N_ACTION_DEBUG_SETTINGS] = "Ir a configuración de depuración",
|
||||
/* English: ShadowMount+ v%s exFAT/UFS/PFS */
|
||||
[SM_L10N_STARTUP] = "ShadowMount+ v%s exFAT/UFS/PFS",
|
||||
/* English: ShadowMount+: recursive_scan=1 is deprecated, using scan_depth=2. */
|
||||
[SM_L10N_RECURSIVE_SCAN_DEPRECATED] = "ShadowMount+: recursive_scan=1 está obsoleto, se usará scan_depth=2.",
|
||||
/* English: ShadowMount+: scan depth %u enabled. */
|
||||
[SM_L10N_SCAN_DEPTH_ENABLED] = "ShadowMount+: profundidad de escaneo %u activada.",
|
||||
/* English: Backport overlay failed: %s\n%s\n0x%08X */
|
||||
[SM_L10N_BACKPORT_OVERLAY_FAILED] = "Falló el overlay de backport: %s\n%s\n0x%08X",
|
||||
/* English: Backport applied to game: %s */
|
||||
[SM_L10N_GAME_BACKPORTED] = "Backport aplicado al juego: %s",
|
||||
/* English: Backport applied to game: %s\nEmulators updated */
|
||||
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Backport aplicado al juego: %s\nEmuladores actualizados",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Montaje de imagen rechazado:\n%s\nEl tamaño de clúster es demasiado pequeño (%llu < %u).\nSe guardó el valor de image_sector: %u.\nIntenta montar de nuevo.",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "Montaje de imagen rechazado:\n%s\nEl tamaño del clúster es demasiado pequeño (%llu < %u).\nUsa un clúster más grande en la imagen o un tamaño de sector menor en la configuración.",
|
||||
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
|
||||
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "Falló el registro: %s (%s)\nAppInstallTitleDir no disponible",
|
||||
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
|
||||
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "Error de registro: %s (%s)\nEl puente TitleDir no está disponible",
|
||||
/* English: Register failed: %s (%s)\ncode=0x%08X */
|
||||
[SM_L10N_REGISTER_FAILED_CODE] = "Falló el registro: %s (%s)\ncódigo=0x%08X",
|
||||
/* English: Installing: %s (%s)... */
|
||||
[SM_L10N_INSTALLING] = "Instalando: %s (%s)...",
|
||||
/* English: KStuff active again */
|
||||
[SM_L10N_KSTUFF_ACTIVE] = "KStuff activo otra vez",
|
||||
/* English: KStuff paused while the game is running */
|
||||
[SM_L10N_KSTUFF_PAUSED] = "KStuff pausado mientras el juego está corriendo",
|
||||
/* English: Installed: %s */
|
||||
[SM_L10N_INSTALLED] = "Instalado: %s",
|
||||
/* English: Unknown */
|
||||
[SM_L10N_UNKNOWN] = "Desconocido",
|
||||
/* English: Image mount failed: 0x%08X (%s)\n%s */
|
||||
[SM_L10N_IMAGE_MOUNT_FAILED] = "Falló el montaje de imagen: 0x%08X (%s)\n%s",
|
||||
/* English: Filesystem cluster size (%llu) is smaller than device sector size (%u) for %s */
|
||||
[SM_L10N_FS_CLUSTER_SMALL] = "El tamaño de clúster del sistema de archivos (%llu) es menor que el tamaño de sector del dispositivo (%u) para %s",
|
||||
/* English: Mounted image is unreadable or damaged */
|
||||
[SM_L10N_IMAGE_UNREADABLE_DAMAGED] = "La imagen montada no se puede leer o está dañada",
|
||||
/* English: Image cache full (%u entries), cannot track mount %s */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_TRACK] = "La caché de imágenes está llena (%u entradas); no se puede supervisar el montaje %s",
|
||||
/* English: Image cache full (%u entries), rolling back mount */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_ROLLBACK] = "La caché de imágenes está llena (%u entradas); se deshará el montaje",
|
||||
/* English: Install failed: %s (%s)\nTimed out after 5 minutes */
|
||||
[SM_L10N_INSTALL_FAILED_TIMEOUT] = "Falló la instalación: %s (%s)\nTiempo agotado después de 5 minutos",
|
||||
/* English: Batch install queued (%d): */
|
||||
[SM_L10N_BATCH_INSTALL_QUEUED] = "Instalación por lotes en cola (%d):",
|
||||
/* English: \n... and %d more */
|
||||
[SM_L10N_BATCH_INSTALL_AND_MORE] = "\n... y %d más",
|
||||
/* English: Batch install failed for %d app(s).\ncode=0x%08X */
|
||||
[SM_L10N_BATCH_INSTALL_FAILED] = "La instalación por lotes falló para %d aplicaciones.\ncódigo=0x%08X",
|
||||
/* English: %s crashed before KStuff was paused. KStuff is not the cause. */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF] = "%s falló antes de pausar KStuff. KStuff no es la causa.",
|
||||
/* English: %s crashed before KStuff pause:\n%s */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF_FATAL] = "%s falló antes de pausar KStuff:\n%s",
|
||||
/* English: %s crashed after KStuff was paused. Automatic tuning skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_SKIPPED] = "%s falló después de pausar KStuff. Se omitió el ajuste automático.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nAutotune skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_FATAL_SKIPPED] = "%s falló después de pausar KStuff:\n%s\nSe omitió el ajuste automático.",
|
||||
/* English: Can't load module after KStuff pause */
|
||||
[SM_L10N_RTLD_AFTER_KSTUFF_FALLBACK] = "No se puede cargar el módulo después de la pausa de KStuff",
|
||||
/* English: can't load module %.*s after KStuff pause */
|
||||
[SM_L10N_RTLD_MODULE_AFTER_KSTUFF] = "no se puede cargar el módulo %.*s después de pausar KStuff",
|
||||
/* English: %s: %s. Delay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_DELAY_INCREASED_RELAUNCH] = "%s: %s. Retardo aumentado a %u s.\nInicia el juego de nuevo.",
|
||||
/* English: Crash detected after KStuff pause: pause delay for %s increased to %us. Launch the game again. */
|
||||
[SM_L10N_CRASH_DELAY_INCREASED] = "Se detectó una falla después de pausar KStuff: el retraso de pausa de %s aumentó a %u s. Inicia el juego de nuevo.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nDelay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_INCREASED] = "%s falló después de pausar KStuff:\n%s\nEl retraso aumentó a %u s.\nInicia el juego de nuevo.",
|
||||
/* English: Crash detected after KStuff pause: %s. Failed to update autotune delay. */
|
||||
[SM_L10N_CRASH_DELAY_UPDATE_FAILED] = "Se detectó una falla después de pausar KStuff: %s. No se pudo actualizar el retraso del ajuste automático.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nFailed to update autotune delay. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_UPDATE_FAILED] = "%s falló después de pausar KStuff:\n%s\nNo se pudo actualizar el retraso del ajuste automático.",
|
||||
/* English: Missing/invalid param.json:\n%s */
|
||||
[SM_L10N_MISSING_PARAM] = "param.json faltante o no válido:\n%s",
|
||||
/* English: Found %d new games. Processing... */
|
||||
[SM_L10N_FOUND_NEW_GAMES] = "Se encontraron %d juegos nuevos. Procesando...",
|
||||
/* English: Library synchronized.\nFound %d games. */
|
||||
[SM_L10N_LIBRARY_SYNCED] = "Biblioteca sincronizada.\nSe encontraron %d juegos.",
|
||||
/* English: ShadowMount+: config reloaded. */
|
||||
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: configuración recargada.",
|
||||
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
|
||||
[SM_L10N_DUPLICATE_IGNORED] = "Duplicado %s ignorado:\n%s\nexistente: %s",
|
||||
/* English: AMPR emulator updated */
|
||||
[SM_L10N_AMPR_EMULATOR_UPDATED] = "Emulador AMPR actualizado",
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
static const char *const g_fi_fi[SM_L10N_COUNT] = {
|
||||
/* English: Go to Debug Settings */
|
||||
[SM_L10N_ACTION_DEBUG_SETTINGS] = "Siirry virheenkorjausasetuksiin",
|
||||
/* English: ShadowMount+ v%s exFAT/UFS/PFS */
|
||||
[SM_L10N_STARTUP] = "ShadowMount+ v%s exFAT/UFS/PFS",
|
||||
/* English: ShadowMount+: recursive_scan=1 is deprecated, using scan_depth=2. */
|
||||
[SM_L10N_RECURSIVE_SCAN_DEPRECATED] = "ShadowMount+: recursive_scan=1 on vanhentunut, käytetään scan_depth=2.",
|
||||
/* English: ShadowMount+: scan depth %u enabled. */
|
||||
[SM_L10N_SCAN_DEPTH_ENABLED] = "ShadowMount+: skannaussyvyys %u käytössä.",
|
||||
/* English: Backport overlay failed: %s\n%s\n0x%08X */
|
||||
[SM_L10N_BACKPORT_OVERLAY_FAILED] = "Backport-overlay epäonnistui: %s\n%s\n0x%08X",
|
||||
/* English: Backport applied to game: %s */
|
||||
[SM_L10N_GAME_BACKPORTED] = "Peliin käytettiin backportia: %s",
|
||||
/* English: Backport applied to game: %s\nEmulators updated */
|
||||
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Peliin käytettiin backportia: %s\nEmulaattorit päivitetty",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Imagen liittäminen hylätty:\n%s\nKlusterikoko on liian pieni (%llu < %u).\nimage_sector-ohitus tallennettu: %u.\nYritä liittää uudelleen.",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "Levykuvan liittäminen hylättiin:\n%s\nKlusterikoko on liian pieni (%llu < %u).\nKäytä levykuvassa suurempaa klusteria tai määrityksissä pienempää sektorikokoa.",
|
||||
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
|
||||
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "Rekisteröinti epäonnistui: %s (%s)\nAppInstallTitleDir ei ole käytettävissä",
|
||||
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
|
||||
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "Rekisteröinti epäonnistui: %s (%s)\nTitleDir-silta ei ole käytettävissä",
|
||||
/* English: Register failed: %s (%s)\ncode=0x%08X */
|
||||
[SM_L10N_REGISTER_FAILED_CODE] = "Rekisteröinti epäonnistui: %s (%s)\nkoodi=0x%08X",
|
||||
/* English: Installing: %s (%s)... */
|
||||
[SM_L10N_INSTALLING] = "Asennetaan: %s (%s)...",
|
||||
/* English: KStuff active again */
|
||||
[SM_L10N_KSTUFF_ACTIVE] = "KStuff on taas aktiivinen",
|
||||
/* English: KStuff paused while the game is running */
|
||||
[SM_L10N_KSTUFF_PAUSED] = "KStuff keskeytetty pelin ollessa käynnissä",
|
||||
/* English: Installed: %s */
|
||||
[SM_L10N_INSTALLED] = "Asennettu: %s",
|
||||
/* English: Unknown */
|
||||
[SM_L10N_UNKNOWN] = "Tuntematon",
|
||||
/* English: Image mount failed: 0x%08X (%s)\n%s */
|
||||
[SM_L10N_IMAGE_MOUNT_FAILED] = "Imagen liittäminen epäonnistui: 0x%08X (%s)\n%s",
|
||||
/* English: Filesystem cluster size (%llu) is smaller than device sector size (%u) for %s */
|
||||
[SM_L10N_FS_CLUSTER_SMALL] = "Tiedostojärjestelmän klusterikoko (%llu) on pienempi kuin laitteen sektorikoko (%u) kohteelle %s",
|
||||
/* English: Mounted image is unreadable or damaged */
|
||||
[SM_L10N_IMAGE_UNREADABLE_DAMAGED] = "Liitetty levykuva ei ole luettavissa tai se on vioittunut",
|
||||
/* English: Image cache full (%u entries), cannot track mount %s */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_TRACK] = "Levykuvavälimuisti on täynnä (%u merkintää), liittämistä %s ei voi seurata",
|
||||
/* English: Image cache full (%u entries), rolling back mount */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_ROLLBACK] = "Levykuvavälimuisti on täynnä (%u merkintää), liittäminen perutaan",
|
||||
/* English: Install failed: %s (%s)\nTimed out after 5 minutes */
|
||||
[SM_L10N_INSTALL_FAILED_TIMEOUT] = "Asennus epäonnistui: %s (%s)\nAikakatkaisu 5 minuutin jälkeen",
|
||||
/* English: Batch install queued (%d): */
|
||||
[SM_L10N_BATCH_INSTALL_QUEUED] = "Eräasennus jonossa (%d):",
|
||||
/* English: \n... and %d more */
|
||||
[SM_L10N_BATCH_INSTALL_AND_MORE] = "\n... ja %d lisää",
|
||||
/* English: Batch install failed for %d app(s).\ncode=0x%08X */
|
||||
[SM_L10N_BATCH_INSTALL_FAILED] = "Eräasennus epäonnistui %d sovellukselle.\nkoodi=0x%08X",
|
||||
/* English: %s crashed before KStuff was paused. KStuff is not the cause. */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF] = "Sovellus %s kaatui ennen KStuffin keskeytystä. KStuff ei ole syyllinen.",
|
||||
/* English: %s crashed before KStuff pause:\n%s */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF_FATAL] = "%s kaatui ennen KStuffin keskeytystä:\n%s",
|
||||
/* English: %s crashed after KStuff was paused. Automatic tuning skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_SKIPPED] = "Sovellus %s kaatui KStuffin keskeytyksen jälkeen. Automaattinen säätö ohitettiin.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nAutotune skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_FATAL_SKIPPED] = "%s kaatui KStuffin keskeytyksen jälkeen:\n%s\nAutomaattinen säätö ohitettiin.",
|
||||
/* English: Can't load module after KStuff pause */
|
||||
[SM_L10N_RTLD_AFTER_KSTUFF_FALLBACK] = "Moduulia ei voi ladata KStuff-tauon jälkeen",
|
||||
/* English: can't load module %.*s after KStuff pause */
|
||||
[SM_L10N_RTLD_MODULE_AFTER_KSTUFF] = "moduulia %.*s ei voi ladata KStuffin keskeytyksen jälkeen",
|
||||
/* English: %s: %s. Delay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_DELAY_INCREASED_RELAUNCH] = "%s: %s. Viive nostettu arvoon %u s.\nKäynnistä peli uudelleen.",
|
||||
/* English: Crash detected after KStuff pause: pause delay for %s increased to %us. Launch the game again. */
|
||||
[SM_L10N_CRASH_DELAY_INCREASED] = "KStuffin keskeytyksen jälkeen havaittiin kaatuminen: kohteen %s viive nostettiin arvoon %u s. Käynnistä peli uudelleen.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nDelay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_INCREASED] = "%s kaatui KStuffin keskeytyksen jälkeen:\n%s\nViive nostettiin arvoon %u s.\nKäynnistä peli uudelleen.",
|
||||
/* English: Crash detected after KStuff pause: %s. Failed to update autotune delay. */
|
||||
[SM_L10N_CRASH_DELAY_UPDATE_FAILED] = "KStuffin keskeytyksen jälkeen havaittiin kaatuminen: %s. Automaattisen säädön viivettä ei voitu päivittää.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nFailed to update autotune delay. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_UPDATE_FAILED] = "%s kaatui KStuffin keskeytyksen jälkeen:\n%s\nAutomaattisen säädön viivettä ei voitu päivittää.",
|
||||
/* English: Missing/invalid param.json:\n%s */
|
||||
[SM_L10N_MISSING_PARAM] = "param.json puuttuu tai on virheellinen:\n%s",
|
||||
/* English: Found %d new games. Processing... */
|
||||
[SM_L10N_FOUND_NEW_GAMES] = "Löytyi %d uutta peliä. Käsitellään...",
|
||||
/* English: Library synchronized.\nFound %d games. */
|
||||
[SM_L10N_LIBRARY_SYNCED] = "Kirjasto synkronoitu.\nLöytyi %d peliä.",
|
||||
/* English: ShadowMount+: config reloaded. */
|
||||
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: määritykset ladattiin uudelleen.",
|
||||
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
|
||||
[SM_L10N_DUPLICATE_IGNORED] = "Kaksoiskappale %s ohitettu:\n%s\nolemassa: %s",
|
||||
/* English: AMPR emulator updated */
|
||||
[SM_L10N_AMPR_EMULATOR_UPDATED] = "AMPR-emulaattori päivitetty",
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
static const char *const g_fr_ca[SM_L10N_COUNT] = {
|
||||
/* English: Go to Debug Settings */
|
||||
[SM_L10N_ACTION_DEBUG_SETTINGS] = "Accéder aux paramètres de débogage",
|
||||
/* English: ShadowMount+ v%s exFAT/UFS/PFS */
|
||||
[SM_L10N_STARTUP] = "ShadowMount+ v%s exFAT/UFS/PFS",
|
||||
/* English: ShadowMount+: recursive_scan=1 is deprecated, using scan_depth=2. */
|
||||
[SM_L10N_RECURSIVE_SCAN_DEPRECATED] = "ShadowMount+ : recursive_scan=1 est obsolète, scan_depth=2 sera utilisé.",
|
||||
/* English: ShadowMount+: scan depth %u enabled. */
|
||||
[SM_L10N_SCAN_DEPTH_ENABLED] = "ShadowMount+ : profondeur d’analyse %u activée.",
|
||||
/* English: Backport overlay failed: %s\n%s\n0x%08X */
|
||||
[SM_L10N_BACKPORT_OVERLAY_FAILED] = "Échec du montage de la surcouche de backport : %s\n%s\n0x%08X",
|
||||
/* English: Backport applied to game: %s */
|
||||
[SM_L10N_GAME_BACKPORTED] = "Backport appliqué au jeu : %s",
|
||||
/* English: Backport applied to game: %s\nEmulators updated */
|
||||
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Backport appliqué au jeu : %s\nÉmulateurs mis à jour",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Montage de l’image refusé :\n%s\nLa taille des grappes est trop petite (%llu < %u).\nValeur image_sector enregistrée : %u.\nRéessayez de monter l’image.",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "Montage de l’image refusé :\n%s\nLa taille des grappes est trop petite (%llu < %u).\nUtilisez des grappes plus grandes dans l’image ou une taille de secteur plus petite dans la configuration.",
|
||||
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
|
||||
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "Échec de l'enregistrement : %s (%s)\nAppInstallTitleDir indisponible",
|
||||
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
|
||||
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "Échec de l’enregistrement : %s (%s)\nLe pont TitleDir n’est pas disponible",
|
||||
/* English: Register failed: %s (%s)\ncode=0x%08X */
|
||||
[SM_L10N_REGISTER_FAILED_CODE] = "Échec de l'enregistrement : %s (%s)\ncode=0x%08X",
|
||||
/* English: Installing: %s (%s)... */
|
||||
[SM_L10N_INSTALLING] = "Installation : %s (%s)...",
|
||||
/* English: KStuff active again */
|
||||
[SM_L10N_KSTUFF_ACTIVE] = "KStuff est de nouveau actif",
|
||||
/* English: KStuff paused while the game is running */
|
||||
[SM_L10N_KSTUFF_PAUSED] = "KStuff mis en pause pendant l'exécution du jeu",
|
||||
/* English: Installed: %s */
|
||||
[SM_L10N_INSTALLED] = "Installé : %s",
|
||||
/* English: Unknown */
|
||||
[SM_L10N_UNKNOWN] = "Inconnu",
|
||||
/* English: Image mount failed: 0x%08X (%s)\n%s */
|
||||
[SM_L10N_IMAGE_MOUNT_FAILED] = "Échec du montage de l'image : 0x%08X (%s)\n%s",
|
||||
/* English: Filesystem cluster size (%llu) is smaller than device sector size (%u) for %s */
|
||||
[SM_L10N_FS_CLUSTER_SMALL] = "La taille des grappes du système de fichiers (%llu) est inférieure à la taille des secteurs du périphérique (%u) pour %s",
|
||||
/* English: Mounted image is unreadable or damaged */
|
||||
[SM_L10N_IMAGE_UNREADABLE_DAMAGED] = "L'image montée est illisible ou endommagée",
|
||||
/* English: Image cache full (%u entries), cannot track mount %s */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_TRACK] = "Le cache d’images est plein (%u entrées), impossible de suivre le montage %s",
|
||||
/* English: Image cache full (%u entries), rolling back mount */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_ROLLBACK] = "Le cache d’images est plein (%u entrées), annulation du montage",
|
||||
/* English: Install failed: %s (%s)\nTimed out after 5 minutes */
|
||||
[SM_L10N_INSTALL_FAILED_TIMEOUT] = "Échec de l'installation : %s (%s)\nDélai expiré après 5 minutes",
|
||||
/* English: Batch install queued (%d): */
|
||||
[SM_L10N_BATCH_INSTALL_QUEUED] = "Installation par lot mise en file (%d) :",
|
||||
/* English: \n... and %d more */
|
||||
[SM_L10N_BATCH_INSTALL_AND_MORE] = "\n... et %d de plus",
|
||||
/* English: Batch install failed for %d app(s).\ncode=0x%08X */
|
||||
[SM_L10N_BATCH_INSTALL_FAILED] = "Échec de l'installation par lot pour %d application(s).\ncode=0x%08X",
|
||||
/* English: %s crashed before KStuff was paused. KStuff is not the cause. */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF] = "%s a planté avant la mise en pause de KStuff. KStuff n’est pas en cause.",
|
||||
/* English: %s crashed before KStuff pause:\n%s */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF_FATAL] = "%s a planté avant la mise en pause de KStuff :\n%s",
|
||||
/* English: %s crashed after KStuff was paused. Automatic tuning skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_SKIPPED] = "%s a planté après la mise en pause de KStuff. Réglage automatique ignoré.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nAutotune skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_FATAL_SKIPPED] = "%s a planté après la mise en pause de KStuff :\n%s\nRéglage automatique ignoré.",
|
||||
/* English: Can't load module after KStuff pause */
|
||||
[SM_L10N_RTLD_AFTER_KSTUFF_FALLBACK] = "Impossible de charger le module après la pause KStuff",
|
||||
/* English: can't load module %.*s after KStuff pause */
|
||||
[SM_L10N_RTLD_MODULE_AFTER_KSTUFF] = "impossible de charger le module %.*s après la mise en pause de KStuff",
|
||||
/* English: %s: %s. Delay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_DELAY_INCREASED_RELAUNCH] = "%s : %s. Délai augmenté à %u s.\nRelancez le jeu.",
|
||||
/* English: Crash detected after KStuff pause: pause delay for %s increased to %us. Launch the game again. */
|
||||
[SM_L10N_CRASH_DELAY_INCREASED] = "Plantage détecté après la mise en pause de KStuff : le délai de pause de %s a été porté à %u s. Relancez le jeu.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nDelay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_INCREASED] = "%s a planté après la mise en pause de KStuff :\n%s\nDélai augmenté à %u s.\nRelancez le jeu.",
|
||||
/* English: Crash detected after KStuff pause: %s. Failed to update autotune delay. */
|
||||
[SM_L10N_CRASH_DELAY_UPDATE_FAILED] = "Plantage détecté après la mise en pause de KStuff : %s. Impossible de mettre à jour le délai du réglage automatique.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nFailed to update autotune delay. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_UPDATE_FAILED] = "%s a planté après la mise en pause de KStuff :\n%s\nImpossible de mettre à jour le délai du réglage automatique.",
|
||||
/* English: Missing/invalid param.json:\n%s */
|
||||
[SM_L10N_MISSING_PARAM] = "param.json manquant ou invalide :\n%s",
|
||||
/* English: Found %d new games. Processing... */
|
||||
[SM_L10N_FOUND_NEW_GAMES] = "%d nouveaux jeux trouvés. Traitement en cours...",
|
||||
/* English: Library synchronized.\nFound %d games. */
|
||||
[SM_L10N_LIBRARY_SYNCED] = "Bibliothèque synchronisée.\n%d jeux trouvés.",
|
||||
/* English: ShadowMount+: config reloaded. */
|
||||
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+ : configuration rechargée.",
|
||||
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
|
||||
[SM_L10N_DUPLICATE_IGNORED] = "Doublon %s ignoré :\n%s\nexistant : %s",
|
||||
/* English: AMPR emulator updated */
|
||||
[SM_L10N_AMPR_EMULATOR_UPDATED] = "Émulateur AMPR mis à jour",
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
static const char *const g_fr_fr[SM_L10N_COUNT] = {
|
||||
/* English: Go to Debug Settings */
|
||||
[SM_L10N_ACTION_DEBUG_SETTINGS] = "Accéder aux paramètres de débogage",
|
||||
/* English: ShadowMount+ v%s exFAT/UFS/PFS */
|
||||
[SM_L10N_STARTUP] = "ShadowMount+ v%s exFAT/UFS/PFS",
|
||||
/* English: ShadowMount+: recursive_scan=1 is deprecated, using scan_depth=2. */
|
||||
[SM_L10N_RECURSIVE_SCAN_DEPRECATED] = "ShadowMount+ : recursive_scan=1 est obsolète, scan_depth=2 sera utilisé.",
|
||||
/* English: ShadowMount+: scan depth %u enabled. */
|
||||
[SM_L10N_SCAN_DEPTH_ENABLED] = "ShadowMount+ : profondeur d’analyse %u activée.",
|
||||
/* English: Backport overlay failed: %s\n%s\n0x%08X */
|
||||
[SM_L10N_BACKPORT_OVERLAY_FAILED] = "Échec du montage de la surcouche de backport : %s\n%s\n0x%08X",
|
||||
/* English: Backport applied to game: %s */
|
||||
[SM_L10N_GAME_BACKPORTED] = "Backport appliqué au jeu : %s",
|
||||
/* English: Backport applied to game: %s\nEmulators updated */
|
||||
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Backport appliqué au jeu : %s\nÉmulateurs mis à jour",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Montage de l’image refusé :\n%s\nLa taille des clusters est trop petite (%llu < %u).\nValeur image_sector enregistrée : %u.\nRéessayez de monter l’image.",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "Montage de l’image refusé :\n%s\nLa taille des clusters est trop petite (%llu < %u).\nUtilisez des clusters plus grands dans l’image ou une taille de secteur plus petite dans la configuration.",
|
||||
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
|
||||
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "Échec de l'enregistrement : %s (%s)\nAppInstallTitleDir indisponible",
|
||||
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
|
||||
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "Échec de l’enregistrement : %s (%s)\nLe pont TitleDir n’est pas disponible",
|
||||
/* English: Register failed: %s (%s)\ncode=0x%08X */
|
||||
[SM_L10N_REGISTER_FAILED_CODE] = "Échec de l'enregistrement : %s (%s)\ncode=0x%08X",
|
||||
/* English: Installing: %s (%s)... */
|
||||
[SM_L10N_INSTALLING] = "Installation : %s (%s)...",
|
||||
/* English: KStuff active again */
|
||||
[SM_L10N_KSTUFF_ACTIVE] = "KStuff est de nouveau actif",
|
||||
/* English: KStuff paused while the game is running */
|
||||
[SM_L10N_KSTUFF_PAUSED] = "KStuff mis en pause pendant l'exécution du jeu",
|
||||
/* English: Installed: %s */
|
||||
[SM_L10N_INSTALLED] = "Installé : %s",
|
||||
/* English: Unknown */
|
||||
[SM_L10N_UNKNOWN] = "Inconnu",
|
||||
/* English: Image mount failed: 0x%08X (%s)\n%s */
|
||||
[SM_L10N_IMAGE_MOUNT_FAILED] = "Échec du montage de l'image : 0x%08X (%s)\n%s",
|
||||
/* English: Filesystem cluster size (%llu) is smaller than device sector size (%u) for %s */
|
||||
[SM_L10N_FS_CLUSTER_SMALL] = "La taille des clusters du système de fichiers (%llu) est inférieure à la taille des secteurs du périphérique (%u) pour %s",
|
||||
/* English: Mounted image is unreadable or damaged */
|
||||
[SM_L10N_IMAGE_UNREADABLE_DAMAGED] = "L'image montée est illisible ou endommagée",
|
||||
/* English: Image cache full (%u entries), cannot track mount %s */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_TRACK] = "Le cache d’images est plein (%u entrées), impossible de suivre le montage %s",
|
||||
/* English: Image cache full (%u entries), rolling back mount */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_ROLLBACK] = "Le cache d’images est plein (%u entrées), annulation du montage",
|
||||
/* English: Install failed: %s (%s)\nTimed out after 5 minutes */
|
||||
[SM_L10N_INSTALL_FAILED_TIMEOUT] = "Échec de l'installation : %s (%s)\nDélai expiré après 5 minutes",
|
||||
/* English: Batch install queued (%d): */
|
||||
[SM_L10N_BATCH_INSTALL_QUEUED] = "Installation par lot mise en file (%d) :",
|
||||
/* English: \n... and %d more */
|
||||
[SM_L10N_BATCH_INSTALL_AND_MORE] = "\n... et %d de plus",
|
||||
/* English: Batch install failed for %d app(s).\ncode=0x%08X */
|
||||
[SM_L10N_BATCH_INSTALL_FAILED] = "Échec de l'installation par lot pour %d application(s).\ncode=0x%08X",
|
||||
/* English: %s crashed before KStuff was paused. KStuff is not the cause. */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF] = "%s a planté avant la mise en pause de KStuff. KStuff n’est pas en cause.",
|
||||
/* English: %s crashed before KStuff pause:\n%s */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF_FATAL] = "%s a planté avant la mise en pause de KStuff :\n%s",
|
||||
/* English: %s crashed after KStuff was paused. Automatic tuning skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_SKIPPED] = "%s a planté après la mise en pause de KStuff. Réglage automatique ignoré.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nAutotune skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_FATAL_SKIPPED] = "%s a planté après la mise en pause de KStuff :\n%s\nRéglage automatique ignoré.",
|
||||
/* English: Can't load module after KStuff pause */
|
||||
[SM_L10N_RTLD_AFTER_KSTUFF_FALLBACK] = "Impossible de charger le module après la pause KStuff",
|
||||
/* English: can't load module %.*s after KStuff pause */
|
||||
[SM_L10N_RTLD_MODULE_AFTER_KSTUFF] = "impossible de charger le module %.*s après la mise en pause de KStuff",
|
||||
/* English: %s: %s. Delay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_DELAY_INCREASED_RELAUNCH] = "%s : %s. Délai augmenté à %u s.\nRelancez le jeu.",
|
||||
/* English: Crash detected after KStuff pause: pause delay for %s increased to %us. Launch the game again. */
|
||||
[SM_L10N_CRASH_DELAY_INCREASED] = "Plantage détecté après la mise en pause de KStuff : le délai de pause de %s a été porté à %u s. Relancez le jeu.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nDelay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_INCREASED] = "%s a planté après la mise en pause de KStuff :\n%s\nDélai augmenté à %u s.\nRelancez le jeu.",
|
||||
/* English: Crash detected after KStuff pause: %s. Failed to update autotune delay. */
|
||||
[SM_L10N_CRASH_DELAY_UPDATE_FAILED] = "Plantage détecté après la mise en pause de KStuff : %s. Impossible de mettre à jour le délai du réglage automatique.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nFailed to update autotune delay. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_UPDATE_FAILED] = "%s a planté après la mise en pause de KStuff :\n%s\nImpossible de mettre à jour le délai du réglage automatique.",
|
||||
/* English: Missing/invalid param.json:\n%s */
|
||||
[SM_L10N_MISSING_PARAM] = "param.json manquant ou invalide :\n%s",
|
||||
/* English: Found %d new games. Processing... */
|
||||
[SM_L10N_FOUND_NEW_GAMES] = "%d nouveaux jeux trouvés. Traitement en cours...",
|
||||
/* English: Library synchronized.\nFound %d games. */
|
||||
[SM_L10N_LIBRARY_SYNCED] = "Bibliothèque synchronisée.\n%d jeux trouvés.",
|
||||
/* English: ShadowMount+: config reloaded. */
|
||||
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+ : configuration rechargée.",
|
||||
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
|
||||
[SM_L10N_DUPLICATE_IGNORED] = "Doublon %s ignoré :\n%s\nexistant : %s",
|
||||
/* English: AMPR emulator updated */
|
||||
[SM_L10N_AMPR_EMULATOR_UPDATED] = "Émulateur AMPR mis à jour",
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
static const char *const g_hu_hu[SM_L10N_COUNT] = {
|
||||
/* English: Go to Debug Settings */
|
||||
[SM_L10N_ACTION_DEBUG_SETTINGS] = "Ugrás a hibakeresési beállításokhoz",
|
||||
/* English: ShadowMount+ v%s exFAT/UFS/PFS */
|
||||
[SM_L10N_STARTUP] = "ShadowMount+ v%s exFAT/UFS/PFS",
|
||||
/* English: ShadowMount+: recursive_scan=1 is deprecated, using scan_depth=2. */
|
||||
[SM_L10N_RECURSIVE_SCAN_DEPRECATED] = "ShadowMount+: a recursive_scan=1 elavult, scan_depth=2 lesz használva.",
|
||||
/* English: ShadowMount+: scan depth %u enabled. */
|
||||
[SM_L10N_SCAN_DEPTH_ENABLED] = "ShadowMount+: %u keresési mélység engedélyezve.",
|
||||
/* English: Backport overlay failed: %s\n%s\n0x%08X */
|
||||
[SM_L10N_BACKPORT_OVERLAY_FAILED] = "Backport overlay sikertelen: %s\n%s\n0x%08X",
|
||||
/* English: Backport applied to game: %s */
|
||||
[SM_L10N_GAME_BACKPORTED] = "Játék backportolva: %s",
|
||||
/* English: Backport applied to game: %s\nEmulators updated */
|
||||
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Játék backportolva: %s\nEmulátorok frissítve",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "A lemezkép csatolása elutasítva:\n%s\nA klaszter mérete túl kicsi (%llu < %u).\nAz image_sector felülírása mentve: %u.\nPróbáld újra csatolni.",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "A lemezkép csatolása elutasítva:\n%s\nA klaszter mérete túl kicsi (%llu < %u).\nHasználj nagyobb klasztert a lemezképben, vagy kisebb szektorméretet a beállításokban.",
|
||||
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
|
||||
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "Regisztráció sikertelen: %s (%s)\nAppInstallTitleDir nem elérhető",
|
||||
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
|
||||
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "A regisztráció sikertelen: %s (%s)\nA TitleDir-híd nem érhető el",
|
||||
/* English: Register failed: %s (%s)\ncode=0x%08X */
|
||||
[SM_L10N_REGISTER_FAILED_CODE] = "Regisztráció sikertelen: %s (%s)\nkód=0x%08X",
|
||||
/* English: Installing: %s (%s)... */
|
||||
[SM_L10N_INSTALLING] = "Telepítés: %s (%s)...",
|
||||
/* English: KStuff active again */
|
||||
[SM_L10N_KSTUFF_ACTIVE] = "A KStuff újra aktív",
|
||||
/* English: KStuff paused while the game is running */
|
||||
[SM_L10N_KSTUFF_PAUSED] = "A KStuff szünetel, amíg a játék fut",
|
||||
/* English: Installed: %s */
|
||||
[SM_L10N_INSTALLED] = "Telepítve: %s",
|
||||
/* English: Unknown */
|
||||
[SM_L10N_UNKNOWN] = "Ismeretlen",
|
||||
/* English: Image mount failed: 0x%08X (%s)\n%s */
|
||||
[SM_L10N_IMAGE_MOUNT_FAILED] = "A lemezkép csatolása sikertelen: 0x%08X (%s)\n%s",
|
||||
/* English: Filesystem cluster size (%llu) is smaller than device sector size (%u) for %s */
|
||||
[SM_L10N_FS_CLUSTER_SMALL] = "A fájlrendszer cluster mérete (%llu) kisebb, mint az eszköz szektormérete (%u): %s",
|
||||
/* English: Mounted image is unreadable or damaged */
|
||||
[SM_L10N_IMAGE_UNREADABLE_DAMAGED] = "A mountolt image olvashatatlan vagy sérült",
|
||||
/* English: Image cache full (%u entries), cannot track mount %s */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_TRACK] = "A lemezkép-gyorsítótár megtelt (%u bejegyzés), a(z) %s csatolás nem követhető",
|
||||
/* English: Image cache full (%u entries), rolling back mount */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_ROLLBACK] = "A lemezkép-gyorsítótár megtelt (%u bejegyzés), a csatolás visszavonása folyamatban",
|
||||
/* English: Install failed: %s (%s)\nTimed out after 5 minutes */
|
||||
[SM_L10N_INSTALL_FAILED_TIMEOUT] = "Telepítés sikertelen: %s (%s)\nIdőtúllépés 5 perc után",
|
||||
/* English: Batch install queued (%d): */
|
||||
[SM_L10N_BATCH_INSTALL_QUEUED] = "Kötegelt telepítés sorban (%d):",
|
||||
/* English: \n... and %d more */
|
||||
[SM_L10N_BATCH_INSTALL_AND_MORE] = "\n... és még %d",
|
||||
/* English: Batch install failed for %d app(s).\ncode=0x%08X */
|
||||
[SM_L10N_BATCH_INSTALL_FAILED] = "A kötegelt telepítés %d alkalmazásnál sikertelen.\nkód=0x%08X",
|
||||
/* English: %s crashed before KStuff was paused. KStuff is not the cause. */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF] = "A(z) %s összeomlott a KStuff szüneteltetése előtt. Nem a KStuff a hibás.",
|
||||
/* English: %s crashed before KStuff pause:\n%s */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF_FATAL] = "A(z) %s összeomlott a KStuff szüneteltetése előtt:\n%s",
|
||||
/* English: %s crashed after KStuff was paused. Automatic tuning skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_SKIPPED] = "A(z) %s összeomlott a KStuff szüneteltetése után. Az automatikus hangolás kimaradt.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nAutotune skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_FATAL_SKIPPED] = "A(z) %s összeomlott a KStuff szüneteltetése után:\n%s\nAz automatikus hangolás kihagyva.",
|
||||
/* English: Can't load module after KStuff pause */
|
||||
[SM_L10N_RTLD_AFTER_KSTUFF_FALLBACK] = "A modul nem tölthető be a KStuff szünet után",
|
||||
/* English: can't load module %.*s after KStuff pause */
|
||||
[SM_L10N_RTLD_MODULE_AFTER_KSTUFF] = "a(z) %.*s modul nem tölthető be a KStuff szüneteltetése után",
|
||||
/* English: %s: %s. Delay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_DELAY_INCREASED_RELAUNCH] = "%s: %s. Késleltetés %u s értékre növelve.\nIndítsd újra a játékot.",
|
||||
/* English: Crash detected after KStuff pause: pause delay for %s increased to %us. Launch the game again. */
|
||||
[SM_L10N_CRASH_DELAY_INCREASED] = "Összeomlás történt a KStuff szüneteltetése után: a(z) %s késleltetése %u másodpercre nőtt. Indítsd újra a játékot.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nDelay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_INCREASED] = "A(z) %s összeomlott a KStuff szüneteltetése után:\n%s\nA késleltetés %u másodpercre nőtt.\nIndítsd újra a játékot.",
|
||||
/* English: Crash detected after KStuff pause: %s. Failed to update autotune delay. */
|
||||
[SM_L10N_CRASH_DELAY_UPDATE_FAILED] = "Összeomlás történt a KStuff szüneteltetése után: %s. Az automatikus hangolás késleltetése nem frissíthető.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nFailed to update autotune delay. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_UPDATE_FAILED] = "A(z) %s összeomlott a KStuff szüneteltetése után:\n%s\nAz automatikus hangolás késleltetése nem frissíthető.",
|
||||
/* English: Missing/invalid param.json:\n%s */
|
||||
[SM_L10N_MISSING_PARAM] = "Hiányzó vagy érvénytelen param.json:\n%s",
|
||||
/* English: Found %d new games. Processing... */
|
||||
[SM_L10N_FOUND_NEW_GAMES] = "%d új játék található. Feldolgozás...",
|
||||
/* English: Library synchronized.\nFound %d games. */
|
||||
[SM_L10N_LIBRARY_SYNCED] = "Könyvtár szinkronizálva.\n%d játék található.",
|
||||
/* English: ShadowMount+: config reloaded. */
|
||||
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: a beállítások újratöltve.",
|
||||
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
|
||||
[SM_L10N_DUPLICATE_IGNORED] = "Duplikált %s kihagyva:\n%s\nlétező: %s",
|
||||
/* English: AMPR emulator updated */
|
||||
[SM_L10N_AMPR_EMULATOR_UPDATED] = "Az AMPR-emulátor frissítve",
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
static const char *const g_id_id[SM_L10N_COUNT] = {
|
||||
/* English: Go to Debug Settings */
|
||||
[SM_L10N_ACTION_DEBUG_SETTINGS] = "Buka pengaturan debug",
|
||||
/* English: ShadowMount+ v%s exFAT/UFS/PFS */
|
||||
[SM_L10N_STARTUP] = "ShadowMount+ v%s exFAT/UFS/PFS",
|
||||
/* English: ShadowMount+: recursive_scan=1 is deprecated, using scan_depth=2. */
|
||||
[SM_L10N_RECURSIVE_SCAN_DEPRECATED] = "ShadowMount+: recursive_scan=1 sudah usang, memakai scan_depth=2.",
|
||||
/* English: ShadowMount+: scan depth %u enabled. */
|
||||
[SM_L10N_SCAN_DEPTH_ENABLED] = "ShadowMount+: kedalaman pemindaian %u aktif.",
|
||||
/* English: Backport overlay failed: %s\n%s\n0x%08X */
|
||||
[SM_L10N_BACKPORT_OVERLAY_FAILED] = "Backport overlay gagal: %s\n%s\n0x%08X",
|
||||
/* English: Backport applied to game: %s */
|
||||
[SM_L10N_GAME_BACKPORTED] = "Backport game diterapkan: %s",
|
||||
/* English: Backport applied to game: %s\nEmulators updated */
|
||||
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Backport game diterapkan: %s\nSemua emulator diperbarui",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Pemasangan image ditolak:\n%s\nUkuran cluster terlalu kecil (%llu < %u).\nNilai image_sector disimpan: %u.\nCoba pasang lagi.",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "Pemasangan image ditolak:\n%s\nUkuran cluster terlalu kecil (%llu < %u).\nGunakan cluster yang lebih besar pada image atau ukuran sektor yang lebih kecil pada konfigurasi.",
|
||||
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
|
||||
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "Registrasi gagal: %s (%s)\nAppInstallTitleDir tidak tersedia",
|
||||
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
|
||||
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "Registrasi gagal: %s (%s)\nBridge TitleDir tidak tersedia",
|
||||
/* English: Register failed: %s (%s)\ncode=0x%08X */
|
||||
[SM_L10N_REGISTER_FAILED_CODE] = "Registrasi gagal: %s (%s)\nkode=0x%08X",
|
||||
/* English: Installing: %s (%s)... */
|
||||
[SM_L10N_INSTALLING] = "Menginstal: %s (%s)...",
|
||||
/* English: KStuff active again */
|
||||
[SM_L10N_KSTUFF_ACTIVE] = "KStuff aktif lagi",
|
||||
/* English: KStuff paused while the game is running */
|
||||
[SM_L10N_KSTUFF_PAUSED] = "KStuff dijeda saat game berjalan",
|
||||
/* English: Installed: %s */
|
||||
[SM_L10N_INSTALLED] = "Terinstal: %s",
|
||||
/* English: Unknown */
|
||||
[SM_L10N_UNKNOWN] = "Tidak diketahui",
|
||||
/* English: Image mount failed: 0x%08X (%s)\n%s */
|
||||
[SM_L10N_IMAGE_MOUNT_FAILED] = "Pemasangan image gagal: 0x%08X (%s)\n%s",
|
||||
/* English: Filesystem cluster size (%llu) is smaller than device sector size (%u) for %s */
|
||||
[SM_L10N_FS_CLUSTER_SMALL] = "Ukuran cluster filesystem (%llu) lebih kecil dari ukuran sector perangkat (%u) untuk %s",
|
||||
/* English: Mounted image is unreadable or damaged */
|
||||
[SM_L10N_IMAGE_UNREADABLE_DAMAGED] = "Image yang dimount tidak terbaca atau rusak",
|
||||
/* English: Image cache full (%u entries), cannot track mount %s */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_TRACK] = "Cache image penuh (%u entri), pemasangan %s tidak dapat dilacak",
|
||||
/* English: Image cache full (%u entries), rolling back mount */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_ROLLBACK] = "Cache image penuh (%u entri), pemasangan dibatalkan",
|
||||
/* English: Install failed: %s (%s)\nTimed out after 5 minutes */
|
||||
[SM_L10N_INSTALL_FAILED_TIMEOUT] = "Instalasi gagal: %s (%s)\nWaktu habis setelah 5 menit",
|
||||
/* English: Batch install queued (%d): */
|
||||
[SM_L10N_BATCH_INSTALL_QUEUED] = "Instalasi batch masuk antrean (%d):",
|
||||
/* English: \n... and %d more */
|
||||
[SM_L10N_BATCH_INSTALL_AND_MORE] = "\n... dan %d lagi",
|
||||
/* English: Batch install failed for %d app(s).\ncode=0x%08X */
|
||||
[SM_L10N_BATCH_INSTALL_FAILED] = "Instalasi batch gagal untuk %d aplikasi.\nkode=0x%08X",
|
||||
/* English: %s crashed before KStuff was paused. KStuff is not the cause. */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF] = "Aplikasi %s berhenti mendadak sebelum KStuff dijeda. KStuff bukan penyebabnya.",
|
||||
/* English: %s crashed before KStuff pause:\n%s */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF_FATAL] = "%s berhenti mendadak sebelum KStuff dijeda:\n%s",
|
||||
/* English: %s crashed after KStuff was paused. Automatic tuning skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_SKIPPED] = "Aplikasi %s berhenti mendadak setelah KStuff dijeda. Penyetelan otomatis dilewati.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nAutotune skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_FATAL_SKIPPED] = "%s berhenti mendadak setelah KStuff dijeda:\n%s\nPenyetelan otomatis dilewati.",
|
||||
/* English: Can't load module after KStuff pause */
|
||||
[SM_L10N_RTLD_AFTER_KSTUFF_FALLBACK] = "Tidak dapat memuat module setelah KStuff dijeda",
|
||||
/* English: can't load module %.*s after KStuff pause */
|
||||
[SM_L10N_RTLD_MODULE_AFTER_KSTUFF] = "tidak dapat memuat modul %.*s setelah KStuff dijeda",
|
||||
/* English: %s: %s. Delay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_DELAY_INCREASED_RELAUNCH] = "%s: %s. Penundaan ditingkatkan menjadi %u dtk.\nJalankan game lagi.",
|
||||
/* English: Crash detected after KStuff pause: pause delay for %s increased to %us. Launch the game again. */
|
||||
[SM_L10N_CRASH_DELAY_INCREASED] = "Aplikasi berhenti mendadak setelah KStuff dijeda: penundaan untuk %s ditingkatkan menjadi %u dtk. Jalankan game lagi.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nDelay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_INCREASED] = "%s berhenti mendadak setelah KStuff dijeda:\n%s\nPenundaan ditingkatkan menjadi %u dtk.\nJalankan game lagi.",
|
||||
/* English: Crash detected after KStuff pause: %s. Failed to update autotune delay. */
|
||||
[SM_L10N_CRASH_DELAY_UPDATE_FAILED] = "Aplikasi berhenti mendadak setelah KStuff dijeda: %s. Gagal memperbarui penundaan penyetelan otomatis.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nFailed to update autotune delay. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_UPDATE_FAILED] = "%s berhenti mendadak setelah KStuff dijeda:\n%s\nGagal memperbarui penundaan penyetelan otomatis.",
|
||||
/* English: Missing/invalid param.json:\n%s */
|
||||
[SM_L10N_MISSING_PARAM] = "param.json hilang atau tidak valid:\n%s",
|
||||
/* English: Found %d new games. Processing... */
|
||||
[SM_L10N_FOUND_NEW_GAMES] = "Menemukan %d game baru. Memproses...",
|
||||
/* English: Library synchronized.\nFound %d games. */
|
||||
[SM_L10N_LIBRARY_SYNCED] = "Pustaka disinkronkan.\nMenemukan %d game.",
|
||||
/* English: ShadowMount+: config reloaded. */
|
||||
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: konfigurasi dimuat ulang.",
|
||||
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
|
||||
[SM_L10N_DUPLICATE_IGNORED] = "Duplikat %s diabaikan:\n%s\nyang ada: %s",
|
||||
/* English: AMPR emulator updated */
|
||||
[SM_L10N_AMPR_EMULATOR_UPDATED] = "Emulator AMPR diperbarui",
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
static const char *const g_it_it[SM_L10N_COUNT] = {
|
||||
/* English: Go to Debug Settings */
|
||||
[SM_L10N_ACTION_DEBUG_SETTINGS] = "Vai alle impostazioni di debug",
|
||||
/* English: ShadowMount+ v%s exFAT/UFS/PFS */
|
||||
[SM_L10N_STARTUP] = "ShadowMount+ v%s exFAT/UFS/PFS",
|
||||
/* English: ShadowMount+: recursive_scan=1 is deprecated, using scan_depth=2. */
|
||||
[SM_L10N_RECURSIVE_SCAN_DEPRECATED] = "ShadowMount+: recursive_scan=1 è deprecato; verrà usato scan_depth=2.",
|
||||
/* English: ShadowMount+: scan depth %u enabled. */
|
||||
[SM_L10N_SCAN_DEPTH_ENABLED] = "ShadowMount+: profondità di scansione %u abilitata.",
|
||||
/* English: Backport overlay failed: %s\n%s\n0x%08X */
|
||||
[SM_L10N_BACKPORT_OVERLAY_FAILED] = "Overlay backport non riuscito: %s\n%s\n0x%08X",
|
||||
/* English: Backport applied to game: %s */
|
||||
[SM_L10N_GAME_BACKPORTED] = "Backport applicato al gioco: %s",
|
||||
/* English: Backport applied to game: %s\nEmulators updated */
|
||||
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Backport applicato al gioco: %s\nEmulatori aggiornati",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Montaggio immagine rifiutato:\n%s\nLa dimensione cluster è troppo piccola (%llu < %u).\nOverride image_sector salvato: %u.\nProva a montare di nuovo.",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "Montaggio dell’immagine rifiutato:\n%s\nLa dimensione del cluster è troppo piccola (%llu < %u).\nUsa un cluster più grande nell’immagine o una dimensione del settore più piccola nella configurazione.",
|
||||
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
|
||||
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "Registrazione non riuscita: %s (%s)\nAppInstallTitleDir non disponibile",
|
||||
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
|
||||
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "Registrazione non riuscita: %s (%s)\nBridge TitleDir non disponibile",
|
||||
/* English: Register failed: %s (%s)\ncode=0x%08X */
|
||||
[SM_L10N_REGISTER_FAILED_CODE] = "Registrazione non riuscita: %s (%s)\ncodice=0x%08X",
|
||||
/* English: Installing: %s (%s)... */
|
||||
[SM_L10N_INSTALLING] = "Installazione: %s (%s)...",
|
||||
/* English: KStuff active again */
|
||||
[SM_L10N_KSTUFF_ACTIVE] = "KStuff di nuovo attivo",
|
||||
/* English: KStuff paused while the game is running */
|
||||
[SM_L10N_KSTUFF_PAUSED] = "KStuff in pausa mentre il gioco è in esecuzione",
|
||||
/* English: Installed: %s */
|
||||
[SM_L10N_INSTALLED] = "Installato: %s",
|
||||
/* English: Unknown */
|
||||
[SM_L10N_UNKNOWN] = "Sconosciuto",
|
||||
/* English: Image mount failed: 0x%08X (%s)\n%s */
|
||||
[SM_L10N_IMAGE_MOUNT_FAILED] = "Montaggio immagine non riuscito: 0x%08X (%s)\n%s",
|
||||
/* English: Filesystem cluster size (%llu) is smaller than device sector size (%u) for %s */
|
||||
[SM_L10N_FS_CLUSTER_SMALL] = "La dimensione cluster del filesystem (%llu) è minore della dimensione settore del dispositivo (%u) per %s",
|
||||
/* English: Mounted image is unreadable or damaged */
|
||||
[SM_L10N_IMAGE_UNREADABLE_DAMAGED] = "L'immagine montata è illeggibile o danneggiata",
|
||||
/* English: Image cache full (%u entries), cannot track mount %s */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_TRACK] = "La cache delle immagini è piena (%u voci), impossibile monitorare il montaggio %s",
|
||||
/* English: Image cache full (%u entries), rolling back mount */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_ROLLBACK] = "La cache delle immagini è piena (%u voci), annullamento del montaggio",
|
||||
/* English: Install failed: %s (%s)\nTimed out after 5 minutes */
|
||||
[SM_L10N_INSTALL_FAILED_TIMEOUT] = "Installazione non riuscita: %s (%s)\nTimeout dopo 5 minuti",
|
||||
/* English: Batch install queued (%d): */
|
||||
[SM_L10N_BATCH_INSTALL_QUEUED] = "Installazione batch in coda (%d):",
|
||||
/* English: \n... and %d more */
|
||||
[SM_L10N_BATCH_INSTALL_AND_MORE] = "\n... e altri %d",
|
||||
/* English: Batch install failed for %d app(s).\ncode=0x%08X */
|
||||
[SM_L10N_BATCH_INSTALL_FAILED] = "Installazione batch non riuscita per %d app.\ncodice=0x%08X",
|
||||
/* English: %s crashed before KStuff was paused. KStuff is not the cause. */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF] = "%s si è arrestata prima della pausa di KStuff. KStuff non è responsabile.",
|
||||
/* English: %s crashed before KStuff pause:\n%s */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF_FATAL] = "%s si è arrestato prima della pausa di KStuff:\n%s",
|
||||
/* English: %s crashed after KStuff was paused. Automatic tuning skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_SKIPPED] = "%s si è arrestata dopo la pausa di KStuff. Regolazione automatica ignorata.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nAutotune skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_FATAL_SKIPPED] = "%s si è arrestato dopo la pausa di KStuff:\n%s\nRegolazione automatica ignorata.",
|
||||
/* English: Can't load module after KStuff pause */
|
||||
[SM_L10N_RTLD_AFTER_KSTUFF_FALLBACK] = "Impossibile caricare il modulo dopo la pausa KStuff",
|
||||
/* English: can't load module %.*s after KStuff pause */
|
||||
[SM_L10N_RTLD_MODULE_AFTER_KSTUFF] = "impossibile caricare il modulo %.*s dopo la pausa di KStuff",
|
||||
/* English: %s: %s. Delay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_DELAY_INCREASED_RELAUNCH] = "%s: %s. Ritardo aumentato a %u s.\nAvvia di nuovo il gioco.",
|
||||
/* English: Crash detected after KStuff pause: pause delay for %s increased to %us. Launch the game again. */
|
||||
[SM_L10N_CRASH_DELAY_INCREASED] = "Arresto anomalo rilevato dopo la pausa di KStuff: il ritardo di pausa per %s è stato aumentato a %u s. Avvia di nuovo il gioco.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nDelay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_INCREASED] = "%s si è arrestato dopo la pausa di KStuff:\n%s\nRitardo aumentato a %u s.\nAvvia di nuovo il gioco.",
|
||||
/* English: Crash detected after KStuff pause: %s. Failed to update autotune delay. */
|
||||
[SM_L10N_CRASH_DELAY_UPDATE_FAILED] = "Arresto anomalo rilevato dopo la pausa di KStuff: %s. Impossibile aggiornare il ritardo della regolazione automatica.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nFailed to update autotune delay. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_UPDATE_FAILED] = "%s si è arrestato dopo la pausa di KStuff:\n%s\nImpossibile aggiornare il ritardo della regolazione automatica.",
|
||||
/* English: Missing/invalid param.json:\n%s */
|
||||
[SM_L10N_MISSING_PARAM] = "param.json mancante o non valido:\n%s",
|
||||
/* English: Found %d new games. Processing... */
|
||||
[SM_L10N_FOUND_NEW_GAMES] = "Trovati %d nuovi giochi. Elaborazione in corso...",
|
||||
/* English: Library synchronized.\nFound %d games. */
|
||||
[SM_L10N_LIBRARY_SYNCED] = "Libreria sincronizzata.\nTrovati %d giochi.",
|
||||
/* English: ShadowMount+: config reloaded. */
|
||||
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: configurazione ricaricata.",
|
||||
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
|
||||
[SM_L10N_DUPLICATE_IGNORED] = "Duplicato %s ignorato:\n%s\nesistente: %s",
|
||||
/* English: AMPR emulator updated */
|
||||
[SM_L10N_AMPR_EMULATOR_UPDATED] = "Emulatore AMPR aggiornato",
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
static const char *const g_ja_jp[SM_L10N_COUNT] = {
|
||||
/* English: Go to Debug Settings */
|
||||
[SM_L10N_ACTION_DEBUG_SETTINGS] = "デバッグ設定へ移動",
|
||||
/* English: ShadowMount+ v%s exFAT/UFS/PFS */
|
||||
[SM_L10N_STARTUP] = "ShadowMount+ v%s exFAT/UFS/PFS",
|
||||
/* English: ShadowMount+: recursive_scan=1 is deprecated, using scan_depth=2. */
|
||||
[SM_L10N_RECURSIVE_SCAN_DEPRECATED] = "ShadowMount+: recursive_scan=1 は非推奨です。scan_depth=2 を使用します。",
|
||||
/* English: ShadowMount+: scan depth %u enabled. */
|
||||
[SM_L10N_SCAN_DEPTH_ENABLED] = "ShadowMount+: スキャン深度 %u が有効です。",
|
||||
/* English: Backport overlay failed: %s\n%s\n0x%08X */
|
||||
[SM_L10N_BACKPORT_OVERLAY_FAILED] = "バックポートオーバーレイのマウントに失敗しました: %s\n%s\n0x%08X",
|
||||
/* English: Backport applied to game: %s */
|
||||
[SM_L10N_GAME_BACKPORTED] = "ゲームにバックポートを適用しました: %s",
|
||||
/* English: Backport applied to game: %s\nEmulators updated */
|
||||
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "ゲームにバックポートを適用しました: %s\n複数のエミュレーターを更新しました",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "イメージのマウントが拒否されました:\n%s\nクラスタサイズが小さすぎます (%llu < %u)。\nimage_sector override を保存しました: %u。\nもう一度マウントしてください。",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "イメージのマウントが拒否されました:\n%s\nクラスタサイズが小さすぎます (%llu < %u)。\nイメージのクラスタサイズを大きくするか、設定のセクタサイズを小さくしてください。",
|
||||
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
|
||||
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "登録に失敗しました: %s (%s)\nAppInstallTitleDir が利用できません",
|
||||
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
|
||||
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "登録に失敗しました: %s (%s)\nTitleDir ブリッジを利用できません",
|
||||
/* English: Register failed: %s (%s)\ncode=0x%08X */
|
||||
[SM_L10N_REGISTER_FAILED_CODE] = "登録に失敗しました: %s (%s)\nコード=0x%08X",
|
||||
/* English: Installing: %s (%s)... */
|
||||
[SM_L10N_INSTALLING] = "インストール中: %s (%s)...",
|
||||
/* English: KStuff active again */
|
||||
[SM_L10N_KSTUFF_ACTIVE] = "KStuff が再び有効です",
|
||||
/* English: KStuff paused while the game is running */
|
||||
[SM_L10N_KSTUFF_PAUSED] = "ゲーム実行中のため KStuff を一時停止しました",
|
||||
/* English: Installed: %s */
|
||||
[SM_L10N_INSTALLED] = "インストール済み: %s",
|
||||
/* English: Unknown */
|
||||
[SM_L10N_UNKNOWN] = "不明",
|
||||
/* English: Image mount failed: 0x%08X (%s)\n%s */
|
||||
[SM_L10N_IMAGE_MOUNT_FAILED] = "イメージのマウントに失敗しました: 0x%08X (%s)\n%s",
|
||||
/* English: Filesystem cluster size (%llu) is smaller than device sector size (%u) for %s */
|
||||
[SM_L10N_FS_CLUSTER_SMALL] = "ファイルシステムのクラスタサイズ (%llu) がデバイスセクタサイズ (%u) より小さいです: %s",
|
||||
/* English: Mounted image is unreadable or damaged */
|
||||
[SM_L10N_IMAGE_UNREADABLE_DAMAGED] = "マウント済みイメージを読み取れないか破損しています",
|
||||
/* English: Image cache full (%u entries), cannot track mount %s */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_TRACK] = "イメージキャッシュが満杯です (%u 件)。マウント %s を追跡できません",
|
||||
/* English: Image cache full (%u entries), rolling back mount */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_ROLLBACK] = "イメージキャッシュが満杯です (%u 件)。マウントを元に戻します",
|
||||
/* English: Install failed: %s (%s)\nTimed out after 5 minutes */
|
||||
[SM_L10N_INSTALL_FAILED_TIMEOUT] = "インストールに失敗しました: %s (%s)\n5 分後にタイムアウトしました",
|
||||
/* English: Batch install queued (%d): */
|
||||
[SM_L10N_BATCH_INSTALL_QUEUED] = "一括インストールをキューに追加しました (%d):",
|
||||
/* English: \n... and %d more */
|
||||
[SM_L10N_BATCH_INSTALL_AND_MORE] = "\n... 他 %d 件",
|
||||
/* English: Batch install failed for %d app(s).\ncode=0x%08X */
|
||||
[SM_L10N_BATCH_INSTALL_FAILED] = "%d 個のアプリの一括インストールに失敗しました。\nコード=0x%08X",
|
||||
/* English: %s crashed before KStuff was paused. KStuff is not the cause. */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF] = "%s は KStuff の一時停止前にクラッシュしました。KStuff が原因ではありません。",
|
||||
/* English: %s crashed before KStuff pause:\n%s */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF_FATAL] = "%s は KStuff の一時停止前にクラッシュしました:\n%s",
|
||||
/* English: %s crashed after KStuff was paused. Automatic tuning skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_SKIPPED] = "%s は KStuff の一時停止後にクラッシュしました。自動調整をスキップしました。",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nAutotune skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_FATAL_SKIPPED] = "%s は KStuff の一時停止後にクラッシュしました:\n%s\n自動調整をスキップしました。",
|
||||
/* English: Can't load module after KStuff pause */
|
||||
[SM_L10N_RTLD_AFTER_KSTUFF_FALLBACK] = "KStuff 一時停止後にモジュールを読み込めません",
|
||||
/* English: can't load module %.*s after KStuff pause */
|
||||
[SM_L10N_RTLD_MODULE_AFTER_KSTUFF] = "KStuff の一時停止後にモジュール %.*s を読み込めません",
|
||||
/* English: %s: %s. Delay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_DELAY_INCREASED_RELAUNCH] = "%s: %s。遅延を %u 秒に増やしました。\nゲームをもう一度起動してください。",
|
||||
/* English: Crash detected after KStuff pause: pause delay for %s increased to %us. Launch the game again. */
|
||||
[SM_L10N_CRASH_DELAY_INCREASED] = "KStuff の一時停止後にクラッシュを検出しました: %s の遅延を %u 秒に増やしました。ゲームをもう一度起動してください。",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nDelay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_INCREASED] = "%s は KStuff の一時停止後にクラッシュしました:\n%s\n遅延を %u 秒に増やしました。\nゲームをもう一度起動してください。",
|
||||
/* English: Crash detected after KStuff pause: %s. Failed to update autotune delay. */
|
||||
[SM_L10N_CRASH_DELAY_UPDATE_FAILED] = "KStuff の一時停止後にクラッシュを検出しました: %s。自動調整の遅延を更新できませんでした。",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nFailed to update autotune delay. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_UPDATE_FAILED] = "%s は KStuff の一時停止後にクラッシュしました:\n%s\n自動調整の遅延を更新できませんでした。",
|
||||
/* English: Missing/invalid param.json:\n%s */
|
||||
[SM_L10N_MISSING_PARAM] = "param.json が見つからないか無効です:\n%s",
|
||||
/* English: Found %d new games. Processing... */
|
||||
[SM_L10N_FOUND_NEW_GAMES] = "%d 本の新しいゲームを検出しました。処理中...",
|
||||
/* English: Library synchronized.\nFound %d games. */
|
||||
[SM_L10N_LIBRARY_SYNCED] = "ライブラリを同期しました。\n%d 本のゲームを検出しました。",
|
||||
/* English: ShadowMount+: config reloaded. */
|
||||
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: 設定を再読み込みしました。",
|
||||
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
|
||||
[SM_L10N_DUPLICATE_IGNORED] = "重複 %s を無視しました:\n%s\n既存: %s",
|
||||
/* English: AMPR emulator updated */
|
||||
[SM_L10N_AMPR_EMULATOR_UPDATED] = "AMPRエミュレーターを更新しました",
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
static const char *const g_ko_kr[SM_L10N_COUNT] = {
|
||||
/* English: Go to Debug Settings */
|
||||
[SM_L10N_ACTION_DEBUG_SETTINGS] = "디버그 설정으로 이동",
|
||||
/* English: ShadowMount+ v%s exFAT/UFS/PFS */
|
||||
[SM_L10N_STARTUP] = "ShadowMount+ v%s exFAT/UFS/PFS",
|
||||
/* English: ShadowMount+: recursive_scan=1 is deprecated, using scan_depth=2. */
|
||||
[SM_L10N_RECURSIVE_SCAN_DEPRECATED] = "ShadowMount+: recursive_scan=1은 더 이상 권장되지 않으며 scan_depth=2를 사용합니다.",
|
||||
/* English: ShadowMount+: scan depth %u enabled. */
|
||||
[SM_L10N_SCAN_DEPTH_ENABLED] = "ShadowMount+: 스캔 깊이 %u 활성화됨.",
|
||||
/* English: Backport overlay failed: %s\n%s\n0x%08X */
|
||||
[SM_L10N_BACKPORT_OVERLAY_FAILED] = "백포트 오버레이 마운트 실패: %s\n%s\n0x%08X",
|
||||
/* English: Backport applied to game: %s */
|
||||
[SM_L10N_GAME_BACKPORTED] = "게임에 백포트 적용됨: %s",
|
||||
/* English: Backport applied to game: %s\nEmulators updated */
|
||||
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "게임에 백포트 적용됨: %s\n에뮬레이터들이 업데이트됨",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "이미지 마운트 거부됨:\n%s\n클러스터 크기가 너무 작습니다 (%llu < %u).\nimage_sector override 저장됨: %u.\n다시 마운트해 보세요.",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "이미지 마운트가 거부되었습니다:\n%s\n클러스터 크기가 너무 작습니다(%llu < %u).\n이미지의 클러스터 크기를 늘리거나 설정의 섹터 크기를 줄이세요.",
|
||||
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
|
||||
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "등록 실패: %s (%s)\nAppInstallTitleDir 사용 불가",
|
||||
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
|
||||
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "등록 실패: %s (%s)\nTitleDir 브리지를 사용할 수 없음",
|
||||
/* English: Register failed: %s (%s)\ncode=0x%08X */
|
||||
[SM_L10N_REGISTER_FAILED_CODE] = "등록 실패: %s (%s)\n코드=0x%08X",
|
||||
/* English: Installing: %s (%s)... */
|
||||
[SM_L10N_INSTALLING] = "설치 중: %s (%s)...",
|
||||
/* English: KStuff active again */
|
||||
[SM_L10N_KSTUFF_ACTIVE] = "KStuff 다시 활성화됨",
|
||||
/* English: KStuff paused while the game is running */
|
||||
[SM_L10N_KSTUFF_PAUSED] = "게임 실행 중 KStuff 일시 중지됨",
|
||||
/* English: Installed: %s */
|
||||
[SM_L10N_INSTALLED] = "설치됨: %s",
|
||||
/* English: Unknown */
|
||||
[SM_L10N_UNKNOWN] = "알 수 없음",
|
||||
/* English: Image mount failed: 0x%08X (%s)\n%s */
|
||||
[SM_L10N_IMAGE_MOUNT_FAILED] = "이미지 마운트 실패: 0x%08X (%s)\n%s",
|
||||
/* English: Filesystem cluster size (%llu) is smaller than device sector size (%u) for %s */
|
||||
[SM_L10N_FS_CLUSTER_SMALL] = "파일 시스템 클러스터 크기(%llu)가 장치 섹터 크기(%u)보다 작습니다: %s",
|
||||
/* English: Mounted image is unreadable or damaged */
|
||||
[SM_L10N_IMAGE_UNREADABLE_DAMAGED] = "마운트된 이미지를 읽을 수 없거나 손상되었습니다",
|
||||
/* English: Image cache full (%u entries), cannot track mount %s */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_TRACK] = "이미지 캐시가 가득 찼습니다(%u개). 마운트 %s을(를) 추적할 수 없습니다",
|
||||
/* English: Image cache full (%u entries), rolling back mount */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_ROLLBACK] = "이미지 캐시가 가득 찼습니다(%u개). 마운트를 되돌립니다",
|
||||
/* English: Install failed: %s (%s)\nTimed out after 5 minutes */
|
||||
[SM_L10N_INSTALL_FAILED_TIMEOUT] = "설치 실패: %s (%s)\n5분 후 시간 초과",
|
||||
/* English: Batch install queued (%d): */
|
||||
[SM_L10N_BATCH_INSTALL_QUEUED] = "일괄 설치 대기열 추가됨 (%d):",
|
||||
/* English: \n... and %d more */
|
||||
[SM_L10N_BATCH_INSTALL_AND_MORE] = "\n... 외 %d개",
|
||||
/* English: Batch install failed for %d app(s).\ncode=0x%08X */
|
||||
[SM_L10N_BATCH_INSTALL_FAILED] = "%d개 앱 일괄 설치 실패.\n코드=0x%08X",
|
||||
/* English: %s crashed before KStuff was paused. KStuff is not the cause. */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF] = "%s이(가) KStuff 일시 중지 전에 충돌했습니다. KStuff가 원인이 아닙니다.",
|
||||
/* English: %s crashed before KStuff pause:\n%s */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF_FATAL] = "%s이(가) KStuff 일시 중지 전에 충돌했습니다:\n%s",
|
||||
/* English: %s crashed after KStuff was paused. Automatic tuning skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_SKIPPED] = "%s이(가) KStuff 일시 중지 후에 충돌했습니다. 자동 조정을 건너뛰었습니다.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nAutotune skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_FATAL_SKIPPED] = "%s이(가) KStuff 일시 중지 후에 충돌했습니다:\n%s\n자동 조정을 건너뛰었습니다.",
|
||||
/* English: Can't load module after KStuff pause */
|
||||
[SM_L10N_RTLD_AFTER_KSTUFF_FALLBACK] = "KStuff 일시 중지 후 모듈을 로드할 수 없습니다",
|
||||
/* English: can't load module %.*s after KStuff pause */
|
||||
[SM_L10N_RTLD_MODULE_AFTER_KSTUFF] = "KStuff 일시 중지 후 모듈 %.*s을(를) 로드할 수 없음",
|
||||
/* English: %s: %s. Delay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_DELAY_INCREASED_RELAUNCH] = "%s: %s. 지연이 %u초로 증가했습니다.\n게임을 다시 실행하세요.",
|
||||
/* English: Crash detected after KStuff pause: pause delay for %s increased to %us. Launch the game again. */
|
||||
[SM_L10N_CRASH_DELAY_INCREASED] = "KStuff 일시 중지 후 충돌을 감지했습니다: %s의 지연을 %u초로 늘렸습니다. 게임을 다시 실행하세요.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nDelay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_INCREASED] = "%s이(가) KStuff 일시 중지 후에 충돌했습니다:\n%s\n지연을 %u초로 늘렸습니다.\n게임을 다시 실행하세요.",
|
||||
/* English: Crash detected after KStuff pause: %s. Failed to update autotune delay. */
|
||||
[SM_L10N_CRASH_DELAY_UPDATE_FAILED] = "KStuff 일시 중지 후 충돌을 감지했습니다: %s. 자동 조정 지연을 업데이트하지 못했습니다.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nFailed to update autotune delay. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_UPDATE_FAILED] = "%s이(가) KStuff 일시 중지 후에 충돌했습니다:\n%s\n자동 조정 지연을 업데이트하지 못했습니다.",
|
||||
/* English: Missing/invalid param.json:\n%s */
|
||||
[SM_L10N_MISSING_PARAM] = "param.json 누락 또는 잘못됨:\n%s",
|
||||
/* English: Found %d new games. Processing... */
|
||||
[SM_L10N_FOUND_NEW_GAMES] = "새 게임 %d개를 찾았습니다. 처리 중...",
|
||||
/* English: Library synchronized.\nFound %d games. */
|
||||
[SM_L10N_LIBRARY_SYNCED] = "라이브러리를 동기화했습니다.\n게임 %d개를 찾았습니다.",
|
||||
/* English: ShadowMount+: config reloaded. */
|
||||
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: 설정을 다시 불러왔습니다.",
|
||||
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
|
||||
[SM_L10N_DUPLICATE_IGNORED] = "중복 %s 무시됨:\n%s\n기존: %s",
|
||||
/* English: AMPR emulator updated */
|
||||
[SM_L10N_AMPR_EMULATOR_UPDATED] = "AMPR 에뮬레이터가 업데이트되었습니다",
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
static const char *const g_nl_nl[SM_L10N_COUNT] = {
|
||||
/* English: Go to Debug Settings */
|
||||
[SM_L10N_ACTION_DEBUG_SETTINGS] = "Ga naar debug-instellingen",
|
||||
/* English: ShadowMount+ v%s exFAT/UFS/PFS */
|
||||
[SM_L10N_STARTUP] = "ShadowMount+ v%s exFAT/UFS/PFS",
|
||||
/* English: ShadowMount+: recursive_scan=1 is deprecated, using scan_depth=2. */
|
||||
[SM_L10N_RECURSIVE_SCAN_DEPRECATED] = "ShadowMount+: recursive_scan=1 is verouderd, scan_depth=2 wordt gebruikt.",
|
||||
/* English: ShadowMount+: scan depth %u enabled. */
|
||||
[SM_L10N_SCAN_DEPTH_ENABLED] = "ShadowMount+: scandiepte %u ingeschakeld.",
|
||||
/* English: Backport overlay failed: %s\n%s\n0x%08X */
|
||||
[SM_L10N_BACKPORT_OVERLAY_FAILED] = "Backport-overlay mislukt: %s\n%s\n0x%08X",
|
||||
/* English: Backport applied to game: %s */
|
||||
[SM_L10N_GAME_BACKPORTED] = "Backport op game toegepast: %s",
|
||||
/* English: Backport applied to game: %s\nEmulators updated */
|
||||
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Backport op game toegepast: %s\nEmulators bijgewerkt",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Koppelen van image geweigerd:\n%s\nDe clustergrootte is te klein (%llu < %u).\nimage_sector-waarde opgeslagen: %u.\nProbeer opnieuw te koppelen.",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "Koppelen van image geweigerd:\n%s\nDe clustergrootte is te klein (%llu < %u).\nGebruik een groter cluster in de image of een kleinere sectorgrootte in de configuratie.",
|
||||
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
|
||||
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "Registreren mislukt: %s (%s)\nAppInstallTitleDir niet beschikbaar",
|
||||
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
|
||||
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "Registratie mislukt: %s (%s)\nTitleDir-bridge niet beschikbaar",
|
||||
/* English: Register failed: %s (%s)\ncode=0x%08X */
|
||||
[SM_L10N_REGISTER_FAILED_CODE] = "Registreren mislukt: %s (%s)\ncode=0x%08X",
|
||||
/* English: Installing: %s (%s)... */
|
||||
[SM_L10N_INSTALLING] = "Installeren: %s (%s)...",
|
||||
/* English: KStuff active again */
|
||||
[SM_L10N_KSTUFF_ACTIVE] = "KStuff weer actief",
|
||||
/* English: KStuff paused while the game is running */
|
||||
[SM_L10N_KSTUFF_PAUSED] = "KStuff gepauzeerd terwijl de game draait",
|
||||
/* English: Installed: %s */
|
||||
[SM_L10N_INSTALLED] = "Geïnstalleerd: %s",
|
||||
/* English: Unknown */
|
||||
[SM_L10N_UNKNOWN] = "Onbekend",
|
||||
/* English: Image mount failed: 0x%08X (%s)\n%s */
|
||||
[SM_L10N_IMAGE_MOUNT_FAILED] = "Koppelen van image mislukt: 0x%08X (%s)\n%s",
|
||||
/* English: Filesystem cluster size (%llu) is smaller than device sector size (%u) for %s */
|
||||
[SM_L10N_FS_CLUSTER_SMALL] = "Clustergrootte van bestandssysteem (%llu) is kleiner dan apparaatsectorgrootte (%u) voor %s",
|
||||
/* English: Mounted image is unreadable or damaged */
|
||||
[SM_L10N_IMAGE_UNREADABLE_DAMAGED] = "Gemounte image is onleesbaar of beschadigd",
|
||||
/* English: Image cache full (%u entries), cannot track mount %s */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_TRACK] = "Imagecache vol (%u vermeldingen), koppeling %s kan niet worden gevolgd",
|
||||
/* English: Image cache full (%u entries), rolling back mount */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_ROLLBACK] = "Imagecache vol (%u vermeldingen), koppeling wordt ongedaan gemaakt",
|
||||
/* English: Install failed: %s (%s)\nTimed out after 5 minutes */
|
||||
[SM_L10N_INSTALL_FAILED_TIMEOUT] = "Installatie mislukt: %s (%s)\nTime-out na 5 minuten",
|
||||
/* English: Batch install queued (%d): */
|
||||
[SM_L10N_BATCH_INSTALL_QUEUED] = "Batchinstallatie in wachtrij (%d):",
|
||||
/* English: \n... and %d more */
|
||||
[SM_L10N_BATCH_INSTALL_AND_MORE] = "\n... en nog %d",
|
||||
/* English: Batch install failed for %d app(s).\ncode=0x%08X */
|
||||
[SM_L10N_BATCH_INSTALL_FAILED] = "Batchinstallatie mislukt voor %d apps.\ncode=0x%08X",
|
||||
/* English: %s crashed before KStuff was paused. KStuff is not the cause. */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF] = "%s is gecrasht voordat KStuff werd gepauzeerd. KStuff is niet de oorzaak.",
|
||||
/* English: %s crashed before KStuff pause:\n%s */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF_FATAL] = "%s is gecrasht voordat KStuff werd gepauzeerd:\n%s",
|
||||
/* English: %s crashed after KStuff was paused. Automatic tuning skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_SKIPPED] = "%s is gecrasht nadat KStuff werd gepauzeerd. Automatische afstelling overgeslagen.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nAutotune skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_FATAL_SKIPPED] = "%s is gecrasht nadat KStuff werd gepauzeerd:\n%s\nAutomatische afstelling overgeslagen.",
|
||||
/* English: Can't load module after KStuff pause */
|
||||
[SM_L10N_RTLD_AFTER_KSTUFF_FALLBACK] = "Kan module niet laden na KStuff-pauze",
|
||||
/* English: can't load module %.*s after KStuff pause */
|
||||
[SM_L10N_RTLD_MODULE_AFTER_KSTUFF] = "kan module %.*s niet laden nadat KStuff is gepauzeerd",
|
||||
/* English: %s: %s. Delay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_DELAY_INCREASED_RELAUNCH] = "%s: %s. Vertraging verhoogd naar %u s.\nStart de game opnieuw.",
|
||||
/* English: Crash detected after KStuff pause: pause delay for %s increased to %us. Launch the game again. */
|
||||
[SM_L10N_CRASH_DELAY_INCREASED] = "Vastloper gedetecteerd nadat KStuff werd gepauzeerd: de pauzevertraging voor %s is verhoogd naar %u s. Start de game opnieuw.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nDelay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_INCREASED] = "%s is gecrasht nadat KStuff werd gepauzeerd:\n%s\nVertraging verhoogd naar %u s.\nStart de game opnieuw.",
|
||||
/* English: Crash detected after KStuff pause: %s. Failed to update autotune delay. */
|
||||
[SM_L10N_CRASH_DELAY_UPDATE_FAILED] = "Vastloper gedetecteerd nadat KStuff werd gepauzeerd: %s. De vertraging voor automatische afstelling kon niet worden bijgewerkt.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nFailed to update autotune delay. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_UPDATE_FAILED] = "%s is gecrasht nadat KStuff werd gepauzeerd:\n%s\nKan de vertraging voor automatische afstelling niet bijwerken.",
|
||||
/* English: Missing/invalid param.json:\n%s */
|
||||
[SM_L10N_MISSING_PARAM] = "param.json ontbreekt of is ongeldig:\n%s",
|
||||
/* English: Found %d new games. Processing... */
|
||||
[SM_L10N_FOUND_NEW_GAMES] = "%d nieuwe games gevonden. Verwerken...",
|
||||
/* English: Library synchronized.\nFound %d games. */
|
||||
[SM_L10N_LIBRARY_SYNCED] = "Bibliotheek gesynchroniseerd.\n%d games gevonden.",
|
||||
/* English: ShadowMount+: config reloaded. */
|
||||
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: configuratie opnieuw geladen.",
|
||||
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
|
||||
[SM_L10N_DUPLICATE_IGNORED] = "Duplicaat %s genegeerd:\n%s\nbestaand: %s",
|
||||
/* English: AMPR emulator updated */
|
||||
[SM_L10N_AMPR_EMULATOR_UPDATED] = "AMPR-emulator bijgewerkt",
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
static const char *const g_no_no[SM_L10N_COUNT] = {
|
||||
/* English: Go to Debug Settings */
|
||||
[SM_L10N_ACTION_DEBUG_SETTINGS] = "Gå til feilsøkingsinnstillinger",
|
||||
/* English: ShadowMount+ v%s exFAT/UFS/PFS */
|
||||
[SM_L10N_STARTUP] = "ShadowMount+ v%s exFAT/UFS/PFS",
|
||||
/* English: ShadowMount+: recursive_scan=1 is deprecated, using scan_depth=2. */
|
||||
[SM_L10N_RECURSIVE_SCAN_DEPRECATED] = "ShadowMount+: recursive_scan=1 er utdatert, bruker scan_depth=2.",
|
||||
/* English: ShadowMount+: scan depth %u enabled. */
|
||||
[SM_L10N_SCAN_DEPTH_ENABLED] = "ShadowMount+: skannedybde %u aktivert.",
|
||||
/* English: Backport overlay failed: %s\n%s\n0x%08X */
|
||||
[SM_L10N_BACKPORT_OVERLAY_FAILED] = "Backport-overlay mislyktes: %s\n%s\n0x%08X",
|
||||
/* English: Backport applied to game: %s */
|
||||
[SM_L10N_GAME_BACKPORTED] = "Backport brukt på spillet: %s",
|
||||
/* English: Backport applied to game: %s\nEmulators updated */
|
||||
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Backport brukt på spillet: %s\nEmulatorer oppdatert",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Montering av image avvist:\n%s\nKlyngestørrelsen er for liten (%llu < %u).\nimage_sector-overstyring lagret: %u.\nPrøv å montere igjen.",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "Montering av image avvist:\n%s\nKlyngestørrelsen er for liten (%llu < %u).\nBruk en større klyngestørrelse i imaget eller en mindre sektorstørrelse i konfigurasjonen.",
|
||||
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
|
||||
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "Registrering mislyktes: %s (%s)\nAppInstallTitleDir utilgjengelig",
|
||||
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
|
||||
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "Registrering mislyktes: %s (%s)\nTitleDir-broen er ikke tilgjengelig",
|
||||
/* English: Register failed: %s (%s)\ncode=0x%08X */
|
||||
[SM_L10N_REGISTER_FAILED_CODE] = "Registrering mislyktes: %s (%s)\nkode=0x%08X",
|
||||
/* English: Installing: %s (%s)... */
|
||||
[SM_L10N_INSTALLING] = "Installerer: %s (%s)...",
|
||||
/* English: KStuff active again */
|
||||
[SM_L10N_KSTUFF_ACTIVE] = "KStuff aktiv igjen",
|
||||
/* English: KStuff paused while the game is running */
|
||||
[SM_L10N_KSTUFF_PAUSED] = "KStuff pauset mens spillet kjører",
|
||||
/* English: Installed: %s */
|
||||
[SM_L10N_INSTALLED] = "Installert: %s",
|
||||
/* English: Unknown */
|
||||
[SM_L10N_UNKNOWN] = "Ukjent",
|
||||
/* English: Image mount failed: 0x%08X (%s)\n%s */
|
||||
[SM_L10N_IMAGE_MOUNT_FAILED] = "Montering av image mislyktes: 0x%08X (%s)\n%s",
|
||||
/* English: Filesystem cluster size (%llu) is smaller than device sector size (%u) for %s */
|
||||
[SM_L10N_FS_CLUSTER_SMALL] = "Filsystemets klyngestørrelse (%llu) er mindre enn enhetens sektorstørrelse (%u) for %s",
|
||||
/* English: Mounted image is unreadable or damaged */
|
||||
[SM_L10N_IMAGE_UNREADABLE_DAMAGED] = "Mountet image er uleselig eller skadet",
|
||||
/* English: Image cache full (%u entries), cannot track mount %s */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_TRACK] = "Image-cachen er full (%u oppføringer), monteringen %s kan ikke spores",
|
||||
/* English: Image cache full (%u entries), rolling back mount */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_ROLLBACK] = "Image-cachen er full (%u oppføringer), monteringen rulles tilbake",
|
||||
/* English: Install failed: %s (%s)\nTimed out after 5 minutes */
|
||||
[SM_L10N_INSTALL_FAILED_TIMEOUT] = "Installasjon mislyktes: %s (%s)\nTidsavbrudd etter 5 minutter",
|
||||
/* English: Batch install queued (%d): */
|
||||
[SM_L10N_BATCH_INSTALL_QUEUED] = "Batchinstallasjon i kø (%d):",
|
||||
/* English: \n... and %d more */
|
||||
[SM_L10N_BATCH_INSTALL_AND_MORE] = "\n... og %d til",
|
||||
/* English: Batch install failed for %d app(s).\ncode=0x%08X */
|
||||
[SM_L10N_BATCH_INSTALL_FAILED] = "Batchinstallasjonen mislyktes for %d apper.\nkode=0x%08X",
|
||||
/* English: %s crashed before KStuff was paused. KStuff is not the cause. */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF] = "Appen %s krasjet før KStuff ble satt på pause. KStuff er ikke årsaken.",
|
||||
/* English: %s crashed before KStuff pause:\n%s */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF_FATAL] = "%s krasjet før KStuff ble satt på pause:\n%s",
|
||||
/* English: %s crashed after KStuff was paused. Automatic tuning skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_SKIPPED] = "Appen %s krasjet etter at KStuff ble satt på pause. Automatisk justering ble hoppet over.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nAutotune skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_FATAL_SKIPPED] = "%s krasjet etter at KStuff ble satt på pause:\n%s\nAutomatisk justering ble hoppet over.",
|
||||
/* English: Can't load module after KStuff pause */
|
||||
[SM_L10N_RTLD_AFTER_KSTUFF_FALLBACK] = "Kan ikke laste modul etter KStuff-pause",
|
||||
/* English: can't load module %.*s after KStuff pause */
|
||||
[SM_L10N_RTLD_MODULE_AFTER_KSTUFF] = "kan ikke laste modulen %.*s etter KStuff-pausen",
|
||||
/* English: %s: %s. Delay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_DELAY_INCREASED_RELAUNCH] = "%s: %s. Forsinkelse økt til %u s.\nStart spillet igjen.",
|
||||
/* English: Crash detected after KStuff pause: pause delay for %s increased to %us. Launch the game again. */
|
||||
[SM_L10N_CRASH_DELAY_INCREASED] = "Krasj oppdaget etter KStuff-pausen: pauseforsinkelsen for %s ble økt til %u s. Start spillet på nytt.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nDelay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_INCREASED] = "%s krasjet etter at KStuff ble satt på pause:\n%s\nForsinkelsen ble økt til %u s.\nStart spillet på nytt.",
|
||||
/* English: Crash detected after KStuff pause: %s. Failed to update autotune delay. */
|
||||
[SM_L10N_CRASH_DELAY_UPDATE_FAILED] = "Krasj oppdaget etter KStuff-pausen: %s. Forsinkelsen for automatisk justering kunne ikke oppdateres.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nFailed to update autotune delay. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_UPDATE_FAILED] = "%s krasjet etter at KStuff ble satt på pause:\n%s\nKunne ikke oppdatere forsinkelsen for automatisk justering.",
|
||||
/* English: Missing/invalid param.json:\n%s */
|
||||
[SM_L10N_MISSING_PARAM] = "param.json mangler eller er ugyldig:\n%s",
|
||||
/* English: Found %d new games. Processing... */
|
||||
[SM_L10N_FOUND_NEW_GAMES] = "Fant %d nye spill. Behandler...",
|
||||
/* English: Library synchronized.\nFound %d games. */
|
||||
[SM_L10N_LIBRARY_SYNCED] = "Bibliotek synkronisert.\nFant %d spill.",
|
||||
/* English: ShadowMount+: config reloaded. */
|
||||
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: konfigurasjonen ble lastet inn på nytt.",
|
||||
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
|
||||
[SM_L10N_DUPLICATE_IGNORED] = "Duplikat %s ignorert:\n%s\neksisterende: %s",
|
||||
/* English: AMPR emulator updated */
|
||||
[SM_L10N_AMPR_EMULATOR_UPDATED] = "AMPR-emulator oppdatert",
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
static const char *const g_pl_pl[SM_L10N_COUNT] = {
|
||||
/* English: Go to Debug Settings */
|
||||
[SM_L10N_ACTION_DEBUG_SETTINGS] = "Przejdź do ustawień debugowania",
|
||||
/* English: ShadowMount+ v%s exFAT/UFS/PFS */
|
||||
[SM_L10N_STARTUP] = "ShadowMount+ v%s exFAT/UFS/PFS",
|
||||
/* English: ShadowMount+: recursive_scan=1 is deprecated, using scan_depth=2. */
|
||||
[SM_L10N_RECURSIVE_SCAN_DEPRECATED] = "ShadowMount+: recursive_scan=1 jest przestarzałe, zastosowano scan_depth=2.",
|
||||
/* English: ShadowMount+: scan depth %u enabled. */
|
||||
[SM_L10N_SCAN_DEPTH_ENABLED] = "ShadowMount+: włączono głębokość skanowania %u.",
|
||||
/* English: Backport overlay failed: %s\n%s\n0x%08X */
|
||||
[SM_L10N_BACKPORT_OVERLAY_FAILED] = "Nakładka backportu nie powiodła się: %s\n%s\n0x%08X",
|
||||
/* English: Backport applied to game: %s */
|
||||
[SM_L10N_GAME_BACKPORTED] = "Zastosowano backport gry: %s",
|
||||
/* English: Backport applied to game: %s\nEmulators updated */
|
||||
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Zastosowano backport gry: %s\nEmulatory zaktualizowane",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Odrzucono montowanie obrazu:\n%s\nRozmiar klastra jest za mały (%llu < %u).\nZastosowano nadpisanie image_sector: %u.\nSpróbuj zamontować ponownie.",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "Odrzucono montowanie obrazu:\n%s\nRozmiar klastra jest za mały (%llu < %u).\nUżyj większego rozmiaru klastra w obrazie lub mniejszego rozmiaru sektora w konfiguracji.",
|
||||
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
|
||||
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "Rejestracja nie powiodła się: %s (%s)\nAppInstallTitleDir niedostępne",
|
||||
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
|
||||
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "Rejestracja nie powiodła się: %s (%s)\nMost TitleDir jest niedostępny",
|
||||
/* English: Register failed: %s (%s)\ncode=0x%08X */
|
||||
[SM_L10N_REGISTER_FAILED_CODE] = "Rejestracja nie powiodła się: %s (%s)\nkod=0x%08X",
|
||||
/* English: Installing: %s (%s)... */
|
||||
[SM_L10N_INSTALLING] = "Instalowanie: %s (%s)...",
|
||||
/* English: KStuff active again */
|
||||
[SM_L10N_KSTUFF_ACTIVE] = "KStuff ponownie aktywny",
|
||||
/* English: KStuff paused while the game is running */
|
||||
[SM_L10N_KSTUFF_PAUSED] = "KStuff wstrzymany podczas działania gry",
|
||||
/* English: Installed: %s */
|
||||
[SM_L10N_INSTALLED] = "Zainstalowano: %s",
|
||||
/* English: Unknown */
|
||||
[SM_L10N_UNKNOWN] = "Nieznane",
|
||||
/* English: Image mount failed: 0x%08X (%s)\n%s */
|
||||
[SM_L10N_IMAGE_MOUNT_FAILED] = "Montowanie obrazu nie powiodło się: 0x%08X (%s)\n%s",
|
||||
/* English: Filesystem cluster size (%llu) is smaller than device sector size (%u) for %s */
|
||||
[SM_L10N_FS_CLUSTER_SMALL] = "Rozmiar klastra systemu plików (%llu) jest mniejszy niż rozmiar sektora urządzenia (%u) dla %s",
|
||||
/* English: Mounted image is unreadable or damaged */
|
||||
[SM_L10N_IMAGE_UNREADABLE_DAMAGED] = "Zamontowany obraz jest nieczytelny lub uszkodzony",
|
||||
/* English: Image cache full (%u entries), cannot track mount %s */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_TRACK] = "Pamięć podręczna obrazów jest pełna (%u wpisów), nie można śledzić montowania %s",
|
||||
/* English: Image cache full (%u entries), rolling back mount */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_ROLLBACK] = "Pamięć podręczna obrazów jest pełna (%u wpisów), montowanie zostanie wycofane",
|
||||
/* English: Install failed: %s (%s)\nTimed out after 5 minutes */
|
||||
[SM_L10N_INSTALL_FAILED_TIMEOUT] = "Instalacja nie powiodła się: %s (%s)\nPrzekroczono limit czasu po 5 minutach",
|
||||
/* English: Batch install queued (%d): */
|
||||
[SM_L10N_BATCH_INSTALL_QUEUED] = "Instalacja zbiorcza dodana do kolejki (%d):",
|
||||
/* English: \n... and %d more */
|
||||
[SM_L10N_BATCH_INSTALL_AND_MORE] = "\n... i jeszcze %d",
|
||||
/* English: Batch install failed for %d app(s).\ncode=0x%08X */
|
||||
[SM_L10N_BATCH_INSTALL_FAILED] = "Instalacja zbiorcza nie powiodła się dla %d aplikacji.\nkod=0x%08X",
|
||||
/* English: %s crashed before KStuff was paused. KStuff is not the cause. */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF] = "Aplikacja %s uległa awarii przed wstrzymaniem KStuff. KStuff nie jest przyczyną.",
|
||||
/* English: %s crashed before KStuff pause:\n%s */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF_FATAL] = "Aplikacja %s uległa awarii przed wstrzymaniem KStuff:\n%s",
|
||||
/* English: %s crashed after KStuff was paused. Automatic tuning skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_SKIPPED] = "Aplikacja %s uległa awarii po wstrzymaniu KStuff. Pominięto automatyczne dostrajanie.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nAutotune skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_FATAL_SKIPPED] = "Aplikacja %s uległa awarii po wstrzymaniu KStuff:\n%s\nPominięto automatyczne dostrajanie.",
|
||||
/* English: Can't load module after KStuff pause */
|
||||
[SM_L10N_RTLD_AFTER_KSTUFF_FALLBACK] = "Nie można załadować modułu po wstrzymaniu KStuff",
|
||||
/* English: can't load module %.*s after KStuff pause */
|
||||
[SM_L10N_RTLD_MODULE_AFTER_KSTUFF] = "nie można załadować modułu %.*s po wstrzymaniu KStuff",
|
||||
/* English: %s: %s. Delay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_DELAY_INCREASED_RELAUNCH] = "%s: %s. Opóźnienie zwiększono do %u s.\nUruchom grę ponownie.",
|
||||
/* English: Crash detected after KStuff pause: pause delay for %s increased to %us. Launch the game again. */
|
||||
[SM_L10N_CRASH_DELAY_INCREASED] = "Wykryto awarię po wstrzymaniu KStuff: opóźnienie dla %s zwiększono do %u s. Uruchom grę ponownie.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nDelay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_INCREASED] = "Aplikacja %s uległa awarii po wstrzymaniu KStuff:\n%s\nOpóźnienie zwiększono do %u s.\nUruchom grę ponownie.",
|
||||
/* English: Crash detected after KStuff pause: %s. Failed to update autotune delay. */
|
||||
[SM_L10N_CRASH_DELAY_UPDATE_FAILED] = "Wykryto awarię po wstrzymaniu KStuff: %s. Nie udało się zaktualizować opóźnienia automatycznego dostrajania.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nFailed to update autotune delay. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_UPDATE_FAILED] = "Aplikacja %s uległa awarii po wstrzymaniu KStuff:\n%s\nNie udało się zaktualizować opóźnienia automatycznego dostrajania.",
|
||||
/* English: Missing/invalid param.json:\n%s */
|
||||
[SM_L10N_MISSING_PARAM] = "Brak pliku param.json lub plik jest nieprawidłowy:\n%s",
|
||||
/* English: Found %d new games. Processing... */
|
||||
[SM_L10N_FOUND_NEW_GAMES] = "Znaleziono %d nowych gier. Przetwarzanie...",
|
||||
/* English: Library synchronized.\nFound %d games. */
|
||||
[SM_L10N_LIBRARY_SYNCED] = "Biblioteka zsynchronizowana.\nZnaleziono %d gier.",
|
||||
/* English: ShadowMount+: config reloaded. */
|
||||
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: konfiguracja przeładowana.",
|
||||
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
|
||||
[SM_L10N_DUPLICATE_IGNORED] = "Duplikat %s zignorowany:\n%s\nistniejący: %s",
|
||||
/* English: AMPR emulator updated */
|
||||
[SM_L10N_AMPR_EMULATOR_UPDATED] = "Emulator AMPR został zaktualizowany",
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
static const char *const g_pt_br[SM_L10N_COUNT] = {
|
||||
/* English: Go to Debug Settings */
|
||||
[SM_L10N_ACTION_DEBUG_SETTINGS] = "Ir para as Configurações de Depuração",
|
||||
/* English: ShadowMount+ v%s exFAT/UFS/PFS */
|
||||
[SM_L10N_STARTUP] = "ShadowMount+ v%s exFAT/UFS/PFS",
|
||||
/* English: ShadowMount+: recursive_scan=1 is deprecated, using scan_depth=2. */
|
||||
[SM_L10N_RECURSIVE_SCAN_DEPRECATED] = "ShadowMount+: recursive_scan=1 está obsoleto, usando scan_depth=2.",
|
||||
/* English: ShadowMount+: scan depth %u enabled. */
|
||||
[SM_L10N_SCAN_DEPTH_ENABLED] = "ShadowMount+: profundidade de varredura %u ativada.",
|
||||
/* English: Backport overlay failed: %s\n%s\n0x%08X */
|
||||
[SM_L10N_BACKPORT_OVERLAY_FAILED] = "Falha no overlay de backport: %s\n%s\n0x%08X",
|
||||
/* English: Backport applied to game: %s */
|
||||
[SM_L10N_GAME_BACKPORTED] = "Backport aplicado ao jogo: %s",
|
||||
/* English: Backport applied to game: %s\nEmulators updated */
|
||||
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Backport aplicado ao jogo: %s\nEmuladores atualizados",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Montagem da imagem rejeitada:\n%s\nO tamanho do cluster é pequeno demais (%llu < %u).\nNovo valor para o parâmetro image_sector salvo: %u.\nTente montar novamente.",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "Montagem da imagem rejeitada:\n%s\nO tamanho do cluster é pequeno demais (%llu < %u).\nUse um cluster maior na imagem ou um tamanho de setor menor na configuração.",
|
||||
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
|
||||
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "Falha no registro: %s (%s)\nAppInstallTitleDir indisponível",
|
||||
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
|
||||
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "Falha no registro: %s (%s)\nA ponte TitleDir não está disponível",
|
||||
/* English: Register failed: %s (%s)\ncode=0x%08X */
|
||||
[SM_L10N_REGISTER_FAILED_CODE] = "Registro falhou: %s (%s)\ncódigo=0x%08X",
|
||||
/* English: Installing: %s (%s)... */
|
||||
[SM_L10N_INSTALLING] = "Instalando: %s (%s)...",
|
||||
/* English: KStuff active again */
|
||||
[SM_L10N_KSTUFF_ACTIVE] = "KStuff ativo novamente",
|
||||
/* English: KStuff paused while the game is running */
|
||||
[SM_L10N_KSTUFF_PAUSED] = "KStuff pausado enquanto o jogo está em execução",
|
||||
/* English: Installed: %s */
|
||||
[SM_L10N_INSTALLED] = "Instalado: %s",
|
||||
/* English: Unknown */
|
||||
[SM_L10N_UNKNOWN] = "Desconhecido",
|
||||
/* English: Image mount failed: 0x%08X (%s)\n%s */
|
||||
[SM_L10N_IMAGE_MOUNT_FAILED] = "Falha ao montar imagem: 0x%08X (%s)\n%s",
|
||||
/* English: Filesystem cluster size (%llu) is smaller than device sector size (%u) for %s */
|
||||
[SM_L10N_FS_CLUSTER_SMALL] = "O tamanho do cluster do sistema de arquivos (%llu) é menor que o tamanho do setor do dispositivo (%u) para %s",
|
||||
/* English: Mounted image is unreadable or damaged */
|
||||
[SM_L10N_IMAGE_UNREADABLE_DAMAGED] = "A imagem montada está ilegível ou danificada",
|
||||
/* English: Image cache full (%u entries), cannot track mount %s */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_TRACK] = "Cache de imagens cheio (%u entradas), não é possível rastrear a montagem %s",
|
||||
/* English: Image cache full (%u entries), rolling back mount */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_ROLLBACK] = "Cache de imagens cheio (%u entradas), revertendo a montagem",
|
||||
/* English: Install failed: %s (%s)\nTimed out after 5 minutes */
|
||||
[SM_L10N_INSTALL_FAILED_TIMEOUT] = "A instalação falhou: %s (%s)\nTempo esgotado após 5 minutos",
|
||||
/* English: Batch install queued (%d): */
|
||||
[SM_L10N_BATCH_INSTALL_QUEUED] = "Instalação em lote enfileirada (%d):",
|
||||
/* English: \n... and %d more */
|
||||
[SM_L10N_BATCH_INSTALL_AND_MORE] = "\n... e mais %d",
|
||||
/* English: Batch install failed for %d app(s).\ncode=0x%08X */
|
||||
[SM_L10N_BATCH_INSTALL_FAILED] = "A instalação em lote falhou para %d aplicativos.\ncódigo=0x%08X",
|
||||
/* English: %s crashed before KStuff was paused. KStuff is not the cause. */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF] = "O aplicativo %s travou antes da pausa do KStuff. O KStuff não é o responsável.",
|
||||
/* English: %s crashed before KStuff pause:\n%s */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF_FATAL] = "%s travou antes da pausa do KStuff:\n%s",
|
||||
/* English: %s crashed after KStuff was paused. Automatic tuning skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_SKIPPED] = "O aplicativo %s travou após a pausa do KStuff. O ajuste automático foi ignorado.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nAutotune skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_FATAL_SKIPPED] = "%s travou após a pausa do KStuff:\n%s\nO ajuste automático foi ignorado.",
|
||||
/* English: Can't load module after KStuff pause */
|
||||
[SM_L10N_RTLD_AFTER_KSTUFF_FALLBACK] = "Não é possível carregar o módulo após a pausa do KStuff",
|
||||
/* English: can't load module %.*s after KStuff pause */
|
||||
[SM_L10N_RTLD_MODULE_AFTER_KSTUFF] = "não foi possível carregar o módulo %.*s após a pausa do KStuff",
|
||||
/* English: %s: %s. Delay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_DELAY_INCREASED_RELAUNCH] = "%s: %s. O atraso aumentou para %u s.\nInicie o jogo novamente.",
|
||||
/* English: Crash detected after KStuff pause: pause delay for %s increased to %us. Launch the game again. */
|
||||
[SM_L10N_CRASH_DELAY_INCREASED] = "Falha detectada após a pausa do KStuff: o atraso de pausa de %s aumentou para %u s. Inicie o jogo novamente.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nDelay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_INCREASED] = "%s travou após a pausa do KStuff:\n%s\nO atraso aumentou para %u s.\nInicie o jogo novamente.",
|
||||
/* English: Crash detected after KStuff pause: %s. Failed to update autotune delay. */
|
||||
[SM_L10N_CRASH_DELAY_UPDATE_FAILED] = "Falha detectada após a pausa do KStuff: %s. Não foi possível atualizar o atraso do ajuste automático.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nFailed to update autotune delay. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_UPDATE_FAILED] = "%s travou após a pausa do KStuff:\n%s\nNão foi possível atualizar o atraso do ajuste automático.",
|
||||
/* English: Missing/invalid param.json:\n%s */
|
||||
[SM_L10N_MISSING_PARAM] = "Arquivo param.json ausente ou inválido:\n%s",
|
||||
/* English: Found %d new games. Processing... */
|
||||
[SM_L10N_FOUND_NEW_GAMES] = "%d novos jogos encontrados. Processando...",
|
||||
/* English: Library synchronized.\nFound %d games. */
|
||||
[SM_L10N_LIBRARY_SYNCED] = "Biblioteca sincronizada.\n%d jogos encontrados.",
|
||||
/* English: ShadowMount+: config reloaded. */
|
||||
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: configuração recarregada.",
|
||||
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
|
||||
[SM_L10N_DUPLICATE_IGNORED] = "Cópia duplicada de %s ignorada:\n%s\nExistente: %s",
|
||||
/* English: AMPR emulator updated */
|
||||
[SM_L10N_AMPR_EMULATOR_UPDATED] = "Emulador AMPR atualizado",
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
static const char *const g_pt_pt[SM_L10N_COUNT] = {
|
||||
/* English: Go to Debug Settings */
|
||||
[SM_L10N_ACTION_DEBUG_SETTINGS] = "Ir para Definições de depuração",
|
||||
/* English: ShadowMount+ v%s exFAT/UFS/PFS */
|
||||
[SM_L10N_STARTUP] = "ShadowMount+ v%s exFAT/UFS/PFS",
|
||||
/* English: ShadowMount+: recursive_scan=1 is deprecated, using scan_depth=2. */
|
||||
[SM_L10N_RECURSIVE_SCAN_DEPRECATED] = "ShadowMount+: recursive_scan=1 está obsoleto, será usado scan_depth=2.",
|
||||
/* English: ShadowMount+: scan depth %u enabled. */
|
||||
[SM_L10N_SCAN_DEPTH_ENABLED] = "ShadowMount+: profundidade de pesquisa %u ativada.",
|
||||
/* English: Backport overlay failed: %s\n%s\n0x%08X */
|
||||
[SM_L10N_BACKPORT_OVERLAY_FAILED] = "Falha no overlay de backport: %s\n%s\n0x%08X",
|
||||
/* English: Backport applied to game: %s */
|
||||
[SM_L10N_GAME_BACKPORTED] = "Jogo com backport aplicado: %s",
|
||||
/* English: Backport applied to game: %s\nEmulators updated */
|
||||
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Jogo com backport aplicado: %s\nEmuladores atualizados",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Montagem da imagem rejeitada:\n%s\nO tamanho do cluster é demasiado pequeno (%llu < %u).\nSubstituição image_sector guardada: %u.\nTente montar novamente.",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "Montagem da imagem rejeitada:\n%s\nO tamanho do cluster é demasiado pequeno (%llu < %u).\nUtilize um cluster maior na imagem ou um tamanho de setor menor na configuração.",
|
||||
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
|
||||
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "Registo falhou: %s (%s)\nAppInstallTitleDir indisponível",
|
||||
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
|
||||
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "Falha no registo: %s (%s)\nA ponte TitleDir não está disponível",
|
||||
/* English: Register failed: %s (%s)\ncode=0x%08X */
|
||||
[SM_L10N_REGISTER_FAILED_CODE] = "Registo falhou: %s (%s)\ncódigo=0x%08X",
|
||||
/* English: Installing: %s (%s)... */
|
||||
[SM_L10N_INSTALLING] = "A instalar: %s (%s)...",
|
||||
/* English: KStuff active again */
|
||||
[SM_L10N_KSTUFF_ACTIVE] = "KStuff ativo novamente",
|
||||
/* English: KStuff paused while the game is running */
|
||||
[SM_L10N_KSTUFF_PAUSED] = "KStuff em pausa enquanto o jogo está em execução",
|
||||
/* English: Installed: %s */
|
||||
[SM_L10N_INSTALLED] = "Instalado: %s",
|
||||
/* English: Unknown */
|
||||
[SM_L10N_UNKNOWN] = "Desconhecido",
|
||||
/* English: Image mount failed: 0x%08X (%s)\n%s */
|
||||
[SM_L10N_IMAGE_MOUNT_FAILED] = "Falha ao montar imagem: 0x%08X (%s)\n%s",
|
||||
/* English: Filesystem cluster size (%llu) is smaller than device sector size (%u) for %s */
|
||||
[SM_L10N_FS_CLUSTER_SMALL] = "O tamanho do cluster do sistema de ficheiros (%llu) é menor que o tamanho do setor do dispositivo (%u) para %s",
|
||||
/* English: Mounted image is unreadable or damaged */
|
||||
[SM_L10N_IMAGE_UNREADABLE_DAMAGED] = "A imagem montada está ilegível ou danificada",
|
||||
/* English: Image cache full (%u entries), cannot track mount %s */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_TRACK] = "A cache de imagens está cheia (%u entradas), não é possível acompanhar a montagem %s",
|
||||
/* English: Image cache full (%u entries), rolling back mount */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_ROLLBACK] = "A cache de imagens está cheia (%u entradas), a reverter a montagem",
|
||||
/* English: Install failed: %s (%s)\nTimed out after 5 minutes */
|
||||
[SM_L10N_INSTALL_FAILED_TIMEOUT] = "Instalação falhou: %s (%s)\nTempo esgotado após 5 minutos",
|
||||
/* English: Batch install queued (%d): */
|
||||
[SM_L10N_BATCH_INSTALL_QUEUED] = "Instalação em lote na fila (%d):",
|
||||
/* English: \n... and %d more */
|
||||
[SM_L10N_BATCH_INSTALL_AND_MORE] = "\n... e mais %d",
|
||||
/* English: Batch install failed for %d app(s).\ncode=0x%08X */
|
||||
[SM_L10N_BATCH_INSTALL_FAILED] = "A instalação em lote falhou para %d aplicações.\ncódigo=0x%08X",
|
||||
/* English: %s crashed before KStuff was paused. KStuff is not the cause. */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF] = "A aplicação %s falhou antes da pausa do KStuff. O KStuff não é a causa.",
|
||||
/* English: %s crashed before KStuff pause:\n%s */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF_FATAL] = "%s falhou antes da pausa do KStuff:\n%s",
|
||||
/* English: %s crashed after KStuff was paused. Automatic tuning skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_SKIPPED] = "A aplicação %s falhou após a pausa do KStuff. O ajuste automático foi ignorado.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nAutotune skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_FATAL_SKIPPED] = "%s falhou após a pausa do KStuff:\n%s\nO ajuste automático foi ignorado.",
|
||||
/* English: Can't load module after KStuff pause */
|
||||
[SM_L10N_RTLD_AFTER_KSTUFF_FALLBACK] = "Não é possível carregar o módulo após a pausa do KStuff",
|
||||
/* English: can't load module %.*s after KStuff pause */
|
||||
[SM_L10N_RTLD_MODULE_AFTER_KSTUFF] = "não foi possível carregar o módulo %.*s após a pausa do KStuff",
|
||||
/* English: %s: %s. Delay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_DELAY_INCREASED_RELAUNCH] = "%s: %s. Atraso aumentado para %u s.\nInicie o jogo novamente.",
|
||||
/* English: Crash detected after KStuff pause: pause delay for %s increased to %us. Launch the game again. */
|
||||
[SM_L10N_CRASH_DELAY_INCREASED] = "Falha detetada após a pausa do KStuff: o atraso de pausa de %s aumentou para %u s. Inicie o jogo novamente.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nDelay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_INCREASED] = "%s falhou após a pausa do KStuff:\n%s\nO atraso aumentou para %u s.\nInicie o jogo novamente.",
|
||||
/* English: Crash detected after KStuff pause: %s. Failed to update autotune delay. */
|
||||
[SM_L10N_CRASH_DELAY_UPDATE_FAILED] = "Falha detetada após a pausa do KStuff: %s. Não foi possível atualizar o atraso do ajuste automático.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nFailed to update autotune delay. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_UPDATE_FAILED] = "%s falhou após a pausa do KStuff:\n%s\nNão foi possível atualizar o atraso do ajuste automático.",
|
||||
/* English: Missing/invalid param.json:\n%s */
|
||||
[SM_L10N_MISSING_PARAM] = "param.json em falta ou inválido:\n%s",
|
||||
/* English: Found %d new games. Processing... */
|
||||
[SM_L10N_FOUND_NEW_GAMES] = "%d novos jogos encontrados. A processar...",
|
||||
/* English: Library synchronized.\nFound %d games. */
|
||||
[SM_L10N_LIBRARY_SYNCED] = "Biblioteca sincronizada.\n%d jogos encontrados.",
|
||||
/* English: ShadowMount+: config reloaded. */
|
||||
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: configuração recarregada.",
|
||||
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
|
||||
[SM_L10N_DUPLICATE_IGNORED] = "Duplicado %s ignorado:\n%s\nexistente: %s",
|
||||
/* English: AMPR emulator updated */
|
||||
[SM_L10N_AMPR_EMULATOR_UPDATED] = "Emulador AMPR atualizado",
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
static const char *const g_ro_ro[SM_L10N_COUNT] = {
|
||||
/* English: Go to Debug Settings */
|
||||
[SM_L10N_ACTION_DEBUG_SETTINGS] = "Mergi la setările de depanare",
|
||||
/* English: ShadowMount+ v%s exFAT/UFS/PFS */
|
||||
[SM_L10N_STARTUP] = "ShadowMount+ v%s exFAT/UFS/PFS",
|
||||
/* English: ShadowMount+: recursive_scan=1 is deprecated, using scan_depth=2. */
|
||||
[SM_L10N_RECURSIVE_SCAN_DEPRECATED] = "ShadowMount+: recursive_scan=1 este depășit, se folosește scan_depth=2.",
|
||||
/* English: ShadowMount+: scan depth %u enabled. */
|
||||
[SM_L10N_SCAN_DEPTH_ENABLED] = "ShadowMount+: adâncimea de scanare %u este activată.",
|
||||
/* English: Backport overlay failed: %s\n%s\n0x%08X */
|
||||
[SM_L10N_BACKPORT_OVERLAY_FAILED] = "Overlay backport eșuat: %s\n%s\n0x%08X",
|
||||
/* English: Backport applied to game: %s */
|
||||
[SM_L10N_GAME_BACKPORTED] = "Backport aplicat jocului: %s",
|
||||
/* English: Backport applied to game: %s\nEmulators updated */
|
||||
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Backport aplicat jocului: %s\nEmulatoare actualizate",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Montarea imaginii respinsă:\n%s\nDimensiunea clusterului este prea mică (%llu < %u).\nOverride image_sector salvat: %u.\nÎncercați montarea din nou.",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "Montarea imaginii a fost respinsă:\n%s\nDimensiunea clusterului este prea mică (%llu < %u).\nFolosiți un cluster mai mare în imagine sau o dimensiune mai mică a sectorului în configurație.",
|
||||
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
|
||||
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "Înregistrare eșuată: %s (%s)\nAppInstallTitleDir indisponibil",
|
||||
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
|
||||
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "Înregistrarea a eșuat: %s (%s)\nPuntea TitleDir nu este disponibilă",
|
||||
/* English: Register failed: %s (%s)\ncode=0x%08X */
|
||||
[SM_L10N_REGISTER_FAILED_CODE] = "Înregistrare eșuată: %s (%s)\ncod=0x%08X",
|
||||
/* English: Installing: %s (%s)... */
|
||||
[SM_L10N_INSTALLING] = "Se instalează: %s (%s)...",
|
||||
/* English: KStuff active again */
|
||||
[SM_L10N_KSTUFF_ACTIVE] = "KStuff este activ din nou",
|
||||
/* English: KStuff paused while the game is running */
|
||||
[SM_L10N_KSTUFF_PAUSED] = "KStuff este pauzat cât timp jocul rulează",
|
||||
/* English: Installed: %s */
|
||||
[SM_L10N_INSTALLED] = "Instalat: %s",
|
||||
/* English: Unknown */
|
||||
[SM_L10N_UNKNOWN] = "Necunoscut",
|
||||
/* English: Image mount failed: 0x%08X (%s)\n%s */
|
||||
[SM_L10N_IMAGE_MOUNT_FAILED] = "Montarea imaginii a eșuat: 0x%08X (%s)\n%s",
|
||||
/* English: Filesystem cluster size (%llu) is smaller than device sector size (%u) for %s */
|
||||
[SM_L10N_FS_CLUSTER_SMALL] = "Dimensiunea clusterului sistemului de fișiere (%llu) este mai mică decât sectorul dispozitivului (%u) pentru %s",
|
||||
/* English: Mounted image is unreadable or damaged */
|
||||
[SM_L10N_IMAGE_UNREADABLE_DAMAGED] = "Imaginea montată este ilizibilă sau deteriorată",
|
||||
/* English: Image cache full (%u entries), cannot track mount %s */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_TRACK] = "Cache-ul de imagini este plin (%u intrări), montarea %s nu poate fi urmărită",
|
||||
/* English: Image cache full (%u entries), rolling back mount */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_ROLLBACK] = "Cache-ul de imagini este plin (%u intrări), montarea este anulată",
|
||||
/* English: Install failed: %s (%s)\nTimed out after 5 minutes */
|
||||
[SM_L10N_INSTALL_FAILED_TIMEOUT] = "Instalare eșuată: %s (%s)\nTimeout după 5 minute",
|
||||
/* English: Batch install queued (%d): */
|
||||
[SM_L10N_BATCH_INSTALL_QUEUED] = "Instalare în lot pusă în coadă (%d):",
|
||||
/* English: \n... and %d more */
|
||||
[SM_L10N_BATCH_INSTALL_AND_MORE] = "\n... și încă %d",
|
||||
/* English: Batch install failed for %d app(s).\ncode=0x%08X */
|
||||
[SM_L10N_BATCH_INSTALL_FAILED] = "Instalarea în lot a eșuat pentru %d aplicații.\ncod=0x%08X",
|
||||
/* English: %s crashed before KStuff was paused. KStuff is not the cause. */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF] = "Aplicația %s s-a blocat înainte de suspendarea KStuff. KStuff nu este cauza.",
|
||||
/* English: %s crashed before KStuff pause:\n%s */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF_FATAL] = "%s s-a blocat înainte de suspendarea KStuff:\n%s",
|
||||
/* English: %s crashed after KStuff was paused. Automatic tuning skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_SKIPPED] = "Aplicația %s s-a blocat după suspendarea KStuff. Reglarea automată a fost omisă.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nAutotune skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_FATAL_SKIPPED] = "%s s-a blocat după suspendarea KStuff:\n%s\nReglarea automată a fost omisă.",
|
||||
/* English: Can't load module after KStuff pause */
|
||||
[SM_L10N_RTLD_AFTER_KSTUFF_FALLBACK] = "Nu se poate încărca modulul după pauza KStuff",
|
||||
/* English: can't load module %.*s after KStuff pause */
|
||||
[SM_L10N_RTLD_MODULE_AFTER_KSTUFF] = "modulul %.*s nu poate fi încărcat după suspendarea KStuff",
|
||||
/* English: %s: %s. Delay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_DELAY_INCREASED_RELAUNCH] = "%s: %s. Întârzierea a crescut la %u s.\nPorniți jocul din nou.",
|
||||
/* English: Crash detected after KStuff pause: pause delay for %s increased to %us. Launch the game again. */
|
||||
[SM_L10N_CRASH_DELAY_INCREASED] = "Blocare detectată după suspendarea KStuff: întârzierea pentru %s a crescut la %u s. Porniți jocul din nou.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nDelay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_INCREASED] = "%s s-a blocat după suspendarea KStuff:\n%s\nÎntârzierea a crescut la %u s.\nPorniți jocul din nou.",
|
||||
/* English: Crash detected after KStuff pause: %s. Failed to update autotune delay. */
|
||||
[SM_L10N_CRASH_DELAY_UPDATE_FAILED] = "Blocare detectată după suspendarea KStuff: %s. Întârzierea reglării automate nu a putut fi actualizată.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nFailed to update autotune delay. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_UPDATE_FAILED] = "%s s-a blocat după suspendarea KStuff:\n%s\nÎntârzierea reglării automate nu a putut fi actualizată.",
|
||||
/* English: Missing/invalid param.json:\n%s */
|
||||
[SM_L10N_MISSING_PARAM] = "param.json lipsește sau este invalid:\n%s",
|
||||
/* English: Found %d new games. Processing... */
|
||||
[SM_L10N_FOUND_NEW_GAMES] = "S-au găsit %d jocuri noi. Se procesează...",
|
||||
/* English: Library synchronized.\nFound %d games. */
|
||||
[SM_L10N_LIBRARY_SYNCED] = "Bibliotecă sincronizată.\nS-au găsit %d jocuri.",
|
||||
/* English: ShadowMount+: config reloaded. */
|
||||
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: configurația a fost reîncărcată.",
|
||||
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
|
||||
[SM_L10N_DUPLICATE_IGNORED] = "Duplicatul %s ignorat:\n%s\nexistent: %s",
|
||||
/* English: AMPR emulator updated */
|
||||
[SM_L10N_AMPR_EMULATOR_UPDATED] = "Emulatorul AMPR a fost actualizat",
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
static const char *const g_ru_ru[SM_L10N_COUNT] = {
|
||||
/* English: Go to Debug Settings */
|
||||
[SM_L10N_ACTION_DEBUG_SETTINGS] = "Перейти к настройкам отладки",
|
||||
/* English: ShadowMount+ v%s exFAT/UFS/PFS */
|
||||
[SM_L10N_STARTUP] = "ShadowMount+ v%s exFAT/UFS/PFS",
|
||||
/* English: ShadowMount+: recursive_scan=1 is deprecated, using scan_depth=2. */
|
||||
[SM_L10N_RECURSIVE_SCAN_DEPRECATED] = "ShadowMount+: параметр recursive_scan=1 устарел; используется scan_depth=2.",
|
||||
/* English: ShadowMount+: scan depth %u enabled. */
|
||||
[SM_L10N_SCAN_DEPTH_ENABLED] = "ShadowMount+: включена глубина сканирования %u.",
|
||||
/* English: Backport overlay failed: %s\n%s\n0x%08X */
|
||||
[SM_L10N_BACKPORT_OVERLAY_FAILED] = "Не удалось смонтировать оверлей бэкпорта: %s\n%s\n0x%08X",
|
||||
/* English: Backport applied to game: %s */
|
||||
[SM_L10N_GAME_BACKPORTED] = "К игре %s применён бэкпорт",
|
||||
/* English: Backport applied to game: %s\nEmulators updated */
|
||||
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "К игре %s применён бэкпорт\nЭмуляторы обновлены",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Не удалось смонтировать образ:\n%s\nРазмер кластера слишком мал (%llu < %u).\nСохранено значение image_sector: %u.\nПопробуйте смонтировать снова.",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "Не удалось смонтировать образ:\n%s\nРазмер кластера слишком мал (%llu < %u).\nУвеличьте размер кластера в образе или уменьшите размер сектора в конфигурации.",
|
||||
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
|
||||
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "Не удалось зарегистрировать: %s (%s)\nAppInstallTitleDir недоступен",
|
||||
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
|
||||
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "Не удалось зарегистрировать: %s (%s)\nМост TitleDir недоступен",
|
||||
/* English: Register failed: %s (%s)\ncode=0x%08X */
|
||||
[SM_L10N_REGISTER_FAILED_CODE] = "Не удалось зарегистрировать: %s (%s)\nкод=0x%08X",
|
||||
/* English: Installing: %s (%s)... */
|
||||
[SM_L10N_INSTALLING] = "Устанавливается: %s (%s)...",
|
||||
/* English: KStuff active again */
|
||||
[SM_L10N_KSTUFF_ACTIVE] = "KStuff снова активен",
|
||||
/* English: KStuff paused while the game is running */
|
||||
[SM_L10N_KSTUFF_PAUSED] = "Пока игра запущена, KStuff приостановлен",
|
||||
/* English: Installed: %s */
|
||||
[SM_L10N_INSTALLED] = "Установлено: %s",
|
||||
/* English: Unknown */
|
||||
[SM_L10N_UNKNOWN] = "Неизвестно",
|
||||
/* English: Image mount failed: 0x%08X (%s)\n%s */
|
||||
[SM_L10N_IMAGE_MOUNT_FAILED] = "Не удалось смонтировать образ: 0x%08X (%s)\n%s",
|
||||
/* English: Filesystem cluster size (%llu) is smaller than device sector size (%u) for %s */
|
||||
[SM_L10N_FS_CLUSTER_SMALL] = "Размер кластера файловой системы (%llu) меньше размера сектора устройства (%u) для %s",
|
||||
/* English: Mounted image is unreadable or damaged */
|
||||
[SM_L10N_IMAGE_UNREADABLE_DAMAGED] = "Смонтированный образ не читается или повреждён",
|
||||
/* English: Image cache full (%u entries), cannot track mount %s */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_TRACK] = "Кэш образов заполнен (%u записей), невозможно отследить монтирование %s",
|
||||
/* English: Image cache full (%u entries), rolling back mount */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_ROLLBACK] = "Кэш образов заполнен (%u записей), монтирование отменяется",
|
||||
/* English: Install failed: %s (%s)\nTimed out after 5 minutes */
|
||||
[SM_L10N_INSTALL_FAILED_TIMEOUT] = "Сбой установки: %s (%s)\nПревышено время ожидания (5 минут)",
|
||||
/* English: Batch install queued (%d): */
|
||||
[SM_L10N_BATCH_INSTALL_QUEUED] = "Пакетная установка добавлена в очередь (%d):",
|
||||
/* English: \n... and %d more */
|
||||
[SM_L10N_BATCH_INSTALL_AND_MORE] = "\n... и ещё %d",
|
||||
/* English: Batch install failed for %d app(s).\ncode=0x%08X */
|
||||
[SM_L10N_BATCH_INSTALL_FAILED] = "Сбой пакетной установки для %d приложений.\nкод=0x%08X",
|
||||
/* English: %s crashed before KStuff was paused. KStuff is not the cause. */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF] = "Приложение аварийно завершилось до приостановки KStuff: %s. Причина не в KStuff.",
|
||||
/* English: %s crashed before KStuff pause:\n%s */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF_FATAL] = "Приложение %s аварийно завершилось до приостановки KStuff:\n%s",
|
||||
/* English: %s crashed after KStuff was paused. Automatic tuning skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_SKIPPED] = "Приложение аварийно завершилось после приостановки KStuff: %s. Автоподбор задержки пропущен.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nAutotune skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_FATAL_SKIPPED] = "Приложение %s аварийно завершилось после приостановки KStuff:\n%s\nАвтоподбор задержки пропущен.",
|
||||
/* English: Can't load module after KStuff pause */
|
||||
[SM_L10N_RTLD_AFTER_KSTUFF_FALLBACK] = "Не удалось загрузить модуль после приостановки KStuff",
|
||||
/* English: can't load module %.*s after KStuff pause */
|
||||
[SM_L10N_RTLD_MODULE_AFTER_KSTUFF] = "не удалось загрузить модуль %.*s после приостановки KStuff",
|
||||
/* English: %s: %s. Delay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_DELAY_INCREASED_RELAUNCH] = "%s: %s. Задержка увеличена до %u с.\nЗапустите игру снова.",
|
||||
/* English: Crash detected after KStuff pause: pause delay for %s increased to %us. Launch the game again. */
|
||||
[SM_L10N_CRASH_DELAY_INCREASED] = "Обнаружен сбой после приостановки KStuff: задержка для %s увеличена до %u с. Запустите игру снова.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nDelay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_INCREASED] = "Приложение %s аварийно завершилось после приостановки KStuff:\n%s\nЗадержка увеличена до %u с.\nЗапустите игру снова.",
|
||||
/* English: Crash detected after KStuff pause: %s. Failed to update autotune delay. */
|
||||
[SM_L10N_CRASH_DELAY_UPDATE_FAILED] = "Обнаружен сбой после приостановки KStuff: %s. Не удалось обновить задержку автоподбора.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nFailed to update autotune delay. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_UPDATE_FAILED] = "Приложение %s аварийно завершилось после приостановки KStuff:\n%s\nНе удалось обновить задержку автоподбора.",
|
||||
/* English: Missing/invalid param.json:\n%s */
|
||||
[SM_L10N_MISSING_PARAM] = "Отсутствует или некорректен файл param.json:\n%s",
|
||||
/* English: Found %d new games. Processing... */
|
||||
[SM_L10N_FOUND_NEW_GAMES] = "Найдено %d новых игр.\nВыполняется обновление библиотеки.",
|
||||
/* English: Library synchronized.\nFound %d games. */
|
||||
[SM_L10N_LIBRARY_SYNCED] = "Библиотека обновлена\nНайдено %d игр(ы)",
|
||||
/* English: ShadowMount+: config reloaded. */
|
||||
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: конфигурация перезагружена.",
|
||||
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
|
||||
[SM_L10N_DUPLICATE_IGNORED] = "Дубликат %s пропущен:\n%s\nуже существует: %s",
|
||||
/* English: AMPR emulator updated */
|
||||
[SM_L10N_AMPR_EMULATOR_UPDATED] = "Эмулятор AMPR обновлён",
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
static const char *const g_sv_se[SM_L10N_COUNT] = {
|
||||
/* English: Go to Debug Settings */
|
||||
[SM_L10N_ACTION_DEBUG_SETTINGS] = "Gå till felsökningsinställningar",
|
||||
/* English: ShadowMount+ v%s exFAT/UFS/PFS */
|
||||
[SM_L10N_STARTUP] = "ShadowMount+ v%s exFAT/UFS/PFS",
|
||||
/* English: ShadowMount+: recursive_scan=1 is deprecated, using scan_depth=2. */
|
||||
[SM_L10N_RECURSIVE_SCAN_DEPRECATED] = "ShadowMount+: recursive_scan=1 är föråldrat, använder scan_depth=2.",
|
||||
/* English: ShadowMount+: scan depth %u enabled. */
|
||||
[SM_L10N_SCAN_DEPTH_ENABLED] = "ShadowMount+: skanningsdjup %u aktiverat.",
|
||||
/* English: Backport overlay failed: %s\n%s\n0x%08X */
|
||||
[SM_L10N_BACKPORT_OVERLAY_FAILED] = "Backport-overlay misslyckades: %s\n%s\n0x%08X",
|
||||
/* English: Backport applied to game: %s */
|
||||
[SM_L10N_GAME_BACKPORTED] = "Backport tillämpad på spelet: %s",
|
||||
/* English: Backport applied to game: %s\nEmulators updated */
|
||||
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Backport tillämpad på spelet: %s\nEmulatorer uppdaterade",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Image-montering nekades:\n%s\nKlusterstorleken är för liten (%llu < %u).\nimage_sector-åsidosättning sparad: %u.\nFörsök montera igen.",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "Montering av image nekades:\n%s\nKlusterstorleken är för liten (%llu < %u).\nAnvänd en större klusterstorlek i imagen eller en mindre sektorstorlek i konfigurationen.",
|
||||
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
|
||||
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "Registrering misslyckades: %s (%s)\nAppInstallTitleDir inte tillgänglig",
|
||||
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
|
||||
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "Registreringen misslyckades: %s (%s)\nTitleDir-bryggan är inte tillgänglig",
|
||||
/* English: Register failed: %s (%s)\ncode=0x%08X */
|
||||
[SM_L10N_REGISTER_FAILED_CODE] = "Registrering misslyckades: %s (%s)\nkod=0x%08X",
|
||||
/* English: Installing: %s (%s)... */
|
||||
[SM_L10N_INSTALLING] = "Installerar: %s (%s)...",
|
||||
/* English: KStuff active again */
|
||||
[SM_L10N_KSTUFF_ACTIVE] = "KStuff aktivt igen",
|
||||
/* English: KStuff paused while the game is running */
|
||||
[SM_L10N_KSTUFF_PAUSED] = "KStuff pausat medan spelet körs",
|
||||
/* English: Installed: %s */
|
||||
[SM_L10N_INSTALLED] = "Installerat: %s",
|
||||
/* English: Unknown */
|
||||
[SM_L10N_UNKNOWN] = "Okänd",
|
||||
/* English: Image mount failed: 0x%08X (%s)\n%s */
|
||||
[SM_L10N_IMAGE_MOUNT_FAILED] = "Image-montering misslyckades: 0x%08X (%s)\n%s",
|
||||
/* English: Filesystem cluster size (%llu) is smaller than device sector size (%u) for %s */
|
||||
[SM_L10N_FS_CLUSTER_SMALL] = "Filsystemets klusterstorlek (%llu) är mindre än enhetens sektorstorlek (%u) för %s",
|
||||
/* English: Mounted image is unreadable or damaged */
|
||||
[SM_L10N_IMAGE_UNREADABLE_DAMAGED] = "Monterad image är oläsbar eller skadad",
|
||||
/* English: Image cache full (%u entries), cannot track mount %s */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_TRACK] = "Image-cachen är full (%u poster), monteringen %s kan inte spåras",
|
||||
/* English: Image cache full (%u entries), rolling back mount */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_ROLLBACK] = "Image-cachen är full (%u poster), monteringen återställs",
|
||||
/* English: Install failed: %s (%s)\nTimed out after 5 minutes */
|
||||
[SM_L10N_INSTALL_FAILED_TIMEOUT] = "Installation misslyckades: %s (%s)\nTidsgräns efter 5 minuter",
|
||||
/* English: Batch install queued (%d): */
|
||||
[SM_L10N_BATCH_INSTALL_QUEUED] = "Batchinstallation köad (%d):",
|
||||
/* English: \n... and %d more */
|
||||
[SM_L10N_BATCH_INSTALL_AND_MORE] = "\n... och %d till",
|
||||
/* English: Batch install failed for %d app(s).\ncode=0x%08X */
|
||||
[SM_L10N_BATCH_INSTALL_FAILED] = "Batchinstallationen misslyckades för %d appar.\nkod=0x%08X",
|
||||
/* English: %s crashed before KStuff was paused. KStuff is not the cause. */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF] = "Appen %s kraschade innan KStuff pausades. KStuff är inte orsaken.",
|
||||
/* English: %s crashed before KStuff pause:\n%s */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF_FATAL] = "%s kraschade innan KStuff pausades:\n%s",
|
||||
/* English: %s crashed after KStuff was paused. Automatic tuning skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_SKIPPED] = "Appen %s kraschade efter att KStuff pausades. Automatisk justering hoppades över.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nAutotune skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_FATAL_SKIPPED] = "%s kraschade efter att KStuff pausades:\n%s\nAutomatisk justering hoppades över.",
|
||||
/* English: Can't load module after KStuff pause */
|
||||
[SM_L10N_RTLD_AFTER_KSTUFF_FALLBACK] = "Kan inte läsa in modul efter KStuff-paus",
|
||||
/* English: can't load module %.*s after KStuff pause */
|
||||
[SM_L10N_RTLD_MODULE_AFTER_KSTUFF] = "det går inte att läsa in modulen %.*s efter KStuff-pausen",
|
||||
/* English: %s: %s. Delay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_DELAY_INCREASED_RELAUNCH] = "%s: %s. Fördröjning ökad till %u s.\nStarta spelet igen.",
|
||||
/* English: Crash detected after KStuff pause: pause delay for %s increased to %us. Launch the game again. */
|
||||
[SM_L10N_CRASH_DELAY_INCREASED] = "Krasch upptäckt efter KStuff-pausen: pausfördröjningen för %s ökades till %u s. Starta spelet igen.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nDelay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_INCREASED] = "%s kraschade efter att KStuff pausades:\n%s\nFördröjningen ökades till %u s.\nStarta spelet igen.",
|
||||
/* English: Crash detected after KStuff pause: %s. Failed to update autotune delay. */
|
||||
[SM_L10N_CRASH_DELAY_UPDATE_FAILED] = "Krasch upptäckt efter KStuff-pausen: %s. Fördröjningen för automatisk justering kunde inte uppdateras.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nFailed to update autotune delay. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_UPDATE_FAILED] = "%s kraschade efter att KStuff pausades:\n%s\nDet gick inte att uppdatera fördröjningen för automatisk justering.",
|
||||
/* English: Missing/invalid param.json:\n%s */
|
||||
[SM_L10N_MISSING_PARAM] = "param.json saknas eller är ogiltig:\n%s",
|
||||
/* English: Found %d new games. Processing... */
|
||||
[SM_L10N_FOUND_NEW_GAMES] = "Hittade %d nya spel. Bearbetar...",
|
||||
/* English: Library synchronized.\nFound %d games. */
|
||||
[SM_L10N_LIBRARY_SYNCED] = "Bibliotek synkroniserat.\nHittade %d spel.",
|
||||
/* English: ShadowMount+: config reloaded. */
|
||||
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: konfigurationen lästes in på nytt.",
|
||||
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
|
||||
[SM_L10N_DUPLICATE_IGNORED] = "Dubblett %s ignorerad:\n%s\nbefintlig: %s",
|
||||
/* English: AMPR emulator updated */
|
||||
[SM_L10N_AMPR_EMULATOR_UPDATED] = "AMPR-emulatorn har uppdaterats",
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
static const char *const g_th_th[SM_L10N_COUNT] = {
|
||||
/* English: Go to Debug Settings */
|
||||
[SM_L10N_ACTION_DEBUG_SETTINGS] = "ไปที่การตั้งค่าดีบัก",
|
||||
/* English: ShadowMount+ v%s exFAT/UFS/PFS */
|
||||
[SM_L10N_STARTUP] = "ShadowMount+ v%s exFAT/UFS/PFS",
|
||||
/* English: ShadowMount+: recursive_scan=1 is deprecated, using scan_depth=2. */
|
||||
[SM_L10N_RECURSIVE_SCAN_DEPRECATED] = "ShadowMount+: recursive_scan=1 เลิกใช้แล้ว จะใช้ scan_depth=2",
|
||||
/* English: ShadowMount+: scan depth %u enabled. */
|
||||
[SM_L10N_SCAN_DEPTH_ENABLED] = "ShadowMount+: เปิดใช้ความลึกการสแกน %u",
|
||||
/* English: Backport overlay failed: %s\n%s\n0x%08X */
|
||||
[SM_L10N_BACKPORT_OVERLAY_FAILED] = "backport overlay ล้มเหลว: %s\n%s\n0x%08X",
|
||||
/* English: Backport applied to game: %s */
|
||||
[SM_L10N_GAME_BACKPORTED] = "ใช้ backport กับเกมแล้ว: %s",
|
||||
/* English: Backport applied to game: %s\nEmulators updated */
|
||||
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "ใช้ backport กับเกมแล้ว: %s\nอัปเดตอีมูเลเตอร์หลายตัวแล้ว",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "ปฏิเสธการเมานต์อิมเมจ:\n%s\nขนาดคลัสเตอร์เล็กเกินไป (%llu < %u)\nบันทึกค่า image_sector แล้ว: %u\nลองเมานต์อีกครั้ง",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "ปฏิเสธการเมานต์อิมเมจ:\n%s\nขนาดคลัสเตอร์เล็กเกินไป (%llu < %u)\nใช้คลัสเตอร์ที่ใหญ่ขึ้นในอิมเมจ หรือลดขนาดเซกเตอร์ในการตั้งค่า",
|
||||
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
|
||||
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "ลงทะเบียนล้มเหลว: %s (%s)\nAppInstallTitleDir ไม่พร้อมใช้งาน",
|
||||
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
|
||||
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "ลงทะเบียนไม่สำเร็จ: %s (%s)\nบริดจ์ TitleDir ไม่พร้อมใช้งาน",
|
||||
/* English: Register failed: %s (%s)\ncode=0x%08X */
|
||||
[SM_L10N_REGISTER_FAILED_CODE] = "ลงทะเบียนล้มเหลว: %s (%s)\nรหัส=0x%08X",
|
||||
/* English: Installing: %s (%s)... */
|
||||
[SM_L10N_INSTALLING] = "กำลังติดตั้ง: %s (%s)...",
|
||||
/* English: KStuff active again */
|
||||
[SM_L10N_KSTUFF_ACTIVE] = "KStuff ทำงานอีกครั้ง",
|
||||
/* English: KStuff paused while the game is running */
|
||||
[SM_L10N_KSTUFF_PAUSED] = "พัก KStuff ขณะเกมกำลังทำงาน",
|
||||
/* English: Installed: %s */
|
||||
[SM_L10N_INSTALLED] = "ติดตั้งแล้ว: %s",
|
||||
/* English: Unknown */
|
||||
[SM_L10N_UNKNOWN] = "ไม่ทราบ",
|
||||
/* English: Image mount failed: 0x%08X (%s)\n%s */
|
||||
[SM_L10N_IMAGE_MOUNT_FAILED] = "เมานต์อิมเมจไม่สำเร็จ: 0x%08X (%s)\n%s",
|
||||
/* English: Filesystem cluster size (%llu) is smaller than device sector size (%u) for %s */
|
||||
[SM_L10N_FS_CLUSTER_SMALL] = "ขนาด cluster ของ filesystem (%llu) เล็กกว่าขนาด sector ของอุปกรณ์ (%u) สำหรับ %s",
|
||||
/* English: Mounted image is unreadable or damaged */
|
||||
[SM_L10N_IMAGE_UNREADABLE_DAMAGED] = "อิมเมจที่เมานต์ไว้อ่านไม่ได้หรือเสียหาย",
|
||||
/* English: Image cache full (%u entries), cannot track mount %s */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_TRACK] = "แคชอิมเมจเต็ม (%u รายการ) จึงติดตามการเมานต์ %s ไม่ได้",
|
||||
/* English: Image cache full (%u entries), rolling back mount */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_ROLLBACK] = "แคชอิมเมจเต็ม (%u รายการ) กำลังย้อนกลับการเมานต์",
|
||||
/* English: Install failed: %s (%s)\nTimed out after 5 minutes */
|
||||
[SM_L10N_INSTALL_FAILED_TIMEOUT] = "ติดตั้งล้มเหลว: %s (%s)\nหมดเวลาหลัง 5 นาที",
|
||||
/* English: Batch install queued (%d): */
|
||||
[SM_L10N_BATCH_INSTALL_QUEUED] = "เพิ่มการติดตั้งแบบชุดเข้าคิวแล้ว (%d):",
|
||||
/* English: \n... and %d more */
|
||||
[SM_L10N_BATCH_INSTALL_AND_MORE] = "\n... และอีก %d",
|
||||
/* English: Batch install failed for %d app(s).\ncode=0x%08X */
|
||||
[SM_L10N_BATCH_INSTALL_FAILED] = "การติดตั้งแบบชุดล้มเหลวสำหรับ %d แอป\nรหัส=0x%08X",
|
||||
/* English: %s crashed before KStuff was paused. KStuff is not the cause. */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF] = "แอป %s ขัดข้องก่อนพัก KStuff โดย KStuff ไม่ใช่สาเหตุ",
|
||||
/* English: %s crashed before KStuff pause:\n%s */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF_FATAL] = "%s ขัดข้องก่อนหยุด KStuff ชั่วคราว:\n%s",
|
||||
/* English: %s crashed after KStuff was paused. Automatic tuning skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_SKIPPED] = "แอป %s ขัดข้องหลังพัก KStuff จึงข้ามการปรับอัตโนมัติ",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nAutotune skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_FATAL_SKIPPED] = "%s ขัดข้องหลังหยุด KStuff ชั่วคราว:\n%s\nข้ามการปรับอัตโนมัติ",
|
||||
/* English: Can't load module after KStuff pause */
|
||||
[SM_L10N_RTLD_AFTER_KSTUFF_FALLBACK] = "โหลดโมดูลหลังพัก KStuff ไม่ได้",
|
||||
/* English: can't load module %.*s after KStuff pause */
|
||||
[SM_L10N_RTLD_MODULE_AFTER_KSTUFF] = "ไม่สามารถโหลดโมดูล %.*s หลังหยุด KStuff ชั่วคราว",
|
||||
/* English: %s: %s. Delay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_DELAY_INCREASED_RELAUNCH] = "%s: %s. เพิ่ม delay เป็น %u วินาที\nเปิดเกมอีกครั้ง",
|
||||
/* English: Crash detected after KStuff pause: pause delay for %s increased to %us. Launch the game again. */
|
||||
[SM_L10N_CRASH_DELAY_INCREASED] = "พบการขัดข้องหลังพัก KStuff: เพิ่มเวลาหน่วงของ %s เป็น %u วินาที เปิดเกมอีกครั้ง",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nDelay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_INCREASED] = "%s ขัดข้องหลังหยุด KStuff ชั่วคราว:\n%s\nเพิ่มเวลาหน่วงเป็น %u วินาที\nเปิดเกมอีกครั้ง",
|
||||
/* English: Crash detected after KStuff pause: %s. Failed to update autotune delay. */
|
||||
[SM_L10N_CRASH_DELAY_UPDATE_FAILED] = "พบการขัดข้องหลังพัก KStuff: %s ไม่สามารถอัปเดตเวลาหน่วงการปรับอัตโนมัติ",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nFailed to update autotune delay. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_UPDATE_FAILED] = "%s ขัดข้องหลังหยุด KStuff ชั่วคราว:\n%s\nไม่สามารถอัปเดตเวลาหน่วงการปรับอัตโนมัติ",
|
||||
/* English: Missing/invalid param.json:\n%s */
|
||||
[SM_L10N_MISSING_PARAM] = "param.json หายไปหรือไม่ถูกต้อง:\n%s",
|
||||
/* English: Found %d new games. Processing... */
|
||||
[SM_L10N_FOUND_NEW_GAMES] = "พบเกมใหม่ %d เกม กำลังประมวลผล...",
|
||||
/* English: Library synchronized.\nFound %d games. */
|
||||
[SM_L10N_LIBRARY_SYNCED] = "ซิงค์ไลบรารีแล้ว\nพบ %d เกม",
|
||||
/* English: ShadowMount+: config reloaded. */
|
||||
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: โหลดการตั้งค่าใหม่แล้ว",
|
||||
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
|
||||
[SM_L10N_DUPLICATE_IGNORED] = "ข้ามรายการซ้ำ %s:\n%s\nที่มีอยู่: %s",
|
||||
/* English: AMPR emulator updated */
|
||||
[SM_L10N_AMPR_EMULATOR_UPDATED] = "อัปเดตโปรแกรมจำลอง AMPR แล้ว",
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
static const char *const g_tr_tr[SM_L10N_COUNT] = {
|
||||
/* English: Go to Debug Settings */
|
||||
[SM_L10N_ACTION_DEBUG_SETTINGS] = "Hata ayıklama ayarlarına git",
|
||||
/* English: ShadowMount+ v%s exFAT/UFS/PFS */
|
||||
[SM_L10N_STARTUP] = "ShadowMount+ v%s exFAT/UFS/PFS",
|
||||
/* English: ShadowMount+: recursive_scan=1 is deprecated, using scan_depth=2. */
|
||||
[SM_L10N_RECURSIVE_SCAN_DEPRECATED] = "ShadowMount+: recursive_scan=1 kullanımdan kaldırıldı; scan_depth=2 kullanılacak.",
|
||||
/* English: ShadowMount+: scan depth %u enabled. */
|
||||
[SM_L10N_SCAN_DEPTH_ENABLED] = "ShadowMount+: tarama derinliği %u etkin.",
|
||||
/* English: Backport overlay failed: %s\n%s\n0x%08X */
|
||||
[SM_L10N_BACKPORT_OVERLAY_FAILED] = "Backport overlay başarısız: %s\n%s\n0x%08X",
|
||||
/* English: Backport applied to game: %s */
|
||||
[SM_L10N_GAME_BACKPORTED] = "Oyuna backport uygulandı: %s",
|
||||
/* English: Backport applied to game: %s\nEmulators updated */
|
||||
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Oyuna backport uygulandı: %s\nEmülatörler güncellendi",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "İmaj bağlama işlemi reddedildi:\n%s\nKüme boyutu çok küçük (%llu < %u).\nimage_sector değeri kaydedildi: %u.\nBağlamayı yeniden deneyin.",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "İmaj bağlama işlemi reddedildi:\n%s\nKüme boyutu çok küçük (%llu < %u).\nİmajda daha büyük bir küme veya yapılandırmada daha küçük bir sektör boyutu kullanın.",
|
||||
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
|
||||
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "Kayıt başarısız: %s (%s)\nAppInstallTitleDir kullanılamıyor",
|
||||
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
|
||||
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "Kayıt başarısız: %s (%s)\nTitleDir köprüsü kullanılamıyor",
|
||||
/* English: Register failed: %s (%s)\ncode=0x%08X */
|
||||
[SM_L10N_REGISTER_FAILED_CODE] = "Kayıt başarısız: %s (%s)\nkod=0x%08X",
|
||||
/* English: Installing: %s (%s)... */
|
||||
[SM_L10N_INSTALLING] = "Yükleniyor: %s (%s)...",
|
||||
/* English: KStuff active again */
|
||||
[SM_L10N_KSTUFF_ACTIVE] = "KStuff tekrar etkin",
|
||||
/* English: KStuff paused while the game is running */
|
||||
[SM_L10N_KSTUFF_PAUSED] = "Oyun çalışırken KStuff duraklatıldı",
|
||||
/* English: Installed: %s */
|
||||
[SM_L10N_INSTALLED] = "Yüklendi: %s",
|
||||
/* English: Unknown */
|
||||
[SM_L10N_UNKNOWN] = "Bilinmiyor",
|
||||
/* English: Image mount failed: 0x%08X (%s)\n%s */
|
||||
[SM_L10N_IMAGE_MOUNT_FAILED] = "İmaj bağlama işlemi başarısız: 0x%08X (%s)\n%s",
|
||||
/* English: Filesystem cluster size (%llu) is smaller than device sector size (%u) for %s */
|
||||
[SM_L10N_FS_CLUSTER_SMALL] = "Dosya sistemi küme boyutu (%llu), aygıt sektör boyutundan (%u) küçük: %s",
|
||||
/* English: Mounted image is unreadable or damaged */
|
||||
[SM_L10N_IMAGE_UNREADABLE_DAMAGED] = "Bağlanan imaj okunamıyor veya hasarlı",
|
||||
/* English: Image cache full (%u entries), cannot track mount %s */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_TRACK] = "İmaj önbelleği dolu (%u kayıt), %s bağlama işlemi izlenemiyor",
|
||||
/* English: Image cache full (%u entries), rolling back mount */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_ROLLBACK] = "İmaj önbelleği dolu (%u kayıt), bağlama işlemi geri alınıyor",
|
||||
/* English: Install failed: %s (%s)\nTimed out after 5 minutes */
|
||||
[SM_L10N_INSTALL_FAILED_TIMEOUT] = "Yükleme başarısız: %s (%s)\n5 dakika sonra zaman aşımı",
|
||||
/* English: Batch install queued (%d): */
|
||||
[SM_L10N_BATCH_INSTALL_QUEUED] = "Toplu yükleme sıraya alındı (%d):",
|
||||
/* English: \n... and %d more */
|
||||
[SM_L10N_BATCH_INSTALL_AND_MORE] = "\n... ve %d tane daha",
|
||||
/* English: Batch install failed for %d app(s).\ncode=0x%08X */
|
||||
[SM_L10N_BATCH_INSTALL_FAILED] = "%d uygulama için toplu yükleme başarısız.\nkod=0x%08X",
|
||||
/* English: %s crashed before KStuff was paused. KStuff is not the cause. */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF] = "%s, KStuff duraklatılmadan önce çöktü. Sorunun nedeni KStuff değil.",
|
||||
/* English: %s crashed before KStuff pause:\n%s */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF_FATAL] = "%s, KStuff duraklatılmadan önce çöktü:\n%s",
|
||||
/* English: %s crashed after KStuff was paused. Automatic tuning skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_SKIPPED] = "%s, KStuff duraklatıldıktan sonra çöktü. Otomatik ayarlama atlandı.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nAutotune skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_FATAL_SKIPPED] = "%s, KStuff duraklatıldıktan sonra çöktü:\n%s\nOtomatik ayarlama atlandı.",
|
||||
/* English: Can't load module after KStuff pause */
|
||||
[SM_L10N_RTLD_AFTER_KSTUFF_FALLBACK] = "KStuff duraklamasından sonra modül yüklenemiyor",
|
||||
/* English: can't load module %.*s after KStuff pause */
|
||||
[SM_L10N_RTLD_MODULE_AFTER_KSTUFF] = "KStuff duraklatıldıktan sonra %.*s modülü yüklenemiyor",
|
||||
/* English: %s: %s. Delay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_DELAY_INCREASED_RELAUNCH] = "%s: %s. Gecikme %u s değerine çıkarıldı.\nOyunu tekrar başlatın.",
|
||||
/* English: Crash detected after KStuff pause: pause delay for %s increased to %us. Launch the game again. */
|
||||
[SM_L10N_CRASH_DELAY_INCREASED] = "KStuff duraklatıldıktan sonra çökme algılandı: %s için gecikme %u sn. olarak artırıldı. Oyunu yeniden başlatın.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nDelay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_INCREASED] = "%s, KStuff duraklatıldıktan sonra çöktü:\n%s\nGecikme %u sn. olarak artırıldı.\nOyunu yeniden başlatın.",
|
||||
/* English: Crash detected after KStuff pause: %s. Failed to update autotune delay. */
|
||||
[SM_L10N_CRASH_DELAY_UPDATE_FAILED] = "KStuff duraklatıldıktan sonra çökme algılandı: %s. Otomatik ayarlama gecikmesi güncellenemedi.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nFailed to update autotune delay. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_UPDATE_FAILED] = "%s, KStuff duraklatıldıktan sonra çöktü:\n%s\nOtomatik ayarlama gecikmesi güncellenemedi.",
|
||||
/* English: Missing/invalid param.json:\n%s */
|
||||
[SM_L10N_MISSING_PARAM] = "param.json eksik veya geçersiz:\n%s",
|
||||
/* English: Found %d new games. Processing... */
|
||||
[SM_L10N_FOUND_NEW_GAMES] = "%d yeni oyun bulundu. İşleniyor...",
|
||||
/* English: Library synchronized.\nFound %d games. */
|
||||
[SM_L10N_LIBRARY_SYNCED] = "Kütüphane senkronize edildi.\n%d oyun bulundu.",
|
||||
/* English: ShadowMount+: config reloaded. */
|
||||
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: yapılandırma yeniden yüklendi.",
|
||||
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
|
||||
[SM_L10N_DUPLICATE_IGNORED] = "Yinelenen %s yok sayıldı:\n%s\nmevcut: %s",
|
||||
/* English: AMPR emulator updated */
|
||||
[SM_L10N_AMPR_EMULATOR_UPDATED] = "AMPR emülatörü güncellendi",
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
static const char *const g_vi_vn[SM_L10N_COUNT] = {
|
||||
/* English: Go to Debug Settings */
|
||||
[SM_L10N_ACTION_DEBUG_SETTINGS] = "Đi tới cài đặt gỡ lỗi",
|
||||
/* English: ShadowMount+ v%s exFAT/UFS/PFS */
|
||||
[SM_L10N_STARTUP] = "ShadowMount+ v%s exFAT/UFS/PFS",
|
||||
/* English: ShadowMount+: recursive_scan=1 is deprecated, using scan_depth=2. */
|
||||
[SM_L10N_RECURSIVE_SCAN_DEPRECATED] = "ShadowMount+: recursive_scan=1 đã lỗi thời; sẽ sử dụng scan_depth=2.",
|
||||
/* English: ShadowMount+: scan depth %u enabled. */
|
||||
[SM_L10N_SCAN_DEPTH_ENABLED] = "ShadowMount+: đã bật độ sâu quét %u.",
|
||||
/* English: Backport overlay failed: %s\n%s\n0x%08X */
|
||||
[SM_L10N_BACKPORT_OVERLAY_FAILED] = "Backport overlay thất bại: %s\n%s\n0x%08X",
|
||||
/* English: Backport applied to game: %s */
|
||||
[SM_L10N_GAME_BACKPORTED] = "Đã áp dụng backport cho game: %s",
|
||||
/* English: Backport applied to game: %s\nEmulators updated */
|
||||
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Đã áp dụng backport cho game: %s\nĐã cập nhật các trình giả lập",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Từ chối gắn ảnh đĩa:\n%s\nKích thước cụm quá nhỏ (%llu < %u).\nĐã lưu giá trị image_sector: %u.\nHãy thử gắn lại.",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "Từ chối gắn ảnh đĩa:\n%s\nKích thước cụm quá nhỏ (%llu < %u).\nHãy dùng cụm lớn hơn trong ảnh đĩa hoặc kích thước cung từ nhỏ hơn trong cấu hình.",
|
||||
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
|
||||
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "Đăng ký thất bại: %s (%s)\nAppInstallTitleDir không khả dụng",
|
||||
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
|
||||
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "Đăng ký thất bại: %s (%s)\nCầu nối TitleDir không khả dụng",
|
||||
/* English: Register failed: %s (%s)\ncode=0x%08X */
|
||||
[SM_L10N_REGISTER_FAILED_CODE] = "Đăng ký thất bại: %s (%s)\nmã=0x%08X",
|
||||
/* English: Installing: %s (%s)... */
|
||||
[SM_L10N_INSTALLING] = "Đang cài đặt: %s (%s)...",
|
||||
/* English: KStuff active again */
|
||||
[SM_L10N_KSTUFF_ACTIVE] = "KStuff hoạt động lại",
|
||||
/* English: KStuff paused while the game is running */
|
||||
[SM_L10N_KSTUFF_PAUSED] = "KStuff tạm dừng khi game đang chạy",
|
||||
/* English: Installed: %s */
|
||||
[SM_L10N_INSTALLED] = "Đã cài đặt: %s",
|
||||
/* English: Unknown */
|
||||
[SM_L10N_UNKNOWN] = "Không xác định",
|
||||
/* English: Image mount failed: 0x%08X (%s)\n%s */
|
||||
[SM_L10N_IMAGE_MOUNT_FAILED] = "Không thể gắn ảnh đĩa: 0x%08X (%s)\n%s",
|
||||
/* English: Filesystem cluster size (%llu) is smaller than device sector size (%u) for %s */
|
||||
[SM_L10N_FS_CLUSTER_SMALL] = "Kích thước cluster của hệ thống tệp (%llu) nhỏ hơn kích thước sector thiết bị (%u) cho %s",
|
||||
/* English: Mounted image is unreadable or damaged */
|
||||
[SM_L10N_IMAGE_UNREADABLE_DAMAGED] = "Ảnh đĩa đã gắn không thể đọc được hoặc bị hỏng",
|
||||
/* English: Image cache full (%u entries), cannot track mount %s */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_TRACK] = "Bộ nhớ đệm ảnh đĩa đã đầy (%u mục), không thể theo dõi điểm gắn %s",
|
||||
/* English: Image cache full (%u entries), rolling back mount */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_ROLLBACK] = "Bộ nhớ đệm ảnh đĩa đã đầy (%u mục), đang hoàn tác việc gắn",
|
||||
/* English: Install failed: %s (%s)\nTimed out after 5 minutes */
|
||||
[SM_L10N_INSTALL_FAILED_TIMEOUT] = "Cài đặt thất bại: %s (%s)\nHết thời gian sau 5 phút",
|
||||
/* English: Batch install queued (%d): */
|
||||
[SM_L10N_BATCH_INSTALL_QUEUED] = "Cài đặt hàng loạt đã vào hàng đợi (%d):",
|
||||
/* English: \n... and %d more */
|
||||
[SM_L10N_BATCH_INSTALL_AND_MORE] = "\n... và thêm %d",
|
||||
/* English: Batch install failed for %d app(s).\ncode=0x%08X */
|
||||
[SM_L10N_BATCH_INSTALL_FAILED] = "Cài đặt hàng loạt thất bại cho %d ứng dụng.\nmã=0x%08X",
|
||||
/* English: %s crashed before KStuff was paused. KStuff is not the cause. */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF] = "Ứng dụng %s gặp sự cố trước khi KStuff tạm dừng. KStuff không phải là nguyên nhân.",
|
||||
/* English: %s crashed before KStuff pause:\n%s */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF_FATAL] = "%s gặp sự cố trước khi tạm dừng KStuff:\n%s",
|
||||
/* English: %s crashed after KStuff was paused. Automatic tuning skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_SKIPPED] = "Ứng dụng %s gặp sự cố sau khi KStuff tạm dừng. Đã bỏ qua tinh chỉnh tự động.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nAutotune skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_FATAL_SKIPPED] = "%s gặp sự cố sau khi tạm dừng KStuff:\n%s\nĐã bỏ qua tự động tinh chỉnh.",
|
||||
/* English: Can't load module after KStuff pause */
|
||||
[SM_L10N_RTLD_AFTER_KSTUFF_FALLBACK] = "Không thể tải module sau khi KStuff tạm dừng",
|
||||
/* English: can't load module %.*s after KStuff pause */
|
||||
[SM_L10N_RTLD_MODULE_AFTER_KSTUFF] = "không thể tải mô-đun %.*s sau khi tạm dừng KStuff",
|
||||
/* English: %s: %s. Delay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_DELAY_INCREASED_RELAUNCH] = "%s: %s. Độ trễ đã tăng lên %u giây.\nHãy khởi chạy lại trò chơi.",
|
||||
/* English: Crash detected after KStuff pause: pause delay for %s increased to %us. Launch the game again. */
|
||||
[SM_L10N_CRASH_DELAY_INCREASED] = "Phát hiện sự cố sau khi KStuff tạm dừng: độ trễ của %s đã tăng lên %u giây. Hãy khởi chạy lại trò chơi.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nDelay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_INCREASED] = "%s gặp sự cố sau khi tạm dừng KStuff:\n%s\nĐộ trễ đã tăng lên %u giây.\nHãy khởi chạy lại trò chơi.",
|
||||
/* English: Crash detected after KStuff pause: %s. Failed to update autotune delay. */
|
||||
[SM_L10N_CRASH_DELAY_UPDATE_FAILED] = "Phát hiện sự cố sau khi KStuff tạm dừng: %s. Không thể cập nhật độ trễ tinh chỉnh tự động.",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nFailed to update autotune delay. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_UPDATE_FAILED] = "%s gặp sự cố sau khi tạm dừng KStuff:\n%s\nKhông thể cập nhật độ trễ tự động tinh chỉnh.",
|
||||
/* English: Missing/invalid param.json:\n%s */
|
||||
[SM_L10N_MISSING_PARAM] = "param.json thiếu hoặc không hợp lệ:\n%s",
|
||||
/* English: Found %d new games. Processing... */
|
||||
[SM_L10N_FOUND_NEW_GAMES] = "Tìm thấy %d trò chơi mới. Đang xử lý...",
|
||||
/* English: Library synchronized.\nFound %d games. */
|
||||
[SM_L10N_LIBRARY_SYNCED] = "Đã đồng bộ thư viện.\nTìm thấy %d trò chơi.",
|
||||
/* English: ShadowMount+: config reloaded. */
|
||||
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: đã tải lại cấu hình.",
|
||||
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
|
||||
[SM_L10N_DUPLICATE_IGNORED] = "Đã bỏ qua bản trùng %s:\n%s\nđã có: %s",
|
||||
/* English: AMPR emulator updated */
|
||||
[SM_L10N_AMPR_EMULATOR_UPDATED] = "Đã cập nhật trình giả lập AMPR",
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
static const char *const g_zh_cn[SM_L10N_COUNT] = {
|
||||
/* English: Go to Debug Settings */
|
||||
[SM_L10N_ACTION_DEBUG_SETTINGS] = "前往调试设置",
|
||||
/* English: ShadowMount+ v%s exFAT/UFS/PFS */
|
||||
[SM_L10N_STARTUP] = "ShadowMount+ v%s exFAT/UFS/PFS",
|
||||
/* English: ShadowMount+: recursive_scan=1 is deprecated, using scan_depth=2. */
|
||||
[SM_L10N_RECURSIVE_SCAN_DEPRECATED] = "ShadowMount+: recursive_scan=1 已弃用,将使用 scan_depth=2。",
|
||||
/* English: ShadowMount+: scan depth %u enabled. */
|
||||
[SM_L10N_SCAN_DEPTH_ENABLED] = "ShadowMount+: 已启用扫描深度 %u。",
|
||||
/* English: Backport overlay failed: %s\n%s\n0x%08X */
|
||||
[SM_L10N_BACKPORT_OVERLAY_FAILED] = "向下兼容覆盖层挂载失败:%s\n%s\n0x%08X",
|
||||
/* English: Backport applied to game: %s */
|
||||
[SM_L10N_GAME_BACKPORTED] = "已为游戏应用向下兼容补丁:%s",
|
||||
/* English: Backport applied to game: %s\nEmulators updated */
|
||||
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "已为游戏应用向下兼容补丁:%s\n多个模拟器已更新",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "镜像挂载被拒绝:\n%s\n簇大小太小 (%llu < %u)。\n已保存 image_sector 覆盖值: %u。\n请再次尝试挂载。",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "镜像挂载被拒绝:\n%s\n簇大小过小(%llu < %u)。\n请增大镜像的簇大小,或在配置中减小扇区大小。",
|
||||
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
|
||||
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "注册失败: %s (%s)\nAppInstallTitleDir 不可用",
|
||||
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
|
||||
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "注册失败:%s(%s)\nTitleDir 桥接不可用",
|
||||
/* English: Register failed: %s (%s)\ncode=0x%08X */
|
||||
[SM_L10N_REGISTER_FAILED_CODE] = "注册失败: %s (%s)\n代码=0x%08X",
|
||||
/* English: Installing: %s (%s)... */
|
||||
[SM_L10N_INSTALLING] = "正在安装: %s (%s)...",
|
||||
/* English: KStuff active again */
|
||||
[SM_L10N_KSTUFF_ACTIVE] = "KStuff 已重新启用",
|
||||
/* English: KStuff paused while the game is running */
|
||||
[SM_L10N_KSTUFF_PAUSED] = "游戏运行时 KStuff 已暂停",
|
||||
/* English: Installed: %s */
|
||||
[SM_L10N_INSTALLED] = "已安装: %s",
|
||||
/* English: Unknown */
|
||||
[SM_L10N_UNKNOWN] = "未知",
|
||||
/* English: Image mount failed: 0x%08X (%s)\n%s */
|
||||
[SM_L10N_IMAGE_MOUNT_FAILED] = "镜像挂载失败: 0x%08X (%s)\n%s",
|
||||
/* English: Filesystem cluster size (%llu) is smaller than device sector size (%u) for %s */
|
||||
[SM_L10N_FS_CLUSTER_SMALL] = "文件系统簇大小 (%llu) 小于设备扇区大小 (%u):%s",
|
||||
/* English: Mounted image is unreadable or damaged */
|
||||
[SM_L10N_IMAGE_UNREADABLE_DAMAGED] = "已挂载的镜像不可读或已损坏",
|
||||
/* English: Image cache full (%u entries), cannot track mount %s */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_TRACK] = "镜像缓存已满(%u 项),无法跟踪挂载 %s",
|
||||
/* English: Image cache full (%u entries), rolling back mount */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_ROLLBACK] = "镜像缓存已满(%u 项),正在撤销挂载",
|
||||
/* English: Install failed: %s (%s)\nTimed out after 5 minutes */
|
||||
[SM_L10N_INSTALL_FAILED_TIMEOUT] = "安装失败: %s (%s)\n5 分钟后超时",
|
||||
/* English: Batch install queued (%d): */
|
||||
[SM_L10N_BATCH_INSTALL_QUEUED] = "批量安装已排队 (%d):",
|
||||
/* English: \n... and %d more */
|
||||
[SM_L10N_BATCH_INSTALL_AND_MORE] = "\n... 以及另外 %d 个",
|
||||
/* English: Batch install failed for %d app(s).\ncode=0x%08X */
|
||||
[SM_L10N_BATCH_INSTALL_FAILED] = "%d 个应用批量安装失败。\n代码=0x%08X",
|
||||
/* English: %s crashed before KStuff was paused. KStuff is not the cause. */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF] = "%s 在暂停 KStuff 前崩溃。问题并非由 KStuff 引起。",
|
||||
/* English: %s crashed before KStuff pause:\n%s */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF_FATAL] = "%s 在暂停 KStuff 前崩溃:\n%s",
|
||||
/* English: %s crashed after KStuff was paused. Automatic tuning skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_SKIPPED] = "%s 在暂停 KStuff 后崩溃。已跳过自动调节。",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nAutotune skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_FATAL_SKIPPED] = "%s 在暂停 KStuff 后崩溃:\n%s\n已跳过自动调节。",
|
||||
/* English: Can't load module after KStuff pause */
|
||||
[SM_L10N_RTLD_AFTER_KSTUFF_FALLBACK] = "KStuff 暂停后无法加载模块",
|
||||
/* English: can't load module %.*s after KStuff pause */
|
||||
[SM_L10N_RTLD_MODULE_AFTER_KSTUFF] = "暂停 KStuff 后无法加载模块 %.*s",
|
||||
/* English: %s: %s. Delay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_DELAY_INCREASED_RELAUNCH] = "%s: %s。延迟已增加到 %u 秒。\n请重新启动游戏。",
|
||||
/* English: Crash detected after KStuff pause: pause delay for %s increased to %us. Launch the game again. */
|
||||
[SM_L10N_CRASH_DELAY_INCREASED] = "检测到暂停 KStuff 后发生崩溃:%s 的延迟已增加至 %u 秒。请重新启动游戏。",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nDelay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_INCREASED] = "%s 在暂停 KStuff 后崩溃:\n%s\n延迟已增加至 %u 秒。\n请重新启动游戏。",
|
||||
/* English: Crash detected after KStuff pause: %s. Failed to update autotune delay. */
|
||||
[SM_L10N_CRASH_DELAY_UPDATE_FAILED] = "检测到暂停 KStuff 后发生崩溃:%s。无法更新自动调节延迟。",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nFailed to update autotune delay. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_UPDATE_FAILED] = "%s 在暂停 KStuff 后崩溃:\n%s\n无法更新自动调节延迟。",
|
||||
/* English: Missing/invalid param.json:\n%s */
|
||||
[SM_L10N_MISSING_PARAM] = "param.json 缺失或无效:\n%s",
|
||||
/* English: Found %d new games. Processing... */
|
||||
[SM_L10N_FOUND_NEW_GAMES] = "发现 %d 个新游戏。正在处理...",
|
||||
/* English: Library synchronized.\nFound %d games. */
|
||||
[SM_L10N_LIBRARY_SYNCED] = "游戏库已同步。\n共发现 %d 个游戏。",
|
||||
/* English: ShadowMount+: config reloaded. */
|
||||
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+:配置已重新加载。",
|
||||
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
|
||||
[SM_L10N_DUPLICATE_IGNORED] = "已忽略重复项 %s:\n%s\n现有项:%s",
|
||||
/* English: AMPR emulator updated */
|
||||
[SM_L10N_AMPR_EMULATOR_UPDATED] = "AMPR 模拟器已更新",
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
static const char *const g_zh_tw[SM_L10N_COUNT] = {
|
||||
/* English: Go to Debug Settings */
|
||||
[SM_L10N_ACTION_DEBUG_SETTINGS] = "前往除錯設定",
|
||||
/* English: ShadowMount+ v%s exFAT/UFS/PFS */
|
||||
[SM_L10N_STARTUP] = "ShadowMount+ v%s exFAT/UFS/PFS",
|
||||
/* English: ShadowMount+: recursive_scan=1 is deprecated, using scan_depth=2. */
|
||||
[SM_L10N_RECURSIVE_SCAN_DEPRECATED] = "ShadowMount+: recursive_scan=1 已淘汰,將使用 scan_depth=2。",
|
||||
/* English: ShadowMount+: scan depth %u enabled. */
|
||||
[SM_L10N_SCAN_DEPTH_ENABLED] = "ShadowMount+: 已啟用掃描深度 %u。",
|
||||
/* English: Backport overlay failed: %s\n%s\n0x%08X */
|
||||
[SM_L10N_BACKPORT_OVERLAY_FAILED] = "向下相容覆蓋層掛載失敗:%s\n%s\n0x%08X",
|
||||
/* English: Backport applied to game: %s */
|
||||
[SM_L10N_GAME_BACKPORTED] = "已為遊戲套用向下相容修補程式:%s",
|
||||
/* English: Backport applied to game: %s\nEmulators updated */
|
||||
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "已為遊戲套用向下相容修補程式:%s\n多個模擬器已更新",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "映像掛載被拒絕:\n%s\n叢集大小太小 (%llu < %u)。\n已儲存 image_sector 覆寫值: %u。\n請再次嘗試掛載。",
|
||||
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
|
||||
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "映像掛載遭拒:\n%s\n叢集大小過小(%llu < %u)。\n請增大映像的叢集大小,或在設定中減小磁區大小。",
|
||||
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
|
||||
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "註冊失敗: %s (%s)\nAppInstallTitleDir 無法使用",
|
||||
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
|
||||
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "註冊失敗:%s(%s)\nTitleDir 橋接無法使用",
|
||||
/* English: Register failed: %s (%s)\ncode=0x%08X */
|
||||
[SM_L10N_REGISTER_FAILED_CODE] = "註冊失敗: %s (%s)\n代碼=0x%08X",
|
||||
/* English: Installing: %s (%s)... */
|
||||
[SM_L10N_INSTALLING] = "正在安裝: %s (%s)...",
|
||||
/* English: KStuff active again */
|
||||
[SM_L10N_KSTUFF_ACTIVE] = "KStuff 已重新啟用",
|
||||
/* English: KStuff paused while the game is running */
|
||||
[SM_L10N_KSTUFF_PAUSED] = "遊戲執行時 KStuff 已暫停",
|
||||
/* English: Installed: %s */
|
||||
[SM_L10N_INSTALLED] = "已安裝: %s",
|
||||
/* English: Unknown */
|
||||
[SM_L10N_UNKNOWN] = "未知",
|
||||
/* English: Image mount failed: 0x%08X (%s)\n%s */
|
||||
[SM_L10N_IMAGE_MOUNT_FAILED] = "映像掛載失敗: 0x%08X (%s)\n%s",
|
||||
/* English: Filesystem cluster size (%llu) is smaller than device sector size (%u) for %s */
|
||||
[SM_L10N_FS_CLUSTER_SMALL] = "檔案系統叢集大小 (%llu) 小於裝置磁區大小 (%u):%s",
|
||||
/* English: Mounted image is unreadable or damaged */
|
||||
[SM_L10N_IMAGE_UNREADABLE_DAMAGED] = "已掛載的映像無法讀取或已損壞",
|
||||
/* English: Image cache full (%u entries), cannot track mount %s */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_TRACK] = "映像快取已滿(%u 筆),無法追蹤掛載 %s",
|
||||
/* English: Image cache full (%u entries), rolling back mount */
|
||||
[SM_L10N_IMAGE_CACHE_FULL_ROLLBACK] = "映像快取已滿(%u 筆),正在復原掛載",
|
||||
/* English: Install failed: %s (%s)\nTimed out after 5 minutes */
|
||||
[SM_L10N_INSTALL_FAILED_TIMEOUT] = "安裝失敗: %s (%s)\n5 分鐘後逾時",
|
||||
/* English: Batch install queued (%d): */
|
||||
[SM_L10N_BATCH_INSTALL_QUEUED] = "批次安裝已排入佇列 (%d):",
|
||||
/* English: \n... and %d more */
|
||||
[SM_L10N_BATCH_INSTALL_AND_MORE] = "\n... 以及另外 %d 個",
|
||||
/* English: Batch install failed for %d app(s).\ncode=0x%08X */
|
||||
[SM_L10N_BATCH_INSTALL_FAILED] = "%d 個應用程式批次安裝失敗。\n代碼=0x%08X",
|
||||
/* English: %s crashed before KStuff was paused. KStuff is not the cause. */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF] = "%s 在暫停 KStuff 前當機。問題並非由 KStuff 引起。",
|
||||
/* English: %s crashed before KStuff pause:\n%s */
|
||||
[SM_L10N_CRASH_BEFORE_KSTUFF_FATAL] = "%s 在暫停 KStuff 前當機:\n%s",
|
||||
/* English: %s crashed after KStuff was paused. Automatic tuning skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_SKIPPED] = "%s 在暫停 KStuff 後當機。已略過自動調整。",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nAutotune skipped. */
|
||||
[SM_L10N_CRASH_AFTER_KSTUFF_FATAL_SKIPPED] = "%s 在暫停 KStuff 後當機:\n%s\n已略過自動調整。",
|
||||
/* English: Can't load module after KStuff pause */
|
||||
[SM_L10N_RTLD_AFTER_KSTUFF_FALLBACK] = "KStuff 暫停後無法載入模組",
|
||||
/* English: can't load module %.*s after KStuff pause */
|
||||
[SM_L10N_RTLD_MODULE_AFTER_KSTUFF] = "暫停 KStuff 後無法載入模組 %.*s",
|
||||
/* English: %s: %s. Delay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_DELAY_INCREASED_RELAUNCH] = "%s: %s。延遲已增加到 %u 秒。\n請重新啟動遊戲。",
|
||||
/* English: Crash detected after KStuff pause: pause delay for %s increased to %us. Launch the game again. */
|
||||
[SM_L10N_CRASH_DELAY_INCREASED] = "偵測到暫停 KStuff 後發生當機:%s 的延遲已增加至 %u 秒。請重新啟動遊戲。",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nDelay increased to %us.\nLaunch the game again. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_INCREASED] = "%s 在暫停 KStuff 後當機:\n%s\n延遲已增加至 %u 秒。\n請重新啟動遊戲。",
|
||||
/* English: Crash detected after KStuff pause: %s. Failed to update autotune delay. */
|
||||
[SM_L10N_CRASH_DELAY_UPDATE_FAILED] = "偵測到暫停 KStuff 後發生當機:%s。無法更新自動調整延遲。",
|
||||
/* English: %s crashed after KStuff pause:\n%s\nFailed to update autotune delay. */
|
||||
[SM_L10N_CRASH_FATAL_DELAY_UPDATE_FAILED] = "%s 在暫停 KStuff 後當機:\n%s\n無法更新自動調整延遲。",
|
||||
/* English: Missing/invalid param.json:\n%s */
|
||||
[SM_L10N_MISSING_PARAM] = "param.json 遺失或無效:\n%s",
|
||||
/* English: Found %d new games. Processing... */
|
||||
[SM_L10N_FOUND_NEW_GAMES] = "找到 %d 個新遊戲。正在處理...",
|
||||
/* English: Library synchronized.\nFound %d games. */
|
||||
[SM_L10N_LIBRARY_SYNCED] = "遊戲庫已同步。\n共找到 %d 個遊戲。",
|
||||
/* English: ShadowMount+: config reloaded. */
|
||||
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+:設定已重新載入。",
|
||||
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
|
||||
[SM_L10N_DUPLICATE_IGNORED] = "已略過重複項目 %s:\n%s\n現有項目:%s",
|
||||
/* English: AMPR emulator updated */
|
||||
[SM_L10N_AMPR_EMULATOR_UPDATED] = "AMPR 模擬器已更新",
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
#ifndef SM_AMPR_UPDATER_H
|
||||
#define SM_AMPR_UPDATER_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "sm_types.h"
|
||||
|
||||
// Start the dormant-by-default AMPR update worker.
|
||||
bool sm_ampr_updater_start(void);
|
||||
// Interrupt any active request and join the update worker.
|
||||
void sm_ampr_updater_stop(void);
|
||||
// Wake the worker when its enabled state, URL, or destination changes.
|
||||
void sm_ampr_updater_on_config_reload(const runtime_config_t *old_cfg,
|
||||
const runtime_config_t *new_cfg);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,16 @@
|
||||
#ifndef SM_API_PROTOCOL_H
|
||||
#define SM_API_PROTOCOL_H
|
||||
|
||||
#define SM_API_DEFAULT_BIND_ADDRESS "127.0.0.1"
|
||||
#define SM_API_DEFAULT_PORT 10101u
|
||||
#define SM_API_VERSION 1u
|
||||
#define SM_API_MAX_HTTP_HEADER_SIZE 8192u
|
||||
#define SM_API_MAX_JSON_BODY_SIZE 4096u
|
||||
|
||||
#define SM_API_ROUTE_VERSION "/api/v1/version"
|
||||
#define SM_API_ROUTE_IMAGES "/api/v1/images"
|
||||
#define SM_API_ROUTE_GAMES "/api/v1/games"
|
||||
#define SM_API_ROUTE_MOUNT "/api/v1/games/mount"
|
||||
#define SM_API_ROUTE_UNMOUNT "/api/v1/games/unmount"
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,10 @@
|
||||
#ifndef SM_API_SERVICE_H
|
||||
#define SM_API_SERVICE_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
bool sm_api_service_start(void);
|
||||
void sm_api_service_stop(void);
|
||||
bool sm_api_service_reconfigure(void);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,30 @@
|
||||
#ifndef SM_APPDB_H
|
||||
#define SM_APPDB_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
struct AppDbTitleList;
|
||||
|
||||
// Close the cached app.db handle and release related resources.
|
||||
void shutdown_app_db(void);
|
||||
// Normalize existing snd0info rows during process startup.
|
||||
bool app_db_run_startup_maintenance(void);
|
||||
// Update snd0 metadata for a registered title.
|
||||
int update_snd0info(const char *title_id);
|
||||
// Normalize an existing snd0Info path for one title.
|
||||
int normalize_snd0info_for_title(const char *title_id);
|
||||
// Check whether a title ID exists in a cached app.db title list.
|
||||
bool app_db_title_list_contains(const struct AppDbTitleList *list,
|
||||
const char *title_id);
|
||||
// Release a title-list snapshot returned by get_app_db_title_list_cached().
|
||||
void free_app_db_title_list(struct AppDbTitleList *list);
|
||||
// Drop the cached app.db title list so it is reloaded on next access.
|
||||
void invalidate_app_db_title_cache(void);
|
||||
// Copy the cached app.db title list into caller-owned storage, loading it if needed.
|
||||
bool get_app_db_title_list_cached(struct AppDbTitleList *list_out);
|
||||
// Copy titles whose metadata path matches /user/app/PPSA*/sce_sys.
|
||||
bool get_app_db_auto_remove_title_list(struct AppDbTitleList *list_out);
|
||||
// Copy the cached PPSA titles that app.db marks as not uninstallable.
|
||||
bool get_app_db_blocked_uninstall_ppsa_list(struct AppDbTitleList *list_out);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,47 @@
|
||||
#ifndef SM_CONFIG_MOUNT_H
|
||||
#define SM_CONFIG_MOUNT_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct runtime_config runtime_config_t;
|
||||
|
||||
// Ensure runtime configuration is loaded before use.
|
||||
void ensure_runtime_config_ready(void);
|
||||
// Load runtime configuration from disk and apply defaults.
|
||||
bool load_runtime_config(void);
|
||||
// Reload runtime configuration from disk when config.ini changed.
|
||||
bool reload_runtime_config_if_changed(bool *reloaded_out);
|
||||
// Return the current runtime configuration.
|
||||
const runtime_config_t *runtime_config(void);
|
||||
// Return the number of configured scan roots.
|
||||
int get_scan_path_count(void);
|
||||
// Return a scan root by index, or NULL if out of range.
|
||||
const char *get_scan_path(int index);
|
||||
// Return scan depth for a root, including managed container-root expansion.
|
||||
uint32_t get_scan_depth_for_root(const char *scan_path);
|
||||
// Resolve a per-image read-only override from the file name.
|
||||
bool get_image_mode_override(const char *filename, bool *mount_read_only_out);
|
||||
// Resolve a per-image sector-size override from autotune.ini or config.ini.
|
||||
bool get_image_sector_size_override(const char *filename,
|
||||
uint32_t *sector_size_out);
|
||||
// Return true when kstuff auto-pause is disabled for the given title ID.
|
||||
bool is_kstuff_pause_disabled_for_title(const char *title_id);
|
||||
// Resolve a per-title kstuff pause-delay override in seconds.
|
||||
bool get_kstuff_pause_delay_override_for_title(const char *title_id,
|
||||
uint32_t *delay_seconds_out);
|
||||
// Resolve a per-title autotuned kstuff pause-delay override in seconds.
|
||||
bool get_kstuff_autotune_pause_delay_for_title(const char *title_id,
|
||||
uint32_t *delay_seconds_out);
|
||||
// Upsert an autotuned pause-delay override for the title.
|
||||
bool upsert_kstuff_autotune_pause_delay(const char *title_id,
|
||||
uint32_t current_delay_seconds,
|
||||
uint32_t *delay_seconds_out);
|
||||
// Upsert an autotuned per-image sector-size override.
|
||||
bool upsert_image_sector_size_autotune(const char *filename,
|
||||
uint32_t sector_size,
|
||||
uint32_t *sector_size_out);
|
||||
// Return true when the global fakelib overlay is disabled for this title.
|
||||
bool is_global_fakelib_excluded_for_title(const char *title_id);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,22 @@
|
||||
#ifndef SM_FAKELIB_H
|
||||
#define SM_FAKELIB_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
// Return true when per-game fakelib backport automation is enabled.
|
||||
bool sm_fakelib_game_feature_enabled(void);
|
||||
// Prepare or refresh the combined fakelib cache before title runtime mounting.
|
||||
void sm_fakelib_prepare_title_cache(const char *title_id,
|
||||
const char *game_path);
|
||||
// Remove invalid and unused fakelib caches during the scanner's rare cycle.
|
||||
void sm_fakelib_cleanup_caches(void);
|
||||
// Track a supported game launch and mount its fakelib overlay immediately.
|
||||
void sm_fakelib_game_on_exec(pid_t pid, const char *title_id);
|
||||
// Remove a tracked fakelib overlay for a stopped game.
|
||||
void sm_fakelib_game_on_exit(pid_t pid);
|
||||
// Unmount all tracked overlays during watcher shutdown.
|
||||
void sm_fakelib_game_shutdown(void);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,8 @@
|
||||
#ifndef SM_FAN_H
|
||||
#define SM_FAN_H
|
||||
|
||||
// Apply the configured automatic fan-controller target for a confirmed running
|
||||
// game. System-managed configuration is a no-op.
|
||||
void sm_fan_apply_game_target(const char *title_id);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,77 @@
|
||||
#ifndef SM_FILESYSTEM_H
|
||||
#define SM_FILESYSTEM_H
|
||||
|
||||
#include <dirent.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "sm_limits.h"
|
||||
|
||||
// Check whether a title is present in the installed app set.
|
||||
bool is_installed(const char *title_id);
|
||||
// Check whether /user/appmeta/<TITLE_ID>/param.json exists.
|
||||
bool has_appmeta_data(const char *title_id);
|
||||
// Check whether a title currently has mounted data.
|
||||
bool is_data_mounted(const char *title_id);
|
||||
// Read the mount.lnk source path for a title tracked under /user/app.
|
||||
bool read_mount_link(const char *title_id, char *out, size_t out_size);
|
||||
// Atomically persist the mount.lnk source path for a title.
|
||||
bool write_mount_link(const char *title_id, const char *source_path);
|
||||
// Read the optional backing image path retained for an image-backed title.
|
||||
bool read_mount_image_link(const char *title_id, char *out, size_t out_size);
|
||||
// Persist the outer backing-image path for an already staged title.
|
||||
bool write_mount_image_link(const char *title_id, const char *image_path);
|
||||
bool read_mount_image_chain(
|
||||
const char *title_id,
|
||||
char paths[MAX_IMAGE_CHAIN_DEPTH][MAX_PATH], size_t *count_out);
|
||||
bool write_mount_image_chain(
|
||||
const char *title_id,
|
||||
const char paths[MAX_IMAGE_CHAIN_DEPTH][MAX_PATH], size_t count);
|
||||
// Validate a /user/app/<TITLE_ID> tracker entry and optionally build its path.
|
||||
bool resolve_title_app_dir(const struct dirent *entry, char *app_dir,
|
||||
size_t app_dir_size);
|
||||
// Recover staged mount links and warm image source mappings on startup.
|
||||
void cleanup_staged_mount_links(void);
|
||||
// Remove duplicated managed mount layers left from previous runs.
|
||||
void cleanup_duplicate_title_mounts(void);
|
||||
// Remount /system_ex with the expected flags.
|
||||
int remount_system_ex(void);
|
||||
// Mount a title source into /system_ex/app/<title_id> via nullfs.
|
||||
bool mount_title_nullfs(const char *title_id, const char *src_path);
|
||||
// Roll back a just-mounted title nullfs layer when publication is aborted.
|
||||
bool rollback_title_nullfs_mount(const char *title_id, const char *src_path);
|
||||
// Drop runtime layers for a title while preserving its metadata/link files.
|
||||
bool unmount_title_runtime_layers(const char *title_id);
|
||||
// Same operation without expected EBUSY diagnostics during a bounded retry.
|
||||
bool unmount_title_runtime_layers_quiet(const char *title_id);
|
||||
// Release every managed title layer while preserving link metadata.
|
||||
bool unmount_all_title_runtime_layers(void);
|
||||
// Reconcile the title mount stack against the expected source/backport state.
|
||||
bool reconcile_title_backport_mount(const char *title_id, const char *src_path,
|
||||
const char *expected_backport_path,
|
||||
bool *overlay_active_out);
|
||||
// Mount a prepared backport overlay on top of an already mounted title.
|
||||
// Returns false only when nmount() itself fails.
|
||||
bool mount_backport_overlay(const char *mount_point,
|
||||
const char *backport_path,
|
||||
const char *title_id);
|
||||
// Unmount all managed /system_ex/app/<title_id> mount stacks on shutdown.
|
||||
bool shutdown_title_mounts(void);
|
||||
// Remove stale mount links and optionally restore image-backed mounts.
|
||||
void cleanup_mount_links(const char *removed_source_root,
|
||||
bool unmount_system_ex_bind);
|
||||
// Unmount and remove title links that point under a source root.
|
||||
void cleanup_mount_links_for_source_unmount(const char *source_root);
|
||||
// Unmount and remove title links backed by USB sources or USB-backed images.
|
||||
void cleanup_usb_mount_links_for_suspend(void);
|
||||
// Recursively copy a directory tree.
|
||||
int copy_dir(const char *src, const char *dst);
|
||||
// Merge a directory tree and set mode as each destination entry is created.
|
||||
int copy_dir_with_mode(const char *src, const char *dst, mode_t mode);
|
||||
// Copy a single file.
|
||||
int copy_file(const char *src, const char *dst);
|
||||
// Copy a single file and set its mode immediately after creation.
|
||||
int copy_file_with_mode(const char *src, const char *dst, mode_t mode);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,45 @@
|
||||
#ifndef SM_GAME_CACHE_H
|
||||
#define SM_GAME_CACHE_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include "sm_limits.h"
|
||||
|
||||
typedef struct {
|
||||
char path[MAX_PATH];
|
||||
char title_id[MAX_TITLE_ID];
|
||||
char title_name[MAX_TITLE_NAME];
|
||||
} sm_game_cache_snapshot_entry_t;
|
||||
|
||||
typedef bool (*game_cache_iter_fn)(const char *path, const char *title_id,
|
||||
const char *title_name,
|
||||
const char *owning_scan_root, void *ctx);
|
||||
|
||||
// Cache resolved metadata for a mounted or discovered game.
|
||||
void cache_game_entry(const char *path, const char *title_id,
|
||||
const char *title_name);
|
||||
// Drop invalid or stale entries from the game cache.
|
||||
void prune_game_cache(void);
|
||||
// Drop invalid or stale entries that belong to a specific scan root.
|
||||
void prune_game_cache_for_root(const char *root);
|
||||
// Cancel pending removal when the same title is discovered at a valid source.
|
||||
void note_game_cache_source_seen(const char *path, const char *title_id,
|
||||
const char *title_name);
|
||||
// Reconcile app.db auto-remove candidates and uninstall expired missing entries.
|
||||
void reconcile_missing_app_db_games(void);
|
||||
// Reset pending missing timers after relevant runtime config changes.
|
||||
void reset_missing_game_cache_timers(void);
|
||||
// Look up a cached game entry by path or title ID.
|
||||
bool find_cached_game(const char *path, const char *title_id,
|
||||
const char **existing_path_out);
|
||||
// Visit cached games, optionally limited to one source root.
|
||||
void for_each_cached_game_entry(const char *root, game_cache_iter_fn fn,
|
||||
void *ctx);
|
||||
// Allocate a stable snapshot of all cached games. Caller frees it.
|
||||
bool sm_game_cache_snapshot(sm_game_cache_snapshot_entry_t **entries_out,
|
||||
size_t *count_out);
|
||||
// Remove a game cache entry by path.
|
||||
void clear_cached_game(const char *path);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,20 @@
|
||||
#ifndef SM_GAME_LIFECYCLE_H
|
||||
#define SM_GAME_LIFECYCLE_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// Start the shared game lifecycle watcher used by runtime game features.
|
||||
bool start_game_lifecycle_watcher(void);
|
||||
// Wake the shared watcher so pending lifecycle work is processed immediately.
|
||||
void wake_game_lifecycle_watcher(void);
|
||||
// Stop the shared watcher and let lifecycle modules clean up tracked state.
|
||||
void stop_game_lifecycle_watcher(void);
|
||||
// Return true when a supported game is currently being tracked as running.
|
||||
bool sm_game_lifecycle_has_active_game(void);
|
||||
// Publish an SceShellCoreUtilAppFocus change for lifecycle-thread processing.
|
||||
void sm_game_lifecycle_note_app_focus(uint32_t app_id);
|
||||
// Ensure the shared watcher is running for runtime game-state tracking.
|
||||
bool refresh_game_lifecycle_watcher(void);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,15 @@
|
||||
#ifndef SM_GAMEINFO_H
|
||||
#define SM_GAMEINFO_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
// Read title ID and title name from a mounted game directory.
|
||||
bool get_game_info(const char *base_path, const struct stat *param_st,
|
||||
char *out_id, char *out_name);
|
||||
// Accept canonical PS4/PS5 game title IDs used in filesystem paths.
|
||||
bool is_supported_game_title_id(const char *title_id);
|
||||
// Check whether a directory contains sce_sys/param.json and stat it.
|
||||
bool directory_has_param_json(const char *dir_path, struct stat *param_st_out);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,16 @@
|
||||
#ifndef SM_HASH_H
|
||||
#define SM_HASH_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// Return a stable 32-bit FNV-1a hash for a NUL-terminated string.
|
||||
static inline uint32_t sm_fnv1a32(const char *s) {
|
||||
uint32_t h = 2166136261u;
|
||||
while (s && *s) {
|
||||
h ^= (uint8_t)(*s++);
|
||||
h *= 16777619u;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,42 @@
|
||||
#ifndef SM_IMAGE_H
|
||||
#define SM_IMAGE_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "sm_types.h"
|
||||
|
||||
// Log filesystem statistics for a mounted path.
|
||||
void log_fs_stats(const char *tag, const char *path, const char *type_hint);
|
||||
// Attach and mount an image file to its runtime mount point.
|
||||
bool mount_image(const char *file_path, image_fs_type_t fs_type);
|
||||
// Unmount an image mount point and detach its backing device.
|
||||
bool unmount_image(const char *file_path, int unit_id, attach_backend_t backend);
|
||||
// Unmount a runtime image without deleting persistent title metadata links.
|
||||
bool unmount_runtime_image(const char *file_path, int unit_id,
|
||||
attach_backend_t backend);
|
||||
// Release one cached runtime image mount. An absent live mount is harmless.
|
||||
bool release_runtime_image_mount(const char *file_path);
|
||||
// Release all discovery/runtime image mounts without deleting title metadata.
|
||||
bool release_runtime_image_mounts(void);
|
||||
// Reconcile cached image mounts with current sources and remount if needed.
|
||||
void cleanup_stale_image_mounts(void);
|
||||
// Reconcile cached image mounts that belong to a specific scan root.
|
||||
void cleanup_stale_image_mounts_for_root(const char *root);
|
||||
// Unmount cached image mounts backed by USB storage during suspend.
|
||||
bool unmount_usb_image_mounts_for_suspend(void);
|
||||
// Unmount every cached image mount during shutdown.
|
||||
bool shutdown_image_mounts(void);
|
||||
// Remove empty directories left under the image mount root.
|
||||
void cleanup_mount_dirs(void);
|
||||
// Mount an image file if it is stable and not currently rate-limited.
|
||||
bool maybe_mount_image_file(const char *full_path, const char *display_name,
|
||||
bool *unstable_out);
|
||||
// Build the deterministic runtime mount point for an image source path.
|
||||
void get_image_mount_point_for_source(const char *file_path,
|
||||
char mount_point[MAX_PATH]);
|
||||
// Return true when the filename has a supported image extension.
|
||||
bool is_supported_image_file_name(const char *name);
|
||||
// Return true when the source path is a supported image, including nested names.
|
||||
bool is_supported_image_file_path(const char *full_path, const char *name);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,53 @@
|
||||
#ifndef SM_IMAGE_CACHE_H
|
||||
#define SM_IMAGE_CACHE_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "sm_types.h"
|
||||
|
||||
typedef struct {
|
||||
char path[MAX_PATH];
|
||||
char mount_point[MAX_PATH];
|
||||
int unit_id;
|
||||
attach_backend_t backend;
|
||||
attached_unit_detach_state_t detach_state;
|
||||
} image_cache_entry_t;
|
||||
|
||||
// Cache a successful image mount and its attached device.
|
||||
// Returns false when no free cache slot is available.
|
||||
bool cache_image_mount(const char *path, const char *mount_point,
|
||||
int unit_id, attach_backend_t backend);
|
||||
// Cache an image source mapping without attach metadata.
|
||||
bool cache_image_source_mapping(const char *path, const char *mount_point);
|
||||
// Return a cached image mount entry by index.
|
||||
bool get_image_cache_entry(int index, image_cache_entry_t *entry_out);
|
||||
// Find a cached source path with one cache lock acquisition.
|
||||
bool find_image_cache_entry(const char *path, image_cache_entry_t *entry_out,
|
||||
int *index_out);
|
||||
// Publish an accepted asynchronous detach for the same cached device.
|
||||
bool update_image_cache_detach_state(
|
||||
const char *path, int unit_id, attach_backend_t backend,
|
||||
const attached_unit_detach_state_t *detach_state);
|
||||
// Mark a cached image mount entry as invalid.
|
||||
void invalidate_image_cache_entry(int index);
|
||||
// Resolve a device mapping from the in-memory mount cache.
|
||||
bool resolve_device_from_mount_cache(const char *mount_point,
|
||||
attach_backend_t *backend_out,
|
||||
int *unit_out);
|
||||
// Resolve the original image file path for a cached runtime mount point.
|
||||
bool resolve_image_source_from_mount_cache(const char *mount_point,
|
||||
char *path_out,
|
||||
size_t path_out_size);
|
||||
// Resolve the image whose mount point owns a mounted child path.
|
||||
bool resolve_owning_image_source_from_mount_cache(const char *path,
|
||||
char *path_out,
|
||||
size_t path_out_size);
|
||||
// Resolve every owning image from the outermost source to the innermost one.
|
||||
size_t resolve_image_source_chain_from_mount_cache(
|
||||
const char *path,
|
||||
char chain[MAX_IMAGE_CHAIN_DEPTH][MAX_PATH]);
|
||||
// Resolve only the outermost owning image source.
|
||||
bool resolve_outermost_image_source_from_mount_cache(
|
||||
const char *path, char *path_out, size_t path_out_size);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,46 @@
|
||||
#ifndef SM_IMAGE_INDEX_H
|
||||
#define SM_IMAGE_INDEX_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "sm_limits.h"
|
||||
|
||||
struct AppDbTitleList;
|
||||
|
||||
typedef void (*sm_image_index_title_visitor_t)(const char *title_id);
|
||||
|
||||
typedef struct {
|
||||
char path[MAX_PATH];
|
||||
int64_t size;
|
||||
int64_t mtime_sec;
|
||||
int32_t mtime_nsec;
|
||||
bool complete;
|
||||
} sm_image_index_snapshot_entry_t;
|
||||
|
||||
// Return true when a complete unchanged index entry can be reused instead of
|
||||
// opening the image, visiting every retained title ID before returning.
|
||||
bool sm_image_index_visit_ready_titles(
|
||||
const char *path, const struct stat *st,
|
||||
const struct AppDbTitleList *app_db_titles, bool app_db_titles_ready,
|
||||
sm_image_index_title_visitor_t visitor);
|
||||
// Return true when a title is mapped to an existing backing image.
|
||||
bool sm_image_index_has_source_for_title(const char *title_id);
|
||||
// Start a new fingerprint generation before mounting an image.
|
||||
void sm_image_index_begin_scan(const char *path, const struct stat *st);
|
||||
// Associate a discovered mounted game with its outer backing image. Returns
|
||||
// false when an image-backed title could not be retained in the index.
|
||||
bool sm_image_index_record_game(const char *game_path, const char *title_id);
|
||||
// Commit one fingerprint after its mounted content was scanned.
|
||||
void sm_image_index_complete_scan(const char *path);
|
||||
// Atomically persist accumulated index changes at the scan-cycle boundary.
|
||||
void sm_image_index_flush(void);
|
||||
// Drop persistent entries whose backing image disappeared.
|
||||
void sm_image_index_prune(void);
|
||||
// Allocate a stable snapshot of all known backing images. Caller frees it.
|
||||
bool sm_image_index_snapshot(sm_image_index_snapshot_entry_t **entries_out,
|
||||
size_t *count_out);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,10 @@
|
||||
#ifndef SM_INSTALL_H
|
||||
#define SM_INSTALL_H
|
||||
|
||||
typedef struct scan_candidate scan_candidate_t;
|
||||
|
||||
// Install or remount the collected scan candidates.
|
||||
void process_scan_candidates(const scan_candidate_t *candidates,
|
||||
int candidate_count);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,30 @@
|
||||
#ifndef SM_INSTALL_QUEUE_H
|
||||
#define SM_INSTALL_QUEUE_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct scan_candidate scan_candidate_t;
|
||||
|
||||
// Return whether a title is already waiting for async batch installation.
|
||||
bool is_title_install_pending(const char *title_id);
|
||||
// Drop queued/submitted async install state for a title after confirmed removal.
|
||||
void sm_install_forget_pending_title(const char *title_id);
|
||||
// Return the next pending-install wake deadline in monotonic microseconds.
|
||||
uint64_t sm_install_next_wake_us(uint64_t now_us);
|
||||
// Poll async batch installs for success/timeout completion only.
|
||||
void sm_install_poll_pending(void);
|
||||
// Poll async batch installs for success/timeout completion and auto-submit
|
||||
// queued installs when possible.
|
||||
void sm_install_service_pending(void);
|
||||
// Queue a staged title for the next batch install request.
|
||||
bool sm_install_queue_candidate(const scan_candidate_t *candidate,
|
||||
bool has_src_snd0);
|
||||
// Submit all queued titles when no batch install is currently pending.
|
||||
bool sm_install_submit_queued(void);
|
||||
// Record retry state for every queued title after a batch submit failure.
|
||||
void sm_install_note_submit_failure(void);
|
||||
// Return true while an install still needs its staged mounts.
|
||||
bool sm_install_has_pending_work(void);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,44 @@
|
||||
#ifndef SM_KSTUFF_H
|
||||
#define SM_KSTUFF_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
// Resolve firmware-specific kstuff sysentvec addresses and initialize state.
|
||||
void sm_kstuff_init(void);
|
||||
// Shut down runtime kstuff control state.
|
||||
void sm_kstuff_shutdown(void);
|
||||
// Return true when firmware-specific kstuff control is available.
|
||||
bool sm_kstuff_is_supported(void);
|
||||
// Return true when both tracked kstuff sysentvecs are enabled.
|
||||
bool sm_kstuff_is_enabled(void);
|
||||
// Set both tracked kstuff sysentvecs and return the resulting enabled state.
|
||||
bool sm_kstuff_set_enabled(bool enabled, bool notify_user);
|
||||
|
||||
// Return true when game-driven kstuff automation should run.
|
||||
bool sm_kstuff_game_feature_enabled(void);
|
||||
// Track a supported game launch for delayed auto-pause handling.
|
||||
void sm_kstuff_game_on_exec(pid_t pid, const char *title_id, uint32_t app_id,
|
||||
uint64_t exec_time_us);
|
||||
// Preserve kstuff state across an ExitSpawn/LoadExec process replacement.
|
||||
bool sm_kstuff_game_handoff(pid_t old_pid, pid_t new_pid,
|
||||
const char *title_id, uint32_t app_id);
|
||||
// Publish an AppFocus change for processing on the lifecycle/kstuff thread.
|
||||
void sm_kstuff_note_app_focus(uint32_t app_id);
|
||||
// Return the next wake deadline in monotonic microseconds, or 0 when idle.
|
||||
uint64_t sm_kstuff_game_next_wake_us(uint64_t now_us);
|
||||
// Forget a tracked game and restore kstuff when no paused entries remain.
|
||||
void sm_kstuff_game_on_exit(pid_t pid);
|
||||
// Apply any delayed kstuff pause whose deadline has elapsed.
|
||||
void sm_kstuff_game_poll(void);
|
||||
// Clear tracked game state and restore kstuff if the watcher paused it.
|
||||
void sm_kstuff_game_shutdown(void);
|
||||
// Pause kstuff for runtime sleep without restoring game-driven pauses.
|
||||
void sm_kstuff_sleep_enter(void);
|
||||
// Restore kstuff after runtime sleep when sleep entry disabled it.
|
||||
void sm_kstuff_sleep_leave(void);
|
||||
// Re-apply runtime config changes to the currently tracked game, if any.
|
||||
void sm_kstuff_on_config_reload(void);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,60 @@
|
||||
#ifndef SM_L10N_H
|
||||
#define SM_L10N_H
|
||||
|
||||
#include "sm_platform.h"
|
||||
|
||||
#define SM_LANGUAGE_AUTO (-1)
|
||||
|
||||
typedef enum {
|
||||
SM_L10N_ACTION_DEBUG_SETTINGS = 0,
|
||||
SM_L10N_STARTUP,
|
||||
SM_L10N_RECURSIVE_SCAN_DEPRECATED,
|
||||
SM_L10N_SCAN_DEPTH_ENABLED,
|
||||
SM_L10N_BACKPORT_OVERLAY_FAILED,
|
||||
SM_L10N_GAME_BACKPORTED,
|
||||
SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED,
|
||||
SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED,
|
||||
SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG,
|
||||
SM_L10N_REGISTER_FAILED_UNAVAILABLE,
|
||||
SM_L10N_REGISTER_BRIDGE_UNAVAILABLE,
|
||||
SM_L10N_REGISTER_FAILED_CODE,
|
||||
SM_L10N_INSTALLING,
|
||||
SM_L10N_KSTUFF_ACTIVE,
|
||||
SM_L10N_KSTUFF_PAUSED,
|
||||
SM_L10N_INSTALLED,
|
||||
SM_L10N_UNKNOWN,
|
||||
SM_L10N_IMAGE_MOUNT_FAILED,
|
||||
SM_L10N_FS_CLUSTER_SMALL,
|
||||
SM_L10N_IMAGE_UNREADABLE_DAMAGED,
|
||||
SM_L10N_IMAGE_CACHE_FULL_TRACK,
|
||||
SM_L10N_IMAGE_CACHE_FULL_ROLLBACK,
|
||||
SM_L10N_INSTALL_FAILED_TIMEOUT,
|
||||
SM_L10N_BATCH_INSTALL_QUEUED,
|
||||
SM_L10N_BATCH_INSTALL_AND_MORE,
|
||||
SM_L10N_BATCH_INSTALL_FAILED,
|
||||
SM_L10N_CRASH_BEFORE_KSTUFF,
|
||||
SM_L10N_CRASH_BEFORE_KSTUFF_FATAL,
|
||||
SM_L10N_CRASH_AFTER_KSTUFF_SKIPPED,
|
||||
SM_L10N_CRASH_AFTER_KSTUFF_FATAL_SKIPPED,
|
||||
SM_L10N_RTLD_AFTER_KSTUFF_FALLBACK,
|
||||
SM_L10N_RTLD_MODULE_AFTER_KSTUFF,
|
||||
SM_L10N_DELAY_INCREASED_RELAUNCH,
|
||||
SM_L10N_CRASH_DELAY_INCREASED,
|
||||
SM_L10N_CRASH_FATAL_DELAY_INCREASED,
|
||||
SM_L10N_CRASH_DELAY_UPDATE_FAILED,
|
||||
SM_L10N_CRASH_FATAL_DELAY_UPDATE_FAILED,
|
||||
SM_L10N_MISSING_PARAM,
|
||||
SM_L10N_FOUND_NEW_GAMES,
|
||||
SM_L10N_LIBRARY_SYNCED,
|
||||
SM_L10N_CONFIG_RELOADED,
|
||||
SM_L10N_DUPLICATE_IGNORED,
|
||||
SM_L10N_AMPR_EMULATOR_UPDATED,
|
||||
SM_L10N_COUNT
|
||||
} sm_l10n_key_t;
|
||||
|
||||
void sm_l10n_init(void);
|
||||
bool sm_l10n_parse_language_id(const char *value, int32_t *language_id_out);
|
||||
const char *sm_l10n_language_name(int32_t language_id);
|
||||
const char *sm_l10n_get(sm_l10n_key_t key);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,66 @@
|
||||
#ifndef SM_LIMITS_H
|
||||
#define SM_LIMITS_H
|
||||
|
||||
#define DEFAULT_SCAN_INTERVAL_US 15000000u
|
||||
#define DEFAULT_SCAN_DEPTH 1u
|
||||
#define DEFAULT_STABILITY_WAIT_SECONDS 10u
|
||||
#define DEFAULT_AUTO_REMOVE_MISSING_DELAY_SECONDS 300u
|
||||
#define DEFAULT_KSTUFF_PAUSE_DELAY_IMAGE_SECONDS 25u
|
||||
#define DEFAULT_KSTUFF_PAUSE_DELAY_DIRECT_SECONDS 15u
|
||||
#define FAN_TARGET_TEMPERATURE_SYSTEM 0u
|
||||
#define MIN_FAN_TARGET_TEMPERATURE_C 50u
|
||||
#define MAX_FAN_TARGET_TEMPERATURE_C 91u
|
||||
#define MAX_PENDING 512
|
||||
#define MAX_IMAGE_MOUNTS 2048
|
||||
#define MAX_IMAGE_TITLES 8192
|
||||
#define MAX_IMAGE_MODE_RULES 128
|
||||
#define MAX_KSTUFF_TITLE_RULES 128
|
||||
#define MAX_FAKELIB_EXCLUDE_RULES 128
|
||||
#define PATH_STATE_CAPACITY MAX_PENDING
|
||||
#define TITLE_STATE_CAPACITY MAX_PENDING
|
||||
#define STATE_HASH_SIZE 1024u
|
||||
#define MAX_SCAN_PATHS 256
|
||||
|
||||
#define MAX_FAILED_MOUNT_ATTEMPTS 2
|
||||
#define MAX_REGISTER_ATTEMPTS 2
|
||||
#define MAX_MISSING_PARAM_SCAN_ATTEMPTS 3
|
||||
#define MAX_IMAGE_MOUNT_ATTEMPTS 3
|
||||
#define MAX_LAYERED_UNMOUNT_ATTEMPTS 4
|
||||
#define MAX_IMAGE_CHAIN_DEPTH 8
|
||||
|
||||
#define LVD_RELEASE_WAIT_MAX_US 15000000u
|
||||
#define LVD_RELEASE_WAIT_POLL_US 500000u
|
||||
#define GAME_LIFECYCLE_POLL_INTERVAL_US 250000u
|
||||
#define GAME_APPINFO_LOOKUP_TIMEOUT_US 1000000u
|
||||
// Keep a dead game PID eligible for ExitSpawn/LoadExec replacement while the
|
||||
// title sandbox still exists. Once the sandbox disappears, retain a short
|
||||
// grace window so a transient sandbox transition cannot tear down the runtime
|
||||
// before the replacement NOTE_EXEC is resolved.
|
||||
#define GAME_PROCESS_HANDOFF_GRACE_US 1000000u
|
||||
#define GAME_SLEEP_EXIT_TIMEOUT_US 5000000u
|
||||
#define RUNTIME_RESUME_GRACE_US 10000000u
|
||||
#define RUNTIME_RESUME_POLL_US 100000u
|
||||
#define KSTUFF_RESTORE_RETRY_US 1000000u
|
||||
#define SHELLCORE_FLAG_POLL_INTERVAL_US 100000u
|
||||
#define MIN_SCAN_DEPTH 1u
|
||||
#define MAX_SCAN_DEPTH 2u
|
||||
#define MIN_SCAN_INTERVAL_SECONDS 1u
|
||||
#define MAX_SCAN_INTERVAL_SECONDS 3600u
|
||||
#define MAX_STABILITY_WAIT_SECONDS 3600u
|
||||
#define MIN_AUTO_REMOVE_MISSING_DELAY_SECONDS 1u
|
||||
#define MAX_AUTO_REMOVE_MISSING_DELAY_SECONDS 86400u
|
||||
#define MAX_KSTUFF_PAUSE_DELAY_SECONDS 3600u
|
||||
|
||||
#define APP_DB_QUERY_BUSY_RETRIES 3
|
||||
#define APP_DB_UPDATE_BUSY_RETRIES 25
|
||||
#define APP_DB_PREPARE_BUSY_RETRIES 25
|
||||
#define APP_DB_BUSY_RETRY_SLEEP_US 200000u
|
||||
#define APP_DB_BUSY_TIMEOUT_MS 5000
|
||||
|
||||
#define MAX_PATH 1024
|
||||
#define MAX_TITLE_ID 32
|
||||
#define MAX_TITLE_NAME 256
|
||||
#define MAX_API_BIND_ADDRESS 16
|
||||
#define MAX_PARAM_JSON_SIZE (1024u * 1024u)
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,42 @@
|
||||
#ifndef SM_LOG_H
|
||||
#define SM_LOG_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "sm_l10n.h"
|
||||
|
||||
typedef struct sm_error sm_error_t;
|
||||
|
||||
// Write a formatted line to the debug log.
|
||||
void log_debug(const char *fmt, ...);
|
||||
// Prepare notification assets such as the packaged icon file.
|
||||
void sm_notifications_init(void);
|
||||
// Flush and close persistent log resources.
|
||||
void sm_log_shutdown(void);
|
||||
// Send a localized rich toast notification with packaged icon and version header.
|
||||
void notify_system_rich_l10n(bool allow_in_quiet_mode, sm_l10n_key_t key, ...);
|
||||
// Send the "game installed" rich toast for the given title ID.
|
||||
void notify_game_installed_rich(const char *title_id);
|
||||
// Send an informational system notification (suppressed in quiet mode).
|
||||
void notify_system_info(const char *fmt, ...);
|
||||
void notify_system_info_l10n(sm_l10n_key_t key, ...);
|
||||
// Send a system notification with formatted text.
|
||||
void notify_system(const char *fmt, ...);
|
||||
void notify_system_l10n(sm_l10n_key_t key, ...);
|
||||
// Clear the last recorded subsystem error.
|
||||
void sm_error_clear(void);
|
||||
// Store the last subsystem error with formatted details.
|
||||
void sm_error_set(const char *subsystem, int code, const char *path,
|
||||
const char *fmt, ...);
|
||||
void sm_error_set_l10n(const char *subsystem, int code, const char *path,
|
||||
sm_l10n_key_t key, ...);
|
||||
// Return the last recorded subsystem error.
|
||||
const sm_error_t *sm_last_error(void);
|
||||
// Return whether the current error was already notified.
|
||||
bool sm_error_notified(void);
|
||||
// Mark the current error as already shown to the user.
|
||||
void sm_error_mark_notified(void);
|
||||
// Notify the user about an image mount failure once per error state.
|
||||
void notify_image_mount_failed(const char *path, int mount_err);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,20 @@
|
||||
#ifndef SM_MANUAL_H
|
||||
#define SM_MANUAL_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
struct AppDbTitleList;
|
||||
|
||||
// Visit each manual source path from manual.lst.
|
||||
bool sm_manual_for_each_path(bool (*visit)(const char *path, void *ctx),
|
||||
void *ctx);
|
||||
// Mark previously installed manual titles as deleted when they disappear from
|
||||
// app.db, and remove their source lines from manual.lst.
|
||||
bool sm_manual_reconcile_deleted_titles(const struct AppDbTitleList *app_db_titles,
|
||||
bool app_db_titles_ready);
|
||||
// Record that a manual source is installed and associated with a title.
|
||||
void sm_manual_note_installed(const char *manual_source_path,
|
||||
const char *title_id,
|
||||
const char *title_name);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,26 @@
|
||||
#ifndef SM_MDBG_H
|
||||
#define SM_MDBG_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
// Initialize mdbg-backed crash monitoring state.
|
||||
void sm_mdbg_init(void);
|
||||
// Shut down mdbg-backed crash monitoring state.
|
||||
void sm_mdbg_shutdown(void);
|
||||
// Start monitoring the tracked game process for crash-candidate state.
|
||||
void sm_mdbg_game_on_exec(pid_t pid, const char *title_id, uint32_t app_id);
|
||||
// Record that kstuff auto-pause was actually applied to the tracked game.
|
||||
void sm_mdbg_game_on_kstuff_pause(pid_t pid, uint64_t pause_time_us,
|
||||
uint32_t pause_delay_seconds);
|
||||
// Stop monitoring the tracked game process on exit.
|
||||
void sm_mdbg_game_on_exit(pid_t pid);
|
||||
// Clear tracked game monitoring state unconditionally.
|
||||
void sm_mdbg_game_shutdown(void);
|
||||
// Return the next wake deadline in monotonic microseconds, or 0 when idle.
|
||||
uint64_t sm_mdbg_next_wake_us(uint64_t now_us);
|
||||
// Poll the tracked process for mdbg crash-candidate state.
|
||||
void sm_mdbg_poll(void);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,325 @@
|
||||
#ifndef SM_MOUNT_DEFS_H
|
||||
#define SM_MOUNT_DEFS_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define IMAGE_MOUNT_READ_ONLY 1
|
||||
|
||||
// Build-time backend defaults. runtime config.ini can override both values.
|
||||
// 1 = legacy /dev/mdctl, 0 = /dev/lvdctl.
|
||||
#define EXFAT_ATTACH_USE_MDCTL 0
|
||||
#define UFS_ATTACH_USE_MDCTL 0
|
||||
|
||||
/*
|
||||
* Mount profiles and cache layers
|
||||
* ================================================================
|
||||
*
|
||||
* LVD geometry
|
||||
* ------------
|
||||
* sector_size is the logical sector exported by /dev/lvdN. It controls BIO
|
||||
* alignment and is not a filesystem allocation block. ShadowMount defaults to
|
||||
* 512 bytes for exFAT and 4096 bytes for UFS/PFS.
|
||||
*
|
||||
* secondary_unit is the LVD mapping/split granularity. The optimized profile
|
||||
* uses 64 KiB for all supported image filesystems. The legacy 1.6 profile uses
|
||||
* sector_size except for exFAT, which already used 64 KiB. The value must be
|
||||
* divisible by sector_size; 64 KiB is required by the type-5 BFS sdimg path.
|
||||
*
|
||||
* LVD attach ABI and image types
|
||||
* ------------------------------
|
||||
* The kernel exposes base attach V0, extended attach V1/Attach2 and detach.
|
||||
* ShadowMount builds one complete V0 layer and marks it NO_BITMAP. source_type
|
||||
* 1 is a regular file; source_type 2 is a device/special source.
|
||||
*
|
||||
* Accepted image types and kernel names:
|
||||
* 0 Dfl, 1 GmI, 2 GmO, 3 PtcI, 4 PtcO, 5 Sv, 6 Trp, 7 Dwl,
|
||||
* 8 AcI, 9 AcO, 10 MaI, 11 MaO, 12 Pcdwl.
|
||||
*
|
||||
* Types 0/5/6/7/12 use lvdstart_vnode_direct. Types 1-4/8-11 use
|
||||
* lvdstart_vnode and resolve each secondary unit through layered bitmap and
|
||||
* optional OTBL metadata. Optimized ShadowMount profiles are:
|
||||
*
|
||||
* .exfat type 5 Sv, direct + BFS sdimg batching
|
||||
* fallback: type 0 Dfl
|
||||
* .ffpkg (UFS) type 5 Sv, direct + BFS sdimg batching
|
||||
* standalone .ffpfs type 5 Sv, direct
|
||||
* outer .ffpfsc type 9 AcO, layered package outer
|
||||
* inner .ffpfs/pfs_image.dat type 8 AcI, layered package inner
|
||||
*
|
||||
* ShadowMount deliberately uses type 5 with single/save attach flags for UFS
|
||||
* images so a standalone .ffpkg on BFS can use sdimg batching. Type 7 with
|
||||
* Download Data instead favors HDD request isolation. UFS nmount has
|
||||
* no separate download-data flag: 0x10000000 is the generic MNT_NOATIME bit,
|
||||
* not an UFS or DD mode. Do not substitute GmO/PtcO only to request the UFS
|
||||
* block-map cache: that replaces direct dispatch with layered dispatch. The
|
||||
* kernel requests that cache only for type 2/4, an HDD-class regular backing
|
||||
* vnode, and a backing filesystem that is UFS.
|
||||
*
|
||||
* Type-5 BFS sdimg fast path
|
||||
* -------------------------
|
||||
* Type 5 labels the LVD provider "Ssd" and may enable BFS sdimg when the image
|
||||
* is a regular file on internal BFS and secondary_unit is exactly 64 KiB. The
|
||||
* kernel also applies platform/storage-mode and lower-provider guards.
|
||||
*
|
||||
* When enabled, lvd_worker accepts only requests whose length is exactly
|
||||
* 64 KiB and whose backing-file offset is 64-KiB aligned. It collects the
|
||||
* initial request plus up to 30 matching queued requests and submits them via
|
||||
* bfs_iosession_rw_sdimg/BfsSdimg. All other requests fall back to the normal
|
||||
* vnode path. This is block-I/O batching, not an exFAT cache or decompressor.
|
||||
*
|
||||
* For exFAT keep sector_size at 512 and create the filesystem with a 64-KiB
|
||||
* allocation unit. Setting sector_size to 65536 does not request this mode;
|
||||
* the relevant LVD value is secondary_unit. The path is unavailable through
|
||||
* MD or when the image is backed by USB/UFS/PFS/exFAT instead of BFS.
|
||||
*
|
||||
* Raw flags and validator
|
||||
* -----------------------
|
||||
* Download-data flags exist only in the LVD attach ABI. ShadowMount calls
|
||||
* /dev/lvdctl directly and normalizes wrapper-style presets:
|
||||
* raw 0x8/0x9 -> single/save-data family 0x14/0x1c;
|
||||
* raw 0xc/0xd -> download-data family 0x16/0x1e.
|
||||
* Firmware pairs raw bit 0 with read-only variants; it is not an independent
|
||||
* LVD read-only flag. The normalized validator masks are:
|
||||
*
|
||||
* type 0,5,6,8-11: (flags & 0x92) == 0x10
|
||||
* type 1: (flags & 0x82) == 0x80
|
||||
* type 2,3,4: (flags & 0x82) == 0
|
||||
* type 7,12: (flags & 0x92) == 0x12
|
||||
*
|
||||
* Download Data HDD worker policy
|
||||
* -------------------------------
|
||||
* Download Data is normalized LVD bit 0x02 and is required by types 7 and 12.
|
||||
* On an HDD-class provider it makes LVD create two request workers instead of
|
||||
* one: worker 0 is labelled System (lvdhdsw), worker 1 Game (lvdhdgw). The LVD
|
||||
* start routine routes each BIO using the kernel classification byte at
|
||||
* BIO + 0x172: value 1 selects Game and every other value selects System. LVD
|
||||
* also tracks and drains the workers separately during shutdown. The flag is
|
||||
* therefore an HDD queue-isolation/QoS policy, not an on-disk-format property.
|
||||
*
|
||||
* Download Data does not select direct BIO dispatch (image_type does), enable
|
||||
* bitmap/OTBL interpretation, populate the UFS block-map cache (types 2/4),
|
||||
* unlock BFS sdimg batching (type 5), or enable PFS/compression caches. For a
|
||||
* standalone image, type 7 + Download Data is the HDD-oriented choice when
|
||||
* System/Game queue separation matters. Type 5 + Single/Save is preferable for
|
||||
* a BFS sdimg-backed .ffpkg when maximum sequential/batched throughput matters
|
||||
* and the two-queue HDD policy is not needed.
|
||||
*
|
||||
* Layer metadata and independent caches
|
||||
* -------------------------------------
|
||||
* LVD bitmap says whether a logical unit is present in a layer. V1 OTBL maps a
|
||||
* logical unit to a physical unit inside the layer file. It is unrelated to a
|
||||
* PFSC compressed-offset table.
|
||||
*
|
||||
* LVD bitmap/OTBL image-layer presence/remapping
|
||||
* UFS block-map cache backing UFS file blocks -> physical UFS blocks
|
||||
* PFS GDDR5 ioctl PFSC compressed offsets and optional full ICV
|
||||
*
|
||||
* The UFS cache depends on the filesystem containing the image, not UFS stored
|
||||
* inside .ffpkg, and is unavailable for BFS/PFS/exFAT backing files. The PFS
|
||||
* metadata ioctl applies to every nested image backed by pfs, ppr_pfs,
|
||||
* transaction_pfs, or a forwarding overlay; the filesystem inside the image
|
||||
* does not need to be PFS.
|
||||
*
|
||||
* PFS/PPR compression dispatch
|
||||
* ----------------------------
|
||||
* Classic pfs and ppr_pfs are distinct kernel read paths. A classic compressed
|
||||
* PFS block uses the classic PFSC header/table interpretation and the pfs path
|
||||
* dispatches its compressed payload to zlib. The PPR variant interprets the
|
||||
* PPR block metadata and can dispatch Kraken. LVD image_type, the nested
|
||||
* selector and PFS_GDDR5_CACHE_IOCTL do not select a decompressor and cannot
|
||||
* turn a classic zlib block header into a PPR/Kraken block. The image creator
|
||||
* must emit the matching on-disk block records and offset table.
|
||||
*
|
||||
* Compression is decided per block. Files excluded from compression and blocks
|
||||
* that are not smaller after compression are stored as raw blocks but remain
|
||||
* represented in the same image offset mapping. This is independent of the
|
||||
* mount-time caches described below.
|
||||
*
|
||||
* PFS compression caches and big-app reserve
|
||||
* ------------------------------------------
|
||||
* Three independent mechanisms have similar historical GDDR5/SBRAM cache
|
||||
* names and must not be treated as one allocation:
|
||||
*
|
||||
* 1. A compressed PFS/PFSC mount can allocate its own cmp_offtbl cache. The
|
||||
* kernel message
|
||||
*
|
||||
* pfs_allocate_cmp_offtbl_cache(): cmp_offtbl is cached
|
||||
*
|
||||
* confirms this mount-local compressed-block offset table. It is created
|
||||
* by the PFS mount path and is not evidence that a big-app memory reserve
|
||||
* exists.
|
||||
*
|
||||
* 2. PFS_GDDR5_CACHE_IOCTL can be issued on a nested image's backing vnode
|
||||
* before LVD attach. The optimized profile requests it automatically; the
|
||||
* legacy profile requires legacy_gddr5_cache=1. It requests the containing
|
||||
* PFS/PPR read path to cache the
|
||||
* compressed-offset table used to reach that vnode's data; it does not
|
||||
* inspect or impose a format on the bytes stored inside the file. Thus the
|
||||
* nested file may contain PFS, UFS, exFAT or arbitrary block data and need
|
||||
* not itself be compressed. The hard requirement for the offset request is
|
||||
* that the containing PFS/PPR image has its compressed-image flag set. On
|
||||
* an uncompressed containing image the kernel logs "NOT a compressed
|
||||
* image" and accepts the request as a compatibility no-op.
|
||||
*
|
||||
* cmp_cache_units is a count of shared-cache allocation units, not bytes.
|
||||
* The compressed-offset pool uses 0x10000-byte units and has 600 units
|
||||
* (37.5 MiB) with at most eight simultaneous slots. A zero count makes the
|
||||
* kernel derive the required count from the on-disk offset-table geometry;
|
||||
* this is the preferred full-table/default request. An explicit nonzero
|
||||
* count limits the reservation and can force more LRU refills. EBUSY is
|
||||
* non-fatal: it means that this vnode already has a cache or that all eight
|
||||
* slots are occupied. ENOMEM means that the requested units exceed the
|
||||
* remaining total or contiguous shared-cache space.
|
||||
*
|
||||
* The optional full-ICV request is independent. It requires the containing
|
||||
* image's signed-image flag and usable ICV metadata; otherwise the kernel
|
||||
* rejects that half of the request. It also has its own eight-slot pool.
|
||||
* Neither half selects zlib/Kraken or changes the block-record format.
|
||||
*
|
||||
* 3. The 180-MiB big-app reserve is application-lifecycle memory. It is not
|
||||
* allocated by the ioctl, LVD, PFS nmount, EnsureBigAppBudget, or a visible
|
||||
* /dev/sbram device. During BEGIN_APP_MOUNT_EVENT the kernel allocates
|
||||
* 0x0B400000 bytes aligned to 0x200000 for selected category values:
|
||||
*
|
||||
* 0x0000F000, 0x0000F100, 0x0001F000, 0x0001F300,
|
||||
* 0x0002F000, 0x0200F100.
|
||||
*
|
||||
* The allocation belongs to the big-app/application context. PFS can bind
|
||||
* to it through the application lifecycle callbacks; userland is not given
|
||||
* a buffer pointer. EndAppMount tears the context down and releases the
|
||||
* reserve after the application pre-delete path. Consequently a managed
|
||||
* image stack must remain mounted until the matching EndAppMount hook, and
|
||||
* no explicit metadata-cache clear should run while its PFS vnode is live.
|
||||
*
|
||||
* For unsigned ShadowMount PFS profiles sigverify=0, so request only compressed
|
||||
* offsets (cache_full_icv=0). Signing does not make ordinary reads faster: it
|
||||
* enables integrity verification and can add work; a cached full ICV table
|
||||
* only reduces metadata lookup cost when verification is actually enabled.
|
||||
* Both compressed-offset caches are properties of the outer PFS read path.
|
||||
* They can accelerate an LVD-backed nested exFAT/UFS/PFS image because the
|
||||
* inner filesystem format is irrelevant at that layer.
|
||||
*
|
||||
* Backend and nmount policy
|
||||
* -------------------------
|
||||
* LVD is the default for every format. MD is a compatibility backend for
|
||||
* UFS/exFAT; it uses AUTOUNIT|ASYNC and configurable logical sectors (modern
|
||||
* UFS default 4096, legacy UFS default 512, exFAT default 512). Read-only state
|
||||
* is applied to the backing device and nmount. The shared nmount profiles are:
|
||||
*
|
||||
* UFS ufs + budgetid + async + noatime + automounted
|
||||
* exFAT exfatfs + large + static-timezone + async + noatime + ignoreacl
|
||||
* PFS pfs + AC + game + sigverify/playgo/disc=0 + zero ekpfs
|
||||
* + async + noatime
|
||||
*
|
||||
* The optimized profile passes only MNT_RDONLY (0x1) in nmount's numeric flags
|
||||
* argument. Legacy UFS also passes MNT_NOATIME, matching version 1.6. Generic
|
||||
* named options are decoded by the kernel as async=MNT_ASYNC (0x40),
|
||||
* noatime=MNT_NOATIME (0x10000000), and automounted=MNT_AUTOMOUNTED
|
||||
* (0x200000000). UFS options compressedfile, iochunk, largewrite and related
|
||||
* zone-aware modes are separate filesystem features and are not DD flags.
|
||||
*
|
||||
* force is appended only for explicit recovery. Unsigned PFS keeps signature
|
||||
* verification disabled. The optimized profile primes every nested image on
|
||||
* PFS before LVD attach; the legacy profile does so only when explicitly
|
||||
* enabled.
|
||||
*/
|
||||
|
||||
#define LVD_CTRL_PATH "/dev/lvdctl"
|
||||
#define MD_CTRL_PATH "/dev/mdctl"
|
||||
#define SCE_LVD_IOC_ATTACH_V0 0xC0286D00
|
||||
#define SCE_LVD_IOC_ATTACH_V1 0xC0286D09
|
||||
#define SCE_LVD_IOC_DETACH 0xC0286D01
|
||||
#define LVD_ATTACH_IO_VERSION_V0 0u
|
||||
#define LVD_ATTACH_IO_VERSION_V1 1u
|
||||
#define LVD_ATTACH_RAW_BIT_RO_VARIANT 0x0001u
|
||||
#define LVD_ATTACH_RAW_BIT_INNER_PROFILE 0x0002u
|
||||
#define LVD_ATTACH_RAW_BIT_DOWNLOAD_DATA_PROFILE 0x0004u
|
||||
#define LVD_ATTACH_RAW_BIT_SINGLE_SAVE_PROFILE 0x0008u
|
||||
#define LVD_ATTACH_RAW_PASSTHROUGH_MASK 0x8000u
|
||||
|
||||
#define LVD_ATTACH_FLAG_DOWNLOAD_DATA_PROFILE 0x0002u
|
||||
#define LVD_ATTACH_FLAG_BASE 0x0004u
|
||||
#define LVD_ATTACH_FLAG_RO_VARIANT 0x0008u
|
||||
#define LVD_ATTACH_FLAG_SINGLE_SAVE_PROFILE 0x0010u
|
||||
#define LVD_ATTACH_FLAG_INNER_PROFILE 0x0080u
|
||||
|
||||
#define LVD_ATTACH_RAW_FLAGS_SINGLE_RW LVD_ATTACH_RAW_BIT_SINGLE_SAVE_PROFILE
|
||||
#define LVD_ATTACH_RAW_FLAGS_SINGLE_RO \
|
||||
(LVD_ATTACH_RAW_FLAGS_SINGLE_RW | LVD_ATTACH_RAW_BIT_RO_VARIANT)
|
||||
#define LVD_ATTACH_RAW_FLAGS_DD_RW \
|
||||
(LVD_ATTACH_RAW_BIT_SINGLE_SAVE_PROFILE | \
|
||||
LVD_ATTACH_RAW_BIT_DOWNLOAD_DATA_PROFILE)
|
||||
#define LVD_ATTACH_RAW_FLAGS_DD_RO \
|
||||
(LVD_ATTACH_RAW_FLAGS_DD_RW | LVD_ATTACH_RAW_BIT_RO_VARIANT)
|
||||
|
||||
#define LVD_ATTACH_FLAGS_SINGLE_RW \
|
||||
(LVD_ATTACH_FLAG_BASE | LVD_ATTACH_FLAG_SINGLE_SAVE_PROFILE)
|
||||
#define LVD_ATTACH_FLAGS_SINGLE_RO \
|
||||
(LVD_ATTACH_FLAGS_SINGLE_RW | LVD_ATTACH_FLAG_RO_VARIANT)
|
||||
#define LVD_ATTACH_FLAGS_DD_RW \
|
||||
(LVD_ATTACH_FLAGS_SINGLE_RW | LVD_ATTACH_FLAG_DOWNLOAD_DATA_PROFILE)
|
||||
#define LVD_ATTACH_FLAGS_DD_RO \
|
||||
(LVD_ATTACH_FLAGS_DD_RW | LVD_ATTACH_FLAG_RO_VARIANT)
|
||||
#define LVD_SECTOR_SIZE_EXFAT 512u
|
||||
#define LVD_SECTOR_SIZE_UFS 4096u
|
||||
#define LVD_SECTOR_SIZE_PFS 4096u
|
||||
#define LVD_SECONDARY_UNIT_IMAGE_IO 0x10000u
|
||||
#define MD_SECTOR_SIZE_EXFAT 512u
|
||||
#define MD_SECTOR_SIZE_UFS_OPTIMIZED 4096u
|
||||
#define MD_SECTOR_SIZE_UFS_LEGACY 512u
|
||||
_Static_assert(LVD_SECONDARY_UNIT_IMAGE_IO % LVD_SECTOR_SIZE_EXFAT == 0,
|
||||
"exFAT LVD geometry must be aligned");
|
||||
_Static_assert(LVD_SECONDARY_UNIT_IMAGE_IO % LVD_SECTOR_SIZE_UFS == 0,
|
||||
"UFS LVD geometry must be aligned");
|
||||
_Static_assert(LVD_SECONDARY_UNIT_IMAGE_IO % LVD_SECTOR_SIZE_PFS == 0,
|
||||
"PFS LVD geometry must be aligned");
|
||||
#define LVD_ATTACH_IMAGE_TYPE_SINGLE 0
|
||||
#define LVD_ATTACH_IMAGE_TYPE_SAVE_DATA 5
|
||||
#define LVD_ATTACH_IMAGE_TYPE_DOWNLOAD_DATA 7
|
||||
#define PFS_NESTED_OUTER_IMG_TYPE 0x02u
|
||||
#define PFS_NESTED_INNER_IMG_TYPE 0x82u
|
||||
#define LVD_ATTACH_LAYER_COUNT 1
|
||||
#define LVD_ENTRY_TYPE_FILE 1
|
||||
#define LVD_ENTRY_TYPE_SPECIAL 2
|
||||
#define LVD_ENTRY_FLAG_NO_BITMAP 0x1
|
||||
#define LVD_NODE_WAIT_US 100000
|
||||
#define LVD_NODE_WAIT_RETRIES 100
|
||||
// PFS nmount values used by ShadowMount. Other firmware flows also expose
|
||||
// transaction_pfs/ppr_pfs, SD/GD key modes and system budget IDs.
|
||||
#define DEVPFS_BUDGET_GAME "game"
|
||||
#define DEVPFS_BUDGET_SYSTEM "system"
|
||||
#define DEVPFS_MKEYMODE_SD "SD"
|
||||
#define DEVPFS_MKEYMODE_GD "GD"
|
||||
#define DEVPFS_MKEYMODE_AC "AC"
|
||||
#define PFS_MOUNT_BUDGET_ID DEVPFS_BUDGET_GAME
|
||||
#define PFS_MOUNT_MKEYMODE DEVPFS_MKEYMODE_AC
|
||||
#define PFS_MOUNT_SIGVERIFY 0
|
||||
#define PFS_MOUNT_PLAYGO 0
|
||||
#define PFS_MOUNT_DISC 0
|
||||
#define PFS_GDDR5_CACHE_IOCTL 0x80209168
|
||||
|
||||
// 0x80209168 is an IOC_IN request with a 0x20-byte payload and is accepted on a
|
||||
// read-only fd. Despite the userland "GDDR5 cache" name, the pfs and ppr_pfs
|
||||
// vnode handlers use it for two metadata caches belonging to the containing
|
||||
// filesystem: compressed offsets (only when its compressed-image flag is set)
|
||||
// and, optionally, full ICV (only for a signed image). The nested file's own
|
||||
// magic, filesystem and compression method are not inspected. Counts are
|
||||
// allocation units rather than bytes; zero asks the kernel to derive the full
|
||||
// size. The caller must have the kernel privilege checked as privilege 0x2ab.
|
||||
typedef struct pfs_gddr5_cache_request {
|
||||
uint8_t cache_cmp_offsets;
|
||||
uint8_t reserved01[7];
|
||||
uint64_t cmp_cache_units; // 0 = auto; each cmp unit is 0x10000 bytes
|
||||
uint8_t cache_full_icv;
|
||||
uint8_t reserved11[7];
|
||||
uint64_t icv_cache_units; // 0 = auto; independent signed-image ICV pool
|
||||
} pfs_gddr5_cache_request_t;
|
||||
|
||||
_Static_assert(sizeof(pfs_gddr5_cache_request_t) == 0x20,
|
||||
"PFS GDDR5 cache request must be 0x20 bytes");
|
||||
|
||||
// 4x64-bit PFS key encoded as 64 hex chars.
|
||||
#define PFS_ZERO_EKPFS_KEY_HEX \
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,31 @@
|
||||
#ifndef SM_MOUNT_DEVICE_H
|
||||
#define SM_MOUNT_DEVICE_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "sm_types.h"
|
||||
|
||||
// Return a printable name for an attach backend enum value.
|
||||
const char *attach_backend_name(attach_backend_t backend);
|
||||
// Check whether a source path has aged enough to be mounted safely.
|
||||
bool is_source_stable_for_mount(const char *path, const char *name,
|
||||
const char *tag);
|
||||
// Wait until a device node appears or disappears.
|
||||
bool wait_for_dev_node_state(const char *devname, bool should_exist);
|
||||
// Resolve backend and unit ID for a mounted image path.
|
||||
bool resolve_device_from_mount(const char *mount_point,
|
||||
attach_backend_t *backend_out, int *unit_out);
|
||||
// Return true if a path is an active mount point.
|
||||
bool is_active_image_mount_point(const char *path);
|
||||
// Wait until /dev/lvd2 is no longer mounted during startup.
|
||||
bool wait_for_lvd_release(void);
|
||||
// Detach a previously attached MD or LVD unit. A non-NULL state makes an
|
||||
// accepted asynchronous detach idempotent across later cleanup attempts.
|
||||
bool detach_attached_unit(attach_backend_t backend, int unit_id,
|
||||
attached_unit_detach_state_t *state);
|
||||
// Wait for an accepted detach to remove its node, or detect unit reuse.
|
||||
bool wait_for_attached_unit_release(
|
||||
attach_backend_t backend, int unit_id,
|
||||
const attached_unit_detach_state_t *state);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,7 @@
|
||||
#ifndef SM_MOUNT_DIAG_H
|
||||
#define SM_MOUNT_DIAG_H
|
||||
|
||||
// Log read-only evidence explaining why unmount(path) returned EBUSY.
|
||||
void sm_mount_diag_log_busy(const char *mount_point);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,42 @@
|
||||
#ifndef SM_PATH_STATE_H
|
||||
#define SM_PATH_STATE_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
// Load cached game info if the param file state still matches.
|
||||
bool load_cached_game_info(const char *path, const struct stat *param_st,
|
||||
char *out_id, char *out_name, bool *valid_out);
|
||||
// Store parsed game info together with the current param file state.
|
||||
void store_cached_game_info(const char *path, const struct stat *param_st,
|
||||
bool valid, const char *title_id,
|
||||
const char *title_name);
|
||||
// Drop expired entries from path-based retry and metadata state.
|
||||
void prune_path_state(void);
|
||||
// Drop expired entries that belong to a specific scan root.
|
||||
void prune_path_state_for_root(const char *root);
|
||||
// Return whether scans for a path are temporarily suppressed after failures.
|
||||
bool is_missing_param_scan_limited(const char *path);
|
||||
// Record a missing param.json failure for scan backoff.
|
||||
void record_missing_param_failure(const char *path);
|
||||
// Clear missing-param backoff state for a path.
|
||||
void clear_missing_param_entry(const char *path);
|
||||
// Return whether image mount retries are temporarily suppressed for a path.
|
||||
bool is_image_mount_limited(const char *path);
|
||||
// Increment and return the image mount retry counter for a path.
|
||||
uint8_t bump_image_mount_attempts(const char *path);
|
||||
// Clear image mount retry state for a path.
|
||||
void clear_image_mount_attempts(const char *path);
|
||||
// Return whether backport overlay remounts are suppressed for a path.
|
||||
bool is_backport_mount_blocked(const char *path);
|
||||
// Record the first failed backport overlay mount for a path.
|
||||
void note_backport_mount_failure(const char *path);
|
||||
// Clear failed backport overlay mount state for a path.
|
||||
void clear_backport_mount_failure(const char *path);
|
||||
// Return true once for a missing manual source until the path appears again.
|
||||
bool note_manual_missing_source_once(const char *path);
|
||||
// Allow a manual source to be reported missing again after it becomes visible.
|
||||
void clear_manual_missing_source(const char *path);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,27 @@
|
||||
#ifndef SM_PATH_UTILS_H
|
||||
#define SM_PATH_UTILS_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include "sm_types.h"
|
||||
|
||||
// Return the last path component without modifying the input string.
|
||||
const char *get_filename_component(const char *path);
|
||||
// Return true when stat() can see the path.
|
||||
bool path_exists(const char *path);
|
||||
// Return true when a path lives under the image mount root.
|
||||
bool is_under_image_mount_base(const char *path);
|
||||
// Return true when a path is the PFSC container mount root or lives under it.
|
||||
bool is_pfsc_image_mount_base_or_child(const char *path);
|
||||
// Return true when path is equal to root or is under it.
|
||||
bool path_matches_root_or_child(const char *path, const char *root);
|
||||
// Return true when a path lives on USB-backed storage.
|
||||
bool is_usb_storage_path(const char *path);
|
||||
// Return true when the path's USB root is mounted, not just an empty mount
|
||||
// point under /mnt.
|
||||
bool usb_storage_root_mounted(const char *path);
|
||||
// Build "<scan_path>/backports" for a managed scan root.
|
||||
bool build_backports_root_path(const char *scan_path, char out[MAX_PATH]);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,53 @@
|
||||
#ifndef SM_PATHS_H
|
||||
#define SM_PATHS_H
|
||||
|
||||
#define IMAGE_MOUNT_BASE "/mnt/shadowmnt"
|
||||
#define PFSC_IMAGE_MOUNT_BASE "/mnt/shadowmnt/pfsc"
|
||||
|
||||
#define DEFAULT_BACKPORTS_DIR_NAME "backports"
|
||||
#define DEFAULT_GLOBAL_FAKELIB_PATH "/data/shadowmount/fakelib"
|
||||
#define DEFAULT_EMULATORS_PATH "/data/shadowmount/emus"
|
||||
#define DEFAULT_AMPR_UPDATE_URL \
|
||||
"https://github.com/drakmor/ampr_emu/releases/latest/download/" \
|
||||
"libSceAmpr.sprx"
|
||||
#define FAKELIB_CACHE_PATH "/data/shadowmount/cache"
|
||||
#define LOG_DIR "/data/shadowmount"
|
||||
#define LOG_FILE "/data/shadowmount/debug.log"
|
||||
#define LOG_FILE_PREV "/data/shadowmount/debug.log.1"
|
||||
#define CONFIG_FILE "/data/shadowmount/config.ini"
|
||||
#define AUTOTUNE_FILE "/data/shadowmount/autotune.ini"
|
||||
#define MANUAL_LIST_FILE "/data/shadowmount/manual.lst"
|
||||
#define MANUAL_STATUS_FILE "/data/shadowmount/manual.status"
|
||||
#define IMAGE_INDEX_FILE_PATH "/data/shadowmount/image_index.bin"
|
||||
#define APPMETA_BASE "/user/appmeta"
|
||||
#define APP_BASE "/user/app"
|
||||
#define KSTUFF_NOAUTOMOUNT_FILE "/data/.kstuff_noautomount"
|
||||
#define KILL_FILE "/data/shadowmount/STOP"
|
||||
#define TOAST_FILE "/data/shadowmount/notify.txt"
|
||||
#define NOTIFY_ICON_DIR "/user/data/shadowmount"
|
||||
#define NOTIFY_ICON_FILE "/user/data/shadowmount/smp_icon.png"
|
||||
#define APP_DB_PATH "/system_data/priv/mms/app.db"
|
||||
|
||||
/* Compile-time default scan roots (used when config has no scanpath entries). */
|
||||
#define SM_DEFAULT_SCAN_PATHS_INITIALIZER \
|
||||
{ \
|
||||
/* Internal */ \
|
||||
"/data/homebrew", "/data/etaHEN/games", \
|
||||
/* Extended Storage */ \
|
||||
"/mnt/ext0/homebrew", "/mnt/ext0/etaHEN/games", \
|
||||
/* M.2 Drive */ \
|
||||
"/mnt/ext1/homebrew", "/mnt/ext1/etaHEN/games", \
|
||||
/* USB Subfolders */ \
|
||||
"/mnt/usb0/homebrew", "/mnt/usb1/homebrew", "/mnt/usb2/homebrew", \
|
||||
"/mnt/usb3/homebrew", "/mnt/usb4/homebrew", "/mnt/usb5/homebrew", \
|
||||
"/mnt/usb6/homebrew", "/mnt/usb7/homebrew", \
|
||||
"/mnt/usb0/etaHEN/games", "/mnt/usb1/etaHEN/games", \
|
||||
"/mnt/usb2/etaHEN/games", "/mnt/usb3/etaHEN/games", \
|
||||
"/mnt/usb4/etaHEN/games", "/mnt/usb5/etaHEN/games", \
|
||||
"/mnt/usb6/etaHEN/games", "/mnt/usb7/etaHEN/games", \
|
||||
/* USB Root Paths */ \
|
||||
"/mnt/usb0", "/mnt/usb1", "/mnt/usb2", "/mnt/usb3", "/mnt/usb4", \
|
||||
"/mnt/usb5", "/mnt/usb6", "/mnt/usb7", "/mnt/ext0", "/mnt/ext1", NULL \
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,73 @@
|
||||
#ifndef SM_PLATFORM_H
|
||||
#define SM_PLATFORM_H
|
||||
|
||||
#include <ctype.h>
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <signal.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/file.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mdioctl.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/uio.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <ps5/kernel.h>
|
||||
|
||||
typedef struct {
|
||||
uint32_t app_id;
|
||||
uint64_t unknown1;
|
||||
char title_id[14];
|
||||
char unknown2[0x3c];
|
||||
} app_info_t;
|
||||
|
||||
static inline uint32_t sm_firmware_major_version(void) {
|
||||
uint32_t fw = kernel_get_fw_version();
|
||||
uint32_t major_bcd = (fw >> 24) & 0xFFu;
|
||||
return ((major_bcd >> 4) & 0xFu) * 10u + (major_bcd & 0xFu);
|
||||
}
|
||||
|
||||
static inline bool sm_set_fd_nonblocking(int fd) {
|
||||
int flags = fcntl(fd, F_GETFL, 0);
|
||||
if (flags < 0)
|
||||
return false;
|
||||
return fcntl(fd, F_SETFL, flags | O_NONBLOCK) == 0;
|
||||
}
|
||||
|
||||
// --- SDK Imports ---
|
||||
int sceAppInstUtilInitialize(void);
|
||||
int sceAppInstUtilAppInstallAll(void *reserved);
|
||||
int sceAppInstUtilAppUnInstall(const char *title_id);
|
||||
int sceKernelGetAppInfo(pid_t pid, app_info_t *info);
|
||||
int sceKernelUsleep(unsigned int microseconds);
|
||||
int32_t sceSystemServiceParamGetInt(int32_t param_id, int32_t *value);
|
||||
int sceUserServiceInitialize(void *);
|
||||
void sceUserServiceTerminate(void);
|
||||
|
||||
// Standard Notification
|
||||
typedef struct notify_request {
|
||||
char unused[45];
|
||||
char message[3075];
|
||||
} notify_request_t;
|
||||
int sceKernelSendNotificationRequest(int, notify_request_t *, size_t, int);
|
||||
|
||||
#define IOVEC_ENTRY(x) \
|
||||
{(void *)(x), \
|
||||
((const char *)(x) == NULL ? 0u : (size_t)(strlen((const char *)(x)) + 1u))}
|
||||
#define IOVEC_SIZE(x) (sizeof(x) / sizeof(struct iovec))
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,33 @@
|
||||
#ifndef SM_RUNTIME_H
|
||||
#define SM_RUNTIME_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
// Install process signal handlers used for graceful shutdown.
|
||||
void install_signal_handlers(void);
|
||||
// Return process pid with the given name, or 0 if not found.
|
||||
pid_t find_pid_by_name(const char *name, bool exclude_self);
|
||||
// Return true when shutdown was requested by signal or kill file.
|
||||
bool should_stop_requested(void);
|
||||
// Request graceful shutdown with a descriptive source string.
|
||||
void request_shutdown_stop(const char *reason);
|
||||
// Return true when the payload is paused for system suspend/resume.
|
||||
bool runtime_sleep_mode_active(void);
|
||||
// Return true while a launch may briefly wait for USB storage after resume.
|
||||
bool runtime_resume_grace_active(void);
|
||||
// Enter or leave sleep mode with a descriptive source string.
|
||||
// Returns true when the runtime sleep state changed.
|
||||
bool request_runtime_sleep_mode(bool active, const char *reason);
|
||||
// Serialize mount/link/cache publication against suspend cleanup.
|
||||
void runtime_mount_state_lock(void);
|
||||
void runtime_mount_state_unlock(void);
|
||||
// Request an immediate scan cycle with a descriptive source string.
|
||||
void request_scan_now(const char *reason);
|
||||
// Consume a pending immediate scan request and copy its reason into caller storage.
|
||||
bool consume_scan_now_request(char *reason_out, size_t reason_out_size);
|
||||
// Sleep in chunks and stop early if shutdown was requested.
|
||||
bool sleep_with_stop_check(unsigned int total_us);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,39 @@
|
||||
#ifndef SM_SCAN_H
|
||||
#define SM_SCAN_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "sm_limits.h"
|
||||
|
||||
typedef struct scan_candidate scan_candidate_t;
|
||||
|
||||
// Unmount and clean up mounts whose backing sources disappeared.
|
||||
void cleanup_lost_sources_before_scan(void);
|
||||
// Unmount and clean up mounts whose backing sources disappeared under one root.
|
||||
void cleanup_lost_sources_for_scan_root(const char *scan_root);
|
||||
// Immediately unmount runtime mounts backed by USB storage for suspend.
|
||||
void unmount_usb_sources_for_suspend(void);
|
||||
// Scan configured roots and collect install candidates.
|
||||
int collect_scan_candidates(scan_candidate_t *candidates, int max_candidates,
|
||||
int *total_found_out,
|
||||
bool *unstable_found_out);
|
||||
// Scan a single configured root and collect install candidates.
|
||||
int collect_scan_candidates_for_scan_root(const char *scan_root,
|
||||
scan_candidate_t *candidates,
|
||||
int max_candidates,
|
||||
int *total_found_out,
|
||||
bool *unstable_found_out);
|
||||
// Resolve the source directory of a title backport without mounting it.
|
||||
bool resolve_backport_path_for_title(const char *title_id,
|
||||
const char *owning_scan_root,
|
||||
char backport_path[MAX_PATH]);
|
||||
// Ensure a stable backport overlay is mounted when one is present.
|
||||
// Absence is harmless; an unusable or failed required overlay returns false.
|
||||
bool mount_backport_overlay_for_title(const char *source_path,
|
||||
const char *title_id,
|
||||
const char *owning_scan_root,
|
||||
bool *unstable_found_out);
|
||||
// Remove discovery title/image mounts while preserving persistent metadata.
|
||||
bool release_scan_runtime_mounts(void);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,31 @@
|
||||
#ifndef SM_SCAN_TREE_H
|
||||
#define SM_SCAN_TREE_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef enum {
|
||||
SM_SCAN_TREE_DIR_DESCEND = 0,
|
||||
SM_SCAN_TREE_DIR_SKIP_DESCEND,
|
||||
SM_SCAN_TREE_DIR_ABORT,
|
||||
} sm_scan_tree_dir_visit_t;
|
||||
|
||||
typedef sm_scan_tree_dir_visit_t (*sm_scan_tree_dir_fn)(const char *dir_path,
|
||||
unsigned int depth_from_root,
|
||||
void *ctx);
|
||||
typedef bool (*sm_scan_tree_image_fn)(const char *image_path,
|
||||
const char *image_name,
|
||||
unsigned int depth_from_root,
|
||||
void *ctx);
|
||||
|
||||
typedef struct {
|
||||
sm_scan_tree_dir_fn on_directory;
|
||||
sm_scan_tree_image_fn on_image_file;
|
||||
} sm_scan_tree_callbacks_t;
|
||||
|
||||
// Walk a managed scan subtree using the shared scan-depth and path filtering rules.
|
||||
bool sm_scan_tree_walk(const char *scan_root, const char *dir_path,
|
||||
unsigned int depth_from_root,
|
||||
unsigned int remaining_depth,
|
||||
const sm_scan_tree_callbacks_t *callbacks, void *ctx);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,19 @@
|
||||
#ifndef SM_SCANNER_H
|
||||
#define SM_SCANNER_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
// Initialize scanner service resources such as wake pipe and file watches.
|
||||
bool sm_scanner_init(void);
|
||||
// Wake a blocked scanner wait. Safe to call from signal context.
|
||||
void sm_scanner_wake(void);
|
||||
// Return true after scanner-owned USB directory descriptors are closed.
|
||||
bool sm_scanner_usb_watches_suspended(void);
|
||||
// Run the initial full synchronization cycle.
|
||||
bool sm_scanner_run_startup_sync(void);
|
||||
// Run the steady-state scanner wait/scan loop until shutdown is requested.
|
||||
void sm_scanner_run_loop(void);
|
||||
// Release scanner service resources.
|
||||
void sm_scanner_shutdown(void);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,9 @@
|
||||
#ifndef SM_SHELLCORE_FLAGS_H
|
||||
#define SM_SHELLCORE_FLAGS_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
bool sm_shellcore_flags_start(void);
|
||||
void sm_shellcore_flags_stop(void);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,19 @@
|
||||
#ifndef SM_SHELLCORE_HOOKS_H
|
||||
#define SM_SHELLCORE_HOOKS_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
// Install/remove the fail-open SceShellCore lifecycle bridge.
|
||||
bool sm_shellcore_hooks_start(void);
|
||||
void sm_shellcore_hooks_stop(void);
|
||||
|
||||
// Return whether this firmware uses the internal TitleDir bridge.
|
||||
bool sm_shellcore_install_bridge_enabled(void);
|
||||
|
||||
// Invoke the internal TitleDir path through one armed AppInstallAll dispatch.
|
||||
// Returns false when the bridge is unavailable or was not consumed.
|
||||
bool sm_shellcore_install_title_dir(const char *title_id,
|
||||
const char *install_dir,
|
||||
int *result_out);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,28 @@
|
||||
#ifndef SM_SHELLCORE_OFFSETS_H
|
||||
#define SM_SHELLCORE_OFFSETS_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef enum {
|
||||
SM_SHELLCORE_TARGET_LAUNCH_APP = 0,
|
||||
SM_SHELLCORE_TARGET_INSTALL_TITLE_DIR,
|
||||
SM_SHELLCORE_TARGET_INSTALL_ALL,
|
||||
SM_SHELLCORE_TARGET_COUNT
|
||||
} sm_shellcore_target_t;
|
||||
|
||||
typedef struct {
|
||||
uintptr_t offset;
|
||||
uint8_t patch_size;
|
||||
} sm_shellcore_target_offset_t;
|
||||
|
||||
typedef struct {
|
||||
uint16_t firmware;
|
||||
const char *name;
|
||||
sm_shellcore_target_offset_t targets[SM_SHELLCORE_TARGET_COUNT];
|
||||
} sm_shellcore_firmware_offsets_t;
|
||||
|
||||
const sm_shellcore_firmware_offsets_t *
|
||||
sm_shellcore_offsets_for_firmware(uint32_t raw_firmware);
|
||||
const char *sm_shellcore_target_name(sm_shellcore_target_t target);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,28 @@
|
||||
#ifndef SM_SHELLCORE_PROTOCOL_H
|
||||
#define SM_SHELLCORE_PROTOCOL_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "sm_limits.h"
|
||||
|
||||
#define SM_SHELLCORE_SOCKET_PATH "/system_tmp/shadowmount.sock"
|
||||
#define SM_SHELLCORE_PROTOCOL_MAGIC 0x534d5343u
|
||||
#define SM_SHELLCORE_PROTOCOL_VERSION 1u
|
||||
|
||||
typedef enum {
|
||||
SM_SHELLCORE_REQUEST_LAUNCH = 1,
|
||||
SM_SHELLCORE_REQUEST_LAUNCH_FAILED = 2
|
||||
} sm_shellcore_request_op_t;
|
||||
|
||||
typedef struct {
|
||||
uint32_t magic;
|
||||
uint16_t version;
|
||||
uint16_t operation;
|
||||
char title_id[MAX_TITLE_ID];
|
||||
} sm_shellcore_request_t;
|
||||
|
||||
typedef struct {
|
||||
int32_t status;
|
||||
} sm_shellcore_response_t;
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,26 @@
|
||||
#ifndef SM_SHELLCORE_REMOTE_H
|
||||
#define SM_SHELLCORE_REMOTE_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "sm_shellcore_offsets.h"
|
||||
|
||||
typedef struct {
|
||||
pid_t pid;
|
||||
uintptr_t image_base;
|
||||
uintptr_t targets[SM_SHELLCORE_TARGET_COUNT];
|
||||
const sm_shellcore_firmware_offsets_t *offsets;
|
||||
} sm_shellcore_remote_t;
|
||||
|
||||
// Resolve and validate every lifecycle target in a running SceShellCore.
|
||||
bool sm_shellcore_remote_resolve(pid_t pid, sm_shellcore_remote_t *remote_out);
|
||||
// Read or write another process while handling post-8.20 write restrictions.
|
||||
bool sm_remote_process_read(pid_t pid, uintptr_t address, void *buffer,
|
||||
size_t size);
|
||||
bool sm_remote_process_write(pid_t pid, uintptr_t address, const void *buffer,
|
||||
size_t size);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,41 @@
|
||||
#ifndef SM_SHELLCORE_SERVICE_H
|
||||
#define SM_SHELLCORE_SERVICE_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "sm_limits.h"
|
||||
|
||||
// Start/stop the local Unix-socket owner used by the SceShellCore bridge.
|
||||
bool sm_shellcore_service_start(void);
|
||||
void sm_shellcore_service_stop(void);
|
||||
|
||||
// Release transient title/image mounts after launch failure or explicit API use.
|
||||
bool sm_shellcore_release_title_runtime(const char *title_id);
|
||||
// Public API entry points. Return 0 or a positive errno value.
|
||||
int sm_shellcore_mount_title_runtime(const char *title_id);
|
||||
int sm_shellcore_unmount_title_runtime(const char *title_id);
|
||||
// Bind the prepared managed title to the app id stored in LncApplication.
|
||||
void sm_shellcore_service_bind_prepared_app(const char *title_id,
|
||||
uint32_t app_id, pid_t pid);
|
||||
// Publish process exit while ShellCore still owns the title sandbox and copy
|
||||
// the matching owned title to title_id_out.
|
||||
// Returns true when an owned incoming/outgoing title became exit-pending.
|
||||
bool sm_shellcore_service_note_game_exit(pid_t pid,
|
||||
char title_id_out[MAX_TITLE_ID]);
|
||||
// Release exit-pending runtime stacks whose /mnt/sandbox/<TITLE_ID>_* entries
|
||||
// have all disappeared. Returns 0 when no exit-pending runtime remains, EAGAIN
|
||||
// while a sandbox remains, or another positive errno after a release failure.
|
||||
int sm_shellcore_service_release_exited_titles(void);
|
||||
// Return true while ShellCore still exposes a sandbox for the title.
|
||||
bool sm_shellcore_service_title_sandbox_exists(const char *title_id);
|
||||
// Ensure that a managed title has its image/nullfs/backport runtime stack.
|
||||
// Unmanaged stock titles are treated as already ready.
|
||||
bool sm_shellcore_ensure_title_runtime(const char *title_id);
|
||||
// Return true when the named title owns an incoming or outgoing runtime mount.
|
||||
bool sm_shellcore_service_title_is_prepared(const char *title_id);
|
||||
// Return true while ShellCore owns any title mount prepared for launch or exit.
|
||||
bool sm_shellcore_service_has_prepared_mount(void);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,10 @@
|
||||
#ifndef SM_SOCKET_IO_H
|
||||
#define SM_SOCKET_IO_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
bool sm_socket_read_full(int fd, void *buffer, size_t size);
|
||||
bool sm_socket_write_full(int fd, const void *buffer, size_t size);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
#ifndef SM_STABILITY_H
|
||||
#define SM_STABILITY_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
// Check whether a path is old enough since its latest mtime/ctime change.
|
||||
bool is_path_stable_now(const char *path, double *root_diff_out,
|
||||
int *stat_errno_out);
|
||||
// Wait briefly for a path to become stable before giving up.
|
||||
bool wait_for_stability_fast(const char *path, const char *name);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,9 @@
|
||||
#ifndef SM_TIME_H
|
||||
#define SM_TIME_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// Return a monotonic timestamp in microseconds, or 0 on failure.
|
||||
uint64_t monotonic_time_us(void);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,27 @@
|
||||
#ifndef SM_TITLE_STATE_H
|
||||
#define SM_TITLE_STATE_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// Return whether registration was already attempted for a title.
|
||||
bool was_register_attempted(const char *title_id);
|
||||
// Return the number of registration attempts for a title.
|
||||
uint8_t get_register_attempts(const char *title_id);
|
||||
// Record a registration attempt for a title.
|
||||
void mark_register_attempted(const char *title_id);
|
||||
// Clear registration attempt state after success or confirmed user removal.
|
||||
void clear_register_attempts(const char *title_id);
|
||||
// Notify once about duplicate title IDs from different paths.
|
||||
void notify_duplicate_title_once(const char *title_id, const char *path_a,
|
||||
const char *path_b);
|
||||
// Clear the one-shot duplicate notification state for a title.
|
||||
void clear_duplicate_title_notification(const char *title_id);
|
||||
// Return the number of failed install/remount attempts for a title.
|
||||
uint8_t get_failed_mount_attempts(const char *title_id);
|
||||
// Clear failed install/remount attempts for a title.
|
||||
void clear_failed_mount_attempts(const char *title_id);
|
||||
// Increment and return failed install/remount attempts for a title.
|
||||
uint8_t bump_failed_mount_attempts(const char *title_id);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,180 @@
|
||||
#ifndef SM_TYPES_H
|
||||
#define SM_TYPES_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "sm_limits.h"
|
||||
|
||||
typedef struct {
|
||||
// +0x00: 1 -> mount read-only, 0 -> allow write.
|
||||
uint32_t ro;
|
||||
// +0x04: reserved in observed Shell/FSMP callers.
|
||||
uint32_t reserved0;
|
||||
// +0x08: logical budget/domain string, usually "game" or "system".
|
||||
const char *budget_id;
|
||||
// +0x10: reserved in observed Shell/FSMP callers.
|
||||
uint32_t reserved1;
|
||||
// +0x14: bitmask consumed by devpfs mount logic.
|
||||
uint32_t flags;
|
||||
// +0x18: optional "maxpkgszingib" value (GiB), 0 means not set.
|
||||
uint64_t max_pkg_gib;
|
||||
} devpfs_mount_opt_t;
|
||||
|
||||
typedef struct {
|
||||
// Human-readable profile id for logs.
|
||||
const char *name;
|
||||
// Raw option payload mapped to FSMP mount behavior.
|
||||
devpfs_mount_opt_t opt;
|
||||
} devpfs_mount_profile_t;
|
||||
|
||||
typedef struct {
|
||||
// Source object class (observed: 1=file, 2=device-like/special source).
|
||||
uint16_t source_type;
|
||||
// Layer descriptor flags (observed bit0 = no bitmap file path).
|
||||
uint16_t flags;
|
||||
// Must be zero.
|
||||
uint32_t reserved0;
|
||||
// Backing file or device path.
|
||||
const char *path;
|
||||
// Data start offset in backing object (bytes).
|
||||
uint64_t offset;
|
||||
// Data size exposed via this layer (bytes).
|
||||
uint64_t size;
|
||||
// Optional bitmap file path.
|
||||
const char *bitmap_path;
|
||||
// Bitmap offset in bitmap file (bytes).
|
||||
uint64_t bitmap_offset;
|
||||
// Bitmap size (bytes), 0 when bitmap is unused.
|
||||
uint64_t bitmap_size;
|
||||
} lvd_ioctl_layer_v0_t;
|
||||
|
||||
typedef struct {
|
||||
// Protocol version for /dev/lvdctl ioctl payload (0 = V0/base attach).
|
||||
uint32_t io_version;
|
||||
// Input: usually -1 for auto-assign. Output: created lvd unit id.
|
||||
int32_t device_id;
|
||||
// User-visible sector size exported by /dev/lvdN.
|
||||
uint32_t sector_size;
|
||||
// Secondary unit/granularity validated against sector_size.
|
||||
uint32_t secondary_unit;
|
||||
// Normalized attach flags produced from wrapper raw options.
|
||||
uint16_t flags;
|
||||
// LVD image type id (validator accepts 0..0xC).
|
||||
uint16_t image_type;
|
||||
// Number of valid entries in layers_ptr.
|
||||
uint32_t layer_count;
|
||||
// Total exported virtual size (bytes).
|
||||
uint64_t device_size;
|
||||
// Pointer to V0 layer array in user payload.
|
||||
lvd_ioctl_layer_v0_t *layers_ptr;
|
||||
} lvd_ioctl_attach_v0_t;
|
||||
|
||||
typedef struct {
|
||||
// Must be zero.
|
||||
uint32_t reserved0;
|
||||
// Target lvd unit id to detach.
|
||||
int32_t device_id;
|
||||
// Reserved padding required by kernel ABI.
|
||||
uint8_t reserved[0x20];
|
||||
} lvd_ioctl_detach_t;
|
||||
|
||||
struct AppDbTitleList {
|
||||
char(*ids)[MAX_TITLE_ID];
|
||||
int count;
|
||||
int capacity;
|
||||
};
|
||||
|
||||
typedef struct scan_candidate {
|
||||
char path[MAX_PATH];
|
||||
char title_id[MAX_TITLE_ID];
|
||||
char title_name[MAX_TITLE_NAME];
|
||||
char manual_source_path[MAX_PATH];
|
||||
bool installed;
|
||||
bool in_app_db;
|
||||
bool manual;
|
||||
} scan_candidate_t;
|
||||
|
||||
typedef enum {
|
||||
ATTACH_BACKEND_NONE = 0,
|
||||
// /dev/lvdctl -> /dev/lvdN
|
||||
ATTACH_BACKEND_LVD,
|
||||
// /dev/mdctl -> /dev/mdN
|
||||
ATTACH_BACKEND_MD,
|
||||
} attach_backend_t;
|
||||
|
||||
typedef struct {
|
||||
// A detach ioctl was accepted for this specific device instance. Once set,
|
||||
// the same unit number must not receive another detach request because the
|
||||
// kernel may already have reused it for a different application.
|
||||
bool requested;
|
||||
bool node_identity_valid;
|
||||
uint64_t node_device;
|
||||
uint64_t node_inode;
|
||||
uint64_t node_rdev;
|
||||
} attached_unit_detach_state_t;
|
||||
|
||||
typedef struct runtime_config {
|
||||
bool debug_enabled;
|
||||
bool quiet_mode;
|
||||
bool mount_read_only;
|
||||
bool force_mount;
|
||||
bool persistent_image_mounts;
|
||||
bool app_install_all_enabled;
|
||||
bool auto_remove_missing_games;
|
||||
bool auto_remove_games_with_dlc;
|
||||
bool backport_fakelib_enabled;
|
||||
bool global_fakelib_enabled;
|
||||
bool global_fakelib_game_priority;
|
||||
bool update_emulators_enabled;
|
||||
bool auto_update_ampr_enabled;
|
||||
bool kstuff_game_auto_toggle;
|
||||
bool kstuff_crash_detection_enabled;
|
||||
bool legacy_recursive_scan_forced;
|
||||
char api_bind_address[MAX_API_BIND_ADDRESS];
|
||||
uint32_t api_port;
|
||||
char global_fakelib_path[MAX_PATH];
|
||||
char emulators_path[MAX_PATH];
|
||||
char ampr_update_url[MAX_PATH];
|
||||
uint32_t global_fakelib_exclude_title_count;
|
||||
char global_fakelib_exclude_title_ids[MAX_FAKELIB_EXCLUDE_RULES][MAX_TITLE_ID];
|
||||
uint32_t scan_depth;
|
||||
uint32_t scan_interval_us;
|
||||
uint32_t stability_wait_seconds;
|
||||
uint32_t auto_remove_missing_delay_seconds;
|
||||
uint32_t kstuff_pause_delay_image_seconds;
|
||||
uint32_t kstuff_pause_delay_direct_seconds;
|
||||
uint32_t fan_target_temperature_c;
|
||||
int32_t language_id;
|
||||
attach_backend_t exfat_backend;
|
||||
attach_backend_t ufs_backend;
|
||||
bool legacy_mount_ufs;
|
||||
bool legacy_mount_exfat;
|
||||
bool legacy_mount_pfs;
|
||||
bool legacy_mount_pfsc;
|
||||
bool legacy_gddr5_cache_enabled;
|
||||
uint32_t lvd_sector_exfat;
|
||||
uint32_t lvd_sector_ufs;
|
||||
uint32_t lvd_sector_pfs;
|
||||
uint32_t md_sector_exfat;
|
||||
uint32_t md_sector_ufs;
|
||||
} runtime_config_t;
|
||||
|
||||
typedef enum {
|
||||
IMAGE_FS_UNKNOWN = 0,
|
||||
IMAGE_FS_UFS,
|
||||
IMAGE_FS_EXFAT,
|
||||
IMAGE_FS_PFS,
|
||||
IMAGE_FS_PFSC_CONTAINER,
|
||||
} image_fs_type_t;
|
||||
|
||||
typedef struct sm_error {
|
||||
int code;
|
||||
char subsystem[16];
|
||||
char message[256];
|
||||
char path[MAX_PATH];
|
||||
bool valid;
|
||||
bool notified;
|
||||
} sm_error_t;
|
||||
|
||||
#endif
|
||||
+4
-18
@@ -1,6 +1,6 @@
|
||||
#!/bin/sh
|
||||
# For WSL2/Ubuntu/Debian: sudo apt-get install -y exfatprogs exfat-fuse fuse3 rsync
|
||||
# Create an exFAT image from a directory
|
||||
# Create a 64 KiB-cluster exFAT image for the LVD type-5/BFS sdimg profile.
|
||||
# Usage: mkexfat.sh <input_dir> [output_file]
|
||||
|
||||
set -e
|
||||
@@ -28,9 +28,8 @@ fi
|
||||
# - FAT + allocation bitmap estimates from cluster count
|
||||
# - directory/entry metadata estimate
|
||||
# - fixed metadata and runtime headroom
|
||||
CLUSTER_SIZE=32768
|
||||
MKFS_CLUSTER_ARG="32K"
|
||||
LARGE_FILE_THRESHOLD=$((1024 * 1024))
|
||||
CLUSTER_SIZE=65536
|
||||
MKFS_CLUSTER_ARG="64K"
|
||||
META_FIXED=$((32 * 1024 * 1024)) # boot region, upcase, root and misc
|
||||
MIN_SLACK=$((64 * 1024 * 1024)) # minimum copy/runtime safety margin
|
||||
SPARE_MIN=$((64 * 1024 * 1024)) # lower bound for dynamic headroom
|
||||
@@ -42,19 +41,6 @@ DIR_COUNT=$(find "$INPUT_DIR" -type d | wc -l | tr -d ' ')
|
||||
RAW_FILE_BYTES=$(find "$INPUT_DIR" -type f -printf '%s\n' | \
|
||||
awk '{s += $1} END {print s + 0}')
|
||||
|
||||
AVG_FILE_BYTES=0
|
||||
if [ "$FILE_COUNT" -gt 0 ]; then
|
||||
AVG_FILE_BYTES=$((RAW_FILE_BYTES / FILE_COUNT))
|
||||
fi
|
||||
|
||||
# exFAT profile selection (same idea as UFS2 profile):
|
||||
# - large-file sets: 64K cluster
|
||||
# - small/mixed-file sets: 32K cluster
|
||||
if [ "$AVG_FILE_BYTES" -ge "$LARGE_FILE_THRESHOLD" ]; then
|
||||
CLUSTER_SIZE=65536
|
||||
MKFS_CLUSTER_ARG="64K"
|
||||
fi
|
||||
|
||||
DATA_BYTES=$(find "$INPUT_DIR" -type f -printf '%s\n' | \
|
||||
awk -v cls="$CLUSTER_SIZE" '{s += int(($1 + cls - 1) / cls) * cls} END {print s + 0}')
|
||||
DATA_CLUSTERS=$(( (DATA_BYTES + CLUSTER_SIZE - 1) / CLUSTER_SIZE ))
|
||||
@@ -82,7 +68,7 @@ MB=$(( (TOTAL + 1024*1024 - 1) / (1024*1024) ))
|
||||
echo "Input size (raw files): $RAW_FILE_BYTES bytes"
|
||||
echo "Input size (exFAT alloc): $DATA_BYTES bytes"
|
||||
echo "Files: $FILE_COUNT, Dirs: $DIR_COUNT"
|
||||
echo "exFAT profile: -c $MKFS_CLUSTER_ARG (avg file=$AVG_FILE_BYTES bytes)"
|
||||
echo "exFAT LVD/BFS profile: -c $MKFS_CLUSTER_ARG (fixed 64 KiB cluster)"
|
||||
echo "Image size: ${MB}MB"
|
||||
|
||||
truncate -s "${MB}M" "$OUTPUT"
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
#!/bin/sh
|
||||
# macOS exFAT image builder — requires macOS 10.6.5+ (exFAT and rsync built-in)
|
||||
# Usage: mkexfat_macos.sh <input_dir> [output_file]
|
||||
# (c) @drakmor,@betmoar
|
||||
|
||||
set -e
|
||||
|
||||
# ── argument validation ───────────────────────────────────────────────────────
|
||||
|
||||
if [ -z "$1" ]; then
|
||||
echo "Usage: $0 <input_dir> [output_file]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
INPUT_DIR="$1"
|
||||
OUTPUT="${2:-test.exfat}"
|
||||
# newfs_exfat interprets relative paths as device names under /dev/ — resolve to absolute
|
||||
OUTPUT="$(cd "$(dirname "$OUTPUT")" && pwd)/$(basename "$OUTPUT")"
|
||||
|
||||
if [ ! -d "$INPUT_DIR" ]; then
|
||||
echo "Error: input directory not found: $INPUT_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$INPUT_DIR/eboot.bin" ]; then
|
||||
echo "Error: eboot.bin not found in source directory: $INPUT_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ── sizing constants ──────────────────────────────────────────────────────────
|
||||
|
||||
CLUSTER_SIZE=65536
|
||||
META_FIXED=$((32 * 1024 * 1024)) # boot region, upcase, root and misc
|
||||
MIN_SLACK=$((64 * 1024 * 1024)) # minimum copy/runtime safety margin
|
||||
SPARE_MIN=$((64 * 1024 * 1024)) # lower bound for dynamic headroom
|
||||
SPARE_MAX=$((512 * 1024 * 1024)) # upper bound for dynamic headroom
|
||||
ENTRY_META_BYTES=256
|
||||
|
||||
# Single filesystem traversal: collect all file sizes into a temp file.
|
||||
# FILE_COUNT and RAW_FILE_BYTES are derived from it in one awk pass.
|
||||
# DIR_COUNT needs a separate traversal (different -type).
|
||||
SIZES_FILE=$(mktemp)
|
||||
MOUNT_POINT=""
|
||||
LOOP_DEVICE=""
|
||||
|
||||
cleanup() {
|
||||
if [ -n "$MOUNT_POINT" ] && mount | grep -q "on $MOUNT_POINT "; then
|
||||
umount "$MOUNT_POINT" 2>/dev/null || true
|
||||
fi
|
||||
if [ -n "$LOOP_DEVICE" ]; then
|
||||
hdiutil detach "$LOOP_DEVICE" 2>/dev/null || true
|
||||
fi
|
||||
if [ -n "$MOUNT_POINT" ]; then
|
||||
rmdir "$MOUNT_POINT" 2>/dev/null || true
|
||||
fi
|
||||
rm -f "$SIZES_FILE"
|
||||
}
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
find "$INPUT_DIR" -type f -exec stat -f '%z' {} \; > "$SIZES_FILE"
|
||||
|
||||
read -r FILE_COUNT RAW_FILE_BYTES <<EOF
|
||||
$(awk '{c++; s += $1} END {print c+0, s+0}' "$SIZES_FILE")
|
||||
EOF
|
||||
|
||||
DIR_COUNT=$(find "$INPUT_DIR" -type d | wc -l | tr -d ' ')
|
||||
|
||||
# DATA_BYTES re-reads the already-collected sizes from the temp file — no second traversal
|
||||
DATA_BYTES=$(awk -v cls="$CLUSTER_SIZE" \
|
||||
'{s += int(($1 + cls - 1) / cls) * cls} END {print s + 0}' "$SIZES_FILE")
|
||||
rm -f "$SIZES_FILE"
|
||||
|
||||
DATA_CLUSTERS=$(( (DATA_BYTES + CLUSTER_SIZE - 1) / CLUSTER_SIZE ))
|
||||
FAT_BYTES=$((DATA_CLUSTERS * 4))
|
||||
BITMAP_BYTES=$(( (DATA_CLUSTERS + 7) / 8 ))
|
||||
ENTRY_BYTES=$(( (FILE_COUNT + DIR_COUNT) * ENTRY_META_BYTES ))
|
||||
|
||||
BASE_TOTAL=$((DATA_BYTES + FAT_BYTES + BITMAP_BYTES + ENTRY_BYTES + META_FIXED))
|
||||
SPARE_BYTES=$((BASE_TOTAL / 200)) # ~0.5%
|
||||
if [ "$SPARE_BYTES" -lt "$SPARE_MIN" ]; then SPARE_BYTES=$SPARE_MIN; fi
|
||||
if [ "$SPARE_BYTES" -gt "$SPARE_MAX" ]; then SPARE_BYTES=$SPARE_MAX; fi
|
||||
TOTAL=$((BASE_TOTAL + SPARE_BYTES))
|
||||
MIN_TOTAL=$((RAW_FILE_BYTES + MIN_SLACK))
|
||||
if [ "$TOTAL" -lt "$MIN_TOTAL" ]; then TOTAL=$MIN_TOTAL; fi
|
||||
|
||||
# Round up to nearest MB
|
||||
MB=$(( (TOTAL + 1024*1024 - 1) / (1024*1024) ))
|
||||
|
||||
echo "Input size (raw files): $RAW_FILE_BYTES bytes"
|
||||
echo "Input size (exFAT alloc): $DATA_BYTES bytes"
|
||||
echo "Files: $FILE_COUNT, Dirs: $DIR_COUNT"
|
||||
echo "exFAT LVD/BFS profile: -b ${CLUSTER_SIZE} (fixed 64 KiB cluster)"
|
||||
echo "Image size: ${MB}MB"
|
||||
|
||||
# ── create, attach, format, mount ────────────────────────────────────────────
|
||||
|
||||
MOUNT_POINT=$(mktemp -d)
|
||||
|
||||
# mkfile -n creates a sparse file without writing zeros (equivalent to Linux truncate)
|
||||
mkfile -n "${MB}m" "$OUTPUT"
|
||||
|
||||
# Attach as a block device first — newfs_exfat requires real sector geometry via ioctl
|
||||
# and cannot format a raw file directly
|
||||
ATTACH_OUTPUT=$(hdiutil attach \
|
||||
-imagekey diskimage-class=CRawDiskImage \
|
||||
-nomount "$OUTPUT")
|
||||
LOOP_DEVICE=$(printf '%s\n' "$ATTACH_OUTPUT" | awk 'NR==1 {print $1; exit}')
|
||||
if [ -z "$LOOP_DEVICE" ]; then
|
||||
echo "Error: failed to determine attached device: $OUTPUT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# newfs_exfat -b takes bytes-per-cluster directly
|
||||
newfs_exfat -b "$CLUSTER_SIZE" "$LOOP_DEVICE"
|
||||
|
||||
mount -t exfat "$LOOP_DEVICE" "$MOUNT_POINT"
|
||||
|
||||
rsync -r --progress "$INPUT_DIR"/ "$MOUNT_POINT"/
|
||||
|
||||
umount "$MOUNT_POINT"
|
||||
hdiutil detach "$LOOP_DEVICE"
|
||||
LOOP_DEVICE="" # prevent double-detach in trap
|
||||
|
||||
echo "Created $OUTPUT"
|
||||
@@ -23,14 +23,17 @@ if [ ! -f "$INPUT_DIR/eboot.bin" ]; then
|
||||
fi
|
||||
|
||||
# More accurate sizing for UFS2:
|
||||
# - file payload rounded to fragment size (-f)
|
||||
# - file payload rounded to the actual allocation unit (-f)
|
||||
# - minimal per-directory allocation
|
||||
# - inode table estimate
|
||||
# - fixed filesystem metadata headroom
|
||||
BLOCK_SIZE=32768
|
||||
FRAG_SIZE=4096
|
||||
|
||||
BLOCK_SIZE=65536
|
||||
FRAG_SIZE=65536
|
||||
SECTOR_SIZE=4096
|
||||
MINFREE_PERCENT=0
|
||||
BYTES_PER_INODE=262144
|
||||
DEFAULT_BYTES_PER_INODE=$((4 * FRAG_SIZE))
|
||||
MIN_BYTES_PER_INODE=$FRAG_SIZE
|
||||
INODE_SIZE=256
|
||||
INODE_SPARE=2048
|
||||
META_FIXED=$((64 * 1024 * 1024)) # superblocks, cg metadata, allocator slack
|
||||
@@ -43,18 +46,6 @@ DIR_COUNT=$(find "$INPUT_DIR" -type d | wc -l | tr -d ' ')
|
||||
RAW_FILE_BYTES=$(find "$INPUT_DIR" -type f -exec stat -f '%z' {} + | \
|
||||
awk '{s += $1} END {print s + 0}')
|
||||
|
||||
AVG_FILE_BYTES=0
|
||||
if [ "$FILE_COUNT" -gt 0 ]; then
|
||||
AVG_FILE_BYTES=$((RAW_FILE_BYTES / FILE_COUNT))
|
||||
fi
|
||||
|
||||
# UFS profile selection:
|
||||
# - large-file sets: 64K block
|
||||
# - small/mixed-file sets: 32K block (safer/denser default)
|
||||
if [ "$AVG_FILE_BYTES" -ge $((1024 * 1024)) ]; then
|
||||
BLOCK_SIZE=65536
|
||||
fi
|
||||
|
||||
DATA_BYTES=$(find "$INPUT_DIR" -type f -exec stat -f '%z' {} + | \
|
||||
awk -v frag="$FRAG_SIZE" '{s += int(($1 + frag - 1) / frag) * frag} END {print s + 0}')
|
||||
DIR_BYTES=$((DIR_COUNT * FRAG_SIZE))
|
||||
@@ -77,17 +68,33 @@ fi
|
||||
|
||||
# Round up to nearest MB
|
||||
MB=$(( (TOTAL + 1024*1024 - 1) / (1024*1024) ))
|
||||
IMAGE_BYTES=$((MB * 1024 * 1024))
|
||||
|
||||
# Tune inode density for the actual tree size. Keep the documented default
|
||||
# unless the source contains enough files/directories to require denser inodes.
|
||||
BYTES_PER_INODE=$DEFAULT_BYTES_PER_INODE
|
||||
if [ "$INODE_COUNT" -gt 0 ]; then
|
||||
AUTO_BYTES_PER_INODE=$((IMAGE_BYTES / INODE_COUNT))
|
||||
AUTO_BYTES_PER_INODE=$(( (AUTO_BYTES_PER_INODE / SECTOR_SIZE) * SECTOR_SIZE ))
|
||||
if [ "$AUTO_BYTES_PER_INODE" -lt "$MIN_BYTES_PER_INODE" ]; then
|
||||
AUTO_BYTES_PER_INODE=$MIN_BYTES_PER_INODE
|
||||
fi
|
||||
if [ "$AUTO_BYTES_PER_INODE" -lt "$BYTES_PER_INODE" ]; then
|
||||
BYTES_PER_INODE=$AUTO_BYTES_PER_INODE
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Input size (raw files): $RAW_FILE_BYTES bytes"
|
||||
echo "Input size (UFS alloc): $DATA_BYTES bytes"
|
||||
echo "Files: $FILE_COUNT, Dirs: $DIR_COUNT"
|
||||
echo "UFS profile: -b $BLOCK_SIZE -f $FRAG_SIZE -m $MINFREE_PERCENT -i $BYTES_PER_INODE (avg file=$AVG_FILE_BYTES bytes)"
|
||||
echo "Inode target: $INODE_COUNT"
|
||||
echo "UFS profile: -b $BLOCK_SIZE -f $FRAG_SIZE -m $MINFREE_PERCENT -S $SECTOR_SIZE -i $BYTES_PER_INODE"
|
||||
echo "Image size: ${MB}MB"
|
||||
|
||||
truncate -s "${MB}M" "$OUTPUT"
|
||||
|
||||
MD=$(mdconfig -a -t vnode -f "$(realpath "$OUTPUT")")
|
||||
newfs -O 2 -b "$BLOCK_SIZE" -f "$FRAG_SIZE" -m "$MINFREE_PERCENT" -i "$BYTES_PER_INODE" /dev/${MD}
|
||||
newfs -O 2 -b "$BLOCK_SIZE" -f "$FRAG_SIZE" -m "$MINFREE_PERCENT" -S "$SECTOR_SIZE" -i "$BYTES_PER_INODE" /dev/${MD}
|
||||
|
||||
mkdir -p /mnt
|
||||
mount /dev/${MD} /mnt
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
@@ -0,0 +1,3 @@
|
||||
asm(".global \"C49jelxiaVE\"\n"
|
||||
".type \"C49jelxiaVE\" @function\n"
|
||||
"\"C49jelxiaVE\":\n");
|
||||
+535
-3714
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,109 @@
|
||||
/* PEM trust anchors for the certificate chains used by GitHub releases. */
|
||||
static char g_ampr_ca_sectigo_e46_usertrust[] =
|
||||
"-----BEGIN CERTIFICATE-----\n"
|
||||
"MIIDRjCCAsugAwIBAgIQGp6v7G3o4ZtcGTFBto2Q3TAKBggqhkjOPQQDAzCBiDEL\n"
|
||||
"MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl\n"
|
||||
"eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT\n"
|
||||
"JVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMjEwMzIy\n"
|
||||
"MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjBfMQswCQYDVQQGEwJHQjEYMBYGA1UEChMP\n"
|
||||
"U2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1YmxpYyBTZXJ2ZXIg\n"
|
||||
"QXV0aGVudGljYXRpb24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAR2\n"
|
||||
"+pmpbiDt+dd34wc7qNs9Xzjoq1WmVk/WSOrsfy2qw7LFeeyZYX8QeccCWvkEN/U0\n"
|
||||
"NSt3zn8gj1KjAIns1aeibVvjS5KToID1AZTc8GgHHs3u/iVStSBDHBv+6xnOQ6Oj\n"
|
||||
"ggEgMIIBHDAfBgNVHSMEGDAWgBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAdBgNVHQ4E\n"
|
||||
"FgQU0SLaTFnxS18mOKqd1u7rDcP7qWEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB\n"
|
||||
"/wQFMAMBAf8wHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMBEGA1UdIAQK\n"
|
||||
"MAgwBgYEVR0gADBQBgNVHR8ESTBHMEWgQ6BBhj9odHRwOi8vY3JsLnVzZXJ0cnVz\n"
|
||||
"dC5jb20vVVNFUlRydXN0RUNDQ2VydGlmaWNhdGlvbkF1dGhvcml0eS5jcmwwNQYI\n"
|
||||
"KwYBBQUHAQEEKTAnMCUGCCsGAQUFBzABhhlodHRwOi8vb2NzcC51c2VydHJ1c3Qu\n"
|
||||
"Y29tMAoGCCqGSM49BAMDA2kAMGYCMQCMCyBit99vX2ba6xEkDe+YO7vC0twjbkv9\n"
|
||||
"PKpqGGuZ61JZryjFsp+DFpEclCVy4noCMQCwvZDXD/m2Ko1HA5Bkmz7YQOFAiNDD\n"
|
||||
"49IWa2wdT7R3DtODaSXH/BiXv8fwB9su4tU=\n"
|
||||
"-----END CERTIFICATE-----\n";
|
||||
|
||||
static char g_ampr_ca_isrg_root_yr_by_x1[] =
|
||||
"-----BEGIN CERTIFICATE-----\n"
|
||||
"MIIF9DCCA9ygAwIBAgIRAPJLbRf52a18scn+p4eCaZ8wDQYJKoZIhvcNAQELBQAw\n"
|
||||
"TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\n"
|
||||
"cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjYwNTEzMDAwMDAw\n"
|
||||
"WhcNMzIwOTAyMjM1OTU5WjAuMQswCQYDVQQGEwJVUzENMAsGA1UEChMESVNSRzEQ\n"
|
||||
"MA4GA1UEAxMHUm9vdCBZUjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB\n"
|
||||
"ANvGJnN78CTJdWL3+eGfsLN5TrNBJs+VH9hRXqRbwxu9sGNiB0BD1fcOxbSUQCJI\n"
|
||||
"M1xE13Db+5Cw1w0s0EBYsvuIP/6joF0w8cuImbgR1OGgYbSQ4OpzI+DG8SGuTlcE\n"
|
||||
"873OCS+kh3srlo6vl43M5OJg4Aeo1sfHp6kTJDoIiFBNJAY+OKfX/FUvYKuhjT+n\n"
|
||||
"o49lmqmupSBI5PkBQiqrEGtWU5uxU/cQWHGu8jSjFBznZqvbNPLMXMLFxCb3WTfr\n"
|
||||
"JBXXjqvWG+v4bjzxjjeAtOlU7qarRDvNOyAuQYLln904M+faKx8hnLCpJ15ZqaEg\n"
|
||||
"cNlY+9MMWcC5yvL2A2j3l9+2buggZX+dOE91zYmIdawTvSZuVvlbRrAlLxIB6pwM\n"
|
||||
"BjneXCjYQ8+3BCCjssbSNpZU3hTcBDdhfAlEDlYr6pEatnMdmDT5BqnKC92bd0Eh\n"
|
||||
"M1fbLHioLccLCuievT8ZkPhZrq7Mii7gNXAcUEAR8+lzYal+9zTg7C5DALyVOeG/\n"
|
||||
"CqfRAMn1KSHCR0NSA6P8tn/mGRlnCct5rtVCLnVySVpU6H1qGg3DgTOuskf8eahT\n"
|
||||
"MiYbI5ezPJmO5ertalskQ1utp74+eDy92PI4ftHKTbq9IWhH4YZKh3WnJEIt+oQv\n"
|
||||
"lYZbY8tpEroKrFB6PFGzrJIDRyts4HqvuH52RFj2zv/BAgMBAAGjgeswgegwDgYD\n"
|
||||
"VR0PAQH/BAQDAgEGMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQFMAMB\n"
|
||||
"Af8wHQYDVR0OBBYEFN7nW2DQIm1AKH0/DQH+pLVStFGUMB8GA1UdIwQYMBaAFHm0\n"
|
||||
"WeZ7tuXkAXOACIjIGlj26ZtuMDIGCCsGAQUFBwEBBCYwJDAiBggrBgEFBQcwAoYW\n"
|
||||
"aHR0cDovL3gxLmkubGVuY3Iub3JnLzATBgNVHSAEDDAKMAgGBmeBDAECATAnBgNV\n"
|
||||
"HR8EIDAeMBygGqAYhhZodHRwOi8veDEuYy5sZW5jci5vcmcvMA0GCSqGSIb3DQEB\n"
|
||||
"CwUAA4ICAQA8spSI95KKfn2W6GMmDpHBJSPaLbsS3W93cijJCRCYAc1fsJgL1FIL\n"
|
||||
"7C0C9ecPOdcwB2fi0Dk2p94j9iTJCxmt5CFSKLRWwnXT2MMSXexVxqoVB79BdWPx\n"
|
||||
"VXETkVme/qYSAuKVHh5Ps+5BixgmwS1JkjSAc+MfrUbNssVEEnH0aEiAh+rotXAV\n"
|
||||
"JSP/Ye7LJPEwD9DWG72vVWbhAcuOf5OLjz57Ctk7MgQHynZ7+PlHJtajroCaIbtC\n"
|
||||
"r6tcZZaAwUQm+jQyeWdV+2hv9deOYFmKeQyjjcSrN5Nadrw+L9DZJLbA1HqeNvLh\n"
|
||||
"BgqpP0fvJq2N6EtD574N6eMI7uMsJTnji2UDz9el5XLSv9fqJMuDQtYVb2oTNoKp\n"
|
||||
"oUqhxPVC0aq4eG5MESaIdn8b5ZGSSeAJLMHXljEdlNza+ncfkviXk1POLnnFdvx8\n"
|
||||
"/gk6M374WbLWFXw8N141B/Rl/tINGfl1TxOIiqtiMYkL02RSGb1kq34BL9NPP27z\n"
|
||||
"RGMuHGnzS3hFIrRTfKxrzUZ9RzQWzEG3K6fJ3r2nqSltkeytis9DIBoFY9VmVyjL\n"
|
||||
"M71DMi+y1+TRSJVClEMwvA4yL++7q9XZx5r5wBRWB4kQTKH5qyoZnDw7iiuh1lID\n"
|
||||
"yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==\n"
|
||||
"-----END CERTIFICATE-----\n";
|
||||
|
||||
static char g_ampr_ca_usertrust_ecc[] =
|
||||
"-----BEGIN CERTIFICATE-----\n"
|
||||
"MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL\n"
|
||||
"MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl\n"
|
||||
"eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT\n"
|
||||
"JVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMjAx\n"
|
||||
"MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT\n"
|
||||
"Ck5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUg\n"
|
||||
"VVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlm\n"
|
||||
"aWNhdGlvbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqflo\n"
|
||||
"I+d61SRvU8Za2EurxtW20eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinng\n"
|
||||
"o4N+LZfQYcTxmdwlkWOrfzCjtHDix6EznPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0G\n"
|
||||
"A1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNVHQ8BAf8EBAMCAQYwDwYD\n"
|
||||
"VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBBHU6+4WMB\n"
|
||||
"zzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbW\n"
|
||||
"RNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg=\n"
|
||||
"-----END CERTIFICATE-----\n";
|
||||
|
||||
static char g_ampr_ca_isrg_root_x1[] =
|
||||
"-----BEGIN CERTIFICATE-----\n"
|
||||
"MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw\n"
|
||||
"TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\n"
|
||||
"cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4\n"
|
||||
"WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu\n"
|
||||
"ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY\n"
|
||||
"MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc\n"
|
||||
"h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+\n"
|
||||
"0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U\n"
|
||||
"A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW\n"
|
||||
"T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH\n"
|
||||
"B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC\n"
|
||||
"B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv\n"
|
||||
"KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn\n"
|
||||
"OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn\n"
|
||||
"jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw\n"
|
||||
"qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI\n"
|
||||
"rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV\n"
|
||||
"HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq\n"
|
||||
"hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL\n"
|
||||
"ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ\n"
|
||||
"3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK\n"
|
||||
"NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5\n"
|
||||
"ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur\n"
|
||||
"TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC\n"
|
||||
"jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc\n"
|
||||
"oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq\n"
|
||||
"4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA\n"
|
||||
"mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d\n"
|
||||
"emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=\n"
|
||||
"-----END CERTIFICATE-----\n";
|
||||
@@ -0,0 +1,957 @@
|
||||
#include "sm_platform.h"
|
||||
|
||||
#include <pthread.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#include "sm_ampr_updater.h"
|
||||
#include "sm_config_mount.h"
|
||||
#include "sm_game_lifecycle.h"
|
||||
#include "sm_log.h"
|
||||
#include "sm_runtime.h"
|
||||
#include "sm_shellcore_service.h"
|
||||
|
||||
#define AMPR_FILE_NAME "libSceAmpr.sprx"
|
||||
#define AMPR_TEMP_FILE_NAME ".libSceAmpr.sprx.download"
|
||||
#define AMPR_INITIAL_CHECK_DELAY_SECONDS 30u
|
||||
#define AMPR_UPDATE_INTERVAL_SECONDS (4u * 60u * 60u)
|
||||
#define AMPR_BUSY_RETRY_SECONDS (10u * 60u)
|
||||
#define AMPR_NET_POOL_SIZE (32u * 1024u)
|
||||
#define AMPR_PRIVATE_CA_COUNT 4u
|
||||
#define AMPR_SSL_POOL_SIZE \
|
||||
((256u + 4u * AMPR_PRIVATE_CA_COUNT) * 1024u)
|
||||
#define AMPR_HTTP_POOL_SIZE (256u * 1024u)
|
||||
#define AMPR_IO_BUFFER_SIZE (64u * 1024u)
|
||||
#define AMPR_MAX_DOWNLOAD_SIZE (16u * 1024u * 1024u)
|
||||
#define AMPR_HTTP_TIMEOUT_US (30u * 1000u * 1000u)
|
||||
#define AMPR_HTTP_VERSION_1_1 2
|
||||
|
||||
int sceNetInit(void);
|
||||
int sceNetPoolCreate(const char *name, int size, int flags);
|
||||
int sceNetPoolDestroy(int pool_id);
|
||||
int sceSslInit(size_t pool_size);
|
||||
int sceSslTerm(int ssl_id);
|
||||
typedef struct {
|
||||
char *ptr;
|
||||
size_t size;
|
||||
} SceSslData;
|
||||
int sceSslLoadCert(int ssl_id, int ca_count, const SceSslData **ca_list,
|
||||
const SceSslData *cert, const SceSslData *private_key);
|
||||
int sceSslUnloadCert(int ssl_id);
|
||||
int sceHttp2Init(int net_pool_id, int ssl_id, size_t pool_size,
|
||||
int max_concurrent_requests);
|
||||
int sceHttp2Term(int http_id);
|
||||
int sceHttp2CreateTemplate(int http_id, const char *user_agent,
|
||||
int http_version, int auto_proxy_config);
|
||||
int sceHttp2DeleteTemplate(int template_id);
|
||||
int sceHttp2CreateRequestWithURL(int template_id, const char *method,
|
||||
const char *url, uint64_t content_length);
|
||||
int sceHttp2DeleteRequest(int request_id);
|
||||
int sceHttp2SendRequest(int request_id, const void *data, size_t size);
|
||||
int sceHttp2GetStatusCode(int request_id, int *status_code);
|
||||
int sceHttp2GetAllResponseHeaders(int request_id, char **headers,
|
||||
size_t *headers_size);
|
||||
int sceHttp2ReadData(int request_id, void *buffer, size_t size);
|
||||
int sceHttp2AbortRequest(int request_id);
|
||||
int sceHttp2SetAutoRedirect(int id, int enabled);
|
||||
int sceHttp2SetTimeOut(int id, uint32_t timeout_us);
|
||||
int sceHttp2SetResolveTimeOut(int id, uint32_t timeout_us);
|
||||
int sceHttp2SetConnectTimeOut(int id, uint32_t timeout_us);
|
||||
int sceHttp2SetSendTimeOut(int id, uint32_t timeout_us);
|
||||
int sceHttp2SetRecvTimeOut(int id, uint32_t timeout_us);
|
||||
|
||||
#include "sm_ampr_ca.inc"
|
||||
|
||||
typedef struct {
|
||||
bool enabled;
|
||||
uint64_t generation;
|
||||
char url[MAX_PATH];
|
||||
char emulators_path[MAX_PATH];
|
||||
} ampr_update_config_t;
|
||||
|
||||
typedef enum {
|
||||
AMPR_UPDATE_FAILED = 0,
|
||||
AMPR_UPDATE_UNCHANGED,
|
||||
AMPR_UPDATE_REPLACED,
|
||||
AMPR_UPDATE_CANCELLED,
|
||||
} ampr_update_result_t;
|
||||
|
||||
typedef struct {
|
||||
pthread_t thread;
|
||||
pthread_mutex_t mutex;
|
||||
pthread_cond_t cond;
|
||||
bool started;
|
||||
bool stop_requested;
|
||||
bool wake_requested;
|
||||
bool net_initialized;
|
||||
uint64_t config_generation;
|
||||
int active_request_id;
|
||||
} ampr_updater_state_t;
|
||||
|
||||
static ampr_updater_state_t g_ampr_updater = {
|
||||
.mutex = PTHREAD_MUTEX_INITIALIZER,
|
||||
.cond = PTHREAD_COND_INITIALIZER,
|
||||
.active_request_id = -1,
|
||||
};
|
||||
|
||||
static void log_sce_failure(const char *operation, int error) {
|
||||
log_debug(" [AMPR] %s failed: 0x%08x", operation, (unsigned int)error);
|
||||
}
|
||||
|
||||
static bool ampr_update_busy(void) {
|
||||
return runtime_sleep_mode_active() || sm_game_lifecycle_has_active_game() ||
|
||||
sm_shellcore_service_has_prepared_mount();
|
||||
}
|
||||
|
||||
static void snapshot_update_config(ampr_update_config_t *snapshot) {
|
||||
const runtime_config_t *cfg = runtime_config();
|
||||
snapshot->enabled = cfg->auto_update_ampr_enabled;
|
||||
snapshot->generation = g_ampr_updater.config_generation;
|
||||
(void)strlcpy(snapshot->url, cfg->ampr_update_url, sizeof(snapshot->url));
|
||||
(void)strlcpy(snapshot->emulators_path, cfg->emulators_path,
|
||||
sizeof(snapshot->emulators_path));
|
||||
}
|
||||
|
||||
static bool
|
||||
update_snapshot_is_current_locked(const ampr_update_config_t *snapshot) {
|
||||
const runtime_config_t *cfg = runtime_config();
|
||||
return !g_ampr_updater.stop_requested &&
|
||||
snapshot->generation == g_ampr_updater.config_generation &&
|
||||
cfg->auto_update_ampr_enabled &&
|
||||
strcmp(snapshot->url, cfg->ampr_update_url) == 0 &&
|
||||
strcmp(snapshot->emulators_path, cfg->emulators_path) == 0;
|
||||
}
|
||||
|
||||
static bool update_snapshot_is_current(const ampr_update_config_t *snapshot) {
|
||||
pthread_mutex_lock(&g_ampr_updater.mutex);
|
||||
bool current = update_snapshot_is_current_locked(snapshot);
|
||||
pthread_mutex_unlock(&g_ampr_updater.mutex);
|
||||
return current;
|
||||
}
|
||||
|
||||
static bool wait_for_update_event_locked(uint32_t seconds,
|
||||
bool wake_early) {
|
||||
struct timespec deadline;
|
||||
if (clock_gettime(CLOCK_REALTIME, &deadline) != 0) {
|
||||
log_debug(" [AMPR] cannot create update deadline: %s", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
deadline.tv_sec += (time_t)seconds;
|
||||
|
||||
bool wait_failed = false;
|
||||
while (!g_ampr_updater.stop_requested) {
|
||||
if (wake_early && g_ampr_updater.wake_requested)
|
||||
break;
|
||||
g_ampr_updater.wake_requested = false;
|
||||
int rc = pthread_cond_timedwait(&g_ampr_updater.cond,
|
||||
&g_ampr_updater.mutex, &deadline);
|
||||
if (rc == ETIMEDOUT)
|
||||
break;
|
||||
if (rc != 0) {
|
||||
log_debug(" [AMPR] update wait failed: %s", strerror(rc));
|
||||
wait_failed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool keep_running = !g_ampr_updater.stop_requested;
|
||||
g_ampr_updater.wake_requested = false;
|
||||
return keep_running && !wait_failed;
|
||||
}
|
||||
|
||||
static bool publish_active_request(int request_id,
|
||||
const ampr_update_config_t *snapshot) {
|
||||
pthread_mutex_lock(&g_ampr_updater.mutex);
|
||||
bool current = update_snapshot_is_current_locked(snapshot);
|
||||
if (current)
|
||||
g_ampr_updater.active_request_id = request_id;
|
||||
pthread_mutex_unlock(&g_ampr_updater.mutex);
|
||||
return current;
|
||||
}
|
||||
|
||||
static void clear_active_request(int request_id) {
|
||||
pthread_mutex_lock(&g_ampr_updater.mutex);
|
||||
if (g_ampr_updater.active_request_id == request_id)
|
||||
g_ampr_updater.active_request_id = -1;
|
||||
pthread_mutex_unlock(&g_ampr_updater.mutex);
|
||||
}
|
||||
|
||||
static bool ensure_net_initialized(void) {
|
||||
if (g_ampr_updater.net_initialized)
|
||||
return true;
|
||||
|
||||
int rc = sceNetInit();
|
||||
if (rc != 0) {
|
||||
log_sce_failure("sceNetInit", rc);
|
||||
return false;
|
||||
}
|
||||
g_ampr_updater.net_initialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ensure_emulators_directory(const char *path) {
|
||||
struct stat st;
|
||||
if (stat(path, &st) == 0) {
|
||||
if (S_ISDIR(st.st_mode))
|
||||
return true;
|
||||
errno = ENOTDIR;
|
||||
return false;
|
||||
}
|
||||
if (errno != ENOENT)
|
||||
return false;
|
||||
if (mkdir(path, 0777) == 0)
|
||||
return true;
|
||||
if (errno != EEXIST || stat(path, &st) != 0 || !S_ISDIR(st.st_mode))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool load_ampr_ca_certificates(int ssl_id) {
|
||||
SceSslData certificates[AMPR_PRIVATE_CA_COUNT] = {
|
||||
{
|
||||
.ptr = g_ampr_ca_sectigo_e46_usertrust,
|
||||
.size = sizeof(g_ampr_ca_sectigo_e46_usertrust) - 1u,
|
||||
},
|
||||
{
|
||||
.ptr = g_ampr_ca_isrg_root_yr_by_x1,
|
||||
.size = sizeof(g_ampr_ca_isrg_root_yr_by_x1) - 1u,
|
||||
},
|
||||
{
|
||||
.ptr = g_ampr_ca_usertrust_ecc,
|
||||
.size = sizeof(g_ampr_ca_usertrust_ecc) - 1u,
|
||||
},
|
||||
{
|
||||
.ptr = g_ampr_ca_isrg_root_x1,
|
||||
.size = sizeof(g_ampr_ca_isrg_root_x1) - 1u,
|
||||
},
|
||||
};
|
||||
const SceSslData *ca_list[AMPR_PRIVATE_CA_COUNT];
|
||||
for (unsigned i = 0; i < AMPR_PRIVATE_CA_COUNT; ++i)
|
||||
ca_list[i] = &certificates[i];
|
||||
int rc = sceSslLoadCert(ssl_id, (int)AMPR_PRIVATE_CA_COUNT, ca_list, NULL,
|
||||
NULL);
|
||||
if (rc != 0) {
|
||||
log_sce_failure("sceSslLoadCert", rc);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool write_all(int fd, const void *data, size_t size) {
|
||||
const unsigned char *cursor = data;
|
||||
while (size > 0) {
|
||||
ssize_t written = write(fd, cursor, size);
|
||||
if (written < 0 && errno == EINTR)
|
||||
continue;
|
||||
if (written <= 0)
|
||||
return false;
|
||||
cursor += (size_t)written;
|
||||
size -= (size_t)written;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool downloaded_file_is_sprx(const char *path) {
|
||||
static const unsigned char self_magic[4] = {0x4f, 0x15, 0x3d, 0x1d};
|
||||
static const unsigned char elf_magic[4] = {0x7f, 'E', 'L', 'F'};
|
||||
unsigned char magic[sizeof(self_magic)];
|
||||
|
||||
int fd = open(path, O_RDONLY);
|
||||
if (fd < 0)
|
||||
return false;
|
||||
ssize_t length;
|
||||
do {
|
||||
length = read(fd, magic, sizeof(magic));
|
||||
} while (length < 0 && errno == EINTR);
|
||||
(void)close(fd);
|
||||
return length == (ssize_t)sizeof(magic) &&
|
||||
(memcmp(magic, self_magic, sizeof(magic)) == 0 ||
|
||||
memcmp(magic, elf_magic, sizeof(magic)) == 0);
|
||||
}
|
||||
|
||||
static bool find_http_header(const char *headers, size_t headers_size,
|
||||
const char *name, char *value,
|
||||
size_t value_size) {
|
||||
if (!headers || !name || !value || value_size == 0)
|
||||
return false;
|
||||
|
||||
size_t name_size = strlen(name);
|
||||
size_t offset = 0;
|
||||
while (offset < headers_size) {
|
||||
size_t line_end = offset;
|
||||
while (line_end < headers_size && headers[line_end] != '\0' &&
|
||||
headers[line_end] != '\r' && headers[line_end] != '\n') {
|
||||
line_end++;
|
||||
}
|
||||
|
||||
size_t colon = offset;
|
||||
while (colon < line_end && headers[colon] != ':')
|
||||
colon++;
|
||||
if (colon - offset == name_size && colon < line_end &&
|
||||
strncasecmp(headers + offset, name, name_size) == 0) {
|
||||
size_t begin = colon + 1u;
|
||||
while (begin < line_end &&
|
||||
(headers[begin] == ' ' || headers[begin] == '\t')) {
|
||||
begin++;
|
||||
}
|
||||
size_t length = line_end - begin;
|
||||
while (length > 0 &&
|
||||
(headers[begin + length - 1u] == ' ' ||
|
||||
headers[begin + length - 1u] == '\t')) {
|
||||
length--;
|
||||
}
|
||||
if (length == 0 || length >= value_size)
|
||||
return false;
|
||||
memcpy(value, headers + begin, length);
|
||||
value[length] = '\0';
|
||||
return true;
|
||||
}
|
||||
|
||||
if (line_end < headers_size && headers[line_end] == '\0')
|
||||
break;
|
||||
offset = line_end;
|
||||
while (offset < headers_size &&
|
||||
(headers[offset] == '\r' || headers[offset] == '\n')) {
|
||||
offset++;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool is_leap_year(unsigned year) {
|
||||
return year % 4u == 0 && (year % 100u != 0 || year % 400u == 0);
|
||||
}
|
||||
|
||||
static int month_number(const char *name) {
|
||||
static const char *const months[] = {
|
||||
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
||||
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
|
||||
};
|
||||
for (int i = 0; i < 12; ++i) {
|
||||
if (strcasecmp(name, months[i]) == 0)
|
||||
return i + 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int64_t days_from_civil(int year, unsigned month, unsigned day) {
|
||||
year -= month <= 2u;
|
||||
int era = (year >= 0 ? year : year - 399) / 400;
|
||||
unsigned year_of_era = (unsigned)(year - era * 400);
|
||||
unsigned adjusted_month =
|
||||
(unsigned)((int)month + (month > 2u ? -3 : 9));
|
||||
unsigned day_of_year =
|
||||
(153u * adjusted_month + 2u) / 5u + day - 1u;
|
||||
unsigned day_of_era = year_of_era * 365u + year_of_era / 4u -
|
||||
year_of_era / 100u + day_of_year;
|
||||
return (int64_t)era * 146097ll + (int64_t)day_of_era - 719468ll;
|
||||
}
|
||||
|
||||
static bool parse_http_modified_time(const char *value, time_t *time_out) {
|
||||
if (!value || !time_out)
|
||||
return false;
|
||||
|
||||
char weekday[5] = {0};
|
||||
char month_name[4] = {0};
|
||||
char zone[4] = {0};
|
||||
char extra;
|
||||
unsigned day = 0;
|
||||
unsigned year = 0;
|
||||
unsigned hour = 0;
|
||||
unsigned minute = 0;
|
||||
unsigned second = 0;
|
||||
int fields = sscanf(value, "%4s %u %3s %u %u:%u:%u %3s %c", weekday,
|
||||
&day, month_name, &year, &hour, &minute, &second, zone,
|
||||
&extra);
|
||||
int month = month_number(month_name);
|
||||
static const unsigned days_per_month[] = {31u, 28u, 31u, 30u, 31u, 30u,
|
||||
31u, 31u, 30u, 31u, 30u, 31u};
|
||||
if (fields != 8 || strlen(weekday) != 4u || weekday[3] != ',' ||
|
||||
strcmp(zone, "GMT") != 0 || year < 1970u || year > 9999u ||
|
||||
month == 0 || hour > 23u || minute > 59u || second > 59u) {
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned max_day = days_per_month[month - 1];
|
||||
if (month == 2 && is_leap_year(year))
|
||||
max_day++;
|
||||
if (day == 0 || day > max_day)
|
||||
return false;
|
||||
|
||||
int64_t timestamp =
|
||||
days_from_civil((int)year, (unsigned)month, day) * 86400ll +
|
||||
(int64_t)hour * 3600ll + (int64_t)minute * 60ll + (int64_t)second;
|
||||
time_t converted = (time_t)timestamp;
|
||||
if ((int64_t)converted != timestamp)
|
||||
return false;
|
||||
*time_out = converted;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool try_read_remote_modified_time(int request_id, time_t *time_out) {
|
||||
char *headers = NULL;
|
||||
size_t headers_size = 0;
|
||||
int rc =
|
||||
sceHttp2GetAllResponseHeaders(request_id, &headers, &headers_size);
|
||||
if (rc != 0) {
|
||||
log_sce_failure("sceHttp2GetAllResponseHeaders", rc);
|
||||
return false;
|
||||
}
|
||||
|
||||
char value[64];
|
||||
return find_http_header(headers, headers_size, "Last-Modified", value,
|
||||
sizeof(value)) &&
|
||||
parse_http_modified_time(value, time_out);
|
||||
}
|
||||
|
||||
static bool inspect_local_emulator(const char *path, bool *missing_out,
|
||||
time_t *modified_time_out) {
|
||||
*missing_out = false;
|
||||
*modified_time_out = 0;
|
||||
struct stat st;
|
||||
if (lstat(path, &st) != 0) {
|
||||
if (errno == ENOENT) {
|
||||
*missing_out = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (!S_ISREG(st.st_mode)) {
|
||||
errno = EINVAL;
|
||||
return false;
|
||||
}
|
||||
*modified_time_out = st.st_mtime;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool set_file_modified_time(const char *path, time_t modified_time) {
|
||||
struct timeval times[2] = {
|
||||
{.tv_sec = modified_time, .tv_usec = 0},
|
||||
{.tv_sec = modified_time, .tv_usec = 0},
|
||||
};
|
||||
return utimes(path, times) == 0;
|
||||
}
|
||||
|
||||
static bool regular_files_equal(const char *first, const char *second,
|
||||
bool *equal_out) {
|
||||
*equal_out = false;
|
||||
|
||||
struct stat first_st;
|
||||
if (lstat(first, &first_st) != 0) {
|
||||
if (errno == ENOENT)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
if (!S_ISREG(first_st.st_mode)) {
|
||||
errno = EINVAL;
|
||||
return false;
|
||||
}
|
||||
|
||||
struct stat second_st;
|
||||
if (lstat(second, &second_st) != 0)
|
||||
return false;
|
||||
if (!S_ISREG(second_st.st_mode)) {
|
||||
errno = EINVAL;
|
||||
return false;
|
||||
}
|
||||
if (first_st.st_size != second_st.st_size)
|
||||
return true;
|
||||
|
||||
int first_fd = open(first, O_RDONLY);
|
||||
if (first_fd < 0)
|
||||
return false;
|
||||
int second_fd = open(second, O_RDONLY);
|
||||
if (second_fd < 0) {
|
||||
int saved_errno = errno;
|
||||
(void)close(first_fd);
|
||||
errno = saved_errno;
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned char *buffers = malloc(AMPR_IO_BUFFER_SIZE * 2u);
|
||||
if (!buffers) {
|
||||
(void)close(second_fd);
|
||||
(void)close(first_fd);
|
||||
errno = ENOMEM;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool io_ok = true;
|
||||
bool equal = true;
|
||||
while (io_ok && equal) {
|
||||
ssize_t first_size;
|
||||
do {
|
||||
first_size = read(first_fd, buffers, AMPR_IO_BUFFER_SIZE);
|
||||
} while (first_size < 0 && errno == EINTR);
|
||||
if (first_size < 0) {
|
||||
io_ok = false;
|
||||
break;
|
||||
}
|
||||
|
||||
size_t received = 0;
|
||||
while (received < (size_t)first_size) {
|
||||
ssize_t second_size =
|
||||
read(second_fd, buffers + AMPR_IO_BUFFER_SIZE + received,
|
||||
(size_t)first_size - received);
|
||||
if (second_size < 0 && errno == EINTR)
|
||||
continue;
|
||||
if (second_size <= 0) {
|
||||
if (second_size == 0)
|
||||
errno = EIO;
|
||||
io_ok = false;
|
||||
break;
|
||||
}
|
||||
received += (size_t)second_size;
|
||||
}
|
||||
if (!io_ok ||
|
||||
memcmp(buffers, buffers + AMPR_IO_BUFFER_SIZE,
|
||||
(size_t)first_size) != 0) {
|
||||
equal = false;
|
||||
break;
|
||||
}
|
||||
if (first_size == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
int saved_errno = io_ok ? 0 : errno;
|
||||
free(buffers);
|
||||
(void)close(second_fd);
|
||||
(void)close(first_fd);
|
||||
if (!io_ok)
|
||||
errno = saved_errno;
|
||||
*equal_out = equal;
|
||||
return io_ok;
|
||||
}
|
||||
|
||||
static bool configure_http_template(int template_id) {
|
||||
int rc = sceHttp2SetAutoRedirect(template_id, 1);
|
||||
if (rc != 0) {
|
||||
log_sce_failure("sceHttp2SetAutoRedirect", rc);
|
||||
return false;
|
||||
}
|
||||
|
||||
int timeout_error = 0;
|
||||
rc = sceHttp2SetTimeOut(template_id, AMPR_HTTP_TIMEOUT_US);
|
||||
if (rc != 0)
|
||||
timeout_error = rc;
|
||||
rc = sceHttp2SetResolveTimeOut(template_id, AMPR_HTTP_TIMEOUT_US);
|
||||
if (timeout_error == 0 && rc != 0)
|
||||
timeout_error = rc;
|
||||
rc = sceHttp2SetConnectTimeOut(template_id, AMPR_HTTP_TIMEOUT_US);
|
||||
if (timeout_error == 0 && rc != 0)
|
||||
timeout_error = rc;
|
||||
rc = sceHttp2SetSendTimeOut(template_id, AMPR_HTTP_TIMEOUT_US);
|
||||
if (timeout_error == 0 && rc != 0)
|
||||
timeout_error = rc;
|
||||
rc = sceHttp2SetRecvTimeOut(template_id, AMPR_HTTP_TIMEOUT_US);
|
||||
if (timeout_error == 0 && rc != 0)
|
||||
timeout_error = rc;
|
||||
if (timeout_error != 0) {
|
||||
log_sce_failure("HTTP timeout configuration", timeout_error);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool create_and_send_request(
|
||||
int template_id, const char *method, const ampr_update_config_t *snapshot,
|
||||
int *request_id_out, int *status_code_out,
|
||||
ampr_update_result_t *result_out) {
|
||||
int request_id = sceHttp2CreateRequestWithURL(template_id, method,
|
||||
snapshot->url, 0);
|
||||
if (request_id < 0) {
|
||||
log_sce_failure("sceHttp2CreateRequestWithURL", request_id);
|
||||
return false;
|
||||
}
|
||||
*request_id_out = request_id;
|
||||
|
||||
if (!publish_active_request(request_id, snapshot)) {
|
||||
*result_out = AMPR_UPDATE_CANCELLED;
|
||||
return false;
|
||||
}
|
||||
|
||||
int rc = sceHttp2SendRequest(request_id, NULL, 0);
|
||||
if (rc != 0) {
|
||||
if (update_snapshot_is_current(snapshot))
|
||||
log_sce_failure("sceHttp2SendRequest", rc);
|
||||
else
|
||||
*result_out = AMPR_UPDATE_CANCELLED;
|
||||
return false;
|
||||
}
|
||||
|
||||
rc = sceHttp2GetStatusCode(request_id, status_code_out);
|
||||
if (rc != 0) {
|
||||
if (update_snapshot_is_current(snapshot))
|
||||
log_sce_failure("sceHttp2GetStatusCode", rc);
|
||||
else
|
||||
*result_out = AMPR_UPDATE_CANCELLED;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static ampr_update_result_t
|
||||
check_and_update_ampr(const ampr_update_config_t *snapshot) {
|
||||
int net_pool_id = -1;
|
||||
int ssl_id = -1;
|
||||
int http_id = -1;
|
||||
int template_id = -1;
|
||||
int request_id = -1;
|
||||
int output_fd = -1;
|
||||
unsigned char *buffer = NULL;
|
||||
bool temp_exists = false;
|
||||
bool ca_certificates_loaded = false;
|
||||
bool destination_missing = false;
|
||||
time_t local_modified_time = 0;
|
||||
time_t remote_modified_time = 0;
|
||||
ampr_update_result_t result = AMPR_UPDATE_FAILED;
|
||||
char destination[MAX_PATH];
|
||||
char temp_path[MAX_PATH];
|
||||
|
||||
int written = snprintf(destination, sizeof(destination), "%s/%s",
|
||||
snapshot->emulators_path, AMPR_FILE_NAME);
|
||||
int temp_written = snprintf(temp_path, sizeof(temp_path), "%s/%s",
|
||||
snapshot->emulators_path, AMPR_TEMP_FILE_NAME);
|
||||
if (written <= 0 || (size_t)written >= sizeof(destination) ||
|
||||
temp_written <= 0 || (size_t)temp_written >= sizeof(temp_path)) {
|
||||
log_debug(" [AMPR] emulator destination path is too long: %s",
|
||||
snapshot->emulators_path);
|
||||
return AMPR_UPDATE_FAILED;
|
||||
}
|
||||
|
||||
if (!inspect_local_emulator(destination, &destination_missing,
|
||||
&local_modified_time)) {
|
||||
log_debug(" [AMPR] cannot inspect %s: %s", destination,
|
||||
strerror(errno));
|
||||
return AMPR_UPDATE_FAILED;
|
||||
}
|
||||
|
||||
if (!ensure_net_initialized())
|
||||
return AMPR_UPDATE_FAILED;
|
||||
|
||||
net_pool_id = sceNetPoolCreate("sm_ampr", AMPR_NET_POOL_SIZE, 0);
|
||||
if (net_pool_id < 0) {
|
||||
log_sce_failure("sceNetPoolCreate", net_pool_id);
|
||||
goto cleanup;
|
||||
}
|
||||
ssl_id = sceSslInit(AMPR_SSL_POOL_SIZE);
|
||||
if (ssl_id < 0) {
|
||||
log_sce_failure("sceSslInit", ssl_id);
|
||||
goto cleanup;
|
||||
}
|
||||
if (strncasecmp(snapshot->url, "https://", 8u) == 0) {
|
||||
if (!load_ampr_ca_certificates(ssl_id))
|
||||
goto cleanup;
|
||||
ca_certificates_loaded = true;
|
||||
}
|
||||
http_id = sceHttp2Init(net_pool_id, ssl_id, AMPR_HTTP_POOL_SIZE, 1);
|
||||
if (http_id < 0) {
|
||||
log_sce_failure("sceHttp2Init", http_id);
|
||||
goto cleanup;
|
||||
}
|
||||
template_id = sceHttp2CreateTemplate(http_id, "ShadowMountPlus/1.0",
|
||||
AMPR_HTTP_VERSION_1_1, 1);
|
||||
if (template_id < 0) {
|
||||
log_sce_failure("sceHttp2CreateTemplate", template_id);
|
||||
goto cleanup;
|
||||
}
|
||||
if (!configure_http_template(template_id))
|
||||
goto cleanup;
|
||||
|
||||
int status_code = 0;
|
||||
if (!destination_missing) {
|
||||
if (!create_and_send_request(template_id, "HEAD", snapshot, &request_id,
|
||||
&status_code, &result)) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (status_code != 200) {
|
||||
log_debug(" [AMPR] metadata check returned HTTP %d: %s", status_code,
|
||||
snapshot->url);
|
||||
goto cleanup;
|
||||
}
|
||||
if (!try_read_remote_modified_time(request_id, &remote_modified_time)) {
|
||||
log_debug(" [AMPR] response has no valid Last-Modified header");
|
||||
goto cleanup;
|
||||
}
|
||||
if (remote_modified_time <= local_modified_time) {
|
||||
result = AMPR_UPDATE_UNCHANGED;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
clear_active_request(request_id);
|
||||
(void)sceHttp2DeleteRequest(request_id);
|
||||
request_id = -1;
|
||||
} else {
|
||||
log_debug(" [AMPR] emulator is missing; downloading: %s", destination);
|
||||
}
|
||||
|
||||
if (!update_snapshot_is_current(snapshot)) {
|
||||
result = AMPR_UPDATE_CANCELLED;
|
||||
goto cleanup;
|
||||
}
|
||||
if (!ensure_emulators_directory(snapshot->emulators_path)) {
|
||||
log_debug(" [AMPR] cannot use emulator folder %s: %s",
|
||||
snapshot->emulators_path, strerror(errno));
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
status_code = 0;
|
||||
if (!create_and_send_request(template_id, "GET", snapshot, &request_id,
|
||||
&status_code, &result)) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (status_code != 200) {
|
||||
log_debug(" [AMPR] update download returned HTTP %d: %s", status_code,
|
||||
snapshot->url);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (destination_missing &&
|
||||
!try_read_remote_modified_time(request_id, &remote_modified_time)) {
|
||||
remote_modified_time = time(NULL);
|
||||
if (remote_modified_time == (time_t)-1) {
|
||||
log_debug(" [AMPR] cannot determine download modification time");
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
(void)unlink(temp_path);
|
||||
output_fd = open(temp_path, O_WRONLY | O_CREAT | O_EXCL, 0777);
|
||||
if (output_fd < 0) {
|
||||
log_debug(" [AMPR] cannot create temporary download %s: %s", temp_path,
|
||||
strerror(errno));
|
||||
goto cleanup;
|
||||
}
|
||||
temp_exists = true;
|
||||
|
||||
buffer = malloc(AMPR_IO_BUFFER_SIZE);
|
||||
if (!buffer) {
|
||||
log_debug(" [AMPR] download buffer allocation failed");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
size_t total_size = 0;
|
||||
for (;;) {
|
||||
if (!update_snapshot_is_current(snapshot)) {
|
||||
result = AMPR_UPDATE_CANCELLED;
|
||||
goto cleanup;
|
||||
}
|
||||
int received = sceHttp2ReadData(request_id, buffer, AMPR_IO_BUFFER_SIZE);
|
||||
if (received < 0) {
|
||||
if (update_snapshot_is_current(snapshot))
|
||||
log_sce_failure("sceHttp2ReadData", received);
|
||||
else
|
||||
result = AMPR_UPDATE_CANCELLED;
|
||||
goto cleanup;
|
||||
}
|
||||
if (received == 0)
|
||||
break;
|
||||
if ((size_t)received > AMPR_MAX_DOWNLOAD_SIZE - total_size) {
|
||||
log_debug(" [AMPR] update exceeds the %u-byte size limit",
|
||||
(unsigned)AMPR_MAX_DOWNLOAD_SIZE);
|
||||
goto cleanup;
|
||||
}
|
||||
if (!write_all(output_fd, buffer, (size_t)received)) {
|
||||
log_debug(" [AMPR] write failed for %s: %s", temp_path,
|
||||
strerror(errno));
|
||||
goto cleanup;
|
||||
}
|
||||
total_size += (size_t)received;
|
||||
}
|
||||
|
||||
if (total_size < 4u || fsync(output_fd) != 0) {
|
||||
log_debug(" [AMPR] incomplete download for %s: %s", temp_path,
|
||||
total_size < 4u ? "file is empty" : strerror(errno));
|
||||
goto cleanup;
|
||||
}
|
||||
if (close(output_fd) != 0) {
|
||||
output_fd = -1;
|
||||
log_debug(" [AMPR] close failed for %s: %s", temp_path, strerror(errno));
|
||||
goto cleanup;
|
||||
}
|
||||
output_fd = -1;
|
||||
free(buffer);
|
||||
buffer = NULL;
|
||||
|
||||
if (!set_file_modified_time(temp_path, remote_modified_time)) {
|
||||
log_debug(" [AMPR] cannot preserve server modification time for %s: %s",
|
||||
temp_path, strerror(errno));
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (!downloaded_file_is_sprx(temp_path)) {
|
||||
log_debug(" [AMPR] downloaded file has an invalid SELF/ELF header");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
bool unchanged = false;
|
||||
if (!regular_files_equal(destination, temp_path, &unchanged)) {
|
||||
log_debug(" [AMPR] cannot compare %s: %s", destination, strerror(errno));
|
||||
goto cleanup;
|
||||
}
|
||||
if (!update_snapshot_is_current(snapshot) || ampr_update_busy()) {
|
||||
result = AMPR_UPDATE_CANCELLED;
|
||||
goto cleanup;
|
||||
}
|
||||
if (unchanged) {
|
||||
if (!set_file_modified_time(destination, remote_modified_time)) {
|
||||
log_debug(" [AMPR] cannot update modification time for %s: %s",
|
||||
destination, strerror(errno));
|
||||
goto cleanup;
|
||||
}
|
||||
result = AMPR_UPDATE_UNCHANGED;
|
||||
goto cleanup;
|
||||
}
|
||||
if (rename(temp_path, destination) != 0) {
|
||||
log_debug(" [AMPR] cannot replace %s: %s", destination, strerror(errno));
|
||||
goto cleanup;
|
||||
}
|
||||
temp_exists = false;
|
||||
result = AMPR_UPDATE_REPLACED;
|
||||
|
||||
cleanup:
|
||||
free(buffer);
|
||||
if (output_fd >= 0)
|
||||
(void)close(output_fd);
|
||||
if (request_id >= 0) {
|
||||
clear_active_request(request_id);
|
||||
(void)sceHttp2DeleteRequest(request_id);
|
||||
}
|
||||
if (template_id >= 0)
|
||||
(void)sceHttp2DeleteTemplate(template_id);
|
||||
if (http_id >= 0)
|
||||
(void)sceHttp2Term(http_id);
|
||||
if (ca_certificates_loaded)
|
||||
(void)sceSslUnloadCert(ssl_id);
|
||||
if (ssl_id >= 0)
|
||||
(void)sceSslTerm(ssl_id);
|
||||
if (net_pool_id >= 0)
|
||||
(void)sceNetPoolDestroy(net_pool_id);
|
||||
if (temp_exists)
|
||||
(void)unlink(temp_path);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void *ampr_updater_thread_main(void *unused) {
|
||||
(void)unused;
|
||||
|
||||
pthread_mutex_lock(&g_ampr_updater.mutex);
|
||||
g_ampr_updater.wake_requested = false;
|
||||
if (!wait_for_update_event_locked(AMPR_INITIAL_CHECK_DELAY_SECONDS, false)) {
|
||||
pthread_mutex_unlock(&g_ampr_updater.mutex);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
while (!g_ampr_updater.stop_requested) {
|
||||
ampr_update_config_t snapshot;
|
||||
snapshot_update_config(&snapshot);
|
||||
g_ampr_updater.wake_requested = false;
|
||||
|
||||
if (!snapshot.enabled) {
|
||||
while (!g_ampr_updater.stop_requested &&
|
||||
!g_ampr_updater.wake_requested) {
|
||||
int rc =
|
||||
pthread_cond_wait(&g_ampr_updater.cond, &g_ampr_updater.mutex);
|
||||
if (rc != 0) {
|
||||
log_debug(" [AMPR] disabled wait failed: %s", strerror(rc));
|
||||
pthread_mutex_unlock(&g_ampr_updater.mutex);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ampr_update_busy()) {
|
||||
if (!wait_for_update_event_locked(AMPR_BUSY_RETRY_SECONDS, true))
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&g_ampr_updater.mutex);
|
||||
log_debug(" [AMPR] checking emulator modification time: %s",
|
||||
snapshot.url);
|
||||
ampr_update_result_t result = check_and_update_ampr(&snapshot);
|
||||
if (result == AMPR_UPDATE_REPLACED) {
|
||||
log_debug(" [AMPR] emulator updated: %s/%s", snapshot.emulators_path,
|
||||
AMPR_FILE_NAME);
|
||||
notify_system_l10n(SM_L10N_AMPR_EMULATOR_UPDATED);
|
||||
} else if (result == AMPR_UPDATE_UNCHANGED) {
|
||||
log_debug(" [AMPR] emulator is already current");
|
||||
}
|
||||
pthread_mutex_lock(&g_ampr_updater.mutex);
|
||||
|
||||
if (!g_ampr_updater.stop_requested &&
|
||||
!g_ampr_updater.wake_requested &&
|
||||
!wait_for_update_event_locked(AMPR_UPDATE_INTERVAL_SECONDS, true)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&g_ampr_updater.mutex);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool sm_ampr_updater_start(void) {
|
||||
pthread_mutex_lock(&g_ampr_updater.mutex);
|
||||
if (g_ampr_updater.started) {
|
||||
pthread_mutex_unlock(&g_ampr_updater.mutex);
|
||||
return true;
|
||||
}
|
||||
|
||||
g_ampr_updater.stop_requested = false;
|
||||
g_ampr_updater.wake_requested = true;
|
||||
g_ampr_updater.config_generation++;
|
||||
g_ampr_updater.active_request_id = -1;
|
||||
int rc = pthread_create(&g_ampr_updater.thread, NULL,
|
||||
ampr_updater_thread_main, NULL);
|
||||
if (rc != 0) {
|
||||
g_ampr_updater.wake_requested = false;
|
||||
pthread_mutex_unlock(&g_ampr_updater.mutex);
|
||||
errno = rc;
|
||||
return false;
|
||||
}
|
||||
g_ampr_updater.started = true;
|
||||
pthread_mutex_unlock(&g_ampr_updater.mutex);
|
||||
return true;
|
||||
}
|
||||
|
||||
void sm_ampr_updater_stop(void) {
|
||||
pthread_mutex_lock(&g_ampr_updater.mutex);
|
||||
if (!g_ampr_updater.started) {
|
||||
pthread_mutex_unlock(&g_ampr_updater.mutex);
|
||||
return;
|
||||
}
|
||||
g_ampr_updater.stop_requested = true;
|
||||
g_ampr_updater.wake_requested = true;
|
||||
g_ampr_updater.config_generation++;
|
||||
if (g_ampr_updater.active_request_id >= 0)
|
||||
(void)sceHttp2AbortRequest(g_ampr_updater.active_request_id);
|
||||
pthread_cond_broadcast(&g_ampr_updater.cond);
|
||||
pthread_mutex_unlock(&g_ampr_updater.mutex);
|
||||
|
||||
(void)pthread_join(g_ampr_updater.thread, NULL);
|
||||
|
||||
pthread_mutex_lock(&g_ampr_updater.mutex);
|
||||
g_ampr_updater.started = false;
|
||||
g_ampr_updater.stop_requested = false;
|
||||
g_ampr_updater.wake_requested = false;
|
||||
g_ampr_updater.active_request_id = -1;
|
||||
pthread_mutex_unlock(&g_ampr_updater.mutex);
|
||||
}
|
||||
|
||||
void sm_ampr_updater_on_config_reload(const runtime_config_t *old_cfg,
|
||||
const runtime_config_t *new_cfg) {
|
||||
if (!old_cfg || !new_cfg ||
|
||||
(old_cfg->auto_update_ampr_enabled ==
|
||||
new_cfg->auto_update_ampr_enabled &&
|
||||
strcmp(old_cfg->ampr_update_url, new_cfg->ampr_update_url) == 0 &&
|
||||
strcmp(old_cfg->emulators_path, new_cfg->emulators_path) == 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&g_ampr_updater.mutex);
|
||||
g_ampr_updater.config_generation++;
|
||||
g_ampr_updater.wake_requested = true;
|
||||
if (g_ampr_updater.active_request_id >= 0)
|
||||
(void)sceHttp2AbortRequest(g_ampr_updater.active_request_id);
|
||||
pthread_cond_broadcast(&g_ampr_updater.cond);
|
||||
pthread_mutex_unlock(&g_ampr_updater.mutex);
|
||||
}
|
||||
@@ -0,0 +1,934 @@
|
||||
#include "sm_platform.h"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <json-c/json.h>
|
||||
#include <netinet/in.h>
|
||||
#include <pthread.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include "sm_api_protocol.h"
|
||||
#include "sm_api_service.h"
|
||||
#include "sm_config_mount.h"
|
||||
#include "sm_filesystem.h"
|
||||
#include "sm_game_cache.h"
|
||||
#include "sm_gameinfo.h"
|
||||
#include "sm_image.h"
|
||||
#include "sm_image_cache.h"
|
||||
#include "sm_image_index.h"
|
||||
#include "sm_log.h"
|
||||
#include "sm_mount_device.h"
|
||||
#include "sm_path_utils.h"
|
||||
#include "sm_shellcore_service.h"
|
||||
#include "sm_socket_io.h"
|
||||
|
||||
#ifndef SHADOWMOUNT_VERSION
|
||||
#define SHADOWMOUNT_VERSION "unknown"
|
||||
#endif
|
||||
|
||||
#define SM_API_ROUTE_SIZE 128u
|
||||
#define SM_API_RETRY_INITIAL_MS 250u
|
||||
#define SM_API_RETRY_MAX_MS 5000u
|
||||
#define SM_API_CORS_HEADERS \
|
||||
"Access-Control-Allow-Origin: *\r\n" \
|
||||
"Access-Control-Allow-Methods: POST, OPTIONS\r\n" \
|
||||
"Access-Control-Allow-Headers: Content-Type\r\n" \
|
||||
"Access-Control-Allow-Private-Network: true\r\n"
|
||||
|
||||
typedef struct {
|
||||
pthread_t thread;
|
||||
pthread_mutex_t mutex;
|
||||
pthread_cond_t retry_cond;
|
||||
bool started;
|
||||
bool stop_requested;
|
||||
int listen_fd;
|
||||
int client_fd;
|
||||
char bind_address[MAX_API_BIND_ADDRESS];
|
||||
uint16_t port;
|
||||
} sm_api_service_state_t;
|
||||
|
||||
typedef struct {
|
||||
char route[SM_API_ROUTE_SIZE];
|
||||
char *storage;
|
||||
char *body;
|
||||
size_t body_size;
|
||||
bool preflight;
|
||||
} sm_http_request_t;
|
||||
|
||||
static sm_api_service_state_t g_api = {
|
||||
.mutex = PTHREAD_MUTEX_INITIALIZER,
|
||||
.retry_cond = PTHREAD_COND_INITIALIZER,
|
||||
.listen_fd = -1,
|
||||
.client_fd = -1,
|
||||
};
|
||||
|
||||
static int open_http_listener(const char *bind_address, uint16_t port) {
|
||||
int fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (fd < 0)
|
||||
return -1;
|
||||
int reuse = 1;
|
||||
(void)setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
|
||||
|
||||
struct sockaddr_in address;
|
||||
memset(&address, 0, sizeof(address));
|
||||
address.sin_len = sizeof(address);
|
||||
address.sin_family = AF_INET;
|
||||
address.sin_port = htons(port);
|
||||
if (inet_pton(AF_INET, bind_address, &address.sin_addr) != 1) {
|
||||
close(fd);
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
if (bind(fd, (struct sockaddr *)&address, sizeof(address)) != 0 ||
|
||||
listen(fd, 4) != 0) {
|
||||
int saved_errno = errno;
|
||||
close(fd);
|
||||
errno = saved_errno;
|
||||
return -1;
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
static uint32_t next_retry_delay_ms(uint32_t current) {
|
||||
if (current >= SM_API_RETRY_MAX_MS)
|
||||
return SM_API_RETRY_MAX_MS;
|
||||
if (current > SM_API_RETRY_MAX_MS / 2u)
|
||||
return SM_API_RETRY_MAX_MS;
|
||||
return current * 2u;
|
||||
}
|
||||
|
||||
static bool wait_before_listener_retry(uint32_t delay_ms) {
|
||||
struct timespec deadline;
|
||||
if (clock_gettime(CLOCK_REALTIME, &deadline) != 0) {
|
||||
sceKernelUsleep(SM_API_RETRY_INITIAL_MS * 1000u);
|
||||
pthread_mutex_lock(&g_api.mutex);
|
||||
bool keep_running = !g_api.stop_requested;
|
||||
pthread_mutex_unlock(&g_api.mutex);
|
||||
return keep_running;
|
||||
}
|
||||
|
||||
deadline.tv_sec += (time_t)(delay_ms / 1000u);
|
||||
deadline.tv_nsec += (long)(delay_ms % 1000u) * 1000000l;
|
||||
if (deadline.tv_nsec >= 1000000000l) {
|
||||
deadline.tv_sec++;
|
||||
deadline.tv_nsec -= 1000000000l;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&g_api.mutex);
|
||||
int wait_status = 0;
|
||||
while (!g_api.stop_requested) {
|
||||
wait_status =
|
||||
pthread_cond_timedwait(&g_api.retry_cond, &g_api.mutex, &deadline);
|
||||
if (wait_status != 0)
|
||||
break;
|
||||
}
|
||||
bool keep_running = !g_api.stop_requested;
|
||||
pthread_mutex_unlock(&g_api.mutex);
|
||||
|
||||
if (keep_running && wait_status != 0 && wait_status != ETIMEDOUT) {
|
||||
sceKernelUsleep(SM_API_RETRY_INITIAL_MS * 1000u);
|
||||
pthread_mutex_lock(&g_api.mutex);
|
||||
keep_running = !g_api.stop_requested;
|
||||
pthread_mutex_unlock(&g_api.mutex);
|
||||
}
|
||||
return keep_running;
|
||||
}
|
||||
|
||||
static bool accept_error_needs_wait(int error) {
|
||||
return error == EAGAIN || error == EWOULDBLOCK || error == EMFILE ||
|
||||
error == ENFILE || error == ENOBUFS || error == ENOMEM;
|
||||
}
|
||||
|
||||
static const char *http_reason_phrase(int status) {
|
||||
switch (status) {
|
||||
case 200:
|
||||
return "OK";
|
||||
case 400:
|
||||
return "Bad Request";
|
||||
case 404:
|
||||
return "Not Found";
|
||||
case 405:
|
||||
return "Method Not Allowed";
|
||||
case 409:
|
||||
return "Conflict";
|
||||
case 411:
|
||||
return "Length Required";
|
||||
case 413:
|
||||
return "Payload Too Large";
|
||||
case 415:
|
||||
return "Unsupported Media Type";
|
||||
case 431:
|
||||
return "Request Header Fields Too Large";
|
||||
case 500:
|
||||
return "Internal Server Error";
|
||||
case 501:
|
||||
return "Not Implemented";
|
||||
default:
|
||||
return "Error";
|
||||
}
|
||||
}
|
||||
|
||||
static bool send_json_text(int fd, int http_status, const char *body,
|
||||
size_t body_size) {
|
||||
char header[512];
|
||||
int count = snprintf(header, sizeof(header),
|
||||
"HTTP/1.1 %d %s\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
SM_API_CORS_HEADERS
|
||||
"Content-Length: %zu\r\n"
|
||||
"Connection: close\r\n"
|
||||
"Cache-Control: no-store\r\n\r\n",
|
||||
http_status, http_reason_phrase(http_status), body_size);
|
||||
if (count < 0 || (size_t)count >= sizeof(header))
|
||||
return false;
|
||||
return sm_socket_write_full(fd, header, (size_t)count) &&
|
||||
sm_socket_write_full(fd, body, body_size);
|
||||
}
|
||||
|
||||
static void send_preflight_response(int fd) {
|
||||
static const char response[] =
|
||||
"HTTP/1.1 204 No Content\r\n"
|
||||
SM_API_CORS_HEADERS
|
||||
"Access-Control-Max-Age: 86400\r\n"
|
||||
"Content-Length: 0\r\n"
|
||||
"Connection: close\r\n\r\n";
|
||||
(void)sm_socket_write_full(fd, response, sizeof(response) - 1u);
|
||||
}
|
||||
|
||||
static bool send_json_object(int fd, int http_status,
|
||||
struct json_object *response) {
|
||||
const char *body =
|
||||
json_object_to_json_string_ext(response, JSON_C_TO_STRING_PLAIN);
|
||||
if (!body)
|
||||
return false;
|
||||
return send_json_text(fd, http_status, body, strlen(body));
|
||||
}
|
||||
|
||||
static bool add_json_value(struct json_object *object, const char *key,
|
||||
struct json_object *value) {
|
||||
if (!value)
|
||||
return false;
|
||||
if (json_object_object_add_ex(object, key, value,
|
||||
JSON_C_OBJECT_ADD_CONSTANT_KEY) == 0) {
|
||||
return true;
|
||||
}
|
||||
json_object_put(value);
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool add_json_int(struct json_object *object, const char *key,
|
||||
int64_t value) {
|
||||
return add_json_value(object, key, json_object_new_int64(value));
|
||||
}
|
||||
|
||||
static bool add_json_bool(struct json_object *object, const char *key,
|
||||
bool value) {
|
||||
return add_json_value(object, key, json_object_new_boolean(value));
|
||||
}
|
||||
|
||||
static bool add_json_string(struct json_object *object, const char *key,
|
||||
const char *value) {
|
||||
return add_json_value(object, key, json_object_new_string(value));
|
||||
}
|
||||
|
||||
static bool append_json_string(struct json_object *array, const char *value) {
|
||||
struct json_object *item = json_object_new_string(value);
|
||||
if (!item)
|
||||
return false;
|
||||
if (json_object_array_add(array, item) == 0)
|
||||
return true;
|
||||
json_object_put(item);
|
||||
return false;
|
||||
}
|
||||
|
||||
static struct json_object *new_status_response(int status) {
|
||||
struct json_object *response = json_object_new_object();
|
||||
if (response && !add_json_int(response, "status", status)) {
|
||||
json_object_put(response);
|
||||
return NULL;
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
static void send_out_of_memory_response(int fd) {
|
||||
static const char body[] =
|
||||
"{\"status\":12,\"error\":\"out of memory\"}";
|
||||
(void)send_json_text(fd, 500, body, sizeof(body) - 1u);
|
||||
}
|
||||
|
||||
static void send_error_response(int fd, int http_status, int status,
|
||||
const char *message) {
|
||||
struct json_object *response = new_status_response(status);
|
||||
if (!response) {
|
||||
send_out_of_memory_response(fd);
|
||||
return;
|
||||
}
|
||||
if (!add_json_string(response, "error", message)) {
|
||||
json_object_put(response);
|
||||
send_out_of_memory_response(fd);
|
||||
return;
|
||||
}
|
||||
(void)send_json_object(fd, http_status, response);
|
||||
json_object_put(response);
|
||||
}
|
||||
|
||||
static char *find_http_header_end(char *buffer, size_t size) {
|
||||
if (size < 4u)
|
||||
return NULL;
|
||||
for (size_t i = 0; i + 3u < size; ++i) {
|
||||
if (buffer[i] == '\r' && buffer[i + 1u] == '\n' &&
|
||||
buffer[i + 2u] == '\r' && buffer[i + 3u] == '\n') {
|
||||
return &buffer[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static char *trim_http_space(char *value) {
|
||||
while (*value == ' ' || *value == '\t')
|
||||
value++;
|
||||
size_t size = strlen(value);
|
||||
while (size > 0u && (value[size - 1u] == ' ' || value[size - 1u] == '\t'))
|
||||
value[--size] = '\0';
|
||||
return value;
|
||||
}
|
||||
|
||||
static bool is_json_content_type(const char *value) {
|
||||
static const char media_type[] = "application/json";
|
||||
size_t size = sizeof(media_type) - 1u;
|
||||
if (strncasecmp(value, media_type, size) != 0)
|
||||
return false;
|
||||
value += size;
|
||||
while (*value == ' ' || *value == '\t')
|
||||
value++;
|
||||
return *value == '\0' || *value == ';';
|
||||
}
|
||||
|
||||
static bool parse_content_length(const char *value, size_t *size_out) {
|
||||
if (!value || !size_out || value[0] == '\0' || value[0] == '-')
|
||||
return false;
|
||||
errno = 0;
|
||||
char *end = NULL;
|
||||
unsigned long long parsed = strtoull(value, &end, 10);
|
||||
if (errno != 0 || end == value || *end != '\0' || parsed > SIZE_MAX)
|
||||
return false;
|
||||
*size_out = (size_t)parsed;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool reject_http_request(char *buffer, int http_status,
|
||||
const char *error, int *http_status_out,
|
||||
const char **error_out) {
|
||||
free(buffer);
|
||||
*http_status_out = http_status;
|
||||
*error_out = error;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool receive_http_request(int fd, sm_http_request_t *request,
|
||||
int *http_status_out,
|
||||
const char **error_out) {
|
||||
size_t capacity = SM_API_MAX_HTTP_HEADER_SIZE + SM_API_MAX_JSON_BODY_SIZE + 1u;
|
||||
char *buffer = malloc(capacity);
|
||||
if (!buffer)
|
||||
return reject_http_request(NULL, 500, "out of memory", http_status_out,
|
||||
error_out);
|
||||
|
||||
size_t received = 0;
|
||||
char *header_end = NULL;
|
||||
while (!header_end) {
|
||||
if (received >= SM_API_MAX_HTTP_HEADER_SIZE)
|
||||
return reject_http_request(buffer, 431, "HTTP headers are too large",
|
||||
http_status_out, error_out);
|
||||
ssize_t count = recv(fd, buffer + received,
|
||||
SM_API_MAX_HTTP_HEADER_SIZE - received, 0);
|
||||
if (count < 0 && errno == EINTR)
|
||||
continue;
|
||||
if (count <= 0)
|
||||
return reject_http_request(buffer, 400, "incomplete HTTP request",
|
||||
http_status_out, error_out);
|
||||
received += (size_t)count;
|
||||
header_end = find_http_header_end(buffer, received);
|
||||
}
|
||||
|
||||
size_t header_size = (size_t)(header_end - buffer) + 4u;
|
||||
*header_end = '\0';
|
||||
char *line_end = strstr(buffer, "\r\n");
|
||||
if (!line_end)
|
||||
return reject_http_request(buffer, 400, "invalid HTTP request line",
|
||||
http_status_out, error_out);
|
||||
*line_end = '\0';
|
||||
|
||||
char method[8];
|
||||
char version[16];
|
||||
char extra;
|
||||
if (sscanf(buffer, "%7s %127s %15s %c", method, request->route, version,
|
||||
&extra) != 3 ||
|
||||
(strcmp(version, "HTTP/1.1") != 0 && strcmp(version, "HTTP/1.0") != 0))
|
||||
return reject_http_request(buffer, 400, "invalid HTTP request line",
|
||||
http_status_out, error_out);
|
||||
if (strcmp(method, "POST") == 0) {
|
||||
request->preflight = false;
|
||||
} else if (strcmp(method, "OPTIONS") == 0) {
|
||||
request->preflight = true;
|
||||
} else {
|
||||
return reject_http_request(buffer, 405,
|
||||
"only POST and OPTIONS requests are supported",
|
||||
http_status_out, error_out);
|
||||
}
|
||||
|
||||
bool has_content_length = false;
|
||||
bool has_json_content_type = false;
|
||||
bool has_transfer_encoding = false;
|
||||
size_t body_size = 0;
|
||||
char *line = line_end + 2;
|
||||
while (line < header_end && line[0] != '\0') {
|
||||
char *next = strstr(line, "\r\n");
|
||||
if (!next)
|
||||
next = header_end;
|
||||
*next = '\0';
|
||||
char *colon = strchr(line, ':');
|
||||
if (!colon)
|
||||
return reject_http_request(buffer, 400, "invalid HTTP header",
|
||||
http_status_out, error_out);
|
||||
*colon = '\0';
|
||||
char *name = trim_http_space(line);
|
||||
char *value = trim_http_space(colon + 1);
|
||||
if (strcasecmp(name, "Content-Length") == 0) {
|
||||
if (has_content_length || !parse_content_length(value, &body_size))
|
||||
return reject_http_request(buffer, 400,
|
||||
"invalid Content-Length header",
|
||||
http_status_out, error_out);
|
||||
has_content_length = true;
|
||||
} else if (strcasecmp(name, "Content-Type") == 0) {
|
||||
has_json_content_type = is_json_content_type(value);
|
||||
} else if (strcasecmp(name, "Transfer-Encoding") == 0) {
|
||||
has_transfer_encoding = true;
|
||||
}
|
||||
line = next + 2;
|
||||
}
|
||||
|
||||
if (has_transfer_encoding)
|
||||
return reject_http_request(buffer, 501,
|
||||
"Transfer-Encoding is not supported",
|
||||
http_status_out, error_out);
|
||||
if (request->preflight) {
|
||||
request->storage = buffer;
|
||||
return true;
|
||||
}
|
||||
if (!has_content_length)
|
||||
return reject_http_request(buffer, 411, "Content-Length is required",
|
||||
http_status_out, error_out);
|
||||
if (!has_json_content_type)
|
||||
return reject_http_request(buffer, 415,
|
||||
"Content-Type must be application/json",
|
||||
http_status_out, error_out);
|
||||
if (body_size > SM_API_MAX_JSON_BODY_SIZE)
|
||||
return reject_http_request(buffer, 413, "JSON request body is too large",
|
||||
http_status_out, error_out);
|
||||
|
||||
size_t total_size = header_size + body_size;
|
||||
while (received < total_size) {
|
||||
ssize_t count = recv(fd, buffer + received, total_size - received, 0);
|
||||
if (count < 0 && errno == EINTR)
|
||||
continue;
|
||||
if (count <= 0)
|
||||
return reject_http_request(buffer, 400,
|
||||
"incomplete JSON request body",
|
||||
http_status_out, error_out);
|
||||
received += (size_t)count;
|
||||
}
|
||||
buffer[total_size] = '\0';
|
||||
request->storage = buffer;
|
||||
request->body = buffer + header_size;
|
||||
request->body_size = body_size;
|
||||
return true;
|
||||
}
|
||||
|
||||
static struct json_object *parse_request_json(const sm_http_request_t *request) {
|
||||
struct json_tokener *tokener = json_tokener_new_ex(16);
|
||||
if (!tokener)
|
||||
return NULL;
|
||||
json_tokener_set_flags(tokener, JSON_TOKENER_STRICT | JSON_TOKENER_VALIDATE_UTF8);
|
||||
struct json_object *root =
|
||||
json_tokener_parse_ex(tokener, request->body, (int)request->body_size);
|
||||
enum json_tokener_error error = json_tokener_get_error(tokener);
|
||||
size_t parsed_size = json_tokener_get_parse_end(tokener);
|
||||
while (parsed_size < request->body_size &&
|
||||
isspace((unsigned char)request->body[parsed_size])) {
|
||||
parsed_size++;
|
||||
}
|
||||
bool valid = error == json_tokener_success && root != NULL &&
|
||||
parsed_size == request->body_size &&
|
||||
json_object_is_type(root, json_type_object);
|
||||
json_tokener_free(tokener);
|
||||
if (!valid) {
|
||||
if (root)
|
||||
json_object_put(root);
|
||||
return NULL;
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
static const char *get_title_id(struct json_object *request) {
|
||||
struct json_object *value = NULL;
|
||||
if (!json_object_object_get_ex(request, "title_id", &value) ||
|
||||
!json_object_is_type(value, json_type_string)) {
|
||||
return NULL;
|
||||
}
|
||||
const char *title_id = json_object_get_string(value);
|
||||
return is_supported_game_title_id(title_id) ? title_id : NULL;
|
||||
}
|
||||
|
||||
static void handle_version(int fd) {
|
||||
struct json_object *response = new_status_response(0);
|
||||
struct json_object *capabilities = json_object_new_array();
|
||||
if (!response || !capabilities) {
|
||||
if (response)
|
||||
json_object_put(response);
|
||||
if (capabilities)
|
||||
json_object_put(capabilities);
|
||||
send_out_of_memory_response(fd);
|
||||
return;
|
||||
}
|
||||
if (!add_json_int(response, "api_version", SM_API_VERSION) ||
|
||||
!add_json_string(response, "shadowmount_version", SHADOWMOUNT_VERSION) ||
|
||||
!append_json_string(capabilities, "list_images") ||
|
||||
!append_json_string(capabilities, "list_games") ||
|
||||
!append_json_string(capabilities, "mount_game") ||
|
||||
!append_json_string(capabilities, "unmount_game")) {
|
||||
json_object_put(capabilities);
|
||||
json_object_put(response);
|
||||
send_out_of_memory_response(fd);
|
||||
return;
|
||||
}
|
||||
if (!add_json_value(response, "capabilities", capabilities)) {
|
||||
json_object_put(response);
|
||||
send_out_of_memory_response(fd);
|
||||
return;
|
||||
}
|
||||
(void)send_json_object(fd, 200, response);
|
||||
json_object_put(response);
|
||||
}
|
||||
|
||||
static struct json_object *image_to_json(const void *entry) {
|
||||
const sm_image_index_snapshot_entry_t *source = entry;
|
||||
struct json_object *item = json_object_new_object();
|
||||
if (!item)
|
||||
return NULL;
|
||||
|
||||
char mount_point[MAX_PATH];
|
||||
get_image_mount_point_for_source(source->path, mount_point);
|
||||
bool source_available = path_exists(source->path);
|
||||
bool mapped = false;
|
||||
bool mounted = false;
|
||||
int unit_id = -1;
|
||||
attach_backend_t backend = ATTACH_BACKEND_NONE;
|
||||
|
||||
image_cache_entry_t cached;
|
||||
if (find_image_cache_entry(source->path, &cached, NULL)) {
|
||||
mapped = true;
|
||||
(void)strlcpy(mount_point, cached.mount_point, sizeof(mount_point));
|
||||
unit_id = cached.unit_id;
|
||||
backend = cached.backend;
|
||||
mounted = cached.unit_id >= 0 && cached.backend != ATTACH_BACKEND_NONE &&
|
||||
is_active_image_mount_point(cached.mount_point);
|
||||
}
|
||||
|
||||
if (!add_json_string(item, "path", source->path) ||
|
||||
!add_json_string(item, "mount_point", mount_point) ||
|
||||
!add_json_int(item, "size", source->size) ||
|
||||
!add_json_int(item, "mtime_sec", source->mtime_sec) ||
|
||||
!add_json_int(item, "mtime_nsec", source->mtime_nsec) ||
|
||||
!add_json_int(item, "unit_id", unit_id) ||
|
||||
!add_json_string(item, "backend", attach_backend_name(backend)) ||
|
||||
!add_json_bool(item, "complete", source->complete) ||
|
||||
!add_json_bool(item, "source_available", source_available) ||
|
||||
!add_json_bool(item, "mapped", mapped) ||
|
||||
!add_json_bool(item, "mounted", mounted)) {
|
||||
json_object_put(item);
|
||||
return NULL;
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
static struct json_object *game_to_json(const void *entry) {
|
||||
const sm_game_cache_snapshot_entry_t *source = entry;
|
||||
struct json_object *item = json_object_new_object();
|
||||
if (!item)
|
||||
return NULL;
|
||||
|
||||
char managed_path[MAX_PATH];
|
||||
char image_path[MAX_PATH] = {0};
|
||||
bool installed = is_installed(source->title_id);
|
||||
bool mounted = is_data_mounted(source->title_id);
|
||||
bool managed =
|
||||
read_mount_link(source->title_id, managed_path, sizeof(managed_path));
|
||||
bool image_backed = read_mount_image_link(source->title_id, image_path,
|
||||
sizeof(image_path));
|
||||
bool source_available = path_exists(source->path) ||
|
||||
(image_path[0] != '\0' && path_exists(image_path));
|
||||
|
||||
if (!add_json_string(item, "path", source->path) ||
|
||||
!add_json_string(item, "image_path", image_path) ||
|
||||
!add_json_string(item, "title_id", source->title_id) ||
|
||||
!add_json_string(item, "title_name", source->title_name) ||
|
||||
!add_json_bool(item, "installed", installed) ||
|
||||
!add_json_bool(item, "managed", managed) ||
|
||||
!add_json_bool(item, "mounted", mounted) ||
|
||||
!add_json_bool(item, "image_backed", image_backed) ||
|
||||
!add_json_bool(item, "source_available", source_available)) {
|
||||
json_object_put(item);
|
||||
return NULL;
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
typedef struct json_object *(*snapshot_to_json_fn)(const void *entry);
|
||||
|
||||
static void send_snapshot_response(int fd, void *snapshot, size_t count,
|
||||
size_t entry_size, const char *array_key,
|
||||
snapshot_to_json_fn to_json) {
|
||||
struct json_object *response = new_status_response(0);
|
||||
struct json_object *items = json_object_new_array_ext((int)count);
|
||||
if (!response || !items) {
|
||||
if (response)
|
||||
json_object_put(response);
|
||||
if (items)
|
||||
json_object_put(items);
|
||||
free(snapshot);
|
||||
send_out_of_memory_response(fd);
|
||||
return;
|
||||
}
|
||||
|
||||
const unsigned char *entries = snapshot;
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
struct json_object *item = to_json(entries + i * entry_size);
|
||||
if (!item || json_object_array_add(items, item) != 0) {
|
||||
if (item)
|
||||
json_object_put(item);
|
||||
json_object_put(items);
|
||||
json_object_put(response);
|
||||
free(snapshot);
|
||||
send_out_of_memory_response(fd);
|
||||
return;
|
||||
}
|
||||
}
|
||||
free(snapshot);
|
||||
|
||||
if (!add_json_int(response, "count", (int64_t)count)) {
|
||||
json_object_put(items);
|
||||
json_object_put(response);
|
||||
send_out_of_memory_response(fd);
|
||||
return;
|
||||
}
|
||||
if (!add_json_value(response, array_key, items)) {
|
||||
json_object_put(response);
|
||||
send_out_of_memory_response(fd);
|
||||
return;
|
||||
}
|
||||
(void)send_json_object(fd, 200, response);
|
||||
json_object_put(response);
|
||||
}
|
||||
|
||||
static void handle_images(int fd) {
|
||||
sm_image_index_snapshot_entry_t *snapshot = NULL;
|
||||
size_t count = 0;
|
||||
if (!sm_image_index_snapshot(&snapshot, &count)) {
|
||||
send_out_of_memory_response(fd);
|
||||
return;
|
||||
}
|
||||
send_snapshot_response(fd, snapshot, count, sizeof(*snapshot), "images",
|
||||
image_to_json);
|
||||
}
|
||||
|
||||
static void handle_games(int fd) {
|
||||
sm_game_cache_snapshot_entry_t *snapshot = NULL;
|
||||
size_t count = 0;
|
||||
if (!sm_game_cache_snapshot(&snapshot, &count)) {
|
||||
send_out_of_memory_response(fd);
|
||||
return;
|
||||
}
|
||||
send_snapshot_response(fd, snapshot, count, sizeof(*snapshot), "games",
|
||||
game_to_json);
|
||||
}
|
||||
|
||||
static int operation_http_status(int status) {
|
||||
switch (status) {
|
||||
case 0:
|
||||
return 200;
|
||||
case EINVAL:
|
||||
return 400;
|
||||
case ENOENT:
|
||||
return 404;
|
||||
case EBUSY:
|
||||
return 409;
|
||||
case ENOTSUP:
|
||||
return 501;
|
||||
default:
|
||||
return 500;
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_mount_operation(int fd, struct json_object *request,
|
||||
bool mount) {
|
||||
const char *title_id = get_title_id(request);
|
||||
if (!title_id) {
|
||||
send_error_response(fd, 400, EINVAL,
|
||||
"title_id must be a valid PS4 or PS5 title ID");
|
||||
return;
|
||||
}
|
||||
|
||||
int status = mount ? sm_shellcore_mount_title_runtime(title_id)
|
||||
: sm_shellcore_unmount_title_runtime(title_id);
|
||||
log_debug(" [API] HTTP %s game: title=%s status=%d",
|
||||
mount ? "mount" : "unmount", title_id, status);
|
||||
if (status != 0) {
|
||||
send_error_response(fd, operation_http_status(status), status,
|
||||
strerror(status));
|
||||
return;
|
||||
}
|
||||
|
||||
struct json_object *response = new_status_response(0);
|
||||
if (!response) {
|
||||
send_out_of_memory_response(fd);
|
||||
return;
|
||||
}
|
||||
if (!add_json_string(response, "title_id", title_id) ||
|
||||
!add_json_bool(response, "mounted", mount)) {
|
||||
json_object_put(response);
|
||||
send_out_of_memory_response(fd);
|
||||
return;
|
||||
}
|
||||
(void)send_json_object(fd, 200, response);
|
||||
json_object_put(response);
|
||||
}
|
||||
|
||||
static void handle_client(int fd) {
|
||||
sm_http_request_t request;
|
||||
memset(&request, 0, sizeof(request));
|
||||
int http_status = 400;
|
||||
const char *error = "invalid HTTP request";
|
||||
if (!receive_http_request(fd, &request, &http_status, &error)) {
|
||||
send_error_response(fd, http_status,
|
||||
http_status == 500 ? ENOMEM : EPROTO, error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (request.preflight) {
|
||||
send_preflight_response(fd);
|
||||
free(request.storage);
|
||||
return;
|
||||
}
|
||||
|
||||
struct json_object *json = parse_request_json(&request);
|
||||
if (!json) {
|
||||
send_error_response(fd, 400, EINVAL,
|
||||
"request body must be one valid JSON object");
|
||||
free(request.storage);
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(request.route, SM_API_ROUTE_VERSION) == 0) {
|
||||
handle_version(fd);
|
||||
} else if (strcmp(request.route, SM_API_ROUTE_IMAGES) == 0) {
|
||||
handle_images(fd);
|
||||
} else if (strcmp(request.route, SM_API_ROUTE_GAMES) == 0) {
|
||||
handle_games(fd);
|
||||
} else if (strcmp(request.route, SM_API_ROUTE_MOUNT) == 0) {
|
||||
handle_mount_operation(fd, json, true);
|
||||
} else if (strcmp(request.route, SM_API_ROUTE_UNMOUNT) == 0) {
|
||||
handle_mount_operation(fd, json, false);
|
||||
} else {
|
||||
send_error_response(fd, 404, ENOENT, "unknown API route");
|
||||
}
|
||||
|
||||
json_object_put(json);
|
||||
free(request.storage);
|
||||
}
|
||||
|
||||
static void *service_thread_main(void *arg) {
|
||||
(void)arg;
|
||||
pthread_mutex_lock(&g_api.mutex);
|
||||
char bind_address[MAX_API_BIND_ADDRESS];
|
||||
(void)strlcpy(bind_address, g_api.bind_address, sizeof(bind_address));
|
||||
uint16_t port = g_api.port;
|
||||
pthread_mutex_unlock(&g_api.mutex);
|
||||
|
||||
int listen_fd = -1;
|
||||
uint32_t retry_delay_ms = SM_API_RETRY_INITIAL_MS;
|
||||
bool outage_logged = false;
|
||||
while (true) {
|
||||
if (listen_fd < 0) {
|
||||
listen_fd = open_http_listener(bind_address, port);
|
||||
if (listen_fd < 0) {
|
||||
int open_error = errno;
|
||||
if (!outage_logged) {
|
||||
log_debug(" [API] HTTP listener unavailable at %s:%u: %s; "
|
||||
"recovery scheduled",
|
||||
bind_address, (unsigned)port, strerror(open_error));
|
||||
outage_logged = true;
|
||||
}
|
||||
if (!wait_before_listener_retry(retry_delay_ms))
|
||||
break;
|
||||
retry_delay_ms = next_retry_delay_ms(retry_delay_ms);
|
||||
continue;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&g_api.mutex);
|
||||
if (g_api.stop_requested) {
|
||||
pthread_mutex_unlock(&g_api.mutex);
|
||||
close(listen_fd);
|
||||
break;
|
||||
}
|
||||
g_api.listen_fd = listen_fd;
|
||||
pthread_mutex_unlock(&g_api.mutex);
|
||||
|
||||
if (outage_logged) {
|
||||
log_debug(" [API] HTTP listener recovered: http://%s:%u/api/v1",
|
||||
bind_address, (unsigned)port);
|
||||
} else {
|
||||
log_debug(" [API] HTTP/JSON ready: http://%s:%u/api/v1 (v%u)",
|
||||
bind_address, (unsigned)port, SM_API_VERSION);
|
||||
}
|
||||
outage_logged = false;
|
||||
retry_delay_ms = SM_API_RETRY_INITIAL_MS;
|
||||
}
|
||||
|
||||
int client = accept(listen_fd, NULL, NULL);
|
||||
if (client < 0) {
|
||||
int accept_error = errno;
|
||||
if (accept_error == EINTR || accept_error == ECONNABORTED ||
|
||||
accept_error == EPROTO) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&g_api.mutex);
|
||||
if (g_api.stop_requested) {
|
||||
pthread_mutex_unlock(&g_api.mutex);
|
||||
break;
|
||||
}
|
||||
|
||||
if (accept_error_needs_wait(accept_error)) {
|
||||
pthread_mutex_unlock(&g_api.mutex);
|
||||
if (!outage_logged) {
|
||||
log_debug(" [API] HTTP accept paused: %s; recovery scheduled",
|
||||
strerror(accept_error));
|
||||
outage_logged = true;
|
||||
}
|
||||
if (!wait_before_listener_retry(retry_delay_ms))
|
||||
break;
|
||||
retry_delay_ms = next_retry_delay_ms(retry_delay_ms);
|
||||
continue;
|
||||
}
|
||||
|
||||
bool owns_listener = g_api.listen_fd == listen_fd;
|
||||
if (owns_listener)
|
||||
g_api.listen_fd = -1;
|
||||
pthread_mutex_unlock(&g_api.mutex);
|
||||
if (owns_listener)
|
||||
close(listen_fd);
|
||||
listen_fd = -1;
|
||||
if (!outage_logged) {
|
||||
log_debug(" [API] HTTP listener lost: %s; recovery scheduled",
|
||||
strerror(accept_error));
|
||||
outage_logged = true;
|
||||
}
|
||||
if (!wait_before_listener_retry(retry_delay_ms))
|
||||
break;
|
||||
retry_delay_ms = next_retry_delay_ms(retry_delay_ms);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (outage_logged) {
|
||||
log_debug(" [API] HTTP accept recovered");
|
||||
outage_logged = false;
|
||||
}
|
||||
retry_delay_ms = SM_API_RETRY_INITIAL_MS;
|
||||
|
||||
struct timeval timeout = {.tv_sec = 5, .tv_usec = 0};
|
||||
(void)setsockopt(client, SOL_SOCKET, SO_RCVTIMEO, &timeout,
|
||||
sizeof(timeout));
|
||||
(void)setsockopt(client, SOL_SOCKET, SO_SNDTIMEO, &timeout,
|
||||
sizeof(timeout));
|
||||
|
||||
pthread_mutex_lock(&g_api.mutex);
|
||||
if (g_api.stop_requested) {
|
||||
pthread_mutex_unlock(&g_api.mutex);
|
||||
close(client);
|
||||
break;
|
||||
}
|
||||
g_api.client_fd = client;
|
||||
pthread_mutex_unlock(&g_api.mutex);
|
||||
|
||||
handle_client(client);
|
||||
|
||||
pthread_mutex_lock(&g_api.mutex);
|
||||
if (g_api.client_fd == client)
|
||||
g_api.client_fd = -1;
|
||||
pthread_mutex_unlock(&g_api.mutex);
|
||||
close(client);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool sm_api_service_start(void) {
|
||||
const runtime_config_t *cfg = runtime_config();
|
||||
char bind_address[MAX_API_BIND_ADDRESS];
|
||||
(void)strlcpy(bind_address, cfg->api_bind_address, sizeof(bind_address));
|
||||
uint16_t port = (uint16_t)cfg->api_port;
|
||||
|
||||
pthread_mutex_lock(&g_api.mutex);
|
||||
if (g_api.started) {
|
||||
pthread_mutex_unlock(&g_api.mutex);
|
||||
return true;
|
||||
}
|
||||
g_api.listen_fd = -1;
|
||||
g_api.client_fd = -1;
|
||||
g_api.stop_requested = false;
|
||||
(void)strlcpy(g_api.bind_address, bind_address,
|
||||
sizeof(g_api.bind_address));
|
||||
g_api.port = port;
|
||||
int rc = pthread_create(&g_api.thread, NULL, service_thread_main, NULL);
|
||||
if (rc != 0) {
|
||||
g_api.listen_fd = -1;
|
||||
pthread_mutex_unlock(&g_api.mutex);
|
||||
errno = rc;
|
||||
return false;
|
||||
}
|
||||
g_api.started = true;
|
||||
pthread_mutex_unlock(&g_api.mutex);
|
||||
return true;
|
||||
}
|
||||
|
||||
void sm_api_service_stop(void) {
|
||||
pthread_mutex_lock(&g_api.mutex);
|
||||
if (!g_api.started) {
|
||||
pthread_mutex_unlock(&g_api.mutex);
|
||||
return;
|
||||
}
|
||||
g_api.stop_requested = true;
|
||||
int fd = g_api.listen_fd;
|
||||
int client_fd = g_api.client_fd;
|
||||
g_api.listen_fd = -1;
|
||||
pthread_cond_broadcast(&g_api.retry_cond);
|
||||
pthread_mutex_unlock(&g_api.mutex);
|
||||
|
||||
if (client_fd >= 0)
|
||||
(void)shutdown(client_fd, SHUT_RDWR);
|
||||
if (fd >= 0) {
|
||||
(void)shutdown(fd, SHUT_RDWR);
|
||||
close(fd);
|
||||
}
|
||||
pthread_join(g_api.thread, NULL);
|
||||
|
||||
pthread_mutex_lock(&g_api.mutex);
|
||||
g_api.started = false;
|
||||
g_api.stop_requested = false;
|
||||
g_api.client_fd = -1;
|
||||
pthread_mutex_unlock(&g_api.mutex);
|
||||
}
|
||||
|
||||
bool sm_api_service_reconfigure(void) {
|
||||
sm_api_service_stop();
|
||||
return sm_api_service_start();
|
||||
}
|
||||
+573
@@ -0,0 +1,573 @@
|
||||
#include "sm_platform.h"
|
||||
|
||||
#include <pthread.h>
|
||||
#include <sqlite3.h>
|
||||
|
||||
#include "sm_runtime.h"
|
||||
#include "sm_types.h"
|
||||
#include "sm_appdb.h"
|
||||
#include "sm_limits.h"
|
||||
#include "sm_log.h"
|
||||
#include "sm_paths.h"
|
||||
|
||||
static sqlite3 *g_app_db;
|
||||
static sqlite3_stmt *g_app_db_stmt_update_snd0;
|
||||
static sqlite3_stmt *g_app_db_stmt_normalize_snd0;
|
||||
static struct AppDbTitleList g_app_db_title_cache;
|
||||
static struct AppDbTitleList g_app_db_auto_remove_cache;
|
||||
static struct AppDbTitleList g_app_db_blocked_uninstall_ppsa_cache;
|
||||
static bool g_app_db_title_cache_ready = false;
|
||||
static bool g_app_db_auto_remove_cache_ready = false;
|
||||
static bool g_app_db_blocked_uninstall_ppsa_cache_ready = false;
|
||||
static time_t g_app_db_title_cache_mtime = 0;
|
||||
static time_t g_app_db_auto_remove_cache_mtime = 0;
|
||||
static time_t g_app_db_blocked_uninstall_ppsa_cache_mtime = 0;
|
||||
static pthread_mutex_t g_app_db_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
#define APP_DB_STARTUP_MAINTENANCE_RETRIES 3
|
||||
#define APP_DB_STARTUP_MAINTENANCE_BUSY_TIMEOUT_MS 250
|
||||
|
||||
static void close_app_db(void) {
|
||||
if (g_app_db_stmt_update_snd0) {
|
||||
sqlite3_finalize(g_app_db_stmt_update_snd0);
|
||||
g_app_db_stmt_update_snd0 = NULL;
|
||||
}
|
||||
if (g_app_db_stmt_normalize_snd0) {
|
||||
sqlite3_finalize(g_app_db_stmt_normalize_snd0);
|
||||
g_app_db_stmt_normalize_snd0 = NULL;
|
||||
}
|
||||
if (g_app_db) {
|
||||
sqlite3_close(g_app_db);
|
||||
g_app_db = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void free_app_db_title_list(struct AppDbTitleList *list) {
|
||||
free(list->ids);
|
||||
list->ids = NULL;
|
||||
list->count = 0;
|
||||
list->capacity = 0;
|
||||
}
|
||||
|
||||
void shutdown_app_db(void) {
|
||||
pthread_mutex_lock(&g_app_db_mutex);
|
||||
free_app_db_title_list(&g_app_db_title_cache);
|
||||
free_app_db_title_list(&g_app_db_auto_remove_cache);
|
||||
free_app_db_title_list(&g_app_db_blocked_uninstall_ppsa_cache);
|
||||
g_app_db_title_cache_ready = false;
|
||||
g_app_db_auto_remove_cache_ready = false;
|
||||
g_app_db_blocked_uninstall_ppsa_cache_ready = false;
|
||||
g_app_db_title_cache_mtime = 0;
|
||||
g_app_db_auto_remove_cache_mtime = 0;
|
||||
g_app_db_blocked_uninstall_ppsa_cache_mtime = 0;
|
||||
close_app_db();
|
||||
pthread_mutex_unlock(&g_app_db_mutex);
|
||||
}
|
||||
|
||||
static bool ensure_app_db_open(void) {
|
||||
if (!g_app_db) {
|
||||
if (sqlite3_open_v2(APP_DB_PATH, &g_app_db, SQLITE_OPEN_READWRITE, NULL) !=
|
||||
SQLITE_OK) {
|
||||
log_debug(" [DB] open failed: %s",
|
||||
(g_app_db ? sqlite3_errmsg(g_app_db) : APP_DB_PATH));
|
||||
close_app_db();
|
||||
return false;
|
||||
}
|
||||
(void)sqlite3_busy_timeout(g_app_db, APP_DB_BUSY_TIMEOUT_MS);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool app_db_wait_retry(int rc, int attempt, int max_attempts,
|
||||
bool close_before_retry) {
|
||||
if (rc != SQLITE_BUSY && rc != SQLITE_LOCKED)
|
||||
return false;
|
||||
if (attempt + 1 >= max_attempts || should_stop_requested())
|
||||
return false;
|
||||
if (close_before_retry)
|
||||
close_app_db();
|
||||
sceKernelUsleep(APP_DB_BUSY_RETRY_SLEEP_US);
|
||||
return true;
|
||||
}
|
||||
|
||||
static int app_db_prepare_with_retry(const char *sql, sqlite3_stmt **stmt_out,
|
||||
int max_attempts, const char *label) {
|
||||
for (int attempt = 0; attempt < max_attempts; attempt++) {
|
||||
if (!ensure_app_db_open())
|
||||
return -1;
|
||||
|
||||
int rc = sqlite3_prepare_v2(g_app_db, sql, -1, stmt_out, NULL);
|
||||
if (rc == SQLITE_OK)
|
||||
return SQLITE_OK;
|
||||
|
||||
if (app_db_wait_retry(rc, attempt, max_attempts, true))
|
||||
continue;
|
||||
|
||||
log_debug(" [DB] prepare failed for %s: rc=%d err=%s", label, rc,
|
||||
(g_app_db ? sqlite3_errmsg(g_app_db) : "unknown"));
|
||||
close_app_db();
|
||||
return rc;
|
||||
}
|
||||
|
||||
close_app_db();
|
||||
return -1;
|
||||
}
|
||||
|
||||
static bool app_db_exec_with_retry(const char *sql, int max_attempts,
|
||||
const char *label, int *changes_out) {
|
||||
if (changes_out)
|
||||
*changes_out = 0;
|
||||
|
||||
for (int attempt = 0; attempt < max_attempts; attempt++) {
|
||||
if (!ensure_app_db_open())
|
||||
return false;
|
||||
|
||||
char *err_msg = NULL;
|
||||
int rc = sqlite3_exec(g_app_db, sql, NULL, NULL, &err_msg);
|
||||
if (rc == SQLITE_OK) {
|
||||
if (changes_out)
|
||||
*changes_out = sqlite3_changes(g_app_db);
|
||||
sqlite3_free(err_msg);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (app_db_wait_retry(rc, attempt, max_attempts, false)) {
|
||||
sqlite3_free(err_msg);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (err_msg) {
|
||||
log_debug(" [DB] exec failed for %s: rc=%d err=%s", label, rc, err_msg);
|
||||
} else {
|
||||
log_debug(" [DB] exec failed for %s: rc=%d err=%s", label, rc,
|
||||
sqlite3_errmsg(g_app_db));
|
||||
}
|
||||
sqlite3_free(err_msg);
|
||||
|
||||
close_app_db();
|
||||
return false;
|
||||
}
|
||||
|
||||
close_app_db();
|
||||
return false;
|
||||
}
|
||||
|
||||
static int app_db_query_int(const char *sql, const char *label) {
|
||||
sqlite3_stmt *stmt = NULL;
|
||||
int prep_rc = app_db_prepare_with_retry(sql, &stmt,
|
||||
APP_DB_STARTUP_MAINTENANCE_RETRIES,
|
||||
label);
|
||||
if (prep_rc != SQLITE_OK)
|
||||
return -1;
|
||||
|
||||
int result = -1;
|
||||
for (int attempt = 0; attempt < APP_DB_QUERY_BUSY_RETRIES; attempt++) {
|
||||
int rc = sqlite3_step(stmt);
|
||||
if (rc == SQLITE_ROW) {
|
||||
result = sqlite3_column_int(stmt, 0);
|
||||
break;
|
||||
}
|
||||
if (app_db_wait_retry(rc, attempt, APP_DB_QUERY_BUSY_RETRIES, false))
|
||||
continue;
|
||||
|
||||
log_debug(" [DB] query failed for %s: rc=%d err=%s", label, rc,
|
||||
sqlite3_errmsg(g_app_db));
|
||||
break;
|
||||
}
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool app_db_run_startup_maintenance(void) {
|
||||
bool ok = false;
|
||||
pthread_mutex_lock(&g_app_db_mutex);
|
||||
|
||||
if (!ensure_app_db_open())
|
||||
goto out;
|
||||
(void)sqlite3_busy_timeout(g_app_db,
|
||||
APP_DB_STARTUP_MAINTENANCE_BUSY_TIMEOUT_MS);
|
||||
|
||||
const char *backfill_count_sql =
|
||||
"SELECT COUNT(*) FROM tbl_contentinfo "
|
||||
"WHERE instr(snd0Info, '/user/app/') > 0 "
|
||||
"OR instr(snd0Info, '/sce_sys') > 0;";
|
||||
int pending_backfill =
|
||||
app_db_query_int(backfill_count_sql, "snd0info normalize backfill check");
|
||||
if (pending_backfill < 0) {
|
||||
close_app_db();
|
||||
goto out;
|
||||
}
|
||||
|
||||
int changes = 0;
|
||||
if (pending_backfill > 0) {
|
||||
const char *backfill_sql =
|
||||
"UPDATE tbl_contentinfo "
|
||||
"SET snd0Info = replace(replace(snd0Info, '/user/app/', "
|
||||
"'/user/appmeta/'), '/sce_sys', '') "
|
||||
"WHERE instr(snd0Info, '/user/app/') > 0 "
|
||||
"OR instr(snd0Info, '/sce_sys') > 0;";
|
||||
|
||||
if (!app_db_exec_with_retry(backfill_sql,
|
||||
APP_DB_STARTUP_MAINTENANCE_RETRIES,
|
||||
"snd0info startup normalize", &changes))
|
||||
goto out;
|
||||
}
|
||||
|
||||
log_debug(" [DB] snd0info startup maintenance done rows=%d pending=%d",
|
||||
changes, pending_backfill);
|
||||
|
||||
close_app_db();
|
||||
ok = true;
|
||||
|
||||
out:
|
||||
pthread_mutex_unlock(&g_app_db_mutex);
|
||||
return ok;
|
||||
}
|
||||
|
||||
int update_snd0info(const char *title_id) {
|
||||
int result = -1;
|
||||
pthread_mutex_lock(&g_app_db_mutex);
|
||||
|
||||
if (!g_app_db_stmt_update_snd0) {
|
||||
const char *sql =
|
||||
"UPDATE tbl_contentinfo "
|
||||
"SET snd0info = '/user/appmeta/' || ?1 || '/snd0.at9' "
|
||||
"WHERE titleId = ?1;";
|
||||
int prep_rc = app_db_prepare_with_retry(sql, &g_app_db_stmt_update_snd0,
|
||||
APP_DB_PREPARE_BUSY_RETRIES,
|
||||
"snd0info update");
|
||||
if (prep_rc != SQLITE_OK)
|
||||
goto out;
|
||||
}
|
||||
|
||||
for (int attempt = 0; attempt < APP_DB_UPDATE_BUSY_RETRIES; attempt++) {
|
||||
sqlite3_reset(g_app_db_stmt_update_snd0);
|
||||
sqlite3_clear_bindings(g_app_db_stmt_update_snd0);
|
||||
if (sqlite3_bind_text(g_app_db_stmt_update_snd0, 1, title_id, -1,
|
||||
SQLITE_TRANSIENT) != SQLITE_OK) {
|
||||
log_debug(" [DB] bind failed for snd0info update: %s",
|
||||
sqlite3_errmsg(g_app_db));
|
||||
close_app_db();
|
||||
goto out;
|
||||
}
|
||||
|
||||
int rc = sqlite3_step(g_app_db_stmt_update_snd0);
|
||||
if (rc == SQLITE_DONE) {
|
||||
int changes = sqlite3_changes(g_app_db);
|
||||
close_app_db();
|
||||
result = changes;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (app_db_wait_retry(rc, attempt, APP_DB_UPDATE_BUSY_RETRIES, false))
|
||||
continue;
|
||||
|
||||
log_debug(" [DB] step failed for snd0info update: rc=%d err=%s", rc,
|
||||
sqlite3_errmsg(g_app_db));
|
||||
close_app_db();
|
||||
goto out;
|
||||
}
|
||||
|
||||
close_app_db();
|
||||
out:
|
||||
pthread_mutex_unlock(&g_app_db_mutex);
|
||||
return result;
|
||||
}
|
||||
|
||||
int normalize_snd0info_for_title(const char *title_id) {
|
||||
int result = -1;
|
||||
pthread_mutex_lock(&g_app_db_mutex);
|
||||
|
||||
if (!g_app_db_stmt_normalize_snd0) {
|
||||
const char *sql =
|
||||
"UPDATE tbl_contentinfo "
|
||||
"SET snd0Info = replace(replace(snd0Info, '/user/app/', "
|
||||
"'/user/appmeta/'), '/sce_sys', '') "
|
||||
"WHERE titleId = ?1 "
|
||||
"AND (instr(snd0Info, '/user/app/') > 0 "
|
||||
"OR instr(snd0Info, '/sce_sys') > 0);";
|
||||
int prep_rc = app_db_prepare_with_retry(sql, &g_app_db_stmt_normalize_snd0,
|
||||
APP_DB_PREPARE_BUSY_RETRIES,
|
||||
"snd0info normalize");
|
||||
if (prep_rc != SQLITE_OK)
|
||||
goto out;
|
||||
}
|
||||
|
||||
for (int attempt = 0; attempt < APP_DB_UPDATE_BUSY_RETRIES; attempt++) {
|
||||
sqlite3_reset(g_app_db_stmt_normalize_snd0);
|
||||
sqlite3_clear_bindings(g_app_db_stmt_normalize_snd0);
|
||||
if (sqlite3_bind_text(g_app_db_stmt_normalize_snd0, 1, title_id, -1,
|
||||
SQLITE_TRANSIENT) != SQLITE_OK) {
|
||||
log_debug(" [DB] bind failed for snd0info normalize: %s",
|
||||
sqlite3_errmsg(g_app_db));
|
||||
close_app_db();
|
||||
goto out;
|
||||
}
|
||||
|
||||
int rc = sqlite3_step(g_app_db_stmt_normalize_snd0);
|
||||
if (rc == SQLITE_DONE) {
|
||||
int changes = sqlite3_changes(g_app_db);
|
||||
close_app_db();
|
||||
result = changes;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (app_db_wait_retry(rc, attempt, APP_DB_UPDATE_BUSY_RETRIES, false))
|
||||
continue;
|
||||
|
||||
log_debug(" [DB] step failed for snd0info normalize: rc=%d err=%s", rc,
|
||||
sqlite3_errmsg(g_app_db));
|
||||
close_app_db();
|
||||
goto out;
|
||||
}
|
||||
|
||||
close_app_db();
|
||||
out:
|
||||
pthread_mutex_unlock(&g_app_db_mutex);
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool append_app_db_title(struct AppDbTitleList *list,
|
||||
const char *title_id) {
|
||||
if (!list || !title_id || title_id[0] == '\0')
|
||||
return true;
|
||||
|
||||
if (list->count >= list->capacity) {
|
||||
int new_capacity = (list->capacity > 0) ? (list->capacity * 2) : 1024;
|
||||
char(*new_ids)[MAX_TITLE_ID] =
|
||||
realloc(list->ids, (size_t)new_capacity * sizeof(*list->ids));
|
||||
if (!new_ids)
|
||||
return false;
|
||||
list->ids = new_ids;
|
||||
list->capacity = new_capacity;
|
||||
}
|
||||
|
||||
(void)strlcpy(list->ids[list->count], title_id, MAX_TITLE_ID);
|
||||
list->count++;
|
||||
return true;
|
||||
}
|
||||
|
||||
static int compare_title_id_str(const void *a, const void *b) {
|
||||
return strcmp((const char *)a, (const char *)b);
|
||||
}
|
||||
|
||||
bool app_db_title_list_contains(const struct AppDbTitleList *list,
|
||||
const char *title_id) {
|
||||
if (list->count <= 0)
|
||||
return false;
|
||||
return bsearch(title_id, list->ids, (size_t)list->count, sizeof(*list->ids),
|
||||
compare_title_id_str) != NULL;
|
||||
}
|
||||
|
||||
static bool step_app_db_title_query(struct AppDbTitleList *list,
|
||||
sqlite3_stmt *stmt,
|
||||
const char *label) {
|
||||
int busy_attempts = 0;
|
||||
while (!should_stop_requested()) {
|
||||
int rc = sqlite3_step(stmt);
|
||||
if (rc == SQLITE_ROW) {
|
||||
const char *title_id = (const char *)sqlite3_column_text(stmt, 0);
|
||||
if (!append_app_db_title(list, title_id)) {
|
||||
log_debug(" [DB] %s allocation failed", label);
|
||||
sqlite3_finalize(stmt);
|
||||
close_app_db();
|
||||
free_app_db_title_list(list);
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (rc == SQLITE_DONE) {
|
||||
sqlite3_finalize(stmt);
|
||||
close_app_db();
|
||||
return true;
|
||||
}
|
||||
if ((rc == SQLITE_BUSY || rc == SQLITE_LOCKED) &&
|
||||
busy_attempts + 1 < APP_DB_QUERY_BUSY_RETRIES) {
|
||||
busy_attempts++;
|
||||
sceKernelUsleep(APP_DB_BUSY_RETRY_SLEEP_US);
|
||||
continue;
|
||||
}
|
||||
|
||||
log_debug(" [DB] %s query failed: rc=%d err=%s", label, rc,
|
||||
sqlite3_errmsg(g_app_db));
|
||||
sqlite3_finalize(stmt);
|
||||
close_app_db();
|
||||
free_app_db_title_list(list);
|
||||
return false;
|
||||
}
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
close_app_db();
|
||||
free_app_db_title_list(list);
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool load_app_db_title_list(struct AppDbTitleList *list) {
|
||||
free_app_db_title_list(list);
|
||||
|
||||
const char *sql =
|
||||
"SELECT DISTINCT titleId "
|
||||
"FROM tbl_contentinfo "
|
||||
"WHERE titleId != '' "
|
||||
"ORDER BY titleId;";
|
||||
sqlite3_stmt *stmt = NULL;
|
||||
int prep_rc = app_db_prepare_with_retry(sql, &stmt,
|
||||
APP_DB_PREPARE_BUSY_RETRIES,
|
||||
"title list query");
|
||||
if (prep_rc != SQLITE_OK)
|
||||
return false;
|
||||
|
||||
bool loaded = step_app_db_title_query(list, stmt, "title list");
|
||||
if (loaded)
|
||||
log_debug(" [DB] loaded app.db title list: %d entries", list->count);
|
||||
return loaded;
|
||||
}
|
||||
|
||||
static bool load_app_db_auto_remove_title_list(struct AppDbTitleList *list) {
|
||||
free_app_db_title_list(list);
|
||||
|
||||
const char *sql =
|
||||
"SELECT DISTINCT titleId "
|
||||
"FROM tbl_contentinfo "
|
||||
"WHERE metaDataPath GLOB '/user/app/PPSA*/sce_sys' "
|
||||
"ORDER BY titleId;";
|
||||
sqlite3_stmt *stmt = NULL;
|
||||
int prep_rc = app_db_prepare_with_retry(sql, &stmt,
|
||||
APP_DB_PREPARE_BUSY_RETRIES,
|
||||
"auto-remove title query");
|
||||
if (prep_rc != SQLITE_OK)
|
||||
return false;
|
||||
|
||||
bool loaded = step_app_db_title_query(list, stmt, "auto-remove title list");
|
||||
if (loaded)
|
||||
log_debug(" [DB] loaded app.db auto-remove title list: %d entries",
|
||||
list->count);
|
||||
return loaded;
|
||||
}
|
||||
|
||||
static bool load_app_db_blocked_uninstall_ppsa_list(struct AppDbTitleList *list) {
|
||||
free_app_db_title_list(list);
|
||||
|
||||
const char *sql =
|
||||
"SELECT DISTINCT titleId "
|
||||
"FROM tbl_contentinfo "
|
||||
"WHERE titleId LIKE 'PPSA%' "
|
||||
"AND uninstallable = 0 "
|
||||
"ORDER BY titleId;";
|
||||
sqlite3_stmt *stmt = NULL;
|
||||
int prep_rc =
|
||||
app_db_prepare_with_retry(sql, &stmt, APP_DB_PREPARE_BUSY_RETRIES,
|
||||
"blocked PPSA uninstallable query");
|
||||
if (prep_rc != SQLITE_OK)
|
||||
return false;
|
||||
|
||||
bool loaded = step_app_db_title_query(list, stmt, "blocked PPSA uninstall");
|
||||
if (loaded)
|
||||
log_debug(" [DB] loaded blocked PPSA uninstall list: %d entries",
|
||||
list->count);
|
||||
return loaded;
|
||||
}
|
||||
|
||||
void invalidate_app_db_title_cache(void) {
|
||||
pthread_mutex_lock(&g_app_db_mutex);
|
||||
g_app_db_title_cache_ready = false;
|
||||
g_app_db_auto_remove_cache_ready = false;
|
||||
g_app_db_blocked_uninstall_ppsa_cache_ready = false;
|
||||
g_app_db_title_cache_mtime = 0;
|
||||
g_app_db_auto_remove_cache_mtime = 0;
|
||||
g_app_db_blocked_uninstall_ppsa_cache_mtime = 0;
|
||||
pthread_mutex_unlock(&g_app_db_mutex);
|
||||
}
|
||||
|
||||
static bool copy_app_db_title_list(struct AppDbTitleList *dst,
|
||||
const struct AppDbTitleList *src) {
|
||||
if (!dst || !src)
|
||||
return false;
|
||||
|
||||
free_app_db_title_list(dst);
|
||||
if (src->count <= 0)
|
||||
return true;
|
||||
|
||||
char(*ids)[MAX_TITLE_ID] = malloc((size_t)src->count * sizeof(*ids));
|
||||
if (!ids)
|
||||
return false;
|
||||
|
||||
memcpy(ids, src->ids, (size_t)src->count * sizeof(*ids));
|
||||
dst->ids = ids;
|
||||
dst->count = src->count;
|
||||
dst->capacity = src->count;
|
||||
return true;
|
||||
}
|
||||
|
||||
typedef bool (*load_app_db_title_list_fn)(struct AppDbTitleList *list);
|
||||
|
||||
static bool get_cached_app_db_title_list(
|
||||
struct AppDbTitleList *list_out, struct AppDbTitleList *cache,
|
||||
bool *cache_ready, time_t *cache_mtime, load_app_db_title_list_fn load_fn) {
|
||||
free_app_db_title_list(list_out);
|
||||
|
||||
// ShellCore can keep app.db busy for several five-second timeout windows
|
||||
// immediately after WORKING. The pre-suspend snapshot is sufficient for
|
||||
// mounting already registered games and keeps resume scans non-blocking.
|
||||
if (*cache_ready && runtime_resume_grace_active())
|
||||
return copy_app_db_title_list(list_out, cache);
|
||||
|
||||
struct stat st;
|
||||
int app_db_stat_rc = stat(APP_DB_PATH, &st);
|
||||
if (!*cache_ready ||
|
||||
(app_db_stat_rc == 0 && *cache_mtime != st.st_mtime)) {
|
||||
struct AppDbTitleList fresh = {0};
|
||||
if (app_db_stat_rc == 0 && load_fn(&fresh)) {
|
||||
free_app_db_title_list(cache);
|
||||
*cache = fresh;
|
||||
*cache_mtime = st.st_mtime;
|
||||
*cache_ready = true;
|
||||
} else {
|
||||
free_app_db_title_list(&fresh);
|
||||
if (!*cache_ready)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return copy_app_db_title_list(list_out, cache);
|
||||
}
|
||||
|
||||
bool get_app_db_title_list_cached(struct AppDbTitleList *list_out) {
|
||||
if (!list_out)
|
||||
return false;
|
||||
|
||||
pthread_mutex_lock(&g_app_db_mutex);
|
||||
bool copied = get_cached_app_db_title_list(
|
||||
list_out, &g_app_db_title_cache, &g_app_db_title_cache_ready,
|
||||
&g_app_db_title_cache_mtime, load_app_db_title_list);
|
||||
pthread_mutex_unlock(&g_app_db_mutex);
|
||||
return copied;
|
||||
}
|
||||
|
||||
bool get_app_db_auto_remove_title_list(struct AppDbTitleList *list_out) {
|
||||
if (!list_out)
|
||||
return false;
|
||||
|
||||
pthread_mutex_lock(&g_app_db_mutex);
|
||||
bool copied = get_cached_app_db_title_list(
|
||||
list_out, &g_app_db_auto_remove_cache,
|
||||
&g_app_db_auto_remove_cache_ready, &g_app_db_auto_remove_cache_mtime,
|
||||
load_app_db_auto_remove_title_list);
|
||||
pthread_mutex_unlock(&g_app_db_mutex);
|
||||
return copied;
|
||||
}
|
||||
|
||||
bool get_app_db_blocked_uninstall_ppsa_list(struct AppDbTitleList *list_out) {
|
||||
if (!list_out)
|
||||
return false;
|
||||
|
||||
pthread_mutex_lock(&g_app_db_mutex);
|
||||
bool loaded = get_cached_app_db_title_list(
|
||||
list_out, &g_app_db_blocked_uninstall_ppsa_cache,
|
||||
&g_app_db_blocked_uninstall_ppsa_cache_ready,
|
||||
&g_app_db_blocked_uninstall_ppsa_cache_mtime,
|
||||
load_app_db_blocked_uninstall_ppsa_list);
|
||||
pthread_mutex_unlock(&g_app_db_mutex);
|
||||
return loaded;
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
+1233
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,78 @@
|
||||
#include "sm_platform.h"
|
||||
|
||||
#include "sm_config_mount.h"
|
||||
#include "sm_fan.h"
|
||||
#include "sm_limits.h"
|
||||
#include "sm_log.h"
|
||||
#include "sm_types.h"
|
||||
|
||||
#define FAN_DEVICE "/dev/icc_fan"
|
||||
#define FAN_GET_AUTOSERVO 0xC01C8F08UL
|
||||
#define FAN_SET_AUTOSERVO 0xC01C8F07UL
|
||||
#define FAN_CONFIG_SIZE 28u
|
||||
#define FAN_SELECTOR 0u
|
||||
#define FAN_TARGET_OFFSET 5u
|
||||
|
||||
static int get_fan_config(int fd, uint8_t config[FAN_CONFIG_SIZE]) {
|
||||
memset(config, 0, FAN_CONFIG_SIZE);
|
||||
config[2] = FAN_SELECTOR;
|
||||
return ioctl(fd, FAN_GET_AUTOSERVO, config);
|
||||
}
|
||||
|
||||
static int set_fan_target(int fd, uint8_t target_c,
|
||||
uint8_t *previous_target_out) {
|
||||
uint8_t current[FAN_CONFIG_SIZE];
|
||||
uint8_t verified[FAN_CONFIG_SIZE];
|
||||
|
||||
if (get_fan_config(fd, current) != 0)
|
||||
return -1;
|
||||
|
||||
*previous_target_out = current[FAN_TARGET_OFFSET];
|
||||
if (*previous_target_out == target_c)
|
||||
return 0;
|
||||
|
||||
current[FAN_TARGET_OFFSET] = target_c;
|
||||
if (ioctl(fd, FAN_SET_AUTOSERVO, current) != 0)
|
||||
return -1;
|
||||
if (get_fan_config(fd, verified) != 0)
|
||||
return -1;
|
||||
if (verified[FAN_TARGET_OFFSET] == target_c)
|
||||
return 0;
|
||||
|
||||
errno = EIO;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int apply_fan_target(uint8_t target_c,
|
||||
uint8_t *previous_target_out) {
|
||||
int fd = open(FAN_DEVICE, O_RDWR | O_DIRECT);
|
||||
if (fd < 0)
|
||||
return -1;
|
||||
|
||||
int result = set_fan_target(fd, target_c, previous_target_out);
|
||||
int saved_errno = errno;
|
||||
close(fd);
|
||||
errno = saved_errno;
|
||||
return result;
|
||||
}
|
||||
|
||||
void sm_fan_apply_game_target(const char *title_id) {
|
||||
uint32_t target_c = runtime_config()->fan_target_temperature_c;
|
||||
if (target_c == FAN_TARGET_TEMPERATURE_SYSTEM)
|
||||
return;
|
||||
|
||||
uint8_t previous_target = 0;
|
||||
if (apply_fan_target((uint8_t)target_c, &previous_target) != 0) {
|
||||
log_debug(" [FAN] target apply failed for %s: %s", title_id,
|
||||
strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
if (previous_target == target_c) {
|
||||
log_debug(" [FAN] target already %u C for %s", (unsigned)target_c,
|
||||
title_id);
|
||||
} else {
|
||||
log_debug(" [FAN] target applied for %s: %u C -> %u C", title_id,
|
||||
(unsigned)previous_target, (unsigned)target_c);
|
||||
}
|
||||
}
|
||||
+1566
File diff suppressed because it is too large.
Load diff
Loaded 100 of 136 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user