Compare commits

..
2 Commits
Author SHA1 Message Date
Drakmor 3530d8d88e Merge pull request #107 from blackbearreloaded/main
Add support to auto load and register FAKE title IDs
2026-08-28 21:37:40 +03:00
BlackBearReloaded 4dafdde002 lifecycle: support FAKE title IDs 2026-08-27 22:35:00 -04:00
106 changed files with 711 additions and 10891 deletions

No files matched your search

-3
View File
@@ -10,6 +10,3 @@ 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,7 +47,6 @@ jobs:
automake \
build-essential \
clang-18 \
cmake \
curl \
git \
libarchive-tools \
@@ -57,8 +56,6 @@ jobs:
meson \
pacman-package-manager \
pkg-config \
python3 \
python3-yaml \
xxd \
zip
@@ -68,29 +65,12 @@ jobs:
makepkg -c -f
sudo pacman --noconfirm -U ./ps5-payload-*.pkg.tar.gz
- name: Build PS5 openlibm package
working-directory: pacbrew-repo/openlibm
run: |
makepkg -c -f
sudo pacman --noconfirm -U ./ps5-payload-*.pkg.tar.gz
- name: Build PS5 json-c package
working-directory: pacbrew-repo/json-c
run: |
makepkg -c -f
sudo pacman --noconfirm -U ./ps5-payload-*.pkg.tar.gz
- name: Build PS5 sqlite package
working-directory: pacbrew-repo/sqlite
run: |
makepkg -c -f
sudo pacman --noconfirm -U ./ps5-payload-*.pkg.tar.gz
- name: Check API tools and manifest
run: |
python3 -m py_compile tools/api_test.py
python3 -c 'import pathlib, yaml; doc = yaml.safe_load(pathlib.Path("docs/openapi.yaml").read_text()); assert doc["openapi"] == "3.1.2" and len(doc["paths"]) == 5'
- name: Build payload
run: |
make clean all PS5_SCE_STUBS_DIR="$GITHUB_WORKSPACE/ps5-sdk/sce_stubs"
-3
View File
@@ -1,8 +1,5 @@
*.o
*.elf
*.so
*.pyc
__pycache__/
daemon.lock
debug.log
notify.txt
+5 -16
View File
@@ -3,27 +3,23 @@ include $(PS5_PAYLOAD_SDK)/toolchain/prospero.mk
VERSION_TAG := $(shell git describe --abbrev=6 --dirty --always --tags 2>/dev/null || echo unknown)
# Compiler and dependency flags
JSON_C_ROOT := $(PS5_PAYLOAD_SDK)/target/user/homebrew
JSON_CFLAGS := -I$(JSON_C_ROOT)/include
# Standard Flags (No extra libraries)
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
# Libraries
LIBS := -lSceNotification -lSceSystemService -lSceUserService -lSceAppInstUtil -lsqlite3 $(JSON_C_ROOT)/lib/libjson-c.a -lm
# Standard Libraries Only
LIBS := -lSceNotification -lSceSystemService -lSceUserService -lSceAppInstUtil -lsqlite3
PS5_SCE_STUBS_DIR ?= $(PS5_PAYLOAD_SDK)/src/sce_stubs
KERNEL_SYS_STUB_SO := src/libkernel_sys_ext.so
KERNEL_SYS_STUB_SRCS := $(PS5_SCE_STUBS_DIR)/libkernel_sys.c src/libkernel_sys_ext.c
ASSET_SRCS := src/notify_icon_asset.c src/config_ini_example_asset.c
SRCS := src/main.c $(wildcard src/sm_*.c) $(ASSET_SRCS)
ASM_SRCS := src/sm_shellcore_bridge.S
OBJS := $(SRCS:.c=.o) $(ASM_SRCS:.S=.o)
OBJS := $(SRCS:.c=.o)
HEADERS := $(wildcard include/*.h)
L10N_CATALOGS := $(wildcard include/lang/*.inc)
# Targets
all: shadowmountplus.elf
@@ -44,15 +40,8 @@ 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 $@ $<
src/%.o: src/%.S
$(CC) $(CFLAGS) -c -o $@ $<
clean:
rm -f shadowmountplus.elf api-test.elf kill.elf src/*.o $(KERNEL_SYS_STUB_SO) src/notify_icon_asset.c src/config_ini_example_asset.c
rm -f shadowmountplus.elf kill.elf src/*.o $(KERNEL_SYS_STUB_SO) src/notify_icon_asset.c src/config_ini_example_asset.c
+2 -54
View File
@@ -54,16 +54,9 @@ 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`)
- `app_install_all=1|0` (`1` stages new titles and submits them through `sceAppInstUtilAppInstallAll`; default: `0` on FW below `12.00`, forced `1` on FW `12.00+`)
- `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)
@@ -95,44 +88,6 @@ 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.
@@ -147,12 +102,6 @@ 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.
@@ -167,7 +116,6 @@ 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:
@@ -199,7 +147,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.
- 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 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,19 +21,6 @@
# 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
@@ -46,37 +33,12 @@
# 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
@@ -1,570 +0,0 @@
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
-97
View File
@@ -1,97 +0,0 @@
# ShadowMount HTTP/JSON API v1
The machine-readable OpenAPI 3.1 description is available in
[`openapi.yaml`](openapi.yaml).
ShadowMount exposes its public API over HTTP/TCP. The default endpoint is
`127.0.0.1:10101`, so only software running on the PS5 can connect. Set
`api_bind_address=0.0.0.0` to allow clients on the network, or select a
specific PS5 IPv4 address. `api_port` accepts values from 1 through 65535.
Both settings are applied on runtime config reload.
The API has no authentication. Bind it to a network-facing address only on a
trusted network.
The internal `/system_tmp/shadowmount.sock` Unix socket remains private to the
SceShellCore bridge and must not be used by third-party applications.
The HTTP service owns and monitors its listener. If initial `socket/bind/listen`
or a later `accept` fails, it retries with an interruptible 250 ms to 5 s
backoff. Only the transition into recovery and the successful recovery are
logged. Shutdown and config reload interrupt the wait immediately.
Every API operation must use `POST`, include `Content-Type: application/json`
and contain one JSON object. Every response is a JSON object. Connections are
closed after one response; chunked request bodies are not supported. The
maximum request body is 4096 bytes.
All responses include `Access-Control-Allow-Origin: *`. Browser preflight
requests using `OPTIONS` are supported and allow `POST`, the `Content-Type`
header and Private Network Access. This permits calls from any browser origin;
the listener still needs `api_bind_address=0.0.0.0` for access from another
device.
## Routes
| Route | Request | Purpose |
| --- | --- | --- |
| `/api/v1/version` | `{}` | API and ShadowMount versions plus capabilities |
| `/api/v1/images` | `{}` | Complete image snapshot |
| `/api/v1/games` | `{}` | Complete game snapshot |
| `/api/v1/games/mount` | `{"title_id":"PPSA12345"}` | Mount a managed game |
| `/api/v1/games/unmount` | `{"title_id":"PPSA12345"}` | Unmount a managed game |
Example:
```sh
curl -sS http://127.0.0.1:10101/api/v1/version \
-H 'Content-Type: application/json' \
-d '{}'
curl -sS http://127.0.0.1:10101/api/v1/games \
-H 'Content-Type: application/json' \
-d '{}'
```
## Responses
Every response contains numeric `status`. It is zero on success, or a positive
errno value on failure. Error responses also contain `error` and use an
appropriate HTTP status such as 400, 404, 409, 413 or 415.
A successful version response has this shape:
```json
{
"status": 0,
"api_version": 1,
"shadowmount_version": "1.7",
"capabilities": ["list_images", "list_games", "mount_game", "unmount_game"]
}
```
List responses contain `count` and the complete `images` or `games` array.
Image items expose `path`, `mount_point`, `size`, modification time, `unit_id`,
`backend`, `complete`, `source_available`, `mapped` and `mounted`. Game items
expose `path`, `image_path`, `title_id`, `title_name`, `installed`, `managed`,
`mounted`, `image_backed` and `source_available`.
Mount mutations remain conservative. They return HTTP 409 with `status` set to
`EBUSY` while a game is active, while ShellCore owns another prepared title,
during suspend, or while a conflicting mount/release is in progress. Clients
must not retry this response in a tight loop. HTTP 404 with `ENOENT` means the
title has no ShadowMount `mount.lnk` tracker.
With `persistent_image_mounts=1`, unmounting a game releases its per-title
runtime layers but keeps the backing image mounted.
## Test client
Run the host-side Python client while ShadowMount is active:
```sh
python3 tools/api_test.py 192.168.1.50 10101
```
It validates CORS preflight, the version response and complete image/game
snapshots, then selects the first safe managed game for a mount/unmount cycle.
The mutation test is skipped if there is no candidate or the runtime is busy.
-84
View File
@@ -1,84 +0,0 @@
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
@@ -1,84 +0,0 @@
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
@@ -1,84 +0,0 @@
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
@@ -1,84 +0,0 @@
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
@@ -1,84 +0,0 @@
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
@@ -1,84 +0,0 @@
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
@@ -1,72 +0,0 @@
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
@@ -1,84 +0,0 @@
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
@@ -1,84 +0,0 @@
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
@@ -1,84 +0,0 @@
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
@@ -1,84 +0,0 @@
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
@@ -1,84 +0,0 @@
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
@@ -1,84 +0,0 @@
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
@@ -1,84 +0,0 @@
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
@@ -1,84 +0,0 @@
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
@@ -1,84 +0,0 @@
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
@@ -1,84 +0,0 @@
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
@@ -1,84 +0,0 @@
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
@@ -1,84 +0,0 @@
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
@@ -1,84 +0,0 @@
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
@@ -1,84 +0,0 @@
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
@@ -1,84 +0,0 @@
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
@@ -1,84 +0,0 @@
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
@@ -1,84 +0,0 @@
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
@@ -1,84 +0,0 @@
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
@@ -1,84 +0,0 @@
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
@@ -1,84 +0,0 @@
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
@@ -1,84 +0,0 @@
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
@@ -1,84 +0,0 @@
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
@@ -1,84 +0,0 @@
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",
};
-16
View File
@@ -1,16 +0,0 @@
#ifndef SM_API_PROTOCOL_H
#define SM_API_PROTOCOL_H
#define SM_API_DEFAULT_BIND_ADDRESS "127.0.0.1"
#define SM_API_DEFAULT_PORT 10101u
#define SM_API_VERSION 1u
#define SM_API_MAX_HTTP_HEADER_SIZE 8192u
#define SM_API_MAX_JSON_BODY_SIZE 4096u
#define SM_API_ROUTE_VERSION "/api/v1/version"
#define SM_API_ROUTE_IMAGES "/api/v1/images"
#define SM_API_ROUTE_GAMES "/api/v1/games"
#define SM_API_ROUTE_MOUNT "/api/v1/games/mount"
#define SM_API_ROUTE_UNMOUNT "/api/v1/games/unmount"
#endif
-10
View File
@@ -1,10 +0,0 @@
#ifndef SM_API_SERVICE_H
#define SM_API_SERVICE_H
#include <stdbool.h>
bool sm_api_service_start(void);
void sm_api_service_stop(void);
bool sm_api_service_reconfigure(void);
#endif
-2
View File
@@ -22,8 +22,6 @@ 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);
+1 -21
View File
@@ -5,8 +5,6 @@
#include <stdbool.h>
#include <stddef.h>
#include "sm_limits.h"
// Check whether a title is present in the installed app set.
bool is_installed(const char *title_id);
// Check whether /user/appmeta/<TITLE_ID>/param.json exists.
@@ -15,18 +13,6 @@ bool has_appmeta_data(const char *title_id);
bool is_data_mounted(const char *title_id);
// Read the mount.lnk source path for a title tracked under /user/app.
bool read_mount_link(const char *title_id, char *out, size_t out_size);
// Atomically persist the mount.lnk source path for a title.
bool write_mount_link(const char *title_id, const char *source_path);
// Read the optional backing image path retained for an image-backed title.
bool read_mount_image_link(const char *title_id, char *out, size_t out_size);
// Persist the outer backing-image path for an already staged title.
bool write_mount_image_link(const char *title_id, const char *image_path);
bool read_mount_image_chain(
const char *title_id,
char paths[MAX_IMAGE_CHAIN_DEPTH][MAX_PATH], size_t *count_out);
bool write_mount_image_chain(
const char *title_id,
const char paths[MAX_IMAGE_CHAIN_DEPTH][MAX_PATH], size_t count);
// Validate a /user/app/<TITLE_ID> tracker entry and optionally build its path.
bool resolve_title_app_dir(const struct dirent *entry, char *app_dir,
size_t app_dir_size);
@@ -40,12 +26,6 @@ int remount_system_ex(void);
bool mount_title_nullfs(const char *title_id, const char *src_path);
// Roll back a just-mounted title nullfs layer when publication is aborted.
bool rollback_title_nullfs_mount(const char *title_id, const char *src_path);
// Drop runtime layers for a title while preserving its metadata/link files.
bool unmount_title_runtime_layers(const char *title_id);
// Same operation without expected EBUSY diagnostics during a bounded retry.
bool unmount_title_runtime_layers_quiet(const char *title_id);
// Release every managed title layer while preserving link metadata.
bool unmount_all_title_runtime_layers(void);
// Reconcile the title mount stack against the expected source/backport state.
bool reconcile_title_backport_mount(const char *title_id, const char *src_path,
const char *expected_backport_path,
@@ -56,7 +36,7 @@ bool mount_backport_overlay(const char *mount_point,
const char *backport_path,
const char *title_id);
// Unmount all managed /system_ex/app/<title_id> mount stacks on shutdown.
bool shutdown_title_mounts(void);
void shutdown_title_mounts(void);
// Remove stale mount links and optionally restore image-backed mounts.
void cleanup_mount_links(const char *removed_source_root,
bool unmount_system_ex_bind);
-19
View File
@@ -2,15 +2,6 @@
#define SM_GAME_CACHE_H
#include <stdbool.h>
#include <stddef.h>
#include "sm_limits.h"
typedef struct {
char path[MAX_PATH];
char title_id[MAX_TITLE_ID];
char title_name[MAX_TITLE_NAME];
} sm_game_cache_snapshot_entry_t;
typedef bool (*game_cache_iter_fn)(const char *path, const char *title_id,
const char *title_name,
@@ -23,22 +14,12 @@ 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);
// 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);
-2
View File
@@ -7,8 +7,6 @@
// Read title ID and title name from a mounted game directory.
bool get_game_info(const char *base_path, const struct stat *param_st,
char *out_id, char *out_name);
// Accept canonical PS4/PS5 game title IDs used in filesystem paths.
bool is_supported_game_title_id(const char *title_id);
// Check whether a directory contains sce_sys/param.json and stat it.
bool directory_has_param_json(const char *dir_path, struct stat *param_st_out);
+1 -8
View File
@@ -11,13 +11,6 @@ void log_fs_stats(const char *tag, const char *path, const char *type_hint);
bool mount_image(const char *file_path, image_fs_type_t fs_type);
// Unmount an image mount point and detach its backing device.
bool unmount_image(const char *file_path, int unit_id, attach_backend_t backend);
// Unmount a runtime image without deleting persistent title metadata links.
bool unmount_runtime_image(const char *file_path, int unit_id,
attach_backend_t backend);
// Release one cached runtime image mount. An absent live mount is harmless.
bool release_runtime_image_mount(const char *file_path);
// Release all discovery/runtime image mounts without deleting title metadata.
bool release_runtime_image_mounts(void);
// Reconcile cached image mounts with current sources and remount if needed.
void cleanup_stale_image_mounts(void);
// Reconcile cached image mounts that belong to a specific scan root.
@@ -29,7 +22,7 @@ bool shutdown_image_mounts(void);
// Remove empty directories left under the image mount root.
void cleanup_mount_dirs(void);
// Mount an image file if it is stable and not currently rate-limited.
bool maybe_mount_image_file(const char *full_path, const char *display_name,
bool maybe_mount_image_file(const char *full_path, const char *name,
bool *unstable_out);
// Build the deterministic runtime mount point for an image source path.
void get_image_mount_point_for_source(const char *file_path,
-14
View File
@@ -20,9 +20,6 @@ bool cache_image_mount(const char *path, const char *mount_point,
bool cache_image_source_mapping(const char *path, const char *mount_point);
// Return a cached image mount entry by index.
bool get_image_cache_entry(int index, image_cache_entry_t *entry_out);
// Find a cached source path with one cache lock acquisition.
bool find_image_cache_entry(const char *path, image_cache_entry_t *entry_out,
int *index_out);
// Mark a cached image mount entry as invalid.
void invalidate_image_cache_entry(int index);
// Resolve a device mapping from the in-memory mount cache.
@@ -33,16 +30,5 @@ bool resolve_device_from_mount_cache(const char *mount_point,
bool resolve_image_source_from_mount_cache(const char *mount_point,
char *path_out,
size_t path_out_size);
// Resolve the image whose mount point owns a mounted child path.
bool resolve_owning_image_source_from_mount_cache(const char *path,
char *path_out,
size_t path_out_size);
// Resolve every owning image from the outermost source to the innermost one.
size_t resolve_image_source_chain_from_mount_cache(
const char *path,
char chain[MAX_IMAGE_CHAIN_DEPTH][MAX_PATH]);
// Resolve only the outermost owning image source.
bool resolve_outermost_image_source_from_mount_cache(
const char *path, char *path_out, size_t path_out_size);
#endif
-46
View File
@@ -1,46 +0,0 @@
#ifndef SM_IMAGE_INDEX_H
#define SM_IMAGE_INDEX_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <sys/stat.h>
#include "sm_limits.h"
struct AppDbTitleList;
typedef void (*sm_image_index_title_visitor_t)(const char *title_id);
typedef struct {
char path[MAX_PATH];
int64_t size;
int64_t mtime_sec;
int32_t mtime_nsec;
bool complete;
} sm_image_index_snapshot_entry_t;
// Return true when a complete unchanged index entry can be reused instead of
// opening the image, visiting every retained title ID before returning.
bool sm_image_index_visit_ready_titles(
const char *path, const struct stat *st,
const struct AppDbTitleList *app_db_titles, bool app_db_titles_ready,
sm_image_index_title_visitor_t visitor);
// Return true when a title is mapped to an existing backing image.
bool sm_image_index_has_source_for_title(const char *title_id);
// Start a new fingerprint generation before mounting an image.
void sm_image_index_begin_scan(const char *path, const struct stat *st);
// Associate a discovered mounted game with its outer backing image. Returns
// false when an image-backed title could not be retained in the index.
bool sm_image_index_record_game(const char *game_path, const char *title_id);
// Commit one fingerprint after its mounted content was scanned.
void sm_image_index_complete_scan(const char *path);
// Atomically persist accumulated index changes at the scan-cycle boundary.
void sm_image_index_flush(void);
// Drop persistent entries whose backing image disappeared.
void sm_image_index_prune(void);
// Allocate a stable snapshot of all known backing images. Caller frees it.
bool sm_image_index_snapshot(sm_image_index_snapshot_entry_t **entries_out,
size_t *count_out);
#endif
-2
View File
@@ -24,7 +24,5 @@ bool sm_install_queue_candidate(const scan_candidate_t *candidate,
bool sm_install_submit_queued(void);
// Record retry state for every queued title after a batch submit failure.
void sm_install_note_submit_failure(void);
// Return true while an install still needs its staged mounts.
bool sm_install_has_pending_work(void);
#endif
-58
View File
@@ -1,58 +0,0 @@
#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
+2 -10
View File
@@ -4,12 +4,11 @@
#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
#define MAX_IMAGE_MOUNTS 256
#define MAX_IMAGE_MODE_RULES 128
#define MAX_KSTUFF_TITLE_RULES 128
#define MAX_FAKELIB_EXCLUDE_RULES 128
@@ -23,15 +22,11 @@
#define MAX_MISSING_PARAM_SCAN_ATTEMPTS 3
#define MAX_IMAGE_MOUNT_ATTEMPTS 3
#define MAX_LAYERED_UNMOUNT_ATTEMPTS 4
#define MAX_IMAGE_CHAIN_DEPTH 8
#define LVD_RELEASE_WAIT_MAX_US 15000000u
#define LVD_RELEASE_WAIT_POLL_US 500000u
#define GAME_LIFECYCLE_POLL_INTERVAL_US 250000u
#define GAME_APPINFO_LOOKUP_TIMEOUT_US 1000000u
#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
@@ -39,8 +34,6 @@
#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
@@ -52,7 +45,6 @@
#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
+2 -8
View File
@@ -3,8 +3,6 @@
#include <stdbool.h>
#include "sm_l10n.h"
typedef struct sm_error sm_error_t;
// Write a formatted line to the debug log.
@@ -13,23 +11,19 @@ void log_debug(const char *fmt, ...);
void sm_notifications_init(void);
// Flush and close persistent log resources.
void sm_log_shutdown(void);
// Send a localized rich toast notification with packaged icon and version header.
void notify_system_rich_l10n(bool allow_in_quiet_mode, sm_l10n_key_t key, ...);
// Send a rich toast notification with packaged icon and version header.
void notify_system_rich(bool allow_in_quiet_mode, const char *fmt, ...);
// 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.
-7
View File
@@ -1,7 +0,0 @@
#ifndef SM_MOUNT_DIAG_H
#define SM_MOUNT_DIAG_H
// Log read-only evidence explaining why unmount(path) returned EBUSY.
void sm_mount_diag_log_busy(const char *mount_point);
#endif
-3
View File
@@ -18,9 +18,6 @@ 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
@@ -13,7 +13,6 @@
#define AUTOTUNE_FILE "/data/shadowmount/autotune.ini"
#define MANUAL_LIST_FILE "/data/shadowmount/manual.lst"
#define MANUAL_STATUS_FILE "/data/shadowmount/manual.status"
#define IMAGE_INDEX_FILE_PATH "/data/shadowmount/image_index.bin"
#define APPMETA_BASE "/user/appmeta"
#define APP_BASE "/user/app"
#define KSTUFF_NOAUTOMOUNT_FILE "/data/.kstuff_noautomount"
+1 -9
View File
@@ -41,20 +41,12 @@ static inline uint32_t sm_firmware_major_version(void) {
return ((major_bcd >> 4) & 0xFu) * 10u + (major_bcd & 0xFu);
}
static inline bool sm_set_fd_nonblocking(int fd) {
int flags = fcntl(fd, F_GETFL, 0);
if (flags < 0)
return false;
return fcntl(fd, F_SETFL, flags | O_NONBLOCK) == 0;
}
// --- SDK Imports ---
int sceAppInstUtilInitialize(void);
int sceAppInstUtilAppInstallAll(void *reserved);
int sceAppInstUtilAppInstallAll(void);
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,8 +15,6 @@ 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 -8
View File
@@ -21,13 +21,7 @@ int collect_scan_candidates_for_scan_root(const char *scan_root,
int max_candidates,
int *total_found_out,
bool *unstable_found_out);
// Ensure a stable backport overlay is mounted when one is present.
// Absence is harmless; an unusable or failed required overlay returns false.
bool mount_backport_overlay_for_title(const char *source_path,
const char *title_id,
const char *owning_scan_root,
bool *unstable_found_out);
// Remove discovery title/image mounts while preserving persistent metadata.
bool release_scan_runtime_mounts(void);
// Mount stable backport overlays for already mounted titles.
void mount_backport_overlays(bool *unstable_found_out);
#endif
-2
View File
@@ -7,8 +7,6 @@
bool sm_scanner_init(void);
// Wake a blocked scanner wait. Safe to call from signal context.
void sm_scanner_wake(void);
// Return true after scanner-owned USB directory descriptors are closed.
bool sm_scanner_usb_watches_suspended(void);
// Run the initial full synchronization cycle.
bool sm_scanner_run_startup_sync(void);
// Run the steady-state scanner wait/scan loop until shutdown is requested.
-21
View File
@@ -1,21 +0,0 @@
#ifndef SM_SHELLCORE_HOOKS_H
#define SM_SHELLCORE_HOOKS_H
#include <stdbool.h>
// Install/remove the fail-open SceShellCore lifecycle bridge.
bool sm_shellcore_hooks_start(void);
void sm_shellcore_hooks_stop(void);
// Return whether 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);
// Invoke the internal TitleDir path through one armed AppInstallAll dispatch.
// Returns false when the bridge is unavailable or was not consumed.
bool sm_shellcore_install_title_dir(const char *title_id,
const char *install_dir,
int *result_out);
#endif
-29
View File
@@ -1,29 +0,0 @@
#ifndef SM_SHELLCORE_OFFSETS_H
#define SM_SHELLCORE_OFFSETS_H
#include <stdint.h>
typedef enum {
SM_SHELLCORE_TARGET_LAUNCH_APP = 0,
SM_SHELLCORE_TARGET_APP_EXIT,
SM_SHELLCORE_TARGET_INSTALL_TITLE_DIR,
SM_SHELLCORE_TARGET_INSTALL_ALL,
SM_SHELLCORE_TARGET_COUNT
} sm_shellcore_target_t;
typedef struct {
uintptr_t offset;
uint8_t patch_size;
} sm_shellcore_target_offset_t;
typedef struct {
uint16_t firmware;
const char *name;
sm_shellcore_target_offset_t targets[SM_SHELLCORE_TARGET_COUNT];
} sm_shellcore_firmware_offsets_t;
const sm_shellcore_firmware_offsets_t *
sm_shellcore_offsets_for_firmware(uint32_t raw_firmware);
const char *sm_shellcore_target_name(sm_shellcore_target_t target);
#endif
-35
View File
@@ -1,35 +0,0 @@
#ifndef SM_SHELLCORE_PROTOCOL_H
#define SM_SHELLCORE_PROTOCOL_H
#include <stdint.h>
#include "sm_limits.h"
#define SM_SHELLCORE_SOCKET_PATH "/system_tmp/shadowmount.sock"
#define SM_SHELLCORE_PROTOCOL_MAGIC 0x534d5343u
#define SM_SHELLCORE_PROTOCOL_VERSION 1u
typedef enum {
SM_SHELLCORE_REQUEST_LAUNCH = 1,
SM_SHELLCORE_REQUEST_LAUNCH_FAILED = 2,
SM_SHELLCORE_REQUEST_APP_EXITED = 3
} sm_shellcore_request_op_t;
typedef struct {
uint32_t magic;
uint16_t version;
uint16_t operation;
union {
char title_id[MAX_TITLE_ID];
struct {
uint32_t app_id;
uint8_t reserved[MAX_TITLE_ID - sizeof(uint32_t)];
} app_exit;
} payload;
} sm_shellcore_request_t;
typedef struct {
int32_t status;
} sm_shellcore_response_t;
#endif
-26
View File
@@ -1,26 +0,0 @@
#ifndef SM_SHELLCORE_REMOTE_H
#define SM_SHELLCORE_REMOTE_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <sys/types.h>
#include "sm_shellcore_offsets.h"
typedef struct {
pid_t pid;
uintptr_t image_base;
uintptr_t targets[SM_SHELLCORE_TARGET_COUNT];
const sm_shellcore_firmware_offsets_t *offsets;
} sm_shellcore_remote_t;
// Resolve and validate every lifecycle target in a running SceShellCore.
bool sm_shellcore_remote_resolve(pid_t pid, sm_shellcore_remote_t *remote_out);
// Read or write another process while handling post-8.20 write restrictions.
bool sm_remote_process_read(pid_t pid, uintptr_t address, void *buffer,
size_t size);
bool sm_remote_process_write(pid_t pid, uintptr_t address, const void *buffer,
size_t size);
#endif
-30
View File
@@ -1,30 +0,0 @@
#ifndef SM_SHELLCORE_SERVICE_H
#define SM_SHELLCORE_SERVICE_H
#include <stdbool.h>
#include <stdint.h>
// Start/stop the local Unix-socket owner used by the SceShellCore bridge.
bool sm_shellcore_service_start(void);
void sm_shellcore_service_stop(void);
// Release transient title/image mounts after launch failure or 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);
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 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 an incoming or outgoing runtime mount.
bool sm_shellcore_service_title_is_prepared(const char *title_id);
// Return true while ShellCore owns any title mount prepared for launch or exit.
bool sm_shellcore_service_has_prepared_mount(void);
#endif
-10
View File
@@ -1,10 +0,0 @@
#ifndef SM_SOCKET_IO_H
#define SM_SOCKET_IO_H
#include <stdbool.h>
#include <stddef.h>
bool sm_socket_read_full(int fd, void *buffer, size_t size);
bool sm_socket_write_full(int fd, const void *buffer, size_t size);
#endif
+1 -7
View File
@@ -108,28 +108,22 @@ 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 app_install_all_forced;
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;
+35 -119
View File
@@ -5,12 +5,9 @@
#include <sys/sysctl.h>
#include "sm_runtime.h"
#include "sm_api_service.h"
#include "sm_types.h"
#include "sm_log.h"
#include "sm_shellcore_flags.h"
#include "sm_shellcore_hooks.h"
#include "sm_shellcore_service.h"
#include "sm_config_mount.h"
#include "sm_game_lifecycle.h"
#include "sm_kstuff.h"
@@ -47,7 +44,6 @@
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;
@@ -81,8 +77,6 @@ void install_signal_handlers(void) {
sigaction(SIGHUP, &sa, NULL);
sigaction(SIGQUIT, &sa, NULL);
sigaction(SIGABRT, &sa, NULL);
sa.sa_handler = SIG_IGN;
sigaction(SIGPIPE, &sa, NULL);
}
bool should_stop_requested(void) {
@@ -132,13 +126,6 @@ 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';
@@ -146,29 +133,13 @@ static void clear_scan_now_request(void) {
}
bool request_runtime_sleep_mode(bool active, const char *reason) {
if (active) {
bool previous = atomic_exchange_explicit(&g_runtime_sleep_mode_active, true,
memory_order_acq_rel);
if (previous)
return false;
bool previous = atomic_exchange_explicit(&g_runtime_sleep_mode_active, active,
memory_order_acq_rel);
if (previous == active)
return false;
if (active)
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";
@@ -190,17 +161,20 @@ 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";
if (runtime_sleep_mode_active())
bool resume_scan =
strcmp(resolved_reason, "SceSystemStateMgrInfo=WORKING") == 0;
if (runtime_sleep_mode_active() && !resume_scan)
return;
char log_reason[sizeof(g_scan_now.reason)];
bool should_log = false;
bool should_log = !resume_scan;
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));
should_log = true;
} else {
should_log = false;
}
pthread_mutex_unlock(&g_scan_now.reason_mutex);
@@ -276,23 +250,12 @@ pid_t find_pid_by_name(const char *name, bool exclude_self) {
pid_t found_pid = 0;
uint8_t *ptr = buf;
uint8_t *end = buf + buf_size;
while ((size_t)(end - ptr) >= sizeof(int)) {
int ki_structsize = 0;
memcpy(&ki_structsize, ptr, sizeof(ki_structsize));
if (ki_structsize <= KINFO_TDNAME_OFFSET ||
(size_t)ki_structsize > (size_t)(end - ptr)) {
found_pid = -1;
break;
}
pid_t ki_pid = 0;
memcpy(&ki_pid, &ptr[KINFO_PID_OFFSET], sizeof(ki_pid));
while (ptr < end) {
int ki_structsize = *(int *)ptr;
pid_t ki_pid = *(pid_t *)&ptr[KINFO_PID_OFFSET];
const char *ki_tdname = (const char *)&ptr[KINFO_TDNAME_OFFSET];
size_t tdname_size = (size_t)ki_structsize - KINFO_TDNAME_OFFSET;
ptr += ki_structsize;
if ((!exclude_self || ki_pid != mypid) &&
strnlen(ki_tdname, tdname_size) < tdname_size &&
strcmp(ki_tdname, name) == 0) {
if ((!exclude_self || ki_pid != mypid) && strcmp(ki_tdname, name) == 0) {
found_pid = ki_pid;
break;
}
@@ -358,21 +321,20 @@ static void log_non_empty_scan_paths(void) {
}
}
static bool ensure_kstuff_noautomount_file(void) {
static void ensure_kstuff_noautomount_file(void) {
if (path_exists(KSTUFF_NOAUTOMOUNT_FILE))
return true;
return;
int fd = open(KSTUFF_NOAUTOMOUNT_FILE, O_WRONLY | O_CREAT, 0666);
if (fd < 0) {
printf("[KSTUFF] Failed to create %s: %s\n", KSTUFF_NOAUTOMOUNT_FILE,
strerror(errno));
return false;
int fd = open(KSTUFF_NOAUTOMOUNT_FILE, O_RDONLY | O_CREAT, 0666);
if (fd >= 0) {
close(fd);
printf("[KSTUFF] Created startup sentinel: %s\n",
KSTUFF_NOAUTOMOUNT_FILE);
return;
}
(void)close(fd);
printf("[KSTUFF] Created automount sentinel: %s\n",
KSTUFF_NOAUTOMOUNT_FILE);
return true;
printf("[KSTUFF] Failed to create %s: %s\n", KSTUFF_NOAUTOMOUNT_FILE,
strerror(errno));
}
static bool write_buffer_to_fd(int fd, const unsigned char *buf, size_t size) {
@@ -419,19 +381,7 @@ static void ensure_runtime_config_file(void) {
printf("[CFG] Created default config from template: %s\n", CONFIG_FILE);
}
static void cleanup_kstuff_noautomount_file(void) {
pid_t replacement_pid = find_pid_by_name(PAYLOAD_NAME, true);
if (replacement_pid > 0) {
log_debug("[KSTUFF] replacement instance pid=%ld; keeping %s",
(long)replacement_pid, KSTUFF_NOAUTOMOUNT_FILE);
return;
}
if (replacement_pid < 0) {
log_debug("[KSTUFF] process enumeration unavailable; keeping %s",
KSTUFF_NOAUTOMOUNT_FILE);
return;
}
static void cleanup_kstuff_noautomount_files(void) {
if (unlink(KSTUFF_NOAUTOMOUNT_FILE) == 0) {
log_debug("[KSTUFF] removed shutdown sentinel: %s",
KSTUFF_NOAUTOMOUNT_FILE);
@@ -478,31 +428,21 @@ int main(void) {
mkdir(LOG_DIR, 0777);
ensure_runtime_config_file();
ensure_kstuff_noautomount_file();
existing_pid = find_pid_by_name(PAYLOAD_NAME, true);
if (existing_pid < 0) {
printf("[RESTART] Failed to enumerate running processes.\n");
sceUserServiceTerminate();
return 1;
}
if (!ensure_kstuff_noautomount_file()) {
sceUserServiceTerminate();
return 1;
}
if (existing_pid > 0) {
printf("[RESTART] Another instance is already running.\n");
if (!wait_for_existing_instance_exit(existing_pid)) {
printf("[KSTUFF] Keeping automount sentinel after failed handoff.\n");
sceUserServiceTerminate();
return 0;
}
restarted_previous_instance = true;
}
// Older builds removed the sentinel unconditionally during shutdown.
// Recreate it after handoff before starting any mount work.
if (!ensure_kstuff_noautomount_file()) {
sceUserServiceTerminate();
return 1;
}
syscall(SYS_thr_set_name, -1, PAYLOAD_NAME);
if (remove(KILL_FILE) == 0) {
@@ -533,13 +473,6 @@ int main(void) {
log_debug(" [SHELLFLAG] monitor unavailable");
sm_mdbg_init();
sm_kstuff_init();
if (!sm_shellcore_service_start())
log_debug(" [SHELLCORE] Unix socket service unavailable: %s",
strerror(errno));
else {
if (!sm_shellcore_hooks_start())
log_debug(" [SHELLCORE] lifecycle hooks unavailable; stock behavior kept");
}
if (!refresh_game_lifecycle_watcher())
log_debug(" [GAME] lifecycle watcher unavailable");
@@ -550,14 +483,14 @@ int main(void) {
log_debug(" [MOUNT] remount_system_ex failed: %s", strerror(errno));
}
notify_system_l10n(SM_L10N_STARTUP, SHADOWMOUNT_VERSION);
notify_system("ShadowMount+ v%s exFAT/UFS/PFS", SHADOWMOUNT_VERSION);
log_non_empty_scan_paths();
if (runtime_config()->legacy_recursive_scan_forced) {
notify_system_info_l10n(SM_L10N_RECURSIVE_SCAN_DEPRECATED);
notify_system_info("ShadowMount+: recursive_scan=1 deprecated, using scan_depth=2.");
} else if (runtime_config()->scan_depth > 1u) {
notify_system_info_l10n(SM_L10N_SCAN_DEPTH_ENABLED,
runtime_config()->scan_depth);
notify_system_info("ShadowMount+: scan depth %u enabled.",
runtime_config()->scan_depth);
}
cleanup_mount_dirs();
@@ -567,11 +500,9 @@ int main(void) {
}
log_debug("[STARTUP] cleanup_staged_mount_links begin");
runtime_mount_state_lock();
cleanup_staged_mount_links();
log_debug("[STARTUP] cleanup_duplicate_title_mounts begin");
cleanup_duplicate_title_mounts();
runtime_mount_state_unlock();
if (!app_db_run_startup_maintenance())
log_debug(" [DB] startup snd0info maintenance unavailable");
log_debug("[STARTUP] scanner startup sync begin");
@@ -580,34 +511,19 @@ int main(void) {
goto shutdown;
}
log_debug("[STARTUP] scanner startup sync done");
if (!sm_api_service_start())
log_debug(" [API] HTTP/JSON service unavailable: %s", strerror(errno));
sm_scanner_run_loop();
shutdown:
sm_api_service_stop();
sm_shellcore_hooks_stop();
sm_shellcore_service_stop();
sm_shellcore_flags_stop();
stop_game_lifecycle_watcher();
sm_scanner_shutdown();
sm_kstuff_shutdown();
sm_mdbg_shutdown();
bool title_mounts_released = shutdown_title_mounts();
bool image_mounts_released = false;
if (title_mounts_released) {
image_mounts_released = shutdown_image_mounts();
} else {
log_debug("[SHUTDOWN] image teardown skipped while title layers remain");
}
if (!image_mounts_released) {
cleanup_kstuff_noautomount_files();
shutdown_title_mounts();
if (!shutdown_image_mounts()) {
log_debug("[SHUTDOWN] some image mounts or devices were not fully released");
}
if (title_mounts_released && image_mounts_released) {
cleanup_kstuff_noautomount_file();
} else {
log_debug("[KSTUFF] keeping automount disabled after incomplete teardown");
}
shutdown_app_db();
if (atomic_load_explicit(&g_shutdown_on_going_stop_requested,
-934
View File
@@ -1,934 +0,0 @@
#include "sm_platform.h"
#include <arpa/inet.h>
#include <json-c/json.h>
#include <netinet/in.h>
#include <pthread.h>
#include <sys/socket.h>
#include "sm_api_protocol.h"
#include "sm_api_service.h"
#include "sm_config_mount.h"
#include "sm_filesystem.h"
#include "sm_game_cache.h"
#include "sm_gameinfo.h"
#include "sm_image.h"
#include "sm_image_cache.h"
#include "sm_image_index.h"
#include "sm_log.h"
#include "sm_mount_device.h"
#include "sm_path_utils.h"
#include "sm_shellcore_service.h"
#include "sm_socket_io.h"
#ifndef SHADOWMOUNT_VERSION
#define SHADOWMOUNT_VERSION "unknown"
#endif
#define SM_API_ROUTE_SIZE 128u
#define SM_API_RETRY_INITIAL_MS 250u
#define SM_API_RETRY_MAX_MS 5000u
#define SM_API_CORS_HEADERS \
"Access-Control-Allow-Origin: *\r\n" \
"Access-Control-Allow-Methods: POST, OPTIONS\r\n" \
"Access-Control-Allow-Headers: Content-Type\r\n" \
"Access-Control-Allow-Private-Network: true\r\n"
typedef struct {
pthread_t thread;
pthread_mutex_t mutex;
pthread_cond_t retry_cond;
bool started;
bool stop_requested;
int listen_fd;
int client_fd;
char bind_address[MAX_API_BIND_ADDRESS];
uint16_t port;
} sm_api_service_state_t;
typedef struct {
char route[SM_API_ROUTE_SIZE];
char *storage;
char *body;
size_t body_size;
bool preflight;
} sm_http_request_t;
static sm_api_service_state_t g_api = {
.mutex = PTHREAD_MUTEX_INITIALIZER,
.retry_cond = PTHREAD_COND_INITIALIZER,
.listen_fd = -1,
.client_fd = -1,
};
static int open_http_listener(const char *bind_address, uint16_t port) {
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0)
return -1;
int reuse = 1;
(void)setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
struct sockaddr_in address;
memset(&address, 0, sizeof(address));
address.sin_len = sizeof(address);
address.sin_family = AF_INET;
address.sin_port = htons(port);
if (inet_pton(AF_INET, bind_address, &address.sin_addr) != 1) {
close(fd);
errno = EINVAL;
return -1;
}
if (bind(fd, (struct sockaddr *)&address, sizeof(address)) != 0 ||
listen(fd, 4) != 0) {
int saved_errno = errno;
close(fd);
errno = saved_errno;
return -1;
}
return fd;
}
static uint32_t next_retry_delay_ms(uint32_t current) {
if (current >= SM_API_RETRY_MAX_MS)
return SM_API_RETRY_MAX_MS;
if (current > SM_API_RETRY_MAX_MS / 2u)
return SM_API_RETRY_MAX_MS;
return current * 2u;
}
static bool wait_before_listener_retry(uint32_t delay_ms) {
struct timespec deadline;
if (clock_gettime(CLOCK_REALTIME, &deadline) != 0) {
sceKernelUsleep(SM_API_RETRY_INITIAL_MS * 1000u);
pthread_mutex_lock(&g_api.mutex);
bool keep_running = !g_api.stop_requested;
pthread_mutex_unlock(&g_api.mutex);
return keep_running;
}
deadline.tv_sec += (time_t)(delay_ms / 1000u);
deadline.tv_nsec += (long)(delay_ms % 1000u) * 1000000l;
if (deadline.tv_nsec >= 1000000000l) {
deadline.tv_sec++;
deadline.tv_nsec -= 1000000000l;
}
pthread_mutex_lock(&g_api.mutex);
int wait_status = 0;
while (!g_api.stop_requested) {
wait_status =
pthread_cond_timedwait(&g_api.retry_cond, &g_api.mutex, &deadline);
if (wait_status != 0)
break;
}
bool keep_running = !g_api.stop_requested;
pthread_mutex_unlock(&g_api.mutex);
if (keep_running && wait_status != 0 && wait_status != ETIMEDOUT) {
sceKernelUsleep(SM_API_RETRY_INITIAL_MS * 1000u);
pthread_mutex_lock(&g_api.mutex);
keep_running = !g_api.stop_requested;
pthread_mutex_unlock(&g_api.mutex);
}
return keep_running;
}
static bool accept_error_needs_wait(int error) {
return error == EAGAIN || error == EWOULDBLOCK || error == EMFILE ||
error == ENFILE || error == ENOBUFS || error == ENOMEM;
}
static const char *http_reason_phrase(int status) {
switch (status) {
case 200:
return "OK";
case 400:
return "Bad Request";
case 404:
return "Not Found";
case 405:
return "Method Not Allowed";
case 409:
return "Conflict";
case 411:
return "Length Required";
case 413:
return "Payload Too Large";
case 415:
return "Unsupported Media Type";
case 431:
return "Request Header Fields Too Large";
case 500:
return "Internal Server Error";
case 501:
return "Not Implemented";
default:
return "Error";
}
}
static bool send_json_text(int fd, int http_status, const char *body,
size_t body_size) {
char header[512];
int count = snprintf(header, sizeof(header),
"HTTP/1.1 %d %s\r\n"
"Content-Type: application/json\r\n"
SM_API_CORS_HEADERS
"Content-Length: %zu\r\n"
"Connection: close\r\n"
"Cache-Control: no-store\r\n\r\n",
http_status, http_reason_phrase(http_status), body_size);
if (count < 0 || (size_t)count >= sizeof(header))
return false;
return sm_socket_write_full(fd, header, (size_t)count) &&
sm_socket_write_full(fd, body, body_size);
}
static void send_preflight_response(int fd) {
static const char response[] =
"HTTP/1.1 204 No Content\r\n"
SM_API_CORS_HEADERS
"Access-Control-Max-Age: 86400\r\n"
"Content-Length: 0\r\n"
"Connection: close\r\n\r\n";
(void)sm_socket_write_full(fd, response, sizeof(response) - 1u);
}
static bool send_json_object(int fd, int http_status,
struct json_object *response) {
const char *body =
json_object_to_json_string_ext(response, JSON_C_TO_STRING_PLAIN);
if (!body)
return false;
return send_json_text(fd, http_status, body, strlen(body));
}
static bool add_json_value(struct json_object *object, const char *key,
struct json_object *value) {
if (!value)
return false;
if (json_object_object_add_ex(object, key, value,
JSON_C_OBJECT_ADD_CONSTANT_KEY) == 0) {
return true;
}
json_object_put(value);
return false;
}
static bool add_json_int(struct json_object *object, const char *key,
int64_t value) {
return add_json_value(object, key, json_object_new_int64(value));
}
static bool add_json_bool(struct json_object *object, const char *key,
bool value) {
return add_json_value(object, key, json_object_new_boolean(value));
}
static bool add_json_string(struct json_object *object, const char *key,
const char *value) {
return add_json_value(object, key, json_object_new_string(value));
}
static bool append_json_string(struct json_object *array, const char *value) {
struct json_object *item = json_object_new_string(value);
if (!item)
return false;
if (json_object_array_add(array, item) == 0)
return true;
json_object_put(item);
return false;
}
static struct json_object *new_status_response(int status) {
struct json_object *response = json_object_new_object();
if (response && !add_json_int(response, "status", status)) {
json_object_put(response);
return NULL;
}
return response;
}
static void send_out_of_memory_response(int fd) {
static const char body[] =
"{\"status\":12,\"error\":\"out of memory\"}";
(void)send_json_text(fd, 500, body, sizeof(body) - 1u);
}
static void send_error_response(int fd, int http_status, int status,
const char *message) {
struct json_object *response = new_status_response(status);
if (!response) {
send_out_of_memory_response(fd);
return;
}
if (!add_json_string(response, "error", message)) {
json_object_put(response);
send_out_of_memory_response(fd);
return;
}
(void)send_json_object(fd, http_status, response);
json_object_put(response);
}
static char *find_http_header_end(char *buffer, size_t size) {
if (size < 4u)
return NULL;
for (size_t i = 0; i + 3u < size; ++i) {
if (buffer[i] == '\r' && buffer[i + 1u] == '\n' &&
buffer[i + 2u] == '\r' && buffer[i + 3u] == '\n') {
return &buffer[i];
}
}
return NULL;
}
static char *trim_http_space(char *value) {
while (*value == ' ' || *value == '\t')
value++;
size_t size = strlen(value);
while (size > 0u && (value[size - 1u] == ' ' || value[size - 1u] == '\t'))
value[--size] = '\0';
return value;
}
static bool is_json_content_type(const char *value) {
static const char media_type[] = "application/json";
size_t size = sizeof(media_type) - 1u;
if (strncasecmp(value, media_type, size) != 0)
return false;
value += size;
while (*value == ' ' || *value == '\t')
value++;
return *value == '\0' || *value == ';';
}
static bool parse_content_length(const char *value, size_t *size_out) {
if (!value || !size_out || value[0] == '\0' || value[0] == '-')
return false;
errno = 0;
char *end = NULL;
unsigned long long parsed = strtoull(value, &end, 10);
if (errno != 0 || end == value || *end != '\0' || parsed > SIZE_MAX)
return false;
*size_out = (size_t)parsed;
return true;
}
static bool reject_http_request(char *buffer, int http_status,
const char *error, int *http_status_out,
const char **error_out) {
free(buffer);
*http_status_out = http_status;
*error_out = error;
return false;
}
static bool receive_http_request(int fd, sm_http_request_t *request,
int *http_status_out,
const char **error_out) {
size_t capacity = SM_API_MAX_HTTP_HEADER_SIZE + SM_API_MAX_JSON_BODY_SIZE + 1u;
char *buffer = malloc(capacity);
if (!buffer)
return reject_http_request(NULL, 500, "out of memory", http_status_out,
error_out);
size_t received = 0;
char *header_end = NULL;
while (!header_end) {
if (received >= SM_API_MAX_HTTP_HEADER_SIZE)
return reject_http_request(buffer, 431, "HTTP headers are too large",
http_status_out, error_out);
ssize_t count = recv(fd, buffer + received,
SM_API_MAX_HTTP_HEADER_SIZE - received, 0);
if (count < 0 && errno == EINTR)
continue;
if (count <= 0)
return reject_http_request(buffer, 400, "incomplete HTTP request",
http_status_out, error_out);
received += (size_t)count;
header_end = find_http_header_end(buffer, received);
}
size_t header_size = (size_t)(header_end - buffer) + 4u;
*header_end = '\0';
char *line_end = strstr(buffer, "\r\n");
if (!line_end)
return reject_http_request(buffer, 400, "invalid HTTP request line",
http_status_out, error_out);
*line_end = '\0';
char method[8];
char version[16];
char extra;
if (sscanf(buffer, "%7s %127s %15s %c", method, request->route, version,
&extra) != 3 ||
(strcmp(version, "HTTP/1.1") != 0 && strcmp(version, "HTTP/1.0") != 0))
return reject_http_request(buffer, 400, "invalid HTTP request line",
http_status_out, error_out);
if (strcmp(method, "POST") == 0) {
request->preflight = false;
} else if (strcmp(method, "OPTIONS") == 0) {
request->preflight = true;
} else {
return reject_http_request(buffer, 405,
"only POST and OPTIONS requests are supported",
http_status_out, error_out);
}
bool has_content_length = false;
bool has_json_content_type = false;
bool has_transfer_encoding = false;
size_t body_size = 0;
char *line = line_end + 2;
while (line < header_end && line[0] != '\0') {
char *next = strstr(line, "\r\n");
if (!next)
next = header_end;
*next = '\0';
char *colon = strchr(line, ':');
if (!colon)
return reject_http_request(buffer, 400, "invalid HTTP header",
http_status_out, error_out);
*colon = '\0';
char *name = trim_http_space(line);
char *value = trim_http_space(colon + 1);
if (strcasecmp(name, "Content-Length") == 0) {
if (has_content_length || !parse_content_length(value, &body_size))
return reject_http_request(buffer, 400,
"invalid Content-Length header",
http_status_out, error_out);
has_content_length = true;
} else if (strcasecmp(name, "Content-Type") == 0) {
has_json_content_type = is_json_content_type(value);
} else if (strcasecmp(name, "Transfer-Encoding") == 0) {
has_transfer_encoding = true;
}
line = next + 2;
}
if (has_transfer_encoding)
return reject_http_request(buffer, 501,
"Transfer-Encoding is not supported",
http_status_out, error_out);
if (request->preflight) {
request->storage = buffer;
return true;
}
if (!has_content_length)
return reject_http_request(buffer, 411, "Content-Length is required",
http_status_out, error_out);
if (!has_json_content_type)
return reject_http_request(buffer, 415,
"Content-Type must be application/json",
http_status_out, error_out);
if (body_size > SM_API_MAX_JSON_BODY_SIZE)
return reject_http_request(buffer, 413, "JSON request body is too large",
http_status_out, error_out);
size_t total_size = header_size + body_size;
while (received < total_size) {
ssize_t count = recv(fd, buffer + received, total_size - received, 0);
if (count < 0 && errno == EINTR)
continue;
if (count <= 0)
return reject_http_request(buffer, 400,
"incomplete JSON request body",
http_status_out, error_out);
received += (size_t)count;
}
buffer[total_size] = '\0';
request->storage = buffer;
request->body = buffer + header_size;
request->body_size = body_size;
return true;
}
static struct json_object *parse_request_json(const sm_http_request_t *request) {
struct json_tokener *tokener = json_tokener_new_ex(16);
if (!tokener)
return NULL;
json_tokener_set_flags(tokener, JSON_TOKENER_STRICT | JSON_TOKENER_VALIDATE_UTF8);
struct json_object *root =
json_tokener_parse_ex(tokener, request->body, (int)request->body_size);
enum json_tokener_error error = json_tokener_get_error(tokener);
size_t parsed_size = json_tokener_get_parse_end(tokener);
while (parsed_size < request->body_size &&
isspace((unsigned char)request->body[parsed_size])) {
parsed_size++;
}
bool valid = error == json_tokener_success && root != NULL &&
parsed_size == request->body_size &&
json_object_is_type(root, json_type_object);
json_tokener_free(tokener);
if (!valid) {
if (root)
json_object_put(root);
return NULL;
}
return root;
}
static const char *get_title_id(struct json_object *request) {
struct json_object *value = NULL;
if (!json_object_object_get_ex(request, "title_id", &value) ||
!json_object_is_type(value, json_type_string)) {
return NULL;
}
const char *title_id = json_object_get_string(value);
return is_supported_game_title_id(title_id) ? title_id : NULL;
}
static void handle_version(int fd) {
struct json_object *response = new_status_response(0);
struct json_object *capabilities = json_object_new_array();
if (!response || !capabilities) {
if (response)
json_object_put(response);
if (capabilities)
json_object_put(capabilities);
send_out_of_memory_response(fd);
return;
}
if (!add_json_int(response, "api_version", SM_API_VERSION) ||
!add_json_string(response, "shadowmount_version", SHADOWMOUNT_VERSION) ||
!append_json_string(capabilities, "list_images") ||
!append_json_string(capabilities, "list_games") ||
!append_json_string(capabilities, "mount_game") ||
!append_json_string(capabilities, "unmount_game")) {
json_object_put(capabilities);
json_object_put(response);
send_out_of_memory_response(fd);
return;
}
if (!add_json_value(response, "capabilities", capabilities)) {
json_object_put(response);
send_out_of_memory_response(fd);
return;
}
(void)send_json_object(fd, 200, response);
json_object_put(response);
}
static struct json_object *image_to_json(const void *entry) {
const sm_image_index_snapshot_entry_t *source = entry;
struct json_object *item = json_object_new_object();
if (!item)
return NULL;
char mount_point[MAX_PATH];
get_image_mount_point_for_source(source->path, mount_point);
bool source_available = path_exists(source->path);
bool mapped = false;
bool mounted = false;
int unit_id = -1;
attach_backend_t backend = ATTACH_BACKEND_NONE;
image_cache_entry_t cached;
if (find_image_cache_entry(source->path, &cached, NULL)) {
mapped = true;
(void)strlcpy(mount_point, cached.mount_point, sizeof(mount_point));
unit_id = cached.unit_id;
backend = cached.backend;
mounted = cached.unit_id >= 0 && cached.backend != ATTACH_BACKEND_NONE &&
is_active_image_mount_point(cached.mount_point);
}
if (!add_json_string(item, "path", source->path) ||
!add_json_string(item, "mount_point", mount_point) ||
!add_json_int(item, "size", source->size) ||
!add_json_int(item, "mtime_sec", source->mtime_sec) ||
!add_json_int(item, "mtime_nsec", source->mtime_nsec) ||
!add_json_int(item, "unit_id", unit_id) ||
!add_json_string(item, "backend", attach_backend_name(backend)) ||
!add_json_bool(item, "complete", source->complete) ||
!add_json_bool(item, "source_available", source_available) ||
!add_json_bool(item, "mapped", mapped) ||
!add_json_bool(item, "mounted", mounted)) {
json_object_put(item);
return NULL;
}
return item;
}
static struct json_object *game_to_json(const void *entry) {
const sm_game_cache_snapshot_entry_t *source = entry;
struct json_object *item = json_object_new_object();
if (!item)
return NULL;
char managed_path[MAX_PATH];
char image_path[MAX_PATH] = {0};
bool installed = is_installed(source->title_id);
bool mounted = is_data_mounted(source->title_id);
bool managed =
read_mount_link(source->title_id, managed_path, sizeof(managed_path));
bool image_backed = read_mount_image_link(source->title_id, image_path,
sizeof(image_path));
bool source_available = path_exists(source->path) ||
(image_path[0] != '\0' && path_exists(image_path));
if (!add_json_string(item, "path", source->path) ||
!add_json_string(item, "image_path", image_path) ||
!add_json_string(item, "title_id", source->title_id) ||
!add_json_string(item, "title_name", source->title_name) ||
!add_json_bool(item, "installed", installed) ||
!add_json_bool(item, "managed", managed) ||
!add_json_bool(item, "mounted", mounted) ||
!add_json_bool(item, "image_backed", image_backed) ||
!add_json_bool(item, "source_available", source_available)) {
json_object_put(item);
return NULL;
}
return item;
}
typedef struct json_object *(*snapshot_to_json_fn)(const void *entry);
static void send_snapshot_response(int fd, void *snapshot, size_t count,
size_t entry_size, const char *array_key,
snapshot_to_json_fn to_json) {
struct json_object *response = new_status_response(0);
struct json_object *items = json_object_new_array_ext((int)count);
if (!response || !items) {
if (response)
json_object_put(response);
if (items)
json_object_put(items);
free(snapshot);
send_out_of_memory_response(fd);
return;
}
const unsigned char *entries = snapshot;
for (size_t i = 0; i < count; ++i) {
struct json_object *item = to_json(entries + i * entry_size);
if (!item || json_object_array_add(items, item) != 0) {
if (item)
json_object_put(item);
json_object_put(items);
json_object_put(response);
free(snapshot);
send_out_of_memory_response(fd);
return;
}
}
free(snapshot);
if (!add_json_int(response, "count", (int64_t)count)) {
json_object_put(items);
json_object_put(response);
send_out_of_memory_response(fd);
return;
}
if (!add_json_value(response, array_key, items)) {
json_object_put(response);
send_out_of_memory_response(fd);
return;
}
(void)send_json_object(fd, 200, response);
json_object_put(response);
}
static void handle_images(int fd) {
sm_image_index_snapshot_entry_t *snapshot = NULL;
size_t count = 0;
if (!sm_image_index_snapshot(&snapshot, &count)) {
send_out_of_memory_response(fd);
return;
}
send_snapshot_response(fd, snapshot, count, sizeof(*snapshot), "images",
image_to_json);
}
static void handle_games(int fd) {
sm_game_cache_snapshot_entry_t *snapshot = NULL;
size_t count = 0;
if (!sm_game_cache_snapshot(&snapshot, &count)) {
send_out_of_memory_response(fd);
return;
}
send_snapshot_response(fd, snapshot, count, sizeof(*snapshot), "games",
game_to_json);
}
static int operation_http_status(int status) {
switch (status) {
case 0:
return 200;
case EINVAL:
return 400;
case ENOENT:
return 404;
case EBUSY:
return 409;
case ENOTSUP:
return 501;
default:
return 500;
}
}
static void handle_mount_operation(int fd, struct json_object *request,
bool mount) {
const char *title_id = get_title_id(request);
if (!title_id) {
send_error_response(fd, 400, EINVAL,
"title_id must be a valid PS4 or PS5 title ID");
return;
}
int status = mount ? sm_shellcore_mount_title_runtime(title_id)
: sm_shellcore_unmount_title_runtime(title_id);
log_debug(" [API] HTTP %s game: title=%s status=%d",
mount ? "mount" : "unmount", title_id, status);
if (status != 0) {
send_error_response(fd, operation_http_status(status), status,
strerror(status));
return;
}
struct json_object *response = new_status_response(0);
if (!response) {
send_out_of_memory_response(fd);
return;
}
if (!add_json_string(response, "title_id", title_id) ||
!add_json_bool(response, "mounted", mount)) {
json_object_put(response);
send_out_of_memory_response(fd);
return;
}
(void)send_json_object(fd, 200, response);
json_object_put(response);
}
static void handle_client(int fd) {
sm_http_request_t request;
memset(&request, 0, sizeof(request));
int http_status = 400;
const char *error = "invalid HTTP request";
if (!receive_http_request(fd, &request, &http_status, &error)) {
send_error_response(fd, http_status,
http_status == 500 ? ENOMEM : EPROTO, error);
return;
}
if (request.preflight) {
send_preflight_response(fd);
free(request.storage);
return;
}
struct json_object *json = parse_request_json(&request);
if (!json) {
send_error_response(fd, 400, EINVAL,
"request body must be one valid JSON object");
free(request.storage);
return;
}
if (strcmp(request.route, SM_API_ROUTE_VERSION) == 0) {
handle_version(fd);
} else if (strcmp(request.route, SM_API_ROUTE_IMAGES) == 0) {
handle_images(fd);
} else if (strcmp(request.route, SM_API_ROUTE_GAMES) == 0) {
handle_games(fd);
} else if (strcmp(request.route, SM_API_ROUTE_MOUNT) == 0) {
handle_mount_operation(fd, json, true);
} else if (strcmp(request.route, SM_API_ROUTE_UNMOUNT) == 0) {
handle_mount_operation(fd, json, false);
} else {
send_error_response(fd, 404, ENOENT, "unknown API route");
}
json_object_put(json);
free(request.storage);
}
static void *service_thread_main(void *arg) {
(void)arg;
pthread_mutex_lock(&g_api.mutex);
char bind_address[MAX_API_BIND_ADDRESS];
(void)strlcpy(bind_address, g_api.bind_address, sizeof(bind_address));
uint16_t port = g_api.port;
pthread_mutex_unlock(&g_api.mutex);
int listen_fd = -1;
uint32_t retry_delay_ms = SM_API_RETRY_INITIAL_MS;
bool outage_logged = false;
while (true) {
if (listen_fd < 0) {
listen_fd = open_http_listener(bind_address, port);
if (listen_fd < 0) {
int open_error = errno;
if (!outage_logged) {
log_debug(" [API] HTTP listener unavailable at %s:%u: %s; "
"recovery scheduled",
bind_address, (unsigned)port, strerror(open_error));
outage_logged = true;
}
if (!wait_before_listener_retry(retry_delay_ms))
break;
retry_delay_ms = next_retry_delay_ms(retry_delay_ms);
continue;
}
pthread_mutex_lock(&g_api.mutex);
if (g_api.stop_requested) {
pthread_mutex_unlock(&g_api.mutex);
close(listen_fd);
break;
}
g_api.listen_fd = listen_fd;
pthread_mutex_unlock(&g_api.mutex);
if (outage_logged) {
log_debug(" [API] HTTP listener recovered: http://%s:%u/api/v1",
bind_address, (unsigned)port);
} else {
log_debug(" [API] HTTP/JSON ready: http://%s:%u/api/v1 (v%u)",
bind_address, (unsigned)port, SM_API_VERSION);
}
outage_logged = false;
retry_delay_ms = SM_API_RETRY_INITIAL_MS;
}
int client = accept(listen_fd, NULL, NULL);
if (client < 0) {
int accept_error = errno;
if (accept_error == EINTR || accept_error == ECONNABORTED ||
accept_error == EPROTO) {
continue;
}
pthread_mutex_lock(&g_api.mutex);
if (g_api.stop_requested) {
pthread_mutex_unlock(&g_api.mutex);
break;
}
if (accept_error_needs_wait(accept_error)) {
pthread_mutex_unlock(&g_api.mutex);
if (!outage_logged) {
log_debug(" [API] HTTP accept paused: %s; recovery scheduled",
strerror(accept_error));
outage_logged = true;
}
if (!wait_before_listener_retry(retry_delay_ms))
break;
retry_delay_ms = next_retry_delay_ms(retry_delay_ms);
continue;
}
bool owns_listener = g_api.listen_fd == listen_fd;
if (owns_listener)
g_api.listen_fd = -1;
pthread_mutex_unlock(&g_api.mutex);
if (owns_listener)
close(listen_fd);
listen_fd = -1;
if (!outage_logged) {
log_debug(" [API] HTTP listener lost: %s; recovery scheduled",
strerror(accept_error));
outage_logged = true;
}
if (!wait_before_listener_retry(retry_delay_ms))
break;
retry_delay_ms = next_retry_delay_ms(retry_delay_ms);
continue;
}
if (outage_logged) {
log_debug(" [API] HTTP accept recovered");
outage_logged = false;
}
retry_delay_ms = SM_API_RETRY_INITIAL_MS;
struct timeval timeout = {.tv_sec = 5, .tv_usec = 0};
(void)setsockopt(client, SOL_SOCKET, SO_RCVTIMEO, &timeout,
sizeof(timeout));
(void)setsockopt(client, SOL_SOCKET, SO_SNDTIMEO, &timeout,
sizeof(timeout));
pthread_mutex_lock(&g_api.mutex);
if (g_api.stop_requested) {
pthread_mutex_unlock(&g_api.mutex);
close(client);
break;
}
g_api.client_fd = client;
pthread_mutex_unlock(&g_api.mutex);
handle_client(client);
pthread_mutex_lock(&g_api.mutex);
if (g_api.client_fd == client)
g_api.client_fd = -1;
pthread_mutex_unlock(&g_api.mutex);
close(client);
}
return NULL;
}
bool sm_api_service_start(void) {
const runtime_config_t *cfg = runtime_config();
char bind_address[MAX_API_BIND_ADDRESS];
(void)strlcpy(bind_address, cfg->api_bind_address, sizeof(bind_address));
uint16_t port = (uint16_t)cfg->api_port;
pthread_mutex_lock(&g_api.mutex);
if (g_api.started) {
pthread_mutex_unlock(&g_api.mutex);
return true;
}
g_api.listen_fd = -1;
g_api.client_fd = -1;
g_api.stop_requested = false;
(void)strlcpy(g_api.bind_address, bind_address,
sizeof(g_api.bind_address));
g_api.port = port;
int rc = pthread_create(&g_api.thread, NULL, service_thread_main, NULL);
if (rc != 0) {
g_api.listen_fd = -1;
pthread_mutex_unlock(&g_api.mutex);
errno = rc;
return false;
}
g_api.started = true;
pthread_mutex_unlock(&g_api.mutex);
return true;
}
void sm_api_service_stop(void) {
pthread_mutex_lock(&g_api.mutex);
if (!g_api.started) {
pthread_mutex_unlock(&g_api.mutex);
return;
}
g_api.stop_requested = true;
int fd = g_api.listen_fd;
int client_fd = g_api.client_fd;
g_api.listen_fd = -1;
pthread_cond_broadcast(&g_api.retry_cond);
pthread_mutex_unlock(&g_api.mutex);
if (client_fd >= 0)
(void)shutdown(client_fd, SHUT_RDWR);
if (fd >= 0) {
(void)shutdown(fd, SHUT_RDWR);
close(fd);
}
pthread_join(g_api.thread, NULL);
pthread_mutex_lock(&g_api.mutex);
g_api.started = false;
g_api.stop_requested = false;
g_api.client_fd = -1;
pthread_mutex_unlock(&g_api.mutex);
}
bool sm_api_service_reconfigure(void) {
sm_api_service_stop();
return sm_api_service_start();
}
+47 -82
View File
@@ -14,13 +14,10 @@ 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;
@@ -52,13 +49,10 @@ 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);
@@ -424,28 +418,6 @@ 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);
@@ -472,10 +444,8 @@ 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);
}
@@ -500,60 +470,34 @@ 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;
}
bool get_app_db_auto_remove_title_list(struct AppDbTitleList *list_out) {
if (!list_out)
return false;
struct stat st;
int app_db_stat_rc = stat(APP_DB_PATH, &st);
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);
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_unlock(&g_app_db_mutex);
return copied;
}
@@ -562,12 +506,33 @@ 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);
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);
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);
pthread_mutex_unlock(&g_app_db_mutex);
return loaded;
}
+21 -106
View File
@@ -1,13 +1,7 @@
#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"
@@ -104,6 +98,7 @@ static bool lookup_kstuff_delay_override_in_file(const char *path,
static bool upsert_kstuff_delay_override_in_file(const char *path,
const char *title_id,
uint32_t delay_seconds);
static void apply_firmware_runtime_overrides(runtime_config_state_t *state);
static char *trim_ascii(char *s) {
while (*s == ' ' || *s == '\t' || *s == '\r' || *s == '\n')
@@ -157,6 +152,19 @@ static bool parse_ini_line(char *line, char **key_out, char **value_out) {
return true;
}
static void apply_firmware_runtime_overrides(runtime_config_state_t *state) {
if (!state)
return;
if (sm_firmware_major_version() >= 12u) {
state->cfg.app_install_all_enabled = true;
state->cfg.app_install_all_forced = true;
return;
}
state->cfg.app_install_all_forced = false;
}
static const runtime_config_state_t *active_runtime_state(void) {
return &g_runtime_state_slots[atomic_load_explicit(&g_runtime_state_active_index,
memory_order_acquire)];
@@ -241,31 +249,23 @@ 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.app_install_all_forced = 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;
@@ -276,6 +276,7 @@ static void init_runtime_config_defaults(runtime_config_state_t *state) {
memset(state->image_mode_rules, 0, sizeof(state->image_mode_rules));
clear_kstuff_title_rules(state);
init_runtime_scan_paths_defaults(state);
apply_firmware_runtime_overrides(state);
}
static config_file_stamp_t read_config_file_stamp(void) {
@@ -1170,42 +1171,6 @@ 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)) {
@@ -1225,39 +1190,13 @@ 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);
continue;
}
state->cfg.app_install_all_enabled = bval;
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;
state->cfg.app_install_all_forced = false;
continue;
}
@@ -1429,22 +1368,6 @@ 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) {
@@ -1546,6 +1469,7 @@ static config_load_status_t load_runtime_config_state(runtime_config_state_t *st
log_debug(" [CFG] recursive_scan=1 is deprecated; forcing scan_depth=2");
}
apply_firmware_runtime_overrides(state);
int image_rule_count = 0;
for (int k = 0; k < MAX_IMAGE_MODE_RULES; k++) {
@@ -1559,11 +1483,8 @@ 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 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 "
log_debug(" [CFG] loaded: debug=%d quiet=%d ro=%d force=%d "
"app_install_all=%d app_install_all_forced=%d 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 "
@@ -1574,16 +1495,10 @@ 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.app_install_all_forced ? 1 : 0, state->cfg.scan_depth,
state->cfg.legacy_recursive_scan_forced ? 1 : 0,
state->cfg.backport_fakelib_enabled ? 1 : 0,
state->cfg.global_fakelib_enabled ? 1 : 0,
+6 -13
View File
@@ -3,7 +3,6 @@
#include "sm_fakelib.h"
#include "sm_config_mount.h"
#include "sm_log.h"
#include "sm_mount_diag.h"
#include "sm_types.h"
#include <pthread.h>
@@ -53,19 +52,15 @@ static bool mount_fakelib_overlay(const char *title_id,
static bool unmount_fakelib_overlay(const fakelib_layer_t *layer) {
const char *mount_path = layer->mount_path;
if (unmount(mount_path, 0) == 0 || errno == ENOENT ||
if (unmount(mount_path, MNT_FORCE) == 0 || errno == ENOENT ||
errno == EINVAL) {
log_debug(" [FAKELIB] %s libraries unmounted: %s -> %s", layer->label,
layer->source_path, mount_path);
return true;
}
int unmount_errno = errno;
if (unmount_errno == EBUSY)
sm_mount_diag_log_busy(mount_path);
log_debug(" [FAKELIB] %s unmount deferred for %s: %s", layer->label,
mount_path, strerror(unmount_errno));
errno = unmount_errno;
log_debug(" [FAKELIB] %s unmount failed for %s: %s", layer->label,
mount_path, strerror(errno));
return false;
}
@@ -113,10 +108,8 @@ static bool resolve_sandbox_context(const char *title_id,
continue;
errno = 0;
char *suffix_end = NULL;
long idx_long = strtol(suffix, &suffix_end, 10);
if (errno == ERANGE || !suffix_end || *suffix_end != '\0' ||
idx_long > INT_MAX)
long idx_long = strtol(suffix, NULL, 10);
if (errno == ERANGE || idx_long > INT_MAX)
continue;
int idx = (int)idx_long;
if (idx < best_index)
@@ -294,7 +287,7 @@ void sm_fakelib_game_on_exec(pid_t pid, const char *title_id) {
}
if (has_game)
notify_system_info_l10n(SM_L10N_GAME_BACKPORTED, title_id);
notify_system_info("Game backported: %s", title_id);
pthread_mutex_unlock(&g_fakelib_mutex);
}
+74 -271
View File
@@ -4,16 +4,11 @@
#include "sm_config_mount.h"
#include "sm_limits.h"
#include "sm_log.h"
#include "sm_mount_diag.h"
#include "sm_image_cache.h"
#include "sm_image.h"
#include "sm_path_utils.h"
#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) {
char path[MAX_PATH];
@@ -92,7 +87,7 @@ typedef bool (*title_app_dir_iter_fn)(const char *title_id,
const title_link_paths_t *paths,
void *ctx);
static bool for_each_title_app_dir(const char *open_error_context,
static void for_each_title_app_dir(const char *open_error_context,
bool stop_on_signal,
title_app_dir_iter_fn fn, void *ctx) {
DIR *d = opendir(APP_BASE);
@@ -100,30 +95,23 @@ static bool for_each_title_app_dir(const char *open_error_context,
if (errno != ENOENT)
log_debug(" [LINK] open %s failed%s: %s", APP_BASE,
open_error_context ? open_error_context : "", strerror(errno));
return errno == ENOENT;
return;
}
bool completed = true;
struct dirent *entry;
while ((entry = readdir(d)) != NULL) {
if (stop_on_signal && should_stop_requested()) {
completed = false;
if (stop_on_signal && should_stop_requested())
break;
}
if (!resolve_title_app_dir(entry, NULL, 0))
continue;
title_link_paths_t paths;
build_title_link_paths(entry->d_name, &paths);
if (!fn(entry->d_name, &paths, ctx)) {
completed = false;
if (!fn(entry->d_name, &paths, ctx))
break;
}
}
if (closedir(d) != 0)
completed = false;
return completed;
closedir(d);
}
bool read_mount_link(const char *title_id, char *out, size_t out_size) {
@@ -136,131 +124,6 @@ bool read_mount_link(const char *title_id, char *out, size_t out_size) {
return read_mount_link_file(paths.mount_link, out, out_size);
}
static bool write_link_lines_atomic(const char *path,
const char *const *lines,
size_t count) {
char temp_path[MAX_PATH];
int written = snprintf(temp_path, sizeof(temp_path), "%s.tmp", path);
if (written < 0 || (size_t)written >= sizeof(temp_path))
return false;
FILE *file = fopen(temp_path, "w");
if (!file)
return false;
bool ok = true;
for (size_t i = 0; i < count; ++i) {
if (!lines[i] || lines[i][0] == '\0' ||
fprintf(file, "%s%s", lines[i], i + 1u < count ? "\n" : "") < 0) {
ok = false;
break;
}
}
if (ok && fflush(file) != 0)
ok = false;
if (ok && fsync(fileno(file)) != 0)
ok = false;
if (fclose(file) != 0)
ok = false;
if (ok && rename(temp_path, path) == 0)
return true;
int saved_errno = errno != 0 ? errno : EIO;
(void)unlink(temp_path);
errno = saved_errno;
return false;
}
bool write_mount_link(const char *title_id, const char *source_path) {
if (!title_id || title_id[0] == '\0' || !source_path ||
source_path[0] == '\0') {
return false;
}
title_link_paths_t paths;
build_title_link_paths(title_id, &paths);
const char *lines[] = {source_path};
return write_link_lines_atomic(paths.mount_link, lines, 1);
}
bool read_mount_image_link(const char *title_id, char *out, size_t out_size) {
if (!title_id || !out || out_size == 0)
return false;
out[0] = '\0';
title_link_paths_t paths;
build_title_link_paths(title_id, &paths);
return read_mount_link_file(paths.mount_image_link, out, out_size);
}
bool write_mount_image_link(const char *title_id, const char *image_path) {
if (!title_id || title_id[0] == '\0' || !image_path ||
image_path[0] == '\0') {
return false;
}
char chain[MAX_IMAGE_CHAIN_DEPTH][MAX_PATH] = {{0}};
(void)strlcpy(chain[0], image_path, sizeof(chain[0]));
return write_mount_image_chain(title_id, chain, 1);
}
bool read_mount_image_chain(
const char *title_id,
char paths[MAX_IMAGE_CHAIN_DEPTH][MAX_PATH], size_t *count_out) {
if (!title_id || !paths || !count_out)
return false;
*count_out = 0;
title_link_paths_t link_paths;
build_title_link_paths(title_id, &link_paths);
FILE *file = fopen(link_paths.mount_image_link, "r");
if (!file)
return false;
bool ok = true;
while (*count_out < MAX_IMAGE_CHAIN_DEPTH &&
fgets(paths[*count_out], MAX_PATH, file)) {
size_t len = strcspn(paths[*count_out], "\r\n");
if (paths[*count_out][len] == '\0' && len + 1u >= MAX_PATH) {
int next = fgetc(file);
if (next != EOF && next != '\n' && next != '\r') {
ok = false;
break;
}
}
paths[*count_out][len] = '\0';
if (len == 0) {
ok = false;
break;
}
(*count_out)++;
}
if (ok && *count_out == MAX_IMAGE_CHAIN_DEPTH && fgetc(file) != EOF)
ok = false;
if (ferror(file))
ok = false;
if (fclose(file) != 0)
ok = false;
if (!ok)
*count_out = 0;
return ok && *count_out > 0;
}
bool write_mount_image_chain(
const char *title_id,
const char paths[MAX_IMAGE_CHAIN_DEPTH][MAX_PATH], size_t count) {
if (!title_id || title_id[0] == '\0' || !paths || count == 0 ||
count > MAX_IMAGE_CHAIN_DEPTH) {
return false;
}
title_link_paths_t link_paths;
build_title_link_paths(title_id, &link_paths);
const char *lines[MAX_IMAGE_CHAIN_DEPTH];
for (size_t i = 0; i < count; ++i)
lines[i] = paths[i];
return write_link_lines_atomic(link_paths.mount_image_link, lines, count);
}
bool resolve_title_app_dir(const struct dirent *entry, char *app_dir,
size_t app_dir_size) {
if (!entry)
@@ -296,18 +159,14 @@ bool resolve_title_app_dir(const struct dirent *entry, char *app_dir,
return true;
}
static bool source_path_needs_cleanup(const char *source_path) {
static bool source_path_needs_cleanup(const char *source_path,
bool *tried_image_recovery) {
if (is_under_image_mount_base(source_path)) {
char image_source_path[MAX_PATH];
bool has_image_source = resolve_image_source_from_mount_cache(
source_path, image_source_path, sizeof(image_source_path));
if (has_image_source) {
if (!path_exists(image_source_path))
return true;
// The image mount point is intentionally absent outside install/launch.
if (!path_exists(source_path))
return false;
}
if (has_image_source && !path_exists(image_source_path))
return true;
}
if (!path_exists(source_path))
@@ -323,6 +182,11 @@ static bool source_path_needs_cleanup(const char *source_path) {
if (path_exists(eboot_path))
return false;
if (!*tried_image_recovery && is_under_image_mount_base(source_path)) {
cleanup_stale_image_mounts();
*tried_image_recovery = true;
}
return !path_exists(eboot_path);
}
@@ -595,8 +459,7 @@ static bool inspect_title_stack(const char *title_id, const char *source_path,
return true;
}
static bool unmount_top_controlled_layer_impl(const char *path,
bool diagnose_busy) {
static bool unmount_top_controlled_layer(const char *path) {
struct statfs mount_st;
if (!get_top_mount(path, &mount_st))
return true;
@@ -613,22 +476,13 @@ static bool unmount_top_controlled_layer_impl(const char *path,
if (unmount(path, 0) == 0 || errno == ENOENT || errno == EINVAL)
return true;
int unmount_errno = errno;
if (unmount_errno == EBUSY && diagnose_busy)
sm_mount_diag_log_busy(path);
if (unmount(path, MNT_FORCE) == 0 || errno == ENOENT || errno == EINVAL)
return true;
if (unmount_errno != EBUSY || diagnose_busy) {
log_debug(" [LINK] unmount deferred for %s: %s", path,
strerror(unmount_errno));
}
errno = unmount_errno;
log_debug(" [LINK] unmount failed for %s: %s", path, strerror(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;
}
@@ -647,60 +501,6 @@ bool rollback_title_nullfs_mount(const char *title_id, const char *src_path) {
return true;
}
static bool unmount_title_runtime_layers_impl(const char *title_id,
bool diagnose_busy) {
if (!title_id || title_id[0] == '\0')
return false;
char path[MAX_PATH];
int written = snprintf(path, sizeof(path), "/system_ex/app/%s", title_id);
if (written < 0 || (size_t)written >= sizeof(path))
return false;
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;
char source_path[MAX_PATH];
if (!read_mount_link_file(paths->mount_link, source_path,
sizeof(source_path))) {
return true;
}
title_mount_state_t state;
if (!inspect_title_stack(title_id, source_path, NULL, &state)) {
*all_released = false;
return true;
}
if (!state.has_our_nullfs) {
return true;
}
if (!unmount_title_runtime_layers(title_id))
*all_released = false;
return true;
}
bool unmount_all_title_runtime_layers(void) {
bool all_released = true;
if (!for_each_title_app_dir(" during runtime mount cleanup", false,
unmount_managed_title_entry, &all_released)) {
all_released = false;
}
return all_released;
}
bool reconcile_title_backport_mount(const char *title_id, const char *src_path,
const char *expected_backport_path,
bool *overlay_active_out) {
@@ -776,18 +576,17 @@ 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_l10n(SM_L10N_BACKPORT_OVERLAY_FAILED, title_id, backport_path,
(uint32_t)overlay_err);
notify_system("Backport overlay failed: %s\n%s\n0x%08X", title_id,
backport_path, (uint32_t)overlay_err);
return false;
}
static bool unmount_controlled_mount_stack_impl(const char *path,
bool diagnose_busy) {
static bool unmount_controlled_mount_stack(const char *path) {
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_impl(path, diagnose_busy))
if (!unmount_top_controlled_layer(path))
return false;
}
@@ -795,14 +594,27 @@ static bool unmount_controlled_mount_stack_impl(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);
}
if (unmount(path, 0) == 0) {
log_debug(" [LINK] extra unmount for recovery: %s", path);
return;
}
if (errno == ENOENT || errno == EINVAL)
return;
if (unmount(path, MNT_FORCE) == 0) {
log_debug(" [LINK] extra forced unmount for recovery: %s", path);
return;
}
if (errno == ENOENT || errno == EINVAL)
return;
log_debug(" [LINK] extra unmount for recovery did not complete for %s: %s",
path, strerror(errno));
}
static bool recover_mount_directory_after_stat_failure(
@@ -943,7 +755,7 @@ int copy_file(const char *src, const char *dst) {
return copy_param_json_rewrite(src, dst);
}
char buf[64 * 1024];
char buf[8192];
FILE *fs = fopen(src, "rb");
if (!fs)
return -1;
@@ -1068,7 +880,8 @@ bool mount_title_nullfs(const char *title_id, const char *src_path) {
return false;
}
if (!path_exists(src_eboot)) {
if (source_path_needs_cleanup(src_path)) {
bool tried_image_recovery = false;
if (source_path_needs_cleanup(src_path, &tried_image_recovery)) {
log_debug(" [LINK] source eboot.bin missing for %s: %s", title_id,
src_eboot);
return false;
@@ -1132,16 +945,20 @@ bool mount_title_nullfs(const char *title_id, const char *src_path) {
}
if (runtime_sleep_mode_active()) {
if (!unmount_top_controlled_layer(dst))
log_debug(" [LINK] nullfs rollback deferred during sleep: %s", dst);
if (unmount(dst, 0) != 0 && errno != ENOENT && errno != EINVAL)
(void)unmount(dst, MNT_FORCE);
return false;
}
if (!path_exists(dst_eboot)) {
log_debug(" [LINK] mounted nullfs but eboot.bin is missing at target: %s",
dst_eboot);
if (!unmount_top_controlled_layer(dst))
log_debug(" [LINK] empty nullfs rollback deferred: %s", dst);
if (unmount(dst, 0) != 0 && errno != ENOENT && errno != EINVAL) {
if (unmount(dst, MNT_FORCE) != 0 && errno != ENOENT && errno != EINVAL) {
log_debug(" [LINK] failed to rollback empty nullfs mount %s: %s", dst,
strerror(errno));
}
}
return false;
}
log_debug(" [LINK] nullfs mounted: %s -> %s", src_path, dst);
@@ -1151,9 +968,9 @@ bool mount_title_nullfs(const char *title_id, const char *src_path) {
typedef struct {
const char *removed_source_root;
bool unmount_system_ex_bind;
bool tried_image_recovery;
bool force_remove_matching_source;
bool match_usb_sources;
bool preserve_mount_links;
} cleanup_mount_links_ctx_t;
static bool cleanup_mount_links_entry(const char *title_id,
@@ -1197,10 +1014,12 @@ static bool cleanup_mount_links_entry(const char *title_id,
return true;
should_remove = ctx->force_remove_matching_source ||
(has_image_source && !path_exists(image_source_path)) ||
source_path_needs_cleanup(source_path);
source_path_needs_cleanup(source_path,
&ctx->tried_image_recovery);
} else {
should_remove = (has_image_source && !path_exists(image_source_path)) ||
source_path_needs_cleanup(source_path);
source_path_needs_cleanup(source_path,
&ctx->tried_image_recovery);
}
}
@@ -1223,19 +1042,17 @@ static bool cleanup_mount_links_entry(const char *title_id,
stack_matches_link = state.has_nullfs_from_root;
}
if (stack_matches_link && !top_is_ours) {
if (inspected && stack_matches_link && !top_is_ours) {
keep_mount_link = true;
log_debug(" [LINK] keeping mount link for %s: stack still active but "
"top layer is not ours", state.system_ex_path);
} else if (top_is_ours && stack_matches_link) {
if (!ctx->preserve_mount_links) {
if (rename(paths->mount_link, paths->staged_mount_link) != 0) {
log_debug(" [LINK] stage failed for %s: %s", paths->mount_link,
strerror(errno));
return true;
}
mount_link_staged = true;
} else if (inspected && top_is_ours && stack_matches_link) {
if (rename(paths->mount_link, paths->staged_mount_link) != 0) {
log_debug(" [LINK] stage failed for %s: %s", paths->mount_link,
strerror(errno));
return true;
}
mount_link_staged = true;
if (!unmount_controlled_mount_stack(state.system_ex_path)) {
keep_mount_link = true;
log_debug(" [LINK] failed to unmount mount stack for %s",
@@ -1249,9 +1066,6 @@ static bool cleanup_mount_links_entry(const char *title_id,
}
}
if (ctx->preserve_mount_links)
keep_mount_link = true;
if (!keep_mount_link) {
int unlink_res = mount_link_staged ? unlink(paths->staged_mount_link)
: unlink(paths->mount_link);
@@ -1270,7 +1084,8 @@ static bool cleanup_mount_links_entry(const char *title_id,
}
if (mount_link_staged && rename(paths->staged_mount_link, paths->mount_link) == 0) {
log_debug(" [LINK] restored staged mount link: %s", paths->mount_link);
log_debug(" [LINK] restored mount link after failed cleanup: %s",
paths->mount_link);
} else if (mount_link_staged) {
log_debug(" [LINK] restore failed for %s: %s", paths->mount_link,
strerror(errno));
@@ -1283,9 +1098,9 @@ void cleanup_mount_links(const char *removed_source_root,
cleanup_mount_links_ctx_t ctx = {
.removed_source_root = removed_source_root,
.unmount_system_ex_bind = unmount_system_ex_bind,
.tried_image_recovery = false,
.force_remove_matching_source = false,
.match_usb_sources = false,
.preserve_mount_links = false,
};
for_each_title_app_dir("", !unmount_system_ex_bind, cleanup_mount_links_entry,
&ctx);
@@ -1298,9 +1113,9 @@ void cleanup_mount_links_for_source_unmount(const char *source_root) {
cleanup_mount_links_ctx_t ctx = {
.removed_source_root = source_root,
.unmount_system_ex_bind = true,
.tried_image_recovery = false,
.force_remove_matching_source = true,
.match_usb_sources = false,
.preserve_mount_links = false,
};
for_each_title_app_dir(" during source unmount cleanup", false,
cleanup_mount_links_entry, &ctx);
@@ -1310,9 +1125,9 @@ void cleanup_usb_mount_links_for_suspend(void) {
cleanup_mount_links_ctx_t ctx = {
.removed_source_root = NULL,
.unmount_system_ex_bind = true,
.tried_image_recovery = false,
.force_remove_matching_source = false,
.match_usb_sources = true,
.preserve_mount_links = true,
};
for_each_title_app_dir(" during USB suspend cleanup", false,
cleanup_mount_links_entry, &ctx);
@@ -1321,7 +1136,7 @@ void cleanup_usb_mount_links_for_suspend(void) {
static bool shutdown_title_mounts_entry(const char *title_id,
const title_link_paths_t *paths,
void *ctx) {
bool *all_released = (bool *)ctx;
(void)ctx;
char source_path[MAX_PATH];
if (!read_mount_link_file(paths->mount_link, source_path, sizeof(source_path))) {
@@ -1329,32 +1144,20 @@ static bool shutdown_title_mounts_entry(const char *title_id,
}
title_mount_state_t state;
if (!inspect_title_stack(title_id, source_path, NULL, &state)) {
*all_released = false;
return true;
}
if (!state.has_our_nullfs)
return true;
if (!title_stack_top_is_managed(&state)) {
log_debug(" [LINK] shutdown stack blocked by unmanaged top for %s",
state.system_ex_path);
*all_released = false;
if (!inspect_title_stack(title_id, source_path, NULL, &state) ||
!state.has_our_nullfs ||
!title_stack_top_is_managed(&state)) {
return true;
}
if (!unmount_controlled_mount_stack(state.system_ex_path)) {
log_debug(" [LINK] failed to unmount shutdown stack for %s",
state.system_ex_path);
*all_released = false;
}
return true;
}
bool shutdown_title_mounts(void) {
bool all_released = true;
if (!for_each_title_app_dir(" during shutdown", false,
shutdown_title_mounts_entry, &all_released)) {
all_released = false;
}
return all_released;
void shutdown_title_mounts(void) {
for_each_title_app_dir(" during shutdown", false,
shutdown_title_mounts_entry, NULL);
}
+23 -317
View File
@@ -1,16 +1,11 @@
#include "sm_platform.h"
#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 {
@@ -18,34 +13,10 @@ 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;
};
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) {
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 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,
char owning_scan_root[MAX_PATH]) {
@@ -85,7 +56,6 @@ 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;
}
@@ -96,27 +66,6 @@ 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;
@@ -140,7 +89,6 @@ void cache_game_entry(const char *path, const char *title_id,
char owning_scan_root[MAX_PATH];
(void)resolve_game_cache_owning_scan_root(path, owning_scan_root);
pthread_mutex_lock(&g_game_cache_mutex);
for (int k = 0; k < MAX_PENDING; k++) {
if (!g_game_cache[k].valid)
continue;
@@ -150,7 +98,6 @@ void cache_game_entry(const char *path, const char *title_id,
}
write_game_cache_slot(&g_game_cache[k], path, title_id, title_name,
owning_scan_root);
pthread_mutex_unlock(&g_game_cache_mutex);
return;
}
@@ -158,218 +105,40 @@ void cache_game_entry(const char *path, const char *title_id,
if (!g_game_cache[k].valid) {
write_game_cache_slot(&g_game_cache[k], path, title_id, title_name,
owning_scan_root);
pthread_mutex_unlock(&g_game_cache_mutex);
return;
}
}
pthread_mutex_unlock(&g_game_cache_mutex);
}
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);
void prune_game_cache(void) {
for (int k = 0; k < MAX_PENDING; k++) {
if (!g_game_cache[k].valid)
continue;
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)
if (path_exists(g_game_cache[k].path))
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;
if (g_game_cache[k].path[0] == '\0')
clear_game_cache_slot(k, NULL);
else
g_game_cache[k].missing_since_us = 0;
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 (path_exists(g_game_cache[k].path))
continue;
clear_game_cache_slot(k, "source removed");
}
pthread_mutex_unlock(&g_game_cache_mutex);
}
void for_each_cached_game_entry(const char *root, game_cache_iter_fn fn,
@@ -378,115 +147,52 @@ void for_each_cached_game_entry(const char *root, game_cache_iter_fn fn,
return;
for (int k = 0; k < MAX_PENDING; k++) {
struct GameCache entry;
bool valid = false;
pthread_mutex_lock(&g_game_cache_mutex);
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
? g_game_cache[k].owning_scan_root
: g_game_cache[k].path;
if (!root || root[0] == '\0' || strcmp(entry_root, root) == 0) {
(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));
(void)strlcpy(entry.owning_scan_root,
has_owning_scan_root ? g_game_cache[k].owning_scan_root
: "",
sizeof(entry.owning_scan_root));
valid = true;
}
}
pthread_mutex_unlock(&g_game_cache_mutex);
if (!valid)
if (!g_game_cache[k].valid)
continue;
if (!fn(entry.path, entry.title_id, entry.title_name,
entry.owning_scan_root[0] != '\0' ? entry.owning_scan_root : NULL,
ctx)) {
bool has_owning_scan_root = ensure_game_cache_owning_scan_root(&g_game_cache[k]);
const char *entry_root =
has_owning_scan_root ? g_game_cache[k].owning_scan_root : g_game_cache[k].path;
if (root && root[0] != '\0' && strcmp(entry_root, root) != 0) {
continue;
}
if (!fn(g_game_cache[k].path, g_game_cache[k].title_id,
g_game_cache[k].title_name,
has_owning_scan_root ? g_game_cache[k].owning_scan_root : NULL, ctx)) {
break;
}
}
}
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 && 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++];
(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));
}
pthread_mutex_unlock(&g_game_cache_mutex);
*entries_out = entries;
*count_out = copied;
return true;
}
bool find_cached_game(const char *path, const char *title_id,
const char **existing_path_out) {
if (existing_path_out)
*existing_path_out = NULL;
pthread_mutex_lock(&g_game_cache_mutex);
for (int k = 0; k < MAX_PENDING; k++) {
if (!g_game_cache[k].valid)
continue;
if (path && strcmp(g_game_cache[k].path, path) == 0) {
if (existing_path_out)
*existing_path_out = g_game_cache[k].path;
pthread_mutex_unlock(&g_game_cache_mutex);
return true;
}
if (title_id && title_id[0] != '\0' &&
strcmp(g_game_cache[k].title_id, title_id) == 0) {
if (existing_path_out)
*existing_path_out = g_game_cache[k].path;
pthread_mutex_unlock(&g_game_cache_mutex);
return true;
}
}
pthread_mutex_unlock(&g_game_cache_mutex);
return false;
}
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 || g_game_cache[k].missing_since_us != 0)
if (!g_game_cache[k].valid)
continue;
if (strcmp(g_game_cache[k].path, path) != 0)
continue;
clear_game_cache_slot(k, "removed from duplicate tracking");
}
pthread_mutex_unlock(&g_game_cache_mutex);
}
+59 -193
View File
@@ -1,5 +1,6 @@
#include "sm_platform.h"
#include <fcntl.h>
#include <pthread.h>
#include <stdatomic.h>
#include <sys/event.h>
@@ -7,19 +8,13 @@
#include "sm_appdb.h"
#include "sm_fakelib.h"
#include "sm_filesystem.h"
#include "sm_game_lifecycle.h"
#include "sm_gameinfo.h"
#include "sm_kstuff.h"
#include "sm_limits.h"
#include "sm_log.h"
#include "sm_mdbg.h"
#include "sm_path_utils.h"
#include "sm_runtime.h"
#include "sm_scan.h"
#include "sm_scanner.h"
#include "sm_shellcore_hooks.h"
#include "sm_shellcore_service.h"
#include "sm_time.h"
#define MAX_PENDING_GAME_EXEC_CANDIDATES 32
@@ -37,19 +32,17 @@ static pending_game_launch_t
g_pending_game_launches[MAX_PENDING_GAME_EXEC_CANDIDATES];
static pthread_t g_game_lifecycle_thread;
static bool g_game_lifecycle_thread_started = false;
static atomic_bool g_game_lifecycle_stop_requested = false;
static volatile sig_atomic_t g_game_lifecycle_stop_requested = 0;
static int g_game_lifecycle_wake_pipe[2] = {-1, -1};
static pthread_mutex_t g_game_lifecycle_start_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t g_game_lifecycle_start_cond = PTHREAD_COND_INITIALIZER;
static bool g_game_lifecycle_start_ready = false;
static bool g_game_lifecycle_start_success = false;
static _Atomic pid_t g_active_game_pid = 0;
// Title storage is owned exclusively by the lifecycle thread. Other threads
// consume only the atomically published pid through the public state helper.
static pthread_mutex_t g_active_game_mutex = PTHREAD_MUTEX_INITIALIZER;
static char g_active_game_title_id[MAX_TITLE_ID];
static bool register_game_exit_watch(int kq, pid_t pid);
static void handle_game_exit(pid_t pid);
static void publish_active_game_pid(pid_t pid) {
pid_t previous_pid = atomic_exchange(&g_active_game_pid, pid);
@@ -59,40 +52,35 @@ static void publish_active_game_pid(pid_t pid) {
}
static void publish_active_game(pid_t pid, const char *title_id) {
pthread_mutex_lock(&g_active_game_mutex);
if (pid > 0 && title_id && title_id[0] != '\0')
(void)strlcpy(g_active_game_title_id, title_id,
sizeof(g_active_game_title_id));
else
g_active_game_title_id[0] = '\0';
pthread_mutex_unlock(&g_active_game_mutex);
publish_active_game_pid(pid);
}
static bool consume_active_game_title(pid_t pid,
char title_id_out[MAX_TITLE_ID]) {
if (pid <= 0 || atomic_load(&g_active_game_pid) != pid ||
g_active_game_title_id[0] == '\0')
return false;
(void)strlcpy(title_id_out, g_active_game_title_id, MAX_TITLE_ID);
g_active_game_title_id[0] = '\0';
return true;
}
static bool snapshot_active_game(pid_t pid, char title_id[MAX_TITLE_ID]) {
title_id[0] = '\0';
if (pid <= 0 || g_active_game_title_id[0] == '\0')
return false;
(void)strlcpy(title_id, g_active_game_title_id, MAX_TITLE_ID);
return true;
}
static bool title_is_usb_backed(const char *title_id, const char *source_path,
bool has_mount_link) {
char image_path[MAX_PATH];
if (read_mount_image_link(title_id, image_path, sizeof(image_path)) &&
is_usb_storage_path(image_path)) {
return true;
bool matched = false;
pthread_mutex_lock(&g_active_game_mutex);
if (pid > 0 && atomic_load(&g_active_game_pid) == pid &&
g_active_game_title_id[0] != '\0') {
(void)strlcpy(title_id_out, g_active_game_title_id, MAX_TITLE_ID);
matched = true;
}
return has_mount_link && is_usb_storage_path(source_path);
if (matched)
g_active_game_title_id[0] = '\0';
pthread_mutex_unlock(&g_active_game_mutex);
return matched;
}
static bool is_supported_title_id(const char *title_id) {
return strncmp(title_id, "PPSA", 4) == 0 ||
strncmp(title_id, "CUSA", 4) == 0 ||
strncmp(title_id, "FAKE", 4) == 0;
}
static uint64_t min_nonzero_u64(uint64_t a, uint64_t b) {
@@ -180,19 +168,9 @@ static bool is_process_alive(pid_t pid) {
static bool dispatch_game_launch(int kq, pid_t pid, uint64_t exec_time_us,
const char *title_id, uint32_t app_id) {
if (!sm_shellcore_ensure_title_runtime(title_id)) {
return false;
}
sm_shellcore_service_bind_prepared_app(title_id, app_id);
if (!register_game_exit_watch(kq, pid)) {
log_debug(" [GAME] skipping launch tracking for %s pid=%ld without exit watch",
title_id, (long)pid);
if (sm_shellcore_service_title_is_prepared(title_id) &&
!sm_shellcore_release_title_runtime(title_id)) {
log_debug(" [SHELLCORE] failed to roll back untracked runtime: %s",
title_id);
}
return false;
}
@@ -229,7 +207,7 @@ static void drain_game_lifecycle_wake_pipe(void) {
}
}
static bool drain_game_lifecycle_events_nowait(int kq) {
static bool discard_game_lifecycle_events_nowait(int kq) {
struct kevent events[16];
struct timespec timeout;
memset(&timeout, 0, sizeof(timeout));
@@ -245,15 +223,17 @@ static bool drain_game_lifecycle_events_nowait(int kq) {
}
if (nev == 0)
return true;
for (int i = 0; i < nev; ++i) {
if (events[i].filter == EVFILT_PROC &&
(events[i].fflags & NOTE_EXIT) != 0) {
handle_game_exit((pid_t)events[i].ident);
}
}
}
}
static bool set_fd_nonblocking(int fd) {
int flags = fcntl(fd, F_GETFL, 0);
if (flags < 0)
return false;
return fcntl(fd, F_SETFL, flags | O_NONBLOCK) == 0;
}
static void set_game_lifecycle_start_result(bool success) {
pthread_mutex_lock(&g_game_lifecycle_start_mutex);
g_game_lifecycle_start_success = success;
@@ -318,12 +298,6 @@ static pending_game_launch_t *queue_pending_game_launch(pid_t pid,
static void defer_confirmed_game_launch_retry(pid_t pid, uint64_t exec_time_us,
uint64_t now_us,
const char *title_id) {
if (now_us == 0) {
log_debug(" [GAME] cannot schedule launch retry without monotonic clock: "
"%s pid=%ld",
title_id, (long)pid);
return;
}
clear_all_pending_game_launches();
queue_pending_game_launch(pid, exec_time_us, now_us);
@@ -386,28 +360,13 @@ static void poll_game_modules(int kq) {
if (resolve_game_title_id(entry->pid, title_id, &app_id)) {
pid_t pid = entry->pid;
uint64_t exec_time_us = entry->exec_time_us;
if (is_supported_game_title_id(title_id)) {
if (entry->deadline_us == 0 || now_us == 0 ||
now_us >= entry->deadline_us) {
log_debug(" [GAME] runtime preparation timed out for %s pid=%ld",
title_id, (long)pid);
clear_pending_game_launch(entry);
break;
}
if (is_supported_title_id(title_id)) {
if (dispatch_game_launch(kq, pid, exec_time_us, title_id, app_id)) {
clear_pending_game_launch(entry);
clear_all_pending_game_launches();
break;
}
uint64_t retry_now_us = monotonic_time_us();
if (retry_now_us == 0 || retry_now_us >= entry->deadline_us) {
log_debug(" [GAME] runtime preparation timed out for %s pid=%ld",
title_id, (long)pid);
clear_pending_game_launch(entry);
} else {
defer_confirmed_game_launch_retry(pid, exec_time_us, retry_now_us,
title_id);
}
defer_confirmed_game_launch_retry(pid, exec_time_us, now_us, title_id);
break;
} else {
clear_pending_game_launch(entry);
@@ -448,7 +407,7 @@ static void handle_game_exec(int kq, pid_t pid) {
char title_id[MAX_TITLE_ID];
uint32_t app_id = 0;
if (resolve_game_title_id(pid, title_id, &app_id)) {
if (is_supported_game_title_id(title_id)) {
if (is_supported_title_id(title_id)) {
if (dispatch_game_launch(kq, pid, now_us, title_id, app_id))
clear_all_pending_game_launches();
else
@@ -460,8 +419,7 @@ static void handle_game_exec(int kq, pid_t pid) {
if (!is_process_alive(pid))
return;
if (now_us != 0)
queue_pending_game_launch(pid, now_us, now_us);
queue_pending_game_launch(pid, now_us, now_us);
}
static void handle_game_exit(pid_t pid) {
@@ -470,20 +428,11 @@ static void handle_game_exit(pid_t pid) {
pending_game_launch_t *entry = find_pending_game_launch(pid);
if (entry)
clear_pending_game_launch(entry);
if (had_active_title)
if (atomic_load(&g_active_game_pid) == pid)
publish_active_game_pid(0);
sm_fakelib_game_on_exit(pid);
sm_kstuff_game_on_exit(pid);
if (had_active_title) {
if (sm_shellcore_service_note_game_exit(title_id)) {
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 app-exit hook; runtime release deferred: %s",
title_id);
}
}
int snd0_updates = normalize_snd0info_for_title(title_id);
if (snd0_updates >= 0)
log_debug(" [DB] snd0info normalized after game exit rows=%d title=%s",
@@ -491,69 +440,21 @@ static void handle_game_exit(pid_t pid) {
}
}
static bool terminate_usb_game_for_sleep(int kq, pid_t pid,
const char *title_id) {
if (kill(pid, SIGKILL) != 0) {
if (errno == ESRCH) {
handle_game_exit(pid);
return true;
}
log_debug(" [SLEEP] failed to kill USB game: %s pid=%ld: %s", title_id,
(long)pid, strerror(errno));
return false;
}
log_debug(" [SLEEP] killing USB game before suspend: %s pid=%ld", title_id,
(long)pid);
static void restore_suspended_game_if_alive(int kq, pid_t pid) {
if (pid <= 0 || !is_process_alive(pid))
return;
char title_id[MAX_TITLE_ID];
uint32_t app_id = 0;
if (!resolve_game_title_id(pid, title_id, &app_id))
return;
if (!is_supported_title_id(title_id))
return;
uint64_t now_us = monotonic_time_us();
uint64_t deadline_us =
now_us == 0 ? 0 : now_us + GAME_SLEEP_EXIT_TIMEOUT_US;
while (!atomic_load_explicit(&g_game_lifecycle_stop_requested,
memory_order_relaxed) &&
!should_stop_requested()) {
struct kevent event;
struct timespec timeout = {
.tv_sec = 0,
.tv_nsec = (long)GAME_LIFECYCLE_POLL_INTERVAL_US * 1000L,
};
int nev = kevent(kq, NULL, 0, &event, 1, &timeout);
if (nev < 0) {
if (errno == EINTR)
continue;
log_debug(" [SLEEP] NOTE_EXIT wait failed for %s pid=%ld: %s",
title_id, (long)pid, strerror(errno));
return false;
}
if (nev > 0) {
if (event.filter == EVFILT_READ &&
event.ident == (uintptr_t)g_game_lifecycle_wake_pipe[0]) {
drain_game_lifecycle_wake_pipe();
} else if (event.filter == EVFILT_PROC &&
(event.fflags & NOTE_EXIT) != 0) {
pid_t exited_pid = (pid_t)event.ident;
handle_game_exit(exited_pid);
if (exited_pid == pid) {
log_debug(" [SLEEP] USB game stopped: %s pid=%ld", title_id,
(long)pid);
return true;
}
}
}
now_us = monotonic_time_us();
if (deadline_us != 0 && now_us != 0 && now_us >= deadline_us) {
if (!is_process_alive(pid)) {
handle_game_exit(pid);
log_debug(" [SLEEP] USB game exited without NOTE_EXIT: %s pid=%ld",
title_id, (long)pid);
return true;
}
log_debug(" [SLEEP] timed out waiting NOTE_EXIT: %s pid=%ld", title_id,
(long)pid);
return false;
}
}
return false;
log_debug(" [GAME] resumed: %s pid=%ld app_id=0x%08X", title_id,
(long)pid, app_id);
(void)dispatch_game_launch(kq, pid, now_us, title_id, app_id);
}
static void *game_lifecycle_watcher_main(void *arg) {
@@ -598,42 +499,14 @@ static void *game_lifecycle_watcher_main(void *arg) {
bool sleep_cleanup_done = false;
pid_t suspended_game_pid = 0;
while (!atomic_load_explicit(&g_game_lifecycle_stop_requested,
memory_order_relaxed) &&
!should_stop_requested()) {
while (!g_game_lifecycle_stop_requested && !should_stop_requested()) {
if (runtime_sleep_mode_active()) {
if (!sleep_cleanup_done && sm_scanner_usb_watches_suspended()) {
if (!sleep_cleanup_done) {
suspended_game_pid = atomic_load(&g_active_game_pid);
char usb_game_title_id[MAX_TITLE_ID];
bool active_game = snapshot_active_game(suspended_game_pid,
usb_game_title_id);
char source_path[MAX_PATH];
bool has_mount_link =
active_game && read_mount_link(usb_game_title_id, source_path,
sizeof(source_path));
bool usb_game =
active_game && title_is_usb_backed(
usb_game_title_id, source_path, has_mount_link);
clear_all_pending_game_launches();
sm_fakelib_game_shutdown();
sm_kstuff_sleep_enter();
bool usb_cleanup_allowed = true;
if (usb_game) {
usb_cleanup_allowed = terminate_usb_game_for_sleep(
kq, suspended_game_pid, usb_game_title_id);
if (usb_cleanup_allowed) {
suspended_game_pid = 0;
}
}
if (usb_cleanup_allowed) {
runtime_mount_state_lock();
unmount_usb_sources_for_suspend();
runtime_mount_state_unlock();
} else {
log_debug("[SLEEP] USB cleanup skipped while game process remains");
}
// Keep a live non-USB game published while it is suspended so scanner
// work cannot race its unchanged runtime mount.
publish_active_game(0, NULL);
sleep_cleanup_done = true;
}
@@ -652,13 +525,10 @@ static void *game_lifecycle_watcher_main(void *arg) {
continue;
}
if (sleep_cleanup_done) {
if (!drain_game_lifecycle_events_nowait(kq))
if (!discard_game_lifecycle_events_nowait(kq))
break;
sleep_cleanup_done = false;
if (runtime_sleep_mode_active())
continue;
if (suspended_game_pid > 0 && is_process_alive(suspended_game_pid))
handle_game_exec(kq, suspended_game_pid);
restore_suspended_game_if_alive(kq, suspended_game_pid);
sm_kstuff_sleep_leave();
suspended_game_pid = 0;
}
@@ -670,8 +540,7 @@ static void *game_lifecycle_watcher_main(void *arg) {
if (nev < 0) {
if (errno == EINTR)
continue;
if (atomic_load_explicit(&g_game_lifecycle_stop_requested,
memory_order_relaxed))
if (g_game_lifecycle_stop_requested)
break;
log_debug(" [GAME] kevent wait failed: %s", strerror(errno));
break;
@@ -711,16 +580,15 @@ bool start_game_lifecycle_watcher(void) {
log_debug(" [GAME] wake pipe creation failed: %s", strerror(errno));
return false;
}
if (!sm_set_fd_nonblocking(g_game_lifecycle_wake_pipe[0]) ||
!sm_set_fd_nonblocking(g_game_lifecycle_wake_pipe[1])) {
if (!set_fd_nonblocking(g_game_lifecycle_wake_pipe[0]) ||
!set_fd_nonblocking(g_game_lifecycle_wake_pipe[1])) {
log_debug(" [GAME] wake pipe nonblocking setup failed: %s",
strerror(errno));
close_game_lifecycle_wake_pipe();
return false;
}
atomic_store_explicit(&g_game_lifecycle_stop_requested, false,
memory_order_relaxed);
g_game_lifecycle_stop_requested = 0;
pthread_mutex_lock(&g_game_lifecycle_start_mutex);
g_game_lifecycle_start_ready = false;
g_game_lifecycle_start_success = false;
@@ -766,14 +634,12 @@ void stop_game_lifecycle_watcher(void) {
if (!g_game_lifecycle_thread_started)
return;
atomic_store_explicit(&g_game_lifecycle_stop_requested, true,
memory_order_relaxed);
g_game_lifecycle_stop_requested = 1;
wake_game_lifecycle_watcher();
(void)pthread_join(g_game_lifecycle_thread, NULL);
g_game_lifecycle_thread_started = false;
atomic_store_explicit(&g_game_lifecycle_stop_requested, false,
memory_order_relaxed);
g_game_lifecycle_stop_requested = 0;
publish_active_game(0, NULL);
close_game_lifecycle_wake_pipe();
}
+2 -28
View File
@@ -6,13 +6,8 @@
// --- Game Metadata Parsing (param.json) ---
static int extract_json_string(const char *json, const char *key, char *out,
size_t out_size) {
if (!json || !key || !out || out_size == 0)
return -1;
char search[64];
int written = snprintf(search, sizeof(search), "\"%s\"", key);
if (written < 0 || (size_t)written >= sizeof(search))
return -1;
snprintf(search, sizeof(search), "\"%s\"", key);
const char *p = strstr(json, search);
if (!p)
return -1;
@@ -32,27 +27,9 @@ static int extract_json_string(const char *json, const char *key, char *out,
i++;
}
out[i] = '\0';
if (p[i] != '"') {
out[0] = '\0';
return -4;
}
return 0;
}
bool is_supported_game_title_id(const char *title_id) {
if (!title_id || strlen(title_id) != 9u)
return false;
if (strncmp(title_id, "PPSA", 4u) != 0 &&
strncmp(title_id, "CUSA", 4u) != 0) {
return false;
}
for (size_t i = 4u; i < 9u; ++i) {
if (!isdigit((unsigned char)title_id[i]))
return false;
}
return true;
}
bool get_game_info(const char *base_path, const struct stat *param_st,
char *out_id, char *out_name) {
out_id[0] = '\0';
@@ -100,7 +77,7 @@ bool get_game_info(const char *base_path, const struct stat *param_st,
int res = extract_json_string(buf, "titleId", out_id, MAX_TITLE_ID);
if (res != 0)
res = extract_json_string(buf, "title_id", out_id, MAX_TITLE_ID);
if (res == 0 && is_supported_game_title_id(out_id)) {
if (res == 0) {
const char *en_ptr = strstr(buf, "\"en-US\"");
const char *search_start = en_ptr ? en_ptr : buf;
if (extract_json_string(search_start, "titleName", out_name,
@@ -109,9 +86,6 @@ bool get_game_info(const char *base_path, const struct stat *param_st,
if (out_name[0] == '\0')
(void)strlcpy(out_name, out_id, MAX_TITLE_NAME);
valid = true;
} else {
out_id[0] = '\0';
out_name[0] = '\0';
}
free(buf);
+52 -163
View File
@@ -515,20 +515,15 @@ static const image_backend_ops_t *get_image_backend_ops(attach_backend_t backend
return NULL;
}
static bool get_cached_image_mount(const char *file_path,
image_cache_entry_t *entry_out,
int *index_out) {
image_cache_entry_t entry;
int index = -1;
if (!find_image_cache_entry(file_path, &entry, &index) ||
entry.backend == ATTACH_BACKEND_NONE || entry.unit_id < 0) {
return false;
static bool is_cached_image_path(const char *file_path) {
for (int k = 0; k < MAX_IMAGE_MOUNTS; k++) {
image_cache_entry_t cached_entry;
if (get_image_cache_entry(k, &cached_entry) &&
strcmp(cached_entry.path, file_path) == 0) {
return true;
}
}
if (entry_out)
*entry_out = entry;
if (index_out)
*index_out = index;
return true;
return false;
}
static bool directory_has_visible_entries(const char *path) {
@@ -568,12 +563,11 @@ 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_l10n("IMG", io_err, file_path,
SM_L10N_IMAGE_UNREADABLE_DAMAGED);
sm_error_set("IMG", io_err, file_path, "Mounted image is unreadable or 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));
(void)unmount_runtime_image(file_path, unit_id, attach_backend);
(void)unmount_image(file_path, unit_id, attach_backend);
errno = io_err;
return false;
}
@@ -592,8 +586,7 @@ static bool prepare_image_mount_retry(const image_cache_entry_t *cached_entry,
log_debug(" [IMG][%s] mount unreadable, retrying: %s -> %s: %s",
attach_backend_name(cached_entry->backend), source_path,
mount_point, strerror(root_err));
if (!unmount_runtime_image(source_path, cached_entry->unit_id,
cached_entry->backend))
if (!unmount_image(source_path, cached_entry->unit_id, cached_entry->backend))
return false;
} else {
log_debug(" [IMG][%s] mount lost, retrying: %s -> %s",
@@ -624,8 +617,7 @@ static bool reuse_existing_image_mount(const char *file_path,
log_debug(" [IMG][%s] Existing mount unreadable, reattaching: %s -> %s: %s",
attach_backend_name(existing_backend), file_path, mount_point,
strerror(root_err));
if (!unmount_runtime_image(file_path, existing_unit,
existing_backend)) {
if (!unmount_image(file_path, existing_unit, existing_backend)) {
if (cache_failed_out)
*cache_failed_out = true;
return false;
@@ -634,9 +626,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_l10n("IMG", ENOSPC, file_path,
SM_L10N_IMAGE_CACHE_FULL_TRACK,
(unsigned)MAX_IMAGE_MOUNTS, mount_point);
sm_error_set("IMG", ENOSPC, file_path,
"Image cache full (%u entries), cannot track mount %s",
(unsigned)MAX_IMAGE_MOUNTS, mount_point);
log_debug(" [IMG] image cache full, refusing unmanaged mount reuse: %s",
mount_point);
errno = ENOSPC;
@@ -665,26 +657,10 @@ static bool handle_cached_or_existing_image_mount_locked(
if (runtime_sleep_mode_active())
return true;
image_cache_entry_t cached_entry;
int cached_index = -1;
if (get_cached_image_mount(file_path, &cached_entry, &cached_index)) {
int root_err = 0;
if (is_active_image_mount_point(mount_point) &&
is_image_mount_root_accessible(mount_point, &root_err)) {
if (success_out)
*success_out = true;
return true;
}
log_debug(" [IMG][%s] cached mount is not active, reattaching: %s -> "
"%s",
attach_backend_name(cached_entry.backend), file_path,
mount_point);
if (!unmount_runtime_image(cached_entry.path, cached_entry.unit_id,
cached_entry.backend)) {
return true;
}
invalidate_image_cache_entry(cached_index);
if (is_cached_image_path(file_path)) {
if (success_out)
*success_out = true;
return true;
}
bool cache_failed = false;
@@ -916,23 +892,29 @@ 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_l10n("IMG", EINVAL, file_path, SM_L10N_FS_CLUSTER_SMALL,
(unsigned long long)fs_block_size, min_device_sector,
devname);
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);
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_l10n(SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED, file_path,
(unsigned long long)fs_block_size, min_device_sector,
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);
} else {
notify_system_l10n(SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG, file_path,
(unsigned long long)fs_block_size, min_device_sector);
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);
}
sm_error_mark_notified();
(void)unmount_runtime_image(file_path, unit_id, attach_backend);
(void)unmount_image(file_path, unit_id, attach_backend);
errno = EINVAL;
return false;
}
@@ -1026,7 +1008,7 @@ bool mount_image(const char *file_path, image_fs_type_t fs_type) {
return false;
}
if (runtime_sleep_mode_active()) {
(void)unmount_runtime_image(file_path, unit_id, attach_backend);
(void)unmount_image(file_path, unit_id, attach_backend);
runtime_mount_state_unlock();
return false;
}
@@ -1043,12 +1025,12 @@ 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_l10n("IMG", ENOSPC, file_path,
SM_L10N_IMAGE_CACHE_FULL_ROLLBACK,
(unsigned)MAX_IMAGE_MOUNTS);
sm_error_set("IMG", ENOSPC, file_path,
"Image cache full (%u entries), rolling back mount",
(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);
(void)unmount_image(file_path, unit_id, attach_backend);
errno = ENOSPC;
runtime_mount_state_unlock();
return false;
@@ -1057,31 +1039,20 @@ bool mount_image(const char *file_path, image_fs_type_t fs_type) {
return true;
}
static bool unmount_child_image_mounts_for_container(
const char *mount_point, bool cleanup_source_state) {
static bool unmount_child_image_mounts_for_container(const char *mount_point) {
bool all_unmounted = true;
for (int k = 0; k < MAX_IMAGE_MOUNTS; k++) {
image_cache_entry_t cached_entry;
if (!get_image_cache_entry(k, &cached_entry))
continue;
if (cached_entry.backend == ATTACH_BACKEND_NONE ||
cached_entry.unit_id < 0) {
continue;
}
if (!path_matches_root_or_child(cached_entry.path, mount_point))
continue;
log_debug(" [IMG] container child unmount: %s source=%s",
mount_point, cached_entry.path);
bool unmounted = cleanup_source_state
? unmount_image(cached_entry.path,
cached_entry.unit_id,
cached_entry.backend)
: unmount_runtime_image(cached_entry.path,
cached_entry.unit_id,
cached_entry.backend);
if (unmounted) {
if (unmount_image(cached_entry.path, cached_entry.unit_id,
cached_entry.backend)) {
invalidate_image_cache_entry(k);
continue;
}
@@ -1094,9 +1065,7 @@ static bool unmount_child_image_mounts_for_container(
return all_unmounted;
}
static bool unmount_image_impl(const char *file_path, int unit_id,
attach_backend_t backend,
bool cleanup_source_state) {
bool unmount_image(const char *file_path, int unit_id, attach_backend_t backend) {
char mount_point[MAX_PATH];
image_fs_type_t fs_type = detect_image_fs_type_for_path(file_path, NULL);
build_image_mount_point_for_fs(file_path, fs_type, mount_point);
@@ -1116,16 +1085,14 @@ static bool unmount_image_impl(const char *file_path, int unit_id,
resolved_unit);
if (fs_type == IMAGE_FS_PFSC_CONTAINER &&
!unmount_child_image_mounts_for_container(mount_point,
cleanup_source_state)) {
!unmount_child_image_mounts_for_container(mount_point)) {
return false;
}
if (cleanup_source_state) {
// Permanent source cleanup removes title links before releasing the image.
cleanup_mount_links_for_source_unmount(mount_point);
clear_cached_game(mount_point);
}
// Remove mount.lnk and unmount /system_ex/app/<titleid> that point to this
// source before unmounting the virtual disk itself.
cleanup_mount_links_for_source_unmount(mount_point);
clear_cached_game(mount_point);
// Unmount stacked layers (unionfs over image fs).
for (int i = 0; i < MAX_LAYERED_UNMOUNT_ATTEMPTS; i++) {
@@ -1185,72 +1152,10 @@ static bool unmount_image_impl(const char *file_path, int unit_id,
return detach_ok;
}
bool unmount_image(const char *file_path, int unit_id,
attach_backend_t backend) {
return unmount_image_impl(file_path, unit_id, backend, true);
}
bool unmount_runtime_image(const char *file_path, int unit_id,
attach_backend_t backend) {
return unmount_image_impl(file_path, unit_id, backend, false);
}
bool release_runtime_image_mount(const char *file_path) {
image_cache_entry_t entry;
int index = -1;
if (!find_image_cache_entry(file_path, &entry, &index) ||
entry.backend == ATTACH_BACKEND_NONE || entry.unit_id < 0) {
return true;
}
if (!unmount_runtime_image(entry.path, entry.unit_id, entry.backend))
return false;
invalidate_image_cache_entry(index);
return true;
}
bool release_runtime_image_mounts(void) {
bool all_released = true;
for (int pass = 0; pass < MAX_LAYERED_UNMOUNT_ATTEMPTS; ++pass) {
bool any_remaining = false;
bool progress = false;
for (int k = 0; k < MAX_IMAGE_MOUNTS; ++k) {
image_cache_entry_t entry;
if (!get_image_cache_entry(k, &entry))
continue;
if (entry.backend == ATTACH_BACKEND_NONE || entry.unit_id < 0)
continue;
if (unmount_runtime_image(entry.path, entry.unit_id, entry.backend)) {
invalidate_image_cache_entry(k);
progress = true;
} else {
any_remaining = true;
all_released = false;
}
}
if (!any_remaining)
return true;
if (!progress)
break;
}
return all_released;
}
void cleanup_stale_image_mounts(void) {
if (should_stop_requested())
return;
bool has_cached_mount = false;
for (int k = 0; k < MAX_IMAGE_MOUNTS; ++k) {
image_cache_entry_t entry;
if (get_image_cache_entry(k, &entry) &&
entry.backend != ATTACH_BACKEND_NONE && entry.unit_id >= 0) {
has_cached_mount = true;
break;
}
}
if (!has_cached_mount)
return;
log_debug(" [IMG] stale image cleanup begin");
for (int k = 0; k < MAX_IMAGE_MOUNTS; k++) {
image_cache_entry_t cached_entry;
@@ -1258,10 +1163,6 @@ void cleanup_stale_image_mounts(void) {
return;
if (!get_image_cache_entry(k, &cached_entry))
continue;
if (cached_entry.backend == ATTACH_BACKEND_NONE ||
cached_entry.unit_id < 0) {
continue;
}
log_debug(" [IMG][%s] stale cleanup check: slot=%d source=%s mount=%s",
attach_backend_name(cached_entry.backend), k, cached_entry.path,
@@ -1312,10 +1213,6 @@ void cleanup_stale_image_mounts_for_root(const char *root) {
return;
if (!get_image_cache_entry(k, &cached_entry))
continue;
if (cached_entry.backend == ATTACH_BACKEND_NONE ||
cached_entry.unit_id < 0) {
continue;
}
if (!path_matches_root_or_child(cached_entry.path, root) &&
!path_matches_root_or_child(cached_entry.mount_point, root)) {
continue;
@@ -1357,17 +1254,13 @@ bool unmount_usb_image_mounts_for_suspend(void) {
image_cache_entry_t cached_entry;
if (!get_image_cache_entry(k, &cached_entry))
continue;
if (cached_entry.backend == ATTACH_BACKEND_NONE ||
cached_entry.unit_id < 0) {
continue;
}
if (!is_usb_storage_path(cached_entry.path))
continue;
log_debug(" [IMG][%s] USB suspend unmount: %s",
attach_backend_name(cached_entry.backend), cached_entry.path);
if (unmount_runtime_image(cached_entry.path, cached_entry.unit_id,
cached_entry.backend)) {
if (unmount_image(cached_entry.path, cached_entry.unit_id,
cached_entry.backend)) {
log_debug(" [IMG][%s] USB suspend unmounted: %s",
attach_backend_name(cached_entry.backend), cached_entry.path);
invalidate_image_cache_entry(k);
@@ -1466,13 +1359,9 @@ bool shutdown_image_mounts(void) {
image_cache_entry_t cached_entry;
if (!get_image_cache_entry(k, &cached_entry))
continue;
if (cached_entry.backend == ATTACH_BACKEND_NONE ||
cached_entry.unit_id < 0) {
continue;
}
if (unmount_runtime_image(cached_entry.path, cached_entry.unit_id,
cached_entry.backend)) {
if (unmount_image(cached_entry.path, cached_entry.unit_id,
cached_entry.backend)) {
invalidate_image_cache_entry(k);
progress = true;
continue;
+7 -121
View File
@@ -15,16 +15,6 @@ struct ImageCache {
static struct ImageCache g_image_cache[MAX_IMAGE_MOUNTS];
static pthread_mutex_t g_image_cache_mutex = PTHREAD_MUTEX_INITIALIZER;
static void copy_cache_entry(int index, image_cache_entry_t *entry_out) {
memset(entry_out, 0, sizeof(*entry_out));
(void)strlcpy(entry_out->path, g_image_cache[index].path,
sizeof(entry_out->path));
(void)strlcpy(entry_out->mount_point, g_image_cache[index].mount_point,
sizeof(entry_out->mount_point));
entry_out->unit_id = g_image_cache[index].unit_id;
entry_out->backend = g_image_cache[index].backend;
}
static int find_cache_index(const char *path, const char *mount_point) {
for (int k = 0; k < MAX_IMAGE_MOUNTS; k++) {
if (!g_image_cache[k].valid)
@@ -65,18 +55,6 @@ static int upsert_image_source_mapping(const char *path, const char *mount_point
bool cache_image_source_mapping(const char *path, const char *mount_point) {
pthread_mutex_lock(&g_image_cache_mutex);
int entry_index = find_cache_index(path, mount_point);
if (entry_index >= 0) {
const struct ImageCache *entry = &g_image_cache[entry_index];
// A metadata-only path mapping must never retarget a live mount entry.
// The live entry owns its actual filesystem mount point until release.
if (entry->backend != ATTACH_BACKEND_NONE && entry->unit_id >= 0 &&
(strcmp(entry->path, path) != 0 ||
strcmp(entry->mount_point, mount_point) != 0)) {
pthread_mutex_unlock(&g_image_cache_mutex);
return true;
}
}
bool ok = upsert_image_source_mapping(path, mount_point) >= 0;
pthread_mutex_unlock(&g_image_cache_mutex);
return ok;
@@ -105,25 +83,13 @@ bool get_image_cache_entry(int index, image_cache_entry_t *entry_out) {
return false;
}
copy_cache_entry(index, entry_out);
pthread_mutex_unlock(&g_image_cache_mutex);
return true;
}
bool find_image_cache_entry(const char *path, image_cache_entry_t *entry_out,
int *index_out) {
if (!path || !entry_out)
return false;
pthread_mutex_lock(&g_image_cache_mutex);
int index = find_cache_index(path, NULL);
if (index < 0) {
pthread_mutex_unlock(&g_image_cache_mutex);
return false;
}
copy_cache_entry(index, entry_out);
if (index_out)
*index_out = index;
memset(entry_out, 0, sizeof(*entry_out));
(void)strlcpy(entry_out->path, g_image_cache[index].path,
sizeof(entry_out->path));
(void)strlcpy(entry_out->mount_point, g_image_cache[index].mount_point,
sizeof(entry_out->mount_point));
entry_out->unit_id = g_image_cache[index].unit_id;
entry_out->backend = g_image_cache[index].backend;
pthread_mutex_unlock(&g_image_cache_mutex);
return true;
}
@@ -174,83 +140,3 @@ bool resolve_image_source_from_mount_cache(const char *mount_point,
pthread_mutex_unlock(&g_image_cache_mutex);
return true;
}
static int find_owning_cache_index(const char *path) {
int best_index = -1;
size_t best_length = 0;
for (int i = 0; i < MAX_IMAGE_MOUNTS; ++i) {
if (!g_image_cache[i].valid)
continue;
size_t length = strlen(g_image_cache[i].mount_point);
if (length <= best_length || strncmp(path, g_image_cache[i].mount_point,
length) != 0) {
continue;
}
if (path[length] != '\0' && path[length] != '/')
continue;
best_index = i;
best_length = length;
}
return best_index;
}
static size_t find_owning_chain_indices(const char *path,
int indices[MAX_IMAGE_CHAIN_DEPTH]) {
char current[MAX_PATH];
(void)strlcpy(current, path, sizeof(current));
size_t count = 0;
while (count < MAX_IMAGE_CHAIN_DEPTH) {
int index = find_owning_cache_index(current);
if (index < 0)
break;
indices[count++] = index;
(void)strlcpy(current, g_image_cache[index].path, sizeof(current));
}
return count;
}
bool resolve_owning_image_source_from_mount_cache(const char *path,
char *path_out,
size_t path_out_size) {
if (!path || !path_out || path_out_size == 0)
return false;
pthread_mutex_lock(&g_image_cache_mutex);
int index = find_owning_cache_index(path);
if (index >= 0)
(void)strlcpy(path_out, g_image_cache[index].path, path_out_size);
pthread_mutex_unlock(&g_image_cache_mutex);
return index >= 0;
}
size_t resolve_image_source_chain_from_mount_cache(
const char *path,
char chain[MAX_IMAGE_CHAIN_DEPTH][MAX_PATH]) {
if (!path || !chain)
return 0;
int indices[MAX_IMAGE_CHAIN_DEPTH];
pthread_mutex_lock(&g_image_cache_mutex);
size_t count = find_owning_chain_indices(path, indices);
for (size_t i = 0; i < count; ++i)
(void)strlcpy(chain[i], g_image_cache[indices[count - i - 1u]].path,
MAX_PATH);
pthread_mutex_unlock(&g_image_cache_mutex);
return count;
}
bool resolve_outermost_image_source_from_mount_cache(
const char *path, char *path_out, size_t path_out_size) {
if (!path || !path_out || path_out_size == 0)
return false;
int indices[MAX_IMAGE_CHAIN_DEPTH];
pthread_mutex_lock(&g_image_cache_mutex);
size_t count = find_owning_chain_indices(path, indices);
if (count > 0)
(void)strlcpy(path_out, g_image_cache[indices[count - 1u]].path,
path_out_size);
pthread_mutex_unlock(&g_image_cache_mutex);
return count > 0;
}
-411
View File
@@ -1,411 +0,0 @@
#include "sm_platform.h"
#include <pthread.h>
#include "sm_appdb.h"
#include "sm_filesystem.h"
#include "sm_image_cache.h"
#include "sm_image_index.h"
#include "sm_limits.h"
#include "sm_log.h"
#include "sm_path_utils.h"
#include "sm_paths.h"
#define IMAGE_INDEX_FILE IMAGE_INDEX_FILE_PATH
#define IMAGE_INDEX_TEMP_FILE LOG_DIR "/image_index.bin.tmp"
#define IMAGE_INDEX_MAGIC 0x58444953u
#define IMAGE_INDEX_VERSION 3u
typedef struct {
char path[MAX_PATH];
int64_t size;
int64_t mtime_sec;
int32_t mtime_nsec;
uint8_t complete;
uint8_t valid;
uint8_t reserved[2];
} image_index_entry_t;
typedef struct {
uint16_t image_index;
uint8_t valid;
uint8_t reserved;
char title_id[MAX_TITLE_ID];
} image_index_title_t;
typedef struct {
uint32_t magic;
uint32_t version;
uint32_t entry_capacity;
uint32_t title_capacity;
} image_index_header_t;
static image_index_entry_t g_image_index[MAX_IMAGE_MOUNTS];
static image_index_title_t g_image_titles[MAX_IMAGE_TITLES];
static bool g_image_index_loaded;
static bool g_image_index_dirty;
static pthread_mutex_t g_image_index_mutex = PTHREAD_MUTEX_INITIALIZER;
static bool stamp_matches(const image_index_entry_t *entry,
const struct stat *st) {
return entry->size == (int64_t)st->st_size &&
entry->mtime_sec == (int64_t)st->st_mtim.tv_sec &&
entry->mtime_nsec == (int32_t)st->st_mtim.tv_nsec;
}
static int find_entry(const char *path) {
for (int i = 0; i < MAX_IMAGE_MOUNTS; ++i) {
if (g_image_index[i].valid && strcmp(g_image_index[i].path, path) == 0)
return i;
}
return -1;
}
static void clear_entry_titles(int entry_index) {
for (int i = 0; i < MAX_IMAGE_TITLES; ++i) {
if (g_image_titles[i].valid &&
g_image_titles[i].image_index == (uint16_t)entry_index) {
memset(&g_image_titles[i], 0, sizeof(g_image_titles[i]));
}
}
}
static void clear_entry(int entry_index) {
clear_entry_titles(entry_index);
memset(&g_image_index[entry_index], 0, sizeof(g_image_index[entry_index]));
g_image_index_dirty = true;
}
static bool save_index(void) {
if (!g_image_index_dirty)
return true;
FILE *file = fopen(IMAGE_INDEX_TEMP_FILE, "wb");
if (!file)
return false;
image_index_header_t header = {
.magic = IMAGE_INDEX_MAGIC,
.version = IMAGE_INDEX_VERSION,
.entry_capacity = MAX_IMAGE_MOUNTS,
.title_capacity = MAX_IMAGE_TITLES,
};
bool ok = fwrite(&header, sizeof(header), 1, file) == 1 &&
fwrite(g_image_index, sizeof(g_image_index), 1, file) == 1 &&
fwrite(g_image_titles, sizeof(g_image_titles), 1, file) == 1 &&
fflush(file) == 0;
if (ok && fsync(fileno(file)) != 0)
ok = false;
if (fclose(file) != 0)
ok = false;
if (!ok || rename(IMAGE_INDEX_TEMP_FILE, IMAGE_INDEX_FILE) != 0) {
int saved_errno = errno;
if (saved_errno == 0)
saved_errno = EIO;
(void)unlink(IMAGE_INDEX_TEMP_FILE);
errno = saved_errno;
return false;
}
g_image_index_dirty = false;
return true;
}
static bool loaded_index_is_valid(void) {
for (int i = 0; i < MAX_IMAGE_MOUNTS; ++i) {
const image_index_entry_t *entry = &g_image_index[i];
if (!entry->valid)
continue;
if (entry->complete > 1 || entry->path[0] == '\0' ||
memchr(entry->path, '\0', sizeof(entry->path)) == NULL) {
return false;
}
}
for (int i = 0; i < MAX_IMAGE_TITLES; ++i) {
const image_index_title_t *title = &g_image_titles[i];
if (!title->valid)
continue;
if (title->image_index >= MAX_IMAGE_MOUNTS ||
!g_image_index[title->image_index].valid ||
title->title_id[0] == '\0' ||
memchr(title->title_id, '\0', sizeof(title->title_id)) == NULL) {
return false;
}
}
return true;
}
static void load_index(void) {
if (g_image_index_loaded)
return;
g_image_index_loaded = true;
FILE *file = fopen(IMAGE_INDEX_FILE, "rb");
if (!file)
return;
image_index_header_t header;
bool ok = fread(&header, sizeof(header), 1, file) == 1 &&
header.magic == IMAGE_INDEX_MAGIC &&
header.version == IMAGE_INDEX_VERSION &&
header.entry_capacity == MAX_IMAGE_MOUNTS &&
header.title_capacity == MAX_IMAGE_TITLES &&
fread(g_image_index, sizeof(g_image_index), 1, file) == 1 &&
fread(g_image_titles, sizeof(g_image_titles), 1, file) == 1;
(void)fclose(file);
if (ok)
ok = loaded_index_is_valid();
if (!ok) {
memset(g_image_index, 0, sizeof(g_image_index));
memset(g_image_titles, 0, sizeof(g_image_titles));
(void)unlink(IMAGE_INDEX_FILE);
log_debug(" [IMGIDX] invalid cache discarded: %s", IMAGE_INDEX_FILE);
}
}
static int reserve_entry(const char *path) {
int entry_index = find_entry(path);
if (entry_index >= 0)
return entry_index;
for (int i = 0; i < MAX_IMAGE_MOUNTS; ++i) {
if (!g_image_index[i].valid) {
g_image_index[i].valid = 1;
(void)strlcpy(g_image_index[i].path, path, sizeof(g_image_index[i].path));
return i;
}
}
return -1;
}
static void begin_scan_locked(const char *path, const struct stat *st) {
int entry_index = reserve_entry(path);
if (entry_index < 0) {
log_debug(" [IMGIDX] cache full, scanning without fingerprint: %s", path);
return;
}
clear_entry_titles(entry_index);
image_index_entry_t *entry = &g_image_index[entry_index];
entry->size = (int64_t)st->st_size;
entry->mtime_sec = (int64_t)st->st_mtim.tv_sec;
entry->mtime_nsec = (int32_t)st->st_mtim.tv_nsec;
entry->complete = 0;
entry->valid = 1;
g_image_index_dirty = true;
}
static bool cached_titles_ready(int entry_index,
const struct AppDbTitleList *app_db_titles,
bool app_db_titles_ready) {
const char *image_path = g_image_index[entry_index].path;
for (int i = 0; i < MAX_IMAGE_TITLES; ++i) {
if (!g_image_titles[i].valid ||
g_image_titles[i].image_index != (uint16_t)entry_index) {
continue;
}
const char *title_id = g_image_titles[i].title_id;
if (!is_installed(title_id) || !has_appmeta_data(title_id))
return false;
char runtime_path[MAX_PATH];
char linked_image[MAX_PATH];
if (!read_mount_link(title_id, runtime_path, sizeof(runtime_path)) ||
!is_under_image_mount_base(runtime_path) ||
!read_mount_image_link(title_id, linked_image, sizeof(linked_image)) ||
strcmp(linked_image, image_path) != 0) {
return false;
}
if (app_db_titles_ready &&
!app_db_title_list_contains(app_db_titles, title_id)) {
return false;
}
}
return true;
}
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 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 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) {
pthread_mutex_lock(&g_image_index_mutex);
load_index();
begin_scan_locked(path, st);
pthread_mutex_unlock(&g_image_index_mutex);
}
bool sm_image_index_record_game(const char *game_path, const char *title_id) {
pthread_mutex_lock(&g_image_index_mutex);
load_index();
char image_path[MAX_PATH];
if (!resolve_outermost_image_source_from_mount_cache(
game_path, image_path, sizeof(image_path))) {
pthread_mutex_unlock(&g_image_index_mutex);
return true;
}
int entry_index = find_entry(image_path);
if (entry_index < 0) {
struct stat st;
if (stat(image_path, &st) != 0) {
pthread_mutex_unlock(&g_image_index_mutex);
return false;
}
begin_scan_locked(image_path, &st);
entry_index = find_entry(image_path);
if (entry_index < 0) {
pthread_mutex_unlock(&g_image_index_mutex);
return false;
}
}
char tracked_path[MAX_PATH];
char linked_image[MAX_PATH];
if (is_installed(title_id) &&
read_mount_link(title_id, tracked_path, sizeof(tracked_path)) &&
strcmp(tracked_path, game_path) == 0 &&
(!read_mount_image_link(title_id, linked_image, sizeof(linked_image)) ||
strcmp(linked_image, image_path) != 0) &&
!write_mount_image_link(title_id, image_path)) {
log_debug(" [IMGIDX] failed to repair image link: %s -> %s", title_id,
image_path);
}
for (int i = 0; i < MAX_IMAGE_TITLES; ++i) {
if (g_image_titles[i].valid &&
g_image_titles[i].image_index == (uint16_t)entry_index &&
strcmp(g_image_titles[i].title_id, title_id) == 0) {
pthread_mutex_unlock(&g_image_index_mutex);
return true;
}
}
for (int i = 0; i < MAX_IMAGE_TITLES; ++i) {
if (g_image_titles[i].valid)
continue;
g_image_titles[i].valid = 1;
g_image_titles[i].image_index = (uint16_t)entry_index;
(void)strlcpy(g_image_titles[i].title_id, title_id,
sizeof(g_image_titles[i].title_id));
g_image_index_dirty = true;
pthread_mutex_unlock(&g_image_index_mutex);
return true;
}
log_debug(" [IMGIDX] title cache full: %s", title_id);
pthread_mutex_unlock(&g_image_index_mutex);
return false;
}
void sm_image_index_complete_scan(const char *path) {
pthread_mutex_lock(&g_image_index_mutex);
load_index();
int entry_index = find_entry(path);
if (entry_index < 0) {
pthread_mutex_unlock(&g_image_index_mutex);
return;
}
image_index_entry_t *entry = &g_image_index[entry_index];
if (!entry->complete) {
entry->complete = 1;
g_image_index_dirty = true;
}
pthread_mutex_unlock(&g_image_index_mutex);
}
void sm_image_index_flush(void) {
pthread_mutex_lock(&g_image_index_mutex);
load_index();
if (!save_index())
log_debug(" [IMGIDX] failed to persist scan index: %s", strerror(errno));
pthread_mutex_unlock(&g_image_index_mutex);
}
void sm_image_index_prune(void) {
struct stat st;
pthread_mutex_lock(&g_image_index_mutex);
load_index();
for (int i = 0; i < MAX_IMAGE_MOUNTS; ++i) {
if (g_image_index[i].valid && stat(g_image_index[i].path, &st) != 0 &&
errno == ENOENT) {
clear_entry(i);
}
}
if (!save_index())
log_debug(" [IMGIDX] failed to persist prune: %s", strerror(errno));
pthread_mutex_unlock(&g_image_index_mutex);
}
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;
sm_image_index_snapshot_entry_t *entry = &entries[copied++];
(void)strlcpy(entry->path, source->path, sizeof(entry->path));
entry->size = source->size;
entry->mtime_sec = source->mtime_sec;
entry->mtime_nsec = source->mtime_nsec;
entry->complete = source->complete != 0;
}
pthread_mutex_unlock(&g_image_index_mutex);
*entries_out = entries;
*count_out = copied;
return true;
}
+60 -51
View File
@@ -14,7 +14,6 @@
#include "sm_image_cache.h"
#include "sm_paths.h"
#include "sm_manual.h"
#include "sm_shellcore_hooks.h"
#include <dlfcn.h>
@@ -62,6 +61,29 @@ static app_install_title_dir_fn_t resolve_app_install_title_dir(void) {
return NULL;
}
static bool write_link_file(const char *path, const char *value) {
FILE *f = fopen(path, "w");
if (!f) {
log_debug(" [LINK] open failed for %s: %s", path, strerror(errno));
return false;
}
int saved_errno = 0;
if (fprintf(f, "%s", value) < 0)
saved_errno = errno;
if (fflush(f) != 0 && saved_errno == 0)
saved_errno = errno;
if (fclose(f) != 0 && saved_errno == 0)
saved_errno = errno;
if (saved_errno != 0) {
errno = saved_errno;
log_debug(" [LINK] write failed for %s: %s", path, strerror(errno));
return false;
}
return true;
}
static bool is_appmeta_file(const char *name) {
if (!name)
return false;
@@ -117,11 +139,11 @@ static bool copy_optional_trophy_metadata_file(const char *src_sce_sys,
char dst_path[MAX_PATH];
snprintf(src_path, sizeof(src_path), "%s/%s", src_sce_sys, relative_path);
if (!path_exists(src_path))
if (access(src_path, F_OK) != 0)
return true;
snprintf(dst_path, sizeof(dst_path), "%s/%s", dst_base, relative_path);
if (path_exists(dst_path))
if (access(dst_path, F_OK) == 0)
return true;
if (copy_file(src_path, dst_path) == 0)
@@ -160,13 +182,6 @@ 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,
@@ -178,8 +193,7 @@ static bool mount_and_install(const char *src_path, const char *title_id,
char user_sce_sys[MAX_PATH];
char src_sce_sys[MAX_PATH];
char src_snd0[MAX_PATH];
char image_source_chain[MAX_IMAGE_CHAIN_DEPTH][MAX_PATH];
size_t image_source_count = 0;
char image_source_path[MAX_PATH];
bool has_image_source = false;
bool appmeta_missing = false;
bool metadata_restaged = false;
@@ -192,9 +206,8 @@ static bool mount_and_install(const char *src_path, const char *title_id,
snprintf(user_app_dir, sizeof(user_app_dir), "%s/%s", APP_BASE, title_id);
if (is_under_image_mount_base(src_path)) {
image_source_count = resolve_image_source_chain_from_mount_cache(
src_path, image_source_chain);
has_image_source = image_source_count > 0;
has_image_source = resolve_image_source_from_mount_cache(
src_path, image_source_path, sizeof(image_source_path));
if (!has_image_source) {
log_debug(" [LINK] image source lookup failed for %s: %s", title_id,
src_path);
@@ -276,8 +289,7 @@ static bool mount_and_install(const char *src_path, const char *title_id,
mkdir(APP_BASE, 0777);
mkdir(user_app_dir, 0777);
snprintf(lnk_path, sizeof(lnk_path), "%s/mount.lnk", user_app_dir);
if (!write_mount_link(title_id, src_path)) {
log_debug(" [LINK] write failed for %s: %s", lnk_path, strerror(errno));
if (!write_link_file(lnk_path, src_path)) {
(void)rollback_title_nullfs_mount(title_id, src_path);
runtime_mount_state_unlock();
return false;
@@ -289,18 +301,17 @@ static bool mount_and_install(const char *src_path, const char *title_id,
snprintf(img_lnk_path, sizeof(img_lnk_path), "%s/mount_img.lnk",
user_app_dir);
if (has_image_source) {
if (!write_mount_image_chain(title_id, image_source_chain,
image_source_count)) {
if (!write_link_file(img_lnk_path, image_source_path)) {
(void)unlink(lnk_path);
(void)rollback_title_nullfs_mount(title_id, src_path);
runtime_mount_state_unlock();
return false;
}
log_debug(" [LINK] mount_img.lnk created: %s -> %s layers=%zu",
img_lnk_path, image_source_chain[0], image_source_count);
if (!cache_image_source_mapping(image_source_chain[0], src_path)) {
log_debug(" [LINK] mount_img.lnk created: %s -> %s", img_lnk_path,
image_source_path);
if (!cache_image_source_mapping(image_source_path, src_path)) {
log_debug(" [LINK] image source cache update failed: %s -> %s",
image_source_chain[0], src_path);
src_path, image_source_path);
}
} else if (unlink(img_lnk_path) != 0 && errno != ENOENT) {
log_debug(" [LINK] remove failed for %s: %s", img_lnk_path,
@@ -331,45 +342,43 @@ static bool mount_and_install(const char *src_path, const char *title_id,
}
// REGISTER
int res = 0;
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_l10n(SM_L10N_REGISTER_BRIDGE_UNAVAILABLE, title_name,
title_id);
return false;
}
mark_register_attempted(title_id);
} else {
app_install_title_dir_fn_t app_install_title_dir_fn =
resolve_app_install_title_dir();
if (!app_install_title_dir_fn) {
log_debug(" [REG] sceAppInstUtilAppInstallTitleDir unavailable");
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);
app_install_title_dir_fn_t app_install_title_dir_fn =
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);
return false;
}
mark_register_attempted(title_id);
int res = app_install_title_dir_fn(title_id, APP_BASE "/", 0);
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)
update_registered_title_snd0info(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);
}
} else if ((uint32_t)res == 0x80990002u) {
invalidate_app_db_title_cache();
clear_register_attempts(title_id);
log_debug(" [REG] Restored.");
if (has_src_snd0)
update_registered_title_snd0info(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);
}
// Silent on restore/remount to avoid spam
} else {
log_debug(" [REG] FAIL: 0x%x", res);
notify_system_l10n(SM_L10N_REGISTER_FAILED_CODE, title_name, title_id,
(uint32_t)res);
notify_system("Register failed: %s (%s)\ncode=0x%08X", title_name, title_id,
(uint32_t)res);
return false;
}
@@ -397,8 +406,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_l10n(SM_L10N_INSTALLING, c->title_name,
c->title_id);
notify_system_info("Installing: %s (%s)...", c->title_name,
c->title_id);
}
}
+7 -16
View File
@@ -9,7 +9,6 @@
#include "sm_log.h"
#include "sm_manual.h"
#include "sm_runtime.h"
#include "sm_scan.h"
#include "sm_time.h"
#include "sm_title_state.h"
@@ -50,10 +49,6 @@ static bool install_queue_active(void) {
return runtime_config()->app_install_all_enabled || g_tracked_install_count > 0;
}
bool sm_install_has_pending_work(void) {
return g_tracked_install_count > 0;
}
static pending_install_entry_t *find_pending_install_entry(
const char *title_id) {
if (!title_id || title_id[0] == '\0')
@@ -244,8 +239,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_l10n(SM_L10N_INSTALL_FAILED_TIMEOUT, entry->title_name,
entry->title_id);
notify_system("Install failed: %s (%s)\nTimed out after 5 minutes",
entry->title_name, entry->title_id);
(void)note_pending_install_failure(entry);
clear_pending_install_entry(entry);
}
@@ -285,8 +280,6 @@ static void poll_pending_installs(void) {
if (g_submitted_install_count == 0)
schedule_queued_install_submit(now_us);
free_app_db_title_list(&app_db_titles);
if (!sm_install_has_pending_work() && !release_scan_runtime_mounts())
log_debug(" [IMG] deferred release after install completion");
}
uint64_t sm_install_next_wake_us(uint64_t now_us) {
@@ -372,8 +365,7 @@ static void notify_queued_install_batch(void) {
char message[3075];
int shown_count = 0;
snprintf(message, sizeof(message), sm_l10n_get(SM_L10N_BATCH_INSTALL_QUEUED),
queued_count);
snprintf(message, sizeof(message), "Batch install queued (%d):", queued_count);
for (int i = 0; i < MAX_PENDING; i++) {
const pending_install_entry_t *entry = &g_pending_installs[i];
@@ -386,8 +378,7 @@ 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,
sm_l10n_get(SM_L10N_BATCH_INSTALL_AND_MORE),
(void)snprintf(message + used, sizeof(message) - used, "\n... and %d more",
queued_count - shown_count);
}
}
@@ -434,12 +425,12 @@ bool sm_install_submit_queued(void) {
log_queued_install_batch();
notify_queued_install_batch();
int res = sceAppInstUtilAppInstallAll(NULL);
int res = sceAppInstUtilAppInstallAll();
if (res != 0) {
log_debug(" [REG] Batch install request failed: 0x%x", res);
if (!g_queued_install_submit_failure_notified) {
notify_system_l10n(SM_L10N_BATCH_INSTALL_FAILED, queued_count,
(uint32_t)res);
notify_system("Batch install failed for %d app(s).\ncode=0x%08X",
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_l10n(enabled ? SM_L10N_KSTUFF_ACTIVE
: SM_L10N_KSTUFF_PAUSED);
notify_system_info(enabled ? "KStuff active again"
: "KStuff paused while the game is running");
}
} else {
log_debug(" [KSTUFF] %s request incomplete (ps5=%s ps4=%s)",
-172
View File
@@ -1,172 +0,0 @@
#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] : "";
}
+17 -56
View File
@@ -135,7 +135,6 @@ 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];
@@ -152,12 +151,9 @@ 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;
@@ -190,7 +186,7 @@ static bool send_rich_notification(const char *message) {
" },\n"
" \"actions\": [\n"
" {\n"
" \"actionName\": \"%s\",\n"
" \"actionName\": \"Go to Debug Settings\",\n"
" \"actionType\": \"DeepLink\",\n"
" \"defaultFocus\": true,\n"
" \"parameters\": {\n"
@@ -218,8 +214,8 @@ static bool send_rich_notification(const char *message) {
" \"createdDateTime\": \"%s\",\n"
" \"localNotificationId\": \"%s\"\n"
"}",
escaped_message, escaped_version, escaped_action, escaped_message,
created_at, notification_id);
escaped_message, escaped_version, escaped_message, created_at,
notification_id);
if (len < 0 || (size_t)len >= sizeof(payload))
return false;
@@ -252,10 +248,6 @@ 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;
@@ -319,15 +311,15 @@ void sm_log_shutdown(void) {
pthread_mutex_unlock(&g_log_mutex);
}
void notify_system_rich_l10n(bool allow_in_quiet_mode, sm_l10n_key_t key, ...) {
void notify_system_rich(bool allow_in_quiet_mode, const char *fmt, ...) {
char message[3075];
va_list args;
if (runtime_config()->quiet_mode && !allow_in_quiet_mode)
return;
va_start(args, key);
vsnprintf(message, sizeof(message), sm_l10n_get(key), args);
va_start(args, fmt);
vsnprintf(message, sizeof(message), fmt, args);
va_end(args);
if (!send_rich_notification(message)) {
@@ -340,10 +332,8 @@ void notify_system_rich_l10n(bool allow_in_quiet_mode, sm_l10n_key_t key, ...) {
void notify_game_installed_rich(const char *title_id) {
if (!send_game_installed_rich_notification(title_id)) {
notify_system_info_l10n(
SM_L10N_INSTALLED, (title_id && title_id[0] != '\0')
? title_id
: sm_l10n_get(SM_L10N_UNKNOWN));
notify_system_info("Installed: %s",
(title_id && title_id[0] != '\0') ? title_id : "Unknown");
return;
}
@@ -360,22 +350,12 @@ 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));
}
static void sm_error_set_v(const char *subsystem, int code, const char *path,
const char *fmt, va_list args) {
void sm_error_set(const char *subsystem, int code, const char *path,
const char *fmt, ...) {
memset(&g_last_error, 0, sizeof(g_last_error));
g_last_error.valid = true;
g_last_error.code = code;
@@ -384,24 +364,12 @@ static void sm_error_set_v(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')
if (fmt && fmt[0] != '\0') {
va_list args;
va_start(args, fmt);
vsnprintf(g_last_error.message, sizeof(g_last_error.message), fmt, 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);
va_end(args);
}
}
const sm_error_t *sm_last_error(void) {
@@ -423,13 +391,6 @@ 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 =
@@ -439,6 +400,6 @@ void notify_image_mount_failed(const char *path, int mount_err) {
return;
}
notify_system_l10n(SM_L10N_IMAGE_MOUNT_FAILED, (uint32_t)mount_err,
strerror(mount_err), path);
notify_system("Image mount failed: 0x%08X (%s)\n%s", (uint32_t)mount_err,
strerror(mount_err), path);
}
+2
View File
@@ -292,6 +292,8 @@ static bool rewrite_manual_list_without_deleted_entries(int entry_count) {
}
if (remove_line) {
changed = true;
if (truncated)
drain_manual_file_line(in);
continue;
}
}
+42 -222
View File
@@ -25,10 +25,6 @@ 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
@@ -60,12 +56,6 @@ 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;
@@ -85,13 +75,9 @@ 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;
@@ -107,7 +93,6 @@ 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);
@@ -119,18 +104,11 @@ 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,
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 handle_pre_pause_failure(const char *reason);
static void handle_post_pause_failure(const char *reason, 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);
@@ -201,7 +179,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;
@@ -219,17 +197,9 @@ 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) {
@@ -374,15 +344,14 @@ 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,
sm_l10n_get(SM_L10N_RTLD_MODULE_AFTER_KSTUFF),
"can't load module %.*s after KStuff pause",
(int)(close_paren - open_paren - 1),
open_paren + 1);
if (written > 0 && (size_t)written < summary_out_size)
return;
}
(void)strlcpy(summary_out,
sm_l10n_get(SM_L10N_RTLD_AFTER_KSTUFF_FALLBACK),
(void)strlcpy(summary_out, "can't load module after KStuff pause",
summary_out_size);
return;
}
@@ -390,25 +359,19 @@ 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,
mdbg_failure_kind_t kind) {
static void handle_pre_pause_failure(const char *reason) {
log_debug(" [MDBG] %s crashed before kstuff auto-pause%s%s",
g_mdbg.game.title_id, reason ? ": " : "", reason ? reason : "");
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);
}
notify_system_info("App crashed before KStuff pause: %s. KStuff is not to "
"blame.",
g_mdbg.game.title_id);
clear_tracked_game();
}
static void handle_post_pause_failure(const char *reason, uint64_t now_us,
mdbg_failure_kind_t kind) {
static void handle_post_pause_failure(const char *reason, uint64_t now_us) {
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, kind);
handle_pre_pause_failure(reason);
return;
}
@@ -417,20 +380,14 @@ 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 : "");
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);
}
notify_system_info("App crashed after KStuff pause: %s. Autotune skipped.",
g_mdbg.game.title_id);
clear_tracked_game();
return;
}
char reason_summary[128];
bool is_rtld_error = kind == MDBG_FAILURE_RTLD;
bool is_fatal_error = kind == MDBG_FAILURE_FATAL_SIGNAL;
bool is_rtld_error = matches_tracked_rtld_error(reason);
summarize_failure_reason(reason, reason_summary, sizeof(reason_summary),
is_rtld_error);
@@ -443,19 +400,16 @@ 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_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);
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);
} else {
notify_system_info_l10n(SM_L10N_CRASH_DELAY_INCREASED,
g_mdbg.game.title_id, tuned_delay_seconds);
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);
}
clear_tracked_game();
return;
@@ -463,114 +417,18 @@ 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 : "");
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);
}
notify_system_info("Crash detected after KStuff pause: %s. Failed to update "
"autotune delay.",
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, 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();
handle_post_pause_failure(line, now_us);
}
static void append_log_char(char ch, uint64_t now_us) {
@@ -578,14 +436,18 @@ static void append_log_char(char ch, uint64_t now_us) {
return;
if (ch == '\r' || ch == '\n') {
flush_log_line(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();
return;
}
if (g_mdbg.log_line_length + 1u >= sizeof(g_mdbg.log_line)) {
flush_log_line(now_us);
if (!g_mdbg.game.active)
return;
g_mdbg.log_line[g_mdbg.log_line_length] = '\0';
process_log_line(g_mdbg.log_line, now_us);
reset_log_line_buffer();
}
g_mdbg.log_line[g_mdbg.log_line_length++] = ch;
@@ -612,11 +474,6 @@ 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)) {
@@ -627,7 +484,6 @@ 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();
}
}
@@ -640,18 +496,6 @@ 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;
@@ -681,7 +525,6 @@ 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) {
@@ -691,7 +534,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, MDBG_FAILURE_GENERIC);
handle_post_pause_failure("crash-candidate", now_us);
}
void sm_mdbg_init(void) {
@@ -734,7 +577,6 @@ 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,
@@ -742,29 +584,20 @@ 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;
if (!g_mdbg.game.log_monitoring_active)
start_log_monitoring();
start_log_monitoring();
}
void sm_mdbg_game_on_exit(pid_t pid) {
if (!g_mdbg.game.active || g_mdbg.game.pid != pid)
return;
drain_log_monitor_before_clear(monotonic_time_us());
if (g_mdbg.game.active)
clear_tracked_game();
clear_tracked_game();
}
void sm_mdbg_game_shutdown(void) {
@@ -809,23 +642,16 @@ void sm_mdbg_poll(void) {
return;
}
if (g_mdbg.game.pause_seen || g_mdbg.fatal_error_active) {
poll_log_monitor(now_us);
if (!g_mdbg.game.active)
return;
}
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)) {
drain_log_monitor_before_clear(now_us);
if (g_mdbg.game.active)
clear_tracked_game();
clear_tracked_game();
return;
}
if (ret != 0)
@@ -834,11 +660,5 @@ 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 -12
View File
@@ -105,18 +105,7 @@ bool resolve_device_from_mount(const char *mount_point,
static bool is_path_mountpoint(const char *path) {
struct statfs sfs;
if (statfs(path, &sfs) == 0 && strcmp(sfs.f_mntonname, path) == 0)
return true;
// An unreadable filesystem can make statfs(path) fail while it is still in
// the mount table. Treat it as active until unmount removes that entry.
struct statfs *mntbuf = NULL;
int mntcount = getmntinfo(&mntbuf, MNT_NOWAIT);
for (int i = 0; i < mntcount && mntbuf; ++i) {
if (strcmp(mntbuf[i].f_mntonname, path) == 0)
return true;
}
return false;
return (statfs(path, &sfs) == 0 && strcmp(sfs.f_mntonname, path) == 0);
}
bool is_active_image_mount_point(const char *path) {
-323
View File
@@ -1,323 +0,0 @@
#include "sm_platform.h"
#include <sys/sysctl.h>
#include <sys/user.h>
#include <sys/vnode.h>
#include "sm_log.h"
#include "sm_mount_diag.h"
#include "sm_path_utils.h"
#define MOUNT_DIAG_MAX_ROOTS 16u
#define MOUNT_DIAG_MAX_VM_HITS 32u
#define MOUNT_DIAG_MAX_VNODE_HITS 16u
typedef struct {
char paths[MOUNT_DIAG_MAX_ROOTS][MAX_PATH];
size_t count;
} mount_diag_roots_t;
static void *read_sysctl_snapshot(const int *mib, u_int mib_count,
size_t *size_out) {
for (int attempt = 0; attempt < 3; ++attempt) {
size_t needed = 0;
if (sysctl(mib, mib_count, NULL, &needed, NULL, 0) != 0)
return NULL;
if (needed == 0) {
*size_out = 0;
return NULL;
}
size_t capacity = needed + needed / 8u + 4096u;
uint8_t *buffer = malloc(capacity);
if (!buffer)
return NULL;
size_t used = capacity;
if (sysctl(mib, mib_count, buffer, &used, NULL, 0) == 0) {
*size_out = used;
return buffer;
}
int snapshot_errno = errno;
free(buffer);
if (snapshot_errno != ENOMEM) {
errno = snapshot_errno;
return NULL;
}
}
errno = ENOMEM;
return NULL;
}
static bool copy_record_path(char out[MAX_PATH], const uint8_t *record,
size_t record_size, size_t path_offset) {
out[0] = '\0';
if (record_size <= path_offset)
return false;
const char *path = (const char *)(record + path_offset);
size_t available = record_size - path_offset;
size_t length = strnlen(path, available);
if (length == 0 || length == available)
return false;
(void)strlcpy(out, path, MAX_PATH);
return true;
}
static void copy_process_name(char out[TDNAMLEN + 1], const uint8_t *record,
size_t record_size) {
size_t name_offset = offsetof(struct kinfo_proc, ki_tdname);
out[0] = '\0';
if (record_size <= name_offset)
return;
const char *name = (const char *)(record + name_offset);
size_t available = record_size - name_offset;
size_t length = strnlen(name, available);
if (length == available)
return;
if (length > TDNAMLEN)
length = TDNAMLEN;
memcpy(out, name, length);
out[length] = '\0';
}
static bool add_diag_root(mount_diag_roots_t *roots, const char *path) {
if (!path || path[0] == '\0')
return false;
for (size_t i = 0; i < roots->count; ++i) {
if (strcmp(roots->paths[i], path) == 0)
return true;
}
if (roots->count >= MOUNT_DIAG_MAX_ROOTS)
return false;
(void)strlcpy(roots->paths[roots->count++], path, MAX_PATH);
return true;
}
static bool path_matches_diag_roots(const mount_diag_roots_t *roots,
const char *path) {
for (size_t i = 0; i < roots->count; ++i) {
if (path_matches_root_or_child(path, roots->paths[i]))
return true;
}
return false;
}
static const char *normalized_mount_source(const char *source) {
return strncmp(source, "<above>:", 8) == 0 ? source + 8 : source;
}
static void collect_mount_dependencies(const char *mount_point,
mount_diag_roots_t *roots,
size_t *dependency_count_out) {
(void)add_diag_root(roots, mount_point);
struct statfs *mounts = NULL;
int mount_count = getmntinfo(&mounts, MNT_NOWAIT);
if (mount_count <= 0 || !mounts) {
log_debug(" [MOUNT-BUSY] mount table unavailable for %s: %s",
mount_point, strerror(errno));
return;
}
size_t dependency_count = 0;
for (int i = 0; i < mount_count; ++i) {
const char *source = normalized_mount_source(mounts[i].f_mntfromname);
const char *target = mounts[i].f_mntonname;
bool consumes_mount = path_matches_root_or_child(source, mount_point);
bool child_mount = strcmp(target, mount_point) != 0 &&
path_matches_root_or_child(target, mount_point);
if (!consumes_mount && !child_mount)
continue;
dependency_count++;
log_debug(" [MOUNT-BUSY] dependent type=%s from=%s on=%s flags=0x%lX",
mounts[i].f_fstypename, source, target,
(unsigned long)mounts[i].f_flags);
if (consumes_mount)
(void)add_diag_root(roots, target);
}
*dependency_count_out = dependency_count;
}
static void log_process_vm_refs(pid_t pid, const char *name,
const mount_diag_roots_t *roots,
size_t *hit_count) {
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_VMMAP, pid};
size_t data_size = 0;
uint8_t *data = read_sysctl_snapshot(mib, 4, &data_size);
if (!data)
return;
uint8_t *cursor = data;
uint8_t *end = data + data_size;
while ((size_t)(end - cursor) >= sizeof(int)) {
int record_size = 0;
memcpy(&record_size, cursor, sizeof(record_size));
if (record_size <= 0 || (size_t)record_size > (size_t)(end - cursor))
break;
const struct kinfo_vmentry *entry =
(const struct kinfo_vmentry *)cursor;
if ((size_t)record_size > offsetof(struct kinfo_vmentry, kve_path) &&
entry->kve_type == KVME_TYPE_VNODE) {
char path[MAX_PATH];
if (copy_record_path(path, cursor, (size_t)record_size,
offsetof(struct kinfo_vmentry, kve_path)) &&
path_matches_diag_roots(roots, path)) {
if (*hit_count < MOUNT_DIAG_MAX_VM_HITS) {
log_debug(" [MOUNT-BUSY] pid=%ld name=%s vm=0x%llX-0x%llX "
"prot=0x%X path=%s",
(long)pid, name, (unsigned long long)entry->kve_start,
(unsigned long long)entry->kve_end,
entry->kve_protection, path);
}
(*hit_count)++;
}
}
cursor += record_size;
}
free(data);
}
static void log_all_process_vm_refs(const mount_diag_roots_t *roots,
size_t *hit_count_out) {
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PROC, 0};
size_t data_size = 0;
uint8_t *data = read_sysctl_snapshot(mib, 4, &data_size);
if (!data) {
log_debug(" [MOUNT-BUSY] process list unavailable: %s", strerror(errno));
return;
}
size_t hit_count = 0;
uint8_t *cursor = data;
uint8_t *end = data + data_size;
while ((size_t)(end - cursor) >= sizeof(int)) {
int record_size = 0;
memcpy(&record_size, cursor, sizeof(record_size));
if (record_size <= 0 || (size_t)record_size > (size_t)(end - cursor))
break;
if ((size_t)record_size <= offsetof(struct kinfo_proc, ki_tdname))
break;
const struct kinfo_proc *process = (const struct kinfo_proc *)cursor;
char process_name[TDNAMLEN + 1];
copy_process_name(process_name, cursor, (size_t)record_size);
const char *name = process_name[0] ? process_name : "?";
if (process->ki_pid > 0)
log_process_vm_refs(process->ki_pid, name, roots, &hit_count);
cursor += record_size;
}
free(data);
*hit_count_out = hit_count;
if (hit_count > MOUNT_DIAG_MAX_VM_HITS) {
log_debug(" [MOUNT-BUSY] %zu additional VM references suppressed",
hit_count - MOUNT_DIAG_MAX_VM_HITS);
}
}
static void log_vnode_refs(const char *mount_point, size_t *hit_count_out) {
struct stat root_stat;
if (stat(mount_point, &root_stat) != 0)
return;
int mib[2] = {CTL_KERN, KERN_VNODE};
size_t data_size = 0;
uint8_t *data = read_sysctl_snapshot(mib, 2, &data_size);
if (!data)
return;
void *mount_address = NULL;
uint8_t *cursor = data;
uint8_t *end = data + data_size;
while ((size_t)(end - cursor) >= sizeof(size_t)) {
const struct xvnode *vnode = (const struct xvnode *)cursor;
if (vnode->xv_size < sizeof(struct xvnode) ||
vnode->xv_size > (size_t)(end - cursor)) {
break;
}
if (vnode->xv_dev == root_stat.st_dev &&
vnode->xv_ino == root_stat.st_ino) {
mount_address = vnode->xv_mount;
break;
}
cursor += vnode->xv_size;
}
size_t hit_count = 0;
size_t hold_only_count = 0;
if (mount_address) {
cursor = data;
while ((size_t)(end - cursor) >= sizeof(size_t)) {
const struct xvnode *vnode = (const struct xvnode *)cursor;
if (vnode->xv_size < sizeof(struct xvnode) ||
vnode->xv_size > (size_t)(end - cursor)) {
break;
}
if (vnode->xv_mount == mount_address) {
bool active = vnode->xv_usecount > 0 || vnode->xv_writecount > 0 ||
vnode->xv_numoutput > 0;
if (active) {
if (hit_count < MOUNT_DIAG_MAX_VNODE_HITS) {
log_debug(" [MOUNT-BUSY] vnode=%p dev=0x%llX ino=0x%llX "
"use=%d hold=%d write=%d output=%ld",
vnode->xv_vnode, (unsigned long long)vnode->xv_dev,
(unsigned long long)vnode->xv_ino, vnode->xv_usecount,
vnode->xv_holdcnt, vnode->xv_writecount,
vnode->xv_numoutput);
}
hit_count++;
} else if (vnode->xv_holdcnt > 0) {
hold_only_count++;
}
}
cursor += vnode->xv_size;
}
}
free(data);
*hit_count_out = hit_count;
if (hit_count > MOUNT_DIAG_MAX_VNODE_HITS) {
log_debug(" [MOUNT-BUSY] %zu additional active vnodes suppressed",
hit_count - MOUNT_DIAG_MAX_VNODE_HITS);
}
if (hold_only_count > 0) {
log_debug(" [MOUNT-BUSY] hold-only cached vnodes=%zu", hold_only_count);
}
}
void sm_mount_diag_log_busy(const char *mount_point) {
if (!mount_point || mount_point[0] == '\0')
return;
int saved_errno = errno;
log_debug(" [MOUNT-BUSY] diagnostic begin: %s", mount_point);
mount_diag_roots_t roots;
memset(&roots, 0, sizeof(roots));
size_t dependency_count = 0;
collect_mount_dependencies(mount_point, &roots, &dependency_count);
size_t vm_hit_count = 0;
log_all_process_vm_refs(&roots, &vm_hit_count);
size_t vnode_hit_count = 0;
log_vnode_refs(mount_point, &vnode_hit_count);
if (dependency_count == 0 && vm_hit_count == 0 &&
vnode_hit_count == 0) {
log_debug(" [MOUNT-BUSY] no user-visible owner found; likely transient "
"VFS, buffer-cache, or backing-device reference");
}
log_debug(" [MOUNT-BUSY] diagnostic end: dependencies=%zu vm_refs=%zu "
"active_vnodes=%zu",
dependency_count, vm_hit_count, vnode_hit_count);
errno = saved_errno;
}
+1 -4
View File
@@ -17,7 +17,6 @@ struct PathStateEntry {
bool game_info_cached;
bool game_info_valid;
time_t game_info_mtime;
long game_info_mtime_nsec;
off_t game_info_size;
ino_t game_info_ino;
char game_title_id[MAX_TITLE_ID];
@@ -130,7 +129,6 @@ bool load_cached_game_info(const char *path, const struct stat *param_st,
struct PathStateEntry *entry = find_path_state(path);
if (!entry || !entry->game_info_cached ||
entry->game_info_mtime != param_st->st_mtime ||
entry->game_info_mtime_nsec != param_st->st_mtim.tv_nsec ||
entry->game_info_size != param_st->st_size ||
entry->game_info_ino != param_st->st_ino) {
return false;
@@ -152,7 +150,6 @@ void store_cached_game_info(const char *path, const struct stat *param_st,
entry->game_info_cached = true;
entry->game_info_valid = valid;
entry->game_info_mtime = param_st->st_mtime;
entry->game_info_mtime_nsec = param_st->st_mtim.tv_nsec;
entry->game_info_size = param_st->st_size;
entry->game_info_ino = param_st->st_ino;
(void)strlcpy(entry->game_title_id, valid ? title_id : "", MAX_TITLE_ID);
@@ -217,7 +214,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_l10n(SM_L10N_MISSING_PARAM, path);
notify_system("Missing/invalid param.json:\n%s", 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",
+7 -27
View File
@@ -30,44 +30,24 @@ bool path_matches_root_or_child(const char *path, const char *root) {
size_t root_len = strlen(root);
if (strncmp(path, root, root_len) != 0)
return false;
if (root_len == 1u && root[0] == '/')
return path[0] == '/';
return path[root_len] == '\0' || path[root_len] == '/';
}
static const char *find_usb_storage_root(const char *path) {
bool is_usb_storage_path(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 NULL;
return false;
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 usb_roots[i];
return true;
}
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;
return false;
}
bool build_backports_root_path(const char *scan_path, char out[MAX_PATH]) {
+49 -195
View File
@@ -4,7 +4,6 @@
#include "sm_scan_tree.h"
#include "sm_types.h"
#include "sm_game_cache.h"
#include "sm_game_lifecycle.h"
#include "sm_gameinfo.h"
#include "sm_log.h"
#include "sm_config_mount.h"
@@ -17,21 +16,17 @@
#include "sm_stability.h"
#include "sm_title_state.h"
#include "sm_image_cache.h"
#include "sm_image_index.h"
#include "sm_image.h"
#include "sm_install_queue.h"
#include "sm_manual.h"
#include "sm_shellcore_service.h"
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
@@ -41,34 +36,6 @@ 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) {
@@ -266,16 +233,6 @@ static void notify_duplicate_scan_candidate(const char *title_id,
notify_duplicate_title_once(title_id, ignored_path, existing_path);
}
static bool defer_full_candidate_batch(int candidate_count,
int max_candidates,
bool *incomplete_found_out) {
if (candidate_count < max_candidates)
return false;
if (incomplete_found_out)
*incomplete_found_out = true;
return true;
}
static existing_directory_result_t handle_existing_directory_candidate(
const char *full_path, const scan_app_db_context_t *app_db,
const directory_candidate_info_t *info,
@@ -310,11 +267,9 @@ static existing_directory_result_t handle_existing_directory_candidate(
link_matches_source = installed && link_matches_source;
bool source_valid = false;
if (link_matches_source) {
char eboot_path[MAX_PATH];
int written = snprintf(eboot_path, sizeof(eboot_path), "%s/eboot.bin",
full_path);
source_valid = written > 0 && (size_t)written < sizeof(eboot_path) &&
path_exists(eboot_path);
source_valid = is_data_mounted(info->title_id);
if (!source_valid && mount_title_nullfs(info->title_id, full_path))
source_valid = is_data_mounted(info->title_id);
}
if (source_valid && appmeta_present) {
@@ -372,10 +327,9 @@ static bool enqueue_directory_candidate(
return true;
}
if (defer_full_candidate_batch(*candidate_count, max_candidates,
unstable_found_out)) {
log_debug(" [SKIP] candidate queue full (%d), deferring: %s (%s)",
max_candidates, info->title_name, info->title_id);
if (*candidate_count >= max_candidates) {
log_debug(" [SKIP] candidate queue full (%d): %s (%s)", max_candidates,
info->title_name, info->title_id);
return true;
}
@@ -411,15 +365,6 @@ 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;
}
int duplicate_candidate_index = find_scan_candidate_index_by_title_id(
candidates, *candidate_count, info.title_id);
const char *duplicate_candidate_path =
@@ -504,10 +449,6 @@ static sm_scan_tree_dir_visit_t collect_candidate_directory_visit(
return SM_SCAN_TREE_DIR_DESCEND;
collect_candidates_walk_ctx_t *ctx = (collect_candidates_walk_ctx_t *)ctx_ptr;
if (defer_full_candidate_batch(*ctx->candidate_count, ctx->max_candidates,
ctx->unstable_found_out)) {
return SM_SCAN_TREE_DIR_ABORT;
}
if (try_collect_candidate_for_directory(
dir_path, ctx->candidates, ctx->max_candidates, ctx->candidate_count,
ctx->app_db, ctx->discovered_param_roots,
@@ -526,47 +467,17 @@ static bool collect_candidate_image_visit(const char *image_path,
(void)depth_from_root;
collect_candidates_walk_ctx_t *ctx = (collect_candidates_walk_ctx_t *)ctx_ptr;
if (defer_full_candidate_batch(*ctx->candidate_count, ctx->max_candidates,
ctx->unstable_found_out)) {
return false;
}
struct stat image_st;
if (stat(image_path, &image_st) != 0)
return true;
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)
if (!maybe_mount_image_file(image_path, image_name, ctx->unstable_found_out))
return true;
if (index_ready) {
(void)mount_persistent_indexed_image(image_path, image_name,
ctx->unstable_found_out);
return true;
}
bool image_unstable = false;
if (!maybe_mount_image_file(image_path, image_name, &image_unstable)) {
if (image_unstable && ctx->unstable_found_out)
*ctx->unstable_found_out = true;
return true;
}
if (indexed_image)
sm_image_index_begin_scan(image_path, &image_st);
char mount_point[MAX_PATH];
get_image_mount_point_for_source(image_path, mount_point);
collect_scan_candidates_from_manual_root(
mount_point, ctx->manual_source_path, ctx->candidates,
ctx->max_candidates, ctx->candidate_count, ctx->app_db,
ctx->discovered_param_roots, ctx->discovered_param_root_count,
&image_unstable);
if (image_unstable && ctx->unstable_found_out)
*ctx->unstable_found_out = true;
if (indexed_image && !image_unstable && !should_stop_requested() &&
!runtime_sleep_mode_active()) {
sm_image_index_complete_scan(image_path);
if (ctx->manual_source_path && is_pfsc_image_mount_base_or_child(image_path)) {
char mount_point[MAX_PATH];
get_image_mount_point_for_source(image_path, mount_point);
collect_scan_candidates_from_manual_root(
mount_point, ctx->manual_source_path, ctx->candidates,
ctx->max_candidates, ctx->candidate_count, ctx->app_db,
ctx->discovered_param_roots, ctx->discovered_param_root_count,
ctx->unstable_found_out);
}
return true;
}
@@ -623,10 +534,6 @@ static void collect_scan_candidates_from_manual_path(
int *discovered_param_root_count, bool *unstable_found_out) {
if (should_stop_requested() || runtime_sleep_mode_active())
return;
if (defer_full_candidate_batch(*candidate_count, max_candidates,
unstable_found_out)) {
return;
}
struct stat st;
if (stat(manual_path, &st) != 0) {
@@ -639,48 +546,15 @@ 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)) {
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)
if (!maybe_mount_image_file(manual_path, name, unstable_found_out))
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)
*unstable_found_out = true;
return;
}
sm_image_index_begin_scan(manual_path, &st);
char mount_point[MAX_PATH];
get_image_mount_point_for_source(manual_path, 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,
&image_unstable);
if (image_unstable && unstable_found_out)
*unstable_found_out = true;
if (!image_unstable && !should_stop_requested() &&
!runtime_sleep_mode_active()) {
sm_image_index_complete_scan(manual_path);
}
unstable_found_out);
return;
}
@@ -779,39 +653,43 @@ static bool resolve_backport_path(const char *title_id,
return false;
}
bool mount_backport_overlay_for_title(const char *source_path,
const char *title_id,
const char *owning_scan_root,
bool *unstable_found_out) {
typedef struct {
bool *unstable_found_out;
} backport_overlay_ctx_t;
static bool mount_backport_overlay_for_cached_game(const char *source_path,
const char *title_id,
const char *title_name,
const char *owning_scan_root,
void *ctx_ptr) {
(void)title_name;
if (should_stop_requested() || runtime_sleep_mode_active())
return false;
backport_overlay_ctx_t *ctx = (backport_overlay_ctx_t *)ctx_ptr;
char backport_path[MAX_PATH];
if (!resolve_backport_path(title_id, owning_scan_root, backport_path)) {
return true;
}
if (is_backport_mount_blocked(backport_path))
return false;
return true;
bool overlay_active = false;
if (!reconcile_title_backport_mount(title_id, source_path, backport_path,
&overlay_active)) {
return false;
}
if (overlay_active) {
&overlay_active) ||
overlay_active) {
return true;
}
if (!wait_for_stability_fast(backport_path, "BKP")) {
if (unstable_found_out)
*unstable_found_out = true;
return false;
if (ctx->unstable_found_out)
*ctx->unstable_found_out = true;
return true;
}
overlay_active = false;
if (!reconcile_title_backport_mount(title_id, source_path, backport_path,
&overlay_active)) {
return false;
}
if (overlay_active) {
&overlay_active) ||
overlay_active) {
return true;
}
@@ -820,57 +698,35 @@ bool mount_backport_overlay_for_title(const char *source_path,
title_id);
if (!mount_backport_overlay(system_ex_path, backport_path, title_id)) {
note_backport_mount_failure(backport_path);
return false;
return true;
}
clear_backport_mount_failure(backport_path);
return true;
}
bool release_scan_runtime_mounts(void) {
if (runtime_sleep_mode_active())
return false;
if (sm_game_lifecycle_has_active_game() ||
sm_shellcore_service_has_prepared_mount()) {
return true;
}
runtime_mount_state_lock();
if (sm_game_lifecycle_has_active_game() ||
sm_shellcore_service_has_prepared_mount()) {
runtime_mount_state_unlock();
return true;
}
if (!unmount_all_title_runtime_layers()) {
runtime_mount_state_unlock();
return false;
}
bool released = runtime_config()->persistent_image_mounts ||
release_runtime_image_mounts();
runtime_mount_state_unlock();
return released;
void mount_backport_overlays(bool *unstable_found_out) {
backport_overlay_ctx_t ctx = {
.unstable_found_out = unstable_found_out,
};
for_each_cached_game_entry(NULL, mount_backport_overlay_for_cached_game, &ctx);
}
// --- Unified Scan Pass (images + game candidates) ---
void cleanup_lost_sources_before_scan(void) {
// 1) Drop stale game cache entries for deleted sources.
prune_game_cache();
// 2) Recover or remove stale image mounts before validating their title links.
cleanup_stale_image_mounts();
// 3) Drop stale/broken mount links and unmount stale /system_ex stacks.
runtime_mount_state_lock();
// 2) Drop stale/broken mount links and unmount stale /system_ex stacks.
cleanup_mount_links(NULL, true);
runtime_mount_state_unlock();
sm_image_index_prune();
// 3) Unmount stale image mounts for deleted image files.
cleanup_stale_image_mounts();
// 4) Drop stale path-state entries.
prune_path_state();
}
void cleanup_lost_sources_for_scan_root(const char *scan_root) {
prune_game_cache_for_root(scan_root);
cleanup_stale_image_mounts_for_root(scan_root);
runtime_mount_state_lock();
cleanup_mount_links(scan_root, true);
runtime_mount_state_unlock();
sm_image_index_prune();
cleanup_stale_image_mounts_for_root(scan_root);
prune_path_state_for_root(scan_root);
}
@@ -991,8 +847,7 @@ int collect_scan_candidates_for_scan_root(const char *scan_root,
unstable_found_out);
if (total_found_out)
*total_found_out = g_scan_workspace.found_title_count;
sm_image_index_flush();
*total_found_out = discovered_param_root_count;
free_app_db_title_list(&blocked_ppsa_titles);
free_app_db_title_list(&app_db_titles);
return candidate_count;
@@ -1041,8 +896,7 @@ int collect_scan_candidates(scan_candidate_t *candidates, int max_candidates,
blocked_ppsa_titles_ready);
if (total_found_out)
*total_found_out = g_scan_workspace.found_title_count;
sm_image_index_flush();
*total_found_out = discovered_param_root_count;
free_app_db_title_list(&blocked_ppsa_titles);
free_app_db_title_list(&app_db_titles);
return candidate_count;
+1 -4
View File
@@ -94,10 +94,7 @@ bool sm_scan_tree_walk(const char *scan_root, const char *dir_path,
}
char full_path[MAX_PATH];
int written =
snprintf(full_path, sizeof(full_path), "%s/%s", dir_path, entry->d_name);
if (written < 0 || (size_t)written >= sizeof(full_path))
continue;
snprintf(full_path, sizeof(full_path), "%s/%s", dir_path, entry->d_name);
bool is_dir = false;
bool is_regular = false;
+162 -347
View File
@@ -1,25 +1,21 @@
#include "sm_platform.h"
#include <stdatomic.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/event.h>
#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"
#include "sm_install.h"
#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"
@@ -27,7 +23,6 @@
#include "sm_scan.h"
#include "sm_scan_tree.h"
#include "sm_scanner.h"
#include "sm_shellcore_service.h"
#include "sm_time.h"
#include "sm_types.h"
@@ -42,13 +37,13 @@
typedef enum {
SCANNER_WATCH_SCAN_ROOT = 0,
SCANNER_WATCH_SCAN_ROOT_PARENT,
SCANNER_WATCH_SCAN_BACKPORT_ROOT,
SCANNER_WATCH_SCAN_SUBDIR,
SCANNER_WATCH_SCAN_IMAGE_FILE,
} scanner_watch_kind_t;
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;
@@ -70,14 +65,7 @@ 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;
static int g_scanner_manual_fd = -1;
static volatile sig_atomic_t g_scanner_wake_write_fd = -1;
@@ -121,6 +109,14 @@ static void schedule_manual_probe(uint64_t now_us) {
g_scanner_manual_probe_due_us = now_us + SCANNER_MANUAL_PROBE_INTERVAL_US;
}
static bool set_fd_nonblocking(int fd) {
int flags = fcntl(fd, F_GETFL, 0);
if (flags < 0)
return false;
return fcntl(fd, F_SETFL, flags | O_NONBLOCK) == 0;
}
static void reset_scanner_root_states(void) {
memset(g_scanner_root_states, 0, sizeof(g_scanner_root_states));
}
@@ -166,10 +162,8 @@ 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].owns_fd &&
g_scanner_watch_entries[i].fd >= 0) {
if (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);
@@ -300,8 +294,6 @@ 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;
@@ -368,33 +360,15 @@ 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 open_flags = O_RDONLY;
if (kind != SCANNER_WATCH_SCAN_IMAGE_FILE)
open_flags |= O_DIRECTORY;
int fd = open(path, O_RDONLY | O_DIRECTORY);
int fd = open(path, open_flags);
if (fd < 0) {
if (errno != ENOENT && errno != ENOTDIR) {
log_debug(" [SCAN] watcher open failed for %s: %s", path,
@@ -403,46 +377,10 @@ static bool register_scanner_watch_entry(int kq, int scan_root_index,
return true;
}
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) {
if (!ensure_scanner_watch_capacity(g_scanner_watch_count + 1u)) {
close(fd);
(void)append_scanner_watch_subscription(
shared_fd, false, scan_root_index, path, kind, depth);
return true;
return false;
}
if (!ensure_scanner_watch_fd_index_capacity(g_scanner_watch_count + 1u)) {
close(fd);
return false;
@@ -460,12 +398,20 @@ static bool register_scanner_watch_entry(int kq, int scan_root_index,
return false;
}
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]));
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));
close(fd);
g_scanner_watch_count--;
(void)rebuild_scanner_watch_fd_index();
@@ -478,22 +424,9 @@ 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 (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);
if (g_scanner_watch_entries[index].fd >= 0)
close(g_scanner_watch_entries[index].fd);
size_t last_index = g_scanner_watch_count - 1u;
if (index != last_index) {
@@ -618,23 +551,31 @@ static bool resolve_existing_parent_directory_path(
}
}
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) {
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) {
case SCANNER_WATCH_SCAN_ROOT:
case SCANNER_WATCH_SCAN_BACKPORT_ROOT:
case SCANNER_WATCH_SCAN_SUBDIR:
(void)strlcpy(rebuild_path, watched_path, MAX_PATH);
*rebuild_depth_out = subscription->depth;
*kind_out = subscription->kind;
(void)strlcpy(rebuild_path, entry->path, MAX_PATH);
*rebuild_depth_out = entry->depth;
*kind_out = entry->kind;
return true;
case SCANNER_WATCH_SCAN_ROOT_PARENT:
(void)strlcpy(rebuild_path,
get_scan_path(subscription->scan_root_index), MAX_PATH);
(void)strlcpy(rebuild_path, get_scan_path(entry->scan_root_index),
MAX_PATH);
*rebuild_depth_out = 0;
*kind_out = SCANNER_WATCH_SCAN_ROOT;
return true;
case SCANNER_WATCH_SCAN_IMAGE_FILE:
if (!build_parent_directory_path(entry->path, rebuild_path))
return false;
*rebuild_depth_out = (entry->depth > 0u) ? (uint8_t)(entry->depth - 1u) : 0u;
*kind_out =
(entry->depth <= 1u) ? SCANNER_WATCH_SCAN_ROOT : SCANNER_WATCH_SCAN_SUBDIR;
return true;
default:
return false;
}
@@ -643,25 +584,11 @@ static bool resolve_watch_tree_rebuild_target(
typedef struct {
int kq;
int scan_root_index;
unsigned int scan_depth;
} register_watch_tree_ctx_t;
static sm_scan_tree_dir_visit_t register_watch_directory_visit(
const char *dir_path, unsigned int depth_from_root, void *ctx_ptr) {
register_watch_tree_ctx_t *ctx = (register_watch_tree_ctx_t *)ctx_ptr;
if (depth_from_root > 0u) {
// Runtime image descendants can be active PFS/PFSC mounts. Keeping a
// directory fd open there pins the mounted vnode and makes unmount return
// EBUSY. The managed mount-base watcher is sufficient: mounted images are
// read-only and every discovery pass rebuilds this tree.
if (is_under_image_mount_base(dir_path))
return SM_SCAN_TREE_DIR_SKIP_DESCEND;
if (depth_from_root >= ctx->scan_depth ||
directory_has_param_json(dir_path, NULL)) {
return SM_SCAN_TREE_DIR_SKIP_DESCEND;
}
}
scanner_watch_kind_t kind =
(depth_from_root == 0u) ? SCANNER_WATCH_SCAN_ROOT
: SCANNER_WATCH_SCAN_SUBDIR;
@@ -673,6 +600,18 @@ static sm_scan_tree_dir_visit_t register_watch_directory_visit(
return SM_SCAN_TREE_DIR_DESCEND;
}
static bool register_watch_image_visit(const char *image_path,
const char *image_name,
unsigned int depth_from_root,
void *ctx_ptr) {
(void)image_name;
register_watch_tree_ctx_t *ctx = (register_watch_tree_ctx_t *)ctx_ptr;
return register_scanner_watch_entry(ctx->kq, ctx->scan_root_index, image_path,
SCANNER_WATCH_SCAN_IMAGE_FILE,
(uint8_t)depth_from_root);
}
static bool register_scan_root_parent_watch(int kq, int scan_root_index,
const char *scan_root) {
char parent_path[MAX_PATH];
@@ -703,17 +642,24 @@ static bool rebuild_scan_root_watch_tree(int kq, int scan_root_index) {
register_watch_tree_ctx_t walk_ctx = {
.kq = kq,
.scan_root_index = scan_root_index,
.scan_depth = scan_depth,
};
sm_scan_tree_callbacks_t callbacks = {
.on_directory = register_watch_directory_visit,
.on_image_file = NULL,
.on_image_file = register_watch_image_visit,
};
if (!sm_scan_tree_walk(scan_root, scan_root, 0u, scan_depth, &callbacks,
&walk_ctx)) {
return false;
}
char backport_root[MAX_PATH];
if (build_backports_root_path(scan_root, backport_root)) {
if (!register_scanner_watch_entry(kq, scan_root_index, backport_root,
SCANNER_WATCH_SCAN_BACKPORT_ROOT, 1u)) {
return false;
}
}
if (!register_scan_root_parent_watch(kq, scan_root_index, scan_root))
return false;
@@ -732,6 +678,18 @@ static bool rebuild_scan_root_watch_subtree(int kq, int scan_root_index,
return rebuild_scan_root_watch_tree(kq, scan_root_index);
}
if (rebuild_kind == SCANNER_WATCH_SCAN_BACKPORT_ROOT) {
if (!remove_scan_root_watch_entries_for_path(scan_root_index, rebuild_path))
return false;
if (!register_scanner_watch_entry(kq, scan_root_index, rebuild_path,
SCANNER_WATCH_SCAN_BACKPORT_ROOT,
rebuild_depth)) {
return false;
}
clear_scan_root_watch_tree_state(scan_root_index);
return true;
}
unsigned int scan_depth = get_scan_depth_for_root(scan_root);
if (!remove_scan_root_watch_entries_for_path(scan_root_index, rebuild_path))
@@ -744,11 +702,10 @@ static bool rebuild_scan_root_watch_subtree(int kq, int scan_root_index,
register_watch_tree_ctx_t walk_ctx = {
.kq = kq,
.scan_root_index = scan_root_index,
.scan_depth = scan_depth,
};
sm_scan_tree_callbacks_t callbacks = {
.on_directory = register_watch_directory_visit,
.on_image_file = NULL,
.on_image_file = register_watch_image_visit,
};
if (!sm_scan_tree_walk(scan_root, rebuild_path, rebuild_depth,
scan_depth - rebuild_depth, &callbacks, &walk_ctx)) {
@@ -767,39 +724,6 @@ static bool rebuild_all_scan_root_watch_trees(int kq) {
return true;
}
static bool suspend_usb_scan_root_watch_trees(void) {
bool removed_any = false;
for (int i = 0; i < get_scan_path_count(); i++) {
if (!is_usb_storage_path(get_scan_path(i)))
continue;
while (g_scanner_root_watch_heads[i] != SCANNER_WATCH_INDEX_NONE) {
remove_scanner_watch_entry_at(g_scanner_root_watch_heads[i]);
removed_any = true;
}
}
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++) {
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;
}
static void clear_all_dirty_scan_roots(void) {
for (int i = 0; i < get_scan_path_count(); i++) {
g_scanner_root_states[i].dirty = false;
@@ -835,42 +759,46 @@ static void schedule_scan_root_dirty(int scan_root_index, uint64_t now_us,
state->ready_after_us = ready_after_us;
}
static bool scanner_event_requires_consistency_cleanup(uint32_t fflags) {
static bool scanner_event_requires_consistency_cleanup(
const scanner_watch_entry_t *entry, uint32_t fflags) {
if (entry->kind == SCANNER_WATCH_SCAN_BACKPORT_ROOT)
return false;
return (fflags & (NOTE_DELETE | NOTE_RENAME | NOTE_REVOKE)) != 0;
}
static bool scanner_event_requires_watch_tree_refresh(
const scanner_event_subscription_t *subscription, uint32_t fflags) {
const scanner_watch_entry_t *entry, uint32_t fflags) {
uint32_t tree_change_flags =
NOTE_WRITE | NOTE_EXTEND | NOTE_DELETE | NOTE_RENAME | NOTE_REVOKE;
switch (subscription->kind) {
switch (entry->kind) {
case SCANNER_WATCH_SCAN_ROOT:
return (fflags & tree_change_flags) != 0;
case SCANNER_WATCH_SCAN_BACKPORT_ROOT:
return (fflags & (NOTE_DELETE | NOTE_RENAME | NOTE_REVOKE)) != 0;
case SCANNER_WATCH_SCAN_SUBDIR:
return subscription->depth <
get_scan_depth_for_root(
get_scan_path(subscription->scan_root_index)) &&
return entry->depth <
get_scan_depth_for_root(get_scan_path(entry->scan_root_index)) &&
(fflags & tree_change_flags) != 0;
case SCANNER_WATCH_SCAN_IMAGE_FILE:
return (fflags & (NOTE_DELETE | NOTE_RENAME | NOTE_REVOKE)) != 0;
default:
return false;
}
}
static void schedule_scan_root_watch_tree_rebuild(
const scanner_event_subscription_t *subscription,
const char *watched_path) {
const scanner_watch_entry_t *entry) {
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(subscription, watched_path,
rebuild_path, &rebuild_depth,
if (!resolve_watch_tree_rebuild_target(entry, rebuild_path, &rebuild_depth,
&rebuild_kind)) {
return;
}
scanner_root_state_t *state =
&g_scanner_root_states[subscription->scan_root_index];
scanner_root_state_t *state = &g_scanner_root_states[entry->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;
@@ -883,7 +811,9 @@ static void schedule_scan_root_watch_tree_rebuild(
if (strcmp(state->watch_tree_rebuild_path, rebuild_path) == 0) {
if (rebuild_depth < state->watch_tree_rebuild_depth)
state->watch_tree_rebuild_depth = rebuild_depth;
if (rebuild_kind == SCANNER_WATCH_SCAN_ROOT) {
if (rebuild_kind == SCANNER_WATCH_SCAN_ROOT ||
(rebuild_kind == SCANNER_WATCH_SCAN_BACKPORT_ROOT &&
state->watch_tree_rebuild_kind != SCANNER_WATCH_SCAN_ROOT)) {
state->watch_tree_rebuild_kind = rebuild_kind;
}
return;
@@ -904,7 +834,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(subscription->scan_root_index),
get_scan_path(entry->scan_root_index),
sizeof(state->watch_tree_rebuild_path));
}
@@ -992,30 +922,12 @@ 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();
@@ -1066,23 +978,23 @@ static bool run_full_scan_cycle(bool startup_sync, const char *reason,
new_games++;
}
if (new_games > 0)
notify_system_info_l10n(SM_L10N_FOUND_NEW_GAMES, new_games);
notify_system_info("Found %d new games. Executing...", 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");
mount_backport_overlays(&unstable_found);
if (should_abort_scan_cycle())
return false;
if (unstable_found_out)
*unstable_found_out = unstable_found;
if (startup_sync && !should_abort_scan_cycle()) {
notify_system_rich_l10n(true, SM_L10N_LIBRARY_SYNCED, total_found_games);
notify_system_rich(true, "Library Synchronized.\nFound %d games.",
total_found_games);
}
return !should_abort_scan_cycle();
@@ -1112,8 +1024,9 @@ static bool run_targeted_scan_cycle(int scan_root_index,
if (should_abort_scan_cycle())
return false;
if (!sm_install_has_pending_work() && !release_scan_runtime_mounts())
log_debug(" [IMG] some discovery mounts remain busy");
mount_backport_overlays(&unstable_found);
if (should_abort_scan_cycle())
return false;
if (unstable_found_out)
*unstable_found_out = unstable_found;
@@ -1160,8 +1073,7 @@ static int find_due_dirty_scan_root(uint64_t now_us) {
}
static uint64_t compute_next_scan_deadline_us(uint64_t now_us,
uint64_t full_resync_due_us,
bool include_scan_work) {
uint64_t full_resync_due_us) {
uint64_t next_deadline = full_resync_due_us;
uint64_t install_wake_us = sm_install_next_wake_us(now_us);
@@ -1181,7 +1093,7 @@ static uint64_t compute_next_scan_deadline_us(uint64_t now_us,
next_deadline = g_scanner_config_probe_due_us;
}
if (include_scan_work && g_scanner_manual_scan_due_us != 0 &&
if (g_scanner_manual_scan_due_us != 0 &&
(next_deadline == 0 || g_scanner_manual_scan_due_us < next_deadline)) {
next_deadline = g_scanner_manual_scan_due_us;
}
@@ -1191,14 +1103,12 @@ static uint64_t compute_next_scan_deadline_us(uint64_t now_us,
next_deadline = g_scanner_manual_probe_due_us;
}
if (include_scan_work) {
for (int i = 0; i < get_scan_path_count(); i++) {
const scanner_root_state_t *state = &g_scanner_root_states[i];
if (!state->dirty)
continue;
if (next_deadline == 0 || state->ready_after_us < next_deadline)
next_deadline = state->ready_after_us;
}
for (int i = 0; i < get_scan_path_count(); i++) {
const scanner_root_state_t *state = &g_scanner_root_states[i];
if (!state->dirty)
continue;
if (next_deadline == 0 || state->ready_after_us < next_deadline)
next_deadline = state->ready_after_us;
}
return next_deadline;
@@ -1221,41 +1131,29 @@ static const struct timespec *build_wait_timeout(struct timespec *timeout,
}
static bool handle_scan_root_parent_event(
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);
int kq, const scanner_watch_entry_t *entry, uint64_t now_us) {
const char *scan_root = get_scan_path(entry->scan_root_index);
bool root_present = false;
bool root_changed = update_scan_root_presence_state(scan_root_index, scan_root,
&root_present);
bool root_changed = update_scan_root_presence_state(
entry->scan_root_index, scan_root, &root_present);
if (root_present && root_changed) {
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);
schedule_scan_root_dirty(entry->scan_root_index, now_us, false);
schedule_scan_root_watch_tree_rebuild(entry);
return true;
}
if (root_present)
return true;
if (root_changed) {
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);
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);
return true;
}
char parent_path[MAX_PATH];
if (!resolve_existing_parent_directory_path(scan_root, parent_path) ||
strcmp(parent_path, watched_path) != 0) {
return rebuild_scan_root_watch_tree(kq, scan_root_index);
strcmp(parent_path, entry->path) != 0) {
return rebuild_scan_root_watch_tree(kq, entry->scan_root_index);
}
return true;
@@ -1311,61 +1209,25 @@ static bool process_scanner_events(int kq, const struct timespec *timeout,
continue;
}
scanner_watch_entry_t *watch_owner =
scanner_watch_entry_t *watch_entry =
find_scanner_watch_entry_by_fd(event->ident);
if (!watch_owner)
if (!watch_entry)
continue;
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);
if (watch_entry->kind == SCANNER_WATCH_SCAN_ROOT_PARENT) {
if (!handle_scan_root_parent_event(kq, watch_entry, now_us))
return false;
}
subscriptions[subscription_count++] =
(scanner_event_subscription_t){
.scan_root_index = entry->scan_root_index,
.depth = entry->depth,
.kind = entry->kind,
};
continue;
}
for (size_t subscription_index = 0;
subscription_index < subscription_count; subscription_index++) {
const scanner_event_subscription_t *subscription =
&subscriptions[subscription_index];
bool immediate =
(event->fflags & (NOTE_DELETE | NOTE_RENAME | NOTE_REVOKE)) != 0;
if (scanner_event_requires_consistency_cleanup(watch_entry, event->fflags))
schedule_scan_root_cleanup(watch_entry->scan_root_index);
schedule_scan_root_dirty(watch_entry->scan_root_index, now_us, immediate);
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);
}
}
if (scanner_event_requires_watch_tree_refresh(watch_entry, event->fflags))
schedule_scan_root_watch_tree_rebuild(watch_entry);
}
return true;
@@ -1423,16 +1285,14 @@ bool sm_scanner_init(void) {
reset_scanner_root_states();
clear_scanner_config_reload_state();
clear_scanner_manual_scan_state();
atomic_store_explicit(&g_usb_watches_suspended, true,
memory_order_release);
if (pipe(g_scanner_wake_pipe) != 0) {
log_debug(" [SCAN] wake pipe creation failed: %s", strerror(errno));
close_scanner_wake_pipe();
return false;
}
if (!sm_set_fd_nonblocking(g_scanner_wake_pipe[0]) ||
!sm_set_fd_nonblocking(g_scanner_wake_pipe[1])) {
if (!set_fd_nonblocking(g_scanner_wake_pipe[0]) ||
!set_fd_nonblocking(g_scanner_wake_pipe[1])) {
log_debug(" [SCAN] wake pipe nonblocking setup failed: %s",
strerror(errno));
close_scanner_wake_pipe();
@@ -1464,10 +1324,6 @@ void sm_scanner_wake(void) {
(void)write((int)wake_fd, &token, sizeof(token));
}
bool sm_scanner_usb_watches_suspended(void) {
return atomic_load_explicit(&g_usb_watches_suspended, memory_order_acquire);
}
bool sm_scanner_run_startup_sync(void) {
while (!should_stop_requested()) {
while (runtime_sleep_mode_active() && !should_stop_requested())
@@ -1513,22 +1369,18 @@ void sm_scanner_run_loop(void) {
register_config_file_watch(kq, monotonic_time_us());
register_manual_file_watch(kq, monotonic_time_us());
atomic_store_explicit(&g_usb_watches_suspended, false,
memory_order_release);
if (!rebuild_all_scan_root_watch_trees(kq)) {
close(kq);
clear_scanner_watch_entries();
atomic_store_explicit(&g_usb_watches_suspended, true,
memory_order_release);
close_scanner_config_file();
close_scanner_manual_file();
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;
bool scan_work_blocked = false;
while (true) {
if (should_stop_requested()) {
@@ -1537,20 +1389,7 @@ void sm_scanner_run_loop(void) {
}
if (runtime_sleep_mode_active()) {
if (!was_sleeping) {
if (!suspend_usb_scan_root_watch_trees()) {
close(kq);
clear_scanner_watch_entries();
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();
was_sleeping = true;
log_debug("[SLEEP] USB scanner watches suspended");
}
was_sleeping = true;
fd_set readfds;
FD_ZERO(&readfds);
FD_SET(g_scanner_wake_pipe[0], &readfds);
@@ -1568,31 +1407,17 @@ void sm_scanner_run_loop(void) {
continue;
}
if (was_sleeping) {
was_sleeping = false;
if (!discard_scanner_events_nowait(kq)) {
close(kq);
clear_scanner_watch_entries();
request_scanner_shutdown("scanner stale event drain failed");
return;
}
if (!resume_usb_scan_root_watch_trees(kq)) {
close(kq);
clear_scanner_watch_entries();
request_scanner_shutdown("USB watcher resume failed");
return;
}
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");
}
bool game_mount_busy = sm_game_lifecycle_has_active_game() ||
sm_shellcore_service_has_prepared_mount();
char scan_reason[128];
if (!game_mount_busy &&
consume_scan_now_request(scan_reason, sizeof(scan_reason))) {
if (consume_scan_now_request(scan_reason, sizeof(scan_reason))) {
bool unstable_found = false;
if (!run_full_scan_cycle(false, scan_reason, &unstable_found)) {
if (runtime_sleep_mode_active())
@@ -1619,14 +1444,10 @@ void sm_scanner_run_loop(void) {
}
uint64_t now_us = monotonic_time_us();
game_mount_busy = sm_game_lifecycle_has_active_game() ||
sm_shellcore_service_has_prepared_mount();
if (game_mount_busy) {
if (sm_game_lifecycle_has_active_game()) {
next_full_resync_us = 0;
scan_work_blocked = true;
} else if (scan_work_blocked) {
scan_work_blocked = false;
next_full_resync_us = now_us + scanner_full_resync_interval_us();
} else if (next_full_resync_us == 0) {
next_full_resync_us = now_us;
}
if (config_probe_due(now_us)) {
@@ -1672,8 +1493,7 @@ void sm_scanner_run_loop(void) {
request_scanner_shutdown("scanner stale event drain after config reload failed");
return;
}
sm_l10n_init();
notify_system_l10n(SM_L10N_CONFIG_RELOADED);
notify_system("ShadowMount+: config reloaded.");
log_debug(" [CFG] runtime config reloaded");
now_us = monotonic_time_us();
next_full_resync_us =
@@ -1683,7 +1503,7 @@ void sm_scanner_run_loop(void) {
continue;
}
if (!game_mount_busy && g_scanner_manual_scan_due_us != 0 &&
if (g_scanner_manual_scan_due_us != 0 &&
now_us >= g_scanner_manual_scan_due_us) {
g_scanner_manual_scan_due_us = 0;
invalidate_app_db_title_cache();
@@ -1719,8 +1539,7 @@ void sm_scanner_run_loop(void) {
continue;
}
if (!game_mount_busy && next_full_resync_us != 0 &&
now_us >= next_full_resync_us) {
if (next_full_resync_us != 0 && now_us >= next_full_resync_us) {
bool unstable_found = false;
if (!run_full_scan_cycle(false, NULL, &unstable_found)) {
if (runtime_sleep_mode_active())
@@ -1746,16 +1565,14 @@ void sm_scanner_run_loop(void) {
continue;
}
int cleanup_root_index =
game_mount_busy ? -1 : find_pending_cleanup_scan_root();
int cleanup_root_index = find_pending_cleanup_scan_root();
if (cleanup_root_index >= 0) {
g_scanner_root_states[cleanup_root_index].cleanup_pending = false;
cleanup_lost_sources_for_scan_root(get_scan_path(cleanup_root_index));
continue;
}
int dirty_root_index =
game_mount_busy ? -1 : find_due_dirty_scan_root(now_us);
int dirty_root_index = find_due_dirty_scan_root(now_us);
if (dirty_root_index >= 0) {
bool cleanup_pending =
g_scanner_root_states[dirty_root_index].cleanup_pending;
@@ -1818,8 +1635,8 @@ void sm_scanner_run_loop(void) {
continue;
}
uint64_t deadline_us = compute_next_scan_deadline_us(
now_us, next_full_resync_us, !game_mount_busy);
uint64_t deadline_us =
compute_next_scan_deadline_us(now_us, next_full_resync_us);
struct timespec timeout;
const struct timespec *timeout_ptr =
build_wait_timeout(&timeout, now_us, deadline_us);
@@ -1851,6 +1668,4 @@ void sm_scanner_shutdown(void) {
reset_scanner_root_states();
clear_scanner_config_reload_state();
clear_scanner_manual_scan_state();
atomic_store_explicit(&g_usb_watches_suspended, true,
memory_order_release);
}
-226
View File
@@ -1,226 +0,0 @@
/* Position-independent bridge copied into SceShellCore executable memory. */
.text
.p2align 4
.global sm_shellcore_bridge_blob_start
.global sm_shellcore_bridge_blob_end
.global sm_shellcore_bridge_launch_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_app_exit_trampoline
.global sm_shellcore_bridge_install_all_trampoline
.global sm_shellcore_bridge_install_title_dir
.global sm_shellcore_bridge_install_armed
.global sm_shellcore_bridge_install_title_id
.global sm_shellcore_bridge_install_dir
.global sm_shellcore_bridge_socket
.global sm_shellcore_bridge_connect
.global sm_shellcore_bridge_read
.global sm_shellcore_bridge_write
.global sm_shellcore_bridge_close
sm_shellcore_bridge_blob_start:
sm_shellcore_bridge_launch_hook:
pushq %rbx
pushq %r12
movq %rsi, %r12
pushfq
pushq %rax
pushq %rdi
pushq %rsi
pushq %rdx
pushq %rcx
pushq %r8
pushq %r9
pushq %r10
pushq %r11
subq $8, %rsp
movl $1, %edi
xorl %edx, %edx
/* RSI still contains title_id. */
call sm_shellcore_bridge_exchange
addq $8, %rsp
popq %r11
popq %r10
popq %r9
popq %r8
popq %rcx
popq %rdx
popq %rsi
popq %rdi
popq %rax
popfq
subq $8, %rsp
call *sm_shellcore_bridge_launch_trampoline(%rip)
addq $8, %rsp
movq %rax, %rbx
testl %eax, %eax
jns 1f
movl $2, %edi
movq %r12, %rsi
xorl %edx, %edx
subq $8, %rsp
call sm_shellcore_bridge_exchange
addq $8, %rsp
1:
movq %rbx, %rax
popq %r12
popq %rbx
ret
.p2align 4
/*
* 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_app_exit_hook:
pushq %rbx
movl %esi, %ebx
call *sm_shellcore_bridge_app_exit_trampoline(%rip)
pushq %rax
subq $8, %rsp
movl $3, %edi
xorl %esi, %esi
movl %ebx, %edx
call sm_shellcore_bridge_exchange
addq $8, %rsp
popq %rax
popq %rbx
ret
.p2align 4
/*
* FW 12+ removed the public AppInstallTitleDir RPC. AppInstallAll still
* reaches this internal dispatcher, so one explicitly armed request is
* redirected to the internal TitleDir implementation. Every ordinary
* AppInstallAll call follows its original trampoline unchanged.
*/
.p2align 4
sm_shellcore_bridge_install_all_hook:
cmpb $0, sm_shellcore_bridge_install_armed(%rip)
je 1f
movb $0, sm_shellcore_bridge_install_armed(%rip)
leaq sm_shellcore_bridge_install_title_id(%rip), %rdi
leaq sm_shellcore_bridge_install_dir(%rip), %rsi
movl $1, %edx
xorl %ecx, %ecx
jmp *sm_shellcore_bridge_install_title_dir(%rip)
1:
jmp *sm_shellcore_bridge_install_all_trampoline(%rip)
/* int exchange(uint16_t operation, const char *title_id, uint32_t app_id) */
.p2align 4
sm_shellcore_bridge_exchange:
pushq %rbp
movq %rsp, %rbp
pushq %rbx
pushq %r12
subq $160, %rsp
/* Build the fixed 40-byte request. */
movq $0, -64(%rbp)
movq $0, -56(%rbp)
movq $0, -48(%rbp)
movq $0, -40(%rbp)
movq $0, -32(%rbp)
movl $0x534d5343, -64(%rbp)
movw $1, -60(%rbp)
movw %di, -58(%rbp)
movl %edx, -56(%rbp)
testq %rsi, %rsi
je 2f
leaq -56(%rbp), %rdi
movl $31, %ecx
1:
movb (%rsi), %al
testb %al, %al
je 2f
movb %al, (%rdi)
incq %rsi
incq %rdi
decl %ecx
jne 1b
2:
/* Use libkernel_sys wrappers. Direct syscalls from SceShellCore are fatal. */
movl $1, %edi
movl $1, %esi
xorl %edx, %edx
call *sm_shellcore_bridge_socket(%rip)
testl %eax, %eax
js 8f
movl %eax, %ebx
/* connect(fd, &sockaddr, sockaddr.sun_len) */
movl %ebx, %edi
leaq sm_shellcore_bridge_sockaddr(%rip), %rsi
movl $31, %edx
call *sm_shellcore_bridge_connect(%rip)
testl %eax, %eax
jne 7f
/* write(fd, &request, sizeof(request)) */
movl %ebx, %edi
leaq -64(%rbp), %rsi
movl $40, %edx
call *sm_shellcore_bridge_write(%rip)
cmpq $40, %rax
jne 7f
/* Only status is needed by launch and launch-failure requests. */
movl %ebx, %edi
leaq -68(%rbp), %rsi
movl $4, %edx
call *sm_shellcore_bridge_read(%rip)
cmpq $4, %rax
jne 7f
movl -68(%rbp), %r12d
jmp 5f
7:
movl $5, %r12d
5:
movl %ebx, %edi
call *sm_shellcore_bridge_close(%rip)
movl %r12d, %eax
jmp 9f
8:
movl $5, %eax
9:
addq $160, %rsp
popq %r12
popq %rbx
popq %rbp
ret
.p2align 3
sm_shellcore_bridge_launch_trampoline:
.quad 0
sm_shellcore_bridge_app_exit_trampoline:
.quad 0
sm_shellcore_bridge_install_all_trampoline:
.quad 0
sm_shellcore_bridge_install_title_dir:
.quad 0
sm_shellcore_bridge_socket:
.quad 0
sm_shellcore_bridge_connect:
.quad 0
sm_shellcore_bridge_read:
.quad 0
sm_shellcore_bridge_write:
.quad 0
sm_shellcore_bridge_close:
.quad 0
sm_shellcore_bridge_sockaddr:
.byte 31, 1
.asciz "/system_tmp/shadowmount.sock"
.zero 75
.p2align 3
sm_shellcore_bridge_install_armed:
.byte 0
sm_shellcore_bridge_install_title_id:
.zero 32
sm_shellcore_bridge_install_dir:
.zero 1024
sm_shellcore_bridge_blob_end:
.section .note.GNU-stack,"",@progbits
+15 -13
View File
@@ -1,7 +1,5 @@
#include "sm_platform.h"
#include <stdatomic.h>
#include <pthread.h>
#include "sm_game_lifecycle.h"
@@ -9,6 +7,7 @@
#include "sm_limits.h"
#include "sm_log.h"
#include "sm_runtime.h"
#include "sm_scan.h"
#include "sm_shellcore_flags.h"
#define SHELLCORE_FLAG_WAITMODE_OR 2u
@@ -65,7 +64,7 @@ static pthread_cond_t g_shellcore_flag_cond = PTHREAD_COND_INITIALIZER;
static bool g_shellcore_flag_thread_started = false;
static bool g_shellcore_flag_start_ready = false;
static bool g_shellcore_flag_start_success = false;
static atomic_bool g_shellcore_flag_stop_requested = false;
static volatile sig_atomic_t g_shellcore_flag_stop_requested = 0;
static const shellcore_flag_bit_desc_t g_system_state_mgr_status_bits[] = {
{0x0000000000000001ULL, "CEC_ONE_TOUCH_PLAY_COMMAND"},
@@ -510,7 +509,11 @@ static void set_shellcore_flag_start_result(bool success) {
}
static void enter_sleep_mode_and_cleanup(const char *reason) {
(void)request_runtime_sleep_mode(true, reason);
if (request_runtime_sleep_mode(true, reason)) {
runtime_mount_state_lock();
unmount_usb_sources_for_suspend();
runtime_mount_state_unlock();
}
}
static void reset_shellcore_flag_state(shellcore_flag_monitor_t *flag) {
@@ -624,6 +627,8 @@ static void poll_shellcore_flag(shellcore_flag_monitor_t *flag) {
bool entered_resume_working = false;
bool entered_shellui_shutdown_in_progress = false;
bool is_system_state_mgr_info = false;
unsigned current_state = 0;
unsigned previous_state = 0;
if (!flag || !flag->is_open)
return;
@@ -648,8 +653,8 @@ static void poll_shellcore_flag(shellcore_flag_monitor_t *flag) {
is_system_state_mgr_info = strcmp(flag->name, "SceSystemStateMgrInfo") == 0;
if (is_system_state_mgr_info) {
unsigned current_state = (unsigned)(result_pattern & 0xFFFFu);
unsigned previous_state =
current_state = (unsigned)(result_pattern & 0xFFFFu);
previous_state =
flag->has_last_pattern ? (unsigned)(flag->last_pattern & 0xFFFFu) : 0;
entered_shutdown_on_going =
@@ -700,6 +705,7 @@ 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");
}
}
@@ -724,9 +730,7 @@ static void *shellcore_flag_thread_main(void *arg) {
sizeof(g_shellcore_flags) / sizeof(g_shellcore_flags[0]));
set_shellcore_flag_start_result(true);
while (!atomic_load_explicit(&g_shellcore_flag_stop_requested,
memory_order_relaxed) &&
!should_stop_requested()) {
while (!g_shellcore_flag_stop_requested && !should_stop_requested()) {
for (size_t i = 0; i < sizeof(g_shellcore_flags) / sizeof(g_shellcore_flags[0]);
++i) {
poll_shellcore_flag(&g_shellcore_flags[i]);
@@ -745,8 +749,7 @@ bool sm_shellcore_flags_start(void) {
if (g_shellcore_flag_thread_started)
return g_shellcore_flag_start_success;
atomic_store_explicit(&g_shellcore_flag_stop_requested, false,
memory_order_relaxed);
g_shellcore_flag_stop_requested = 0;
g_shellcore_flag_start_ready = false;
g_shellcore_flag_start_success = false;
close_shellcore_flags();
@@ -777,8 +780,7 @@ void sm_shellcore_flags_stop(void) {
if (!g_shellcore_flag_thread_started)
return;
atomic_store_explicit(&g_shellcore_flag_stop_requested, true,
memory_order_relaxed);
g_shellcore_flag_stop_requested = 1;
pthread_join(g_shellcore_flag_thread, NULL);
g_shellcore_flag_thread_started = false;
g_shellcore_flag_start_ready = false;
-501
View File
@@ -1,501 +0,0 @@
#include "sm_platform.h"
#include <signal.h>
#include <sys/mman.h>
#include <pthread.h>
#include "sm_limits.h"
#include "sm_log.h"
#include "sm_runtime.h"
#include "sm_shellcore_hooks.h"
#include "sm_shellcore_remote.h"
#define SHELLCORE_BASE_HOOK_COUNT 2u
#define SHELLCORE_MAX_HOOK_COUNT 3u
#define MAX_HOOK_PROLOGUE_SIZE 32u
#define ABSOLUTE_JUMP_SIZE 12u
/*
* Private payload-SDK ABI from src/crt/kernel.h. The public
* <ps5/kernel.h> intentionally omits dynlib_obj_t/kernel_dynlib_obj(), but
* kernel_dynlib_obj() copies the complete 0x180-byte object. Keep the local
* definition layout-checked because ShellCore bridge placement needs the
* module bounds and its executable eh_frame area.
*/
typedef struct {
uintptr_t next;
uintptr_t path;
uintptr_t unknown0[2];
uint32_t refcount;
uintptr_t handle;
uintptr_t mapbase;
uintptr_t mapsize;
uintptr_t textsize;
uintptr_t database;
uintptr_t datasize;
uintptr_t unknown1;
uintptr_t unknown1size;
uintptr_t entry;
uintptr_t unknown2;
uintptr_t vaddrbase;
uint32_t tlsindex;
uintptr_t tlsinit;
uintptr_t tlsinitsize;
uintptr_t tlssize;
uintptr_t tlsoffset;
uintptr_t tlsalign;
uintptr_t pltgot;
uintptr_t unknown3[6];
uintptr_t init;
uintptr_t fini;
uintptr_t eh_frame_hdr;
uintptr_t eh_frame_hdr_size;
uintptr_t eh_frame;
uintptr_t eh_frame_size;
int32_t status;
int32_t flags;
uintptr_t unknown4[5];
uintptr_t dynsec;
uintptr_t unknown5[6];
} shellcore_dynlib_obj_t;
_Static_assert(offsetof(shellcore_dynlib_obj_t, mapbase) == 0x30,
"unexpected dynlib_obj mapbase offset");
_Static_assert(offsetof(shellcore_dynlib_obj_t, eh_frame) == 0x108,
"unexpected dynlib_obj eh_frame offset");
_Static_assert(sizeof(shellcore_dynlib_obj_t) == 0x180,
"unexpected dynlib_obj size");
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_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_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[];
extern const uint8_t sm_shellcore_bridge_install_title_id[];
extern const uint8_t sm_shellcore_bridge_install_dir[];
extern const uint8_t sm_shellcore_bridge_socket[];
extern const uint8_t sm_shellcore_bridge_connect[];
extern const uint8_t sm_shellcore_bridge_read[];
extern const uint8_t sm_shellcore_bridge_write[];
extern const uint8_t sm_shellcore_bridge_close[];
typedef struct {
const uint8_t *slot;
const char *name;
} shellcore_import_t;
static const shellcore_import_t k_bridge_imports[] = {
{sm_shellcore_bridge_socket, "socket"},
{sm_shellcore_bridge_connect, "connect"},
{sm_shellcore_bridge_read, "read"},
{sm_shellcore_bridge_write, "write"},
{sm_shellcore_bridge_close, "close"},
};
typedef struct {
bool installed;
sm_shellcore_remote_t remote;
uintptr_t bridge_address;
size_t hook_count;
sm_shellcore_target_t target_ids[SHELLCORE_MAX_HOOK_COUNT];
uint8_t original[SHELLCORE_MAX_HOOK_COUNT][MAX_HOOK_PROLOGUE_SIZE];
uint8_t original_size[SHELLCORE_MAX_HOOK_COUNT];
} shellcore_hooks_state_t;
static shellcore_hooks_state_t g_hooks;
static pthread_mutex_t g_install_mutex = PTHREAD_MUTEX_INITIALIZER;
static uintptr_t remote_bridge_symbol(const uint8_t *symbol) {
return g_hooks.bridge_address +
(uintptr_t)(symbol - sm_shellcore_bridge_blob_start);
}
static void build_absolute_jump(uint8_t jump[ABSOLUTE_JUMP_SIZE],
uintptr_t destination) {
jump[0] = 0x48;
jump[1] = 0xb8;
memcpy(jump + 2, &destination, sizeof(destination));
jump[10] = 0xff;
jump[11] = 0xe0;
}
static bool patch_remote_jump(pid_t pid, uintptr_t source,
uintptr_t destination, size_t patch_size) {
if (patch_size < ABSOLUTE_JUMP_SIZE || patch_size > MAX_HOOK_PROLOGUE_SIZE) {
return false;
}
uint8_t patch[MAX_HOOK_PROLOGUE_SIZE];
memset(patch, 0x90, patch_size);
build_absolute_jump(patch, destination);
if (!sm_remote_process_write(pid, source, patch, patch_size))
return false;
uint8_t verify[MAX_HOOK_PROLOGUE_SIZE];
return sm_remote_process_read(pid, source, verify, patch_size) &&
memcmp(verify, patch, patch_size) == 0;
}
static bool verify_remote_bytes(pid_t pid, uintptr_t address,
const void *expected, size_t size) {
uint8_t buffer[256];
const uint8_t *bytes = (const uint8_t *)expected;
while (size != 0) {
size_t chunk = size < sizeof(buffer) ? size : sizeof(buffer);
if (!sm_remote_process_read(pid, address, buffer, chunk) ||
memcmp(buffer, bytes, chunk) != 0) {
return false;
}
address += chunk;
bytes += chunk;
size -= chunk;
}
return true;
}
static bool append_trampoline(uint8_t *bridge, size_t bridge_capacity,
size_t *cursor, const uint8_t *original,
size_t original_size, uintptr_t return_address,
uintptr_t remote_base,
uintptr_t *remote_trampoline_out) {
size_t aligned = (*cursor + 15u) & ~(size_t)15u;
size_t required = aligned + original_size + ABSOLUTE_JUMP_SIZE;
if (required > bridge_capacity)
return false;
memcpy(bridge + aligned, original, original_size);
build_absolute_jump(bridge + aligned + original_size, return_address);
*remote_trampoline_out = remote_base + aligned;
*cursor = required;
return true;
}
static void set_bridge_pointer(uint8_t *bridge, const uint8_t *local_symbol,
uintptr_t value) {
size_t offset = (size_t)(local_symbol - sm_shellcore_bridge_blob_start);
memcpy(bridge + offset, &value, sizeof(value));
}
static bool resolve_bridge_imports(pid_t pid, uint8_t *bridge) {
uint32_t handle = UINT32_MAX;
if (kernel_dynlib_handle(pid, "libkernel_sys.sprx", &handle) != 0)
return false;
for (size_t i = 0; i < sizeof(k_bridge_imports) / sizeof(k_bridge_imports[0]);
++i) {
uintptr_t address =
(uintptr_t)kernel_dynlib_dlsym(pid, handle, k_bridge_imports[i].name);
if (address == 0) {
log_debug(" [SHELLCORE] failed to resolve libkernel %s",
k_bridge_imports[i].name);
return false;
}
set_bridge_pointer(bridge, k_bridge_imports[i].slot, address);
}
return true;
}
static pid_t find_shellcore_pid(void) {
pid_t pid = find_pid_by_name("SceShellCore", true);
if (pid <= 0)
pid = find_pid_by_name("SceShellCore.elf", true);
return pid;
}
bool sm_shellcore_install_bridge_enabled(void) {
uint16_t firmware =
(uint16_t)((kernel_get_fw_version() >> 16) & 0xffffu);
return firmware >= 0x1200u;
}
static bool install_hooks_for_pid(pid_t pid) {
const size_t blob_size =
(size_t)(sm_shellcore_bridge_blob_end - sm_shellcore_bridge_blob_start);
const size_t bridge_capacity =
blob_size + SHELLCORE_MAX_HOOK_COUNT *
(MAX_HOOK_PROLOGUE_SIZE + ABSOLUTE_JUMP_SIZE + 16u);
uint8_t *bridge = calloc(1, bridge_capacity);
if (!bridge)
return false;
memcpy(bridge, sm_shellcore_bridge_blob_start, blob_size);
if (kill(pid, SIGSTOP) != 0) {
free(bridge);
return false;
}
bool ok = false;
sm_shellcore_remote_t remote;
shellcore_dynlib_obj_t object;
if (!sm_shellcore_remote_resolve(pid, &remote) ||
kernel_dynlib_obj(pid, 0, &object) != 0) {
log_debug(" [SHELLCORE] stopped-process layout resolution failed");
goto done;
}
if (object.mapbase != remote.image_base ||
object.mapbase > UINTPTR_MAX - object.mapsize ||
object.eh_frame < object.mapbase ||
object.eh_frame_size < bridge_capacity ||
object.eh_frame > UINTPTR_MAX - bridge_capacity ||
object.eh_frame + bridge_capacity > object.mapbase + object.mapsize) {
log_debug(" [SHELLCORE] invalid layout: base=0x%lx map=0x%lx+0x%lx "
"eh=0x%lx+0x%lx bridge=0x%zx",
(unsigned long)remote.image_base, (unsigned long)object.mapbase,
(unsigned long)object.mapsize, (unsigned long)object.eh_frame,
(unsigned long)object.eh_frame_size, bridge_capacity);
goto done;
}
log_debug(" [SHELLCORE] layout validated: map=0x%lx+0x%lx eh=0x%lx+0x%lx",
(unsigned long)object.mapbase, (unsigned long)object.mapsize,
(unsigned long)object.eh_frame,
(unsigned long)object.eh_frame_size);
const bool install_hook_enabled = sm_shellcore_install_bridge_enabled();
const size_t hook_count = install_hook_enabled
? SHELLCORE_MAX_HOOK_COUNT
: SHELLCORE_BASE_HOOK_COUNT;
uintptr_t targets[SHELLCORE_MAX_HOOK_COUNT] = {
remote.targets[SM_SHELLCORE_TARGET_LAUNCH_APP],
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_APP_EXIT,
SM_SHELLCORE_TARGET_INSTALL_ALL,
};
for (size_t i = 0; i < hook_count; ++i) {
uint8_t patch_size = remote.offsets->targets[target_ids[i]].patch_size;
if (patch_size < ABSOLUTE_JUMP_SIZE ||
patch_size > MAX_HOOK_PROLOGUE_SIZE ||
!sm_remote_process_read(pid, targets[i], g_hooks.original[i],
patch_size)) {
goto done;
}
static const uint8_t function_prologue[] = {0x55, 0x48, 0x89, 0xe5};
if (memcmp(g_hooks.original[i], function_prologue,
sizeof(function_prologue)) != 0) {
log_debug(" [SHELLCORE] unexpected prologue: %s at 0x%lx",
sm_shellcore_target_name(target_ids[i]),
(unsigned long)targets[i]);
goto done;
}
g_hooks.original_size[i] = patch_size;
}
size_t cursor = blob_size;
uintptr_t launch_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],
targets[0] + g_hooks.original_size[0], object.eh_frame,
&launch_trampoline)) {
goto done;
}
set_bridge_pointer(bridge, sm_shellcore_bridge_launch_trampoline,
launch_trampoline);
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,
&app_exit_trampoline)) {
goto done;
}
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],
targets[2] + g_hooks.original_size[2],
object.eh_frame, &install_all_trampoline)) {
goto done;
}
set_bridge_pointer(bridge, sm_shellcore_bridge_install_all_trampoline,
install_all_trampoline);
set_bridge_pointer(
bridge, sm_shellcore_bridge_install_title_dir,
remote.targets[SM_SHELLCORE_TARGET_INSTALL_TITLE_DIR]);
}
if (!resolve_bridge_imports(pid, bridge))
goto done;
if (kernel_mprotect(pid, (intptr_t)object.mapbase, object.mapsize,
PROT_READ | PROT_WRITE | PROT_EXEC) != 0 ||
!sm_remote_process_write(pid, object.eh_frame, bridge, cursor) ||
!verify_remote_bytes(pid, object.eh_frame, bridge, cursor)) {
log_debug(" [SHELLCORE] bridge write verification failed");
goto done;
}
uintptr_t launch_hook =
object.eh_frame + (uintptr_t)(sm_shellcore_bridge_launch_hook -
sm_shellcore_bridge_blob_start);
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 +
(uintptr_t)(sm_shellcore_bridge_install_all_hook -
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], app_exit_hook,
g_hooks.original_size[1]) ||
(install_hook_enabled &&
!patch_remote_jump(pid, targets[2], install_all_hook,
g_hooks.original_size[2]))) {
goto rollback;
}
g_hooks.remote = remote;
g_hooks.bridge_address = object.eh_frame;
g_hooks.hook_count = hook_count;
memcpy(g_hooks.target_ids, target_ids,
hook_count * sizeof(g_hooks.target_ids[0]));
g_hooks.installed = true;
ok = true;
log_debug(" [SHELLCORE] hooks installed: fw=%s pid=%ld lifecycle=1 "
"app_exit=1 install=%d",
remote.offsets->name, (long)pid, install_hook_enabled ? 1 : 0);
goto done;
rollback:
for (size_t i = 0; i < hook_count; ++i) {
(void)sm_remote_process_write(pid, targets[i], g_hooks.original[i],
g_hooks.original_size[i]);
}
done:
free(bridge);
(void)kill(pid, SIGCONT);
return ok;
}
bool sm_shellcore_hooks_start(void) {
pthread_mutex_lock(&g_install_mutex);
if (g_hooks.installed)
goto installed;
memset(&g_hooks, 0, sizeof(g_hooks));
pid_t pid = find_shellcore_pid();
if (pid <= 0) {
log_debug(" [SHELLCORE] process not found");
pthread_mutex_unlock(&g_install_mutex);
return false;
}
bool ok = install_hooks_for_pid(pid);
pthread_mutex_unlock(&g_install_mutex);
return ok;
installed:
pthread_mutex_unlock(&g_install_mutex);
return true;
}
void sm_shellcore_hooks_stop(void) {
pthread_mutex_lock(&g_install_mutex);
if (!g_hooks.installed) {
pthread_mutex_unlock(&g_install_mutex);
return;
}
pid_t pid = g_hooks.remote.pid;
if (kill(pid, SIGSTOP) == 0) {
for (size_t i = 0; i < g_hooks.hook_count; ++i) {
sm_shellcore_target_t target = g_hooks.target_ids[i];
(void)sm_remote_process_write(pid, g_hooks.remote.targets[target],
g_hooks.original[i],
g_hooks.original_size[i]);
}
(void)kill(pid, SIGCONT);
}
memset(&g_hooks, 0, sizeof(g_hooks));
pthread_mutex_unlock(&g_install_mutex);
}
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;
if (active && kill(g_hooks.remote.pid, 0) != 0 && errno == ESRCH)
active = false;
pthread_mutex_unlock(&g_install_mutex);
errno = saved_errno;
return active;
}
bool sm_shellcore_install_title_dir(const char *title_id,
const char *install_dir,
int *result_out) {
if (!title_id || !install_dir || !result_out)
return false;
size_t title_len = strnlen(title_id, MAX_TITLE_ID);
size_t dir_len = strnlen(install_dir, MAX_PATH);
if (title_len == 0 || title_len >= MAX_TITLE_ID || dir_len == 0 ||
dir_len >= MAX_PATH) {
return false;
}
pthread_mutex_lock(&g_install_mutex);
if (!g_hooks.installed ||
g_hooks.hook_count < SHELLCORE_MAX_HOOK_COUNT) {
pthread_mutex_unlock(&g_install_mutex);
return false;
}
char remote_title[MAX_TITLE_ID] = {0};
char remote_dir[MAX_PATH] = {0};
memcpy(remote_title, title_id, title_len);
memcpy(remote_dir, install_dir, dir_len);
pid_t pid = g_hooks.remote.pid;
uint8_t armed = 1;
bool written =
sm_remote_process_write(pid,
remote_bridge_symbol(
sm_shellcore_bridge_install_title_id),
remote_title, sizeof(remote_title)) &&
sm_remote_process_write(
pid, remote_bridge_symbol(sm_shellcore_bridge_install_dir),
remote_dir, sizeof(remote_dir)) &&
sm_remote_process_write(
pid, remote_bridge_symbol(sm_shellcore_bridge_install_armed),
&armed, sizeof(armed));
if (!written) {
armed = 0;
(void)sm_remote_process_write(
pid, remote_bridge_symbol(sm_shellcore_bridge_install_armed), &armed,
sizeof(armed));
pthread_mutex_unlock(&g_install_mutex);
return false;
}
int result = sceAppInstUtilAppInstallAll(NULL);
uint8_t remaining = 1;
bool read_back = sm_remote_process_read(
pid, remote_bridge_symbol(sm_shellcore_bridge_install_armed), &remaining,
sizeof(remaining));
bool consumed = read_back && remaining == 0;
if (!consumed) {
armed = 0;
(void)sm_remote_process_write(
pid, remote_bridge_symbol(sm_shellcore_bridge_install_armed), &armed,
sizeof(armed));
}
pthread_mutex_unlock(&g_install_mutex);
if (!consumed) {
log_debug(" [SHELLCORE] AppInstallAll trigger was not consumed: %s",
title_id);
return false;
}
log_debug(" [SHELLCORE] AppInstallAll bridge consumed: %s result=0x%08x",
title_id, (uint32_t)result);
*result_out = result;
return true;
}
-33
View File
@@ -1,33 +0,0 @@
#include "sm_shellcore_offsets.h"
#include <stddef.h>
static const sm_shellcore_firmware_offsets_t g_shellcore_offsets[] = {
#include "sm_shellcore_offsets.inc"
};
const sm_shellcore_firmware_offsets_t *
sm_shellcore_offsets_for_firmware(uint32_t raw_firmware) {
uint16_t firmware = (uint16_t)((raw_firmware >> 16) & 0xffffu);
for (size_t i = 0;
i < sizeof(g_shellcore_offsets) / sizeof(g_shellcore_offsets[0]); ++i) {
if (g_shellcore_offsets[i].firmware == firmware)
return &g_shellcore_offsets[i];
}
return NULL;
}
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_APP_EXIT:
return "onAppExit";
case SM_SHELLCORE_TARGET_INSTALL_TITLE_DIR:
return "AppInstallTitleDirMain";
case SM_SHELLCORE_TARGET_INSTALL_ALL:
return "AppInstallAll";
default:
return "unknown";
}
}
-286
View File
@@ -1,286 +0,0 @@
/* Generated by tools/generate_shellcore_offsets_from_files.py. */
{
.firmware = 0x0100u,
.name = "1.00",
.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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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}},
}
-144
View File
@@ -1,144 +0,0 @@
#include "sm_platform.h"
#include <ps5/mdbg.h>
#include "sm_log.h"
#include "sm_shellcore_remote.h"
#define X86_PAGE_FRAME 0x000ffffffffff000ull
#define X86_PAGE_VALID 0x001ull
#define X86_PAGE_LARGE 0x080ull
static uintptr_t resolve_vmspace_pmap(uintptr_t vmspace) {
uint32_t version = kernel_get_fw_version() >> 16;
if (version >= 0x0100u && version <= 0x0102u)
return vmspace + 0x2c0u;
if (version >= 0x0105u && version <= 0x0550u)
return vmspace + 0x2e0u;
if (version >= 0x0600u && version <= 0x1270u)
return vmspace + 0x2e8u;
return 0;
}
static bool resolve_process_paging(pid_t pid, uint64_t *cr3_out,
uint64_t *dmap_out) {
uintptr_t process = (uintptr_t)kernel_get_proc(pid);
if (!process)
return false;
uintptr_t vmspace = (uintptr_t)kernel_getlong(
process + (uintptr_t)KERNEL_OFFSET_PROC_P_VMSPACE);
if (!vmspace)
return false;
uintptr_t pmap = resolve_vmspace_pmap(vmspace);
if (!pmap)
return false;
uint64_t paging[2] = {0};
if (kernel_copyout((intptr_t)(pmap + 32u), paging, sizeof(paging)) != 0)
return false;
if (!paging[0] || !paging[1])
return false;
*cr3_out = paging[1];
*dmap_out = paging[0] - paging[1];
return true;
}
static uint64_t virtual_to_physical(uint64_t address, uint64_t dmap,
uint64_t page_map,
uint64_t *physical_limit_out) {
page_map &= X86_PAGE_FRAME;
for (int shift = 39; shift >= 12; shift -= 9) {
uint64_t index = (address >> shift) & 0x1ffu;
page_map = kernel_getlong((intptr_t)(dmap + page_map + index * 8u));
if ((page_map & X86_PAGE_VALID) == 0)
return UINT64_MAX;
if ((page_map & X86_PAGE_LARGE) != 0 || shift == 12) {
page_map &= (1ull << 52) - (1ull << shift);
page_map |= address & ((1ull << shift) - 1ull);
if (physical_limit_out)
*physical_limit_out = (page_map | ((1ull << shift) - 1ull)) + 1ull;
return page_map;
}
page_map &= X86_PAGE_FRAME;
}
return UINT64_MAX;
}
bool sm_remote_process_read(pid_t pid, uintptr_t address, void *buffer,
size_t size) {
if (!buffer || size == 0)
return false;
return mdbg_copyout(pid, (intptr_t)address, buffer, size) == 0;
}
bool sm_remote_process_write(pid_t pid, uintptr_t address, const void *buffer,
size_t size) {
if (!buffer || size == 0)
return false;
if ((kernel_get_fw_version() >> 16) <= 0x0820u)
return mdbg_copyin(pid, buffer, (intptr_t)address, size) == 0;
void *probe = malloc(size);
if (!probe)
return false;
bool faulted = sm_remote_process_read(pid, address, probe, size);
free(probe);
if (!faulted)
return false;
uint64_t cr3 = 0;
uint64_t dmap = 0;
if (!resolve_process_paging(pid, &cr3, &dmap))
return false;
const uint8_t *source = (const uint8_t *)buffer;
while (size != 0) {
uint64_t physical_limit = 0;
uint64_t physical =
virtual_to_physical(address, dmap, cr3, &physical_limit);
if (physical == UINT64_MAX || physical_limit <= physical)
return false;
size_t chunk = (size_t)(physical_limit - physical);
if (chunk > size)
chunk = size;
if (kernel_copyin(source, (intptr_t)(dmap + physical), chunk) != 0)
return false;
address += chunk;
source += chunk;
size -= chunk;
}
return true;
}
bool sm_shellcore_remote_resolve(pid_t pid, sm_shellcore_remote_t *remote_out) {
if (pid <= 0 || !remote_out)
return false;
memset(remote_out, 0, sizeof(*remote_out));
const sm_shellcore_firmware_offsets_t *firmware =
sm_shellcore_offsets_for_firmware(kernel_get_fw_version());
if (!firmware) {
log_debug(" [SHELLCORE] unsupported firmware: 0x%08x",
kernel_get_fw_version());
return false;
}
uintptr_t image_base = (uintptr_t)kernel_dynlib_mapbase_addr(pid, 0);
if (!image_base) {
log_debug(" [SHELLCORE] failed to resolve image base for pid=%ld",
(long)pid);
return false;
}
for (int target = 0; target < SM_SHELLCORE_TARGET_COUNT; ++target) {
remote_out->targets[target] = image_base + firmware->targets[target].offset;
}
remote_out->pid = pid;
remote_out->image_base = image_base;
remote_out->offsets = firmware;
log_debug(" [SHELLCORE] resolved fw=%s pid=%ld base=0x%lx", firmware->name,
(long)pid, (unsigned long)image_base);
return true;
}
-845
View File
@@ -1,845 +0,0 @@
#include "sm_platform.h"
#include <stddef.h>
#include <pthread.h>
#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"
#include "sm_scan.h"
#include "sm_scanner.h"
#include "sm_shellcore_protocol.h"
#include "sm_shellcore_service.h"
#include "sm_socket_io.h"
_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.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;
bool started;
bool stop_requested;
int listen_fd;
int client_fd;
// 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;
char releasing_title_id[MAX_TITLE_ID];
} shellcore_service_state_t;
static shellcore_service_state_t g_service = {
.mutex = PTHREAD_MUTEX_INITIALIZER,
.listen_fd = -1,
.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.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') {
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 (!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 {
g_service.prepare_in_progress = true;
if (launch_request)
g_service.launch_pending = true;
}
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_owned_title(const char *title_id) {
bool changed = false;
pthread_mutex_lock(&g_service.mutex);
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;
}
pthread_mutex_unlock(&g_service.mutex);
if (changed)
sm_scanner_wake();
}
static void finish_prepared_title(const char *title_id,
bool cancel_launch) {
pthread_mutex_lock(&g_service.mutex);
if (mount_owner_matches(&g_service.prepared, title_id)) {
g_service.prepare_in_progress = false;
if (cancel_launch)
g_service.launch_pending = false;
}
pthread_mutex_unlock(&g_service.mutex);
}
static int begin_runtime_release(const char *title_id,
bool public_request) {
pthread_mutex_lock(&g_service.mutex);
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(const char *title_id) {
pthread_mutex_lock(&g_service.mutex);
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,
char image_path[MAX_PATH]) {
DIR *dir = opendir(root);
if (!dir)
return false;
bool found = false;
struct dirent *entry;
while (!found && (entry = readdir(dir)) != NULL) {
if (entry->d_name[0] == '.')
continue;
char path[MAX_PATH];
int written = snprintf(path, sizeof(path), "%s/%s", root, entry->d_name);
if (written < 0 || (size_t)written >= sizeof(path))
continue;
struct stat st;
if (stat(path, &st) != 0)
continue;
if (S_ISREG(st.st_mode) &&
is_supported_image_file_path(path, entry->d_name)) {
char mount_point[MAX_PATH];
get_image_mount_point_for_source(path, mount_point);
if (path_matches_root_or_child(runtime_source, mount_point)) {
(void)strlcpy(image_path, path, MAX_PATH);
found = true;
}
continue;
}
if (depth > 0 && S_ISDIR(st.st_mode))
found = find_required_image_layer(path, runtime_source, depth - 1u,
image_path);
}
closedir(dir);
return found;
}
static bool repair_image_chain_for_runtime_source(
const char *title_id, const char *runtime_source, const char *eboot_path,
char image_chain[MAX_IMAGE_CHAIN_DEPTH][MAX_PATH], size_t *image_count) {
while (*image_count < MAX_IMAGE_CHAIN_DEPTH) {
char mounted_root[MAX_PATH];
get_image_mount_point_for_source(image_chain[*image_count - 1u],
mounted_root);
char image_path[MAX_PATH];
if (!find_required_image_layer(mounted_root, runtime_source,
MAX_SCAN_DEPTH, image_path)) {
return false;
}
const char *name = strrchr(image_path, '/');
name = name ? name + 1 : image_path;
bool unstable = false;
if (!maybe_mount_image_file(image_path, name, &unstable))
return false;
(void)strlcpy(image_chain[*image_count], image_path, MAX_PATH);
(*image_count)++;
if (!write_mount_image_chain(title_id, image_chain, *image_count)) {
log_debug(" [SHELLCORE] failed to persist repaired image chain: %s",
title_id);
} else {
log_debug(" [SHELLCORE] image chain repaired: title=%s layers=%zu",
title_id, *image_count);
}
if (path_exists(eboot_path)) {
return true;
}
}
return false;
}
static bool prepare_image_source(const char *title_id,
const char *runtime_source) {
char eboot_path[MAX_PATH];
int written = snprintf(eboot_path, sizeof(eboot_path), "%s/eboot.bin",
runtime_source);
if (written < 0 || (size_t)written >= sizeof(eboot_path))
return false;
if (path_exists(eboot_path))
return true;
if (!is_under_image_mount_base(runtime_source))
return false;
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 false;
// The USB filesystem can become available shortly after WORKING. Avoid
// repeatedly entering the image attach path until its outer layer exists.
if (!path_exists(image_chain[0]))
return false;
for (size_t i = 0; i < image_count; ++i) {
const char *name = strrchr(image_chain[i], '/');
name = name ? name + 1 : image_chain[i];
bool unstable = false;
if (!maybe_mount_image_file(image_chain[i], name, &unstable)) {
log_debug(" [SHELLCORE] image chain mount failed: title=%s layer=%zu "
"path=%s",
title_id, i, image_chain[i]);
return false;
}
}
if (path_exists(eboot_path))
return true;
if (repair_image_chain_for_runtime_source(title_id, runtime_source,
eboot_path, image_chain,
&image_count))
return true;
log_debug(" [SHELLCORE] image chain ready but eboot missing: %s",
eboot_path);
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))
return false;
runtime_mount_state_lock();
bool title_mounted = !runtime_sleep_mode_active() &&
mount_title_nullfs(title_id, source_path);
bool ready = title_mounted;
if (ready)
ready = mount_backport_overlay_for_title(source_path, title_id, NULL, NULL);
if (title_mounted && !ready)
(void)unmount_title_runtime_layers(title_id);
runtime_mount_state_unlock();
return ready;
}
static bool release_title_runtime(const char *title_id,
bool public_request);
static int mount_managed_title_runtime(const char *title_id,
bool allow_unmanaged,
bool launch_request) {
if (!is_supported_game_title_id(title_id))
return EINVAL;
if (runtime_sleep_mode_active())
return EBUSY;
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 =
claim_prepared_title(title_id, launch_request, &claimed);
if (claim_status != 0)
return claim_status;
if (!claimed && is_data_mounted(title_id)) {
finish_prepared_title(title_id, false);
return 0;
}
errno = 0;
if (prepare_title_runtime(title_id, source_path)) {
finish_prepared_title(title_id, false);
return 0;
}
int prepare_errno = errno;
if (claimed)
rollback_prepared_title(title_id);
else
finish_prepared_title(title_id, true);
return prepare_errno == EBUSY ? EBUSY : EIO;
}
bool sm_shellcore_ensure_title_runtime(const char *title_id) {
return mount_managed_title_runtime(title_id, true, true) == 0;
}
static int handle_launch_request(const char *title_id) {
int status = mount_managed_title_runtime(title_id, false, true);
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;
}
int sm_shellcore_mount_title_runtime(const char *title_id) {
if (sm_game_lifecycle_has_active_game())
return EBUSY;
return mount_managed_title_runtime(title_id, false, false);
}
int sm_shellcore_unmount_title_runtime(const char *title_id) {
if (!is_supported_game_title_id(title_id))
return EINVAL;
if (sm_game_lifecycle_has_active_game())
return EBUSY;
char source_path[MAX_PATH];
if (!read_mount_link(title_id, source_path, sizeof(source_path)))
return ENOENT;
bool released = release_title_runtime(title_id, true);
int status = released ? 0 : (errno == EBUSY ? EBUSY : EIO);
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;
}
return true;
}
static bool release_title_runtime(const char *title_id,
bool public_request) {
if (!title_id || title_id[0] == '\0')
return false;
int begin_status = begin_runtime_release(title_id, public_request);
if (begin_status != 0) {
errno = begin_status;
return false;
}
log_debug(" [SHELLCORE] runtime release start: %s", title_id);
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_owned_title(title_id);
end_runtime_release(title_id);
if (!released && release_errno != 0)
errno = release_errno;
return released;
}
bool sm_shellcore_release_title_runtime(const char *title_id) {
return release_title_runtime(title_id, false);
}
void sm_shellcore_service_bind_prepared_app(const char *title_id,
uint32_t app_id) {
if (!title_id || title_id[0] == '\0' || app_id == 0)
return;
pthread_mutex_lock(&g_service.mutex);
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;
}
pthread_mutex_unlock(&g_service.mutex);
}
bool sm_shellcore_service_note_game_exit(const char *title_id) {
if (!title_id || title_id[0] == '\0')
return false;
bool changed = false;
pthread_mutex_lock(&g_service.mutex);
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);
return changed;
}
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-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' ||
g_service.outgoing.title_id[0] != '\0';
pthread_mutex_unlock(&g_service.mutex);
return prepared;
}
bool sm_shellcore_service_title_is_prepared(const char *title_id) {
if (!title_id || title_id[0] == '\0')
return false;
pthread_mutex_lock(&g_service.mutex);
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 = 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 bool resolve_app_exit_title(uint32_t app_id,
char title_id[MAX_TITLE_ID]) {
if (app_id == 0)
return false;
pthread_mutex_lock(&g_service.mutex);
// 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 false;
log_debug(" [SHELLCORE] onAppExit cleanup complete: %s app_id=0x%08X",
title_id, app_id);
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 ||
request.version != SM_SHELLCORE_PROTOCOL_VERSION ||
((request.operation == SM_SHELLCORE_REQUEST_LAUNCH ||
request.operation == SM_SHELLCORE_REQUEST_LAUNCH_FAILED) &&
strnlen(request.payload.title_id, sizeof(request.payload.title_id)) ==
sizeof(request.payload.title_id))) {
response.status = EPROTO;
} else if (request.operation == SM_SHELLCORE_REQUEST_LAUNCH) {
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_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) {
(void)arg;
int listen_fd = g_service.listen_fd;
while (true) {
int client = accept(listen_fd, NULL, NULL);
if (client < 0) {
if (errno == EINTR)
continue;
pthread_mutex_lock(&g_service.mutex);
bool stopping = g_service.stop_requested;
pthread_mutex_unlock(&g_service.mutex);
if (!stopping)
log_debug(" [SHELLCORE] accept failed: %s", strerror(errno));
break;
}
struct timeval timeout = {.tv_sec = 2, .tv_usec = 0};
(void)setsockopt(client, SOL_SOCKET, SO_RCVTIMEO, &timeout,
sizeof(timeout));
(void)setsockopt(client, SOL_SOCKET, SO_SNDTIMEO, &timeout,
sizeof(timeout));
pthread_mutex_lock(&g_service.mutex);
if (g_service.stop_requested) {
pthread_mutex_unlock(&g_service.mutex);
close(client);
break;
}
g_service.client_fd = client;
pthread_mutex_unlock(&g_service.mutex);
handle_client(client);
pthread_mutex_lock(&g_service.mutex);
if (g_service.client_fd == client)
g_service.client_fd = -1;
pthread_mutex_unlock(&g_service.mutex);
close(client);
}
return NULL;
}
bool sm_shellcore_service_start(void) {
if (g_service.started)
return true;
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_SHELLCORE_SOCKET_PATH,
sizeof(address.sun_path));
(void)unlink(SM_SHELLCORE_SOCKET_PATH);
if (bind(fd, (struct sockaddr *)&address, sizeof(address)) != 0 ||
listen(fd, 4) != 0) {
int saved_errno = errno;
close(fd);
(void)unlink(SM_SHELLCORE_SOCKET_PATH);
errno = saved_errno;
return false;
}
g_service.listen_fd = fd;
g_service.client_fd = -1;
g_service.stop_requested = 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.releasing_title_id[0] = '\0';
int rc = pthread_create(&g_service.thread, NULL, service_thread_main, NULL);
if (rc != 0) {
close(fd);
g_service.listen_fd = -1;
(void)unlink(SM_SHELLCORE_SOCKET_PATH);
errno = rc;
return false;
}
g_service.started = true;
return true;
}
void sm_shellcore_service_stop(void) {
if (!g_service.started)
return;
pthread_mutex_lock(&g_service.mutex);
g_service.stop_requested = true;
int fd = g_service.listen_fd;
int client_fd = g_service.client_fd;
g_service.listen_fd = -1;
pthread_mutex_unlock(&g_service.mutex);
if (client_fd >= 0)
(void)shutdown(client_fd, SHUT_RDWR);
if (fd >= 0)
(void)shutdown(fd, SHUT_RDWR);
if (fd >= 0)
close(fd);
pthread_join(g_service.thread, NULL);
pthread_mutex_lock(&g_service.mutex);
g_service.started = false;
g_service.stop_requested = false;
g_service.client_fd = -1;
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.releasing_title_id[0] = '\0';
pthread_mutex_unlock(&g_service.mutex);
(void)unlink(SM_SHELLCORE_SOCKET_PATH);
}
-39
View File
@@ -1,39 +0,0 @@
#include "sm_platform.h"
#include <sys/socket.h>
#include "sm_socket_io.h"
bool sm_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)
return false;
if (count < 0) {
if (errno == EINTR)
continue;
return false;
}
bytes += (size_t)count;
size -= (size_t)count;
}
return true;
}
bool sm_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)
return false;
if (count < 0) {
if (errno == EINTR)
continue;
return false;
}
bytes += (size_t)count;
size -= (size_t)count;
}
return true;
}
+2 -1
View File
@@ -135,7 +135,8 @@ 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_l10n(SM_L10N_DUPLICATE_IGNORED, title_id, path_a, path_b);
notify_system("Duplicate %s ignored:\n%s\nexisting: %s", title_id, path_a,
path_b);
}
void clear_duplicate_title_notification(const char *title_id) {
Loaded 100 of 106 files, more files were not shown because too many files have changed in this diff. Show more