Compare commits

..
12 Commits
87 changed files with 6629 additions and 1213 deletions

No files matched your search

+3
View File
@@ -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
+20
View File
@@ -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"
+10 -7
View File
@@ -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 $@ $<
+53 -1
View File
@@ -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.
+38
View File
@@ -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
+570
View File
@@ -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
View File
@@ -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.
+84
View File
@@ -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",
};
+84
View File
@@ -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",
};
+84
View File
@@ -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",
};
+84
View File
@@ -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",
};
+84
View File
@@ -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",
};
+84
View File
@@ -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",
};
+72
View File
@@ -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",
};
+84
View File
@@ -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",
};
+84
View File
@@ -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",
};
+84
View File
@@ -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",
};
+84
View File
@@ -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",
};
+84
View File
@@ -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",
};
+84
View File
@@ -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",
};
+84
View File
@@ -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",
};
+84
View File
@@ -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",
};
+84
View File
@@ -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",
};
+84
View File
@@ -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",
};
+84
View File
@@ -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",
};
+84
View File
@@ -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",
};
+84
View File
@@ -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",
};
+84
View File
@@ -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",
};
+84
View File
@@ -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",
};
+84
View File
@@ -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",
};
+84
View File
@@ -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",
};
+84
View File
@@ -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",
};
+84
View File
@@ -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",
};
+84
View File
@@ -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",
};
+84
View File
@@ -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",
};
+84
View File
@@ -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",
};
+84
View File
@@ -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",
};
+9 -88
View File
@@ -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
+1
View File
@@ -5,5 +5,6 @@
bool sm_api_service_start(void);
void sm_api_service_stop(void);
bool sm_api_service_reconfigure(void);
#endif
+2
View File
@@ -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);
+2
View File
@@ -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
View File
@@ -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);
+13 -8
View File
@@ -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
+58
View File
@@ -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
View File
@@ -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
View File
@@ -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.
+3
View File
@@ -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]);
+1
View File
@@ -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);
+2
View File
@@ -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);
+2 -2
View File
@@ -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);
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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;
+5 -6
View File
@@ -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
+7
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large. Load diff
+82 -47
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
+3 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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);
}
}
+8 -6
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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 =
+9 -10
View File
@@ -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
-1
View File
@@ -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
View File
@@ -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;
+2 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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);
}
+1 -2
View File
@@ -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
View File
Binary file not shown.
-249
View File
@@ -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;
}
+263
View File
@@ -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())
+68 -28
View File
@@ -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,
}
+212
View File
@@ -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())
+19 -9
View File
@@ -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