mirror of
https://github.com/drakmor/ShadowMountPlus.git
synced 2026-10-20 21:59:57 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e58123275 | ||
|
|
3aa62a3728 | ||
|
|
38bb6790db | ||
|
|
be92afa71d | ||
|
|
fa8423d427 | ||
|
|
3d8d248336 | ||
|
|
22bacfe168 | ||
|
|
4085fcd8ca | ||
|
|
eb3c611ebf | ||
|
|
9f0242044d | ||
|
|
c4e9839fe5 | ||
|
|
73b26ba3fb |
No files matched your search
@@ -10,3 +10,6 @@ mkexfat_macos.sh
|
||||
mkufs2.sh
|
||||
New-OsfExfatImage.ps1
|
||||
README.md
|
||||
docs/socket-api.md
|
||||
docs/openapi.yaml
|
||||
tools/api_test.py
|
||||
@@ -47,6 +47,7 @@ jobs:
|
||||
automake \
|
||||
build-essential \
|
||||
clang-18 \
|
||||
cmake \
|
||||
curl \
|
||||
git \
|
||||
libarchive-tools \
|
||||
@@ -56,6 +57,8 @@ jobs:
|
||||
meson \
|
||||
pacman-package-manager \
|
||||
pkg-config \
|
||||
python3 \
|
||||
python3-yaml \
|
||||
xxd \
|
||||
zip
|
||||
|
||||
@@ -65,12 +68,29 @@ jobs:
|
||||
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"
|
||||
|
||||
@@ -3,15 +3,17 @@ include $(PS5_PAYLOAD_SDK)/toolchain/prospero.mk
|
||||
|
||||
VERSION_TAG := $(shell git describe --abbrev=6 --dirty --always --tags 2>/dev/null || echo unknown)
|
||||
|
||||
# Standard Flags (No extra libraries)
|
||||
# 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 := -flto=thin -Wl,--gc-sections
|
||||
|
||||
# Standard Libraries Only
|
||||
LIBS := -lSceNotification -lSceSystemService -lSceUserService -lSceAppInstUtil -lsqlite3
|
||||
# Libraries
|
||||
LIBS := -lSceNotification -lSceSystemService -lSceUserService -lSceAppInstUtil -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
|
||||
@@ -21,14 +23,11 @@ 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
|
||||
|
||||
api-test.elf: tools/api_test.c $(HEADERS)
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< -lSceNotification -lSceUserService
|
||||
$(PS5_PAYLOAD_SDK)/bin/prospero-strip --strip-all $@
|
||||
|
||||
# Build Daemon
|
||||
shadowmountplus.elf: $(OBJS) $(KERNEL_SYS_STUB_SO)
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(OBJS) $(KERNEL_SYS_STUB_SO) $(LIBS)
|
||||
@@ -45,6 +44,10 @@ src/notify_icon_asset.c: smp_icon.png
|
||||
src/config_ini_example_asset.c: config.ini.example
|
||||
xxd -i $< > $@
|
||||
|
||||
src/sm_api_service.o: CFLAGS += $(JSON_CFLAGS)
|
||||
|
||||
src/sm_l10n.o: $(L10N_CATALOGS)
|
||||
|
||||
src/%.o: src/%.c $(HEADERS)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
|
||||
@@ -54,9 +54,16 @@ This file is optional. If it does not exist, ShadowMountPlus creates it from the
|
||||
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)
|
||||
- `image_sector=<image_filename>:<sector_size>` (repeatable; force sector size for this image filename)
|
||||
@@ -88,6 +95,44 @@ Supported keys (all optional):
|
||||
- `md_exfat_sector_size=<value>` (default: `512`)
|
||||
- `md_ufs_sector_size=<value>` (default: `512`)
|
||||
|
||||
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).
|
||||
- File names with spaces are supported.
|
||||
@@ -102,6 +147,12 @@ image_ro=legacy_dump.ffpkg
|
||||
image_sector=MYGame 123.exfat:65536
|
||||
```
|
||||
|
||||
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.
|
||||
@@ -116,6 +167,7 @@ Scan path behavior:
|
||||
- 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:
|
||||
@@ -147,7 +199,7 @@ Kstuff game lifecycle behavior:
|
||||
- `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 and kstuff is not to blame.
|
||||
- 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.
|
||||
|
||||
|
||||
@@ -21,6 +21,19 @@
|
||||
# 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
|
||||
@@ -33,12 +46,37 @@
|
||||
# Default: 0
|
||||
# force_mount=0
|
||||
|
||||
# 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
|
||||
|
||||
# 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
|
||||
|
||||
# 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
|
||||
|
||||
@@ -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
|
||||
+86
-41
@@ -1,52 +1,97 @@
|
||||
# ShadowMount socket API v1
|
||||
# ShadowMount HTTP/JSON API v1
|
||||
|
||||
ShadowMount exposes a public Unix stream socket at
|
||||
`/system_tmp/shadowmount-api.sock`. The internal
|
||||
`/system_tmp/shadowmount.sock` endpoint is reserved for the SceShellCore
|
||||
bridge and must not be used by third-party applications.
|
||||
The machine-readable OpenAPI 3.1 description is available in
|
||||
[`openapi.yaml`](openapi.yaml).
|
||||
|
||||
The ABI is defined in `include/sm_api_protocol.h`. All integers use the native
|
||||
PS5 little-endian byte order. A client opens one connection, writes exactly one
|
||||
64-byte `sm_api_request_t`, reads one 32-byte `sm_api_response_t` followed by
|
||||
the advertised payload, and closes the connection.
|
||||
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.
|
||||
|
||||
Validate `magic`, `version`, `operation`, `size`, `item_count`, and `item_size`
|
||||
before consuming a response. `status` is zero on success or a positive errno
|
||||
value. Strings are NUL-terminated fixed-size byte arrays.
|
||||
The API has no authentication. Bind it to a network-facing address only on a
|
||||
trusted network.
|
||||
|
||||
## Operations
|
||||
The internal `/system_tmp/shadowmount.sock` Unix socket remains private to the
|
||||
SceShellCore bridge and must not be used by third-party applications.
|
||||
|
||||
- `SM_API_GET_VERSION` returns one `sm_api_version_info_t`.
|
||||
- `SM_API_LIST_IMAGES` returns `sm_api_image_info_t` entries. `offset` is the
|
||||
zero-based entry offset; `limit` is clamped to 16 and zero means 16.
|
||||
- `SM_API_LIST_GAMES` uses the same pagination and returns
|
||||
`sm_api_game_info_t` entries.
|
||||
- `SM_API_MOUNT_GAME` mounts the managed runtime stack identified by
|
||||
`title_id`.
|
||||
- `SM_API_UNMOUNT_GAME` releases that runtime stack and its backing image
|
||||
chain.
|
||||
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.
|
||||
|
||||
List responses contain the full current count in `total_count` and the current
|
||||
page size in `item_count`. Continue with `offset += item_count` until the offset
|
||||
reaches `total_count`.
|
||||
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.
|
||||
|
||||
Mount mutations are deliberately conservative. They return `EBUSY` while a
|
||||
game is active, while ShellCore owns another prepared title, during suspend,
|
||||
or while a conflicting mount/release is in progress. `ENOENT` means the title
|
||||
does not have a ShadowMount `mount.lnk` tracker. Clients should not retry
|
||||
`EBUSY` in a tight loop.
|
||||
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.
|
||||
|
||||
The socket is created only after startup synchronization completes and is
|
||||
removed before shutdown mount cleanup begins.
|
||||
## Routes
|
||||
|
||||
## Test payload
|
||||
| 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 |
|
||||
|
||||
Build `api-test.elf` and run it while ShadowMount is active. It validates the
|
||||
version response, walks every image and game page, then selects the first
|
||||
managed game that is not already mounted and performs a mount/unmount cycle.
|
||||
The mutation test is skipped when no safe candidate exists or the runtime is
|
||||
busy; an existing mounted game is never selected.
|
||||
Example:
|
||||
|
||||
Clients should call `SM_API_GET_VERSION` first and check its capability bits.
|
||||
Unknown operations return `ENOTSUP`, malformed requests return `EPROTO`, and a
|
||||
future incompatible structure layout will increment `SM_API_VERSION`.
|
||||
```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,84 @@
|
||||
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: 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",
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
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: 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",
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
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: 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",
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
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: 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",
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
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: 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",
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
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: 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",
|
||||
};
|
||||
@@ -0,0 +1,72 @@
|
||||
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_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",
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
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: 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",
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
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: 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",
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
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: 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",
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
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: 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",
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
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: 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",
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
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: 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",
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
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: 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",
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
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: 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",
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
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: 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",
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
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: 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",
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
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: 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",
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
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: 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",
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
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: 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",
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
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: 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",
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
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: 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",
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
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: 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",
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
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: 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",
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
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: 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",
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
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: 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",
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
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: 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",
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
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: 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",
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
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: 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",
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
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: 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",
|
||||
};
|
||||
@@ -1,95 +1,16 @@
|
||||
#ifndef SM_API_PROTOCOL_H
|
||||
#define SM_API_PROTOCOL_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define SM_API_SOCKET_PATH "/system_tmp/shadowmount-api.sock"
|
||||
#define SM_API_MAGIC 0x50414d53u /* "SMAP" in little endian */
|
||||
#define SM_API_DEFAULT_BIND_ADDRESS "127.0.0.1"
|
||||
#define SM_API_DEFAULT_PORT 10101u
|
||||
#define SM_API_VERSION 1u
|
||||
#define SM_API_PATH_SIZE 1024u
|
||||
#define SM_API_TITLE_ID_SIZE 32u
|
||||
#define SM_API_TITLE_NAME_SIZE 256u
|
||||
#define SM_API_VERSION_STRING_SIZE 64u
|
||||
#define SM_API_MAX_PAGE_ITEMS 16u
|
||||
#define SM_API_MAX_HTTP_HEADER_SIZE 8192u
|
||||
#define SM_API_MAX_JSON_BODY_SIZE 4096u
|
||||
|
||||
typedef enum {
|
||||
SM_API_GET_VERSION = 1,
|
||||
SM_API_LIST_IMAGES = 2,
|
||||
SM_API_LIST_GAMES = 3,
|
||||
SM_API_MOUNT_GAME = 4,
|
||||
SM_API_UNMOUNT_GAME = 5,
|
||||
} sm_api_operation_t;
|
||||
|
||||
enum {
|
||||
SM_API_CAP_LIST_IMAGES = 1u << 0,
|
||||
SM_API_CAP_LIST_GAMES = 1u << 1,
|
||||
SM_API_CAP_MOUNT_GAME = 1u << 2,
|
||||
SM_API_CAP_UNMOUNT_GAME = 1u << 3,
|
||||
};
|
||||
|
||||
enum {
|
||||
SM_API_IMAGE_COMPLETE = 1u << 0,
|
||||
SM_API_IMAGE_SOURCE_AVAILABLE = 1u << 1,
|
||||
SM_API_IMAGE_MAPPED = 1u << 2,
|
||||
SM_API_IMAGE_MOUNTED = 1u << 3,
|
||||
};
|
||||
|
||||
enum {
|
||||
SM_API_GAME_INSTALLED = 1u << 0,
|
||||
SM_API_GAME_MANAGED = 1u << 1,
|
||||
SM_API_GAME_MOUNTED = 1u << 2,
|
||||
SM_API_GAME_IMAGE_BACKED = 1u << 3,
|
||||
SM_API_GAME_SOURCE_AVAILABLE = 1u << 4,
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
uint32_t magic;
|
||||
uint16_t version;
|
||||
uint16_t operation;
|
||||
uint32_t size;
|
||||
uint32_t offset;
|
||||
uint32_t limit;
|
||||
uint32_t flags;
|
||||
char title_id[SM_API_TITLE_ID_SIZE];
|
||||
uint8_t reserved[8];
|
||||
} sm_api_request_t;
|
||||
|
||||
typedef struct {
|
||||
uint32_t magic;
|
||||
uint16_t version;
|
||||
uint16_t operation;
|
||||
uint32_t size;
|
||||
int32_t status;
|
||||
uint32_t total_count;
|
||||
uint32_t item_count;
|
||||
uint32_t item_size;
|
||||
uint32_t flags;
|
||||
} sm_api_response_t;
|
||||
|
||||
typedef struct {
|
||||
uint32_t api_version;
|
||||
uint32_t capabilities;
|
||||
char shadowmount_version[SM_API_VERSION_STRING_SIZE];
|
||||
} sm_api_version_info_t;
|
||||
|
||||
typedef struct {
|
||||
char path[SM_API_PATH_SIZE];
|
||||
char mount_point[SM_API_PATH_SIZE];
|
||||
int64_t size;
|
||||
int64_t mtime_sec;
|
||||
int32_t mtime_nsec;
|
||||
int32_t unit_id;
|
||||
uint32_t backend;
|
||||
uint32_t flags;
|
||||
} sm_api_image_info_t;
|
||||
|
||||
typedef struct {
|
||||
char path[SM_API_PATH_SIZE];
|
||||
char image_path[SM_API_PATH_SIZE];
|
||||
char title_id[SM_API_TITLE_ID_SIZE];
|
||||
char title_name[SM_API_TITLE_NAME_SIZE];
|
||||
uint32_t flags;
|
||||
uint32_t reserved;
|
||||
} sm_api_game_info_t;
|
||||
#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
|
||||
@@ -5,5 +5,6 @@
|
||||
|
||||
bool sm_api_service_start(void);
|
||||
void sm_api_service_stop(void);
|
||||
bool sm_api_service_reconfigure(void);
|
||||
|
||||
#endif
|
||||
@@ -22,6 +22,8 @@ void free_app_db_title_list(struct AppDbTitleList *list);
|
||||
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);
|
||||
|
||||
|
||||
@@ -42,6 +42,8 @@ bool mount_title_nullfs(const char *title_id, const char *src_path);
|
||||
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.
|
||||
|
||||
+10
-5
@@ -10,7 +10,6 @@ typedef struct {
|
||||
char path[MAX_PATH];
|
||||
char title_id[MAX_TITLE_ID];
|
||||
char title_name[MAX_TITLE_NAME];
|
||||
char owning_scan_root[MAX_PATH];
|
||||
} sm_game_cache_snapshot_entry_t;
|
||||
|
||||
typedef bool (*game_cache_iter_fn)(const char *path, const char *title_id,
|
||||
@@ -24,16 +23,22 @@ void cache_game_entry(const char *path, const char *title_id,
|
||||
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);
|
||||
// Copy a stable, paginated snapshot for read-only API consumers.
|
||||
size_t sm_game_cache_snapshot(size_t offset,
|
||||
sm_game_cache_snapshot_entry_t *entries,
|
||||
size_t capacity, size_t *total_out);
|
||||
// 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);
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
|
||||
struct AppDbTitleList;
|
||||
|
||||
typedef void (*sm_image_index_title_visitor_t)(const char *title_id);
|
||||
|
||||
typedef struct {
|
||||
char path[MAX_PATH];
|
||||
int64_t size;
|
||||
@@ -18,10 +20,14 @@ typedef struct {
|
||||
bool complete;
|
||||
} sm_image_index_snapshot_entry_t;
|
||||
|
||||
// Return true when an image must be opened to discover/install its contents.
|
||||
bool sm_image_index_needs_scan(const char *path, const struct stat *st,
|
||||
const struct AppDbTitleList *app_db_titles,
|
||||
bool app_db_titles_ready);
|
||||
// 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
|
||||
@@ -33,9 +39,8 @@ void sm_image_index_complete_scan(const char *path);
|
||||
void sm_image_index_flush(void);
|
||||
// Drop persistent entries whose backing image disappeared.
|
||||
void sm_image_index_prune(void);
|
||||
// Copy a stable, paginated snapshot of known backing images.
|
||||
size_t sm_image_index_snapshot(size_t offset,
|
||||
sm_image_index_snapshot_entry_t *entries,
|
||||
size_t capacity, size_t *total_out);
|
||||
// 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,58 @@
|
||||
#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_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_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
|
||||
+6
-1
@@ -4,9 +4,9 @@
|
||||
#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 MAX_PENDING 512
|
||||
#define MAX_IMAGE_MOUNTS 2048
|
||||
#define MAX_IMAGE_TITLES 8192
|
||||
@@ -30,6 +30,8 @@
|
||||
#define GAME_LIFECYCLE_POLL_INTERVAL_US 250000u
|
||||
#define GAME_APPINFO_LOOKUP_TIMEOUT_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
|
||||
@@ -37,6 +39,8 @@
|
||||
#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
|
||||
@@ -48,6 +52,7 @@
|
||||
#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
|
||||
+8
-2
@@ -3,6 +3,8 @@
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "sm_l10n.h"
|
||||
|
||||
typedef struct sm_error sm_error_t;
|
||||
|
||||
// Write a formatted line to the debug log.
|
||||
@@ -11,19 +13,23 @@ void log_debug(const char *fmt, ...);
|
||||
void sm_notifications_init(void);
|
||||
// Flush and close persistent log resources.
|
||||
void sm_log_shutdown(void);
|
||||
// Send a rich toast notification with packaged icon and version header.
|
||||
void notify_system_rich(bool allow_in_quiet_mode, const char *fmt, ...);
|
||||
// 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.
|
||||
|
||||
@@ -18,6 +18,9 @@ bool is_pfsc_image_mount_base_or_child(const char *path);
|
||||
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]);
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@ 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);
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@ bool should_stop_requested(void);
|
||||
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);
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
// Install/remove the fail-open SceShellCore lifecycle bridge.
|
||||
bool sm_shellcore_hooks_start(void);
|
||||
void sm_shellcore_hooks_stop(void);
|
||||
// Return whether the post-unmountWorkspace lifecycle hook is installed.
|
||||
bool sm_shellcore_workspace_hook_active(void);
|
||||
// Return whether the post-onAppExit lifecycle hook is installed.
|
||||
bool sm_shellcore_app_exit_hook_active(void);
|
||||
|
||||
// Return whether this firmware uses the internal TitleDir bridge.
|
||||
bool sm_shellcore_install_bridge_enabled(void);
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
typedef enum {
|
||||
SM_SHELLCORE_TARGET_LAUNCH_APP = 0,
|
||||
SM_SHELLCORE_TARGET_UNMOUNT_WORKSPACE,
|
||||
SM_SHELLCORE_TARGET_APP_EXIT,
|
||||
SM_SHELLCORE_TARGET_INSTALL_TITLE_DIR,
|
||||
SM_SHELLCORE_TARGET_INSTALL_ALL,
|
||||
SM_SHELLCORE_TARGET_COUNT
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
typedef enum {
|
||||
SM_SHELLCORE_REQUEST_LAUNCH = 1,
|
||||
SM_SHELLCORE_REQUEST_LAUNCH_FAILED = 2,
|
||||
SM_SHELLCORE_REQUEST_WORKSPACE_UNMOUNTED = 3
|
||||
SM_SHELLCORE_REQUEST_APP_EXITED = 3
|
||||
} sm_shellcore_request_op_t;
|
||||
|
||||
typedef struct {
|
||||
@@ -24,7 +24,7 @@ typedef struct {
|
||||
struct {
|
||||
uint32_t app_id;
|
||||
uint8_t reserved[MAX_TITLE_ID - sizeof(uint32_t)];
|
||||
} workspace;
|
||||
} app_exit;
|
||||
} payload;
|
||||
} sm_shellcore_request_t;
|
||||
|
||||
|
||||
@@ -8,8 +8,7 @@
|
||||
bool sm_shellcore_service_start(void);
|
||||
void sm_shellcore_service_stop(void);
|
||||
|
||||
// Release transient title/image mounts after launch failure or ShellCore
|
||||
// workspace teardown.
|
||||
// Release transient title/image mounts after launch failure or app exit.
|
||||
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);
|
||||
@@ -17,15 +16,15 @@ 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);
|
||||
// Publish process exit without tearing down the still-live ShellCore workspace.
|
||||
// Returns true when a managed prepared title transitioned to exit-pending.
|
||||
// Publish process exit while ShellCore finishes its own app-exit cleanup.
|
||||
// Returns true when an owned incoming/outgoing title became exit-pending.
|
||||
bool sm_shellcore_service_note_game_exit(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 the prepared runtime mount.
|
||||
// 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 a title mount prepared for launch or exit.
|
||||
// Return true while ShellCore owns any title mount prepared for launch or exit.
|
||||
bool sm_shellcore_service_has_prepared_mount(void);
|
||||
|
||||
#endif
|
||||
@@ -108,21 +108,28 @@ typedef struct runtime_config {
|
||||
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_mount_first;
|
||||
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];
|
||||
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;
|
||||
int32_t language_id;
|
||||
attach_backend_t exfat_backend;
|
||||
attach_backend_t ufs_backend;
|
||||
uint32_t lvd_sector_exfat;
|
||||
|
||||
+38
-17
@@ -47,6 +47,7 @@
|
||||
static volatile sig_atomic_t g_stop_requested = 0;
|
||||
static atomic_bool g_shutdown_on_going_stop_requested = false;
|
||||
static atomic_bool g_runtime_sleep_mode_active = false;
|
||||
static atomic_uint_fast64_t g_runtime_resume_grace_deadline_us = 0;
|
||||
static _Atomic(uintptr_t) g_shutdown_stop_reason_bits = 0;
|
||||
static atomic_uint_fast64_t g_next_stop_file_poll_us = 0;
|
||||
static pthread_mutex_t g_runtime_mount_state_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
@@ -131,6 +132,13 @@ bool runtime_sleep_mode_active(void) {
|
||||
memory_order_acquire);
|
||||
}
|
||||
|
||||
bool runtime_resume_grace_active(void) {
|
||||
uint64_t deadline_us = atomic_load_explicit(
|
||||
&g_runtime_resume_grace_deadline_us, memory_order_acquire);
|
||||
uint64_t now_us = monotonic_time_us();
|
||||
return deadline_us != 0 && now_us != 0 && now_us < deadline_us;
|
||||
}
|
||||
|
||||
static void clear_scan_now_request(void) {
|
||||
pthread_mutex_lock(&g_scan_now.reason_mutex);
|
||||
g_scan_now.reason[0] = '\0';
|
||||
@@ -138,13 +146,29 @@ static void clear_scan_now_request(void) {
|
||||
}
|
||||
|
||||
bool request_runtime_sleep_mode(bool active, const char *reason) {
|
||||
bool previous = atomic_exchange_explicit(&g_runtime_sleep_mode_active, active,
|
||||
memory_order_acq_rel);
|
||||
if (previous == active)
|
||||
return false;
|
||||
|
||||
if (active)
|
||||
if (active) {
|
||||
bool previous = atomic_exchange_explicit(&g_runtime_sleep_mode_active, true,
|
||||
memory_order_acq_rel);
|
||||
if (previous)
|
||||
return false;
|
||||
clear_scan_now_request();
|
||||
atomic_store_explicit(&g_runtime_resume_grace_deadline_us, 0,
|
||||
memory_order_release);
|
||||
} else {
|
||||
if (!runtime_sleep_mode_active())
|
||||
return false;
|
||||
uint64_t now_us = monotonic_time_us();
|
||||
uint64_t deadline_us =
|
||||
now_us != 0 ? now_us + RUNTIME_RESUME_GRACE_US : 0;
|
||||
// Publish the deadline before allowing launch requests to observe that
|
||||
// sleep mode ended.
|
||||
atomic_store_explicit(&g_runtime_resume_grace_deadline_us, deadline_us,
|
||||
memory_order_release);
|
||||
bool previous = atomic_exchange_explicit(&g_runtime_sleep_mode_active,
|
||||
false, memory_order_acq_rel);
|
||||
if (!previous)
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *resolved_reason =
|
||||
(reason && reason[0] != '\0') ? reason : "unknown sleep source";
|
||||
@@ -166,20 +190,17 @@ void runtime_mount_state_unlock(void) {
|
||||
void request_scan_now(const char *reason) {
|
||||
const char *resolved_reason =
|
||||
(reason && reason[0] != '\0') ? reason : "unknown scan source";
|
||||
bool resume_scan =
|
||||
strcmp(resolved_reason, "SceSystemStateMgrInfo=WORKING") == 0;
|
||||
if (runtime_sleep_mode_active() && !resume_scan)
|
||||
if (runtime_sleep_mode_active())
|
||||
return;
|
||||
|
||||
char log_reason[sizeof(g_scan_now.reason)];
|
||||
bool should_log = !resume_scan;
|
||||
bool should_log = false;
|
||||
|
||||
pthread_mutex_lock(&g_scan_now.reason_mutex);
|
||||
if (g_scan_now.reason[0] == '\0') {
|
||||
(void)strlcpy(g_scan_now.reason, resolved_reason, sizeof(g_scan_now.reason));
|
||||
(void)strlcpy(log_reason, g_scan_now.reason, sizeof(log_reason));
|
||||
} else {
|
||||
should_log = false;
|
||||
should_log = true;
|
||||
}
|
||||
pthread_mutex_unlock(&g_scan_now.reason_mutex);
|
||||
|
||||
@@ -529,14 +550,14 @@ int main(void) {
|
||||
log_debug(" [MOUNT] remount_system_ex failed: %s", strerror(errno));
|
||||
}
|
||||
|
||||
notify_system("ShadowMount+ v%s exFAT/UFS/PFS", SHADOWMOUNT_VERSION);
|
||||
notify_system_l10n(SM_L10N_STARTUP, SHADOWMOUNT_VERSION);
|
||||
log_non_empty_scan_paths();
|
||||
|
||||
if (runtime_config()->legacy_recursive_scan_forced) {
|
||||
notify_system_info("ShadowMount+: recursive_scan=1 deprecated, using scan_depth=2.");
|
||||
notify_system_info_l10n(SM_L10N_RECURSIVE_SCAN_DEPRECATED);
|
||||
} else if (runtime_config()->scan_depth > 1u) {
|
||||
notify_system_info("ShadowMount+: scan depth %u enabled.",
|
||||
runtime_config()->scan_depth);
|
||||
notify_system_info_l10n(SM_L10N_SCAN_DEPTH_ENABLED,
|
||||
runtime_config()->scan_depth);
|
||||
}
|
||||
|
||||
cleanup_mount_dirs();
|
||||
@@ -560,7 +581,7 @@ int main(void) {
|
||||
}
|
||||
log_debug("[STARTUP] scanner startup sync done");
|
||||
if (!sm_api_service_start())
|
||||
log_debug(" [API] public socket unavailable: %s", strerror(errno));
|
||||
log_debug(" [API] HTTP/JSON service unavailable: %s", strerror(errno));
|
||||
sm_scanner_run_loop();
|
||||
|
||||
shutdown:
|
||||
|
||||
+782
-211
File diff suppressed because it is too large.
Load diff
+82
-47
@@ -14,10 +14,13 @@ 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;
|
||||
|
||||
@@ -49,10 +52,13 @@ void free_app_db_title_list(struct AppDbTitleList *list) {
|
||||
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);
|
||||
@@ -418,6 +424,28 @@ static bool load_app_db_title_list(struct AppDbTitleList *list) {
|
||||
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);
|
||||
|
||||
@@ -444,8 +472,10 @@ static bool load_app_db_blocked_uninstall_ppsa_list(struct AppDbTitleList *list)
|
||||
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);
|
||||
}
|
||||
@@ -470,34 +500,60 @@ static bool copy_app_db_title_list(struct AppDbTitleList *dst,
|
||||
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;
|
||||
|
||||
free_app_db_title_list(list_out);
|
||||
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;
|
||||
}
|
||||
|
||||
struct stat st;
|
||||
int app_db_stat_rc = stat(APP_DB_PATH, &st);
|
||||
bool get_app_db_auto_remove_title_list(struct AppDbTitleList *list_out) {
|
||||
if (!list_out)
|
||||
return false;
|
||||
|
||||
if (!g_app_db_title_cache_ready ||
|
||||
(app_db_stat_rc == 0 && g_app_db_title_cache_mtime != st.st_mtime)) {
|
||||
struct AppDbTitleList fresh = {0};
|
||||
if (app_db_stat_rc == 0 && load_app_db_title_list(&fresh)) {
|
||||
free_app_db_title_list(&g_app_db_title_cache);
|
||||
g_app_db_title_cache = fresh;
|
||||
g_app_db_title_cache_mtime = st.st_mtime;
|
||||
g_app_db_title_cache_ready = true;
|
||||
} else {
|
||||
free_app_db_title_list(&fresh);
|
||||
if (!g_app_db_title_cache_ready) {
|
||||
pthread_mutex_unlock(&g_app_db_mutex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool copied = copy_app_db_title_list(list_out, &g_app_db_title_cache);
|
||||
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;
|
||||
}
|
||||
@@ -506,33 +562,12 @@ bool get_app_db_blocked_uninstall_ppsa_list(struct AppDbTitleList *list_out) {
|
||||
if (!list_out)
|
||||
return false;
|
||||
|
||||
free_app_db_title_list(list_out);
|
||||
pthread_mutex_lock(&g_app_db_mutex);
|
||||
|
||||
struct stat st;
|
||||
int app_db_stat_rc = stat(APP_DB_PATH, &st);
|
||||
|
||||
if (!g_app_db_blocked_uninstall_ppsa_cache_ready ||
|
||||
(app_db_stat_rc == 0 &&
|
||||
g_app_db_blocked_uninstall_ppsa_cache_mtime != st.st_mtime)) {
|
||||
struct AppDbTitleList fresh = {0};
|
||||
if (app_db_stat_rc == 0 &&
|
||||
load_app_db_blocked_uninstall_ppsa_list(&fresh)) {
|
||||
free_app_db_title_list(&g_app_db_blocked_uninstall_ppsa_cache);
|
||||
g_app_db_blocked_uninstall_ppsa_cache = fresh;
|
||||
g_app_db_blocked_uninstall_ppsa_cache_mtime = st.st_mtime;
|
||||
g_app_db_blocked_uninstall_ppsa_cache_ready = true;
|
||||
} else {
|
||||
free_app_db_title_list(&fresh);
|
||||
if (!g_app_db_blocked_uninstall_ppsa_cache_ready) {
|
||||
pthread_mutex_unlock(&g_app_db_mutex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool loaded =
|
||||
copy_app_db_title_list(list_out, &g_app_db_blocked_uninstall_ppsa_cache);
|
||||
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;
|
||||
}
|
||||
+105
-2
@@ -1,7 +1,13 @@
|
||||
#include "sm_platform.h"
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include "sm_api_protocol.h"
|
||||
#include "sm_config_mount.h"
|
||||
#include "sm_types.h"
|
||||
#include "sm_limits.h"
|
||||
#include "sm_l10n.h"
|
||||
#include "sm_log.h"
|
||||
#include "sm_mount_defs.h"
|
||||
#include "sm_mount_device.h"
|
||||
@@ -235,22 +241,31 @@ static void init_runtime_config_defaults(runtime_config_state_t *state) {
|
||||
state->cfg.quiet_mode = false;
|
||||
state->cfg.mount_read_only = (IMAGE_MOUNT_READ_ONLY != 0);
|
||||
state->cfg.force_mount = false;
|
||||
state->cfg.persistent_image_mounts = false;
|
||||
state->cfg.app_install_all_enabled = false;
|
||||
state->cfg.auto_remove_missing_games = false;
|
||||
state->cfg.auto_remove_games_with_dlc = false;
|
||||
state->cfg.backport_fakelib_enabled = true;
|
||||
state->cfg.global_fakelib_enabled = true;
|
||||
state->cfg.global_fakelib_mount_first = true;
|
||||
state->cfg.kstuff_game_auto_toggle = true;
|
||||
state->cfg.kstuff_crash_detection_enabled = true;
|
||||
state->cfg.legacy_recursive_scan_forced = false;
|
||||
(void)strlcpy(state->cfg.api_bind_address, SM_API_DEFAULT_BIND_ADDRESS,
|
||||
sizeof(state->cfg.api_bind_address));
|
||||
state->cfg.api_port = SM_API_DEFAULT_PORT;
|
||||
(void)strlcpy(state->cfg.global_fakelib_path, DEFAULT_GLOBAL_FAKELIB_PATH,
|
||||
sizeof(state->cfg.global_fakelib_path));
|
||||
state->cfg.scan_depth = DEFAULT_SCAN_DEPTH;
|
||||
state->cfg.scan_interval_us = DEFAULT_SCAN_INTERVAL_US;
|
||||
state->cfg.stability_wait_seconds = DEFAULT_STABILITY_WAIT_SECONDS;
|
||||
state->cfg.auto_remove_missing_delay_seconds =
|
||||
DEFAULT_AUTO_REMOVE_MISSING_DELAY_SECONDS;
|
||||
state->cfg.kstuff_pause_delay_image_seconds =
|
||||
DEFAULT_KSTUFF_PAUSE_DELAY_IMAGE_SECONDS;
|
||||
state->cfg.kstuff_pause_delay_direct_seconds =
|
||||
DEFAULT_KSTUFF_PAUSE_DELAY_DIRECT_SECONDS;
|
||||
state->cfg.language_id = SM_LANGUAGE_AUTO;
|
||||
state->cfg.exfat_backend = default_exfat_backend();
|
||||
state->cfg.ufs_backend = default_ufs_backend();
|
||||
state->cfg.lvd_sector_exfat = LVD_SECTOR_SIZE_EXFAT;
|
||||
@@ -1155,6 +1170,42 @@ static config_load_status_t load_runtime_config_state(runtime_config_state_t *st
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strcasecmp(key, "language") == 0 || strcasecmp(key, "lang") == 0 ||
|
||||
strcasecmp(key, "locale") == 0) {
|
||||
int32_t language_id = SM_LANGUAGE_AUTO;
|
||||
if (!sm_l10n_parse_language_id(value, &language_id)) {
|
||||
log_debug(" [CFG] invalid language at line %d: %s=%s "
|
||||
"(use auto or a supported locale like en-US, ru-RU)",
|
||||
line_no, key, value);
|
||||
continue;
|
||||
}
|
||||
state->cfg.language_id = language_id;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strcasecmp(key, "api_bind_address") == 0) {
|
||||
struct in_addr address;
|
||||
if (strlen(value) >= sizeof(state->cfg.api_bind_address) ||
|
||||
inet_pton(AF_INET, value, &address) != 1) {
|
||||
log_debug(" [CFG] invalid API IPv4 address at line %d: %s=%s",
|
||||
line_no, key, value);
|
||||
continue;
|
||||
}
|
||||
(void)strlcpy(state->cfg.api_bind_address, value,
|
||||
sizeof(state->cfg.api_bind_address));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strcasecmp(key, "api_port") == 0) {
|
||||
if (!parse_u32_ini(value, &u32) || u32 == 0 || u32 > 65535u) {
|
||||
log_debug(" [CFG] invalid API port at line %d: %s=%s (range: 1..65535)",
|
||||
line_no, key, value);
|
||||
continue;
|
||||
}
|
||||
state->cfg.api_port = u32;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strcasecmp(key, "mount_read_only") == 0 ||
|
||||
strcasecmp(key, "read_only") == 0) {
|
||||
if (!parse_bool_ini(value, &bval)) {
|
||||
@@ -1174,6 +1225,15 @@ static config_load_status_t load_runtime_config_state(runtime_config_state_t *st
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strcasecmp(key, "persistent_image_mounts") == 0) {
|
||||
if (!parse_bool_ini(value, &bval)) {
|
||||
log_debug(" [CFG] invalid bool at line %d: %s=%s", line_no, key, value);
|
||||
continue;
|
||||
}
|
||||
state->cfg.persistent_image_mounts = bval;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strcasecmp(key, "app_install_all") == 0) {
|
||||
if (!parse_bool_ini(value, &bval)) {
|
||||
log_debug(" [CFG] invalid bool at line %d: %s=%s", line_no, key, value);
|
||||
@@ -1183,6 +1243,24 @@ static config_load_status_t load_runtime_config_state(runtime_config_state_t *st
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strcasecmp(key, "auto_remove_missing_games") == 0) {
|
||||
if (!parse_bool_ini(value, &bval)) {
|
||||
log_debug(" [CFG] invalid bool at line %d: %s=%s", line_no, key, value);
|
||||
continue;
|
||||
}
|
||||
state->cfg.auto_remove_missing_games = bval;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strcasecmp(key, "auto_remove_games_with_dlc") == 0) {
|
||||
if (!parse_bool_ini(value, &bval)) {
|
||||
log_debug(" [CFG] invalid bool at line %d: %s=%s", line_no, key, value);
|
||||
continue;
|
||||
}
|
||||
state->cfg.auto_remove_games_with_dlc = bval;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strcasecmp(key, "image_ro") == 0 ||
|
||||
strcasecmp(key, "image_rw") == 0) {
|
||||
bool rule_read_only = (strcasecmp(key, "image_ro") == 0);
|
||||
@@ -1351,6 +1429,22 @@ static config_load_status_t load_runtime_config_state(runtime_config_state_t *st
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strcasecmp(key, "auto_remove_missing_delay_seconds") == 0 ||
|
||||
strcasecmp(key, "auto_remove_missing_delay_sec") == 0) {
|
||||
if (!parse_u32_ini(value, &u32) ||
|
||||
u32 < MIN_AUTO_REMOVE_MISSING_DELAY_SECONDS ||
|
||||
u32 > MAX_AUTO_REMOVE_MISSING_DELAY_SECONDS) {
|
||||
log_debug(" [CFG] invalid auto-remove delay at line %d: %s=%s "
|
||||
"(range: %u..%u)",
|
||||
line_no, key, value,
|
||||
(unsigned)MIN_AUTO_REMOVE_MISSING_DELAY_SECONDS,
|
||||
(unsigned)MAX_AUTO_REMOVE_MISSING_DELAY_SECONDS);
|
||||
continue;
|
||||
}
|
||||
state->cfg.auto_remove_missing_delay_seconds = u32;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strcasecmp(key, "kstuff_pause_delay_image_seconds") == 0 ||
|
||||
strcasecmp(key, "kstuff_pause_delay_image_sec") == 0) {
|
||||
if (!parse_u32_ini(value, &u32) || u32 > MAX_KSTUFF_PAUSE_DELAY_SECONDS) {
|
||||
@@ -1465,8 +1559,11 @@ static config_load_status_t load_runtime_config_state(runtime_config_state_t *st
|
||||
kstuff_delay_rule_count++;
|
||||
}
|
||||
|
||||
log_debug(" [CFG] loaded: debug=%d quiet=%d ro=%d force=%d "
|
||||
"app_install_all=%d scan_depth=%u "
|
||||
log_debug(" [CFG] loaded: debug=%d quiet=%d language=%s ro=%d force=%d "
|
||||
"persistent_image_mounts=%d app_install_all=%d "
|
||||
"auto_remove_missing_games=%d "
|
||||
"auto_remove_games_with_dlc=%d auto_remove_missing_delay_s=%u "
|
||||
"api=%s:%u scan_depth=%u "
|
||||
"legacy_recursive_scan_forced=%d backport_fakelib=%d "
|
||||
"global_fakelib=%d global_fakelib_priority=%s "
|
||||
"global_fakelib_path=%s global_fakelib_exclude=%u "
|
||||
@@ -1477,9 +1574,15 @@ static config_load_status_t load_runtime_config_state(runtime_config_state_t *st
|
||||
"scan_interval_s=%u stability_wait_s=%u scan_paths=%d image_rules=%d "
|
||||
"kstuff_no_pause=%d kstuff_delay_rules=%d",
|
||||
state->cfg.debug_enabled ? 1 : 0, state->cfg.quiet_mode ? 1 : 0,
|
||||
sm_l10n_language_name(state->cfg.language_id),
|
||||
state->cfg.mount_read_only ? 1 : 0,
|
||||
state->cfg.force_mount ? 1 : 0,
|
||||
state->cfg.persistent_image_mounts ? 1 : 0,
|
||||
state->cfg.app_install_all_enabled ? 1 : 0,
|
||||
state->cfg.auto_remove_missing_games ? 1 : 0,
|
||||
state->cfg.auto_remove_games_with_dlc ? 1 : 0,
|
||||
state->cfg.auto_remove_missing_delay_seconds,
|
||||
state->cfg.api_bind_address, state->cfg.api_port,
|
||||
state->cfg.scan_depth,
|
||||
state->cfg.legacy_recursive_scan_forced ? 1 : 0,
|
||||
state->cfg.backport_fakelib_enabled ? 1 : 0,
|
||||
|
||||
+1
-1
@@ -294,7 +294,7 @@ void sm_fakelib_game_on_exec(pid_t pid, const char *title_id) {
|
||||
}
|
||||
|
||||
if (has_game)
|
||||
notify_system_info("Game backported: %s", title_id);
|
||||
notify_system_info_l10n(SM_L10N_GAME_BACKPORTED, title_id);
|
||||
pthread_mutex_unlock(&g_fakelib_mutex);
|
||||
}
|
||||
|
||||
|
||||
+34
-11
@@ -11,6 +11,8 @@
|
||||
#include "sm_paths.h"
|
||||
|
||||
static bool unmount_controlled_mount_stack(const char *path);
|
||||
static bool unmount_controlled_mount_stack_impl(const char *path,
|
||||
bool diagnose_busy);
|
||||
|
||||
// --- FILESYSTEM ---
|
||||
bool is_installed(const char *title_id) {
|
||||
@@ -593,7 +595,8 @@ static bool inspect_title_stack(const char *title_id, const char *source_path,
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool unmount_top_controlled_layer(const char *path) {
|
||||
static bool unmount_top_controlled_layer_impl(const char *path,
|
||||
bool diagnose_busy) {
|
||||
struct statfs mount_st;
|
||||
if (!get_top_mount(path, &mount_st))
|
||||
return true;
|
||||
@@ -611,15 +614,21 @@ static bool unmount_top_controlled_layer(const char *path) {
|
||||
if (unmount(path, 0) == 0 || errno == ENOENT || errno == EINVAL)
|
||||
return true;
|
||||
int unmount_errno = errno;
|
||||
if (unmount_errno == EBUSY)
|
||||
if (unmount_errno == EBUSY && diagnose_busy)
|
||||
sm_mount_diag_log_busy(path);
|
||||
|
||||
log_debug(" [LINK] unmount deferred for %s: %s", path,
|
||||
strerror(unmount_errno));
|
||||
if (unmount_errno != EBUSY || diagnose_busy) {
|
||||
log_debug(" [LINK] unmount deferred for %s: %s", path,
|
||||
strerror(unmount_errno));
|
||||
}
|
||||
errno = unmount_errno;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool unmount_top_controlled_layer(const char *path) {
|
||||
return unmount_top_controlled_layer_impl(path, true);
|
||||
}
|
||||
|
||||
static bool title_stack_top_is_managed(const title_mount_state_t *state) {
|
||||
return state->top_is_our_nullfs || state->top_kind == TITLE_STACK_TOP_BACKPORT;
|
||||
}
|
||||
@@ -638,7 +647,8 @@ bool rollback_title_nullfs_mount(const char *title_id, const char *src_path) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool unmount_title_runtime_layers(const char *title_id) {
|
||||
static bool unmount_title_runtime_layers_impl(const char *title_id,
|
||||
bool diagnose_busy) {
|
||||
if (!title_id || title_id[0] == '\0')
|
||||
return false;
|
||||
|
||||
@@ -647,12 +657,20 @@ bool unmount_title_runtime_layers(const char *title_id) {
|
||||
if (written < 0 || (size_t)written >= sizeof(path))
|
||||
return false;
|
||||
|
||||
if (!unmount_controlled_mount_stack(path))
|
||||
if (!unmount_controlled_mount_stack_impl(path, diagnose_busy))
|
||||
return false;
|
||||
log_debug(" [LINK] runtime layers released: %s", title_id);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool unmount_title_runtime_layers(const char *title_id) {
|
||||
return unmount_title_runtime_layers_impl(title_id, true);
|
||||
}
|
||||
|
||||
bool unmount_title_runtime_layers_quiet(const char *title_id) {
|
||||
return unmount_title_runtime_layers_impl(title_id, false);
|
||||
}
|
||||
|
||||
static bool unmount_managed_title_entry(
|
||||
const char *title_id, const title_link_paths_t *paths, void *ctx) {
|
||||
bool *all_released = (bool *)ctx;
|
||||
@@ -758,17 +776,18 @@ bool mount_backport_overlay(const char *mount_point,
|
||||
int overlay_err = errno;
|
||||
log_debug(" [IMG] backport overlay failed: %s -> %s (%s)", backport_path,
|
||||
mount_point, strerror(overlay_err));
|
||||
notify_system("Backport overlay failed: %s\n%s\n0x%08X", title_id,
|
||||
backport_path, (uint32_t)overlay_err);
|
||||
notify_system_l10n(SM_L10N_BACKPORT_OVERLAY_FAILED, title_id, backport_path,
|
||||
(uint32_t)overlay_err);
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool unmount_controlled_mount_stack(const char *path) {
|
||||
static bool unmount_controlled_mount_stack_impl(const char *path,
|
||||
bool diagnose_busy) {
|
||||
for (int i = 0; i < MAX_LAYERED_UNMOUNT_ATTEMPTS * 4; i++) {
|
||||
struct statfs mount_st;
|
||||
if (!get_top_mount(path, &mount_st))
|
||||
return true;
|
||||
if (!unmount_top_controlled_layer(path))
|
||||
if (!unmount_top_controlled_layer_impl(path, diagnose_busy))
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -776,6 +795,10 @@ static bool unmount_controlled_mount_stack(const char *path) {
|
||||
return !get_top_mount(path, &mount_st);
|
||||
}
|
||||
|
||||
static bool unmount_controlled_mount_stack(const char *path) {
|
||||
return unmount_controlled_mount_stack_impl(path, true);
|
||||
}
|
||||
|
||||
static void unmount_mount_point_for_recovery(const char *path) {
|
||||
if (!unmount_controlled_mount_stack(path)) {
|
||||
log_debug(" [LINK] failed to reset mount stack for recovery: %s", path);
|
||||
@@ -920,7 +943,7 @@ int copy_file(const char *src, const char *dst) {
|
||||
return copy_param_json_rewrite(src, dst);
|
||||
}
|
||||
|
||||
char buf[8192];
|
||||
char buf[64 * 1024];
|
||||
FILE *fs = fopen(src, "rb");
|
||||
if (!fs)
|
||||
return -1;
|
||||
|
||||
+257
-40
@@ -2,12 +2,15 @@
|
||||
#include <pthread.h>
|
||||
|
||||
#include "sm_game_cache.h"
|
||||
#include "sm_appdb.h"
|
||||
#include "sm_config_mount.h"
|
||||
#include "sm_filesystem.h"
|
||||
#include "sm_image_cache.h"
|
||||
#include "sm_image_index.h"
|
||||
#include "sm_install_queue.h"
|
||||
#include "sm_limits.h"
|
||||
#include "sm_log.h"
|
||||
#include "sm_path_utils.h"
|
||||
#include "sm_time.h"
|
||||
#include "sm_title_state.h"
|
||||
|
||||
struct GameCache {
|
||||
@@ -15,6 +18,7 @@ struct GameCache {
|
||||
char title_id[MAX_TITLE_ID];
|
||||
char title_name[MAX_TITLE_NAME];
|
||||
char owning_scan_root[MAX_PATH];
|
||||
uint64_t missing_since_us;
|
||||
bool valid;
|
||||
};
|
||||
|
||||
@@ -22,15 +26,25 @@ static struct GameCache g_game_cache[MAX_PENDING];
|
||||
static pthread_mutex_t g_game_cache_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
static bool game_cache_source_exists(const struct GameCache *entry) {
|
||||
if (path_exists(entry->path))
|
||||
return true;
|
||||
if (!is_under_image_mount_base(entry->path))
|
||||
return entry->path[0] != '\0' && path_exists(entry->path);
|
||||
}
|
||||
|
||||
static bool title_is_protected_by_dlc(const char *title_id,
|
||||
bool remove_games_with_dlc) {
|
||||
if (remove_games_with_dlc)
|
||||
return false;
|
||||
|
||||
char image_path[MAX_PATH];
|
||||
return read_mount_image_link(entry->title_id, image_path,
|
||||
sizeof(image_path)) &&
|
||||
path_exists(image_path);
|
||||
char addcont_path[MAX_PATH];
|
||||
int written = snprintf(addcont_path, sizeof(addcont_path),
|
||||
"/user/addcont/%s", title_id);
|
||||
if (written <= 0 || (size_t)written >= sizeof(addcont_path))
|
||||
return true;
|
||||
|
||||
struct stat st;
|
||||
if (stat(addcont_path, &st) == 0)
|
||||
return S_ISDIR(st.st_mode);
|
||||
|
||||
return errno != ENOENT && errno != ENOTDIR;
|
||||
}
|
||||
|
||||
static bool resolve_game_cache_owning_scan_root(const char *path,
|
||||
@@ -71,6 +85,7 @@ static void write_game_cache_slot(struct GameCache *entry, const char *path,
|
||||
(void)strlcpy(entry->title_name, title_name, sizeof(entry->title_name));
|
||||
(void)strlcpy(entry->owning_scan_root, owning_scan_root,
|
||||
sizeof(entry->owning_scan_root));
|
||||
entry->missing_since_us = 0;
|
||||
entry->valid = true;
|
||||
}
|
||||
|
||||
@@ -81,6 +96,27 @@ static bool ensure_game_cache_owning_scan_root(struct GameCache *entry) {
|
||||
return resolve_game_cache_owning_scan_root(entry->path, entry->owning_scan_root);
|
||||
}
|
||||
|
||||
static void start_missing_timer(struct GameCache *entry, uint64_t now_us) {
|
||||
if (entry->missing_since_us != 0 || now_us == 0)
|
||||
return;
|
||||
|
||||
entry->missing_since_us = now_us;
|
||||
if (entry->path[0] != '\0') {
|
||||
log_debug(" [CACHE] source missing, auto-remove timer started: %s (%s)",
|
||||
entry->title_id, entry->path);
|
||||
} else {
|
||||
log_debug(" [CACHE] source missing, auto-remove timer started: %s",
|
||||
entry->title_id);
|
||||
}
|
||||
}
|
||||
|
||||
static bool missing_timer_expired(const struct GameCache *entry,
|
||||
uint64_t now_us, uint64_t delay_us) {
|
||||
return entry->valid && entry->missing_since_us != 0 &&
|
||||
now_us >= entry->missing_since_us &&
|
||||
now_us - entry->missing_since_us >= delay_us;
|
||||
}
|
||||
|
||||
static void clear_game_cache_slot(int index, const char *reason) {
|
||||
if (index < 0 || index >= MAX_PENDING || !g_game_cache[index].valid)
|
||||
return;
|
||||
@@ -129,37 +165,209 @@ void cache_game_entry(const char *path, const char *title_id,
|
||||
pthread_mutex_unlock(&g_game_cache_mutex);
|
||||
}
|
||||
|
||||
void prune_game_cache(void) {
|
||||
static void prune_game_cache_entries(const char *root) {
|
||||
const bool auto_remove = runtime_config()->auto_remove_missing_games;
|
||||
|
||||
pthread_mutex_lock(&g_game_cache_mutex);
|
||||
for (int k = 0; k < MAX_PENDING; k++) {
|
||||
if (!g_game_cache[k].valid)
|
||||
continue;
|
||||
if (game_cache_source_exists(&g_game_cache[k]))
|
||||
if (root) {
|
||||
const char *entry_root =
|
||||
ensure_game_cache_owning_scan_root(&g_game_cache[k])
|
||||
? g_game_cache[k].owning_scan_root
|
||||
: g_game_cache[k].path;
|
||||
if (!path_matches_root_or_child(entry_root, root))
|
||||
continue;
|
||||
}
|
||||
|
||||
struct GameCache *entry = &g_game_cache[k];
|
||||
if (game_cache_source_exists(entry)) {
|
||||
if (entry->missing_since_us != 0) {
|
||||
entry->missing_since_us = 0;
|
||||
log_debug(" [CACHE] source restored, auto-remove cancelled: %s (%s)",
|
||||
entry->title_id, entry->path);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (auto_remove)
|
||||
continue;
|
||||
clear_game_cache_slot(k, "source removed");
|
||||
}
|
||||
pthread_mutex_unlock(&g_game_cache_mutex);
|
||||
}
|
||||
|
||||
void prune_game_cache(void) { prune_game_cache_entries(NULL); }
|
||||
|
||||
void prune_game_cache_for_root(const char *root) {
|
||||
if (!root || root[0] == '\0') {
|
||||
prune_game_cache();
|
||||
return;
|
||||
}
|
||||
|
||||
prune_game_cache_entries(root);
|
||||
}
|
||||
|
||||
void note_game_cache_source_seen(const char *path, const char *title_id,
|
||||
const char *title_name) {
|
||||
if (!path || !title_id || title_id[0] == '\0')
|
||||
return;
|
||||
|
||||
pthread_mutex_lock(&g_game_cache_mutex);
|
||||
for (int k = 0; k < MAX_PENDING; k++) {
|
||||
if (!g_game_cache[k].valid || g_game_cache[k].missing_since_us == 0)
|
||||
continue;
|
||||
if (strcmp(g_game_cache[k].title_id, title_id) != 0)
|
||||
continue;
|
||||
|
||||
char owning_scan_root[MAX_PATH];
|
||||
(void)resolve_game_cache_owning_scan_root(path, owning_scan_root);
|
||||
write_game_cache_slot(&g_game_cache[k], path, title_id,
|
||||
title_name ? title_name : "", owning_scan_root);
|
||||
log_debug(" [CACHE] source restored, auto-remove cancelled: %s (%s)",
|
||||
title_id, path);
|
||||
break;
|
||||
}
|
||||
pthread_mutex_unlock(&g_game_cache_mutex);
|
||||
}
|
||||
|
||||
static void reconcile_missing_game_cache(
|
||||
const struct AppDbTitleList *auto_remove_titles, uint64_t now_us,
|
||||
bool remove_games_with_dlc) {
|
||||
pthread_mutex_lock(&g_game_cache_mutex);
|
||||
for (int k = 0; k < MAX_PENDING; ++k) {
|
||||
struct GameCache *entry = &g_game_cache[k];
|
||||
if (!entry->valid || game_cache_source_exists(entry) ||
|
||||
app_db_title_list_contains(auto_remove_titles, entry->title_id)) {
|
||||
continue;
|
||||
}
|
||||
clear_game_cache_slot(k, "source removed");
|
||||
}
|
||||
pthread_mutex_unlock(&g_game_cache_mutex);
|
||||
|
||||
bool cache_full_logged = false;
|
||||
for (int i = 0; i < auto_remove_titles->count; ++i) {
|
||||
const char *title_id = auto_remove_titles->ids[i];
|
||||
bool protected_by_dlc =
|
||||
title_is_protected_by_dlc(title_id, remove_games_with_dlc);
|
||||
bool image_source_exists =
|
||||
!protected_by_dlc && sm_image_index_has_source_for_title(title_id);
|
||||
|
||||
pthread_mutex_lock(&g_game_cache_mutex);
|
||||
int entry_index = -1;
|
||||
int free_index = -1;
|
||||
for (int k = 0; k < MAX_PENDING; ++k) {
|
||||
if (!g_game_cache[k].valid) {
|
||||
if (free_index < 0)
|
||||
free_index = k;
|
||||
continue;
|
||||
}
|
||||
if (strcmp(g_game_cache[k].title_id, title_id) == 0) {
|
||||
entry_index = k;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (protected_by_dlc) {
|
||||
if (entry_index >= 0) {
|
||||
struct GameCache *entry = &g_game_cache[entry_index];
|
||||
if (game_cache_source_exists(entry))
|
||||
entry->missing_since_us = 0;
|
||||
else
|
||||
clear_game_cache_slot(entry_index, NULL);
|
||||
}
|
||||
} else if (entry_index >= 0) {
|
||||
struct GameCache *entry = &g_game_cache[entry_index];
|
||||
if (game_cache_source_exists(entry) || image_source_exists) {
|
||||
if (entry->missing_since_us != 0) {
|
||||
log_debug(" [CACHE] source restored, auto-remove cancelled: %s",
|
||||
title_id);
|
||||
}
|
||||
if (entry->path[0] == '\0')
|
||||
clear_game_cache_slot(entry_index, NULL);
|
||||
else
|
||||
entry->missing_since_us = 0;
|
||||
} else {
|
||||
start_missing_timer(entry, now_us);
|
||||
}
|
||||
} else if (!image_source_exists && free_index >= 0) {
|
||||
struct GameCache *entry = &g_game_cache[free_index];
|
||||
write_game_cache_slot(entry, "", title_id, "", "");
|
||||
start_missing_timer(entry, now_us);
|
||||
} else if (!image_source_exists && !cache_full_logged) {
|
||||
log_debug(" [CACHE] auto-remove tracking full; remaining titles deferred");
|
||||
cache_full_logged = true;
|
||||
}
|
||||
pthread_mutex_unlock(&g_game_cache_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
void reconcile_missing_app_db_games(void) {
|
||||
const runtime_config_t *cfg = runtime_config();
|
||||
if (!cfg->auto_remove_missing_games)
|
||||
return;
|
||||
|
||||
struct AppDbTitleList auto_remove_titles = {0};
|
||||
if (!get_app_db_auto_remove_title_list(&auto_remove_titles)) {
|
||||
log_debug(" [REG] app.db auto-remove title list unavailable");
|
||||
return;
|
||||
}
|
||||
|
||||
const uint64_t now_us = monotonic_time_us();
|
||||
const uint64_t delay_us =
|
||||
(uint64_t)cfg->auto_remove_missing_delay_seconds * 1000000ull;
|
||||
reconcile_missing_game_cache(&auto_remove_titles, now_us,
|
||||
cfg->auto_remove_games_with_dlc);
|
||||
|
||||
for (;;) {
|
||||
char title_id[MAX_TITLE_ID];
|
||||
title_id[0] = '\0';
|
||||
|
||||
pthread_mutex_lock(&g_game_cache_mutex);
|
||||
for (int k = 0; k < MAX_PENDING; k++) {
|
||||
const struct GameCache *entry = &g_game_cache[k];
|
||||
if (!missing_timer_expired(entry, now_us, delay_us))
|
||||
continue;
|
||||
|
||||
(void)strlcpy(title_id, entry->title_id, sizeof(title_id));
|
||||
clear_game_cache_slot(k, NULL);
|
||||
break;
|
||||
}
|
||||
pthread_mutex_unlock(&g_game_cache_mutex);
|
||||
|
||||
if (title_id[0] == '\0')
|
||||
break;
|
||||
|
||||
if (!app_db_title_list_contains(&auto_remove_titles, title_id))
|
||||
continue;
|
||||
if (title_is_protected_by_dlc(title_id,
|
||||
cfg->auto_remove_games_with_dlc))
|
||||
continue;
|
||||
|
||||
int res = sceAppInstUtilAppUnInstall(title_id);
|
||||
if (res == 0) {
|
||||
sm_install_forget_pending_title(title_id);
|
||||
invalidate_app_db_title_cache();
|
||||
log_debug(" [REG] auto-remove requested: %s", title_id);
|
||||
continue;
|
||||
}
|
||||
|
||||
log_debug(" [REG] auto-remove failed (not retried): %s code=0x%08X",
|
||||
title_id, (uint32_t)res);
|
||||
}
|
||||
|
||||
free_app_db_title_list(&auto_remove_titles);
|
||||
}
|
||||
|
||||
void reset_missing_game_cache_timers(void) {
|
||||
pthread_mutex_lock(&g_game_cache_mutex);
|
||||
for (int k = 0; k < MAX_PENDING; k++) {
|
||||
if (!g_game_cache[k].valid)
|
||||
continue;
|
||||
const char *entry_root =
|
||||
ensure_game_cache_owning_scan_root(&g_game_cache[k])
|
||||
? g_game_cache[k].owning_scan_root
|
||||
: g_game_cache[k].path;
|
||||
if (!path_matches_root_or_child(entry_root, root))
|
||||
continue;
|
||||
if (game_cache_source_exists(&g_game_cache[k]))
|
||||
continue;
|
||||
clear_game_cache_slot(k, "source removed");
|
||||
if (g_game_cache[k].path[0] == '\0')
|
||||
clear_game_cache_slot(k, NULL);
|
||||
else
|
||||
g_game_cache[k].missing_since_us = 0;
|
||||
}
|
||||
pthread_mutex_unlock(&g_game_cache_mutex);
|
||||
}
|
||||
@@ -170,10 +378,10 @@ void for_each_cached_game_entry(const char *root, game_cache_iter_fn fn,
|
||||
return;
|
||||
|
||||
for (int k = 0; k < MAX_PENDING; k++) {
|
||||
sm_game_cache_snapshot_entry_t entry;
|
||||
struct GameCache entry;
|
||||
bool valid = false;
|
||||
pthread_mutex_lock(&g_game_cache_mutex);
|
||||
if (g_game_cache[k].valid) {
|
||||
if (g_game_cache[k].valid && g_game_cache[k].missing_since_us == 0) {
|
||||
bool has_owning_scan_root =
|
||||
ensure_game_cache_owning_scan_root(&g_game_cache[k]);
|
||||
const char *entry_root = has_owning_scan_root
|
||||
@@ -203,35 +411,44 @@ void for_each_cached_game_entry(const char *root, game_cache_iter_fn fn,
|
||||
}
|
||||
}
|
||||
|
||||
size_t sm_game_cache_snapshot(size_t offset,
|
||||
sm_game_cache_snapshot_entry_t *entries,
|
||||
size_t capacity, size_t *total_out) {
|
||||
size_t total = 0;
|
||||
size_t copied = 0;
|
||||
bool sm_game_cache_snapshot(sm_game_cache_snapshot_entry_t **entries_out,
|
||||
size_t *count_out) {
|
||||
if (!entries_out || !count_out)
|
||||
return false;
|
||||
*entries_out = NULL;
|
||||
*count_out = 0;
|
||||
|
||||
pthread_mutex_lock(&g_game_cache_mutex);
|
||||
size_t count = 0;
|
||||
for (int k = 0; k < MAX_PENDING; ++k) {
|
||||
if (!g_game_cache[k].valid)
|
||||
continue;
|
||||
bool has_root = ensure_game_cache_owning_scan_root(&g_game_cache[k]);
|
||||
if (total++ < offset || copied >= capacity || !entries)
|
||||
if (g_game_cache[k].valid && g_game_cache[k].missing_since_us == 0)
|
||||
count++;
|
||||
}
|
||||
|
||||
sm_game_cache_snapshot_entry_t *entries = NULL;
|
||||
if (count > 0) {
|
||||
entries = calloc(count, sizeof(*entries));
|
||||
if (!entries) {
|
||||
pthread_mutex_unlock(&g_game_cache_mutex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
size_t copied = 0;
|
||||
for (int k = 0; k < MAX_PENDING; ++k) {
|
||||
if (!g_game_cache[k].valid || g_game_cache[k].missing_since_us != 0)
|
||||
continue;
|
||||
sm_game_cache_snapshot_entry_t *entry = &entries[copied++];
|
||||
memset(entry, 0, sizeof(*entry));
|
||||
(void)strlcpy(entry->path, g_game_cache[k].path, sizeof(entry->path));
|
||||
(void)strlcpy(entry->title_id, g_game_cache[k].title_id,
|
||||
sizeof(entry->title_id));
|
||||
(void)strlcpy(entry->title_name, g_game_cache[k].title_name,
|
||||
sizeof(entry->title_name));
|
||||
if (has_root) {
|
||||
(void)strlcpy(entry->owning_scan_root,
|
||||
g_game_cache[k].owning_scan_root,
|
||||
sizeof(entry->owning_scan_root));
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&g_game_cache_mutex);
|
||||
if (total_out)
|
||||
*total_out = total;
|
||||
return copied;
|
||||
*entries_out = entries;
|
||||
*count_out = copied;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool find_cached_game(const char *path, const char *title_id,
|
||||
@@ -265,7 +482,7 @@ bool find_cached_game(const char *path, const char *title_id,
|
||||
void clear_cached_game(const char *path) {
|
||||
pthread_mutex_lock(&g_game_cache_mutex);
|
||||
for (int k = 0; k < MAX_PENDING; k++) {
|
||||
if (!g_game_cache[k].valid)
|
||||
if (!g_game_cache[k].valid || g_game_cache[k].missing_since_us != 0)
|
||||
continue;
|
||||
if (strcmp(g_game_cache[k].path, path) != 0)
|
||||
continue;
|
||||
|
||||
@@ -476,11 +476,11 @@ static void handle_game_exit(pid_t pid) {
|
||||
sm_kstuff_game_on_exit(pid);
|
||||
if (had_active_title) {
|
||||
if (sm_shellcore_service_note_game_exit(title_id)) {
|
||||
if (sm_shellcore_workspace_hook_active()) {
|
||||
log_debug(" [SHELLCORE] runtime release awaiting unmountWorkspace: %s",
|
||||
if (sm_shellcore_app_exit_hook_active()) {
|
||||
log_debug(" [SHELLCORE] runtime release awaiting onAppExit: %s",
|
||||
title_id);
|
||||
} else if (!sm_shellcore_release_title_runtime(title_id)) {
|
||||
log_debug(" [SHELLCORE] no workspace hook; runtime release deferred: %s",
|
||||
log_debug(" [SHELLCORE] no app-exit hook; runtime release deferred: %s",
|
||||
title_id);
|
||||
}
|
||||
}
|
||||
|
||||
+16
-21
@@ -568,7 +568,8 @@ static bool reject_mounted_image_io(const char *file_path,
|
||||
const char *devname,
|
||||
const char *mount_point, int io_err,
|
||||
const char *stage) {
|
||||
sm_error_set("IMG", io_err, file_path, "Mounted image is unreadable or damaged");
|
||||
sm_error_set_l10n("IMG", io_err, file_path,
|
||||
SM_L10N_IMAGE_UNREADABLE_DAMAGED);
|
||||
log_debug(" [IMG][%s] unreadable or damaged mount (%s -> %s, %s): %s",
|
||||
attach_backend_name(attach_backend), devname, mount_point, stage,
|
||||
strerror(io_err));
|
||||
@@ -633,9 +634,9 @@ static bool reuse_existing_image_mount(const char *file_path,
|
||||
}
|
||||
if (!cache_image_mount(file_path, mount_point, existing_unit,
|
||||
existing_backend)) {
|
||||
sm_error_set("IMG", ENOSPC, file_path,
|
||||
"Image cache full (%u entries), cannot track mount %s",
|
||||
(unsigned)MAX_IMAGE_MOUNTS, mount_point);
|
||||
sm_error_set_l10n("IMG", ENOSPC, file_path,
|
||||
SM_L10N_IMAGE_CACHE_FULL_TRACK,
|
||||
(unsigned)MAX_IMAGE_MOUNTS, mount_point);
|
||||
log_debug(" [IMG] image cache full, refusing unmanaged mount reuse: %s",
|
||||
mount_point);
|
||||
errno = ENOSPC;
|
||||
@@ -915,26 +916,20 @@ static bool validate_mounted_image(const char *file_path, image_fs_type_t fs_typ
|
||||
upsert_image_sector_size_autotune(get_filename_component(file_path),
|
||||
(uint32_t)fs_block_size,
|
||||
&tuned_sector_size);
|
||||
sm_error_set("IMG", EINVAL, file_path,
|
||||
"Filesystem cluster size (%llu) is smaller than device "
|
||||
"sector size (%u) for %s",
|
||||
(unsigned long long)fs_block_size, min_device_sector, devname);
|
||||
sm_error_set_l10n("IMG", EINVAL, file_path, SM_L10N_FS_CLUSTER_SMALL,
|
||||
(unsigned long long)fs_block_size, min_device_sector,
|
||||
devname);
|
||||
log_debug(" [IMG][%s] %s", attach_backend_name(attach_backend),
|
||||
sm_last_error()->message);
|
||||
if (autotuned) {
|
||||
log_debug(" [CFG] image sector autotuned: %s=%u",
|
||||
get_filename_component(file_path), tuned_sector_size);
|
||||
notify_system("Image mount rejected:\n%s\nCluster size is too small "
|
||||
"(%llu < %u).\nSaved image_sector override: %u.\nTry "
|
||||
"mounting again.",
|
||||
file_path, (unsigned long long)fs_block_size,
|
||||
min_device_sector, tuned_sector_size);
|
||||
notify_system_l10n(SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED, file_path,
|
||||
(unsigned long long)fs_block_size, min_device_sector,
|
||||
tuned_sector_size);
|
||||
} else {
|
||||
notify_system("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.",
|
||||
file_path, (unsigned long long)fs_block_size,
|
||||
min_device_sector);
|
||||
notify_system_l10n(SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG, file_path,
|
||||
(unsigned long long)fs_block_size, min_device_sector);
|
||||
}
|
||||
sm_error_mark_notified();
|
||||
(void)unmount_runtime_image(file_path, unit_id, attach_backend);
|
||||
@@ -1048,9 +1043,9 @@ bool mount_image(const char *file_path, image_fs_type_t fs_type) {
|
||||
log_fs_stats("IMG", mount_point, image_fs_name(fs_type));
|
||||
|
||||
if (!cache_image_mount(file_path, mount_point, unit_id, attach_backend)) {
|
||||
sm_error_set("IMG", ENOSPC, file_path,
|
||||
"Image cache full (%u entries), rolling back mount",
|
||||
(unsigned)MAX_IMAGE_MOUNTS);
|
||||
sm_error_set_l10n("IMG", ENOSPC, file_path,
|
||||
SM_L10N_IMAGE_CACHE_FULL_ROLLBACK,
|
||||
(unsigned)MAX_IMAGE_MOUNTS);
|
||||
log_debug(" [IMG] image cache full, rolling back mount: %s -> %s",
|
||||
file_path, mount_point);
|
||||
(void)unmount_runtime_image(file_path, unit_id, attach_backend);
|
||||
|
||||
+65
-19
@@ -218,18 +218,49 @@ static bool cached_titles_ready(int entry_index,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool sm_image_index_needs_scan(const char *path, const struct stat *st,
|
||||
const struct AppDbTitleList *app_db_titles,
|
||||
bool app_db_titles_ready) {
|
||||
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) {
|
||||
if (!path || !st || !visitor)
|
||||
return false;
|
||||
|
||||
pthread_mutex_lock(&g_image_index_mutex);
|
||||
load_index();
|
||||
int entry_index = find_entry(path);
|
||||
bool needs_scan = entry_index < 0 || !g_image_index[entry_index].complete ||
|
||||
!stamp_matches(&g_image_index[entry_index], st) ||
|
||||
!cached_titles_ready(entry_index, app_db_titles,
|
||||
app_db_titles_ready);
|
||||
bool ready = entry_index >= 0 && g_image_index[entry_index].complete &&
|
||||
stamp_matches(&g_image_index[entry_index], st) &&
|
||||
cached_titles_ready(entry_index, app_db_titles,
|
||||
app_db_titles_ready);
|
||||
if (ready) {
|
||||
for (int i = 0; i < MAX_IMAGE_TITLES; ++i) {
|
||||
const image_index_title_t *title = &g_image_titles[i];
|
||||
if (title->valid && title->image_index == (uint16_t)entry_index)
|
||||
visitor(title->title_id);
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&g_image_index_mutex);
|
||||
return needs_scan;
|
||||
return ready;
|
||||
}
|
||||
|
||||
bool sm_image_index_has_source_for_title(const char *title_id) {
|
||||
if (!title_id || title_id[0] == '\0')
|
||||
return false;
|
||||
|
||||
bool found = false;
|
||||
pthread_mutex_lock(&g_image_index_mutex);
|
||||
load_index();
|
||||
for (int i = 0; i < MAX_IMAGE_TITLES; ++i) {
|
||||
const image_index_title_t *title = &g_image_titles[i];
|
||||
if (!title->valid || strcmp(title->title_id, title_id) != 0)
|
||||
continue;
|
||||
|
||||
const image_index_entry_t *entry = &g_image_index[title->image_index];
|
||||
found = entry->valid && path_exists(entry->path);
|
||||
break;
|
||||
}
|
||||
pthread_mutex_unlock(&g_image_index_mutex);
|
||||
return found;
|
||||
}
|
||||
|
||||
void sm_image_index_begin_scan(const char *path, const struct stat *st) {
|
||||
@@ -337,21 +368,36 @@ void sm_image_index_prune(void) {
|
||||
pthread_mutex_unlock(&g_image_index_mutex);
|
||||
}
|
||||
|
||||
size_t sm_image_index_snapshot(size_t offset,
|
||||
sm_image_index_snapshot_entry_t *entries,
|
||||
size_t capacity, size_t *total_out) {
|
||||
size_t total = 0;
|
||||
size_t copied = 0;
|
||||
bool sm_image_index_snapshot(sm_image_index_snapshot_entry_t **entries_out,
|
||||
size_t *count_out) {
|
||||
if (!entries_out || !count_out)
|
||||
return false;
|
||||
*entries_out = NULL;
|
||||
*count_out = 0;
|
||||
|
||||
pthread_mutex_lock(&g_image_index_mutex);
|
||||
load_index();
|
||||
size_t count = 0;
|
||||
for (int i = 0; i < MAX_IMAGE_MOUNTS; ++i) {
|
||||
if (g_image_index[i].valid)
|
||||
count++;
|
||||
}
|
||||
|
||||
sm_image_index_snapshot_entry_t *entries = NULL;
|
||||
if (count > 0) {
|
||||
entries = calloc(count, sizeof(*entries));
|
||||
if (!entries) {
|
||||
pthread_mutex_unlock(&g_image_index_mutex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
size_t copied = 0;
|
||||
for (int i = 0; i < MAX_IMAGE_MOUNTS; ++i) {
|
||||
const image_index_entry_t *source = &g_image_index[i];
|
||||
if (!source->valid)
|
||||
continue;
|
||||
if (total++ < offset || copied >= capacity || !entries)
|
||||
continue;
|
||||
sm_image_index_snapshot_entry_t *entry = &entries[copied++];
|
||||
memset(entry, 0, sizeof(*entry));
|
||||
(void)strlcpy(entry->path, source->path, sizeof(entry->path));
|
||||
entry->size = source->size;
|
||||
entry->mtime_sec = source->mtime_sec;
|
||||
@@ -359,7 +405,7 @@ size_t sm_image_index_snapshot(size_t offset,
|
||||
entry->complete = source->complete != 0;
|
||||
}
|
||||
pthread_mutex_unlock(&g_image_index_mutex);
|
||||
if (total_out)
|
||||
*total_out = total;
|
||||
return copied;
|
||||
*entries_out = entries;
|
||||
*count_out = copied;
|
||||
return true;
|
||||
}
|
||||
+19
-20
@@ -160,6 +160,13 @@ static bool update_trophy_metadata(const char *title_id,
|
||||
return ok;
|
||||
}
|
||||
|
||||
static void update_registered_title_snd0info(const char *title_id) {
|
||||
sceKernelUsleep(200000);
|
||||
int snd0_updates = update_snd0info(title_id);
|
||||
if (snd0_updates >= 0)
|
||||
log_debug(" [DB] snd0info updated rows=%d", snd0_updates);
|
||||
}
|
||||
|
||||
// --- Install/Remount Action ---
|
||||
static bool mount_and_install(const char *src_path, const char *title_id,
|
||||
const char *title_name, bool is_remount,
|
||||
@@ -328,8 +335,8 @@ static bool mount_and_install(const char *src_path, const char *title_id,
|
||||
if (sm_shellcore_install_bridge_enabled()) {
|
||||
if (!sm_shellcore_install_title_dir(title_id, APP_BASE "/", &res)) {
|
||||
log_debug(" [REG] internal AppInstallTitleDir bridge unavailable");
|
||||
notify_system("Register failed: %s (%s)\nTitleDir bridge unavailable",
|
||||
title_name, title_id);
|
||||
notify_system_l10n(SM_L10N_REGISTER_BRIDGE_UNAVAILABLE, title_name,
|
||||
title_id);
|
||||
return false;
|
||||
}
|
||||
mark_register_attempted(title_id);
|
||||
@@ -338,39 +345,31 @@ static bool mount_and_install(const char *src_path, const char *title_id,
|
||||
resolve_app_install_title_dir();
|
||||
if (!app_install_title_dir_fn) {
|
||||
log_debug(" [REG] sceAppInstUtilAppInstallTitleDir unavailable");
|
||||
notify_system("Register failed: %s (%s)\nAppInstallTitleDir unavailable",
|
||||
title_name, title_id);
|
||||
notify_system_l10n(SM_L10N_REGISTER_FAILED_UNAVAILABLE, title_name,
|
||||
title_id);
|
||||
return false;
|
||||
}
|
||||
mark_register_attempted(title_id);
|
||||
res = app_install_title_dir_fn(title_id, APP_BASE "/", NULL);
|
||||
}
|
||||
sceKernelUsleep(200000);
|
||||
|
||||
if (res == 0) {
|
||||
invalidate_app_db_title_cache();
|
||||
clear_register_attempts(title_id);
|
||||
log_debug(" [REG] Installed NEW!");
|
||||
notify_game_installed_rich(title_id);
|
||||
if (has_src_snd0) {
|
||||
int snd0_updates = update_snd0info(title_id);
|
||||
if (snd0_updates >= 0)
|
||||
log_debug(" [DB] snd0info updated rows=%d", snd0_updates);
|
||||
}
|
||||
if (has_src_snd0)
|
||||
update_registered_title_snd0info(title_id);
|
||||
} else if ((uint32_t)res == 0x80990002u) {
|
||||
invalidate_app_db_title_cache();
|
||||
clear_register_attempts(title_id);
|
||||
log_debug(" [REG] Restored.");
|
||||
if (has_src_snd0) {
|
||||
int snd0_updates = update_snd0info(title_id);
|
||||
if (snd0_updates >= 0)
|
||||
log_debug(" [DB] snd0info updated rows=%d", snd0_updates);
|
||||
}
|
||||
if (has_src_snd0)
|
||||
update_registered_title_snd0info(title_id);
|
||||
// Silent on restore/remount to avoid spam
|
||||
} else {
|
||||
log_debug(" [REG] FAIL: 0x%x", res);
|
||||
notify_system("Register failed: %s (%s)\ncode=0x%08X", title_name, title_id,
|
||||
(uint32_t)res);
|
||||
notify_system_l10n(SM_L10N_REGISTER_FAILED_CODE, title_name, title_id,
|
||||
(uint32_t)res);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -398,8 +397,8 @@ void process_scan_candidates(const scan_candidate_t *candidates,
|
||||
} else {
|
||||
log_debug(" [ACTION] Installing: %s (%s)", c->title_name, c->title_id);
|
||||
if (!use_app_install_all) {
|
||||
notify_system_info("Installing: %s (%s)...", c->title_name,
|
||||
c->title_id);
|
||||
notify_system_info_l10n(SM_L10N_INSTALLING, c->title_name,
|
||||
c->title_id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -244,8 +244,8 @@ static void finalize_pending_install_timeout(pending_install_entry_t *entry) {
|
||||
|
||||
log_debug(" [REG] Install timeout after 5 minutes: %s (%s)",
|
||||
entry->title_name, entry->title_id);
|
||||
notify_system("Install failed: %s (%s)\nTimed out after 5 minutes",
|
||||
entry->title_name, entry->title_id);
|
||||
notify_system_l10n(SM_L10N_INSTALL_FAILED_TIMEOUT, entry->title_name,
|
||||
entry->title_id);
|
||||
(void)note_pending_install_failure(entry);
|
||||
clear_pending_install_entry(entry);
|
||||
}
|
||||
@@ -372,7 +372,8 @@ static void notify_queued_install_batch(void) {
|
||||
|
||||
char message[3075];
|
||||
int shown_count = 0;
|
||||
snprintf(message, sizeof(message), "Batch install queued (%d):", queued_count);
|
||||
snprintf(message, sizeof(message), sm_l10n_get(SM_L10N_BATCH_INSTALL_QUEUED),
|
||||
queued_count);
|
||||
|
||||
for (int i = 0; i < MAX_PENDING; i++) {
|
||||
const pending_install_entry_t *entry = &g_pending_installs[i];
|
||||
@@ -385,7 +386,8 @@ static void notify_queued_install_batch(void) {
|
||||
if (shown_count < queued_count) {
|
||||
size_t used = strlen(message);
|
||||
if (used < sizeof(message)) {
|
||||
(void)snprintf(message + used, sizeof(message) - used, "\n... and %d more",
|
||||
(void)snprintf(message + used, sizeof(message) - used,
|
||||
sm_l10n_get(SM_L10N_BATCH_INSTALL_AND_MORE),
|
||||
queued_count - shown_count);
|
||||
}
|
||||
}
|
||||
@@ -436,8 +438,8 @@ bool sm_install_submit_queued(void) {
|
||||
if (res != 0) {
|
||||
log_debug(" [REG] Batch install request failed: 0x%x", res);
|
||||
if (!g_queued_install_submit_failure_notified) {
|
||||
notify_system("Batch install failed for %d app(s).\ncode=0x%08X",
|
||||
queued_count, (uint32_t)res);
|
||||
notify_system_l10n(SM_L10N_BATCH_INSTALL_FAILED, queued_count,
|
||||
(uint32_t)res);
|
||||
g_queued_install_submit_failure_notified = true;
|
||||
}
|
||||
schedule_queued_install_submit_retry(now_us);
|
||||
|
||||
+2
-2
@@ -708,8 +708,8 @@ static bool apply_kstuff_enabled_state(bool enabled, bool notify_user,
|
||||
if (fully_applied) {
|
||||
log_debug(" [KSTUFF] %s both sysentvecs", enabled ? "enabled" : "disabled");
|
||||
if (notify_user) {
|
||||
notify_system_info(enabled ? "KStuff active again"
|
||||
: "KStuff paused while the game is running");
|
||||
notify_system_info_l10n(enabled ? SM_L10N_KSTUFF_ACTIVE
|
||||
: SM_L10N_KSTUFF_PAUSED);
|
||||
}
|
||||
} else {
|
||||
log_debug(" [KSTUFF] %s request incomplete (ps5=%s ps4=%s)",
|
||||
|
||||
+172
@@ -0,0 +1,172 @@
|
||||
#include "sm_l10n.h"
|
||||
|
||||
#include "sm_config_mount.h"
|
||||
#include "sm_log.h"
|
||||
#include "sm_types.h"
|
||||
|
||||
#include <stdatomic.h>
|
||||
|
||||
#define SCE_SYSTEM_SERVICE_PARAM_ID_LANG 1
|
||||
#define SM_LANGUAGE_EN_US 1
|
||||
#define SM_LANGUAGE_UNINITIALIZED (-2)
|
||||
|
||||
#include "lang/ar_sa.inc"
|
||||
#include "lang/cs_cz.inc"
|
||||
#include "lang/da_dk.inc"
|
||||
#include "lang/de_de.inc"
|
||||
#include "lang/el_gr.inc"
|
||||
#include "lang/en_gb.inc"
|
||||
#include "lang/en_us.inc"
|
||||
#include "lang/es_es.inc"
|
||||
#include "lang/es_mx.inc"
|
||||
#include "lang/fi_fi.inc"
|
||||
#include "lang/fr_ca.inc"
|
||||
#include "lang/fr_fr.inc"
|
||||
#include "lang/hu_hu.inc"
|
||||
#include "lang/id_id.inc"
|
||||
#include "lang/it_it.inc"
|
||||
#include "lang/ja_jp.inc"
|
||||
#include "lang/ko_kr.inc"
|
||||
#include "lang/nl_nl.inc"
|
||||
#include "lang/no_no.inc"
|
||||
#include "lang/pl_pl.inc"
|
||||
#include "lang/pt_br.inc"
|
||||
#include "lang/pt_pt.inc"
|
||||
#include "lang/ro_ro.inc"
|
||||
#include "lang/ru_ru.inc"
|
||||
#include "lang/sv_se.inc"
|
||||
#include "lang/th_th.inc"
|
||||
#include "lang/tr_tr.inc"
|
||||
#include "lang/vi_vn.inc"
|
||||
#include "lang/zh_cn.inc"
|
||||
#include "lang/zh_tw.inc"
|
||||
|
||||
typedef struct {
|
||||
const char *country;
|
||||
const char *lang;
|
||||
const char *locale;
|
||||
const char *const *catalog;
|
||||
} sm_region_t;
|
||||
|
||||
static const sm_region_t g_regions[] = {
|
||||
{"jp", "ja", "ja-JP", g_ja_jp}, {"us", "en", "en-US", g_en_us},
|
||||
{"fr", "fr", "fr-FR", g_fr_fr}, {"es", "es", "es-ES", g_es_es},
|
||||
{"de", "de", "de-DE", g_de_de}, {"it", "it", "it-IT", g_it_it},
|
||||
{"nl", "nl", "nl-NL", g_nl_nl}, {"pt", "pt", "pt-PT", g_pt_pt},
|
||||
{"ru", "ru", "ru-RU", g_ru_ru}, {"kr", "ko", "ko-KR", g_ko_kr},
|
||||
{"tw", "zh", "zh-TW", g_zh_tw}, {"cn", "zh", "zh-CN", g_zh_cn},
|
||||
{"fi", "fi", "fi-FI", g_fi_fi}, {"se", "sv", "sv-SE", g_sv_se},
|
||||
{"dk", "da", "da-DK", g_da_dk}, {"no", "no", "no-NO", g_no_no},
|
||||
{"pl", "pl", "pl-PL", g_pl_pl}, {"br", "pt", "pt-BR", g_pt_br},
|
||||
{"gb", "en", "en-GB", g_en_gb}, {"tr", "tr", "tr-TR", g_tr_tr},
|
||||
{"mx", "es", "es-MX", g_es_mx}, {"sa", "ar", "ar-SA", g_ar_sa},
|
||||
{"ca", "fr", "fr-CA", g_fr_ca}, {"cz", "cs", "cs-CZ", g_cs_cz},
|
||||
{"hu", "hu", "hu-HU", g_hu_hu}, {"gr", "el", "el-GR", g_el_gr},
|
||||
{"ro", "ro", "ro-RO", g_ro_ro}, {"th", "th", "th-TH", g_th_th},
|
||||
{"vn", "vi", "vi-VN", g_vi_vn}, {"id", "id", "id-ID", g_id_id},
|
||||
};
|
||||
|
||||
#define SM_REGION_COUNT (sizeof(g_regions) / sizeof(g_regions[0]))
|
||||
|
||||
_Static_assert(SM_REGION_COUNT == 30, "unexpected SCE language count");
|
||||
|
||||
static atomic_int g_active_lang = ATOMIC_VAR_INIT(SM_LANGUAGE_UNINITIALIZED);
|
||||
|
||||
static bool is_valid_language_id(int32_t language_id) {
|
||||
return language_id >= 0 && language_id < (int32_t)SM_REGION_COUNT;
|
||||
}
|
||||
|
||||
static bool language_code_matches(const char *value, const char *code) {
|
||||
char normalized[16];
|
||||
size_t len = strlen(code);
|
||||
if (len >= sizeof(normalized))
|
||||
return false;
|
||||
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
char ch = code[i];
|
||||
normalized[i] = (ch == '-') ? '_' : ch;
|
||||
}
|
||||
normalized[len] = '\0';
|
||||
|
||||
return strcasecmp(value, code) == 0 || strcasecmp(value, normalized) == 0;
|
||||
}
|
||||
|
||||
bool sm_l10n_parse_language_id(const char *value, int32_t *language_id_out) {
|
||||
if (!value || !language_id_out)
|
||||
return false;
|
||||
|
||||
if (strcasecmp(value, "auto") == 0 || strcasecmp(value, "system") == 0 ||
|
||||
strcasecmp(value, "default") == 0) {
|
||||
*language_id_out = SM_LANGUAGE_AUTO;
|
||||
return true;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < SM_REGION_COUNT; ++i) {
|
||||
const sm_region_t *region = &g_regions[i];
|
||||
if (language_code_matches(value, region->locale) ||
|
||||
strcasecmp(value, region->country) == 0 ||
|
||||
strcasecmp(value, region->lang) == 0) {
|
||||
*language_id_out = (int32_t)i;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *sm_l10n_language_name(int32_t language_id) {
|
||||
if (language_id == SM_LANGUAGE_AUTO)
|
||||
return "auto";
|
||||
if (!is_valid_language_id(language_id))
|
||||
return "invalid";
|
||||
return g_regions[language_id].locale;
|
||||
}
|
||||
|
||||
void sm_l10n_init(void) {
|
||||
int32_t sys_lang = -1;
|
||||
const runtime_config_t *cfg = runtime_config();
|
||||
int32_t active_lang = cfg->language_id;
|
||||
bool auto_language = (active_lang == SM_LANGUAGE_AUTO);
|
||||
|
||||
if (auto_language &&
|
||||
sceSystemServiceParamGetInt(SCE_SYSTEM_SERVICE_PARAM_ID_LANG,
|
||||
&sys_lang) == 0 &&
|
||||
is_valid_language_id(sys_lang)) {
|
||||
active_lang = sys_lang;
|
||||
} else if (auto_language) {
|
||||
active_lang = SM_LANGUAGE_EN_US;
|
||||
}
|
||||
|
||||
if (!is_valid_language_id(active_lang))
|
||||
active_lang = SM_LANGUAGE_EN_US;
|
||||
|
||||
int previous_lang = atomic_load_explicit(&g_active_lang, memory_order_relaxed);
|
||||
atomic_store_explicit(&g_active_lang, active_lang, memory_order_release);
|
||||
|
||||
if (previous_lang != active_lang) {
|
||||
const sm_region_t *region = &g_regions[active_lang];
|
||||
if (auto_language) {
|
||||
log_debug(" [L10N] language=auto active=%d country=%s lang=%s locale=%s",
|
||||
active_lang, region->country, region->lang, region->locale);
|
||||
} else {
|
||||
log_debug(" [L10N] language=config active=%d country=%s lang=%s locale=%s",
|
||||
active_lang, region->country, region->lang, region->locale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const char *sm_l10n_get(sm_l10n_key_t key) {
|
||||
if (key < 0 || key >= SM_L10N_COUNT)
|
||||
return "";
|
||||
|
||||
int active_lang = atomic_load_explicit(&g_active_lang, memory_order_acquire);
|
||||
if (!is_valid_language_id(active_lang)) {
|
||||
sm_l10n_init();
|
||||
active_lang = atomic_load_explicit(&g_active_lang, memory_order_acquire);
|
||||
}
|
||||
|
||||
const char *const *catalog = g_regions[active_lang].catalog;
|
||||
if (catalog[key])
|
||||
return catalog[key];
|
||||
return g_en_us[key] ? g_en_us[key] : "";
|
||||
}
|
||||
+56
-17
@@ -135,6 +135,7 @@ static bool build_notification_metadata(char *created_at,
|
||||
static bool send_rich_notification(const char *message) {
|
||||
char escaped_message[4096];
|
||||
char escaped_version[128];
|
||||
char escaped_action[256];
|
||||
char payload[8192];
|
||||
char created_at[32];
|
||||
char notification_id[32];
|
||||
@@ -151,9 +152,12 @@ static bool send_rich_notification(const char *message) {
|
||||
|
||||
escaped_message[0] = '\0';
|
||||
escaped_version[0] = '\0';
|
||||
escaped_action[0] = '\0';
|
||||
append_json_escaped(escaped_message, sizeof(escaped_message), message);
|
||||
append_json_escaped(escaped_version, sizeof(escaped_version),
|
||||
SHADOWMOUNT_VERSION);
|
||||
append_json_escaped(escaped_action, sizeof(escaped_action),
|
||||
sm_l10n_get(SM_L10N_ACTION_DEBUG_SETTINGS));
|
||||
if (!build_notification_metadata(created_at, notification_id,
|
||||
sizeof(notification_id))) {
|
||||
return false;
|
||||
@@ -186,7 +190,7 @@ static bool send_rich_notification(const char *message) {
|
||||
" },\n"
|
||||
" \"actions\": [\n"
|
||||
" {\n"
|
||||
" \"actionName\": \"Go to Debug Settings\",\n"
|
||||
" \"actionName\": \"%s\",\n"
|
||||
" \"actionType\": \"DeepLink\",\n"
|
||||
" \"defaultFocus\": true,\n"
|
||||
" \"parameters\": {\n"
|
||||
@@ -214,8 +218,8 @@ static bool send_rich_notification(const char *message) {
|
||||
" \"createdDateTime\": \"%s\",\n"
|
||||
" \"localNotificationId\": \"%s\"\n"
|
||||
"}",
|
||||
escaped_message, escaped_version, escaped_message, created_at,
|
||||
notification_id);
|
||||
escaped_message, escaped_version, escaped_action, escaped_message,
|
||||
created_at, notification_id);
|
||||
if (len < 0 || (size_t)len >= sizeof(payload))
|
||||
return false;
|
||||
|
||||
@@ -248,6 +252,10 @@ static void notify_system_plain_v(const char *fmt, va_list args) {
|
||||
notify_system_plain_message(message);
|
||||
}
|
||||
|
||||
static void notify_system_plain_l10n_v(sm_l10n_key_t key, va_list args) {
|
||||
notify_system_plain_v(sm_l10n_get(key), args);
|
||||
}
|
||||
|
||||
static FILE *ensure_log_file_open_locked(void) {
|
||||
if (g_log_file)
|
||||
return g_log_file;
|
||||
@@ -311,15 +319,15 @@ void sm_log_shutdown(void) {
|
||||
pthread_mutex_unlock(&g_log_mutex);
|
||||
}
|
||||
|
||||
void notify_system_rich(bool allow_in_quiet_mode, const char *fmt, ...) {
|
||||
void notify_system_rich_l10n(bool allow_in_quiet_mode, sm_l10n_key_t key, ...) {
|
||||
char message[3075];
|
||||
va_list args;
|
||||
|
||||
if (runtime_config()->quiet_mode && !allow_in_quiet_mode)
|
||||
return;
|
||||
|
||||
va_start(args, fmt);
|
||||
vsnprintf(message, sizeof(message), fmt, args);
|
||||
va_start(args, key);
|
||||
vsnprintf(message, sizeof(message), sm_l10n_get(key), args);
|
||||
va_end(args);
|
||||
|
||||
if (!send_rich_notification(message)) {
|
||||
@@ -332,8 +340,10 @@ void notify_system_rich(bool allow_in_quiet_mode, const char *fmt, ...) {
|
||||
|
||||
void notify_game_installed_rich(const char *title_id) {
|
||||
if (!send_game_installed_rich_notification(title_id)) {
|
||||
notify_system_info("Installed: %s",
|
||||
(title_id && title_id[0] != '\0') ? title_id : "Unknown");
|
||||
notify_system_info_l10n(
|
||||
SM_L10N_INSTALLED, (title_id && title_id[0] != '\0')
|
||||
? title_id
|
||||
: sm_l10n_get(SM_L10N_UNKNOWN));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -350,12 +360,22 @@ void notify_system_info(const char *fmt, ...) {
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void notify_system_info_l10n(sm_l10n_key_t key, ...) {
|
||||
if (runtime_config()->quiet_mode)
|
||||
return;
|
||||
|
||||
va_list args;
|
||||
va_start(args, key);
|
||||
notify_system_plain_l10n_v(key, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void sm_error_clear(void) {
|
||||
memset(&g_last_error, 0, sizeof(g_last_error));
|
||||
}
|
||||
|
||||
void sm_error_set(const char *subsystem, int code, const char *path,
|
||||
const char *fmt, ...) {
|
||||
static void sm_error_set_v(const char *subsystem, int code, const char *path,
|
||||
const char *fmt, va_list args) {
|
||||
memset(&g_last_error, 0, sizeof(g_last_error));
|
||||
g_last_error.valid = true;
|
||||
g_last_error.code = code;
|
||||
@@ -364,12 +384,24 @@ void sm_error_set(const char *subsystem, int code, const char *path,
|
||||
sizeof(g_last_error.subsystem));
|
||||
if (path && path[0] != '\0')
|
||||
(void)strlcpy(g_last_error.path, path, sizeof(g_last_error.path));
|
||||
if (fmt && fmt[0] != '\0') {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
if (fmt && fmt[0] != '\0')
|
||||
vsnprintf(g_last_error.message, sizeof(g_last_error.message), fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
}
|
||||
|
||||
void sm_error_set(const char *subsystem, int code, const char *path,
|
||||
const char *fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
sm_error_set_v(subsystem, code, path, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void sm_error_set_l10n(const char *subsystem, int code, const char *path,
|
||||
sm_l10n_key_t key, ...) {
|
||||
va_list args;
|
||||
va_start(args, key);
|
||||
sm_error_set_v(subsystem, code, path, sm_l10n_get(key), args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
const sm_error_t *sm_last_error(void) {
|
||||
@@ -391,6 +423,13 @@ void notify_system(const char *fmt, ...) {
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void notify_system_l10n(sm_l10n_key_t key, ...) {
|
||||
va_list args;
|
||||
va_start(args, key);
|
||||
notify_system_plain_l10n_v(key, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void notify_image_mount_failed(const char *path, int mount_err) {
|
||||
if (g_last_error.valid && g_last_error.message[0] != '\0') {
|
||||
const char *error_path =
|
||||
@@ -400,6 +439,6 @@ void notify_image_mount_failed(const char *path, int mount_err) {
|
||||
return;
|
||||
}
|
||||
|
||||
notify_system("Image mount failed: 0x%08X (%s)\n%s", (uint32_t)mount_err,
|
||||
strerror(mount_err), path);
|
||||
notify_system_l10n(SM_L10N_IMAGE_MOUNT_FAILED, (uint32_t)mount_err,
|
||||
strerror(mount_err), path);
|
||||
}
|
||||
+222
-42
@@ -25,6 +25,10 @@ int mdbg_call(void *cmd, void *req, void *res);
|
||||
#define MDBG_AUTOTUNE_WINDOW_US (300ull * 1000000ull)
|
||||
#define MDBG_LOG_LINE_BUFFER_SIZE 512u
|
||||
#define MDBG_RTLD_ERROR_PREFIX_SIZE 32u
|
||||
#define MDBG_FATAL_ERROR_BUFFER_SIZE 256u
|
||||
#define MDBG_FATAL_EXCEPTION_PREFIX "# exception: "
|
||||
#define MDBG_FATAL_THREAD_NAME_PREFIX "# thread name: "
|
||||
#define MDBG_FATAL_PROCESS_ID_PREFIX "# proc ID: "
|
||||
#ifndef MDBG_USE_PRIVATE_LOG_TEXT
|
||||
#define MDBG_USE_PRIVATE_LOG_TEXT 0
|
||||
#endif
|
||||
@@ -56,6 +60,12 @@ typedef struct {
|
||||
uint64_t reserved[2];
|
||||
} mdbg_res_t;
|
||||
|
||||
typedef enum {
|
||||
MDBG_FAILURE_GENERIC = 0,
|
||||
MDBG_FAILURE_RTLD,
|
||||
MDBG_FAILURE_FATAL_SIGNAL,
|
||||
} mdbg_failure_kind_t;
|
||||
|
||||
typedef struct {
|
||||
bool active;
|
||||
bool log_monitoring_active;
|
||||
@@ -75,9 +85,13 @@ typedef struct {
|
||||
size_t log_buffer_size;
|
||||
size_t log_snapshot_length;
|
||||
size_t log_line_length;
|
||||
size_t fatal_error_length;
|
||||
pid_t fatal_error_pid;
|
||||
bool fatal_error_active;
|
||||
char *log_snapshot;
|
||||
char *log_storage;
|
||||
char log_line[MDBG_LOG_LINE_BUFFER_SIZE];
|
||||
char fatal_error[MDBG_FATAL_ERROR_BUFFER_SIZE];
|
||||
mdbg_game_state_t game;
|
||||
} sm_mdbg_state_t;
|
||||
|
||||
@@ -93,6 +107,7 @@ static int mdbg_call_raw(int64_t pid, uint64_t subcmd, uint64_t arg,
|
||||
static int query_mdbg_flags(pid_t pid, uint64_t *flags_out);
|
||||
static bool is_mdbg_process_gone_error(int ret);
|
||||
static void reset_log_line_buffer(void);
|
||||
static void reset_fatal_error_buffer(void);
|
||||
static void reset_log_snapshot(void);
|
||||
static void free_log_buffers(void);
|
||||
static bool ensure_log_buffers(void);
|
||||
@@ -104,11 +119,18 @@ static bool matches_tracked_rtld_error(const char *text);
|
||||
static void summarize_failure_reason(const char *reason, char *summary_out,
|
||||
size_t summary_out_size,
|
||||
bool is_rtld_error);
|
||||
static void handle_pre_pause_failure(const char *reason);
|
||||
static void handle_post_pause_failure(const char *reason, uint64_t now_us);
|
||||
static void handle_pre_pause_failure(const char *reason,
|
||||
mdbg_failure_kind_t kind);
|
||||
static void handle_post_pause_failure(const char *reason, uint64_t now_us,
|
||||
mdbg_failure_kind_t kind);
|
||||
static void append_fatal_error_detail(const char *detail);
|
||||
static void parse_fatal_error_pid(const char *line);
|
||||
static void finish_fatal_error(uint64_t now_us);
|
||||
static void process_log_line(const char *line, uint64_t now_us);
|
||||
static void flush_log_line(uint64_t now_us);
|
||||
static void append_log_char(char ch, uint64_t now_us);
|
||||
static void poll_log_monitor(uint64_t now_us);
|
||||
static void drain_log_monitor_before_clear(uint64_t now_us);
|
||||
static void start_log_monitoring(void);
|
||||
static void handle_crash_candidate(uint64_t flags, uint64_t now_us);
|
||||
|
||||
@@ -179,7 +201,7 @@ static int query_mdbg_flags(pid_t pid, uint64_t *flags_out) {
|
||||
int64_t status;
|
||||
uint64_t value;
|
||||
int ret = mdbg_call_raw(pid, MDBG_SUBCMD_FLAGS, 0, &status, &value);
|
||||
if (ret < 0)
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
if (status != 0)
|
||||
return (int)status;
|
||||
@@ -197,9 +219,17 @@ static void reset_log_line_buffer(void) {
|
||||
g_mdbg.log_line[0] = '\0';
|
||||
}
|
||||
|
||||
static void reset_fatal_error_buffer(void) {
|
||||
g_mdbg.fatal_error_active = false;
|
||||
g_mdbg.fatal_error_length = 0;
|
||||
g_mdbg.fatal_error_pid = 0;
|
||||
g_mdbg.fatal_error[0] = '\0';
|
||||
}
|
||||
|
||||
static void reset_log_snapshot(void) {
|
||||
g_mdbg.log_snapshot_length = 0;
|
||||
reset_log_line_buffer();
|
||||
reset_fatal_error_buffer();
|
||||
}
|
||||
|
||||
static void free_log_buffers(void) {
|
||||
@@ -344,14 +374,15 @@ static void summarize_failure_reason(const char *reason, char *summary_out,
|
||||
open_paren ? strchr(open_paren + 1, ')') : NULL;
|
||||
if (open_paren && close_paren && close_paren > open_paren + 1) {
|
||||
int written = snprintf(summary_out, summary_out_size,
|
||||
"can't load module %.*s after KStuff pause",
|
||||
sm_l10n_get(SM_L10N_RTLD_MODULE_AFTER_KSTUFF),
|
||||
(int)(close_paren - open_paren - 1),
|
||||
open_paren + 1);
|
||||
if (written > 0 && (size_t)written < summary_out_size)
|
||||
return;
|
||||
}
|
||||
|
||||
(void)strlcpy(summary_out, "can't load module after KStuff pause",
|
||||
(void)strlcpy(summary_out,
|
||||
sm_l10n_get(SM_L10N_RTLD_AFTER_KSTUFF_FALLBACK),
|
||||
summary_out_size);
|
||||
return;
|
||||
}
|
||||
@@ -359,19 +390,25 @@ static void summarize_failure_reason(const char *reason, char *summary_out,
|
||||
(void)strlcpy(summary_out, reason, summary_out_size);
|
||||
}
|
||||
|
||||
static void handle_pre_pause_failure(const char *reason) {
|
||||
static void handle_pre_pause_failure(const char *reason,
|
||||
mdbg_failure_kind_t kind) {
|
||||
log_debug(" [MDBG] %s crashed before kstuff auto-pause%s%s",
|
||||
g_mdbg.game.title_id, reason ? ": " : "", reason ? reason : "");
|
||||
notify_system_info("App crashed before KStuff pause: %s. KStuff is not to "
|
||||
"blame.",
|
||||
g_mdbg.game.title_id);
|
||||
if (kind == MDBG_FAILURE_FATAL_SIGNAL) {
|
||||
notify_system_info_l10n(SM_L10N_CRASH_BEFORE_KSTUFF_FATAL,
|
||||
g_mdbg.game.title_id, reason);
|
||||
} else {
|
||||
notify_system_info_l10n(SM_L10N_CRASH_BEFORE_KSTUFF,
|
||||
g_mdbg.game.title_id);
|
||||
}
|
||||
clear_tracked_game();
|
||||
}
|
||||
|
||||
static void handle_post_pause_failure(const char *reason, uint64_t now_us) {
|
||||
static void handle_post_pause_failure(const char *reason, uint64_t now_us,
|
||||
mdbg_failure_kind_t kind) {
|
||||
if (!g_mdbg.game.pause_seen || g_mdbg.game.pause_time_us == 0 ||
|
||||
now_us < g_mdbg.game.pause_time_us) {
|
||||
handle_pre_pause_failure(reason);
|
||||
handle_pre_pause_failure(reason, kind);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -380,14 +417,20 @@ static void handle_post_pause_failure(const char *reason, uint64_t now_us) {
|
||||
log_debug(" [MDBG] %s crashed %us after kstuff pause; autotune skipped%s%s",
|
||||
g_mdbg.game.title_id, (unsigned)(post_pause_us / 1000000ull),
|
||||
reason ? ": " : "", reason ? reason : "");
|
||||
notify_system_info("App crashed after KStuff pause: %s. Autotune skipped.",
|
||||
g_mdbg.game.title_id);
|
||||
if (kind == MDBG_FAILURE_FATAL_SIGNAL) {
|
||||
notify_system_info_l10n(SM_L10N_CRASH_AFTER_KSTUFF_FATAL_SKIPPED,
|
||||
g_mdbg.game.title_id, reason);
|
||||
} else {
|
||||
notify_system_info_l10n(SM_L10N_CRASH_AFTER_KSTUFF_SKIPPED,
|
||||
g_mdbg.game.title_id);
|
||||
}
|
||||
clear_tracked_game();
|
||||
return;
|
||||
}
|
||||
|
||||
char reason_summary[128];
|
||||
bool is_rtld_error = matches_tracked_rtld_error(reason);
|
||||
bool is_rtld_error = kind == MDBG_FAILURE_RTLD;
|
||||
bool is_fatal_error = kind == MDBG_FAILURE_FATAL_SIGNAL;
|
||||
summarize_failure_reason(reason, reason_summary, sizeof(reason_summary),
|
||||
is_rtld_error);
|
||||
|
||||
@@ -400,16 +443,19 @@ static void handle_post_pause_failure(const char *reason, uint64_t now_us) {
|
||||
if (reason_summary[0] != '\0')
|
||||
log_debug(" [MDBG] autotune trigger: %s", reason_summary);
|
||||
if (is_rtld_error) {
|
||||
notify_system_info("%s: %s. Delay increased to %us.\nLaunch the game again.",
|
||||
g_mdbg.game.title_id,
|
||||
reason_summary[0] != '\0' ? reason_summary
|
||||
: "Can't load module after "
|
||||
"KStuff pause",
|
||||
tuned_delay_seconds);
|
||||
notify_system_info_l10n(
|
||||
SM_L10N_DELAY_INCREASED_RELAUNCH, g_mdbg.game.title_id,
|
||||
reason_summary[0] != '\0'
|
||||
? reason_summary
|
||||
: sm_l10n_get(SM_L10N_RTLD_AFTER_KSTUFF_FALLBACK),
|
||||
tuned_delay_seconds);
|
||||
} else if (is_fatal_error) {
|
||||
notify_system_info_l10n(SM_L10N_CRASH_FATAL_DELAY_INCREASED,
|
||||
g_mdbg.game.title_id, reason,
|
||||
tuned_delay_seconds);
|
||||
} else {
|
||||
notify_system_info("Crash detected after KStuff pause: pause delay for %s "
|
||||
"increased to %us. Launch the game again.",
|
||||
g_mdbg.game.title_id, tuned_delay_seconds);
|
||||
notify_system_info_l10n(SM_L10N_CRASH_DELAY_INCREASED,
|
||||
g_mdbg.game.title_id, tuned_delay_seconds);
|
||||
}
|
||||
clear_tracked_game();
|
||||
return;
|
||||
@@ -417,18 +463,114 @@ static void handle_post_pause_failure(const char *reason, uint64_t now_us) {
|
||||
|
||||
log_debug(" [MDBG] failed to persist autotune pause delay for %s%s%s",
|
||||
g_mdbg.game.title_id, reason ? ": " : "", reason ? reason : "");
|
||||
notify_system_info("Crash detected after KStuff pause: %s. Failed to update "
|
||||
"autotune delay.",
|
||||
g_mdbg.game.title_id);
|
||||
if (is_fatal_error) {
|
||||
notify_system_info_l10n(SM_L10N_CRASH_FATAL_DELAY_UPDATE_FAILED,
|
||||
g_mdbg.game.title_id, reason);
|
||||
} else {
|
||||
notify_system_info_l10n(SM_L10N_CRASH_DELAY_UPDATE_FAILED,
|
||||
g_mdbg.game.title_id);
|
||||
}
|
||||
clear_tracked_game();
|
||||
}
|
||||
|
||||
static void append_fatal_error_detail(const char *detail) {
|
||||
if (!detail || detail[0] == '\0' ||
|
||||
g_mdbg.fatal_error_length + 1u >= sizeof(g_mdbg.fatal_error)) {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t remaining = sizeof(g_mdbg.fatal_error) - g_mdbg.fatal_error_length;
|
||||
int written = snprintf(g_mdbg.fatal_error + g_mdbg.fatal_error_length,
|
||||
remaining, "%s%s",
|
||||
g_mdbg.fatal_error_length != 0 ? "\n" : "",
|
||||
detail);
|
||||
if (written < 0)
|
||||
return;
|
||||
if ((size_t)written >= remaining) {
|
||||
g_mdbg.fatal_error_length = sizeof(g_mdbg.fatal_error) - 1u;
|
||||
return;
|
||||
}
|
||||
|
||||
g_mdbg.fatal_error_length += (size_t)written;
|
||||
}
|
||||
|
||||
static void parse_fatal_error_pid(const char *line) {
|
||||
const char *value = line + strlen(MDBG_FATAL_PROCESS_ID_PREFIX);
|
||||
char *end = NULL;
|
||||
long parsed = strtol(value, &end, 10);
|
||||
pid_t pid = (pid_t)parsed;
|
||||
|
||||
if (end == value || end[0] != '\0' || pid <= 0 || (long)pid != parsed)
|
||||
return;
|
||||
|
||||
g_mdbg.fatal_error_pid = pid;
|
||||
}
|
||||
|
||||
static void finish_fatal_error(uint64_t now_us) {
|
||||
if (!g_mdbg.fatal_error_active)
|
||||
return;
|
||||
|
||||
g_mdbg.fatal_error_active = false;
|
||||
if (g_mdbg.fatal_error_length == 0) {
|
||||
reset_fatal_error_buffer();
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_mdbg.fatal_error_pid != g_mdbg.game.pid) {
|
||||
log_debug(" [MDBG] ignoring fatal error for pid=%ld while tracking pid=%ld",
|
||||
(long)g_mdbg.fatal_error_pid, (long)g_mdbg.game.pid);
|
||||
reset_fatal_error_buffer();
|
||||
return;
|
||||
}
|
||||
|
||||
log_debug(" [MDBG] fatal error for %s:\n%s", g_mdbg.game.title_id,
|
||||
g_mdbg.fatal_error);
|
||||
handle_post_pause_failure(g_mdbg.fatal_error, now_us,
|
||||
MDBG_FAILURE_FATAL_SIGNAL);
|
||||
}
|
||||
|
||||
static void process_log_line(const char *line, uint64_t now_us) {
|
||||
if (!strncmp(line, MDBG_FATAL_EXCEPTION_PREFIX,
|
||||
strlen(MDBG_FATAL_EXCEPTION_PREFIX))) {
|
||||
reset_fatal_error_buffer();
|
||||
g_mdbg.fatal_error_active = true;
|
||||
append_fatal_error_detail(line + strlen(MDBG_FATAL_EXCEPTION_PREFIX));
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_mdbg.fatal_error_active) {
|
||||
if (line[0] != '#')
|
||||
return;
|
||||
|
||||
if (!strcmp(line, "#")) {
|
||||
finish_fatal_error(now_us);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!strncmp(line, MDBG_FATAL_THREAD_NAME_PREFIX,
|
||||
strlen(MDBG_FATAL_THREAD_NAME_PREFIX))) {
|
||||
append_fatal_error_detail(line + 2);
|
||||
} else if (!strncmp(line, MDBG_FATAL_PROCESS_ID_PREFIX,
|
||||
strlen(MDBG_FATAL_PROCESS_ID_PREFIX))) {
|
||||
parse_fatal_error_pid(line);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!matches_tracked_rtld_error(line))
|
||||
return;
|
||||
|
||||
log_debug(" [MDBG] log load error for %s: %s", g_mdbg.game.title_id, line);
|
||||
handle_post_pause_failure(line, now_us);
|
||||
handle_post_pause_failure(line, now_us, MDBG_FAILURE_RTLD);
|
||||
}
|
||||
|
||||
static void flush_log_line(uint64_t now_us) {
|
||||
if (g_mdbg.log_line_length == 0)
|
||||
return;
|
||||
|
||||
g_mdbg.log_line[g_mdbg.log_line_length] = '\0';
|
||||
process_log_line(g_mdbg.log_line, now_us);
|
||||
reset_log_line_buffer();
|
||||
}
|
||||
|
||||
static void append_log_char(char ch, uint64_t now_us) {
|
||||
@@ -436,18 +578,14 @@ static void append_log_char(char ch, uint64_t now_us) {
|
||||
return;
|
||||
|
||||
if (ch == '\r' || ch == '\n') {
|
||||
if (g_mdbg.log_line_length == 0)
|
||||
return;
|
||||
g_mdbg.log_line[g_mdbg.log_line_length] = '\0';
|
||||
process_log_line(g_mdbg.log_line, now_us);
|
||||
reset_log_line_buffer();
|
||||
flush_log_line(now_us);
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_mdbg.log_line_length + 1u >= sizeof(g_mdbg.log_line)) {
|
||||
g_mdbg.log_line[g_mdbg.log_line_length] = '\0';
|
||||
process_log_line(g_mdbg.log_line, now_us);
|
||||
reset_log_line_buffer();
|
||||
flush_log_line(now_us);
|
||||
if (!g_mdbg.game.active)
|
||||
return;
|
||||
}
|
||||
|
||||
g_mdbg.log_line[g_mdbg.log_line_length++] = ch;
|
||||
@@ -474,6 +612,11 @@ static void poll_log_monitor(uint64_t now_us) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (text_len == g_mdbg.log_snapshot_length &&
|
||||
(text_len == 0 || !memcmp(g_mdbg.log_snapshot, text, text_len))) {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t skip = 0;
|
||||
if (g_mdbg.log_snapshot_length != 0 && text_len >= g_mdbg.log_snapshot_length &&
|
||||
!memcmp(g_mdbg.log_snapshot, text, g_mdbg.log_snapshot_length)) {
|
||||
@@ -484,6 +627,7 @@ static void poll_log_monitor(uint64_t now_us) {
|
||||
log_debug(" [MDBG] log stream reset or truncated for %s",
|
||||
g_mdbg.game.title_id);
|
||||
reset_log_line_buffer();
|
||||
reset_fatal_error_buffer();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -496,6 +640,18 @@ static void poll_log_monitor(uint64_t now_us) {
|
||||
update_log_snapshot(text, text_len);
|
||||
}
|
||||
|
||||
static void drain_log_monitor_before_clear(uint64_t now_us) {
|
||||
poll_log_monitor(now_us);
|
||||
if (!g_mdbg.game.active)
|
||||
return;
|
||||
|
||||
flush_log_line(now_us);
|
||||
if (!g_mdbg.game.active)
|
||||
return;
|
||||
|
||||
finish_fatal_error(now_us);
|
||||
}
|
||||
|
||||
static void start_log_monitoring(void) {
|
||||
if (!g_mdbg.game.active)
|
||||
return;
|
||||
@@ -525,6 +681,7 @@ static void start_log_monitoring(void) {
|
||||
|
||||
update_log_snapshot(text, text_len);
|
||||
reset_log_line_buffer();
|
||||
reset_fatal_error_buffer();
|
||||
}
|
||||
|
||||
static void handle_crash_candidate(uint64_t flags, uint64_t now_us) {
|
||||
@@ -534,7 +691,7 @@ static void handle_crash_candidate(uint64_t flags, uint64_t now_us) {
|
||||
log_debug(" [MDBG] crash-candidate: %s pid=%ld flags=0x%08" PRIx64,
|
||||
g_mdbg.game.title_id, (long)g_mdbg.game.pid, flags);
|
||||
|
||||
handle_post_pause_failure("crash-candidate", now_us);
|
||||
handle_post_pause_failure("crash-candidate", now_us, MDBG_FAILURE_GENERIC);
|
||||
}
|
||||
|
||||
void sm_mdbg_init(void) {
|
||||
@@ -577,6 +734,7 @@ void sm_mdbg_game_on_exec(pid_t pid, const char *title_id, uint32_t app_id) {
|
||||
|
||||
log_debug(" [MDBG] tracking crash-candidate state: %s pid=%ld app_id=0x%08X",
|
||||
g_mdbg.game.title_id, (long)pid, app_id);
|
||||
start_log_monitoring();
|
||||
}
|
||||
|
||||
void sm_mdbg_game_on_kstuff_pause(pid_t pid, uint64_t pause_time_us,
|
||||
@@ -584,20 +742,29 @@ void sm_mdbg_game_on_kstuff_pause(pid_t pid, uint64_t pause_time_us,
|
||||
if (!g_mdbg.game.active || g_mdbg.game.pid != pid)
|
||||
return;
|
||||
|
||||
if (g_mdbg.game.log_monitoring_active) {
|
||||
poll_log_monitor(pause_time_us);
|
||||
if (!g_mdbg.game.active)
|
||||
return;
|
||||
}
|
||||
|
||||
g_mdbg.game.pause_seen = true;
|
||||
g_mdbg.game.pause_time_us = pause_time_us;
|
||||
g_mdbg.game.monitor_deadline_us =
|
||||
g_mdbg.game.pause_time_us + MDBG_AUTOTUNE_WINDOW_US;
|
||||
g_mdbg.game.pause_delay_seconds = pause_delay_seconds;
|
||||
g_mdbg.game.next_poll_us = pause_time_us;
|
||||
start_log_monitoring();
|
||||
if (!g_mdbg.game.log_monitoring_active)
|
||||
start_log_monitoring();
|
||||
}
|
||||
|
||||
void sm_mdbg_game_on_exit(pid_t pid) {
|
||||
if (!g_mdbg.game.active || g_mdbg.game.pid != pid)
|
||||
return;
|
||||
|
||||
clear_tracked_game();
|
||||
drain_log_monitor_before_clear(monotonic_time_us());
|
||||
if (g_mdbg.game.active)
|
||||
clear_tracked_game();
|
||||
}
|
||||
|
||||
void sm_mdbg_game_shutdown(void) {
|
||||
@@ -642,16 +809,23 @@ void sm_mdbg_poll(void) {
|
||||
return;
|
||||
}
|
||||
|
||||
poll_log_monitor(now_us);
|
||||
if (!g_mdbg.game.active)
|
||||
return;
|
||||
if (g_mdbg.game.pause_seen || g_mdbg.fatal_error_active) {
|
||||
poll_log_monitor(now_us);
|
||||
if (!g_mdbg.game.active)
|
||||
return;
|
||||
}
|
||||
|
||||
g_mdbg.game.next_poll_us = now_us + GAME_LIFECYCLE_POLL_INTERVAL_US;
|
||||
|
||||
if (g_mdbg.fatal_error_active)
|
||||
return;
|
||||
|
||||
uint64_t flags = 0;
|
||||
int ret = query_mdbg_flags(g_mdbg.game.pid, &flags);
|
||||
if (is_mdbg_process_gone_error(ret)) {
|
||||
clear_tracked_game();
|
||||
drain_log_monitor_before_clear(now_us);
|
||||
if (g_mdbg.game.active)
|
||||
clear_tracked_game();
|
||||
return;
|
||||
}
|
||||
if (ret != 0)
|
||||
@@ -660,5 +834,11 @@ void sm_mdbg_poll(void) {
|
||||
if ((flags & MDBG_FLAG_EXCEPTION_STOP) == 0)
|
||||
return;
|
||||
|
||||
if (!g_mdbg.game.pause_seen) {
|
||||
poll_log_monitor(now_us);
|
||||
if (!g_mdbg.game.active || g_mdbg.fatal_error_active)
|
||||
return;
|
||||
}
|
||||
|
||||
handle_crash_candidate(flags, now_us);
|
||||
}
|
||||
+1
-1
@@ -217,7 +217,7 @@ void record_missing_param_failure(const char *path) {
|
||||
|
||||
log_debug(" [SCAN] missing/invalid param.json: %s", path);
|
||||
if (entry->missing_param_attempts == 1)
|
||||
notify_system("Missing/invalid param.json:\n%s", path);
|
||||
notify_system_l10n(SM_L10N_MISSING_PARAM, path);
|
||||
if (entry->missing_param_attempts >= MAX_MISSING_PARAM_SCAN_ATTEMPTS &&
|
||||
!entry->missing_param_limit_logged) {
|
||||
log_debug(" [SCAN] attempt limit reached (%u), skipping path: %s",
|
||||
|
||||
+25
-7
@@ -35,21 +35,39 @@ bool path_matches_root_or_child(const char *path, const char *root) {
|
||||
return path[root_len] == '\0' || path[root_len] == '/';
|
||||
}
|
||||
|
||||
bool is_usb_storage_path(const char *path) {
|
||||
static const char *find_usb_storage_root(const char *path) {
|
||||
static const char *usb_roots[] = {
|
||||
"/mnt/usb0", "/mnt/usb1", "/mnt/usb2", "/mnt/usb3",
|
||||
"/mnt/usb4", "/mnt/usb5", "/mnt/usb6", "/mnt/usb7",
|
||||
"/mnt/ext0",
|
||||
"/mnt/usb0", "/mnt/usb1", "/mnt/usb2", "/mnt/usb3", "/mnt/usb4",
|
||||
"/mnt/usb5", "/mnt/usb6", "/mnt/usb7", "/mnt/ext0",
|
||||
};
|
||||
|
||||
if (!path || path[0] == '\0')
|
||||
return false;
|
||||
return NULL;
|
||||
|
||||
for (size_t i = 0; i < sizeof(usb_roots) / sizeof(usb_roots[0]); i++) {
|
||||
if (path_matches_root_or_child(path, usb_roots[i]))
|
||||
return true;
|
||||
return usb_roots[i];
|
||||
}
|
||||
return false;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool is_usb_storage_path(const char *path) {
|
||||
return find_usb_storage_root(path) != NULL;
|
||||
}
|
||||
|
||||
bool usb_storage_root_mounted(const char *path) {
|
||||
const char *usb_root = find_usb_storage_root(path);
|
||||
if (!usb_root)
|
||||
return false;
|
||||
|
||||
struct stat root_st;
|
||||
struct stat parent_st;
|
||||
if (stat(usb_root, &root_st) != 0 || stat("/mnt", &parent_st) != 0)
|
||||
return false;
|
||||
|
||||
// Empty /mnt/usbN directories belong to /mnt. A mounted USB filesystem has
|
||||
// a distinct device identity even when the directory itself always exists.
|
||||
return root_st.st_dev != parent_st.st_dev;
|
||||
}
|
||||
|
||||
bool build_backports_root_path(const char *scan_path, char out[MAX_PATH]) {
|
||||
|
||||
+72
-11
@@ -25,11 +25,13 @@
|
||||
|
||||
typedef struct {
|
||||
char discovered_param_roots[MAX_PENDING][MAX_PATH];
|
||||
char found_titles[MAX_PENDING][MAX_TITLE_ID];
|
||||
char checked_appmeta_titles[MAX_PENDING][MAX_TITLE_ID];
|
||||
char blocked_ppsa_uninstall_titles[MAX_PENDING][MAX_TITLE_ID];
|
||||
bool checked_appmeta_present[MAX_PENDING];
|
||||
int checked_appmeta_count;
|
||||
int blocked_ppsa_uninstall_count;
|
||||
int found_title_count;
|
||||
} scan_workspace_t;
|
||||
|
||||
// Reuse the largest transient scan buffer instead of placing ~512 KiB of path
|
||||
@@ -39,6 +41,34 @@ static scan_workspace_t g_scan_workspace;
|
||||
static void reset_scan_workspace(void) {
|
||||
g_scan_workspace.checked_appmeta_count = 0;
|
||||
g_scan_workspace.blocked_ppsa_uninstall_count = 0;
|
||||
g_scan_workspace.found_title_count = 0;
|
||||
}
|
||||
|
||||
static void note_found_title(const char *title_id) {
|
||||
if (!title_id || title_id[0] == '\0')
|
||||
return;
|
||||
|
||||
for (int i = 0; i < g_scan_workspace.found_title_count; i++) {
|
||||
if (strcmp(g_scan_workspace.found_titles[i], title_id) == 0)
|
||||
return;
|
||||
}
|
||||
if (g_scan_workspace.found_title_count >= MAX_PENDING)
|
||||
return;
|
||||
|
||||
int slot = g_scan_workspace.found_title_count++;
|
||||
(void)strlcpy(g_scan_workspace.found_titles[slot], title_id,
|
||||
sizeof(g_scan_workspace.found_titles[slot]));
|
||||
}
|
||||
|
||||
static bool mount_persistent_indexed_image(const char *image_path,
|
||||
const char *image_name,
|
||||
bool *unstable_found_out) {
|
||||
bool image_unstable = false;
|
||||
if (maybe_mount_image_file(image_path, image_name, &image_unstable))
|
||||
return true;
|
||||
if (image_unstable && unstable_found_out)
|
||||
*unstable_found_out = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool blocked_ppsa_uninstall_requested(const char *title_id) {
|
||||
@@ -381,6 +411,10 @@ static bool try_collect_candidate_for_directory(
|
||||
if (probe_result == DIRECTORY_CANDIDATE_DESCEND)
|
||||
return false;
|
||||
|
||||
note_found_title(info.title_id);
|
||||
|
||||
note_game_cache_source_seen(full_path, info.title_id, info.title_name);
|
||||
|
||||
if (!sm_image_index_record_game(full_path, info.title_id) &&
|
||||
unstable_found_out) {
|
||||
*unstable_found_out = true;
|
||||
@@ -499,10 +533,17 @@ static bool collect_candidate_image_visit(const char *image_path,
|
||||
struct stat image_st;
|
||||
if (stat(image_path, &image_st) != 0)
|
||||
return true;
|
||||
bool persistent_image = !is_under_image_mount_base(image_path);
|
||||
if (persistent_image &&
|
||||
!sm_image_index_needs_scan(image_path, &image_st, ctx->app_db->titles,
|
||||
ctx->app_db->titles_ready)) {
|
||||
bool indexed_image = !is_under_image_mount_base(image_path);
|
||||
bool index_ready =
|
||||
indexed_image && sm_image_index_visit_ready_titles(
|
||||
image_path, &image_st, ctx->app_db->titles,
|
||||
ctx->app_db->titles_ready, note_found_title);
|
||||
if (index_ready && !runtime_config()->persistent_image_mounts)
|
||||
return true;
|
||||
|
||||
if (index_ready) {
|
||||
(void)mount_persistent_indexed_image(image_path, image_name,
|
||||
ctx->unstable_found_out);
|
||||
return true;
|
||||
}
|
||||
bool image_unstable = false;
|
||||
@@ -511,7 +552,7 @@ static bool collect_candidate_image_visit(const char *image_path,
|
||||
*ctx->unstable_found_out = true;
|
||||
return true;
|
||||
}
|
||||
if (persistent_image)
|
||||
if (indexed_image)
|
||||
sm_image_index_begin_scan(image_path, &image_st);
|
||||
|
||||
char mount_point[MAX_PATH];
|
||||
@@ -523,7 +564,7 @@ static bool collect_candidate_image_visit(const char *image_path,
|
||||
&image_unstable);
|
||||
if (image_unstable && ctx->unstable_found_out)
|
||||
*ctx->unstable_found_out = true;
|
||||
if (persistent_image && !image_unstable && !should_stop_requested() &&
|
||||
if (indexed_image && !image_unstable && !should_stop_requested() &&
|
||||
!runtime_sleep_mode_active()) {
|
||||
sm_image_index_complete_scan(image_path);
|
||||
}
|
||||
@@ -598,9 +639,28 @@ static void collect_scan_candidates_from_manual_path(
|
||||
const char *name = get_filename_component(manual_path);
|
||||
if (S_ISREG(st.st_mode) &&
|
||||
is_supported_image_file_path(manual_path, name)) {
|
||||
if (!sm_image_index_needs_scan(manual_path, &st, app_db->titles,
|
||||
app_db->titles_ready))
|
||||
bool index_ready = sm_image_index_visit_ready_titles(
|
||||
manual_path, &st, app_db->titles, app_db->titles_ready,
|
||||
note_found_title);
|
||||
if (index_ready && !runtime_config()->persistent_image_mounts)
|
||||
return;
|
||||
|
||||
if (index_ready) {
|
||||
if (!mount_persistent_indexed_image(manual_path, name,
|
||||
unstable_found_out)) {
|
||||
return;
|
||||
}
|
||||
|
||||
char mount_point[MAX_PATH];
|
||||
get_image_mount_point_for_source(manual_path, mount_point);
|
||||
if (is_pfsc_image_mount_base_or_child(mount_point)) {
|
||||
collect_scan_candidates_from_manual_root(
|
||||
mount_point, manual_path, candidates, max_candidates,
|
||||
candidate_count, app_db, discovered_param_roots,
|
||||
discovered_param_root_count, unstable_found_out);
|
||||
}
|
||||
return;
|
||||
}
|
||||
bool image_unstable = false;
|
||||
if (!maybe_mount_image_file(manual_path, name, &image_unstable)) {
|
||||
if (image_unstable && unstable_found_out)
|
||||
@@ -783,7 +843,8 @@ bool release_scan_runtime_mounts(void) {
|
||||
runtime_mount_state_unlock();
|
||||
return false;
|
||||
}
|
||||
bool released = release_runtime_image_mounts();
|
||||
bool released = runtime_config()->persistent_image_mounts ||
|
||||
release_runtime_image_mounts();
|
||||
runtime_mount_state_unlock();
|
||||
return released;
|
||||
}
|
||||
@@ -930,7 +991,7 @@ int collect_scan_candidates_for_scan_root(const char *scan_root,
|
||||
unstable_found_out);
|
||||
|
||||
if (total_found_out)
|
||||
*total_found_out = discovered_param_root_count;
|
||||
*total_found_out = g_scan_workspace.found_title_count;
|
||||
sm_image_index_flush();
|
||||
free_app_db_title_list(&blocked_ppsa_titles);
|
||||
free_app_db_title_list(&app_db_titles);
|
||||
@@ -980,7 +1041,7 @@ int collect_scan_candidates(scan_candidate_t *candidates, int max_candidates,
|
||||
blocked_ppsa_titles_ready);
|
||||
|
||||
if (total_found_out)
|
||||
*total_found_out = discovered_param_root_count;
|
||||
*total_found_out = g_scan_workspace.found_title_count;
|
||||
sm_image_index_flush();
|
||||
free_app_db_title_list(&blocked_ppsa_titles);
|
||||
free_app_db_title_list(&app_db_titles);
|
||||
|
||||
+228
-67
@@ -6,10 +6,12 @@
|
||||
#include <sys/select.h>
|
||||
|
||||
#include "sm_config_mount.h"
|
||||
#include "sm_api_service.h"
|
||||
#include "sm_appdb.h"
|
||||
#include "sm_fakelib.h"
|
||||
#include "sm_filesystem.h"
|
||||
#include "sm_game_lifecycle.h"
|
||||
#include "sm_game_cache.h"
|
||||
#include "sm_gameinfo.h"
|
||||
#include "sm_hash.h"
|
||||
#include "sm_image.h"
|
||||
@@ -17,6 +19,7 @@
|
||||
#include "sm_install_queue.h"
|
||||
#include "sm_kstuff.h"
|
||||
#include "sm_limits.h"
|
||||
#include "sm_l10n.h"
|
||||
#include "sm_log.h"
|
||||
#include "sm_path_utils.h"
|
||||
#include "sm_paths.h"
|
||||
@@ -44,6 +47,8 @@ typedef enum {
|
||||
|
||||
typedef struct {
|
||||
int fd;
|
||||
bool owns_fd; // Exactly one subscription closes each shared fd.
|
||||
bool fd_shareable; // Cleared after terminal vnode events until rebuild.
|
||||
int scan_root_index;
|
||||
uint8_t depth;
|
||||
scanner_watch_kind_t kind;
|
||||
@@ -65,6 +70,12 @@ typedef struct {
|
||||
char watch_tree_rebuild_path[MAX_PATH];
|
||||
} scanner_root_state_t;
|
||||
|
||||
typedef struct {
|
||||
int scan_root_index;
|
||||
uint8_t depth;
|
||||
scanner_watch_kind_t kind;
|
||||
} scanner_event_subscription_t;
|
||||
|
||||
static int g_scanner_wake_pipe[2] = {-1, -1};
|
||||
static atomic_bool g_usb_watches_suspended = ATOMIC_VAR_INIT(true);
|
||||
static int g_scanner_config_fd = -1;
|
||||
@@ -155,8 +166,10 @@ static void close_scanner_manual_file(void) {
|
||||
|
||||
static void clear_scanner_watch_entries(void) {
|
||||
for (size_t i = 0; i < g_scanner_watch_count; i++) {
|
||||
if (g_scanner_watch_entries[i].fd >= 0)
|
||||
if (g_scanner_watch_entries[i].owns_fd &&
|
||||
g_scanner_watch_entries[i].fd >= 0) {
|
||||
close(g_scanner_watch_entries[i].fd);
|
||||
}
|
||||
}
|
||||
free(g_scanner_watch_entries);
|
||||
free(g_scanner_watch_fd_index);
|
||||
@@ -287,6 +300,8 @@ static bool rebuild_scanner_watch_fd_index_with_count(size_t watch_count) {
|
||||
g_scanner_watch_fd_index[i] = SCANNER_WATCH_INDEX_NONE;
|
||||
|
||||
for (size_t i = 0; i < g_scanner_watch_count; i++) {
|
||||
if (!g_scanner_watch_entries[i].owns_fd)
|
||||
continue;
|
||||
if (!insert_scanner_watch_fd_index_entry(
|
||||
(uintptr_t)g_scanner_watch_entries[i].fd, i)) {
|
||||
return false;
|
||||
@@ -353,10 +368,32 @@ static void rebind_scanner_watch_entry_root_index(size_t old_index,
|
||||
}
|
||||
}
|
||||
|
||||
static size_t append_scanner_watch_subscription(
|
||||
int fd, bool owns_fd, int scan_root_index, const char *path,
|
||||
scanner_watch_kind_t kind, uint8_t depth) {
|
||||
size_t index = g_scanner_watch_count++;
|
||||
scanner_watch_entry_t *entry = &g_scanner_watch_entries[index];
|
||||
memset(entry, 0, sizeof(*entry));
|
||||
entry->fd = fd;
|
||||
entry->owns_fd = owns_fd;
|
||||
entry->fd_shareable = true;
|
||||
entry->scan_root_index = scan_root_index;
|
||||
entry->depth = depth;
|
||||
entry->kind = kind;
|
||||
entry->prev_root_watch_index = SCANNER_WATCH_INDEX_NONE;
|
||||
entry->next_root_watch_index = SCANNER_WATCH_INDEX_NONE;
|
||||
(void)strlcpy(entry->path, path, sizeof(entry->path));
|
||||
link_scanner_watch_entry_to_root(index);
|
||||
return index;
|
||||
}
|
||||
|
||||
static bool register_scanner_watch_entry(int kq, int scan_root_index,
|
||||
const char *path,
|
||||
scanner_watch_kind_t kind,
|
||||
uint8_t depth) {
|
||||
if (!ensure_scanner_watch_capacity(g_scanner_watch_count + 1u))
|
||||
return false;
|
||||
|
||||
int fd = open(path, O_RDONLY | O_DIRECTORY);
|
||||
if (fd < 0) {
|
||||
if (errno != ENOENT && errno != ENOTDIR) {
|
||||
@@ -366,10 +403,46 @@ static bool register_scanner_watch_entry(int kq, int scan_root_index,
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!ensure_scanner_watch_capacity(g_scanner_watch_count + 1u)) {
|
||||
close(fd);
|
||||
return false;
|
||||
struct stat opened_st;
|
||||
bool opened_identity_checked = false;
|
||||
bool opened_identity_ready = false;
|
||||
int shared_fd = -1;
|
||||
for (size_t i = 0; i < g_scanner_watch_count; i++) {
|
||||
const scanner_watch_entry_t *existing = &g_scanner_watch_entries[i];
|
||||
if (!existing->fd_shareable || strcmp(existing->path, path) != 0)
|
||||
continue;
|
||||
if (existing->fd != shared_fd) {
|
||||
struct stat existing_st;
|
||||
if (!opened_identity_checked) {
|
||||
opened_identity_ready = fstat(fd, &opened_st) == 0;
|
||||
opened_identity_checked = true;
|
||||
}
|
||||
if (!opened_identity_ready || fstat(existing->fd, &existing_st) != 0 ||
|
||||
existing_st.st_dev != opened_st.st_dev ||
|
||||
existing_st.st_ino != opened_st.st_ino) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (existing->scan_root_index == scan_root_index) {
|
||||
if (existing->kind == kind && existing->depth == depth) {
|
||||
close(fd);
|
||||
return true;
|
||||
}
|
||||
log_debug(" [SCAN] conflicting watcher subscription for %s", path);
|
||||
close(fd);
|
||||
return false;
|
||||
}
|
||||
if (shared_fd < 0)
|
||||
shared_fd = existing->fd;
|
||||
}
|
||||
|
||||
if (shared_fd >= 0) {
|
||||
close(fd);
|
||||
(void)append_scanner_watch_subscription(
|
||||
shared_fd, false, scan_root_index, path, kind, depth);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!ensure_scanner_watch_fd_index_capacity(g_scanner_watch_count + 1u)) {
|
||||
close(fd);
|
||||
return false;
|
||||
@@ -387,20 +460,12 @@ static bool register_scanner_watch_entry(int kq, int scan_root_index,
|
||||
return false;
|
||||
}
|
||||
|
||||
scanner_watch_entry_t *entry = &g_scanner_watch_entries[g_scanner_watch_count++];
|
||||
memset(entry, 0, sizeof(*entry));
|
||||
entry->fd = fd;
|
||||
entry->scan_root_index = scan_root_index;
|
||||
entry->depth = depth;
|
||||
entry->kind = kind;
|
||||
entry->prev_root_watch_index = SCANNER_WATCH_INDEX_NONE;
|
||||
entry->next_root_watch_index = SCANNER_WATCH_INDEX_NONE;
|
||||
(void)strlcpy(entry->path, path, sizeof(entry->path));
|
||||
link_scanner_watch_entry_to_root(g_scanner_watch_count - 1u);
|
||||
if (!insert_scanner_watch_fd_index_entry((uintptr_t)fd,
|
||||
g_scanner_watch_count - 1u)) {
|
||||
unlink_scanner_watch_entry_from_root(g_scanner_watch_count - 1u);
|
||||
memset(entry, 0, sizeof(*entry));
|
||||
size_t entry_index = append_scanner_watch_subscription(
|
||||
fd, true, scan_root_index, path, kind, depth);
|
||||
if (!insert_scanner_watch_fd_index_entry((uintptr_t)fd, entry_index)) {
|
||||
unlink_scanner_watch_entry_from_root(entry_index);
|
||||
memset(&g_scanner_watch_entries[entry_index], 0,
|
||||
sizeof(g_scanner_watch_entries[entry_index]));
|
||||
close(fd);
|
||||
g_scanner_watch_count--;
|
||||
(void)rebuild_scanner_watch_fd_index();
|
||||
@@ -413,9 +478,22 @@ static void remove_scanner_watch_entry_at(size_t index) {
|
||||
if (index >= g_scanner_watch_count)
|
||||
return;
|
||||
|
||||
scanner_watch_entry_t *removed = &g_scanner_watch_entries[index];
|
||||
int removed_fd = removed->fd;
|
||||
bool close_fd = removed->owns_fd && removed_fd >= 0;
|
||||
|
||||
unlink_scanner_watch_entry_from_root(index);
|
||||
if (g_scanner_watch_entries[index].fd >= 0)
|
||||
close(g_scanner_watch_entries[index].fd);
|
||||
if (close_fd) {
|
||||
for (size_t i = 0; i < g_scanner_watch_count; i++) {
|
||||
if (i == index || g_scanner_watch_entries[i].fd != removed_fd)
|
||||
continue;
|
||||
g_scanner_watch_entries[i].owns_fd = true;
|
||||
close_fd = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (close_fd)
|
||||
close(removed_fd);
|
||||
|
||||
size_t last_index = g_scanner_watch_count - 1u;
|
||||
if (index != last_index) {
|
||||
@@ -540,20 +618,20 @@ static bool resolve_existing_parent_directory_path(
|
||||
}
|
||||
}
|
||||
|
||||
static bool resolve_watch_tree_rebuild_target(const scanner_watch_entry_t *entry,
|
||||
char rebuild_path[MAX_PATH],
|
||||
uint8_t *rebuild_depth_out,
|
||||
scanner_watch_kind_t *kind_out) {
|
||||
switch (entry->kind) {
|
||||
static bool resolve_watch_tree_rebuild_target(
|
||||
const scanner_event_subscription_t *subscription, const char *watched_path,
|
||||
char rebuild_path[MAX_PATH],
|
||||
uint8_t *rebuild_depth_out, scanner_watch_kind_t *kind_out) {
|
||||
switch (subscription->kind) {
|
||||
case SCANNER_WATCH_SCAN_ROOT:
|
||||
case SCANNER_WATCH_SCAN_SUBDIR:
|
||||
(void)strlcpy(rebuild_path, entry->path, MAX_PATH);
|
||||
*rebuild_depth_out = entry->depth;
|
||||
*kind_out = entry->kind;
|
||||
(void)strlcpy(rebuild_path, watched_path, MAX_PATH);
|
||||
*rebuild_depth_out = subscription->depth;
|
||||
*kind_out = subscription->kind;
|
||||
return true;
|
||||
case SCANNER_WATCH_SCAN_ROOT_PARENT:
|
||||
(void)strlcpy(rebuild_path, get_scan_path(entry->scan_root_index),
|
||||
MAX_PATH);
|
||||
(void)strlcpy(rebuild_path,
|
||||
get_scan_path(subscription->scan_root_index), MAX_PATH);
|
||||
*rebuild_depth_out = 0;
|
||||
*kind_out = SCANNER_WATCH_SCAN_ROOT;
|
||||
return true;
|
||||
@@ -703,12 +781,21 @@ static bool suspend_usb_scan_root_watch_trees(void) {
|
||||
return !removed_any || rebuild_scanner_watch_fd_index();
|
||||
}
|
||||
|
||||
static void schedule_scan_root_dirty(int scan_root_index, uint64_t now_us,
|
||||
bool immediate);
|
||||
|
||||
static bool resume_usb_scan_root_watch_trees(int kq) {
|
||||
uint64_t now_us = monotonic_time_us();
|
||||
for (int i = 0; i < get_scan_path_count(); i++) {
|
||||
if (!is_usb_storage_path(get_scan_path(i)))
|
||||
const char *scan_root = get_scan_path(i);
|
||||
if (!is_usb_storage_path(scan_root))
|
||||
continue;
|
||||
// Rebuild every configured USB root because the same disk can resume
|
||||
// under a different usbN assignment.
|
||||
if (!rebuild_scan_root_watch_tree(kq, i))
|
||||
return false;
|
||||
if (usb_storage_root_mounted(scan_root))
|
||||
schedule_scan_root_dirty(i, now_us, true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -753,16 +840,17 @@ static bool scanner_event_requires_consistency_cleanup(uint32_t fflags) {
|
||||
}
|
||||
|
||||
static bool scanner_event_requires_watch_tree_refresh(
|
||||
const scanner_watch_entry_t *entry, uint32_t fflags) {
|
||||
const scanner_event_subscription_t *subscription, uint32_t fflags) {
|
||||
uint32_t tree_change_flags =
|
||||
NOTE_WRITE | NOTE_EXTEND | NOTE_DELETE | NOTE_RENAME | NOTE_REVOKE;
|
||||
|
||||
switch (entry->kind) {
|
||||
switch (subscription->kind) {
|
||||
case SCANNER_WATCH_SCAN_ROOT:
|
||||
return (fflags & tree_change_flags) != 0;
|
||||
case SCANNER_WATCH_SCAN_SUBDIR:
|
||||
return entry->depth <
|
||||
get_scan_depth_for_root(get_scan_path(entry->scan_root_index)) &&
|
||||
return subscription->depth <
|
||||
get_scan_depth_for_root(
|
||||
get_scan_path(subscription->scan_root_index)) &&
|
||||
(fflags & tree_change_flags) != 0;
|
||||
default:
|
||||
return false;
|
||||
@@ -770,16 +858,19 @@ static bool scanner_event_requires_watch_tree_refresh(
|
||||
}
|
||||
|
||||
static void schedule_scan_root_watch_tree_rebuild(
|
||||
const scanner_watch_entry_t *entry) {
|
||||
const scanner_event_subscription_t *subscription,
|
||||
const char *watched_path) {
|
||||
char rebuild_path[MAX_PATH];
|
||||
uint8_t rebuild_depth = 0;
|
||||
scanner_watch_kind_t rebuild_kind = SCANNER_WATCH_SCAN_ROOT;
|
||||
if (!resolve_watch_tree_rebuild_target(entry, rebuild_path, &rebuild_depth,
|
||||
if (!resolve_watch_tree_rebuild_target(subscription, watched_path,
|
||||
rebuild_path, &rebuild_depth,
|
||||
&rebuild_kind)) {
|
||||
return;
|
||||
}
|
||||
|
||||
scanner_root_state_t *state = &g_scanner_root_states[entry->scan_root_index];
|
||||
scanner_root_state_t *state =
|
||||
&g_scanner_root_states[subscription->scan_root_index];
|
||||
if (!state->watch_tree_stale || state->watch_tree_rebuild_path[0] == '\0') {
|
||||
state->watch_tree_stale = true;
|
||||
state->watch_tree_rebuild_depth = rebuild_depth;
|
||||
@@ -813,7 +904,7 @@ static void schedule_scan_root_watch_tree_rebuild(
|
||||
state->watch_tree_rebuild_depth = 0;
|
||||
state->watch_tree_rebuild_kind = SCANNER_WATCH_SCAN_ROOT;
|
||||
(void)strlcpy(state->watch_tree_rebuild_path,
|
||||
get_scan_path(entry->scan_root_index),
|
||||
get_scan_path(subscription->scan_root_index),
|
||||
sizeof(state->watch_tree_rebuild_path));
|
||||
}
|
||||
|
||||
@@ -901,12 +992,30 @@ static bool apply_runtime_config_reload_effects(int kq,
|
||||
const runtime_config_t *old_cfg,
|
||||
const runtime_config_t *new_cfg,
|
||||
bool scan_topology_changed) {
|
||||
if (strcmp(old_cfg->api_bind_address, new_cfg->api_bind_address) != 0 ||
|
||||
old_cfg->api_port != new_cfg->api_port) {
|
||||
if (!sm_api_service_reconfigure())
|
||||
log_debug(" [CFG] HTTP API reconfigure failed: %s", strerror(errno));
|
||||
}
|
||||
|
||||
if (old_cfg->backport_fakelib_enabled &&
|
||||
fakelib_runtime_config_changed(old_cfg, new_cfg))
|
||||
sm_fakelib_game_shutdown();
|
||||
|
||||
sm_kstuff_on_config_reload();
|
||||
|
||||
if (old_cfg->auto_remove_missing_games !=
|
||||
new_cfg->auto_remove_missing_games) {
|
||||
reset_missing_game_cache_timers();
|
||||
}
|
||||
|
||||
if (old_cfg->persistent_image_mounts !=
|
||||
new_cfg->persistent_image_mounts) {
|
||||
request_scan_now(new_cfg->persistent_image_mounts
|
||||
? "persistent image mounting enabled"
|
||||
: "persistent image mounting disabled");
|
||||
}
|
||||
|
||||
if (scan_topology_changed) {
|
||||
clear_scanner_watch_entries();
|
||||
reset_scanner_root_states();
|
||||
@@ -957,13 +1066,15 @@ static bool run_full_scan_cycle(bool startup_sync, const char *reason,
|
||||
new_games++;
|
||||
}
|
||||
if (new_games > 0)
|
||||
notify_system_info("Found %d new games. Executing...", new_games);
|
||||
notify_system_info_l10n(SM_L10N_FOUND_NEW_GAMES, new_games);
|
||||
}
|
||||
|
||||
process_scan_candidates(candidates, candidate_count);
|
||||
if (should_abort_scan_cycle())
|
||||
return false;
|
||||
|
||||
reconcile_missing_app_db_games();
|
||||
|
||||
if (!sm_install_has_pending_work() && !release_scan_runtime_mounts())
|
||||
log_debug(" [IMG] some discovery mounts remain busy");
|
||||
|
||||
@@ -971,8 +1082,7 @@ static bool run_full_scan_cycle(bool startup_sync, const char *reason,
|
||||
*unstable_found_out = unstable_found;
|
||||
|
||||
if (startup_sync && !should_abort_scan_cycle()) {
|
||||
notify_system_rich(true, "Library Synchronized.\nFound %d games.",
|
||||
total_found_games);
|
||||
notify_system_rich_l10n(true, SM_L10N_LIBRARY_SYNCED, total_found_games);
|
||||
}
|
||||
|
||||
return !should_abort_scan_cycle();
|
||||
@@ -1111,29 +1221,41 @@ static const struct timespec *build_wait_timeout(struct timespec *timeout,
|
||||
}
|
||||
|
||||
static bool handle_scan_root_parent_event(
|
||||
int kq, const scanner_watch_entry_t *entry, uint64_t now_us) {
|
||||
const char *scan_root = get_scan_path(entry->scan_root_index);
|
||||
int kq, const scanner_event_subscription_t *subscription,
|
||||
const char *watched_path, uint64_t now_us) {
|
||||
int scan_root_index = subscription->scan_root_index;
|
||||
const char *scan_root = get_scan_path(scan_root_index);
|
||||
bool root_present = false;
|
||||
bool root_changed = update_scan_root_presence_state(
|
||||
entry->scan_root_index, scan_root, &root_present);
|
||||
bool root_changed = update_scan_root_presence_state(scan_root_index, scan_root,
|
||||
&root_present);
|
||||
if (root_present && root_changed) {
|
||||
schedule_scan_root_dirty(entry->scan_root_index, now_us, false);
|
||||
schedule_scan_root_watch_tree_rebuild(entry);
|
||||
bool resumed_usb_root =
|
||||
runtime_resume_grace_active() && is_usb_storage_path(scan_root);
|
||||
if (resumed_usb_root && !usb_storage_root_mounted(scan_root)) {
|
||||
// The permanent /mnt/usbN directory is visible again, but the USB
|
||||
// filesystem is not. Refresh the terminal vnode watch without scanning
|
||||
// or cleaning the temporarily unavailable source.
|
||||
return rebuild_scan_root_watch_tree(kq, scan_root_index);
|
||||
}
|
||||
schedule_scan_root_dirty(scan_root_index, now_us, resumed_usb_root);
|
||||
schedule_scan_root_watch_tree_rebuild(subscription, watched_path);
|
||||
return true;
|
||||
}
|
||||
if (root_present)
|
||||
return true;
|
||||
if (root_changed) {
|
||||
schedule_scan_root_cleanup(entry->scan_root_index);
|
||||
schedule_scan_root_dirty(entry->scan_root_index, now_us, true);
|
||||
schedule_scan_root_watch_tree_rebuild(entry);
|
||||
if (runtime_resume_grace_active() && is_usb_storage_path(scan_root))
|
||||
return rebuild_scan_root_watch_tree(kq, scan_root_index);
|
||||
schedule_scan_root_cleanup(scan_root_index);
|
||||
schedule_scan_root_dirty(scan_root_index, now_us, true);
|
||||
schedule_scan_root_watch_tree_rebuild(subscription, watched_path);
|
||||
return true;
|
||||
}
|
||||
|
||||
char parent_path[MAX_PATH];
|
||||
if (!resolve_existing_parent_directory_path(scan_root, parent_path) ||
|
||||
strcmp(parent_path, entry->path) != 0) {
|
||||
return rebuild_scan_root_watch_tree(kq, entry->scan_root_index);
|
||||
strcmp(parent_path, watched_path) != 0) {
|
||||
return rebuild_scan_root_watch_tree(kq, scan_root_index);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -1189,25 +1311,61 @@ static bool process_scanner_events(int kq, const struct timespec *timeout,
|
||||
continue;
|
||||
}
|
||||
|
||||
scanner_watch_entry_t *watch_entry =
|
||||
scanner_watch_entry_t *watch_owner =
|
||||
find_scanner_watch_entry_by_fd(event->ident);
|
||||
if (!watch_entry)
|
||||
if (!watch_owner)
|
||||
continue;
|
||||
|
||||
if (watch_entry->kind == SCANNER_WATCH_SCAN_ROOT_PARENT) {
|
||||
if (!handle_scan_root_parent_event(kq, watch_entry, now_us))
|
||||
scanner_event_subscription_t subscriptions[MAX_SCAN_PATHS];
|
||||
size_t subscription_count = 0;
|
||||
char watched_path[MAX_PATH];
|
||||
(void)strlcpy(watched_path, watch_owner->path, sizeof(watched_path));
|
||||
bool immediate =
|
||||
scanner_event_requires_consistency_cleanup(event->fflags);
|
||||
|
||||
for (size_t watch_index = 0; watch_index < g_scanner_watch_count;
|
||||
watch_index++) {
|
||||
scanner_watch_entry_t *entry = &g_scanner_watch_entries[watch_index];
|
||||
if ((uintptr_t)entry->fd != event->ident)
|
||||
continue;
|
||||
if (immediate)
|
||||
entry->fd_shareable = false;
|
||||
if (subscription_count >= MAX_SCAN_PATHS) {
|
||||
log_debug(" [SCAN] too many watcher subscriptions for %s",
|
||||
watched_path);
|
||||
return false;
|
||||
continue;
|
||||
}
|
||||
subscriptions[subscription_count++] =
|
||||
(scanner_event_subscription_t){
|
||||
.scan_root_index = entry->scan_root_index,
|
||||
.depth = entry->depth,
|
||||
.kind = entry->kind,
|
||||
};
|
||||
}
|
||||
|
||||
bool immediate =
|
||||
(event->fflags & (NOTE_DELETE | NOTE_RENAME | NOTE_REVOKE)) != 0;
|
||||
if (scanner_event_requires_consistency_cleanup(event->fflags))
|
||||
schedule_scan_root_cleanup(watch_entry->scan_root_index);
|
||||
schedule_scan_root_dirty(watch_entry->scan_root_index, now_us, immediate);
|
||||
for (size_t subscription_index = 0;
|
||||
subscription_index < subscription_count; subscription_index++) {
|
||||
const scanner_event_subscription_t *subscription =
|
||||
&subscriptions[subscription_index];
|
||||
|
||||
if (scanner_event_requires_watch_tree_refresh(watch_entry, event->fflags))
|
||||
schedule_scan_root_watch_tree_rebuild(watch_entry);
|
||||
if (subscription->kind == SCANNER_WATCH_SCAN_ROOT_PARENT) {
|
||||
if (!handle_scan_root_parent_event(kq, subscription, watched_path,
|
||||
now_us)) {
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (immediate)
|
||||
schedule_scan_root_cleanup(subscription->scan_root_index);
|
||||
schedule_scan_root_dirty(subscription->scan_root_index, now_us,
|
||||
immediate);
|
||||
|
||||
if (scanner_event_requires_watch_tree_refresh(subscription,
|
||||
event->fflags)) {
|
||||
schedule_scan_root_watch_tree_rebuild(subscription, watched_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -1367,7 +1525,6 @@ void sm_scanner_run_loop(void) {
|
||||
request_scanner_shutdown("scanner watcher initialization failed");
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t next_full_resync_us =
|
||||
monotonic_time_us() + scanner_full_resync_interval_us();
|
||||
bool was_sleeping = false;
|
||||
@@ -1387,6 +1544,7 @@ void sm_scanner_run_loop(void) {
|
||||
request_scanner_shutdown("USB watcher suspend failed");
|
||||
return;
|
||||
}
|
||||
clear_all_dirty_scan_roots();
|
||||
atomic_store_explicit(&g_usb_watches_suspended, true,
|
||||
memory_order_release);
|
||||
wake_game_lifecycle_watcher();
|
||||
@@ -1425,6 +1583,8 @@ void sm_scanner_run_loop(void) {
|
||||
atomic_store_explicit(&g_usb_watches_suspended, false,
|
||||
memory_order_release);
|
||||
was_sleeping = false;
|
||||
next_full_resync_us =
|
||||
monotonic_time_us() + RUNTIME_RESUME_GRACE_US;
|
||||
log_debug("[SLEEP] USB scanner watches resumed");
|
||||
}
|
||||
|
||||
@@ -1512,7 +1672,8 @@ void sm_scanner_run_loop(void) {
|
||||
request_scanner_shutdown("scanner stale event drain after config reload failed");
|
||||
return;
|
||||
}
|
||||
notify_system("ShadowMount+: config reloaded.");
|
||||
sm_l10n_init();
|
||||
notify_system_l10n(SM_L10N_CONFIG_RELOADED);
|
||||
log_debug(" [CFG] runtime config reloaded");
|
||||
now_us = monotonic_time_us();
|
||||
next_full_resync_us =
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
.global sm_shellcore_bridge_blob_start
|
||||
.global sm_shellcore_bridge_blob_end
|
||||
.global sm_shellcore_bridge_launch_hook
|
||||
.global sm_shellcore_bridge_unmount_hook
|
||||
.global sm_shellcore_bridge_app_exit_hook
|
||||
.global sm_shellcore_bridge_install_all_hook
|
||||
.global sm_shellcore_bridge_launch_trampoline
|
||||
.global sm_shellcore_bridge_unmount_trampoline
|
||||
.global sm_shellcore_bridge_app_exit_trampoline
|
||||
.global sm_shellcore_bridge_install_all_trampoline
|
||||
.global sm_shellcore_bridge_install_title_dir
|
||||
.global sm_shellcore_bridge_install_armed
|
||||
@@ -70,15 +70,14 @@ sm_shellcore_bridge_launch_hook:
|
||||
|
||||
.p2align 4
|
||||
/*
|
||||
* LncApplication::unmountWorkspace() first removes FsSandbox mappings and
|
||||
* tears down m_workspace. Notify ShadowMount only after the original method
|
||||
* returns. The first LncApplication dword is m_appId (also passed by callers
|
||||
* to endAppMount), which lets the service reject unrelated application exits.
|
||||
* LncManager::onAppExit(uint32_t app_id) owns the SysCore application-exit
|
||||
* event. Notify ShadowMount only after the original method returns, after its
|
||||
* firmware-specific exit cleanup. The app id is the second argument.
|
||||
*/
|
||||
sm_shellcore_bridge_unmount_hook:
|
||||
sm_shellcore_bridge_app_exit_hook:
|
||||
pushq %rbx
|
||||
movl (%rdi), %ebx
|
||||
call *sm_shellcore_bridge_unmount_trampoline(%rip)
|
||||
movl %esi, %ebx
|
||||
call *sm_shellcore_bridge_app_exit_trampoline(%rip)
|
||||
pushq %rax
|
||||
subq $8, %rsp
|
||||
movl $3, %edi
|
||||
@@ -196,7 +195,7 @@ sm_shellcore_bridge_exchange:
|
||||
.p2align 3
|
||||
sm_shellcore_bridge_launch_trampoline:
|
||||
.quad 0
|
||||
sm_shellcore_bridge_unmount_trampoline:
|
||||
sm_shellcore_bridge_app_exit_trampoline:
|
||||
.quad 0
|
||||
sm_shellcore_bridge_install_all_trampoline:
|
||||
.quad 0
|
||||
|
||||
@@ -700,7 +700,6 @@ static void poll_shellcore_flag(shellcore_flag_monitor_t *flag) {
|
||||
"SceSystemStateMgrStatus=SHELLUI_SHUTDOWN_IN_PROGRESS");
|
||||
}
|
||||
if (entered_resume_working) {
|
||||
request_scan_now("SceSystemStateMgrInfo=WORKING");
|
||||
request_runtime_sleep_mode(false, "SceSystemStateMgrInfo=WORKING");
|
||||
}
|
||||
}
|
||||
|
||||
+13
-13
@@ -71,10 +71,10 @@ int kernel_dynlib_obj(pid_t pid, uint32_t handle, shellcore_dynlib_obj_t *obj);
|
||||
extern const uint8_t sm_shellcore_bridge_blob_start[];
|
||||
extern const uint8_t sm_shellcore_bridge_blob_end[];
|
||||
extern const uint8_t sm_shellcore_bridge_launch_hook[];
|
||||
extern const uint8_t sm_shellcore_bridge_unmount_hook[];
|
||||
extern const uint8_t sm_shellcore_bridge_app_exit_hook[];
|
||||
extern const uint8_t sm_shellcore_bridge_install_all_hook[];
|
||||
extern const uint8_t sm_shellcore_bridge_launch_trampoline[];
|
||||
extern const uint8_t sm_shellcore_bridge_unmount_trampoline[];
|
||||
extern const uint8_t sm_shellcore_bridge_app_exit_trampoline[];
|
||||
extern const uint8_t sm_shellcore_bridge_install_all_trampoline[];
|
||||
extern const uint8_t sm_shellcore_bridge_install_title_dir[];
|
||||
extern const uint8_t sm_shellcore_bridge_install_armed[];
|
||||
@@ -260,12 +260,12 @@ static bool install_hooks_for_pid(pid_t pid) {
|
||||
: SHELLCORE_BASE_HOOK_COUNT;
|
||||
uintptr_t targets[SHELLCORE_MAX_HOOK_COUNT] = {
|
||||
remote.targets[SM_SHELLCORE_TARGET_LAUNCH_APP],
|
||||
remote.targets[SM_SHELLCORE_TARGET_UNMOUNT_WORKSPACE],
|
||||
remote.targets[SM_SHELLCORE_TARGET_APP_EXIT],
|
||||
remote.targets[SM_SHELLCORE_TARGET_INSTALL_ALL],
|
||||
};
|
||||
sm_shellcore_target_t target_ids[SHELLCORE_MAX_HOOK_COUNT] = {
|
||||
SM_SHELLCORE_TARGET_LAUNCH_APP,
|
||||
SM_SHELLCORE_TARGET_UNMOUNT_WORKSPACE,
|
||||
SM_SHELLCORE_TARGET_APP_EXIT,
|
||||
SM_SHELLCORE_TARGET_INSTALL_ALL,
|
||||
};
|
||||
|
||||
@@ -290,7 +290,7 @@ static bool install_hooks_for_pid(pid_t pid) {
|
||||
|
||||
size_t cursor = blob_size;
|
||||
uintptr_t launch_trampoline = 0;
|
||||
uintptr_t unmount_trampoline = 0;
|
||||
uintptr_t app_exit_trampoline = 0;
|
||||
uintptr_t install_all_trampoline = 0;
|
||||
if (!append_trampoline(bridge, bridge_capacity, &cursor, g_hooks.original[0],
|
||||
g_hooks.original_size[0],
|
||||
@@ -303,11 +303,11 @@ static bool install_hooks_for_pid(pid_t pid) {
|
||||
if (!append_trampoline(bridge, bridge_capacity, &cursor, g_hooks.original[1],
|
||||
g_hooks.original_size[1],
|
||||
targets[1] + g_hooks.original_size[1], object.eh_frame,
|
||||
&unmount_trampoline)) {
|
||||
&app_exit_trampoline)) {
|
||||
goto done;
|
||||
}
|
||||
set_bridge_pointer(bridge, sm_shellcore_bridge_unmount_trampoline,
|
||||
unmount_trampoline);
|
||||
set_bridge_pointer(bridge, sm_shellcore_bridge_app_exit_trampoline,
|
||||
app_exit_trampoline);
|
||||
if (install_hook_enabled) {
|
||||
if (!append_trampoline(bridge, bridge_capacity, &cursor,
|
||||
g_hooks.original[2], g_hooks.original_size[2],
|
||||
@@ -335,8 +335,8 @@ static bool install_hooks_for_pid(pid_t pid) {
|
||||
uintptr_t launch_hook =
|
||||
object.eh_frame + (uintptr_t)(sm_shellcore_bridge_launch_hook -
|
||||
sm_shellcore_bridge_blob_start);
|
||||
uintptr_t unmount_hook =
|
||||
object.eh_frame + (uintptr_t)(sm_shellcore_bridge_unmount_hook -
|
||||
uintptr_t app_exit_hook =
|
||||
object.eh_frame + (uintptr_t)(sm_shellcore_bridge_app_exit_hook -
|
||||
sm_shellcore_bridge_blob_start);
|
||||
uintptr_t install_all_hook =
|
||||
object.eh_frame +
|
||||
@@ -344,7 +344,7 @@ static bool install_hooks_for_pid(pid_t pid) {
|
||||
sm_shellcore_bridge_blob_start);
|
||||
if (!patch_remote_jump(pid, targets[0], launch_hook,
|
||||
g_hooks.original_size[0]) ||
|
||||
!patch_remote_jump(pid, targets[1], unmount_hook,
|
||||
!patch_remote_jump(pid, targets[1], app_exit_hook,
|
||||
g_hooks.original_size[1]) ||
|
||||
(install_hook_enabled &&
|
||||
!patch_remote_jump(pid, targets[2], install_all_hook,
|
||||
@@ -360,7 +360,7 @@ static bool install_hooks_for_pid(pid_t pid) {
|
||||
g_hooks.installed = true;
|
||||
ok = true;
|
||||
log_debug(" [SHELLCORE] hooks installed: fw=%s pid=%ld lifecycle=1 "
|
||||
"workspace=1 install=%d",
|
||||
"app_exit=1 install=%d",
|
||||
remote.offsets->name, (long)pid, install_hook_enabled ? 1 : 0);
|
||||
goto done;
|
||||
|
||||
@@ -417,7 +417,7 @@ void sm_shellcore_hooks_stop(void) {
|
||||
pthread_mutex_unlock(&g_install_mutex);
|
||||
}
|
||||
|
||||
bool sm_shellcore_workspace_hook_active(void) {
|
||||
bool sm_shellcore_app_exit_hook_active(void) {
|
||||
int saved_errno = errno;
|
||||
pthread_mutex_lock(&g_install_mutex);
|
||||
bool active = g_hooks.installed && g_hooks.remote.pid > 0;
|
||||
|
||||
@@ -21,8 +21,8 @@ const char *sm_shellcore_target_name(sm_shellcore_target_t target) {
|
||||
switch (target) {
|
||||
case SM_SHELLCORE_TARGET_LAUNCH_APP:
|
||||
return "launchApp";
|
||||
case SM_SHELLCORE_TARGET_UNMOUNT_WORKSPACE:
|
||||
return "unmountWorkspace";
|
||||
case SM_SHELLCORE_TARGET_APP_EXIT:
|
||||
return "onAppExit";
|
||||
case SM_SHELLCORE_TARGET_INSTALL_TITLE_DIR:
|
||||
return "AppInstallTitleDirMain";
|
||||
case SM_SHELLCORE_TARGET_INSTALL_ALL:
|
||||
|
||||
+118
-117
@@ -1,285 +1,286 @@
|
||||
/* Generated by tools/generate_shellcore_offsets_from_files.py. */
|
||||
{
|
||||
.firmware = 0x0100u,
|
||||
.name = "1.00",
|
||||
.targets = {{.offset = 0x3465a0u, .patch_size = 12u}, {.offset = 0x3479b0u, .patch_size = 12u}, {.offset = 0x1adc50u, .patch_size = 12u}, {.offset = 0xa05c40u, .patch_size = 14u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x1000u,
|
||||
.name = "10.00",
|
||||
.targets = {{.offset = 0x5ca1a0u, .patch_size = 12u}, {.offset = 0x605140u, .patch_size = 13u}, {.offset = 0x2b6700u, .patch_size = 12u}, {.offset = 0x17d1f00u, .patch_size = 16u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x1001u,
|
||||
.name = "10.01",
|
||||
.targets = {{.offset = 0x5ca1a0u, .patch_size = 12u}, {.offset = 0x605140u, .patch_size = 13u}, {.offset = 0x2b6700u, .patch_size = 12u}, {.offset = 0x17d1f00u, .patch_size = 16u}},
|
||||
.targets = {{.offset = 0x3465a0u, .patch_size = 12u}, {.offset = 0x3498e0u, .patch_size = 12u}, {.offset = 0x1adc50u, .patch_size = 12u}, {.offset = 0xa05c40u, .patch_size = 14u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x0101u,
|
||||
.name = "1.01",
|
||||
.targets = {{.offset = 0x3465a0u, .patch_size = 12u}, {.offset = 0x3479b0u, .patch_size = 12u}, {.offset = 0x1adc50u, .patch_size = 12u}, {.offset = 0xa05c40u, .patch_size = 14u}},
|
||||
.targets = {{.offset = 0x3465a0u, .patch_size = 12u}, {.offset = 0x3498e0u, .patch_size = 12u}, {.offset = 0x1adc50u, .patch_size = 12u}, {.offset = 0xa05c40u, .patch_size = 14u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x0102u,
|
||||
.name = "1.02",
|
||||
.targets = {{.offset = 0x3465a0u, .patch_size = 12u}, {.offset = 0x3479b0u, .patch_size = 12u}, {.offset = 0x1adc50u, .patch_size = 12u}, {.offset = 0xa05c40u, .patch_size = 14u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x1020u,
|
||||
.name = "10.20",
|
||||
.targets = {{.offset = 0x5ca1a0u, .patch_size = 12u}, {.offset = 0x605140u, .patch_size = 13u}, {.offset = 0x2b6700u, .patch_size = 12u}, {.offset = 0x17d61c0u, .patch_size = 16u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x1040u,
|
||||
.name = "10.40",
|
||||
.targets = {{.offset = 0x5ca0f0u, .patch_size = 12u}, {.offset = 0x605090u, .patch_size = 13u}, {.offset = 0x2b6760u, .patch_size = 12u}, {.offset = 0x17d61e0u, .patch_size = 16u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x1060u,
|
||||
.name = "10.60",
|
||||
.targets = {{.offset = 0x5cb980u, .patch_size = 12u}, {.offset = 0x606920u, .patch_size = 13u}, {.offset = 0x2b80c0u, .patch_size = 12u}, {.offset = 0x17d7a60u, .patch_size = 16u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x1100u,
|
||||
.name = "11.00",
|
||||
.targets = {{.offset = 0x62a940u, .patch_size = 12u}, {.offset = 0x6668d0u, .patch_size = 15u}, {.offset = 0x2bd880u, .patch_size = 12u}, {.offset = 0x2c4e60u, .patch_size = 15u}},
|
||||
.targets = {{.offset = 0x3465a0u, .patch_size = 12u}, {.offset = 0x3498e0u, .patch_size = 12u}, {.offset = 0x1adc50u, .patch_size = 12u}, {.offset = 0xa05c40u, .patch_size = 14u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x0112u,
|
||||
.name = "1.12",
|
||||
.targets = {{.offset = 0x3469b0u, .patch_size = 12u}, {.offset = 0x347dc0u, .patch_size = 12u}, {.offset = 0x1adc30u, .patch_size = 12u}, {.offset = 0xa063a0u, .patch_size = 14u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x1120u,
|
||||
.name = "11.20",
|
||||
.targets = {{.offset = 0x62aab0u, .patch_size = 12u}, {.offset = 0x666a40u, .patch_size = 15u}, {.offset = 0x2bd930u, .patch_size = 12u}, {.offset = 0x2c4f10u, .patch_size = 15u}},
|
||||
.targets = {{.offset = 0x3469b0u, .patch_size = 12u}, {.offset = 0x349cf0u, .patch_size = 12u}, {.offset = 0x1adc30u, .patch_size = 12u}, {.offset = 0xa063a0u, .patch_size = 14u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x0114u,
|
||||
.name = "1.14",
|
||||
.targets = {{.offset = 0x3469b0u, .patch_size = 12u}, {.offset = 0x347dc0u, .patch_size = 12u}, {.offset = 0x1adc30u, .patch_size = 12u}, {.offset = 0xa06660u, .patch_size = 14u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x1140u,
|
||||
.name = "11.40",
|
||||
.targets = {{.offset = 0x62ba90u, .patch_size = 12u}, {.offset = 0x667a20u, .patch_size = 15u}, {.offset = 0x2be910u, .patch_size = 12u}, {.offset = 0x2c5ef0u, .patch_size = 15u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x1160u,
|
||||
.name = "11.60",
|
||||
.targets = {{.offset = 0x633500u, .patch_size = 12u}, {.offset = 0x66f490u, .patch_size = 15u}, {.offset = 0x2c2c10u, .patch_size = 12u}, {.offset = 0x2ca1f0u, .patch_size = 15u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x1200u,
|
||||
.name = "12.00",
|
||||
.targets = {{.offset = 0x647410u, .patch_size = 12u}, {.offset = 0x683720u, .patch_size = 15u}, {.offset = 0x2e0050u, .patch_size = 12u}, {.offset = 0x2dd100u, .patch_size = 12u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x1202u,
|
||||
.name = "12.02",
|
||||
.targets = {{.offset = 0x647410u, .patch_size = 12u}, {.offset = 0x683720u, .patch_size = 15u}, {.offset = 0x2e0050u, .patch_size = 12u}, {.offset = 0x2dd100u, .patch_size = 12u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x1220u,
|
||||
.name = "12.20",
|
||||
.targets = {{.offset = 0x647410u, .patch_size = 12u}, {.offset = 0x683720u, .patch_size = 15u}, {.offset = 0x2e0050u, .patch_size = 12u}, {.offset = 0x2dd100u, .patch_size = 12u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x1240u,
|
||||
.name = "12.40",
|
||||
.targets = {{.offset = 0x647410u, .patch_size = 12u}, {.offset = 0x683720u, .patch_size = 15u}, {.offset = 0x2e0050u, .patch_size = 12u}, {.offset = 0x2dd100u, .patch_size = 12u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x1260u,
|
||||
.name = "12.60",
|
||||
.targets = {{.offset = 0x648660u, .patch_size = 12u}, {.offset = 0x684970u, .patch_size = 15u}, {.offset = 0x2e0320u, .patch_size = 12u}, {.offset = 0x2dd3d0u, .patch_size = 12u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x1270u,
|
||||
.name = "12.70",
|
||||
.targets = {{.offset = 0x648660u, .patch_size = 12u}, {.offset = 0x684970u, .patch_size = 15u}, {.offset = 0x2e0320u, .patch_size = 12u}, {.offset = 0x2dd3d0u, .patch_size = 12u}},
|
||||
.targets = {{.offset = 0x3469b0u, .patch_size = 12u}, {.offset = 0x349cf0u, .patch_size = 12u}, {.offset = 0x1adc30u, .patch_size = 12u}, {.offset = 0xa06660u, .patch_size = 14u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x0200u,
|
||||
.name = "2.00",
|
||||
.targets = {{.offset = 0x3a4660u, .patch_size = 12u}, {.offset = 0x3a5aa0u, .patch_size = 12u}, {.offset = 0x1df8f0u, .patch_size = 12u}, {.offset = 0xaf3740u, .patch_size = 14u}},
|
||||
.targets = {{.offset = 0x3a4660u, .patch_size = 12u}, {.offset = 0x3a7cf0u, .patch_size = 12u}, {.offset = 0x1df8f0u, .patch_size = 12u}, {.offset = 0xaf3740u, .patch_size = 14u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x0220u,
|
||||
.name = "2.20",
|
||||
.targets = {{.offset = 0x3a4930u, .patch_size = 12u}, {.offset = 0x3a5d70u, .patch_size = 12u}, {.offset = 0x1dfb80u, .patch_size = 12u}, {.offset = 0xaf3aa0u, .patch_size = 14u}},
|
||||
.targets = {{.offset = 0x3a4930u, .patch_size = 12u}, {.offset = 0x3a7fc0u, .patch_size = 12u}, {.offset = 0x1dfb80u, .patch_size = 12u}, {.offset = 0xaf3aa0u, .patch_size = 14u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x0225u,
|
||||
.name = "2.25",
|
||||
.targets = {{.offset = 0x3a4e80u, .patch_size = 12u}, {.offset = 0x3a62c0u, .patch_size = 12u}, {.offset = 0x1dfb80u, .patch_size = 12u}, {.offset = 0xaf3fe0u, .patch_size = 14u}},
|
||||
.targets = {{.offset = 0x3a4e80u, .patch_size = 12u}, {.offset = 0x3a8510u, .patch_size = 12u}, {.offset = 0x1dfb80u, .patch_size = 12u}, {.offset = 0xaf3fe0u, .patch_size = 14u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x0226u,
|
||||
.name = "2.26",
|
||||
.targets = {{.offset = 0x3a65f0u, .patch_size = 12u}, {.offset = 0x3a7a30u, .patch_size = 12u}, {.offset = 0x1e0d50u, .patch_size = 12u}, {.offset = 0xaf57a0u, .patch_size = 14u}},
|
||||
.targets = {{.offset = 0x3a65f0u, .patch_size = 12u}, {.offset = 0x3a9c80u, .patch_size = 12u}, {.offset = 0x1e0d50u, .patch_size = 12u}, {.offset = 0xaf57a0u, .patch_size = 14u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x0230u,
|
||||
.name = "2.30",
|
||||
.targets = {{.offset = 0x3a6d90u, .patch_size = 12u}, {.offset = 0x3a81d0u, .patch_size = 12u}, {.offset = 0x1e0f00u, .patch_size = 12u}, {.offset = 0xaf6360u, .patch_size = 14u}},
|
||||
.targets = {{.offset = 0x3a6d90u, .patch_size = 12u}, {.offset = 0x3aa420u, .patch_size = 12u}, {.offset = 0x1e0f00u, .patch_size = 12u}, {.offset = 0xaf6360u, .patch_size = 14u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x0250u,
|
||||
.name = "2.50",
|
||||
.targets = {{.offset = 0x3a8140u, .patch_size = 12u}, {.offset = 0x3a9580u, .patch_size = 12u}, {.offset = 0x1e0ca0u, .patch_size = 12u}, {.offset = 0xaf7700u, .patch_size = 14u}},
|
||||
.targets = {{.offset = 0x3a8140u, .patch_size = 12u}, {.offset = 0x3ab7d0u, .patch_size = 12u}, {.offset = 0x1e0ca0u, .patch_size = 12u}, {.offset = 0xaf7700u, .patch_size = 14u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x0270u,
|
||||
.name = "2.70",
|
||||
.targets = {{.offset = 0x3a8140u, .patch_size = 12u}, {.offset = 0x3a9580u, .patch_size = 12u}, {.offset = 0x1e0ca0u, .patch_size = 12u}, {.offset = 0xaf7700u, .patch_size = 14u}},
|
||||
.targets = {{.offset = 0x3a8140u, .patch_size = 12u}, {.offset = 0x3ab7d0u, .patch_size = 12u}, {.offset = 0x1e0ca0u, .patch_size = 12u}, {.offset = 0xaf7700u, .patch_size = 14u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x0300u,
|
||||
.name = "3.00",
|
||||
.targets = {{.offset = 0x3f1960u, .patch_size = 12u}, {.offset = 0x3f2ef0u, .patch_size = 12u}, {.offset = 0x2138e0u, .patch_size = 12u}, {.offset = 0x212ab0u, .patch_size = 12u}},
|
||||
.targets = {{.offset = 0x3f1960u, .patch_size = 12u}, {.offset = 0x3f51e0u, .patch_size = 12u}, {.offset = 0x2138e0u, .patch_size = 12u}, {.offset = 0x212ab0u, .patch_size = 12u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x0310u,
|
||||
.name = "3.10",
|
||||
.targets = {{.offset = 0x3f19a0u, .patch_size = 12u}, {.offset = 0x3f2f30u, .patch_size = 12u}, {.offset = 0x213920u, .patch_size = 12u}, {.offset = 0x212af0u, .patch_size = 12u}},
|
||||
.targets = {{.offset = 0x3f19a0u, .patch_size = 12u}, {.offset = 0x3f5220u, .patch_size = 12u}, {.offset = 0x213920u, .patch_size = 12u}, {.offset = 0x212af0u, .patch_size = 12u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x0320u,
|
||||
.name = "3.20",
|
||||
.targets = {{.offset = 0x3f1a50u, .patch_size = 12u}, {.offset = 0x3f2fe0u, .patch_size = 12u}, {.offset = 0x2139d0u, .patch_size = 12u}, {.offset = 0x212ba0u, .patch_size = 12u}},
|
||||
.targets = {{.offset = 0x3f1a50u, .patch_size = 12u}, {.offset = 0x3f52d0u, .patch_size = 12u}, {.offset = 0x2139d0u, .patch_size = 12u}, {.offset = 0x212ba0u, .patch_size = 12u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x0321u,
|
||||
.name = "3.21",
|
||||
.targets = {{.offset = 0x3f1a50u, .patch_size = 12u}, {.offset = 0x3f2fe0u, .patch_size = 12u}, {.offset = 0x2139d0u, .patch_size = 12u}, {.offset = 0x212ba0u, .patch_size = 12u}},
|
||||
.targets = {{.offset = 0x3f1a50u, .patch_size = 12u}, {.offset = 0x3f52d0u, .patch_size = 12u}, {.offset = 0x2139d0u, .patch_size = 12u}, {.offset = 0x212ba0u, .patch_size = 12u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x0400u,
|
||||
.name = "4.00",
|
||||
.targets = {{.offset = 0x432630u, .patch_size = 12u}, {.offset = 0x433d90u, .patch_size = 12u}, {.offset = 0x226c20u, .patch_size = 12u}, {.offset = 0x225e40u, .patch_size = 12u}},
|
||||
.targets = {{.offset = 0x432630u, .patch_size = 12u}, {.offset = 0x436460u, .patch_size = 12u}, {.offset = 0x226c20u, .patch_size = 12u}, {.offset = 0x225e40u, .patch_size = 12u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x0403u,
|
||||
.name = "4.03",
|
||||
.targets = {{.offset = 0x432630u, .patch_size = 12u}, {.offset = 0x433d90u, .patch_size = 12u}, {.offset = 0x226c20u, .patch_size = 12u}, {.offset = 0x225e40u, .patch_size = 12u}},
|
||||
.targets = {{.offset = 0x432630u, .patch_size = 12u}, {.offset = 0x436460u, .patch_size = 12u}, {.offset = 0x226c20u, .patch_size = 12u}, {.offset = 0x225e40u, .patch_size = 12u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x0450u,
|
||||
.name = "4.50",
|
||||
.targets = {{.offset = 0x432d80u, .patch_size = 12u}, {.offset = 0x4344e0u, .patch_size = 12u}, {.offset = 0x227350u, .patch_size = 12u}, {.offset = 0x226570u, .patch_size = 12u}},
|
||||
.targets = {{.offset = 0x432d80u, .patch_size = 12u}, {.offset = 0x436bb0u, .patch_size = 12u}, {.offset = 0x227350u, .patch_size = 12u}, {.offset = 0x226570u, .patch_size = 12u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x0451u,
|
||||
.name = "4.51",
|
||||
.targets = {{.offset = 0x432d80u, .patch_size = 12u}, {.offset = 0x4344e0u, .patch_size = 12u}, {.offset = 0x227350u, .patch_size = 12u}, {.offset = 0x226570u, .patch_size = 12u}},
|
||||
.targets = {{.offset = 0x432d80u, .patch_size = 12u}, {.offset = 0x436bb0u, .patch_size = 12u}, {.offset = 0x227350u, .patch_size = 12u}, {.offset = 0x226570u, .patch_size = 12u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x0500u,
|
||||
.name = "5.00",
|
||||
.targets = {{.offset = 0x498950u, .patch_size = 12u}, {.offset = 0x49a0a0u, .patch_size = 12u}, {.offset = 0x2556a0u, .patch_size = 12u}, {.offset = 0x254900u, .patch_size = 12u}},
|
||||
.targets = {{.offset = 0x498950u, .patch_size = 12u}, {.offset = 0x49c640u, .patch_size = 12u}, {.offset = 0x2556a0u, .patch_size = 12u}, {.offset = 0x254900u, .patch_size = 12u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x0502u,
|
||||
.name = "5.02",
|
||||
.targets = {{.offset = 0x498940u, .patch_size = 12u}, {.offset = 0x49a090u, .patch_size = 12u}, {.offset = 0x2556a0u, .patch_size = 12u}, {.offset = 0x254900u, .patch_size = 12u}},
|
||||
.targets = {{.offset = 0x498940u, .patch_size = 12u}, {.offset = 0x49c630u, .patch_size = 12u}, {.offset = 0x2556a0u, .patch_size = 12u}, {.offset = 0x254900u, .patch_size = 12u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x0510u,
|
||||
.name = "5.10",
|
||||
.targets = {{.offset = 0x4998f0u, .patch_size = 12u}, {.offset = 0x49b650u, .patch_size = 12u}, {.offset = 0x256570u, .patch_size = 12u}, {.offset = 0x2557d0u, .patch_size = 12u}},
|
||||
.targets = {{.offset = 0x4998f0u, .patch_size = 12u}, {.offset = 0x49dfc0u, .patch_size = 12u}, {.offset = 0x256570u, .patch_size = 12u}, {.offset = 0x2557d0u, .patch_size = 12u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x0550u,
|
||||
.name = "5.50",
|
||||
.targets = {{.offset = 0x4998f0u, .patch_size = 12u}, {.offset = 0x49b650u, .patch_size = 12u}, {.offset = 0x256570u, .patch_size = 12u}, {.offset = 0x2557d0u, .patch_size = 12u}},
|
||||
.targets = {{.offset = 0x4998f0u, .patch_size = 12u}, {.offset = 0x49dfc0u, .patch_size = 12u}, {.offset = 0x256570u, .patch_size = 12u}, {.offset = 0x2557d0u, .patch_size = 12u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x0600u,
|
||||
.name = "6.00",
|
||||
.targets = {{.offset = 0x4c7380u, .patch_size = 12u}, {.offset = 0x4fdf70u, .patch_size = 15u}, {.offset = 0x26b9b0u, .patch_size = 12u}, {.offset = 0x271bb0u, .patch_size = 12u}},
|
||||
.targets = {{.offset = 0x4c7380u, .patch_size = 12u}, {.offset = 0x4cb8c0u, .patch_size = 12u}, {.offset = 0x26b9b0u, .patch_size = 12u}, {.offset = 0x271bb0u, .patch_size = 12u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x0602u,
|
||||
.name = "6.02",
|
||||
.targets = {{.offset = 0x4c7380u, .patch_size = 12u}, {.offset = 0x4fdf70u, .patch_size = 15u}, {.offset = 0x26b9b0u, .patch_size = 12u}, {.offset = 0x271bb0u, .patch_size = 12u}},
|
||||
.targets = {{.offset = 0x4c7380u, .patch_size = 12u}, {.offset = 0x4cb8c0u, .patch_size = 12u}, {.offset = 0x26b9b0u, .patch_size = 12u}, {.offset = 0x271bb0u, .patch_size = 12u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x0650u,
|
||||
.name = "6.50",
|
||||
.targets = {{.offset = 0x4c73f0u, .patch_size = 12u}, {.offset = 0x4fdfe0u, .patch_size = 15u}, {.offset = 0x26ba20u, .patch_size = 12u}, {.offset = 0x271c20u, .patch_size = 12u}},
|
||||
.targets = {{.offset = 0x4c73f0u, .patch_size = 12u}, {.offset = 0x4cb930u, .patch_size = 12u}, {.offset = 0x26ba20u, .patch_size = 12u}, {.offset = 0x271c20u, .patch_size = 12u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x0700u,
|
||||
.name = "7.00",
|
||||
.targets = {{.offset = 0x568920u, .patch_size = 12u}, {.offset = 0x5a85d0u, .patch_size = 15u}, {.offset = 0x2852e0u, .patch_size = 12u}, {.offset = 0x28b690u, .patch_size = 12u}},
|
||||
.targets = {{.offset = 0x568920u, .patch_size = 12u}, {.offset = 0x56e9b0u, .patch_size = 12u}, {.offset = 0x2852e0u, .patch_size = 12u}, {.offset = 0x28b690u, .patch_size = 12u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x0701u,
|
||||
.name = "7.01",
|
||||
.targets = {{.offset = 0x568920u, .patch_size = 12u}, {.offset = 0x5a85d0u, .patch_size = 15u}, {.offset = 0x2852e0u, .patch_size = 12u}, {.offset = 0x28b690u, .patch_size = 12u}},
|
||||
.targets = {{.offset = 0x568920u, .patch_size = 12u}, {.offset = 0x56e9b0u, .patch_size = 12u}, {.offset = 0x2852e0u, .patch_size = 12u}, {.offset = 0x28b690u, .patch_size = 12u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x0720u,
|
||||
.name = "7.20",
|
||||
.targets = {{.offset = 0x568940u, .patch_size = 12u}, {.offset = 0x5a85f0u, .patch_size = 15u}, {.offset = 0x2852e0u, .patch_size = 12u}, {.offset = 0x28b690u, .patch_size = 12u}},
|
||||
.targets = {{.offset = 0x568940u, .patch_size = 12u}, {.offset = 0x56e9d0u, .patch_size = 12u}, {.offset = 0x2852e0u, .patch_size = 12u}, {.offset = 0x28b690u, .patch_size = 12u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x0740u,
|
||||
.name = "7.40",
|
||||
.targets = {{.offset = 0x56d430u, .patch_size = 12u}, {.offset = 0x5ad0e0u, .patch_size = 15u}, {.offset = 0x2896b0u, .patch_size = 12u}, {.offset = 0x28fa60u, .patch_size = 12u}},
|
||||
.targets = {{.offset = 0x56d430u, .patch_size = 12u}, {.offset = 0x5734c0u, .patch_size = 12u}, {.offset = 0x2896b0u, .patch_size = 12u}, {.offset = 0x28fa60u, .patch_size = 12u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x0760u,
|
||||
.name = "7.60",
|
||||
.targets = {{.offset = 0x56d430u, .patch_size = 12u}, {.offset = 0x5ad0e0u, .patch_size = 15u}, {.offset = 0x2896b0u, .patch_size = 12u}, {.offset = 0x28fa60u, .patch_size = 12u}},
|
||||
.targets = {{.offset = 0x56d430u, .patch_size = 12u}, {.offset = 0x5734c0u, .patch_size = 12u}, {.offset = 0x2896b0u, .patch_size = 12u}, {.offset = 0x28fa60u, .patch_size = 12u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x0761u,
|
||||
.name = "7.61",
|
||||
.targets = {{.offset = 0x56d430u, .patch_size = 12u}, {.offset = 0x5ad0e0u, .patch_size = 15u}, {.offset = 0x2896b0u, .patch_size = 12u}, {.offset = 0x28fa60u, .patch_size = 12u}},
|
||||
.targets = {{.offset = 0x56d430u, .patch_size = 12u}, {.offset = 0x5734c0u, .patch_size = 12u}, {.offset = 0x2896b0u, .patch_size = 12u}, {.offset = 0x28fa60u, .patch_size = 12u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x0800u,
|
||||
.name = "8.00",
|
||||
.targets = {{.offset = 0x597eb0u, .patch_size = 12u}, {.offset = 0x5d3060u, .patch_size = 13u}, {.offset = 0x29a730u, .patch_size = 12u}, {.offset = 0x2a12f0u, .patch_size = 12u}},
|
||||
.targets = {{.offset = 0x597eb0u, .patch_size = 12u}, {.offset = 0x59c640u, .patch_size = 12u}, {.offset = 0x29a730u, .patch_size = 12u}, {.offset = 0x2a12f0u, .patch_size = 12u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x0820u,
|
||||
.name = "8.20",
|
||||
.targets = {{.offset = 0x598f80u, .patch_size = 12u}, {.offset = 0x5d4130u, .patch_size = 13u}, {.offset = 0x29a9a0u, .patch_size = 12u}, {.offset = 0x2a1560u, .patch_size = 12u}},
|
||||
.targets = {{.offset = 0x598f80u, .patch_size = 12u}, {.offset = 0x59d710u, .patch_size = 12u}, {.offset = 0x29a9a0u, .patch_size = 12u}, {.offset = 0x2a1560u, .patch_size = 12u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x0840u,
|
||||
.name = "8.40",
|
||||
.targets = {{.offset = 0x598f80u, .patch_size = 12u}, {.offset = 0x5d4130u, .patch_size = 13u}, {.offset = 0x29a9a0u, .patch_size = 12u}, {.offset = 0x2a1560u, .patch_size = 12u}},
|
||||
.targets = {{.offset = 0x598f80u, .patch_size = 12u}, {.offset = 0x59d710u, .patch_size = 12u}, {.offset = 0x29a9a0u, .patch_size = 12u}, {.offset = 0x2a1560u, .patch_size = 12u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x0860u,
|
||||
.name = "8.60",
|
||||
.targets = {{.offset = 0x598d60u, .patch_size = 12u}, {.offset = 0x5d3f10u, .patch_size = 13u}, {.offset = 0x29a750u, .patch_size = 12u}, {.offset = 0x2a1310u, .patch_size = 12u}},
|
||||
.targets = {{.offset = 0x598d60u, .patch_size = 12u}, {.offset = 0x59d4f0u, .patch_size = 12u}, {.offset = 0x29a750u, .patch_size = 12u}, {.offset = 0x2a1310u, .patch_size = 12u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x0900u,
|
||||
.name = "9.00",
|
||||
.targets = {{.offset = 0x5cc0a0u, .patch_size = 12u}, {.offset = 0x6088e0u, .patch_size = 13u}, {.offset = 0x2b74d0u, .patch_size = 12u}, {.offset = 0x2be0e0u, .patch_size = 12u}},
|
||||
.targets = {{.offset = 0x5cc0a0u, .patch_size = 12u}, {.offset = 0x5d0a80u, .patch_size = 12u}, {.offset = 0x2b74d0u, .patch_size = 12u}, {.offset = 0x2be0e0u, .patch_size = 12u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x0905u,
|
||||
.name = "9.05",
|
||||
.targets = {{.offset = 0x5cc0a0u, .patch_size = 12u}, {.offset = 0x6088e0u, .patch_size = 13u}, {.offset = 0x2b74d0u, .patch_size = 12u}, {.offset = 0x2be0e0u, .patch_size = 12u}},
|
||||
.targets = {{.offset = 0x5cc0a0u, .patch_size = 12u}, {.offset = 0x5d0a80u, .patch_size = 12u}, {.offset = 0x2b74d0u, .patch_size = 12u}, {.offset = 0x2be0e0u, .patch_size = 12u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x0920u,
|
||||
.name = "9.20",
|
||||
.targets = {{.offset = 0x5cbdc0u, .patch_size = 12u}, {.offset = 0x608600u, .patch_size = 13u}, {.offset = 0x2b74d0u, .patch_size = 12u}, {.offset = 0x2be0e0u, .patch_size = 12u}},
|
||||
.targets = {{.offset = 0x5cbdc0u, .patch_size = 12u}, {.offset = 0x5d07a0u, .patch_size = 12u}, {.offset = 0x2b74d0u, .patch_size = 12u}, {.offset = 0x2be0e0u, .patch_size = 12u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x0940u,
|
||||
.name = "9.40",
|
||||
.targets = {{.offset = 0x5cc490u, .patch_size = 12u}, {.offset = 0x608cd0u, .patch_size = 13u}, {.offset = 0x2b7ba0u, .patch_size = 12u}, {.offset = 0x2be7b0u, .patch_size = 12u}},
|
||||
.targets = {{.offset = 0x5cc490u, .patch_size = 12u}, {.offset = 0x5d0e70u, .patch_size = 12u}, {.offset = 0x2b7ba0u, .patch_size = 12u}, {.offset = 0x2be7b0u, .patch_size = 12u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x0960u,
|
||||
.name = "9.60",
|
||||
.targets = {{.offset = 0x5cc500u, .patch_size = 12u}, {.offset = 0x608d40u, .patch_size = 13u}, {.offset = 0x2b7c10u, .patch_size = 12u}, {.offset = 0x2be820u, .patch_size = 12u}},
|
||||
.targets = {{.offset = 0x5cc500u, .patch_size = 12u}, {.offset = 0x5d0ee0u, .patch_size = 12u}, {.offset = 0x2b7c10u, .patch_size = 12u}, {.offset = 0x2be820u, .patch_size = 12u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x1000u,
|
||||
.name = "10.00",
|
||||
.targets = {{.offset = 0x5ca1a0u, .patch_size = 12u}, {.offset = 0x5ce820u, .patch_size = 12u}, {.offset = 0x2b6700u, .patch_size = 12u}, {.offset = 0x17d1f00u, .patch_size = 16u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x1001u,
|
||||
.name = "10.01",
|
||||
.targets = {{.offset = 0x5ca1a0u, .patch_size = 12u}, {.offset = 0x5ce820u, .patch_size = 12u}, {.offset = 0x2b6700u, .patch_size = 12u}, {.offset = 0x17d1f00u, .patch_size = 16u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x1020u,
|
||||
.name = "10.20",
|
||||
.targets = {{.offset = 0x5ca1a0u, .patch_size = 12u}, {.offset = 0x5ce820u, .patch_size = 12u}, {.offset = 0x2b6700u, .patch_size = 12u}, {.offset = 0x17d61c0u, .patch_size = 16u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x1040u,
|
||||
.name = "10.40",
|
||||
.targets = {{.offset = 0x5ca0f0u, .patch_size = 12u}, {.offset = 0x5ce770u, .patch_size = 12u}, {.offset = 0x2b6760u, .patch_size = 12u}, {.offset = 0x17d61e0u, .patch_size = 16u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x1060u,
|
||||
.name = "10.60",
|
||||
.targets = {{.offset = 0x5cb980u, .patch_size = 12u}, {.offset = 0x5d0000u, .patch_size = 12u}, {.offset = 0x2b80c0u, .patch_size = 12u}, {.offset = 0x17d7a60u, .patch_size = 16u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x1100u,
|
||||
.name = "11.00",
|
||||
.targets = {{.offset = 0x62a940u, .patch_size = 12u}, {.offset = 0x62efe0u, .patch_size = 12u}, {.offset = 0x2bd880u, .patch_size = 12u}, {.offset = 0x2c4e60u, .patch_size = 15u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x1120u,
|
||||
.name = "11.20",
|
||||
.targets = {{.offset = 0x62aab0u, .patch_size = 12u}, {.offset = 0x62f150u, .patch_size = 12u}, {.offset = 0x2bd930u, .patch_size = 12u}, {.offset = 0x2c4f10u, .patch_size = 15u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x1140u,
|
||||
.name = "11.40",
|
||||
.targets = {{.offset = 0x62ba90u, .patch_size = 12u}, {.offset = 0x630130u, .patch_size = 12u}, {.offset = 0x2be910u, .patch_size = 12u}, {.offset = 0x2c5ef0u, .patch_size = 15u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x1160u,
|
||||
.name = "11.60",
|
||||
.targets = {{.offset = 0x633500u, .patch_size = 12u}, {.offset = 0x637ba0u, .patch_size = 12u}, {.offset = 0x2c2c10u, .patch_size = 12u}, {.offset = 0x2ca1f0u, .patch_size = 15u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x1200u,
|
||||
.name = "12.00",
|
||||
.targets = {{.offset = 0x647410u, .patch_size = 12u}, {.offset = 0x64bac0u, .patch_size = 12u}, {.offset = 0x2e0050u, .patch_size = 12u}, {.offset = 0x2dd100u, .patch_size = 12u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x1202u,
|
||||
.name = "12.02",
|
||||
.targets = {{.offset = 0x647410u, .patch_size = 12u}, {.offset = 0x64bac0u, .patch_size = 12u}, {.offset = 0x2e0050u, .patch_size = 12u}, {.offset = 0x2dd100u, .patch_size = 12u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x1220u,
|
||||
.name = "12.20",
|
||||
.targets = {{.offset = 0x647410u, .patch_size = 12u}, {.offset = 0x64bac0u, .patch_size = 12u}, {.offset = 0x2e0050u, .patch_size = 12u}, {.offset = 0x2dd100u, .patch_size = 12u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x1240u,
|
||||
.name = "12.40",
|
||||
.targets = {{.offset = 0x647410u, .patch_size = 12u}, {.offset = 0x64bac0u, .patch_size = 12u}, {.offset = 0x2e0050u, .patch_size = 12u}, {.offset = 0x2dd100u, .patch_size = 12u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x1260u,
|
||||
.name = "12.60",
|
||||
.targets = {{.offset = 0x648660u, .patch_size = 12u}, {.offset = 0x64cd10u, .patch_size = 12u}, {.offset = 0x2e0320u, .patch_size = 12u}, {.offset = 0x2dd3d0u, .patch_size = 12u}},
|
||||
},
|
||||
{
|
||||
.firmware = 0x1270u,
|
||||
.name = "12.70",
|
||||
.targets = {{.offset = 0x648660u, .patch_size = 12u}, {.offset = 0x64cd10u, .patch_size = 12u}, {.offset = 0x2e0320u, .patch_size = 12u}, {.offset = 0x2dd3d0u, .patch_size = 12u}},
|
||||
}
|
||||
+316
-73
@@ -5,10 +5,12 @@
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
|
||||
#include "sm_config_mount.h"
|
||||
#include "sm_filesystem.h"
|
||||
#include "sm_game_lifecycle.h"
|
||||
#include "sm_gameinfo.h"
|
||||
#include "sm_image.h"
|
||||
#include "sm_limits.h"
|
||||
#include "sm_log.h"
|
||||
#include "sm_path_utils.h"
|
||||
#include "sm_runtime.h"
|
||||
@@ -22,11 +24,17 @@ _Static_assert(sizeof(sm_shellcore_request_t) == 40,
|
||||
"unexpected ShellCore request size");
|
||||
_Static_assert(offsetof(sm_shellcore_request_t, payload) == 8,
|
||||
"unexpected ShellCore request payload offset");
|
||||
_Static_assert(offsetof(sm_shellcore_request_t, payload.workspace.app_id) == 8,
|
||||
"unexpected ShellCore workspace app id offset");
|
||||
_Static_assert(offsetof(sm_shellcore_request_t, payload.app_exit.app_id) == 8,
|
||||
"unexpected ShellCore app-exit app id offset");
|
||||
_Static_assert(sizeof(sm_shellcore_response_t) == 4,
|
||||
"unexpected ShellCore response size");
|
||||
|
||||
typedef struct {
|
||||
char title_id[MAX_TITLE_ID];
|
||||
uint32_t app_id;
|
||||
bool game_exited;
|
||||
} shellcore_mount_owner_t;
|
||||
|
||||
typedef struct {
|
||||
pthread_t thread;
|
||||
pthread_mutex_t mutex;
|
||||
@@ -34,13 +42,12 @@ typedef struct {
|
||||
bool stop_requested;
|
||||
int listen_fd;
|
||||
int client_fd;
|
||||
// Owner of the prepared runtime mount, not the lifecycle active-game state.
|
||||
char prepared_title_id[MAX_TITLE_ID];
|
||||
uint32_t prepared_app_id;
|
||||
bool prepared_game_exited;
|
||||
// The incoming title is mounted before launchApp closes the outgoing game.
|
||||
shellcore_mount_owner_t prepared;
|
||||
shellcore_mount_owner_t outgoing;
|
||||
bool prepare_in_progress;
|
||||
bool launch_pending;
|
||||
bool release_in_progress;
|
||||
char releasing_title_id[MAX_TITLE_ID];
|
||||
} shellcore_service_state_t;
|
||||
|
||||
static shellcore_service_state_t g_service = {
|
||||
@@ -49,30 +56,69 @@ static shellcore_service_state_t g_service = {
|
||||
.client_fd = -1,
|
||||
};
|
||||
|
||||
#define SHELLCORE_RELEASE_MAX_ATTEMPTS 5u
|
||||
#define SHELLCORE_RELEASE_RETRY_DELAY_US 150000u
|
||||
|
||||
static bool mount_owner_matches(const shellcore_mount_owner_t *owner,
|
||||
const char *title_id) {
|
||||
return owner->title_id[0] != '\0' && strcmp(owner->title_id, title_id) == 0;
|
||||
}
|
||||
|
||||
static shellcore_mount_owner_t *find_mount_owner_locked(const char *title_id) {
|
||||
if (mount_owner_matches(&g_service.prepared, title_id))
|
||||
return &g_service.prepared;
|
||||
if (mount_owner_matches(&g_service.outgoing, title_id))
|
||||
return &g_service.outgoing;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void clear_mount_owner(shellcore_mount_owner_t *owner) {
|
||||
memset(owner, 0, sizeof(*owner));
|
||||
}
|
||||
|
||||
static void set_mount_owner(shellcore_mount_owner_t *owner,
|
||||
const char *title_id) {
|
||||
clear_mount_owner(owner);
|
||||
(void)strlcpy(owner->title_id, title_id, sizeof(owner->title_id));
|
||||
}
|
||||
|
||||
static int claim_prepared_title(const char *title_id, bool launch_request,
|
||||
bool *claimed_out) {
|
||||
bool changed = false;
|
||||
bool switched = false;
|
||||
char outgoing_title_id[MAX_TITLE_ID] = {0};
|
||||
if (claimed_out)
|
||||
*claimed_out = false;
|
||||
pthread_mutex_lock(&g_service.mutex);
|
||||
if (g_service.release_in_progress || g_service.prepare_in_progress) {
|
||||
if (g_service.prepare_in_progress ||
|
||||
strcmp(g_service.releasing_title_id, title_id) == 0) {
|
||||
pthread_mutex_unlock(&g_service.mutex);
|
||||
return EBUSY;
|
||||
}
|
||||
if (g_service.prepared_title_id[0] == '\0') {
|
||||
(void)strlcpy(g_service.prepared_title_id, title_id,
|
||||
sizeof(g_service.prepared_title_id));
|
||||
g_service.prepared_app_id = 0;
|
||||
g_service.prepared_game_exited = false;
|
||||
if (g_service.prepared.title_id[0] == '\0') {
|
||||
set_mount_owner(&g_service.prepared, title_id);
|
||||
g_service.prepare_in_progress = true;
|
||||
g_service.launch_pending = launch_request;
|
||||
changed = true;
|
||||
if (claimed_out)
|
||||
*claimed_out = true;
|
||||
} else if (strcmp(g_service.prepared_title_id, title_id) != 0) {
|
||||
pthread_mutex_unlock(&g_service.mutex);
|
||||
return EBUSY;
|
||||
} else if (g_service.prepared_game_exited) {
|
||||
} else if (!mount_owner_matches(&g_service.prepared, title_id)) {
|
||||
if (!launch_request || g_service.prepared.app_id == 0 ||
|
||||
g_service.outgoing.title_id[0] != '\0') {
|
||||
pthread_mutex_unlock(&g_service.mutex);
|
||||
return EBUSY;
|
||||
}
|
||||
g_service.outgoing = g_service.prepared;
|
||||
(void)strlcpy(outgoing_title_id, g_service.outgoing.title_id,
|
||||
sizeof(outgoing_title_id));
|
||||
set_mount_owner(&g_service.prepared, title_id);
|
||||
g_service.prepare_in_progress = true;
|
||||
g_service.launch_pending = true;
|
||||
changed = true;
|
||||
switched = true;
|
||||
if (claimed_out)
|
||||
*claimed_out = true;
|
||||
} else if (g_service.prepared.game_exited) {
|
||||
pthread_mutex_unlock(&g_service.mutex);
|
||||
return EBUSY;
|
||||
} else {
|
||||
@@ -83,16 +129,39 @@ static int claim_prepared_title(const char *title_id, bool launch_request,
|
||||
pthread_mutex_unlock(&g_service.mutex);
|
||||
if (changed)
|
||||
sm_scanner_wake();
|
||||
if (switched) {
|
||||
log_debug(" [SHELLCORE] game switch prepared: outgoing=%s incoming=%s",
|
||||
outgoing_title_id, title_id);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void clear_prepared_title(const char *title_id) {
|
||||
static void clear_owned_title(const char *title_id) {
|
||||
bool changed = false;
|
||||
pthread_mutex_lock(&g_service.mutex);
|
||||
if (strcmp(g_service.prepared_title_id, title_id) == 0) {
|
||||
g_service.prepared_title_id[0] = '\0';
|
||||
g_service.prepared_app_id = 0;
|
||||
g_service.prepared_game_exited = false;
|
||||
shellcore_mount_owner_t *owner = find_mount_owner_locked(title_id);
|
||||
if (owner) {
|
||||
if (owner == &g_service.prepared) {
|
||||
g_service.prepare_in_progress = false;
|
||||
g_service.launch_pending = false;
|
||||
}
|
||||
clear_mount_owner(owner);
|
||||
changed = true;
|
||||
}
|
||||
pthread_mutex_unlock(&g_service.mutex);
|
||||
if (changed)
|
||||
sm_scanner_wake();
|
||||
}
|
||||
|
||||
static void rollback_prepared_title(const char *title_id) {
|
||||
bool changed = false;
|
||||
pthread_mutex_lock(&g_service.mutex);
|
||||
if (mount_owner_matches(&g_service.prepared, title_id)) {
|
||||
clear_mount_owner(&g_service.prepared);
|
||||
if (g_service.outgoing.title_id[0] != '\0') {
|
||||
g_service.prepared = g_service.outgoing;
|
||||
clear_mount_owner(&g_service.outgoing);
|
||||
}
|
||||
g_service.prepare_in_progress = false;
|
||||
g_service.launch_pending = false;
|
||||
changed = true;
|
||||
@@ -105,7 +174,7 @@ static void clear_prepared_title(const char *title_id) {
|
||||
static void finish_prepared_title(const char *title_id,
|
||||
bool cancel_launch) {
|
||||
pthread_mutex_lock(&g_service.mutex);
|
||||
if (strcmp(g_service.prepared_title_id, title_id) == 0) {
|
||||
if (mount_owner_matches(&g_service.prepared, title_id)) {
|
||||
g_service.prepare_in_progress = false;
|
||||
if (cancel_launch)
|
||||
g_service.launch_pending = false;
|
||||
@@ -116,24 +185,37 @@ static void finish_prepared_title(const char *title_id,
|
||||
static int begin_runtime_release(const char *title_id,
|
||||
bool public_request) {
|
||||
pthread_mutex_lock(&g_service.mutex);
|
||||
bool different_title = g_service.prepared_title_id[0] != '\0' &&
|
||||
strcmp(g_service.prepared_title_id, title_id) != 0;
|
||||
bool blocked = different_title || g_service.release_in_progress ||
|
||||
g_service.prepare_in_progress ||
|
||||
(public_request && (g_service.launch_pending ||
|
||||
g_service.prepared_app_id != 0));
|
||||
if (!blocked)
|
||||
g_service.release_in_progress = true;
|
||||
shellcore_mount_owner_t *owner = find_mount_owner_locked(title_id);
|
||||
bool prepared = owner == &g_service.prepared;
|
||||
bool has_owner = g_service.prepared.title_id[0] != '\0' ||
|
||||
g_service.outgoing.title_id[0] != '\0';
|
||||
bool different_title = has_owner && !owner;
|
||||
bool blocked = different_title || g_service.releasing_title_id[0] != '\0' ||
|
||||
(prepared && g_service.prepare_in_progress) ||
|
||||
(public_request && prepared &&
|
||||
(g_service.launch_pending || g_service.prepared.app_id != 0));
|
||||
if (!blocked) {
|
||||
(void)strlcpy(g_service.releasing_title_id, title_id,
|
||||
sizeof(g_service.releasing_title_id));
|
||||
}
|
||||
pthread_mutex_unlock(&g_service.mutex);
|
||||
return blocked ? EBUSY : 0;
|
||||
}
|
||||
|
||||
static void end_runtime_release(void) {
|
||||
static void end_runtime_release(const char *title_id) {
|
||||
pthread_mutex_lock(&g_service.mutex);
|
||||
g_service.release_in_progress = false;
|
||||
if (strcmp(g_service.releasing_title_id, title_id) == 0)
|
||||
g_service.releasing_title_id[0] = '\0';
|
||||
pthread_mutex_unlock(&g_service.mutex);
|
||||
}
|
||||
|
||||
static bool shellcore_service_stopping(void) {
|
||||
pthread_mutex_lock(&g_service.mutex);
|
||||
bool stopping = g_service.stop_requested;
|
||||
pthread_mutex_unlock(&g_service.mutex);
|
||||
return stopping;
|
||||
}
|
||||
|
||||
static bool find_required_image_layer(const char *root,
|
||||
const char *runtime_source,
|
||||
unsigned int depth,
|
||||
@@ -253,6 +335,59 @@ static bool prepare_image_source(const char *title_id,
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool resumed_runtime_source_ready(const char *title_id,
|
||||
char runtime_source[MAX_PATH]) {
|
||||
char refreshed_source[MAX_PATH];
|
||||
if (read_mount_link(title_id, refreshed_source, sizeof(refreshed_source)) &&
|
||||
strcmp(refreshed_source, runtime_source) != 0) {
|
||||
(void)strlcpy(runtime_source, refreshed_source, MAX_PATH);
|
||||
log_debug(" [SHELLCORE] resumed source path updated: %s -> %s", title_id,
|
||||
runtime_source);
|
||||
}
|
||||
|
||||
if (is_usb_storage_path(runtime_source)) {
|
||||
char eboot_path[MAX_PATH];
|
||||
int written =
|
||||
snprintf(eboot_path, sizeof(eboot_path), "%s/eboot.bin", runtime_source);
|
||||
return written > 0 && (size_t)written < sizeof(eboot_path) &&
|
||||
path_exists(eboot_path);
|
||||
}
|
||||
if (!is_under_image_mount_base(runtime_source))
|
||||
return true;
|
||||
|
||||
char image_chain[MAX_IMAGE_CHAIN_DEPTH][MAX_PATH];
|
||||
size_t image_count = 0;
|
||||
if (!read_mount_image_chain(title_id, image_chain, &image_count) ||
|
||||
image_count == 0) {
|
||||
return false;
|
||||
}
|
||||
return !is_usb_storage_path(image_chain[0]) || path_exists(image_chain[0]);
|
||||
}
|
||||
|
||||
static bool wait_for_resumed_runtime_source(const char *title_id,
|
||||
char runtime_source[MAX_PATH]) {
|
||||
if (!runtime_resume_grace_active())
|
||||
return true;
|
||||
|
||||
if (resumed_runtime_source_ready(title_id, runtime_source))
|
||||
return true;
|
||||
|
||||
log_debug(" [SHELLCORE] waiting for resumed source: %s", title_id);
|
||||
while (runtime_resume_grace_active() && !runtime_sleep_mode_active() &&
|
||||
!should_stop_requested() && !shellcore_service_stopping()) {
|
||||
if (resumed_runtime_source_ready(title_id, runtime_source)) {
|
||||
log_debug(" [SHELLCORE] resumed source ready: %s", title_id);
|
||||
return true;
|
||||
}
|
||||
(void)sceKernelUsleep(RUNTIME_RESUME_POLL_US);
|
||||
}
|
||||
if (runtime_sleep_mode_active() || should_stop_requested() ||
|
||||
shellcore_service_stopping()) {
|
||||
return false;
|
||||
}
|
||||
return resumed_runtime_source_ready(title_id, runtime_source);
|
||||
}
|
||||
|
||||
static bool prepare_title_runtime(const char *title_id,
|
||||
const char *source_path) {
|
||||
if (!prepare_image_source(title_id, source_path))
|
||||
@@ -283,6 +418,8 @@ static int mount_managed_title_runtime(const char *title_id,
|
||||
char source_path[MAX_PATH];
|
||||
if (!read_mount_link(title_id, source_path, sizeof(source_path)))
|
||||
return allow_unmanaged ? 0 : ENOENT;
|
||||
if (!wait_for_resumed_runtime_source(title_id, source_path))
|
||||
return EIO;
|
||||
|
||||
bool claimed = false;
|
||||
int claim_status =
|
||||
@@ -301,7 +438,7 @@ static int mount_managed_title_runtime(const char *title_id,
|
||||
}
|
||||
int prepare_errno = errno;
|
||||
if (claimed)
|
||||
clear_prepared_title(title_id);
|
||||
rollback_prepared_title(title_id);
|
||||
else
|
||||
finish_prepared_title(title_id, true);
|
||||
return prepare_errno == EBUSY ? EBUSY : EIO;
|
||||
@@ -313,8 +450,15 @@ bool sm_shellcore_ensure_title_runtime(const char *title_id) {
|
||||
|
||||
static int handle_launch_request(const char *title_id) {
|
||||
int status = mount_managed_title_runtime(title_id, false, true);
|
||||
if (status != 0)
|
||||
if (status != 0) {
|
||||
// The launch hook also observes stock games and ShellCore system apps.
|
||||
// ENOENT/EINVAL mean "not managed here", not a runtime mount failure.
|
||||
if (status != ENOENT && status != EINVAL) {
|
||||
log_debug(" [SHELLCORE] launch mount unavailable: %s status=%d (%s)",
|
||||
title_id, status, strerror(status));
|
||||
}
|
||||
return status;
|
||||
}
|
||||
log_debug(" [SHELLCORE] launch mount ready: %s", title_id);
|
||||
return 0;
|
||||
}
|
||||
@@ -339,13 +483,51 @@ int sm_shellcore_unmount_title_runtime(const char *title_id) {
|
||||
return status;
|
||||
}
|
||||
|
||||
static size_t read_other_owned_image_chain(
|
||||
const char *releasing_title_id,
|
||||
char image_chain[MAX_IMAGE_CHAIN_DEPTH][MAX_PATH]) {
|
||||
char other_title_id[MAX_TITLE_ID] = {0};
|
||||
pthread_mutex_lock(&g_service.mutex);
|
||||
if (g_service.prepared.title_id[0] != '\0' &&
|
||||
strcmp(g_service.prepared.title_id, releasing_title_id) != 0)
|
||||
(void)strlcpy(other_title_id, g_service.prepared.title_id, MAX_TITLE_ID);
|
||||
else if (g_service.outgoing.title_id[0] != '\0' &&
|
||||
strcmp(g_service.outgoing.title_id, releasing_title_id) != 0)
|
||||
(void)strlcpy(other_title_id, g_service.outgoing.title_id, MAX_TITLE_ID);
|
||||
pthread_mutex_unlock(&g_service.mutex);
|
||||
|
||||
if (other_title_id[0] == '\0')
|
||||
return 0;
|
||||
|
||||
size_t image_count = 0;
|
||||
if (!read_mount_image_chain(other_title_id, image_chain, &image_count))
|
||||
return 0;
|
||||
return image_count;
|
||||
}
|
||||
|
||||
static bool image_chain_contains(
|
||||
const char image_chain[MAX_IMAGE_CHAIN_DEPTH][MAX_PATH],
|
||||
size_t image_count, const char *image_path) {
|
||||
for (size_t i = 0; i < image_count; ++i) {
|
||||
if (strcmp(image_chain[i], image_path) == 0)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool release_backing_image(const char *title_id) {
|
||||
char image_chain[MAX_IMAGE_CHAIN_DEPTH][MAX_PATH];
|
||||
size_t image_count = 0;
|
||||
if (!read_mount_image_chain(title_id, image_chain, &image_count))
|
||||
return true;
|
||||
|
||||
char retained_chain[MAX_IMAGE_CHAIN_DEPTH][MAX_PATH];
|
||||
size_t retained_count =
|
||||
read_other_owned_image_chain(title_id, retained_chain);
|
||||
for (size_t layer = image_count; layer > 0; --layer) {
|
||||
if (image_chain_contains(retained_chain, retained_count,
|
||||
image_chain[layer - 1u]))
|
||||
continue;
|
||||
if (!release_runtime_image_mount(image_chain[layer - 1u]))
|
||||
return false;
|
||||
}
|
||||
@@ -361,18 +543,47 @@ static bool release_title_runtime(const char *title_id,
|
||||
errno = begin_status;
|
||||
return false;
|
||||
}
|
||||
runtime_mount_state_lock();
|
||||
log_debug(" [SHELLCORE] runtime release start: %s", title_id);
|
||||
bool released = unmount_title_runtime_layers(title_id);
|
||||
if (released)
|
||||
released = release_backing_image(title_id);
|
||||
runtime_mount_state_unlock();
|
||||
bool released = false;
|
||||
int release_errno = 0;
|
||||
unsigned int attempts = public_request ? 1u : SHELLCORE_RELEASE_MAX_ATTEMPTS;
|
||||
unsigned int attempt = 0;
|
||||
for (; attempt < attempts; ++attempt) {
|
||||
bool diagnose_busy = attempt + 1u == attempts;
|
||||
runtime_mount_state_lock();
|
||||
released = diagnose_busy
|
||||
? unmount_title_runtime_layers(title_id)
|
||||
: unmount_title_runtime_layers_quiet(title_id);
|
||||
if (released && !runtime_config()->persistent_image_mounts)
|
||||
released = release_backing_image(title_id);
|
||||
release_errno = released ? 0 : errno;
|
||||
runtime_mount_state_unlock();
|
||||
|
||||
if (released || release_errno != EBUSY || attempt + 1u == attempts ||
|
||||
shellcore_service_stopping()) {
|
||||
break;
|
||||
}
|
||||
if (attempt == 0) {
|
||||
log_debug(" [SHELLCORE] runtime release busy; fast retry armed: %s "
|
||||
"attempts=%u delay_ms=%u",
|
||||
title_id, attempts - 1u,
|
||||
SHELLCORE_RELEASE_RETRY_DELAY_US / 1000u);
|
||||
}
|
||||
(void)sceKernelUsleep(SHELLCORE_RELEASE_RETRY_DELAY_US);
|
||||
}
|
||||
if (released && attempt > 0) {
|
||||
log_debug(" [SHELLCORE] runtime release completed after retry: %s "
|
||||
"attempt=%u",
|
||||
title_id, attempt + 1u);
|
||||
}
|
||||
// The lifecycle owner is gone even when a short-lived ShellCore sandbox
|
||||
// nullfs still pins the title stack. Leaving the title marked as prepared
|
||||
// would block scanner cleanup forever. The intact stack remains discoverable
|
||||
// through mount.lnk and is retried by the normal full-scan cleanup cycle.
|
||||
clear_prepared_title(title_id);
|
||||
end_runtime_release();
|
||||
clear_owned_title(title_id);
|
||||
end_runtime_release(title_id);
|
||||
if (!released && release_errno != 0)
|
||||
errno = release_errno;
|
||||
return released;
|
||||
}
|
||||
|
||||
@@ -386,9 +597,9 @@ void sm_shellcore_service_bind_prepared_app(const char *title_id,
|
||||
return;
|
||||
|
||||
pthread_mutex_lock(&g_service.mutex);
|
||||
if (strcmp(g_service.prepared_title_id, title_id) == 0) {
|
||||
g_service.prepared_app_id = app_id;
|
||||
g_service.prepared_game_exited = false;
|
||||
if (mount_owner_matches(&g_service.prepared, title_id)) {
|
||||
g_service.prepared.app_id = app_id;
|
||||
g_service.prepared.game_exited = false;
|
||||
g_service.prepare_in_progress = false;
|
||||
g_service.launch_pending = false;
|
||||
}
|
||||
@@ -401,9 +612,9 @@ bool sm_shellcore_service_note_game_exit(const char *title_id) {
|
||||
|
||||
bool changed = false;
|
||||
pthread_mutex_lock(&g_service.mutex);
|
||||
bool prepared = strcmp(g_service.prepared_title_id, title_id) == 0;
|
||||
if (prepared && !g_service.prepared_game_exited) {
|
||||
g_service.prepared_game_exited = true;
|
||||
shellcore_mount_owner_t *owner = find_mount_owner_locked(title_id);
|
||||
if (owner && !owner->game_exited) {
|
||||
owner->game_exited = true;
|
||||
changed = true;
|
||||
}
|
||||
pthread_mutex_unlock(&g_service.mutex);
|
||||
@@ -413,9 +624,10 @@ bool sm_shellcore_service_note_game_exit(const char *title_id) {
|
||||
bool sm_shellcore_service_has_prepared_mount(void) {
|
||||
pthread_mutex_lock(&g_service.mutex);
|
||||
// Keep scanner cleanup blocked after process exit as well. Only the
|
||||
// post-unmountWorkspace event (or the explicit no-hook fallback) owns the
|
||||
// post-onAppExit event (or the explicit no-hook fallback) owns the
|
||||
// transition that clears this state and releases the runtime stack.
|
||||
bool prepared = g_service.prepared_title_id[0] != '\0';
|
||||
bool prepared = g_service.prepared.title_id[0] != '\0' ||
|
||||
g_service.outgoing.title_id[0] != '\0';
|
||||
pthread_mutex_unlock(&g_service.mutex);
|
||||
return prepared;
|
||||
}
|
||||
@@ -425,45 +637,75 @@ bool sm_shellcore_service_title_is_prepared(const char *title_id) {
|
||||
return false;
|
||||
|
||||
pthread_mutex_lock(&g_service.mutex);
|
||||
bool prepared = strcmp(g_service.prepared_title_id, title_id) == 0;
|
||||
bool prepared = find_mount_owner_locked(title_id) != NULL;
|
||||
pthread_mutex_unlock(&g_service.mutex);
|
||||
return prepared;
|
||||
}
|
||||
|
||||
static bool reset_switch_title_mount(const char *title_id) {
|
||||
runtime_mount_state_lock();
|
||||
bool reset = unmount_title_runtime_layers(title_id);
|
||||
int reset_errno = reset ? 0 : errno;
|
||||
runtime_mount_state_unlock();
|
||||
if (!reset)
|
||||
errno = reset_errno != 0 ? reset_errno : EIO;
|
||||
return reset;
|
||||
}
|
||||
|
||||
static int handle_launch_failed_request(const char *title_id) {
|
||||
if (!title_id || title_id[0] == '\0')
|
||||
return 0;
|
||||
|
||||
pthread_mutex_lock(&g_service.mutex);
|
||||
bool prepared = strcmp(g_service.prepared_title_id, title_id) == 0;
|
||||
bool prepared = mount_owner_matches(&g_service.prepared, title_id);
|
||||
bool switch_pending = prepared && g_service.outgoing.title_id[0] != '\0';
|
||||
pthread_mutex_unlock(&g_service.mutex);
|
||||
if (!prepared)
|
||||
return 0;
|
||||
if (switch_pending) {
|
||||
// ShellCore retries launchApp while closing the outgoing game. Keep the
|
||||
// expensive backing images attached, but recreate title nullfs/overlay on
|
||||
// every retry so the final mount is newer than ShellCore's exit cleanup.
|
||||
bool reset = reset_switch_title_mount(title_id);
|
||||
if (reset) {
|
||||
log_debug(" [SHELLCORE] launch retry prepared during game switch: %s",
|
||||
title_id);
|
||||
} else {
|
||||
log_debug(" [SHELLCORE] failed to reset switch mount: %s (%s)",
|
||||
title_id, strerror(errno));
|
||||
}
|
||||
return reset ? 0 : EBUSY;
|
||||
}
|
||||
return sm_shellcore_release_title_runtime(title_id) ? 0 : EBUSY;
|
||||
}
|
||||
|
||||
static int handle_workspace_unmounted(uint32_t app_id) {
|
||||
static bool resolve_app_exit_title(uint32_t app_id,
|
||||
char title_id[MAX_TITLE_ID]) {
|
||||
if (app_id == 0)
|
||||
return 0;
|
||||
return false;
|
||||
|
||||
char title_id[MAX_TITLE_ID] = {0};
|
||||
pthread_mutex_lock(&g_service.mutex);
|
||||
if (g_service.prepared_app_id == app_id &&
|
||||
g_service.prepared_title_id[0] != '\0') {
|
||||
(void)strlcpy(title_id, g_service.prepared_title_id, sizeof(title_id));
|
||||
}
|
||||
// During a game switch the outgoing app owns the first exit notification,
|
||||
// even if ShellCore quickly reuses an application id for the incoming game.
|
||||
if (g_service.outgoing.app_id == app_id &&
|
||||
g_service.outgoing.title_id[0] != '\0')
|
||||
(void)strlcpy(title_id, g_service.outgoing.title_id, MAX_TITLE_ID);
|
||||
else if (g_service.prepared.app_id == app_id &&
|
||||
g_service.prepared.title_id[0] != '\0')
|
||||
(void)strlcpy(title_id, g_service.prepared.title_id, MAX_TITLE_ID);
|
||||
pthread_mutex_unlock(&g_service.mutex);
|
||||
if (title_id[0] == '\0')
|
||||
return 0;
|
||||
return false;
|
||||
|
||||
log_debug(" [SHELLCORE] unmountWorkspace complete: %s app_id=0x%08X",
|
||||
log_debug(" [SHELLCORE] onAppExit cleanup complete: %s app_id=0x%08X",
|
||||
title_id, app_id);
|
||||
return sm_shellcore_release_title_runtime(title_id) ? 0 : EBUSY;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void handle_client(int fd) {
|
||||
sm_shellcore_request_t request;
|
||||
sm_shellcore_response_t response;
|
||||
char release_title_id[MAX_TITLE_ID] = {0};
|
||||
if (!sm_socket_read_full(fd, &request, sizeof(request)))
|
||||
return;
|
||||
if (request.magic != SM_SHELLCORE_PROTOCOL_MAGIC ||
|
||||
@@ -477,13 +719,16 @@ static void handle_client(int fd) {
|
||||
response.status = handle_launch_request(request.payload.title_id);
|
||||
} else if (request.operation == SM_SHELLCORE_REQUEST_LAUNCH_FAILED) {
|
||||
response.status = handle_launch_failed_request(request.payload.title_id);
|
||||
} else if (request.operation == SM_SHELLCORE_REQUEST_WORKSPACE_UNMOUNTED) {
|
||||
response.status =
|
||||
handle_workspace_unmounted(request.payload.workspace.app_id);
|
||||
} else if (request.operation == SM_SHELLCORE_REQUEST_APP_EXITED) {
|
||||
(void)resolve_app_exit_title(request.payload.app_exit.app_id,
|
||||
release_title_id);
|
||||
response.status = 0;
|
||||
} else {
|
||||
response.status = ENOTSUP;
|
||||
}
|
||||
(void)sm_socket_write_full(fd, &response, sizeof(response));
|
||||
if (release_title_id[0] != '\0')
|
||||
(void)sm_shellcore_release_title_runtime(release_title_id);
|
||||
}
|
||||
|
||||
static void *service_thread_main(void *arg) {
|
||||
@@ -553,12 +798,11 @@ bool sm_shellcore_service_start(void) {
|
||||
g_service.listen_fd = fd;
|
||||
g_service.client_fd = -1;
|
||||
g_service.stop_requested = false;
|
||||
g_service.prepared_title_id[0] = '\0';
|
||||
g_service.prepared_app_id = 0;
|
||||
g_service.prepared_game_exited = false;
|
||||
clear_mount_owner(&g_service.prepared);
|
||||
clear_mount_owner(&g_service.outgoing);
|
||||
g_service.prepare_in_progress = false;
|
||||
g_service.launch_pending = false;
|
||||
g_service.release_in_progress = false;
|
||||
g_service.releasing_title_id[0] = '\0';
|
||||
int rc = pthread_create(&g_service.thread, NULL, service_thread_main, NULL);
|
||||
if (rc != 0) {
|
||||
close(fd);
|
||||
@@ -591,12 +835,11 @@ void sm_shellcore_service_stop(void) {
|
||||
g_service.started = false;
|
||||
g_service.stop_requested = false;
|
||||
g_service.client_fd = -1;
|
||||
g_service.prepared_title_id[0] = '\0';
|
||||
g_service.prepared_app_id = 0;
|
||||
g_service.prepared_game_exited = false;
|
||||
clear_mount_owner(&g_service.prepared);
|
||||
clear_mount_owner(&g_service.outgoing);
|
||||
g_service.prepare_in_progress = false;
|
||||
g_service.launch_pending = false;
|
||||
g_service.release_in_progress = false;
|
||||
g_service.releasing_title_id[0] = '\0';
|
||||
pthread_mutex_unlock(&g_service.mutex);
|
||||
(void)unlink(SM_SHELLCORE_SOCKET_PATH);
|
||||
}
|
||||
@@ -135,8 +135,7 @@ void notify_duplicate_title_once(const char *title_id, const char *path_a,
|
||||
if (entry->duplicate_notified_once)
|
||||
return;
|
||||
entry->duplicate_notified_once = true;
|
||||
notify_system("Duplicate %s ignored:\n%s\nexisting: %s", title_id, path_a,
|
||||
path_b);
|
||||
notify_system_l10n(SM_L10N_DUPLICATE_IGNORED, title_id, path_a, path_b);
|
||||
}
|
||||
|
||||
void clear_duplicate_title_notification(const char *title_id) {
|
||||
|
||||
BIN
Binary file not shown.
@@ -1,249 +0,0 @@
|
||||
#include "sm_platform.h"
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
|
||||
#include "sm_api_protocol.h"
|
||||
|
||||
#define API_TEST_MAX_PAYLOAD (1024u * 1024u)
|
||||
|
||||
static bool socket_write_full(int fd, const void *buffer, size_t size) {
|
||||
const uint8_t *bytes = (const uint8_t *)buffer;
|
||||
while (size > 0) {
|
||||
ssize_t count = send(fd, bytes, size, 0);
|
||||
if (count < 0 && errno == EINTR)
|
||||
continue;
|
||||
if (count <= 0)
|
||||
return false;
|
||||
bytes += (size_t)count;
|
||||
size -= (size_t)count;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool socket_read_full(int fd, void *buffer, size_t size) {
|
||||
uint8_t *bytes = (uint8_t *)buffer;
|
||||
while (size > 0) {
|
||||
ssize_t count = recv(fd, bytes, size, 0);
|
||||
if (count < 0 && errno == EINTR)
|
||||
continue;
|
||||
if (count <= 0)
|
||||
return false;
|
||||
bytes += (size_t)count;
|
||||
size -= (size_t)count;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void notify_result(const char *message) {
|
||||
notify_request_t request;
|
||||
memset(&request, 0, sizeof(request));
|
||||
(void)strlcpy(request.message, message, sizeof(request.message));
|
||||
(void)sceKernelSendNotificationRequest(0, &request, sizeof(request), 0);
|
||||
}
|
||||
|
||||
static bool api_request(uint16_t operation, uint32_t offset, uint32_t limit,
|
||||
const char *title_id, sm_api_response_t *response,
|
||||
void **payload_out) {
|
||||
*payload_out = NULL;
|
||||
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (fd < 0)
|
||||
return false;
|
||||
|
||||
struct sockaddr_un address;
|
||||
memset(&address, 0, sizeof(address));
|
||||
address.sun_family = AF_UNIX;
|
||||
(void)strlcpy(address.sun_path, SM_API_SOCKET_PATH,
|
||||
sizeof(address.sun_path));
|
||||
if (connect(fd, (struct sockaddr *)&address, sizeof(address)) != 0) {
|
||||
close(fd);
|
||||
return false;
|
||||
}
|
||||
|
||||
sm_api_request_t request;
|
||||
memset(&request, 0, sizeof(request));
|
||||
request.magic = SM_API_MAGIC;
|
||||
request.version = SM_API_VERSION;
|
||||
request.operation = operation;
|
||||
request.size = sizeof(request);
|
||||
request.offset = offset;
|
||||
request.limit = limit;
|
||||
if (title_id)
|
||||
(void)strlcpy(request.title_id, title_id, sizeof(request.title_id));
|
||||
|
||||
bool ok = socket_write_full(fd, &request, sizeof(request)) &&
|
||||
socket_read_full(fd, response, sizeof(*response));
|
||||
if (!ok)
|
||||
goto done;
|
||||
if (response->magic != SM_API_MAGIC ||
|
||||
response->version != SM_API_VERSION ||
|
||||
response->operation != operation ||
|
||||
response->size < sizeof(*response)) {
|
||||
errno = EPROTO;
|
||||
ok = false;
|
||||
goto done;
|
||||
}
|
||||
|
||||
uint32_t payload_size = response->size - sizeof(*response);
|
||||
if (payload_size > API_TEST_MAX_PAYLOAD ||
|
||||
(response->item_count != 0 &&
|
||||
(response->item_size == 0 ||
|
||||
response->item_count > payload_size / response->item_size)) ||
|
||||
(uint64_t)response->item_count * response->item_size != payload_size) {
|
||||
errno = EPROTO;
|
||||
ok = false;
|
||||
goto done;
|
||||
}
|
||||
if (payload_size > 0) {
|
||||
void *payload = malloc(payload_size);
|
||||
if (!payload || !socket_read_full(fd, payload, payload_size)) {
|
||||
free(payload);
|
||||
ok = false;
|
||||
goto done;
|
||||
}
|
||||
*payload_out = payload;
|
||||
}
|
||||
|
||||
done:
|
||||
close(fd);
|
||||
return ok;
|
||||
}
|
||||
|
||||
static bool test_version(void) {
|
||||
sm_api_response_t response;
|
||||
void *payload = NULL;
|
||||
if (!api_request(SM_API_GET_VERSION, 0, 0, NULL, &response, &payload))
|
||||
return false;
|
||||
bool ok = response.status == 0 && response.item_count == 1 &&
|
||||
response.item_size == sizeof(sm_api_version_info_t);
|
||||
if (ok) {
|
||||
const sm_api_version_info_t *version =
|
||||
(const sm_api_version_info_t *)payload;
|
||||
printf("[API-TEST] ShadowMount=%s API=%u capabilities=0x%08X\n",
|
||||
version->shadowmount_version, version->api_version,
|
||||
version->capabilities);
|
||||
}
|
||||
free(payload);
|
||||
return ok;
|
||||
}
|
||||
|
||||
static bool test_image_list(uint32_t *total_out) {
|
||||
uint32_t offset = 0;
|
||||
uint32_t total = 0;
|
||||
do {
|
||||
sm_api_response_t response;
|
||||
void *payload = NULL;
|
||||
if (!api_request(SM_API_LIST_IMAGES, offset, SM_API_MAX_PAGE_ITEMS, NULL,
|
||||
&response, &payload)) {
|
||||
return false;
|
||||
}
|
||||
bool ok = response.status == 0 &&
|
||||
response.item_size == sizeof(sm_api_image_info_t) &&
|
||||
response.item_count <= SM_API_MAX_PAGE_ITEMS;
|
||||
if (!ok) {
|
||||
free(payload);
|
||||
return false;
|
||||
}
|
||||
total = response.total_count;
|
||||
if (response.item_count == 0 && offset < total) {
|
||||
free(payload);
|
||||
return false;
|
||||
}
|
||||
offset += response.item_count;
|
||||
free(payload);
|
||||
} while (offset < total);
|
||||
*total_out = total;
|
||||
printf("[API-TEST] images=%u\n", total);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool test_game_list(uint32_t *total_out,
|
||||
char mount_candidate[SM_API_TITLE_ID_SIZE]) {
|
||||
uint32_t offset = 0;
|
||||
uint32_t total = 0;
|
||||
mount_candidate[0] = '\0';
|
||||
do {
|
||||
sm_api_response_t response;
|
||||
void *payload = NULL;
|
||||
if (!api_request(SM_API_LIST_GAMES, offset, SM_API_MAX_PAGE_ITEMS, NULL,
|
||||
&response, &payload)) {
|
||||
return false;
|
||||
}
|
||||
bool ok = response.status == 0 &&
|
||||
response.item_size == sizeof(sm_api_game_info_t) &&
|
||||
response.item_count <= SM_API_MAX_PAGE_ITEMS;
|
||||
if (!ok) {
|
||||
free(payload);
|
||||
return false;
|
||||
}
|
||||
const sm_api_game_info_t *games = (const sm_api_game_info_t *)payload;
|
||||
for (uint32_t i = 0; i < response.item_count; ++i) {
|
||||
uint32_t required = SM_API_GAME_MANAGED | SM_API_GAME_SOURCE_AVAILABLE;
|
||||
if (mount_candidate[0] == '\0' &&
|
||||
(games[i].flags & required) == required &&
|
||||
(games[i].flags & SM_API_GAME_MOUNTED) == 0) {
|
||||
(void)strlcpy(mount_candidate, games[i].title_id,
|
||||
SM_API_TITLE_ID_SIZE);
|
||||
}
|
||||
}
|
||||
total = response.total_count;
|
||||
if (response.item_count == 0 && offset < total) {
|
||||
free(payload);
|
||||
return false;
|
||||
}
|
||||
offset += response.item_count;
|
||||
free(payload);
|
||||
} while (offset < total);
|
||||
*total_out = total;
|
||||
printf("[API-TEST] games=%u candidate=%s\n", total,
|
||||
mount_candidate[0] ? mount_candidate : "none");
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool test_mount_cycle(const char *title_id, bool *skipped_out) {
|
||||
*skipped_out = title_id[0] == '\0';
|
||||
if (*skipped_out)
|
||||
return true;
|
||||
|
||||
sm_api_response_t response;
|
||||
void *payload = NULL;
|
||||
if (!api_request(SM_API_MOUNT_GAME, 0, 0, title_id, &response, &payload))
|
||||
return false;
|
||||
free(payload);
|
||||
if (response.status == EBUSY) {
|
||||
*skipped_out = true;
|
||||
printf("[API-TEST] mount cycle skipped: %s is busy\n", title_id);
|
||||
return true;
|
||||
}
|
||||
if (response.status != 0)
|
||||
return false;
|
||||
|
||||
payload = NULL;
|
||||
if (!api_request(SM_API_UNMOUNT_GAME, 0, 0, title_id, &response, &payload))
|
||||
return false;
|
||||
free(payload);
|
||||
printf("[API-TEST] mount cycle: %s unmount_status=%d\n", title_id,
|
||||
response.status);
|
||||
return response.status == 0;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
sceUserServiceInitialize(0);
|
||||
uint32_t image_count = 0;
|
||||
uint32_t game_count = 0;
|
||||
char candidate[SM_API_TITLE_ID_SIZE];
|
||||
bool cycle_skipped = false;
|
||||
|
||||
bool ok = test_version() && test_image_list(&image_count) &&
|
||||
test_game_list(&game_count, candidate) &&
|
||||
test_mount_cycle(candidate, &cycle_skipped);
|
||||
char message[256];
|
||||
(void)snprintf(message, sizeof(message),
|
||||
"ShadowMount API test: %s\nimages=%u games=%u mount=%s",
|
||||
ok ? "PASS" : "FAIL", image_count, game_count,
|
||||
cycle_skipped ? "SKIP" : (ok ? "PASS" : "FAIL"));
|
||||
notify_result(message);
|
||||
printf("[API-TEST] %s\n", message);
|
||||
sceUserServiceTerminate();
|
||||
return ok ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Validate a running ShadowMount HTTP/JSON API endpoint."""
|
||||
|
||||
import argparse
|
||||
import errno
|
||||
import http.client
|
||||
import json
|
||||
import sys
|
||||
from typing import Any, Dict, Optional, Tuple
|
||||
|
||||
|
||||
API_VERSION = 1
|
||||
MAX_RESPONSE_SIZE = 16 * 1024 * 1024
|
||||
|
||||
ROUTE_VERSION = "/api/v1/version"
|
||||
ROUTE_IMAGES = "/api/v1/images"
|
||||
ROUTE_GAMES = "/api/v1/games"
|
||||
ROUTE_MOUNT = "/api/v1/games/mount"
|
||||
ROUTE_UNMOUNT = "/api/v1/games/unmount"
|
||||
|
||||
|
||||
class ApiTestError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
class ApiClient:
|
||||
def __init__(self, address: str, port: int, timeout: float) -> None:
|
||||
self.address = address
|
||||
self.port = port
|
||||
self.timeout = timeout
|
||||
|
||||
def request(
|
||||
self,
|
||||
method: str,
|
||||
route: str,
|
||||
body: bytes = b"",
|
||||
headers: Optional[Dict[str, str]] = None,
|
||||
) -> Tuple[int, Dict[str, str], bytes]:
|
||||
connection = http.client.HTTPConnection(
|
||||
self.address, self.port, timeout=self.timeout
|
||||
)
|
||||
try:
|
||||
connection.request(method, route, body=body, headers=headers or {})
|
||||
response = connection.getresponse()
|
||||
response_body = response.read(MAX_RESPONSE_SIZE + 1)
|
||||
response_headers = {
|
||||
name.lower(): value.strip() for name, value in response.getheaders()
|
||||
}
|
||||
response_status = response.status
|
||||
finally:
|
||||
connection.close()
|
||||
|
||||
if len(response_body) > MAX_RESPONSE_SIZE:
|
||||
raise ApiTestError("response body exceeds 16 MiB")
|
||||
if response_headers.get("access-control-allow-origin") != "*":
|
||||
raise ApiTestError(f"{method} {route}: missing CORS allow-origin header")
|
||||
|
||||
content_length = response_headers.get("content-length")
|
||||
if content_length is None:
|
||||
raise ApiTestError(f"{method} {route}: missing Content-Length header")
|
||||
try:
|
||||
declared_size = int(content_length, 10)
|
||||
except ValueError as error:
|
||||
raise ApiTestError(
|
||||
f"{method} {route}: invalid Content-Length header"
|
||||
) from error
|
||||
if declared_size != len(response_body):
|
||||
raise ApiTestError(
|
||||
f"{method} {route}: Content-Length is {declared_size}, "
|
||||
f"received {len(response_body)} bytes"
|
||||
)
|
||||
return response_status, response_headers, response_body
|
||||
|
||||
def post(self, route: str, payload: Dict[str, Any]) -> Tuple[int, Dict[str, Any]]:
|
||||
body = json.dumps(payload, separators=(",", ":")).encode("utf-8")
|
||||
status, headers, response_body = self.request(
|
||||
"POST",
|
||||
route,
|
||||
body,
|
||||
{
|
||||
"Content-Type": "application/json",
|
||||
"Content-Length": str(len(body)),
|
||||
"Connection": "close",
|
||||
},
|
||||
)
|
||||
if not headers.get("content-type", "").lower().startswith(
|
||||
"application/json"
|
||||
):
|
||||
raise ApiTestError(f"POST {route}: response is not JSON")
|
||||
try:
|
||||
response = json.loads(response_body.decode("utf-8"))
|
||||
except (UnicodeDecodeError, json.JSONDecodeError) as error:
|
||||
raise ApiTestError(f"POST {route}: invalid JSON response") from error
|
||||
if not isinstance(response, dict):
|
||||
raise ApiTestError(f"POST {route}: response must be a JSON object")
|
||||
return status, response
|
||||
|
||||
|
||||
def require(condition: bool, message: str) -> None:
|
||||
if not condition:
|
||||
raise ApiTestError(message)
|
||||
|
||||
|
||||
def integer_field(response: Dict[str, Any], key: str) -> int:
|
||||
value = response.get(key)
|
||||
if type(value) is not int:
|
||||
raise ApiTestError(f"response field {key!r} must be an integer")
|
||||
return value
|
||||
|
||||
|
||||
def test_preflight(client: ApiClient) -> None:
|
||||
status, headers, body = client.request(
|
||||
"OPTIONS",
|
||||
ROUTE_VERSION,
|
||||
headers={
|
||||
"Origin": "http://example.test",
|
||||
"Access-Control-Request-Method": "POST",
|
||||
"Access-Control-Request-Headers": "Content-Type",
|
||||
"Access-Control-Request-Private-Network": "true",
|
||||
"Connection": "close",
|
||||
},
|
||||
)
|
||||
require(status == 204, f"CORS preflight returned HTTP {status}")
|
||||
require(not body, "CORS preflight returned a response body")
|
||||
require(
|
||||
headers.get("access-control-allow-methods") == "POST, OPTIONS",
|
||||
"CORS preflight returned invalid allowed methods",
|
||||
)
|
||||
require(
|
||||
headers.get("access-control-allow-headers") == "Content-Type",
|
||||
"CORS preflight returned invalid allowed headers",
|
||||
)
|
||||
require(
|
||||
headers.get("access-control-allow-private-network") == "true",
|
||||
"CORS private-network access is not enabled",
|
||||
)
|
||||
print("[API-TEST] CORS preflight=PASS")
|
||||
|
||||
|
||||
def test_version(client: ApiClient) -> None:
|
||||
http_status, response = client.post(ROUTE_VERSION, {})
|
||||
require(http_status == 200, f"version request returned HTTP {http_status}")
|
||||
require(integer_field(response, "status") == 0, "version request failed")
|
||||
require(
|
||||
integer_field(response, "api_version") == API_VERSION,
|
||||
"unsupported API version",
|
||||
)
|
||||
version = response.get("shadowmount_version")
|
||||
require(isinstance(version, str), "missing ShadowMount version")
|
||||
print(f"[API-TEST] ShadowMount={version} API={API_VERSION}")
|
||||
|
||||
|
||||
def test_list(client: ApiClient, route: str, key: str) -> list:
|
||||
http_status, response = client.post(route, {})
|
||||
require(http_status == 200, f"{key} request returned HTTP {http_status}")
|
||||
require(integer_field(response, "status") == 0, f"{key} request failed")
|
||||
count = integer_field(response, "count")
|
||||
items = response.get(key)
|
||||
require(count >= 0, f"{key} count is negative")
|
||||
require(isinstance(items, list), f"{key} field must be an array")
|
||||
require(len(items) == count, f"{key} count does not match array length")
|
||||
require(
|
||||
all(isinstance(item, dict) for item in items),
|
||||
f"{key} array contains a non-object item",
|
||||
)
|
||||
print(f"[API-TEST] {key}={count}")
|
||||
return items
|
||||
|
||||
|
||||
def find_mount_candidate(games: list) -> str:
|
||||
for game in games:
|
||||
title_id = game.get("title_id")
|
||||
if (
|
||||
game.get("managed") is True
|
||||
and game.get("source_available") is True
|
||||
and game.get("mounted") is False
|
||||
and isinstance(title_id, str)
|
||||
and title_id
|
||||
):
|
||||
return title_id
|
||||
return ""
|
||||
|
||||
|
||||
def title_operation(client: ApiClient, route: str, title_id: str) -> Tuple[int, int]:
|
||||
http_status, response = client.post(route, {"title_id": title_id})
|
||||
return http_status, integer_field(response, "status")
|
||||
|
||||
|
||||
def test_mount_cycle(client: ApiClient, title_id: str) -> str:
|
||||
if not title_id:
|
||||
print("[API-TEST] mount cycle=SKIP (no candidate)")
|
||||
return "SKIP"
|
||||
|
||||
http_status, status = title_operation(client, ROUTE_MOUNT, title_id)
|
||||
if http_status == 409 and status == errno.EBUSY:
|
||||
print(f"[API-TEST] mount cycle=SKIP ({title_id} is busy)")
|
||||
return "SKIP"
|
||||
require(
|
||||
http_status == 200 and status == 0,
|
||||
f"mount {title_id} failed: HTTP {http_status}, status {status}",
|
||||
)
|
||||
|
||||
http_status, status = title_operation(client, ROUTE_UNMOUNT, title_id)
|
||||
require(
|
||||
http_status == 200 and status == 0,
|
||||
f"unmount {title_id} failed: HTTP {http_status}, status {status}",
|
||||
)
|
||||
print(f"[API-TEST] mount cycle=PASS ({title_id})")
|
||||
return "PASS"
|
||||
|
||||
|
||||
def valid_port(value: str) -> int:
|
||||
try:
|
||||
port = int(value, 10)
|
||||
except ValueError as error:
|
||||
raise argparse.ArgumentTypeError("port must be an integer") from error
|
||||
if not 1 <= port <= 65535:
|
||||
raise argparse.ArgumentTypeError("port must be in range 1..65535")
|
||||
return port
|
||||
|
||||
|
||||
def positive_timeout(value: str) -> float:
|
||||
try:
|
||||
timeout = float(value)
|
||||
except ValueError as error:
|
||||
raise argparse.ArgumentTypeError("timeout must be a number") from error
|
||||
if timeout <= 0:
|
||||
raise argparse.ArgumentTypeError("timeout must be positive")
|
||||
return timeout
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("address", help="PS5 IPv4 address or hostname")
|
||||
parser.add_argument("port", type=valid_port, help="ShadowMount HTTP API port")
|
||||
parser.add_argument(
|
||||
"--timeout", type=positive_timeout, default=5.0, help="request timeout"
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
client = ApiClient(args.address, args.port, args.timeout)
|
||||
try:
|
||||
test_preflight(client)
|
||||
test_version(client)
|
||||
images = test_list(client, ROUTE_IMAGES, "images")
|
||||
games = test_list(client, ROUTE_GAMES, "games")
|
||||
mount_result = test_mount_cycle(client, find_mount_candidate(games))
|
||||
except (ApiTestError, http.client.HTTPException, OSError) as error:
|
||||
print(f"[API-TEST] FAIL: {error}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
print(
|
||||
f"[API-TEST] PASS: images={len(images)} games={len(games)} "
|
||||
f"mount={mount_result}"
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -5,6 +5,13 @@ The generator identifies functions through semantic log strings, finds the
|
||||
containing function prologue and emits a compact firmware offset table. It is
|
||||
intentionally independent from IDA databases so the checked-in table can be
|
||||
reproduced from unpacked firmware files.
|
||||
|
||||
The runtime-release hook targets ``LncManager::onAppExit`` rather than an
|
||||
application/workspace destructor. ShellCore can destroy a launch-time
|
||||
``LncApplication`` object with the live app id, so destructor hooks produce a
|
||||
false exit event while the game is still running. ``onAppExit`` is the
|
||||
SysCore exit-event owner and returns after the firmware-specific exit cleanup
|
||||
path has run.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -19,7 +26,7 @@ from capstone import CS_ARCH_X86, CS_MODE_64, Cs
|
||||
PROLOGUE = b"\x55\x48\x89\xe5"
|
||||
TARGET_NAMES = (
|
||||
"launch_app",
|
||||
"unmount_workspace",
|
||||
"app_exit",
|
||||
"install_title_dir",
|
||||
"install_all",
|
||||
)
|
||||
@@ -54,6 +61,7 @@ class ShellCore:
|
||||
self.executable = segment
|
||||
if self.executable is None:
|
||||
raise ValueError(f"{path}: executable PT_LOAD not found")
|
||||
self._rip_references: list[tuple[int, int]] | None = None
|
||||
|
||||
def file_to_virtual(self, offset: int) -> int:
|
||||
for segment in self.loads:
|
||||
@@ -85,25 +93,42 @@ class ShellCore:
|
||||
offset += 1
|
||||
|
||||
def rip_xrefs(self, targets: list[int], slop: int = 0) -> list[int]:
|
||||
segment = self.executable
|
||||
assert segment is not None
|
||||
code = self.data[
|
||||
segment.file_offset : segment.file_offset + segment.file_size
|
||||
if self._rip_references is None:
|
||||
segment = self.executable
|
||||
assert segment is not None
|
||||
code = self.data[
|
||||
segment.file_offset : segment.file_offset + segment.file_size
|
||||
]
|
||||
references: list[tuple[int, int]] = []
|
||||
for index in range(len(code) - 7):
|
||||
if code[index] not in (0x48, 0x4C):
|
||||
continue
|
||||
if code[index + 1] not in (0x8D, 0x8B):
|
||||
continue
|
||||
if code[index + 2] & 0xC7 != 0x05:
|
||||
continue
|
||||
displacement = struct.unpack_from("<i", code, index + 3)[0]
|
||||
instruction_address = segment.virtual_address + index
|
||||
references.append(
|
||||
(instruction_address, instruction_address + 7 + displacement)
|
||||
)
|
||||
self._rip_references = references
|
||||
|
||||
if slop == 0:
|
||||
exact_targets = set(targets)
|
||||
return [
|
||||
address
|
||||
for address, destination in self._rip_references
|
||||
if destination in exact_targets
|
||||
]
|
||||
return [
|
||||
address
|
||||
for address, destination in self._rip_references
|
||||
if any(
|
||||
target - slop <= destination <= target + slop
|
||||
for target in targets
|
||||
)
|
||||
]
|
||||
result: list[int] = []
|
||||
for index in range(len(code) - 7):
|
||||
if code[index] not in (0x48, 0x4C):
|
||||
continue
|
||||
if code[index + 1] not in (0x8D, 0x8B):
|
||||
continue
|
||||
if code[index + 2] & 0xC7 != 0x05:
|
||||
continue
|
||||
displacement = struct.unpack_from("<i", code, index + 3)[0]
|
||||
instruction_address = segment.virtual_address + index
|
||||
destination = instruction_address + 7 + displacement
|
||||
if any(target - slop <= destination <= target + slop for target in targets):
|
||||
result.append(instruction_address)
|
||||
return result
|
||||
|
||||
def preceding_prologue(self, address: int, window: int = 0x40000) -> int:
|
||||
end = self.virtual_to_file(address)
|
||||
@@ -120,17 +145,32 @@ class ShellCore:
|
||||
raise ValueError(f"launchApp xrefs: {launch_xrefs!r}")
|
||||
launch = self.preceding_prologue(launch_xrefs[0])
|
||||
|
||||
unmount_strings = self.string_addresses(
|
||||
b"m_workspace.unmountWorkspace()", False
|
||||
app_exit_strings = self.string_addresses(
|
||||
b"[SceLncService] onAppExit() appId={0x%08x}", False
|
||||
)
|
||||
unmount_groups = {
|
||||
app_exit_groups = {
|
||||
self.preceding_prologue(xref)
|
||||
for xref in self.rip_xrefs(unmount_strings, slop=8)
|
||||
for xref in self.rip_xrefs(app_exit_strings, slop=8)
|
||||
}
|
||||
unmount_groups.discard(launch)
|
||||
if len(unmount_groups) != 1:
|
||||
raise ValueError(f"unmountWorkspace candidates: {unmount_groups!r}")
|
||||
unmount = next(iter(unmount_groups))
|
||||
if len(app_exit_groups) != 1:
|
||||
raise ValueError(f"onAppExit candidates: {app_exit_groups!r}")
|
||||
app_exit = next(iter(app_exit_groups))
|
||||
|
||||
# Validate the candidate with an independent source-expression log.
|
||||
# This prevents a changed format string from silently selecting an
|
||||
# unrelated function that also logs an app id.
|
||||
app_remove_strings = self.string_addresses(
|
||||
b"m_app_list.remove(app);", False
|
||||
)
|
||||
app_remove_groups = {
|
||||
self.preceding_prologue(xref)
|
||||
for xref in self.rip_xrefs(app_remove_strings, slop=8)
|
||||
}
|
||||
if app_exit not in app_remove_groups:
|
||||
raise ValueError(
|
||||
"onAppExit candidate does not remove the application: "
|
||||
f"0x{app_exit:x}, remove candidates {app_remove_groups!r}"
|
||||
)
|
||||
|
||||
install_strings = self.string_addresses(b"AppInstallTitleDirMain", False)
|
||||
install_groups: dict[int, int] = {}
|
||||
@@ -162,7 +202,7 @@ class ShellCore:
|
||||
|
||||
return {
|
||||
"launch_app": launch,
|
||||
"unmount_workspace": unmount,
|
||||
"app_exit": app_exit,
|
||||
"install_title_dir": best[0],
|
||||
"install_all": app_install_all,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Validate localization usage, source comments, coverage, and printf safety."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import ast
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
HEADER = ROOT / "include" / "sm_l10n.h"
|
||||
CATALOG_DIR = ROOT / "include" / "lang"
|
||||
BASE_CATALOG = CATALOG_DIR / "en_us.inc"
|
||||
|
||||
ENUM_RE = re.compile(r"typedef enum\s*\{(.*?)SM_L10N_COUNT", re.DOTALL)
|
||||
KEY_RE = re.compile(r"\b(SM_L10N_[A-Z0-9_]+)\b")
|
||||
ENTRY_RE = re.compile(
|
||||
r"\[(SM_L10N_[A-Z0-9_]+)\]\s*=\s*"
|
||||
r"((?:\"(?:\\.|[^\"\\])*\"\s*)+)",
|
||||
re.DOTALL,
|
||||
)
|
||||
STRING_RE = re.compile(r'"(?:\\.|[^"\\])*"')
|
||||
PRINTF_RE = re.compile(
|
||||
r"%(?:[-+ #0]*)(?:\*|\d+)?(?:\.?(?:\*|\d+))?"
|
||||
r"(?:hh|h|ll|l|j|z|t|L)?[diuoxXfFeEgGaAcspn%]"
|
||||
)
|
||||
ENGLISH_COMMENT_RE = re.compile(
|
||||
r"/\* English: (.*?) \*/\s*\[(SM_L10N_[A-Z0-9_]+)\]", re.DOTALL
|
||||
)
|
||||
|
||||
|
||||
def localization_keys() -> list[str]:
|
||||
match = ENUM_RE.search(HEADER.read_text(encoding="utf-8"))
|
||||
if not match:
|
||||
raise ValueError(f"localization enum not found in {HEADER}")
|
||||
return KEY_RE.findall(match.group(1))
|
||||
|
||||
|
||||
def read_catalog(path: Path) -> tuple[dict[str, str], list[str], int]:
|
||||
source_text = path.read_text(encoding="utf-8")
|
||||
entries: dict[str, str] = {}
|
||||
duplicates: list[str] = []
|
||||
assigned_string_count = 0
|
||||
for key, source in ENTRY_RE.findall(source_text):
|
||||
if key in entries:
|
||||
duplicates.append(key)
|
||||
tokens = STRING_RE.findall(source)
|
||||
assigned_string_count += len(tokens)
|
||||
entries[key] = "".join(ast.literal_eval(token) for token in tokens)
|
||||
|
||||
without_comments = re.sub(r"/\*.*?\*/", "", source_text, flags=re.DOTALL)
|
||||
orphan_string_count = (
|
||||
len(STRING_RE.findall(without_comments)) - assigned_string_count
|
||||
)
|
||||
return entries, duplicates, orphan_string_count
|
||||
|
||||
|
||||
def english_comment(value: str) -> str:
|
||||
return (
|
||||
value.replace("\\", "\\\\")
|
||||
.replace("\n", "\\n")
|
||||
.replace("\r", "\\r")
|
||||
.replace("\t", "\\t")
|
||||
)
|
||||
|
||||
|
||||
def read_english_comments(path: Path) -> tuple[dict[str, str], list[str]]:
|
||||
comments: dict[str, str] = {}
|
||||
duplicates: list[str] = []
|
||||
for source, key in ENGLISH_COMMENT_RE.findall(
|
||||
path.read_text(encoding="utf-8")
|
||||
):
|
||||
if key in comments:
|
||||
duplicates.append(key)
|
||||
comments[key] = source
|
||||
return comments, duplicates
|
||||
|
||||
|
||||
def printf_sequence(value: str) -> tuple[list[str], list[int]]:
|
||||
matches = list(PRINTF_RE.finditer(value))
|
||||
formats = [match.group(0) for match in matches]
|
||||
invalid_positions = [
|
||||
index
|
||||
for index, char in enumerate(value)
|
||||
if char == "%"
|
||||
and not any(match.start() <= index < match.end() for match in matches)
|
||||
]
|
||||
return formats, invalid_positions
|
||||
|
||||
|
||||
def read_source_code() -> str:
|
||||
sources: list[str] = []
|
||||
for directory in (ROOT / "src", ROOT / "include"):
|
||||
for path in directory.rglob("*"):
|
||||
if not path.is_file() or path.suffix not in {".c", ".h", ".S"}:
|
||||
continue
|
||||
if path == HEADER:
|
||||
continue
|
||||
sources.append(path.read_text(encoding="utf-8", errors="ignore"))
|
||||
return "\n".join(sources)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
keys = localization_keys()
|
||||
expected = set(keys)
|
||||
base, base_duplicates, base_orphans = read_catalog(BASE_CATALOG)
|
||||
source_code = read_source_code()
|
||||
errors: list[str] = []
|
||||
|
||||
if base_duplicates:
|
||||
errors.append(f"{BASE_CATALOG.name}: duplicate keys: {base_duplicates}")
|
||||
if base_orphans:
|
||||
errors.append(
|
||||
f"{BASE_CATALOG.name}: {base_orphans} string literal(s) are not "
|
||||
"attached to a localization key"
|
||||
)
|
||||
missing_base = expected - base.keys()
|
||||
if missing_base:
|
||||
errors.append(
|
||||
f"{BASE_CATALOG.name}: missing base keys: {sorted(missing_base)}"
|
||||
)
|
||||
|
||||
base_formats: dict[str, list[str]] = {}
|
||||
for key, value in base.items():
|
||||
formats, invalid_formats = printf_sequence(value)
|
||||
base_formats[key] = formats
|
||||
if invalid_formats:
|
||||
errors.append(
|
||||
f"{BASE_CATALOG.name}: {key}: invalid percent sign(s) at "
|
||||
f"{invalid_formats}"
|
||||
)
|
||||
|
||||
for key in keys:
|
||||
if not re.search(rf"\b{re.escape(key)}\b", source_code):
|
||||
errors.append(f"{key}: localization key is not used by the code")
|
||||
|
||||
catalogs = sorted(CATALOG_DIR.glob("*.inc"))
|
||||
if not catalogs:
|
||||
errors.append(f"no catalogs found in {CATALOG_DIR}")
|
||||
|
||||
for path in catalogs:
|
||||
catalog, duplicates, orphans = read_catalog(path)
|
||||
missing = expected - catalog.keys()
|
||||
extra = catalog.keys() - expected
|
||||
if duplicates:
|
||||
errors.append(f"{path.name}: duplicate keys: {duplicates}")
|
||||
if orphans:
|
||||
errors.append(
|
||||
f"{path.name}: {orphans} string literal(s) are not attached "
|
||||
"to a localization key"
|
||||
)
|
||||
if missing:
|
||||
errors.append(f"{path.name}: missing keys: {sorted(missing)}")
|
||||
if extra:
|
||||
errors.append(f"{path.name}: unknown keys: {sorted(extra)}")
|
||||
|
||||
if path != BASE_CATALOG:
|
||||
comments, duplicate_comments = read_english_comments(path)
|
||||
if duplicate_comments:
|
||||
errors.append(
|
||||
f"{path.name}: duplicate English comments: "
|
||||
f"{duplicate_comments}"
|
||||
)
|
||||
missing_comments = expected - comments.keys()
|
||||
if missing_comments:
|
||||
errors.append(
|
||||
f"{path.name}: missing English comments: "
|
||||
f"{sorted(missing_comments)}"
|
||||
)
|
||||
for key in keys:
|
||||
if key not in comments or key not in base:
|
||||
continue
|
||||
expected_comment = english_comment(base[key])
|
||||
if comments[key] != expected_comment:
|
||||
errors.append(
|
||||
f"{path.name}: {key}: English comment is stale"
|
||||
)
|
||||
|
||||
for key in keys:
|
||||
if key not in base or key not in catalog:
|
||||
continue
|
||||
formats, invalid_formats = printf_sequence(catalog[key])
|
||||
if invalid_formats:
|
||||
errors.append(
|
||||
f"{path.name}: {key}: invalid percent sign(s) at "
|
||||
f"{invalid_formats}"
|
||||
)
|
||||
if formats != base_formats[key]:
|
||||
errors.append(
|
||||
f"{path.name}: {key}: printf sequence {formats} != "
|
||||
f"{base_formats[key]}"
|
||||
)
|
||||
if r"\n" in catalog[key]:
|
||||
errors.append(
|
||||
f"{path.name}: {key}: contains a literal \\n instead of "
|
||||
"a newline"
|
||||
)
|
||||
|
||||
if errors:
|
||||
print("Localization validation failed:", file=sys.stderr)
|
||||
for error in errors:
|
||||
print(f" - {error}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
print(f"Validated {len(catalogs)} catalogs with {len(keys)} messages each.")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -9,21 +9,18 @@ from pathlib import Path
|
||||
from capstone import CS_ARCH_X86, CS_GRP_CALL, CS_GRP_JUMP, CS_MODE_64, Cs
|
||||
from capstone.x86 import X86_OP_MEM, X86_REG_RIP
|
||||
|
||||
from generate_shellcore_offsets import ShellCore
|
||||
from generate_shellcore_offsets import ShellCore, firmware_key
|
||||
from generate_shellcore_offsets_from_files import firmware_files
|
||||
|
||||
|
||||
def verify_firmware(path: Path) -> None:
|
||||
def verify_firmware(path: Path, firmware: int) -> None:
|
||||
shellcore = ShellCore(path)
|
||||
targets = shellcore.locate_targets()
|
||||
decoder = Cs(CS_ARCH_X86, CS_MODE_64)
|
||||
decoder.detail = True
|
||||
|
||||
names = ["launch_app", "unmount_workspace"]
|
||||
names = ["launch_app", "app_exit"]
|
||||
# AppInstallAll is hooked after the public TitleDir RPC disappeared.
|
||||
try:
|
||||
firmware = int(path.parents[2].name.replace(".", ""), 16)
|
||||
except ValueError:
|
||||
firmware = 0
|
||||
if firmware >= 0x1200:
|
||||
names.append("install_all")
|
||||
|
||||
@@ -53,14 +50,27 @@ def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("root", type=Path)
|
||||
args = parser.parse_args()
|
||||
firmware_inputs: list[tuple[int, Path]] = []
|
||||
firmware_dirs = sorted(
|
||||
path
|
||||
for path in args.root.iterdir()
|
||||
if (path / "system/vsh/SceShellCore.elf").is_file()
|
||||
)
|
||||
for firmware_dir in firmware_dirs:
|
||||
verify_firmware(firmware_dir / "system/vsh/SceShellCore.elf")
|
||||
print(f"verified {len(firmware_dirs)} firmware trampoline layouts")
|
||||
firmware_inputs.append(
|
||||
(
|
||||
firmware_key(firmware_dir.name),
|
||||
firmware_dir / "system/vsh/SceShellCore.elf",
|
||||
)
|
||||
)
|
||||
for major, minor, path in firmware_files(args.root):
|
||||
firmware_inputs.append((firmware_key(f"{major}.{minor:02d}"), path))
|
||||
if not firmware_inputs:
|
||||
raise ValueError(f"no SceShellCore firmware files under {args.root}")
|
||||
|
||||
for firmware, path in firmware_inputs:
|
||||
verify_firmware(path, firmware)
|
||||
print(f"verified {len(firmware_inputs)} firmware trampoline layouts")
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user