Compare commits

..
144 Commits
Author SHA1 Message Date
Roman Tarasov 98c5248901 Support game icons in external app metadata 2026-10-10 21:28:18 +03:00
Roman Tarasov b66369a1db Notify immediately when an external drive connects 2026-10-10 18:55:01 +03:00
Roman Tarasov f1286f6021 Refresh sandbox storage aliases on filesystem mount events 2026-10-10 18:51:11 +03:00
Roman Tarasov 83fe699aae Restrict sandbox mnt aliases to mounted storage volumes 2026-10-10 18:33:49 +03:00
Roman Tarasov 721f8e3a6b Bound waits for incomplete game crash reports 2026-10-10 18:22:51 +03:00
Roman Tarasov 00d34327f6 Fix USB hotplug events sharing a kevent batch 2026-10-10 18:22:51 +03:00
Roman Tarasov e79d2d128d Add configurable default fakelib mode and localize its controls 2026-10-10 18:10:35 +03:00
Roman Tarasov 0b954376c6 Add per-game fakelib modes and filter libkernel overlays 2026-10-10 16:22:45 +03:00
Roman Tarasov 67303a57f1 Update game sandbox USB mounts on hotplug
Handle USB changes while scan work is deferred by an active game, including custom scan paths and rapid reattach. Preserve busy aliases for cleanup and reuse the scanner watcher registry for suspension and shutdown.
2026-10-10 15:55:12 +03:00
Roman Tarasov b7c0b2d26a Expose system fonts read-only in game sandboxes
Refs #132
2026-10-10 15:38:35 +03:00
Roman Tarasov 532a3f65ad Fix Simplified Chinese web UI terminology
Refs #133
2026-10-10 15:38:34 +03:00
Roman Tarasov c2e605581f Report fatal signal reasons and crash addresses from klog
Parse signal-format crash reports through their section separators, capture the fault address, RIP and first backtrace frame, and preserve legacy exception reports. Defer the generic crash notification for incomplete fatal headers, retain PID filtering and the original one-minute monitoring deadline. Verified against the supplied PPSA23732 crash log and host regressions with ASan/UBSan.
2026-10-10 14:30:52 +03:00
Roman Tarasov fc916ad5fd Restore lost ShellCore hooks after resume and before registration
Validate the existing bridge and original target bytes before repairing lost hooks in a stopped ShellCore. Retain rollback ownership on partial failures, support process replacement, and disable recovery when stopping. Check once after resume and on registration without adding polling or scanner deadlines. Refs #145, #147; adapts the recovery approaches from PR #148 and #126.
2026-10-10 13:57:56 +03:00
Roman Tarasov 451bddba6b Fix PPR fakelib cleanup when lookup returns no errno
Skip absent cleanup targets and the PPR lookup failure without errno silently. Preserve real lookup/unlink errors and use EIO for deletion failures without errno. Refs #146; adapts PR #148.
2026-10-10 13:57:55 +03:00
Roman Tarasov 3ec59327b5 Remove legacy kstuff autopause 2026-10-10 13:19:00 +03:00
Roman Tarasov 4cde42a229 Show game versions in web cards and API metadata 2026-10-05 01:24:35 +03:00
Roman Tarasov 4c5673da2c Fix web game icons on extended storage 2026-10-05 01:10:10 +03:00
Roman Tarasov 3c3bb484c6 Fix silent skips for missing PPR fakelib cleanup targets 2026-10-05 00:49:20 +03:00
Roman Tarasov eba3f11142 Add size unit labels for all web UI languages 2026-10-03 20:53:19 +03:00
Roman Tarasov 77752bb9e7 Merge origin/1.7 and preserve operation error translations 2026-10-03 19:30:11 +03:00
Roman Tarasov 5c40b1a7bc Fix missing-source reconciliation and localize operation errors 2026-10-03 19:26:53 +03:00
Drakmor d7e35e6ce9 Merge pull request #120 from m2k7m/1.7
Update Arabic translations
2026-10-03 14:34:47 +03:00
Roman Tarasov 900d8a137c Fix storage overview duplicates and show the internal drive 2026-10-03 14:28:59 +03:00
Roman Tarasov 93d9f4e285 Show installed PKG games and manage PS5 fakelib through the API and web UI 2026-10-03 14:22:35 +03:00
Roman Tarasov 6d9f808e8d Add per-title fakelib exclusions to runtime config 2026-10-03 13:47:04 +03:00
Roman Tarasov d70b43e5d4 Remove incompatible fakelib libraries before mounting PPR PKG backports 2026-10-03 13:37:27 +03:00
Roman Tarasov cb6d2bece1 Support LAPY homebrew title IDs in game lifecycle 2026-10-03 13:22:21 +03:00
Roman Tarasov 9a97155738 Mounting /data and /mnt into the game sandbox 2026-10-03 13:13:14 +03:00
Mahdi KM d0f3411c79 Update Arabic translations for ShadowMount library interface and update byte unit labels 2026-10-02 14:13:08 +03:00
Mahdi KM 875065dd7a Update Arabic translations for various messages in ar_sa.inc 2026-10-02 12:33:36 +03:00
Roman Tarasov f0d15ffc46 Reject incomplete fakelib cache signatures and copies 2026-10-01 16:17:21 +03:00
Roman Tarasov c037550551 Reject incomplete scanner directory walks 2026-10-01 16:17:20 +03:00
Roman Tarasov 5c8cfd2803 Preserve config and autotune data on read failures 2026-10-01 16:17:19 +03:00
Roman Tarasov d5364cfaae Skip launcher icon installation when API is disabled 2026-10-01 15:52:17 +03:00
Roman Tarasov 5f3c7fd1c2 Restore legacy 1.6 mount defaults for PFSC containers 2026-10-01 15:47:40 +03:00
Roman Tarasov d370825446 Preserve backport discovery with custom scan roots 2026-10-01 15:29:40 +03:00
Roman Tarasov cd0e11ceb7 Keep generated assets for incremental builds 2026-09-23 21:19:53 +03:00
Roman Tarasov 665dfe3f82 Reject recursive storage transfers through path aliases 2026-09-23 21:19:53 +03:00
Roman Tarasov 4677c244f3 Bound stale scanner event draining 2026-09-23 21:19:53 +03:00
Roman Tarasov 5ef95df417 Honor zero and exact stability wait thresholds 2026-09-23 21:19:53 +03:00
Roman Tarasov 13a8223b1e NSFS backports for PS5 PKG 2026-09-22 00:05:16 +03:00
Roman Tarasov 320d1322d2 Stop plaintext PPR games before suspend 2026-09-21 18:17:46 +03:00
Roman Tarasov 71dd3d3cd2 Offer scanned storage roots as destinations 2026-09-21 17:58:21 +03:00
Roman Tarasov b8ad8cc65a Harden web remount and destination state 2026-09-21 17:55:13 +03:00
Roman Tarasov 5246614ebc Increase storage transfer throughput 2026-09-21 17:52:29 +03:00
Roman Tarasov cb047454d6 Add available storage destination picker 2026-09-21 17:51:39 +03:00
Roman Tarasov 04d4f314ab Refine web mount actions for game sources 2026-09-21 17:47:38 +03:00
Roman Tarasov 738d51435f Remove unused localization key and refresh reference comments 2026-09-21 17:01:39 +03:00
Roman Tarasov b8486358e2 Use synchronous HTTP for AMPR updates and serialize cancellation 2026-09-21 17:01:20 +03:00
Roman Tarasov ae878d70c8 Parse game metadata with json-c and preserve transient failures 2026-09-21 17:01:09 +03:00
Roman Tarasov 733f60dab3 Preserve scanner control events and harden shutdown wakeups 2026-09-21 17:00:52 +03:00
Roman Tarasov 07fdf09e06 Reject unsafe copy paths and report directory read failures 2026-09-21 17:00:31 +03:00
Roman Tarasov 69cf153a3c Distinguish skipped backport overlays from mount failures 2026-09-21 17:00:17 +03:00
Roman Tarasov a67ff8fc4a Use private mount snapshots and preserve unknown mount state 2026-09-21 16:59:46 +03:00
Roman Tarasov 4102fa7aa5 Docs 2026-09-20 23:38:25 +03:00
Roman Tarasov d71bacfbc0 Add Kstuff and ShellCore offsets for PS5 firmware 13.00–13.60 2026-09-20 23:28:12 +03:00
Roman Tarasov 072c17b050 Use full ShellCore bridge cave 2026-09-11 23:49:18 +03:00
Roman Tarasov 7bf0a6efaf Remove kstuff mount hook toggling 2026-09-11 23:41:49 +03:00
Roman Tarasov 2424c98ed4 Mount package fakelib before game spawn 2026-09-11 04:53:03 +03:00
Roman Tarasov c08e207755 Expand ShellCore bridge firmware support 2026-09-11 04:51:52 +03:00
Roman Tarasov 3d55f1c4c7 Resume mount hooks when game process exits 2026-09-11 03:13:26 +03:00
Roman Tarasov 18d0f141e7 Hook ShellCore immediately before game spawn 2026-09-11 02:59:41 +03:00
Roman Tarasov 86c67639f6 Avoid ShellCore execute-only data reads 2026-09-11 01:37:01 +03:00
Roman Tarasov aa69be7892 Mount fakelib before game process spawn 2026-09-11 00:35:24 +03:00
Roman Tarasov 87fbe72b46 Pause mount hooks while games run 2026-09-10 22:15:31 +03:00
Roman Tarasov 6b7dc2ced5 fix: wake runtime waiters when STOP file is detected 2026-09-06 23:08:40 +03:00
Roman Tarasov 7b3c0bd9ba fix: publish runtime config through synchronized snapshots 2026-09-06 23:06:58 +03:00
Roman Tarasov 6488632c73 fix: preserve active fakelib overlays across config reload 2026-09-06 22:57:36 +03:00
Roman Tarasov 5d3ebd8caa Repair backport permissions during scans 2026-09-06 22:14:09 +03:00
Roman Tarasov a57696a512 Wait for successful bridge mprotect before hooking 2026-09-04 06:18:47 +03:00
Roman Tarasov 0d7544a5d6 Add translations for all supported locales 2026-09-03 03:25:43 +03:00
Roman Tarasov ad0ca65f9a Add missing-app cleanup and AMPR update settings 2026-09-03 03:10:18 +03:00
Roman Tarasov 297b9003f2 Add ShadowMount+ Shell UI launcher 2026-09-03 03:00:14 +03:00
Roman Tarasov dc363e69f1 Show web control address in startup notification 2026-09-02 02:22:27 +03:00
Roman Tarasov 5d4f48cedd Keep bridge pages resident and recover stale hooks 2026-09-02 02:12:20 +03:00
Roman Tarasov 5b7ab6ef8c Expand web API and add embedded library interface 2026-09-02 01:54:06 +03:00
Roman Tarasov f81eb60e94 Improve multilingual library controls and game actions (web) 2026-09-02 00:01:42 +03:00
Roman Tarasov 9e6b7165a0 ci/cd 2026-09-01 13:21:55 +03:00
Roman Tarasov 960204baa0 Add resilient async game library management 2026-09-01 13:16:19 +03:00
Roman Tarasov f560898b2c Add HTTP game management API 2026-09-01 02:32:15 +03:00
Roman Tarasov 3a167431a3 Make trampoline execution safe 2026-08-31 19:02:48 +03:00
Roman Tarasov ff0bc74468 Warn about USB clusters larger than 64 KB 2026-08-28 23:25:57 +03:00
BlackBearReloaded 5b9816dd08 lifecycle: support FAKE title IDs 2026-08-28 21:42:11 +03:00
Roman Tarasov f3df8f040d Pause background services during system sleep 2026-08-28 21:39:00 +03:00
Roman Tarasov b564c74611 Use optimized exFAT, UFS/FFPKG and PFS/FFPFS mount profiles only for direct source paths under /data or /user 2026-08-28 19:59:26 +03:00
Roman Tarasov 7671106a32 Add USB connection and scan notifications 2026-08-25 23:51:17 +03:00
Roman Tarasov b1a0dce78d Separate SQLite operations from snapshot cache state 2026-08-25 22:07:42 +03:00
Roman Tarasov 01ea403b88 Prevent detach and cache invalidation reuse races 2026-08-25 15:21:21 +03:00
Roman Tarasov 06576fabe3 Make mount and unmount lifecycle race-safe 2026-08-25 14:53:48 +03:00
Roman Tarasov 01f5cf7182 Enforce one-to-one source and mount mappings 2026-08-25 13:32:37 +03:00
Roman Tarasov 640d8bdd59 Retain attached devices until detach completes 2026-08-25 13:23:35 +03:00
Roman Tarasov bc34a3bb26 Avoid aborting HTTP request under updater mutex 2026-08-25 13:00:23 +03:00
Roman Tarasov 3638cdaae5 Update translations 2026-08-24 22:44:30 +03:00
Roman Tarasov 3190100de8 Keep fakelib2 exclusive from global and emulator overlays 2026-08-24 22:06:20 +03:00
Roman Tarasov 757f4514cc Improve startup and fan target notifications 2026-08-24 20:56:20 +03:00
Roman Tarasov b3368dca15 Rename nested PFS index cache option 2026-08-24 20:38:37 +03:00
Roman Tarasov a594117aaf Harden game process handoff lifecycle 2026-08-24 20:20:40 +03:00
Roman Tarasov 47a8196a8c Add localized notification for ShellCore hook failure 2026-08-24 20:03:29 +03:00
Roman Tarasov 9345a844b9 Refactor ShellCore bridge to use remote dynamic memory 2026-08-24 19:45:17 +03:00
Roman Tarasov 24a421b350 Fix game process handoff and remount fakelib across ExitSpawn/LoadExec. 2026-08-19 15:45:00 +03:00
Roman Tarasov 599d5d04f3 Invalidate image cache after build or configuration changes 2026-08-19 00:12:40 +03:00
Roman Tarasov f5c7870583 Add cached global fakelib overlays 2026-08-18 23:54:05 +03:00
Roman Tarasov 718f02017b AMPR updater 2026-08-18 23:26:32 +03:00
Roman Tarasov 3f49f60cec Add cached emulator updates 2026-08-18 21:57:00 +03:00
Roman Tarasov 5ff85005bf Refine legacy image mount profiles 2026-08-17 20:56:27 +03:00
Roman Tarasov b00536960d Add per-image legacy mount profiles 2026-08-14 12:29:48 +03:00
Roman Tarasov a4a35af5f0 Rename fan target auto mode to system 2026-08-10 22:24:18 +03:00
Roman Tarasov 3bff288a86 Stricter exfat mounting check 2026-08-10 21:17:49 +03:00
Roman Tarasov 9485f22ddc Disable forced detach for MD devices 2026-08-10 01:26:05 +03:00
Roman Tarasov 75b310ee25 Defer runtime unmount until sandbox removal 2026-08-10 01:13:30 +03:00
Roman Tarasov 496ed48e33 Add exFAT LVD image type fallback 2026-08-09 23:53:13 +03:00
Roman Tarasov 4f48237d93 Add configurable fan temperature target 2026-08-08 12:41:14 +03:00
Roman Tarasov 8d888c5a25 Defer busy image unmounts instead of forcing teardown 2026-08-08 10:33:13 +03:00
Roman Tarasov 3c484e68ab Handle asynchronous LVD/MD detach safely 2026-07-23 23:57:27 +03:00
Roman Tarasov 7da387fc12 Updated PFS cache description 2026-07-18 21:19:10 +03:00
Roman Tarasov a393737423 Diagnosing backport mounting problems 2026-07-18 21:19:10 +03:00
Roman Tarasov 6f64551b42 Optimized mount parameters and enabled DDR5 cache for FFPFSC 2026-07-18 21:19:10 +03:00
Roman Tarasov 6e58123275 l10n 2026-07-16 23:49:11 +03:00
Roman Tarasov 3aa62a3728 Support seamless game switching 2026-07-16 22:33:54 +03:00
Roman Tarasov 38bb6790db Speed up USB game recovery after resume 2026-07-16 22:02:09 +03:00
Roman Tarasov be92afa71d Report fatal signal details for tracked games 2026-07-16 21:13:41 +03:00
Roman Tarasov fa8423d427 Optimize scanner file descriptor usage 2026-07-16 12:53:53 +03:00
Roman Tarasov 3d8d248336 Add optional persistent image mounting 2026-07-16 10:22:13 +03:00
Roman Tarasov 22bacfe168 Сount games from reusable image index entries 2026-07-16 02:18:44 +03:00
Roman Tarasov 4085fcd8ca Fix ShellCore runtime release after app exit 2026-07-16 01:57:28 +03:00
Roman Tarasov eb3c611ebf Protect games with installed DLC from automatic removal 2026-07-16 00:04:02 +03:00
Roman Tarasov 9f0242044d Speed up game metadata staging and registration 2026-07-15 23:49:21 +03:00
Roman Tarasov c4e9839fe5 Add automatic removal of unavailable games 2026-07-15 23:31:30 +03:00
Roman Tarasov 73b26ba3fb Add public HTTP/JSON API for runtime control 2026-07-15 22:10:30 +03:00
Roman Tarasov 5691f25ece Make kstuff automount handoff shutdown-safe 2026-07-14 22:55:15 +03:00
Roman Tarasov 9cda9717ea Defer runtime teardown to ShellCore workspace exit 2026-07-14 22:38:07 +03:00
Roman Tarasov e24a444147 Harden managed mount teardown 2026-07-14 22:37:49 +03:00
Roman Tarasov 03ad6d2e45 Clean up static analysis findings 2026-07-13 03:24:14 +03:00
Roman Tarasov 6d59555b66 Fix runtime mount lifecycle and shutdown 2026-07-13 03:24:10 +03:00
Roman Tarasov 7300dc1830 Harden runtime boundary validation 2026-07-13 03:24:05 +03:00
Roman Tarasov ad5afc5689 Reduce scanner watchers and release USB watches during sleep 2026-07-13 02:38:50 +03:00
Roman Tarasov 6d8622c829 Support up to 8192 games across 2048 images 2026-07-13 02:16:16 +03:00
Roman Tarasov 9f85a3b421 Use SceShellCore bridge for title registration 2026-07-13 01:37:49 +03:00
Roman Tarasov 0fb241cba5 Handle game mounts through SceShellCore lifecycle 2026-07-13 01:37:49 +03:00
Roman Tarasov 09589b7ae2 Add SceShellCore hooks and runtime image mounts 2026-07-13 01:37:43 +03:00
Roman Tarasov 8566c0294c Revert "Revert "Attempting to restore occupied mount points for nullfs""
This reverts commit 16873e01f2.
2026-06-28 16:53:42 +03:00
Roman Tarasov 56dd338abd Prefer fakelib2 over fakelib for game overlays
Check app0/fakelib2 before resolving the legacy per-game fakelib
source. Fall back to app0/fakelib when fakelib2 is missing or is
not a directory.
2026-06-28 16:49:30 +03:00
Drakmor 090723879c Update README.md 2026-06-07 02:43:46 +03:00
Roman Tarasov 16873e01f2 Revert "Attempting to restore occupied mount points for nullfs"
This reverts commit c41c3eeba8.
2026-06-03 21:30:59 +03:00
144 changed files with 30805 additions and 3499 deletions

No files matched your search

+4
View File
@@ -10,3 +10,7 @@ mkexfat_macos.sh
mkufs2.sh
New-OsfExfatImage.ps1
README.md
docs/api.md
docs/openapi.yaml
tools/api_test.py
web/index.html
+38
View File
@@ -47,6 +47,7 @@ jobs:
automake \
build-essential \
clang-18 \
cmake \
curl \
git \
libarchive-tools \
@@ -56,6 +57,8 @@ jobs:
meson \
pacman-package-manager \
pkg-config \
python3 \
python3-yaml \
xxd \
zip
@@ -65,12 +68,47 @@ 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 zlib package
working-directory: pacbrew-repo/zlib
run: |
makepkg -c -f
sudo pacman --noconfirm -U ./ps5-payload-*.pkg.tar.gz
- name: Build PS5 libpng package
working-directory: pacbrew-repo/libpng
run: |
makepkg -c -f
sudo pacman --noconfirm -U ./ps5-payload-*.pkg.tar.gz
- name: Build PS5 libmicrohttpd package
working-directory: pacbrew-repo/libmicrohttpd
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; yaml.safe_load(pathlib.Path("docs/openapi.yaml").read_text(encoding="utf-8"))'
- name: Build payload
run: |
make clean all PS5_SCE_STUBS_DIR="$GITHUB_WORKSPACE/ps5-sdk/sce_stubs"
+3
View File
@@ -1,5 +1,8 @@
*.o
*.elf
*.so
*.pyc
__pycache__/
daemon.lock
debug.log
notify.txt
+39 -7
View File
@@ -2,33 +2,45 @@ PS5_PAYLOAD_SDK ?= /opt/ps5-payload-sdk
include $(PS5_PAYLOAD_SDK)/toolchain/prospero.mk
VERSION_TAG := $(shell git describe --abbrev=6 --dirty --always --tags 2>/dev/null || echo unknown)
BUILD_TIME := $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
# Standard Flags (No extra libraries)
# Compiler and dependency flags
HOMEBREW_ROOT := $(PS5_PAYLOAD_SDK)/target/user/homebrew
HOMEBREW_CFLAGS := -I$(HOMEBREW_ROOT)/include
MHD_LIB := $(HOMEBREW_ROOT)/lib/libmicrohttpd.a
PNG_LIB := $(HOMEBREW_ROOT)/lib/libpng16.a
ZLIB_LIB := $(HOMEBREW_ROOT)/lib/libz.a
CFLAGS := -O3 -flto=thin -DNDEBUG -ffunction-sections -fdata-sections -Wall -Wextra -Wstrict-prototypes -Wmissing-prototypes -Werror=strict-prototypes -Werror=missing-prototypes -D_BSD_SOURCE -std=gnu11 -Iinclude -Isrc
CFLAGS += -DSHADOWMOUNT_VERSION=\"$(VERSION_TAG)\"
# Linker
LDFLAGS := -flto=thin -Wl,--gc-sections
# Standard Libraries Only
LIBS := -lSceNotification -lSceSystemService -lSceUserService -lSceAppInstUtil -lsqlite3
# Libraries
LIBS := -lSceNotification -lSceSystemService -lSceUserService -lSceAppInstUtil -lSceNet -lSceSsl -lSceHttp -lsqlite3 $(HOMEBREW_ROOT)/lib/libjson-c.a $(MHD_LIB) $(PNG_LIB) $(ZLIB_LIB) -lpthread -lm
PS5_SCE_STUBS_DIR ?= $(PS5_PAYLOAD_SDK)/src/sce_stubs
KERNEL_SYS_STUB_SO := src/libkernel_sys_ext.so
KERNEL_SYS_STUB_SRCS := $(PS5_SCE_STUBS_DIR)/libkernel_sys.c src/libkernel_sys_ext.c
ASSET_SRCS := src/notify_icon_asset.c src/config_ini_example_asset.c
ASSET_SRCS := src/notify_icon_asset.c src/config_ini_example_asset.c src/web_index_asset.c
ASSET_SRCS += src/shell_icon_param_asset.c
SRCS := src/main.c $(wildcard src/sm_*.c) $(ASSET_SRCS)
OBJS := $(SRCS:.c=.o)
ASM_SRCS := src/sm_shellcore_bridge.S
OBJS := $(SRCS:.c=.o) $(ASM_SRCS:.S=.o)
HEADERS := $(wildcard include/*.h)
L10N_CATALOGS := $(wildcard include/lang/*.inc)
# Targets
all: shadowmountplus.elf
usb-info.elf: tools/usb_info.c
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< -lSceNotification
$(PS5_PAYLOAD_SDK)/bin/prospero-strip --strip-all $@
# Build Daemon
shadowmountplus.elf: $(OBJS) $(KERNEL_SYS_STUB_SO)
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(OBJS) $(KERNEL_SYS_STUB_SO) $(LIBS)
$(PS5_PAYLOAD_SDK)/bin/prospero-strip --strip-all $@
rm -f src/notify_icon_asset.c src/config_ini_example_asset.c
$(KERNEL_SYS_STUB_SO): $(KERNEL_SYS_STUB_SRCS)
test -f "$(PS5_SCE_STUBS_DIR)/libkernel_sys.c"
@@ -40,8 +52,28 @@ src/notify_icon_asset.c: smp_icon.png
src/config_ini_example_asset.c: config.ini.example
xxd -i $< > $@
src/web_index_asset.c: web/index.html
xxd -i $< > $@
src/shell_icon_param_asset.c: assets/shell_icon_param.json
xxd -i $< > $@
src/sm_api_service.o src/sm_icon_thumb.o src/sm_gameinfo.o: CFLAGS += $(HOMEBREW_CFLAGS)
src/main.o src/sm_image_index.o: CFLAGS += -DSHADOWMOUNT_BUILD_TIME=\"$(BUILD_TIME)\"
src/main.o src/sm_image_index.o: FORCE
src/sm_l10n.o: $(L10N_CATALOGS)
src/sm_ampr_updater.o: src/sm_ampr_ca.inc
src/sm_shellcore_offsets.o: src/sm_shellcore_offsets.inc
.PHONY: FORCE
FORCE:
src/%.o: src/%.c $(HEADERS)
$(CC) $(CFLAGS) -c -o $@ $<
src/%.o: src/%.S
$(CC) $(CFLAGS) -c -o $@ $<
clean:
rm -f shadowmountplus.elf kill.elf src/*.o $(KERNEL_SYS_STUB_SO) src/notify_icon_asset.c src/config_ini_example_asset.c
rm -f shadowmountplus.elf usb-info.elf api-test.elf kill.elf src/*.o $(KERNEL_SYS_STUB_SO) $(ASSET_SRCS)
+5 -21
View File
@@ -46,9 +46,9 @@
-----
- This script does NOT auto-elevate. Start PowerShell as Administrator.
- Filesystem is always exFAT.
- Cluster size is auto-selected:
- large-file sets: 65536
- small/mixed-file sets: 32768
- Cluster size is fixed at 65536 bytes for the LVD type-5/BFS sdimg path.
- Keep ShadowMount's LVD exFAT logical sector at 512 bytes; 65536 is the
filesystem allocation unit and LVD secondary unit, not sector_size.
#>
[CmdletBinding()]
@@ -197,22 +197,6 @@ function Get-LogicalDriveFileSystem([string]$driveLetter) {
return ""
}
function Get-OptimalExfatClusterSize([string]$dir) {
[Int64]$largeFileThreshold = 1MB
$files = @(Get-ChildItem -LiteralPath $dir -Recurse -File -Force)
if ($files.Count -eq 0) { return 32768 }
[Int64]$rawFileBytes = 0
foreach ($f in $files) {
$rawFileBytes += [Int64]$f.Length
}
[Int64]$avgFileBytes = [Int64]($rawFileBytes / [Int64]$files.Count)
if ($avgFileBytes -ge $largeFileThreshold) { return 65536 }
return 32768
}
function Format-AllocationUnitArg([int]$clusterSize) {
if ($clusterSize -ge 1MB -and ($clusterSize % 1MB) -eq 0) {
return "{0}M" -f ($clusterSize / 1MB)
@@ -308,7 +292,7 @@ if (Test-Path $ImagePath) {
[Int64]$expectedBytes = 0
[string]$osfSizeArg = $null
[int]$ExfatClusterSize = Get-OptimalExfatClusterSize -dir $SourceDir
[int]$ExfatClusterSize = 65536
[bool]$sizeProvided = -not [string]::IsNullOrWhiteSpace($Size)
[int]$TargetClusterSize = $ExfatClusterSize
@@ -354,7 +338,7 @@ try {
if ($CreateEmptyAndMount) {
Write-Host "[2/2] Done. Empty image is mounted at $dest."
Write-Host "Manual steps:"
Write-Host " 1) Format $dest as exFAT (recommended cluster: 64KB for large-file sets, 32KB for small/mixed sets)."
Write-Host " 1) Format $dest as exFAT with a 64KB allocation unit (required for the LVD/BFS fast-path profile)."
Write-Host " 2) Copy contents of '$SourceDir' to $dest."
Write-Host " 3) Dismount: `"$osf`" -d -m $MountPoint"
return
+145 -50
View File
@@ -11,7 +11,7 @@
**ShadowMountPlus** is a fully automated, background "Auto-Mounter" payload for Jailbroken PlayStation 5 consoles. It streamlines the game mounting process by eliminating the need for manual configuration or external tools (such as DumpRunner or Itemzflow). ShadowMountPlus automatically detects, mounts, and installs game dumps from both **internal and external storage**.
**Compatibility:** Supports all Jailbroken PS5 firmwares running **[Kstuff-lite v1.07+](https://github.com/EchoStretch/kstuff-lite)**.
**Compatibility:** Supports Jailbroken PS5 firmwares running **[Kstuff-lite v1.07+](https://github.com/EchoStretch/kstuff-lite)**, including firmware 2.00.
## 💜 Support Development
@@ -54,41 +54,85 @@ 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`)
- `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+`)
- `persistent_image_mounts=1|0` (`1` keeps discovered images mounted between game launches; default: `0`)
- `app_install_all=1|0` (`1` stages new titles and submits them through the stock batch `sceAppInstUtilAppInstallAll`; default: `0` on every firmware. On FW `12.00+`, the default per-title path is provided by the SceShellCore TitleDir bridge.)
- `auto_remove_missing_games=1|0` (`1` automatically removes games from the system library when their source is no longer available; default: `0`)
- `auto_remove_games_with_dlc=1|0` (`1` allows automatic removal of games that have installed DLC; default: `0`)
- `auto_remove_missing_delay_seconds=<1..86400>` (how long a source must remain unavailable before removal; default: `300`)
- `image_ro=<image_filename>` (repeatable; force read-only mode for this image filename)
- `image_rw=<image_filename>` (repeatable; force read-write mode for this image filename)
- `image_sector=<image_filename>:<sector_size>` (repeatable; force sector size for this image filename)
- `image_sector=<image_filename>:<sector_size>` (repeatable; force the logical device sector size, not the filesystem cluster size)
- `scan_depth=<1..2>` (`1` = scan only first-level subfolders, `2` = also scan one additional nested level; default: `1`)
- `watch_subdirs=1|0` (`1` keeps vnode watchers on scan root subdirectories; default: `0`)
- `watch_image_files=1|0` (`1` keeps vnode watchers on discovered image files; default: `1`)
- `recursive_scan=1|0` (deprecated compatibility key; `1` forces `scan_depth=2`)
- `scan_interval_seconds=<1..3600>` (full scan loop interval; default: `15`)
- `stability_wait_seconds=<0..3600>` (minimum source age before processing; default: `10`)
- `exfat_backend=lvd|md` (default: `lvd`)
- `ufs_backend=lvd|md` (default: `lvd`)
- `nested_pfs_index_cache=1|0` (request the containing PFS compressed-file index cache before attaching a nested image; default: `0`)
- `backport_fakelib=1|0` (`1` mounts sandbox `fakelib` overlays for running games; default: `1`)
- `global_fakelib=1|0` (`1` enables the global fakelib overlay when the folder exists; default: `1`)
- `fakelib_default_mode=full|emulators|disabled` (mode for games without an individual override; default: `full`; also available in web settings)
- `fakelib_mode=<TITLE_ID>:default|full|emulators|disabled` (per-game mode, repeatable; `default` restores inheritance; shared limit of 128 explicit title rules)
- `fakelib_exclude=<TITLE_ID>` (legacy alias for `disabled`; repeatable, up to 128 titles; disables all fakelib overlays for matching PPSA/CUSA/LAPY/FAKE IDs; default: no exclusions)
- `update_emulators=1|0` (`1` updates matching emulator files for folder/image games; installed PKGs and `fakelib2` are excluded; default: `1`)
- `emulators_path=<absolute_path>` (folder containing emulator update files; default: `/data/shadowmount/emus`)
- `auto_update_ampr=1|0` (check for a new `libSceAmpr.sprx` 30 seconds after startup and every four hours; default: `0`)
- `ampr_update_url=<http_or_https_url>` (AMPR emulator download URL; default: `https://github.com/drakmor/ampr_emu/releases/latest/download/libSceAmpr.sprx`)
- `global_fakelib=1|0` (`1` enables the global fakelib overlay when the folder exists; `fakelib2` remains exclusive; default: `1`)
- `global_fakelib_path=<absolute_path>` (global fakelib folder; default: `/data/shadowmount/fakelib`)
- `global_fakelib_priority=game|global` (overlay priority when both global and game fakelib exist; default: `game`)
- `global_fakelib_exclude=<TITLE_ID>` (repeatable; disables the global fakelib overlay for matching titles)
- `kstuff_game_auto_toggle=1|0` (`1` pauses kstuff after tracked game launches and resumes it on stop; default: `1`)
- `kstuff_crash_detection=1|0` (`1` enables crash monitoring and pause-delay autotune updates; default: `1`)
- `kstuff_pause_delay_image_seconds=<0..3600>` (delay before pausing kstuff for image-backed launches; default: `25`)
- `kstuff_pause_delay_direct_seconds=<0..3600>` (delay before pausing kstuff for direct/non-image launches; default: `15`)
- `kstuff_no_pause=<TITLE_ID>` (repeatable; keeps kstuff enabled for matching titles)
- `kstuff_delay=<TITLE_ID>:<0..3600>` (repeatable; per-title pause delay override, last matching rule wins)
- `/data/shadowmount/autotune.ini` may also provide per-title pause-delay overrides with highest priority:
- `kstuff_delay=<TITLE_ID>:<0..3600>`
- `<TITLE_ID>=<0..3600>`
- `image_sector=<image_filename>:<sector_size>`
- `fan_target_temperature=system|<50..91>` (automatic fan-controller target in degrees Celsius, applied when a game starts; `system` leaves fan control to the console and is the default)
- `/data/shadowmount/autotune.ini` can override `image_sector` rules from `config.ini` using `image_sector=<image_filename>:<sector_size>`.
- `scanpath=<absolute_path>` (can be repeated on multiple lines; default: built-in scan path list below)
- `lvd_exfat_sector_size=<value>` (default: `512`)
- `lvd_ufs_sector_size=<value>` (default: `4096`)
- `lvd_pfs_sector_size=<value>` (default: `32768`)
- `lvd_pfs_sector_size=<value>` (default: `4096`; the optimized profile uses a `65536`-byte LVD mapping unit)
- `md_exfat_sector_size=<value>` (default: `512`)
- `md_ufs_sector_size=<value>` (default: `512`)
- `md_ufs_sector_size=<value>` (default: `4096`)
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` |
| Ukrainian | `uk-UA` |
| Vietnamese | `vi-VN` |
The public API routes and JSON schemas are documented in [docs/api.md](docs/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).
@@ -101,14 +145,21 @@ mount_read_only=1
image_rw=PPSA1234-my-image.ffpfs
image_rw=MYGame 123.exfat
image_ro=legacy_dump.ffpkg
image_sector=MYGame 123.exfat:65536
image_sector=legacy-512-sector.exfat:512
```
Persistent image mount behavior:
- With `persistent_image_mounts=0` (default), backing images are mounted when a game starts and released after it exits.
- With `persistent_image_mounts=1`, images discovered during scans remain mounted between launches. Per-title runtime and backport layers are still mounted only when needed.
- USB-backed images are always released during suspend and mounted again by the resume scan.
- Persistent mode consumes one backing device per mounted image layer and is intended for libraries that fit the available LVD/MD device capacity.
Per-image sector override behavior:
- Match is done by image file name (without path).
- `image_sector` in `/data/shadowmount/autotune.ini` has the highest priority for matching image files.
- If no per-image rule matches, the backend-specific global sector size defaults are used.
- When image validation fails because the mounted file-system cluster size is smaller than the selected device sector size, ShadowMountPlus writes `image_sector=<image_filename>:<cluster_size>` into `/data/shadowmount/autotune.ini` and asks you to try mounting again.
- For the exFAT LVD/BFS fast path, leave the logical sector at its default `512`. The required `65536` values are the exFAT allocation unit and the internal LVD `secondary_unit`; do not set `image_sector=...:65536` to request this mode.
Scan path behavior:
- If at least one `scanpath=...` is present, only those custom paths are used.
@@ -118,6 +169,7 @@ Scan path behavior:
- If `recursive_scan=1` is set, ShadowMount+ forces `scan_depth=2`.
- Full scan loop runs every `scan_interval_seconds` (default: `15`).
- Sources newer than `stability_wait_seconds` are deferred until stable (default: `10`).
- With `auto_remove_missing_games=1`, a game is removed from the system library only after its source remains unavailable for `auto_remove_missing_delay_seconds`. If the source becomes available again before removal, the pending removal is cancelled.
- Direct folder installs use `<game>/sce_sys` for this check; image and backport sources use the target path itself.
Backport overlay behavior:
@@ -125,34 +177,35 @@ Backport overlay behavior:
- `<scanpath>/backports/<TITLE_ID>/`
- The `backports` folder is ignored during normal game scanning.
- A backport is applied automatically to the matching mounted game from any configured scan path.
- If multiple scan paths provide the same title backport, the game's own scan path wins; otherwise scan path order is used.
- If `/mnt/sandbox/<TITLE_ID>_XXX/app0/fakelib` exists while the game is running, ShadowMount+ mounts it into that game's sandbox `common/lib`.
- If `global_fakelib=1` and `global_fakelib_path` exists as a directory, ShadowMount+ also mounts that folder into the same sandbox `common/lib`.
- When both global and per-game fakelib exist, the default gives the game's own `fakelib` priority by mounting `/data/shadowmount/fakelib` first and then the game-specific fakelib.
- `global_fakelib_priority=global` reverses that priority.
- `/data/homebrew/backports/<TITLE_ID>/` remains available as a fallback even with custom scan paths; this does not add `/data/homebrew` to normal game scanning.
- For an installed PKG, a matching external `backports/<TITLE_ID>` directory is added to the ready package `app0` through NSFS immediately before process spawn. Files use plain redirects and missing directory subtrees use directory overlays. Up to 256 redirect entries are supported per package sandbox. Redirect state follows the NSFS mount identity, so redirects are rebuilt when ShellCore recreates a sandbox under the same path. Missing backports and non-NSFS sandboxes are harmless skips. The package's own or external-backport `fakelib2`/`fakelib` remains available for the later `common/lib` pre-spawn overlay.
- If multiple scan paths provide the same title backport, the game's own scan path wins; otherwise scan path order is used, followed by the internal fallback.
- ShadowMount+ checks the selected backport for `fakelib2` and then `fakelib`; if neither exists, it checks the original game source in the same order. The selected directory is mounted into the running game's sandbox `common/lib`. A selected `fakelib2` remains exclusive: neither emulator updates nor the global fakelib can replace or supplement it. Per-game filtering and removal of `libkernel.sprx` still apply, using a cache when needed.
- For folder/image games, `update_emulators=1` makes matching files from `emulators_path` replace files in the selected game fakelib, except when `fakelib2` is selected. Installed PKGs never compose `emulators_path` into their fakelib. The cache is refreshed when its sources change and expires after seven days without a game launch.
- Before preparing and mounting fakelib for an installed PPR PKG, ShadowMount+ deletes `libSceAmpr.sprx` and `libScePlayGo.sprx` from the selected `/data/homebrew/backports/<TITLE_ID>/fakelib`. Missing files are harmless skips; a deletion failure stops the fakelib mount.
- With `auto_update_ampr=1`, ShadowMount+ checks for AMPR updates 30 seconds after startup and every four hours. It downloads a missing or newer emulator and displays a notification after a successful update.
- When the mounted fakelib contains only the supported emulator libraries, the notification says `Emulators applied`. Otherwise the backport notification adds `Emulators updated` when emulator files were replaced.
- If both global and per-game fakelib exist, they are combined in the game cache according to `global_fakelib_priority`, unless the selected backport contains `fakelib2`. Without a per-game fakelib, the global folder is used on its own, with a filtered cache when required.
- Use repeatable `global_fakelib_exclude=<TITLE_ID>` entries to skip the global fakelib for specific games without disabling per-game fakelib.
- `backport_fakelib=0` disables the sandbox `fakelib` watcher, including global fakelib.
- The game card in the web interface offers `Default`, `Full`, `Emulators only` and `Disabled` fakelib modes. `Default` inherits `fakelib_default_mode` from web settings or `config.ini` (initially `full`). An explicit per-game mode overrides that default, including `full`. Modes apply to the selected game fakelib (including `fakelib2`) and global fakelib on the next launch; active mounts stay unchanged. Equivalent config entries are `fakelib_mode=PPSA12345:emulators` and `fakelib_mode=PPSA12345:full`; `default` removes the override. The last valid entry for a title wins, including legacy `fakelib_exclude` entries.
- `Emulators only` mounts available regular files named `libSceAppContent.sprx`, `libSceGameUpdate.sprx` and `libSceNpEntitlementAccess.sprx` for installed PKGs. Folder/image games also allow `libSceAmpr.sprx` and `libScePlayGo.sprx`. Missing files are silently skipped; an empty selection creates no fakelib overlay. Emulator updates obey the same allowlist.
- With fakelib enabled, `libkernel.sprx` is removed from the selected writable folder fakelib and participating global fakelib. For image games and installed PKGs, source files remain unchanged and a cache omitting this library is mounted instead. If a required filtered cache cannot be created, the unfiltered source is never mounted.
- Use repeatable `fakelib_exclude=<TITLE_ID>` entries to disable per-game `fakelib`, `fakelib2`, global fakelib and cache preparation for specific games. `/data`, sandbox storage aliases and PKG `app0` backports remain enabled. Reloaded exclusions apply on the next launch; existing mounts remain until game exit. Title IDs are case-insensitive and must have a supported prefix followed by five digits; invalid entries and entries beyond the 128-title limit are logged and ignored.
- `/data` is mounted through writable `nullfs` for PS5 (`PPSA*`), PS4 (`CUSA*`) and homebrew (`LAPY*`, `FAKE*`) titles. Sandbox `/mnt` remains a local directory: only mounted volumes at `/mnt/usb0` through `/mnt/usb7`, `/mnt/ext0` through `/mnt/ext1` and `/mnt/disc` receive individual writable `nullfs` aliases. Empty mount directories and other host `/mnt` entries are skipped. Storage aliases follow filesystem mount/unmount events, including USB hotplug and disc insertion/ejection. All aliases are removed on game process exit.
- `backport_fakelib=0` disables the sandbox `fakelib` watcher, including global fakelib and emulator updates.
- For `backport_fakelib` to work correctly, the standalone `BackPork` payload must be disabled. Running both at the same time will conflict.
Kstuff game lifecycle behavior:
- When `kstuff_game_auto_toggle=1`, ShadowMount watches game `exec/exit` events in the background.
- Image-backed launches use `kstuff_pause_delay_image_seconds`; direct/non-image launches use `kstuff_pause_delay_direct_seconds`.
- `kstuff_crash_detection=0` disables crash monitoring and the automatic pause-delay tuning logic, while leaving normal kstuff auto-pause/auto-resume behavior intact.
- `kstuff_no_pause` skips auto-pause entirely for matching title IDs.
- `kstuff_delay` overrides the pause delay for matching title IDs, regardless of image/direct launch type.
- `/data/shadowmount/autotune.ini` overrides both `config.ini` and `autopause.txt` for matching title IDs.
- `/data/shadowmount/autotune.ini` also overrides `image_sector` rules from `config.ini` for matching image file names.
- A game source folder may optionally contain `autopause.txt`; it is read once at launch time.
- Priority order is: `autotune.ini` -> `kstuff_delay` from `config.ini` -> `autopause.txt` -> global direct/image default delay.
- If `autopause.txt` contains only a number, that value is used for direct launches and doubled for image-backed launches.
- `autopause.txt` may also use:
- `direct=<seconds>`
- `image=<seconds>`
- If both kinds of rule target the same title, `kstuff_no_pause` takes priority.
- When crash monitoring detects an app crash before kstuff was paused, ShadowMountPlus only notifies that the app crashed 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.
Game error monitoring:
- Supported game launches are monitored for up to 60 seconds from the original
launch event. Same-game process replacements keep the original deadline.
- Fatal signals and module-load errors are matched to the game's PID and shown
with their details. An exception-stop flag provides a generic crash notification
when no detailed error is available. Incomplete fatal reports wait for up to
two seconds without report progress, then show the available PID-matched
details or fall back to the tracked process's exception flag.
- Monitoring stops after the first reported error, the deadline, game exit,
runtime sleep or watcher shutdown. It does not control kstuff or tune delays.
Validation:
- See `config.ini.example` for a ready-to-use template.
@@ -177,7 +230,7 @@ PFSC container layout requirement (`.ffpfsc`):
- Do not place game files directly in the container root.
- Place supported nested image files inside the container; ShadowMountPlus mounts those nested images and scans them for the game.
- A nested `pfs_image.dat` file inside a PFSC container is treated as a PFS image.
- `.ffpfsc` uses the nested outer PFS profile (`img_type=0x02`); `.ffpfs` and `pfs_image.dat` files mounted from inside it use the nested inner profile (`img_type=0x82`). Signature verification and GDDR5 cache setup are kept in code but currently disabled.
- `.ffpfsc` and its nested `.ffpfs`/`pfs_image.dat` images use the version 1.6 LVD mapping unit, equal to the logical sector size (4096 bytes by default). The outer selector remains `img_type=0x02`, and the inner selector remains `img_type=0x82`. A standalone `.ffpfs` source uses the optimized profile only under `/data/...` or `/user/...`; other standalone sources use the version 1.6 parameters. Signature verification remains disabled for unsigned images; set `nested_pfs_index_cache=1` to request the PFSC compressed-offset cache before attaching nested images.
## Compressed PFS containers (`.ffpfsc`)
@@ -209,7 +262,7 @@ mkpfs pack folder --verify --no-compress --no-adjust-output-file-extension --ver
'./pfs_image.dat'
```
Then pack the nested image into a compressed PFS container:
Then pack the nested image (**pfs_image.dat**) into a compressed PFS container:
```bash
mkpfs pack file --verify --version PS5 --inode-bits 32 \
@@ -333,6 +386,42 @@ Recommended folder structure:
Recommended only for titles that need external-drive-style compatibility. For general use, prefer `.ffpkg`.
### LVD type-5 / BFS fast-path requirements
For direct source paths under `/data/...` or `/user/...`, ShadowMount attaches
`.exfat` through LVD as `img_type=5 (Sv)`, with a 512-byte logical sector and a
64 KiB `secondary_unit`. This lets the LVD worker collect
up to 31 queued, 64-KiB-aligned requests of exactly 64 KiB and
submit them through `bfs_iosession_rw_sdimg`/`BfsSdimg`. Non-matching requests
continue through the normal vnode path.
All of the following are required for the kernel to consider that fast path:
- Use `exfat_backend=lvd` (the default). `/dev/mdctl` has no LVD image type or
BFS sdimg batching.
- A `.exfat` source outside `/data/...` and `/user/...` uses the `img_type=0`
profile directly. No second mount attempt is made after failure.
- Store the final `.exfat` file directly on the internal BFS, normally below
`/data`. An image on USB/UFS/exFAT/PFS, or an exFAT image nested in compressed
PFS, uses the normal path.
- Format exFAT with a fixed 64 KiB allocation unit. The supplied Linux, macOS,
and Windows builders now always use this value, including small-file images.
- Keep `lvd_exfat_sector_size=512` and do not add an
`image_sector=<name>.exfat:65536` override. A logical sector and an exFAT
allocation unit are different geometries.
- Make the image size a multiple of 64 KiB; the supplied builders round it to
1 MiB and therefore satisfy this automatically.
- Copy the completed image to BFS in one sequential operation when practical.
This does not control eligibility, but avoids needless fragmentation of the
backing file. The default read-only mount is preferred when writes are not
required.
The 64 KiB cluster improves eligibility but cannot force every access through
the batch path: exFAT metadata and small or unaligned application I/O still use
the normal vnode path. In the ShadowMount debug log, an eligible profile request is
shown by `img=5 sec=512 sec2=65536`; the kernel still makes the final BFS and
platform checks internally.
Linux (Ubuntu/Debian):
- Required components installation:
- `sudo apt-get update && sudo apt-get install -y exfatprogs exfat-fuse fuse3 rsync`
@@ -344,10 +433,15 @@ Linux (Ubuntu/Debian):
- Notes:
- Source folder must be the game root and contain `eboot.bin`.
- Auto-calculates image size using rounded file allocation + metadata + safety margin.
- For manual exFAT builds, keep the cluster size at `64 KB` or you may lose performance.
- Automatically selects exFAT cluster profile:
- Large-file profile: `64K`
- Small/mixed-file profile: `32K`
- Always formats with `mkfs.exfat -c 64K` for the LVD/BFS profile.
- For a manual build, use an image size divisible by 64 KiB and run `mkfs.exfat -c 64K <image>`.
macOS:
- Script: `mkexfat_macos.sh` (uses the built-in exFAT, disk-image and `rsync` tools).
- Example: `chmod +x mkexfat_macos.sh && ./mkexfat_macos.sh ./APPXXXX ./PPSA12345.exfat`.
- The script attaches the raw image as a device and formats it with
`newfs_exfat -b 65536` before copying the game-root contents.
Windows:
- Recommended: use `make_image.bat` (wrapper for `New-OsfExfatImage.ps1` + OSFMount).
@@ -355,13 +449,14 @@ Windows:
- Install OSFMount: https://www.osforensics.com/tools/mount-disk-images.html.
- Keep `make_image.bat` and `New-OsfExfatImage.ps1` in the same folder.
- Run `cmd.exe` as Administrator.
- If you build an exFAT image manually instead of using the script, keep the cluster size at `64 KB` or you may lose performance.
- If you build an exFAT image manually, format it with a `64K` allocation unit, for example `format X: /FS:exFAT /A:64K /Q`.
- Usage:
- `make_image.bat "C:\images\game.exfat" "C:\payload\APPXXXX"`
- Behavior:
- Auto-sizes the image to fit source content.
- Source folder must be the game root and contain `eboot.bin`.
- Formats and copies source folder contents into image root.
- Uses a fixed 64 KiB allocation unit for both large-file and small/mixed-file images.
- Optional (fixed size): run PowerShell script directly:
- `powershell.exe -ExecutionPolicy Bypass -File .\New-OsfExfatImage.ps1 -ImagePath "C:\images\game.exfat" -SourceDir "C:\payload\APPXXXX" -Size 8G -ForceOverwrite`
+163
View File
@@ -0,0 +1,163 @@
# ShadowMountPlus 1.7
Version `1.7` is a major ShadowMountPlus update compared with `1.6`.
It focuses on the game lifecycle, performance with large libraries, safe
release of mount resources, and new management features.
> [!WARNING]
> Mounting images can still cause shutdown problems and data corruption on
> internal drives, especially on older firmware versions. Back up important
> data and configuration before updating.
## Highlights
### New SceShellCore based runtime
- Game registration and runtime mount preparation now use the SceShellCore bridge.
- By default, images are mounted on demand before a game starts and released
after the application exits.
- Games can be switched without leaving stale mount layers behind.
- Improved handling of `ExitSpawn`/`LoadExec`, PID changes, game exit,
suspend/resume, and payload shutdown.
- On FW 12.00+, individual games are registered through the TitleDir bridge;
forcing `app_install_all` is no longer required.
- Firmware versions up to 13.60 are supported.
### Large libraries and faster scanning
- A reusable image index supports up to **8,192 games across 2,048 images**.
- Faster metadata reading, staging, and game registration.
- Fewer file descriptors are open at the same time.
- Faster USB library recovery after rest mode.
- The new `persistent_image_mounts=1` mode keeps discovered images mounted
between game launches. It is disabled by default.
- Optional removal of unavailable games from the system library includes a
delay and cancels pending removal if the source returns.
- Games with installed DLC are protected from automatic removal unless
`auto_remove_games_with_dlc=1` is explicitly enabled.
### Web interface and HTTP/JSON API
- The built-in library interface offers search, filters, game cards, source
management, scanning, settings, and log viewing.
- A Shadow Mount+ launcher is installed in the PS5 menu and opens the local
web interface.
- HTTP/JSON API v1 provides access to images, games, and storage devices, as
well as game mounting, source management, settings, and scanning.
- Background jobs can copy, move, unpack an image, or delete a source. Their
progress is available, and they can be cancelled before an irreversible stage.
- The default address is `127.0.0.1:10101`, so the API is accessible only to
applications running on the PS5.
- Configure `api_bind_address` and `api_port` to allow access from the local
network.
- The release includes the [API documentation](docs/api.md),
[OpenAPI schema](docs/openapi.yaml), [web page](web/index.html), and
`tools/api_test.py` test client.
> [!CAUTION]
> The API has no authentication. Do not use `api_bind_address=0.0.0.0` on an
> untrusted network.
### Mount profiles and image performance
- For `.exfat`, `.ffpkg`, and standalone `.ffpfs`, the optimized profile is used
only for direct paths under `/data/...` or `/user/...`. Other direct paths use
the 1.6 profile. Outer `.ffpfsc` images and nested
`.ffpfs`/`pfs_image.dat` images always use the new profile.
- The optimized exFAT profile supports LVD `img_type=5` and the BFS fast path
for images on internal storage.
- The fast path requires the `lvd` backend, a 512-byte logical sector, and
64 KiB exFAT clusters.
- The Linux, macOS, and Windows exFAT image creation scripts now use a fixed
64 KiB cluster size.
- For nested PFS images, `nested_pfs_index_cache` requests the compressed
offset index cache before attachment.
- Improved handling of asynchronous LVD/MD detach, deferred unmounts for busy
resources, and cleanup of incomplete attachments after mount failures.
- Forced detach of MD devices was removed to avoid disrupting mounts that
are still in use.
### fakelib and emulator updates
- Global and per-game `fakelib` are now combined in a per-title cache with
configurable `game` or `global` priority.
- Matching game emulator files can be replaced with files from
`emulators_path` without changing the original game folder.
- Unused fakelib caches are automatically removed seven days after the last
game launch.
- Optional automatic checks and downloads for `libSceAmpr.sprx` updates.
- `fakelib2` always has the highest priority: it is mounted directly and is
not combined with global `fakelib` or emulator updates.
- Fixed fakelib remounting across `ExitSpawn`/`LoadExec` transitions.
- For installed PKGs, their own `fakelib` is mounted before the game process
starts.
### Localization, cooling, and diagnostics
- System notifications are localized into 31 languages.
- `language=auto` selects the console's system language; a language can also
be selected explicitly.
- Added `fan_target_temperature=system|50..91`. The default `system` value
leaves fan control to the console.
- Startup notifications show the active temperature and the result of
backport and emulator updates more clearly.
- Expanded diagnostics for busy mounts, fatal game signals, and the reasons
behind runtime state transitions.
- Improved the reliability of automatic Kstuff pause/resume and recovery
after a game exits early.
## New configuration options
Key options added in 1.7:
```ini
# Notification language
language=auto
# HTTP/JSON API
api_enabled=1
api_bind_address=127.0.0.1
api_port=10101
# Runtime and library maintenance
persistent_image_mounts=0
auto_remove_missing_games=0
auto_remove_games_with_dlc=0
auto_remove_missing_delay_seconds=300
# fakelib and emulators
update_emulators=1
emulators_path=/data/shadowmount/emus
auto_update_ampr=0
# Cooling
fan_target_temperature=system
```
See `config.ini.example` for the full list of options and valid values.
## Upgrading from 1.6beta16
- An existing `/data/shadowmount/config.ini` remains compatible and does not
have to be recreated.
- New options are not added to an existing configuration file automatically.
If needed, copy the relevant lines from the new `config.ini.example`.
- Back up `config.ini` and `autotune.ini` before updating.
- Mount profiles are selected by source path. The old `legacy_mount_*` options
are no longer supported and should be removed from the configuration.
`nested_pfs_index_cache` remains available; `legacy_gddr5_cache` is accepted
as its old name.
- The default `lvd_pfs_sector_size` changed from `32768` to `4096`.
- The default `md_ufs_sector_size` is `4096`.
- For the exFAT fast path, do not set `image_sector=...:65536`: 64 KiB is the
cluster/secondary-unit size, while the logical sector must remain 512 bytes.
- `auto_remove_missing_games`, `persistent_image_mounts`, `auto_update_ampr`,
and network access to the API are disabled by default.
## Compatibility
- Supports jailbroken PS5 systems with **Kstuff-lite v1.07 or newer**.
- UFS (`.ffpkg`) is the recommended format for normal use.
- PFS (`.ffpfs`/`.ffpfsc`) remains experimental.
- Running ShadowMountPlus alongside the separate BackPork payload is still
unsupported because their fakelib mounts conflict.
+9
View File
@@ -0,0 +1,9 @@
{
"applicationCategoryType": 66048,
"titleId": "FAKE10101",
"deeplinkUri": "http://127.0.0.1:10101/",
"localizedParameters": {
"defaultLanguage": "en-US",
"en-US": { "titleName": "Shadow Mount+" }
}
}
+123 -49
View File
@@ -21,6 +21,21 @@
# 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:
# Disabling the API also skips Shell UI launcher installation at startup.
# 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_enabled=1
# api_bind_address=127.0.0.1
# api_port=10101
# Global mount mode:
# 1/true/yes/on/ro -> read-only mounts
# 0/false/no/off/rw -> read-write mounts
@@ -33,30 +48,43 @@
# 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
# Default: 1
# scan_depth=1
# Directory watcher granularity:
# 1 -> keep vnode watchers on scan root subdirectories
# 0 -> watch only scan roots/parents and rely on targeted/full scans for children
# Default: 0
# watch_subdirs=0
# Image file watcher granularity:
# 1 -> keep vnode watchers on discovered image files
# 0 -> detect image file changes through targeted/full scans
# Default: 1
# watch_image_files=1
# Legacy compatibility:
# recursive_scan=1 forces scan_depth=2
# Default: disabled
@@ -75,17 +103,80 @@
# lvd -> /dev/lvdctl -> /dev/lvdN
# md -> /dev/mdctl -> /dev/mdN
# Defaults: exfat=lvd, ufs=lvd
# LVD is required for the exFAT img_type=5/BFS sdimg fast path. The image file
# must also reside on internal BFS and be formatted with a 64 KiB cluster.
# exfat_backend=lvd
# ufs_backend=lvd
# Sandbox fakelib backport watcher:
# 1/true/yes/on -> mount /mnt/sandbox/<TITLE_ID>_XXX/app0/fakelib into common/lib
# 0/false/no/off -> disable fakelib watcher
# PFS compressed-file index cache for nested images:
# 1/true/yes/on -> cache the containing PFS compressed-file index before
# attaching a nested image
# 0/false/no/off -> skip this optional request (default)
# The old legacy_gddr5_cache key remains accepted for compatibility.
# nested_pfs_index_cache=0
# Sandbox fakelib support:
# 1/true/yes/on -> mount fakelib2 when present, otherwise fakelib, into common/lib
# Installed PKGs can receive an external backport through the earlier NSFS app0
# redirect; this switch controls only the later common/lib fakelib overlay.
# 0/false/no/off -> disable fakelib overlays
# Default: 1
# backport_fakelib=1
# Default fakelib mode for games without an individual override.
# full (default), emulators, disabled; also configurable in the web interface.
# Changes apply on the next launch; active mounts stay unchanged.
# fakelib_default_mode=full
# Per-game fakelib mode: default (inherit), full, emulators, disabled.
# Applies to fakelib/fakelib2/global fakelib on the next launch; active mounts stay.
# Repeatable, up to 128 explicit title rules shared with fakelib_exclude.
# An explicit full overrides the default; default removes the title override.
# Last valid entry wins; invalid modes/title IDs and excess rules are logged/ignored.
# fakelib_mode=PPSA12345:emulators
# fakelib_mode=PPSA54321:disabled
# Emulators: AppContent, GameUpdate, NpEntitlementAccess; folder/image games
# also Ampr and PlayGo (libSce*.sprx). Missing files are silently skipped.
# libkernel.sprx is removed from writable folder sources. Images/PKGs use a
# cache without it; source files stay unchanged. Filtering also applies to fakelib2.
# Disable all fakelib overlays for specific games (repeatable, up to 128 titles):
# Includes fakelib, fakelib2 and global fakelib. No exclusions by default.
# Reloaded changes apply on the next game launch; active mounts are kept.
# fakelib_exclude=PPSA12345
# fakelib_exclude=CUSA12345
# fakelib_exclude=LAPY12345
# fakelib_exclude=FAKE12345
# Updates for multiple emulators in the game fakelib:
# 1/true/yes/on -> cache the game's fakelib before launch and replace matching
# cached files from emulators_path; fakelib2 and installed
# PKGs are never changed
# 0/false/no/off -> disable emulator updates
# Default: 1
# update_emulators=1
#
# Folder containing emulator update files (absolute path):
# emulators_path=/data/shadowmount/emus
# Cache: /data/shadowmount/cache/<TITLE_ID>/fakelib
# Unused caches expire seven days after the most recent game launch.
# Automatic libSceAmpr emulator download:
# 1/true/yes/on -> check 30 seconds after startup and then every four hours
# 0/false/no/off -> do not access the network (default)
# A missing local emulator is downloaded directly.
# Otherwise, the SPRX is downloaded only when the server file is newer, then
# replaces emulators_path/libSceAmpr.sprx only when its contents differ.
# Downloads are deferred while a game mount is active.
# Default: 0
# auto_update_ampr=0
#
# HTTP or HTTPS download URL:
# ampr_update_url=https://github.com/drakmor/ampr_emu/releases/latest/download/libSceAmpr.sprx
# Global fakelib overlay:
# 1/true/yes/on -> also mount global fakelib for every tracked game when present
# 1/true/yes/on -> use global fakelib for every tracked game when present;
# fakelib2 always remains exclusive when present
# 0/false/no/off -> do not use the global fakelib folder
# Default: 1
# global_fakelib=1
@@ -93,45 +184,35 @@
# Default global fakelib folder:
# global_fakelib_path=/data/shadowmount/fakelib
#
# Priority when both global and per-game fakelib exist:
# game -> mount global first, then game-specific fakelib (game wins)
# global -> mount game-specific fakelib first, then global fakelib (global wins)
# Priority when both global and per-game fakelib exist in the combined cache:
# game -> game-specific files win
# global -> global files win
# Default: game
# global_fakelib_priority=game
#
## Exclude specific title IDs from the global fakelib overlay (repeatable):
## global_fakelib_exclude=PPSA12345
# Auto-manage kstuff around game launches:
# 1/true/yes/on -> pause kstuff after game launch, resume immediately on stop
# 0/false/no/off -> leave kstuff state untouched
# Default: 1
# kstuff_game_auto_toggle=1
# Detect crash-candidate state and autotune pause delay:
# 1/true/yes/on -> enable crash monitoring and autotune updates
# 0/false/no/off -> disable crash detection/autotune logic
# Default: 1
# kstuff_crash_detection=1
# Delay before pausing kstuff for image-backed titles.
# Default: 25
# kstuff_pause_delay_image_seconds=25
# Delay before pausing kstuff for direct/non-image launches.
# Default: 15
# kstuff_pause_delay_direct_seconds=15
# Automatic fan-controller target for games:
# system -> leave the system-managed target unchanged (default)
# 50..91 -> apply this target when a game starts
# The stock automatic fan controller remains enabled.
# Default: system
# fan_target_temperature=system
# Backend-specific sector size overrides:
# Defaults: lvd_exfat=512, lvd_ufs=4096, lvd_pfs=32768, md_exfat=512, md_ufs=512
# Defaults: lvd_exfat=512, lvd_ufs=4096, lvd_pfs=4096, md_exfat=512,md_ufs=4096.
# Keep lvd_exfat at 512 for the BFS fast path. 65536 is the exFAT cluster and
# LVD secondary unit in the optimized profile, not the logical sector size.
# lvd_exfat_sector_size=512
# lvd_ufs_sector_size=4096
# lvd_pfs_sector_size=32768
# lvd_pfs_sector_size=4096
# md_exfat_sector_size=512
# md_ufs_sector_size=512
# md_ufs_sector_size=4096
# Optional custom scan roots (can be repeated):
# If at least one scanpath=... is present, only these paths are used.
# /data/homebrew/backports remains available as a backport fallback.
# "/mnt/shadowmnt/pfsc" and "/mnt/shadowmnt" are always added automatically.
# Default: use built-in scan path list
# scanpath=/data/homebrew
@@ -144,14 +225,7 @@
## Examples:
## image_ro=PPSA12345.ffpfs
## image_rw=My Game Dump.exfat
## image_sector=My Game Dump.exfat:65536
## Keep kstuff enabled for specific title IDs (repeatable):
## kstuff_no_pause=PPSA12345
## Override pause delay for specific title IDs (repeatable):
## Format: TITLEID:SECONDS
## kstuff_delay=PPSA12345:30
## image_sector=Legacy 512 Sector.exfat:512
## More custom scan root examples:
## scanpath=/data/homebrew
+310
View File
@@ -0,0 +1,310 @@
# 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.
These settings are applied on runtime config reload. `api_enabled` defaults to
`1`; setting it to `0` stops the listener and joins the libmicrohttpd service
and worker threads. Re-enabling it restarts the HTTP service without restarting
the payload. An already-running storage job is allowed to finish, but no new
jobs can be submitted while the API is disabled.
`/data/shadowmount/index.html` overrides the web interface bundled into the
payload. If the file is absent, the same `web/index.html` built into the ELF is
served automatically, so enabling the API always provides a landing page.
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 uses libmicrohttpd with eight fixed polling workers. It accepts
up to 32 concurrent connections, uses a listen backlog of 32 and closes idle
connections after 15 seconds. Slow clients therefore do not serialize unrelated
requests or create an unbounded number of threads. If daemon startup fails, the
service retries with an interruptible 250 ms to 5 s backoff. Suspend, shutdown
and config reload stop the daemon cleanly; resume recreates it.
Every JSON API operation uses `POST`, includes `Content-Type: application/json`
and contains one JSON object. `GET /` returns the optional web interface and the
icon endpoint returns PNG bytes. Connections are closed after one response;
chunked request bodies are not supported. The maximum request body is 4096 bytes.
The parsed request-header limit remains 8192 bytes.
All responses include `Access-Control-Allow-Origin: *`. Browser preflight
requests using `OPTIONS` are supported and allow `GET`, `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 |
| --- | --- | --- |
| `/` | GET | Serve `/data/shadowmount/index.html`, or the embedded page when it is absent |
| `/api/v1/version` | `{}` | API and ShadowMount versions plus capabilities |
| `/api/v1/storage` | `{}` | Mounted storage filesystems with total, free and available space |
| `/api/v1/images` | `{}` | Complete image snapshot |
| `/api/v1/scan` | `{"reset_attempts":false}` | Queue an immediate full rescan; optionally reset title and image retry counters |
| `/api/v1/manual/list` | `{}` | List direct paths to individual game folders or images from `manual.lst` |
| `/api/v1/manual/add` | `{"path":"/mnt/usb0/games/PPSA12345"}` | Idempotently add one game source to `manual.lst` |
| `/api/v1/manual/remove` | `{"path":"/mnt/usb0/games/PPSA12345"}` | Remove matching source lines from `manual.lst` |
| `/api/v1/settings` | `{}` | Read web-managed runtime settings and scan paths |
| `/api/v1/settings/update` | `{"debug":true,"quiet_mode":false,"update_emulators":true,"fakelib_default_mode":"full","auto_update_ampr":false,"auto_remove_missing_games":false,"auto_remove_missing_delay_seconds":300,"allow_lan_access":true,"fan_target_temperature":0,"scan_paths":["/mnt/usb0/games"]}` | Atomically update the web-managed part of `config.ini` |
| `/api/v1/debug-log` | `{"max_bytes":131072}` | Read a bounded tail of `debug.log` for the web log dialog |
| `/api/v1/kernel-log` | `{"max_bytes":131072}` | Read a bounded tail of the SDK kernel-log stream used by crash detection |
| `/api/v1/games` | `{"include_size":false}` | Detailed game snapshot; optional physical source-size calculation |
| `/api/v1/games/info` | `{"title_id":"PPSA12345"}` | Detailed app.db and source information for one game; size is always calculated |
| `/api/v1/games/fakelib` | `{"title_id":"PPSA12345","mode":"emulators"}` | Persist the PS5 game's fakelib policy for its next launch |
| `/api/v1/games/icon?title_id=PPSA12345[&size=thumb]` | GET | Stream the full PNG or a cached 128x128 thumbnail |
| `/api/v1/games/mount` | `{"title_id":"PPSA12345","mode":"ro"}` | Mount a managed game, optionally overriding its image mode with `ro`/`rw` |
| `/api/v1/games/unmount` | `{"title_id":"PPSA12345"}` | Unmount a managed game |
| `/api/v1/games/uninstall` | `{"title_id":"PPSA12345"}` | Request uninstallation through AppInstUtil |
| `/api/v1/games/move` | `{"title_id":"PPSA12345","destination_dir":"/mnt/usb1/games"}` | Start an asynchronous move job |
| `/api/v1/games/copy` | `{"title_id":"PPSA12345","destination_dir":"/mnt/usb1/games"}` | Start an asynchronous copy job |
| `/api/v1/games/unpack` | `{"title_id":"PPSA12345","destination_dir":"/mnt/usb1/games","delete_source":false}` | Mount an image read-only and asynchronously copy the game into a folder |
| `/api/v1/games/storage/status` | `{"job_id":1}` | Get current or last storage job status; `job_id` is optional |
| `/api/v1/games/storage/cancel` | `{"job_id":1}` | Request cancellation while the active job is still cancellable |
| `/api/v1/games/delete` | `{"title_id":"PPSA12345","confirm":true}` | Start asynchronous permanent deletion of the physical source |
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 '{}'
curl -sS http://127.0.0.1:10101/api/v1/scan \
-H 'Content-Type: application/json' \
-d '{}'
curl -sS http://127.0.0.1:10101/api/v1/games/mount \
-H 'Content-Type: application/json' \
-d '{"title_id":"PPSA12345","mode":"rw"}'
```
An autonomous example library UI is provided as `web/index.html`. Copy it to
`/data/shadowmount/index.html` on the PS5 and open the configured API listener
in a browser, for example `http://192.168.1.50:10101/`. The file is read for
each request, so it can be replaced without restarting ShadowMount. If it is
missing, `GET /` returns HTTP 404 with the normal JSON error response.
The bundled interface covers all 31 console locales, selects the closest match
from the browser locale, and keeps a manually selected locale in local storage.
## 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": ["web_ui", "storage_space", "list_images", "list_games", "game_info", "game_icon", "mount_game", "unmount_game", "uninstall_game", "move_game_source", "copy_game_source", "delete_game_source", "unpack_game_image", "storage_job_status", "storage_job_cancel", "list_manual_sources", "add_manual_source", "remove_manual_source", "manage_settings", "read_debug_log", "read_kernel_log", "rescan"]
}
```
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
combine ShadowMount's game cache with `tbl_contentinfo` from app.db. They expose
the physical `path`, `runtime_path`, `source_type` (`folder` or `image`), image
filesystem type, PS4/PS5 platform, title/content IDs, name, last-launch and
install timestamps, game `version`, relative `icon_url`, app.db size, and runtime state.
`version` comes from `AppInfoJson` (`CONTENT_VERSION`, falling back to `APP_VER`)
and is an empty string when unavailable.
The timestamp values from `AppInfoJson` (`#_last_access_time` and
`#_install_time`) take precedence over the stale top-level columns when they
are present.
`POST /api/v1/games` does not walk game directories by default. Set
`include_size=true` to add `size_status` and, on success, `size_bytes` to every
item. `POST /api/v1/games/info` always performs this calculation. An image uses
its file size; a folder uses the sum of its regular-file sizes.
`POST /api/v1/storage` returns capacity-bearing mounted filesystems and omits
virtual mounts such as `devfs`, `nullfs` and `tmpfs`. Each item contains the
device `source`, `mount_point`, `filesystem`, `total_bytes`, `free_bytes`,
`available_bytes`, `used_bytes` and `read_only`. `available_bytes` is the space
available for new files; `used_bytes` is calculated against that value. The
`destinations` array contains only writable, currently available managed
`/data/homebrew`, `/mnt/ext*/homebrew` and `/mnt/usb*/homebrew` paths. Scanned
`/mnt/ext*` and `/mnt/usb*` roots are included as additional destinations. Each
item adds the selectable `path` to the same filesystem metadata.
The web storage overview uses `mounts` to show the internal `/user` filesystem
and mounted `/mnt/ext0..1` and `/mnt/usb0..7` disks once per filesystem/source.
System partitions and game mounts are omitted from that overview. The separate
`destinations` list supplies folder choices for copying, moving and unpacking.
`copy` and `move` preserve the source basename and require an existing
destination under a configured non-runtime scan root. They and `delete` return
HTTP 202 with a `job_id` and continue in one background worker;
only one of these jobs can be active. A second start returns HTTP 409/`EBUSY`.
`delete` still requires `confirm=true`. If a cached source is already absent
(`ENOENT` or `ENOTDIR`), it instead returns HTTP 200 with `source_missing=true`
and `scan_queued=true`, without creating a job or directly uninstalling the title.
The scanner reconciles stale cache and links when runtime is safe. An absent
source can also mean disconnected storage; permission and I/O errors remain
failures. Unknown titles still return HTTP 404.
The status response contains the phase,
byte-based percentage, processed/total bytes,
processed/total files, average processed-byte rate, elapsed time and final
errno-style result. The initial `measuring` phase discovers the totals and has
zero percent until they are known. Only the active or most recently finished
job is retained; an older explicit `job_id` returns HTTP 404.
`scan`, successful `uninstall`, and missing-source delete responses include
`scan_queued`, `scan_deferred`, and `scan_deferred_reason`. The reason is
`game_active`, `runtime_prepared`, `rest_mode`, or an empty string. These fields
describe the runtime at response time; queue acceptance is not scan completion.
A successful uninstall request queues a full synchronization only when its
known source was already absent before uninstall. Retained sources do not
trigger immediate rediscovery; `scan_queued=false` reports this case. Uninstall
does not exclude or delete the source, so a later scan can still discover it.
Busy uninstall/storage responses add a stable `error_reason` code; storage jobs
expose the corresponding `result_error_reason`. The English `error` and
`result_error` remain available for clients without localized catalogs. Reasons distinguish
game activity, pending installation, mutation-gate contention, prepared runtime,
and busy mount release. Close a game fully before retrying; returning to the
home screen alone does not terminate it.
If the source root disappears after a delete job is accepted, an absent root is
treated as already deleted. A missing child in an existing tree, I/O errors,
permission errors, and cancellation remain failures.
Cancellation is cooperative during `preparing`, `measuring` and `transferring`.
A partial copy destination is removed. A delete job becomes non-cancellable
when it enters `deleting`, before its first physical removal. Likewise, once a
cross-filesystem move enters
`finalizing` and starts deleting the complete source, cancellation is rejected
to avoid leaving the only source tree partially removed. The scanner and
ShellCore mutation gates remain held for the whole job and a rescan is queued
after success, cancellation or failure. `move` uses `rename()` on one
filesystem and exact copy-plus-delete across filesystems. A backing image
shared by several titles is handled as one physical source.
`unpack` uses the same worker and progress endpoint. It mounts the requested
image read-only, copies the mounted game tree to `<destination_dir>/<TITLE_ID>`,
and always releases the runtime mount afterward. `delete_source=true` removes
the image only after a complete copy and successful unmount; this irreversible
final phase is non-cancellable. Source deletion is rejected when one image is
shared by several titles.
Changing the API bind address or port restarts only the HTTP listener; an
active storage job continues and remains available through the new listener.
The game list combines discovered folder/image sources with installed PS4 and
PS5 PKGs from `app.db`, identified by an existing `app.pkg`. Title IDs appear
once; managed folder/image sources retain their normal actions. Installed PKGs
have `source_type: "pkg"`, `installed_pkg: true`, `managed: false` and
`mounted: false`. Their `path` points to `app.pkg`; `runtime_path` is empty.
They support information, icons and uninstall, plus fakelib configuration for
PS5. Mount/unmount and source copy/move/delete/unpack reject PKGs with HTTP 403
and `EPERM`. PKG size uses the installed size in `app.db`, falling back to the
package file size; folder/image measurement remains unchanged.
Game responses also provide `can_uninstall`, `can_manage_source`,
`can_toggle_fakelib`, `fakelib_enabled` and `fakelib_effective_enabled`.
`fakelib_mode` is the resolved mode (`full`, `emulators`, `disabled`),
`fakelib_mode_override` is the title's explicit mode or `default`, and
`fakelib_default_mode` is the current global default.
The fakelib route accepts only known PS5 games, including PKGs, images and
folders. `PPSA` identifies PS5; `LAPY`/`FAKE` homebrew uses its `app.db` platform.
Send `mode: "full"`, `"emulators"` or `"disabled"` to persist an explicit override,
or `mode: "default"` to remove it and inherit `fakelib_default_mode`.
Legacy `enabled: true|false` requests remain supported and select explicit
`full` or `disabled`; `mode` takes precedence when both fields are present.
Unrelated config lines, other title overrides and global settings are preserved.
The response includes the three mode fields, `saved: true` and
`applies_on_next_launch: true`. Existing mounts stay until game exit.
`fakelib_enabled` is false when the resolved mode is `disabled`;
`fakelib_effective_enabled` also respects the global `backport_fakelib` switch.
Reaching the 128-title override limit fails without modifying the file;
removing an override remains possible at the limit.
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.
The optional mount `mode` is request-scoped and does not change `config.ini`.
Accepted values are `ro`, `rw`, `r/o` and `r/w`; responses normalize them to
`ro` or `rw`. It applies to every image layer needed for that title. A direct
directory source or an image profile that is inherently read-only rejects an
explicit mode it cannot support with HTTP 501 and `ENOTSUP`. An already mounted
image in the other mode returns HTTP 409 and `EBUSY`.
With `persistent_image_mounts=1`, unmounting a game releases its per-title
runtime layers but keeps the backing image mounted.
The scan command queues the existing full-scan path and wakes the scanner. If a
game runtime mount is active, the request remains pending until the lifecycle
watcher reports that scanning is safe; it does not create a parallel scan.
By default retry counters are preserved. With `reset_attempts=true`, immediately
before the requested full scan starts it clears all title registration and
install/remount counters plus all path-owned image-mount counters. If several
scan requests merge while one is pending, one `true` request keeps the reset
enabled for that scan.
Manual entries are direct paths to individual game directories or image files;
they are not recursive scan roots. Updates atomically replace `manual.lst` and preserve comments and
unrelated lines. Adding an existing normalized path and removing a missing path
both succeed with `changed: false`. A real change queues a scan. Removing a
manual source does not uninstall its game; use the uninstall route separately
when both operations are desired.
The list route returns `{status, count, paths}`. Paths use the same trimming and
trailing-slash normalization as the scanner; comments and empty lines are not
included. A missing `manual.lst` is reported as an empty list.
The settings update owns only `debug`, `quiet_mode`, `update_emulators`,
`fakelib_default_mode`, `auto_update_ampr`, `auto_remove_missing_games`,
`auto_remove_missing_delay_seconds`,
`api_bind_address`, `fan_target_temperature`, and repeated `scanpath` keys. The
`allow_lan_access` field maps to `0.0.0.0` when enabled and `127.0.0.1` when
disabled. It atomically rewrites those keys while preserving every unrelated
`config.ini` line and comment, then wakes the scanner so its existing
runtime-reload path applies the values and rebuilds scan watches. `api_enabled`
is deliberately config-only, so the web interface cannot disable the service it
needs to restore access. A fan target of `0` means system control; explicit
values must be 50 through 91 degrees Celsius. Unlike `manual.lst`, `scan_paths`
are recursive library roots. The settings response contains only explicit
custom roots, never compile-time defaults or internal image-mount roots. Saving
an empty array removes the custom override and restores compile-time defaults.
`GET /api/v1/settings` returns `fakelib_default_mode` (`full`, `emulators`, or
`disabled`; initial default `full`). The settings update accepts the same
optional field; omission preserves its current value for older clients.
Per-title overrides take priority, and mode changes apply on the next launch.
The debug-log route returns at most 256 KiB and never modifies or rotates the log.
The kernel-log route reads a `sceKernelDebugGetSdkLogText` snapshot.
Uninstall returns success after `sceAppInstUtilAppUnInstall` accepts the
request; the on-disk removal itself is asynchronous. It returns `EBUSY` while a
game is active, another title owns a prepared runtime mount, or batch install
work is pending. An unchanged discoverable source may be installed again by a
later scan, so remove its manual entry or source first when that is not wanted.
## 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. It also runs 16 parallel version requests while one client holds an
incomplete request open, 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.
+1964
View File
File diff suppressed because it is too large. Load diff
+81
View File
@@ -0,0 +1,81 @@
static const char *const g_ar_sa[SM_L10N_COUNT] = {
/* English: Go to Debug Settings */
[SM_L10N_ACTION_DEBUG_SETTINGS] = "الانتقال إلى إعدادات التصحيح",
/* English: ShadowMount+ v%s\n© Drakmor and Community */
[SM_L10N_STARTUP] = "ShadowMount+ v%s\n© Drakmor والمجتمع",
/* English: ShadowMount+ v%s\n© Drakmor and Community\nControl: http://%s:%u */
[SM_L10N_STARTUP_WEB] =
"ShadowMount+ v%s\n© Drakmor والمجتمع\nالتحكم: http://%s:%u",
/* 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: Emulators applied to game: %s */
[SM_L10N_GAME_EMULATORS_APPLIED] = "تم تطبيق المحاكيات على اللعبة: %s",
/* English: Backport applied to game: %s */
[SM_L10N_GAME_BACKPORTED] = "طُبّق الدعم الرجعي على اللعبة: %s",
/* English: Backport applied to game: %s\nEmulators updated */
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "طُبّق الدعم الرجعي على اللعبة: %s\nتم تحديث المحاكيات",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "تم رفض تركيب صورة القرص:\n%s\nحجم العنقود صغير جدًا (%llu < %u).\nتم حفظ تجاوز قطاع صورة القرص: %u.\nحاول التركيب مرة أخرى.",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "تم رفض تركيب صورة القرص:\n%s\nحجم العنقود صغير جدًا (%llu < %u).\nاستخدم حجمًا أكبر للعنقود في صورة القرص أو حجمًا أصغر للقطاع في الإعدادات.",
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "فشل التسجيل: %s (%s)\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: 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: 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: Missing/invalid param.json:\n%s */
[SM_L10N_MISSING_PARAM] = "ملف param.json مفقود أو غير صالح:\n%s",
/* English: Found %d new games. Processing... */
[SM_L10N_FOUND_NEW_GAMES] = "عُثر على %d من الألعاب الجديدة. جارٍ معالجتها...",
/* English: Library synchronized.\nFound %d games. */
[SM_L10N_LIBRARY_SYNCED] = "تمت مزامنة المكتبة.\nعُثر على %d من الألعاب.",
/* English: ShadowMount+: config reloaded. */
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: تمت إعادة تحميل الإعدادات.",
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
[SM_L10N_DUPLICATE_IGNORED] = "تم تجاهل %s المكرر:\n%s\nالموجود مسبقًا: %s",
/* English: AMPR emulator updated */
[SM_L10N_AMPR_EMULATOR_UPDATED] = "تم تحديث محاكي AMPR",
/* English: ShadowMount+: failed to install ShellCore hooks */
[SM_L10N_SHELLCORE_HOOKS_FAILED] = "ShadowMount+: تعذر تثبيت خطافات ShellCore",
/* English: Target temperature %u °C → %u °C */
[SM_L10N_TEMPERATURE_TRANSITION] = "درجة الحرارة المستهدفة %u °C → %u °C",
/* English: External drive %s connected\nFree %llu.%llu GB of %llu.%llu (Block size %llu KB)\nGames on drive: %d%s */
[SM_L10N_USB_CONNECTED_SCANNING] = "تم توصيل محرك الأقراص الخارجي %s\nالمساحة المتاحة %llu.%llu من %llu.%llu غيغابايت (حجم الكتلة %llu كيلوبايت)\nالألعاب على القرص: %d%s",
/* English: \nFor maximum performance, use a cluster size of no more than 64 KB */
[SM_L10N_USB_CLUSTER_RECOMMENDATION] = "\nللحصول على أقصى أداء، استخدم حجم عنقود لا يزيد عن 64 كيلوبايت",
/* English: External drive %s disconnected */
[SM_L10N_USB_DISCONNECTED] = "تم فصل محرك الأقراص الخارجي %s",
/* English: Game %s crashed. */
[SM_L10N_GAME_CRASHED] = "تعطّلت اللعبة %s.",
/* English: Error in game %s:\n%s */
[SM_L10N_GAME_ERROR] = "خطأ في اللعبة %s:\n%s",
/* English: External drive %s connected */
[SM_L10N_USB_CONNECTED] = "تم توصيل محرك الأقراص الخارجي %s",
};
+81
View File
@@ -0,0 +1,81 @@
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\n© Drakmor and Community */
[SM_L10N_STARTUP] = "ShadowMount+ v%s\n© Drakmor a komunita",
/* English: ShadowMount+ v%s\n© Drakmor and Community\nControl: http://%s:%u */
[SM_L10N_STARTUP_WEB] =
"ShadowMount+ v%s\n© Drakmor a komunita\nOvládání: http://%s:%u",
/* 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: Emulators applied to game: %s */
[SM_L10N_GAME_EMULATORS_APPLIED] = "Emulátory použity pro hru: %s",
/* English: Backport applied to game: %s */
[SM_L10N_GAME_BACKPORTED] = "Backport hry použit: %s",
/* English: Backport applied to game: %s\nEmulators updated */
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Backport hry použit: %s\nEmulátory aktualizovány",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Připojení obrazu odmítnuto:\n%s\nVelikost clusteru je příliš malá (%llu < %u).\nUloženo přepsání image_sector: %u.\nZkuste připojit znovu.",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "Připojení obrazu odmítnuto:\n%s\nVelikost clusteru je příliš malá (%llu < %u).\nPoužijte v obrazu větší cluster nebo v konfiguraci menší velikost sektoru.",
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "Registrace selhala: %s (%s)\nAppInstallTitleDir není dostupné",
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "Registrace selhala: %s (%s)\nMost TitleDir není dostupný",
/* English: Register failed: %s (%s)\ncode=0x%08X */
[SM_L10N_REGISTER_FAILED_CODE] = "Registrace selhala: %s (%s)\nkód=0x%08X",
/* English: Installing: %s (%s)... */
[SM_L10N_INSTALLING] = "Instalace: %s (%s)...",
/* English: 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: 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: Missing/invalid param.json:\n%s */
[SM_L10N_MISSING_PARAM] = "Chybí nebo je neplatný param.json:\n%s",
/* English: Found %d new games. Processing... */
[SM_L10N_FOUND_NEW_GAMES] = "Nalezeno %d nových her. Zpracovávám...",
/* English: Library synchronized.\nFound %d games. */
[SM_L10N_LIBRARY_SYNCED] = "Knihovna synchronizována.\nNalezeno %d her.",
/* English: ShadowMount+: config reloaded. */
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: konfigurace byla znovu načtena.",
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
[SM_L10N_DUPLICATE_IGNORED] = "Duplicitní %s ignorováno:\n%s\nexistující: %s",
/* English: AMPR emulator updated */
[SM_L10N_AMPR_EMULATOR_UPDATED] = "Emulátor AMPR byl aktualizován",
/* English: ShadowMount+: failed to install ShellCore hooks */
[SM_L10N_SHELLCORE_HOOKS_FAILED] = "ShadowMount+: instalace hooků ShellCore se nezdařila",
/* English: Target temperature %u °C → %u °C */
[SM_L10N_TEMPERATURE_TRANSITION] = "Cílová teplota %u °C → %u °C",
/* English: External drive %s connected\nFree %llu.%llu GB of %llu.%llu (Block size %llu KB)\nGames on drive: %d%s */
[SM_L10N_USB_CONNECTED_SCANNING] = "Externí disk %s připojen.\nVolné místo: %llu.%llu z %llu.%llu GB (blok %llu KB)\nHer na disku: %d%s",
/* English: \nFor maximum performance, use a cluster size of no more than 64 KB */
[SM_L10N_USB_CLUSTER_RECOMMENDATION] = "\nPro maximální výkon použijte velikost clusteru nejvýše 64 KB",
/* English: External drive %s disconnected */
[SM_L10N_USB_DISCONNECTED] = "Externí disk %s odpojen",
/* English: Game %s crashed. */
[SM_L10N_GAME_CRASHED] = "Hra %s spadla.",
/* English: Error in game %s:\n%s */
[SM_L10N_GAME_ERROR] = "Chyba ve hře %s:\n%s",
/* English: External drive %s connected */
[SM_L10N_USB_CONNECTED] = "Externí disk %s připojen.",
};
+81
View File
@@ -0,0 +1,81 @@
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\n© Drakmor and Community */
[SM_L10N_STARTUP] = "ShadowMount+ v%s\n© Drakmor og fællesskabet",
/* English: ShadowMount+ v%s\n© Drakmor and Community\nControl: http://%s:%u */
[SM_L10N_STARTUP_WEB] =
"ShadowMount+ v%s\n© Drakmor og fællesskabet\nStyring: http://%s:%u",
/* 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: Emulators applied to game: %s */
[SM_L10N_GAME_EMULATORS_APPLIED] = "Emulatorer anvendt på spil: %s",
/* English: Backport applied to game: %s */
[SM_L10N_GAME_BACKPORTED] = "Spil backportet: %s",
/* English: Backport applied to game: %s\nEmulators updated */
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Spil backportet: %s\nEmulatorer opdateret",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Montering af image afvist:\n%s\nKlyngestørrelsen er for lille (%llu < %u).\nimage_sector-tilsidesættelse gemt: %u.\nPrøv at montere igen.",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "Montering af image afvist:\n%s\nKlyngestørrelsen er for lille (%llu < %u).\nBrug en større klyngestørrelse i imaget eller en mindre sektorstørrelse i konfigurationen.",
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "Registrering mislykkedes: %s (%s)\nAppInstallTitleDir ikke tilgængelig",
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "Registrering mislykkedes: %s (%s)\nTitleDir-broen er ikke tilgængelig",
/* English: Register failed: %s (%s)\ncode=0x%08X */
[SM_L10N_REGISTER_FAILED_CODE] = "Registrering mislykkedes: %s (%s)\nkode=0x%08X",
/* English: Installing: %s (%s)... */
[SM_L10N_INSTALLING] = "Installerer: %s (%s)...",
/* English: 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: 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: Missing/invalid param.json:\n%s */
[SM_L10N_MISSING_PARAM] = "param.json mangler eller er ugyldig:\n%s",
/* English: Found %d new games. Processing... */
[SM_L10N_FOUND_NEW_GAMES] = "Fandt %d nye spil. Behandler...",
/* English: Library synchronized.\nFound %d games. */
[SM_L10N_LIBRARY_SYNCED] = "Bibliotek synkroniseret.\nFandt %d spil.",
/* English: ShadowMount+: config reloaded. */
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: konfigurationen blev genindlæst.",
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
[SM_L10N_DUPLICATE_IGNORED] = "Dublet %s ignoreret:\n%s\neksisterende: %s",
/* English: AMPR emulator updated */
[SM_L10N_AMPR_EMULATOR_UPDATED] = "AMPR-emulator opdateret",
/* English: ShadowMount+: failed to install ShellCore hooks */
[SM_L10N_SHELLCORE_HOOKS_FAILED] = "ShadowMount+: ShellCore-hooks kunne ikke installeres",
/* English: Target temperature %u °C → %u °C */
[SM_L10N_TEMPERATURE_TRANSITION] = "Måltemperatur %u °C → %u °C",
/* English: External drive %s connected\nFree %llu.%llu GB of %llu.%llu (Block size %llu KB)\nGames on drive: %d%s */
[SM_L10N_USB_CONNECTED_SCANNING] = "Eksternt drev %s tilsluttet.\nLedig plads: %llu.%llu af %llu.%llu GB (blok %llu KB)\nSpil på drevet: %d%s",
/* English: \nFor maximum performance, use a cluster size of no more than 64 KB */
[SM_L10N_USB_CLUSTER_RECOMMENDATION] = "\nBrug en klyngestørrelse på højst 64 KB for maksimal ydeevne",
/* English: External drive %s disconnected */
[SM_L10N_USB_DISCONNECTED] = "Eksternt drev %s frakoblet",
/* English: Game %s crashed. */
[SM_L10N_GAME_CRASHED] = "Spillet %s gik ned.",
/* English: Error in game %s:\n%s */
[SM_L10N_GAME_ERROR] = "Fejl i spillet %s:\n%s",
/* English: External drive %s connected */
[SM_L10N_USB_CONNECTED] = "Eksternt drev %s tilsluttet.",
};
+81
View File
@@ -0,0 +1,81 @@
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\n© Drakmor and Community */
[SM_L10N_STARTUP] = "ShadowMount+ v%s\n© Drakmor und die Community",
/* English: ShadowMount+ v%s\n© Drakmor and Community\nControl: http://%s:%u */
[SM_L10N_STARTUP_WEB] =
"ShadowMount+ v%s\n© Drakmor und die Community\nSteuerung: http://%s:%u",
/* 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: Emulators applied to game: %s */
[SM_L10N_GAME_EMULATORS_APPLIED] = "Emulatoren auf Spiel angewendet: %s",
/* English: Backport applied to game: %s */
[SM_L10N_GAME_BACKPORTED] = "Spiel backportiert: %s",
/* English: Backport applied to game: %s\nEmulators updated */
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Spiel backportiert: %s\nEmulatoren aktualisiert",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Einbindung des Images abgelehnt:\n%s\nDie Clustergröße ist zu klein (%llu < %u).\nimage_sector-Überschreibung gespeichert: %u.\nVersuche es erneut.",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "Einbindung des Images abgelehnt:\n%s\nDie Clustergröße ist zu klein (%llu < %u).\nVerwende im Image eine größere Clustergröße oder in der Konfiguration eine kleinere Sektorgröße.",
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "Registrierung fehlgeschlagen: %s (%s)\nAppInstallTitleDir nicht verfügbar",
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "Registrierung fehlgeschlagen: %s (%s)\nTitleDir-Bridge nicht verfügbar",
/* English: Register failed: %s (%s)\ncode=0x%08X */
[SM_L10N_REGISTER_FAILED_CODE] = "Registrierung fehlgeschlagen: %s (%s)\nCode=0x%08X",
/* English: Installing: %s (%s)... */
[SM_L10N_INSTALLING] = "Installiere: %s (%s)...",
/* English: 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: 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: Missing/invalid param.json:\n%s */
[SM_L10N_MISSING_PARAM] = "param.json fehlt oder ist ungültig:\n%s",
/* English: Found %d new games. Processing... */
[SM_L10N_FOUND_NEW_GAMES] = "%d neue Spiele gefunden. Verarbeitung läuft...",
/* English: Library synchronized.\nFound %d games. */
[SM_L10N_LIBRARY_SYNCED] = "Bibliothek synchronisiert.\n%d Spiele gefunden.",
/* English: ShadowMount+: config reloaded. */
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: Konfiguration neu geladen.",
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
[SM_L10N_DUPLICATE_IGNORED] = "Duplikat %s ignoriert:\n%s\nvorhanden: %s",
/* English: AMPR emulator updated */
[SM_L10N_AMPR_EMULATOR_UPDATED] = "AMPR-Emulator aktualisiert",
/* English: ShadowMount+: failed to install ShellCore hooks */
[SM_L10N_SHELLCORE_HOOKS_FAILED] = "ShadowMount+: ShellCore-Hooks konnten nicht installiert werden",
/* English: Target temperature %u °C → %u °C */
[SM_L10N_TEMPERATURE_TRANSITION] = "Zieltemperatur %u °C → %u °C",
/* English: External drive %s connected\nFree %llu.%llu GB of %llu.%llu (Block size %llu KB)\nGames on drive: %d%s */
[SM_L10N_USB_CONNECTED_SCANNING] = "Externes Laufwerk %s verbunden.\nFrei: %llu.%llu von %llu.%llu GB (Blockgröße %llu KB)\nSpiele auf dem Laufwerk: %d%s",
/* English: \nFor maximum performance, use a cluster size of no more than 64 KB */
[SM_L10N_USB_CLUSTER_RECOMMENDATION] = "\nVerwenden Sie für maximale Leistung eine Clustergröße von höchstens 64 KB",
/* English: External drive %s disconnected */
[SM_L10N_USB_DISCONNECTED] = "Externes Laufwerk %s getrennt",
/* English: Game %s crashed. */
[SM_L10N_GAME_CRASHED] = "Das Spiel %s ist abgestürzt.",
/* English: Error in game %s:\n%s */
[SM_L10N_GAME_ERROR] = "Fehler im Spiel %s:\n%s",
/* English: External drive %s connected */
[SM_L10N_USB_CONNECTED] = "Externes Laufwerk %s verbunden.",
};
+81
View File
@@ -0,0 +1,81 @@
static const char *const g_el_gr[SM_L10N_COUNT] = {
/* English: Go to Debug Settings */
[SM_L10N_ACTION_DEBUG_SETTINGS] = "Μετάβαση στις ρυθμίσεις αποσφαλμάτωσης",
/* English: ShadowMount+ v%s\n© Drakmor and Community */
[SM_L10N_STARTUP] = "ShadowMount+ v%s\n© Drakmor και η κοινότητα",
/* English: ShadowMount+ v%s\n© Drakmor and Community\nControl: http://%s:%u */
[SM_L10N_STARTUP_WEB] =
"ShadowMount+ v%s\n© Drakmor και η κοινότητα\nΈλεγχος: http://%s:%u",
/* 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: Emulators applied to game: %s */
[SM_L10N_GAME_EMULATORS_APPLIED] = "Εφαρμόστηκαν εξομοιωτές στο παιχνίδι: %s",
/* English: Backport applied to game: %s */
[SM_L10N_GAME_BACKPORTED] = "Εφαρμόστηκε backport στο παιχνίδι: %s",
/* English: Backport applied to game: %s\nEmulators updated */
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Εφαρμόστηκε backport στο παιχνίδι: %s\nΟι εξομοιωτές ενημερώθηκαν",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Η προσάρτηση του ειδώλου απορρίφθηκε:\n%s\nΤο μέγεθος συστοιχίας είναι πολύ μικρό (%llu < %u).\nΑποθηκεύτηκε η τιμή image_sector: %u.\nΔοκιμάστε ξανά την προσάρτηση.",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "Η προσάρτηση του ειδώλου απορρίφθηκε:\n%s\nΤο μέγεθος συστοιχίας είναι πολύ μικρό (%llu < %u).\nΧρησιμοποιήστε μεγαλύτερη συστοιχία στο είδωλο ή μικρότερο μέγεθος τομέα στις ρυθμίσεις.",
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "Αποτυχία εγγραφής: %s (%s)\nΤο AppInstallTitleDir δεν είναι διαθέσιμο",
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "Η εγγραφή απέτυχε: %s (%s)\nΗ γέφυρα TitleDir δεν είναι διαθέσιμη",
/* English: Register failed: %s (%s)\ncode=0x%08X */
[SM_L10N_REGISTER_FAILED_CODE] = "Αποτυχία εγγραφής: %s (%s)\nκωδικός=0x%08X",
/* English: Installing: %s (%s)... */
[SM_L10N_INSTALLING] = "Εγκατάσταση: %s (%s)...",
/* English: 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: 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: Missing/invalid param.json:\n%s */
[SM_L10N_MISSING_PARAM] = "Λείπει ή είναι άκυρο το param.json:\n%s",
/* English: Found %d new games. Processing... */
[SM_L10N_FOUND_NEW_GAMES] = "Βρέθηκαν %d νέα παιχνίδια. Γίνεται επεξεργασία...",
/* English: Library synchronized.\nFound %d games. */
[SM_L10N_LIBRARY_SYNCED] = "Η βιβλιοθήκη συγχρονίστηκε.\nΒρέθηκαν %d παιχνίδια.",
/* English: ShadowMount+: config reloaded. */
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: οι ρυθμίσεις επαναφορτώθηκαν.",
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
[SM_L10N_DUPLICATE_IGNORED] = "Το διπλότυπο %s αγνοήθηκε:\n%s\nυπάρχον: %s",
/* English: AMPR emulator updated */
[SM_L10N_AMPR_EMULATOR_UPDATED] = "Ο εξομοιωτής AMPR ενημερώθηκε",
/* English: ShadowMount+: failed to install ShellCore hooks */
[SM_L10N_SHELLCORE_HOOKS_FAILED] = "ShadowMount+: απέτυχε η εγκατάσταση των hooks του ShellCore",
/* English: Target temperature %u °C → %u °C */
[SM_L10N_TEMPERATURE_TRANSITION] = "Θερμοκρασία-στόχος %u °C → %u °C",
/* English: External drive %s connected\nFree %llu.%llu GB of %llu.%llu (Block size %llu KB)\nGames on drive: %d%s */
[SM_L10N_USB_CONNECTED_SCANNING] = "Ο εξωτερικός δίσκος %s συνδέθηκε.\nΕλεύθερα: %llu.%llu από %llu.%llu GB (μέγεθος μπλοκ %llu KB)\nΠαιχνίδια στον δίσκο: %d%s",
/* English: \nFor maximum performance, use a cluster size of no more than 64 KB */
[SM_L10N_USB_CLUSTER_RECOMMENDATION] = "\nΓια μέγιστη απόδοση, χρησιμοποιήστε μέγεθος συμπλέγματος έως 64 KB",
/* English: External drive %s disconnected */
[SM_L10N_USB_DISCONNECTED] = "Ο εξωτερικός δίσκος %s αποσυνδέθηκε",
/* English: Game %s crashed. */
[SM_L10N_GAME_CRASHED] = "Το παιχνίδι %s κατέρρευσε.",
/* English: Error in game %s:\n%s */
[SM_L10N_GAME_ERROR] = "Σφάλμα στο παιχνίδι %s:\n%s",
/* English: External drive %s connected */
[SM_L10N_USB_CONNECTED] = "Ο εξωτερικός δίσκος %s συνδέθηκε.",
};
+81
View File
@@ -0,0 +1,81 @@
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\n© Drakmor and Community */
[SM_L10N_STARTUP] = "ShadowMount+ v%s\n© Drakmor and Community",
/* English: ShadowMount+ v%s\n© Drakmor and Community\nControl: http://%s:%u */
[SM_L10N_STARTUP_WEB] =
"ShadowMount+ v%s\n© Drakmor and Community\nControl: http://%s:%u",
/* 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: Emulators applied to game: %s */
[SM_L10N_GAME_EMULATORS_APPLIED] = "Emulators applied to game: %s",
/* English: Backport applied to game: %s */
[SM_L10N_GAME_BACKPORTED] = "Backport applied to game: %s",
/* English: Backport applied to game: %s\nEmulators updated */
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Backport applied to game: %s\nEmulators updated",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again.",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config.",
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "Register failed: %s (%s)\nAppInstallTitleDir unavailable",
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "Register failed: %s (%s)\nTitleDir bridge unavailable",
/* English: Register failed: %s (%s)\ncode=0x%08X */
[SM_L10N_REGISTER_FAILED_CODE] = "Register failed: %s (%s)\ncode=0x%08X",
/* English: Installing: %s (%s)... */
[SM_L10N_INSTALLING] = "Installing: %s (%s)...",
/* English: 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: 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: Missing/invalid param.json:\n%s */
[SM_L10N_MISSING_PARAM] = "Missing/invalid param.json:\n%s",
/* English: Found %d new games. Processing... */
[SM_L10N_FOUND_NEW_GAMES] = "Found %d new games. Processing...",
/* English: Library synchronized.\nFound %d games. */
[SM_L10N_LIBRARY_SYNCED] = "Library synchronised.\nFound %d games.",
/* English: ShadowMount+: config reloaded. */
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: config reloaded.",
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
[SM_L10N_DUPLICATE_IGNORED] = "Duplicate %s ignored:\n%s\nexisting: %s",
/* English: AMPR emulator updated */
[SM_L10N_AMPR_EMULATOR_UPDATED] = "AMPR emulator updated",
/* English: ShadowMount+: failed to install ShellCore hooks */
[SM_L10N_SHELLCORE_HOOKS_FAILED] = "ShadowMount+: failed to install ShellCore hooks",
/* English: Target temperature %u °C → %u °C */
[SM_L10N_TEMPERATURE_TRANSITION] = "Target temperature %u °C → %u °C",
/* English: External drive %s connected\nFree %llu.%llu GB of %llu.%llu (Block size %llu KB)\nGames on drive: %d%s */
[SM_L10N_USB_CONNECTED_SCANNING] = "External drive %s connected\nFree %llu.%llu GB of %llu.%llu (Block size %llu KB)\nGames on drive: %d%s",
/* English: \nFor maximum performance, use a cluster size of no more than 64 KB */
[SM_L10N_USB_CLUSTER_RECOMMENDATION] = "\nFor maximum performance, use a cluster size of no more than 64 KB",
/* English: External drive %s disconnected */
[SM_L10N_USB_DISCONNECTED] = "External drive %s disconnected",
/* English: Game %s crashed. */
[SM_L10N_GAME_CRASHED] = "Game %s crashed.",
/* English: Error in game %s:\n%s */
[SM_L10N_GAME_ERROR] = "Error in game %s:\n%s",
/* English: External drive %s connected */
[SM_L10N_USB_CONNECTED] = "External drive %s connected",
};
+64
View File
@@ -0,0 +1,64 @@
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\n© Drakmor and Community",
[SM_L10N_STARTUP_WEB] =
"ShadowMount+ v%s\n© Drakmor and Community\nControl: http://%s:%u",
[SM_L10N_RECURSIVE_SCAN_DEPRECATED] =
"ShadowMount+: recursive_scan=1 is deprecated, using scan_depth=2.",
[SM_L10N_SCAN_DEPTH_ENABLED] = "ShadowMount+: scan depth %u enabled.",
[SM_L10N_BACKPORT_OVERLAY_FAILED] =
"Backport overlay failed: %s\n%s\n0x%08X",
[SM_L10N_GAME_BACKPORTED] = "Backport applied to game: %s",
[SM_L10N_GAME_EMULATORS_APPLIED] = "Emulators applied to game: %s",
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] =
"Backport applied to game: %s\nEmulators updated",
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] =
"Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\n"
"Saved image_sector override: %u.\nTry mounting again.",
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] =
"Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\n"
"Use a larger cluster size in the image or lower sector size in config.",
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] =
"Register failed: %s (%s)\nAppInstallTitleDir unavailable",
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] =
"Register failed: %s (%s)\nTitleDir bridge unavailable",
[SM_L10N_REGISTER_FAILED_CODE] = "Register failed: %s (%s)\ncode=0x%08X",
[SM_L10N_INSTALLING] = "Installing: %s (%s)...",
[SM_L10N_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_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_MISSING_PARAM] = "Missing/invalid param.json:\n%s",
[SM_L10N_FOUND_NEW_GAMES] = "Found %d new games. Processing...",
[SM_L10N_LIBRARY_SYNCED] = "Library synchronized.\nFound %d games.",
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: config reloaded.",
[SM_L10N_DUPLICATE_IGNORED] =
"Duplicate %s ignored:\n%s\nexisting: %s",
[SM_L10N_AMPR_EMULATOR_UPDATED] = "AMPR emulator updated",
[SM_L10N_SHELLCORE_HOOKS_FAILED] =
"ShadowMount+: failed to install ShellCore hooks",
[SM_L10N_TEMPERATURE_TRANSITION] =
"Target temperature %u °C → %u °C",
[SM_L10N_USB_CONNECTED_SCANNING] =
"External drive %s connected\n"
"Free %llu.%llu GB of %llu.%llu (Block size %llu KB)\n"
"Games on drive: %d%s",
[SM_L10N_USB_CLUSTER_RECOMMENDATION] =
"\nFor maximum performance, use a cluster size of no more than 64 KB",
[SM_L10N_USB_DISCONNECTED] = "External drive %s disconnected",
[SM_L10N_GAME_CRASHED] = "Game %s crashed.",
[SM_L10N_GAME_ERROR] = "Error in game %s:\n%s",
[SM_L10N_USB_CONNECTED] = "External drive %s connected",
};
+81
View File
@@ -0,0 +1,81 @@
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\n© Drakmor and Community */
[SM_L10N_STARTUP] = "ShadowMount+ v%s\n© Drakmor y la comunidad",
/* English: ShadowMount+ v%s\n© Drakmor and Community\nControl: http://%s:%u */
[SM_L10N_STARTUP_WEB] =
"ShadowMount+ v%s\n© Drakmor y la comunidad\nControl: http://%s:%u",
/* 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: Emulators applied to game: %s */
[SM_L10N_GAME_EMULATORS_APPLIED] = "Emuladores aplicados al juego: %s",
/* English: Backport applied to game: %s */
[SM_L10N_GAME_BACKPORTED] = "Juego con backport aplicado: %s",
/* English: Backport applied to game: %s\nEmulators updated */
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Juego con backport aplicado: %s\nEmuladores actualizados",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Montaje de imagen rechazado:\n%s\nEl tamaño de clúster es demasiado pequeño (%llu < %u).\nSe guardó el valor de image_sector: %u.\nIntente montar de nuevo.",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "Montaje de imagen rechazado:\n%s\nEl tamaño del clúster es demasiado pequeño (%llu < %u).\nUse un clúster mayor en la imagen o un tamaño de sector menor en la configuración.",
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "Error al registrar: %s (%s)\nAppInstallTitleDir no disponible",
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "Error de registro: %s (%s)\nEl puente TitleDir no está disponible",
/* English: Register failed: %s (%s)\ncode=0x%08X */
[SM_L10N_REGISTER_FAILED_CODE] = "Error al registrar: %s (%s)\ncódigo=0x%08X",
/* English: Installing: %s (%s)... */
[SM_L10N_INSTALLING] = "Instalando: %s (%s)...",
/* English: 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: 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: Missing/invalid param.json:\n%s */
[SM_L10N_MISSING_PARAM] = "param.json ausente o no válido:\n%s",
/* English: Found %d new games. Processing... */
[SM_L10N_FOUND_NEW_GAMES] = "Se encontraron %d juegos nuevos. Procesando...",
/* English: Library synchronized.\nFound %d games. */
[SM_L10N_LIBRARY_SYNCED] = "Biblioteca sincronizada.\nSe encontraron %d juegos.",
/* English: ShadowMount+: config reloaded. */
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: configuración recargada.",
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
[SM_L10N_DUPLICATE_IGNORED] = "Duplicado %s ignorado:\n%s\nexistente: %s",
/* English: AMPR emulator updated */
[SM_L10N_AMPR_EMULATOR_UPDATED] = "Emulador AMPR actualizado",
/* English: ShadowMount+: failed to install ShellCore hooks */
[SM_L10N_SHELLCORE_HOOKS_FAILED] = "ShadowMount+: no se pudieron instalar los hooks de ShellCore",
/* English: Target temperature %u °C → %u °C */
[SM_L10N_TEMPERATURE_TRANSITION] = "Temperatura objetivo %u °C → %u °C",
/* English: External drive %s connected\nFree %llu.%llu GB of %llu.%llu (Block size %llu KB)\nGames on drive: %d%s */
[SM_L10N_USB_CONNECTED_SCANNING] = "Unidad externa %s conectada.\nLibres: %llu.%llu de %llu.%llu GB (bloque %llu KB)\nJuegos en la unidad: %d%s",
/* English: \nFor maximum performance, use a cluster size of no more than 64 KB */
[SM_L10N_USB_CLUSTER_RECOMMENDATION] = "\nPara obtener el máximo rendimiento, use un tamaño de clúster no superior a 64 KB",
/* English: External drive %s disconnected */
[SM_L10N_USB_DISCONNECTED] = "Unidad externa %s desconectada",
/* English: Game %s crashed. */
[SM_L10N_GAME_CRASHED] = "El juego %s se ha cerrado inesperadamente.",
/* English: Error in game %s:\n%s */
[SM_L10N_GAME_ERROR] = "Error en el juego %s:\n%s",
/* English: External drive %s connected */
[SM_L10N_USB_CONNECTED] = "Unidad externa %s conectada.",
};
+81
View File
@@ -0,0 +1,81 @@
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\n© Drakmor and Community */
[SM_L10N_STARTUP] = "ShadowMount+ v%s\n© Drakmor y la comunidad",
/* English: ShadowMount+ v%s\n© Drakmor and Community\nControl: http://%s:%u */
[SM_L10N_STARTUP_WEB] =
"ShadowMount+ v%s\n© Drakmor y la comunidad\nControl: http://%s:%u",
/* 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: Emulators applied to game: %s */
[SM_L10N_GAME_EMULATORS_APPLIED] = "Emuladores aplicados al juego: %s",
/* English: Backport applied to game: %s */
[SM_L10N_GAME_BACKPORTED] = "Backport aplicado al juego: %s",
/* English: Backport applied to game: %s\nEmulators updated */
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Backport aplicado al juego: %s\nEmuladores actualizados",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Montaje de imagen rechazado:\n%s\nEl tamaño de clúster es demasiado pequeño (%llu < %u).\nSe guardó el valor de image_sector: %u.\nIntenta montar de nuevo.",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "Montaje de imagen rechazado:\n%s\nEl tamaño del clúster es demasiado pequeño (%llu < %u).\nUsa un clúster más grande en la imagen o un tamaño de sector menor en la configuración.",
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "Falló el registro: %s (%s)\nAppInstallTitleDir no disponible",
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "Error de registro: %s (%s)\nEl puente TitleDir no está disponible",
/* English: Register failed: %s (%s)\ncode=0x%08X */
[SM_L10N_REGISTER_FAILED_CODE] = "Falló el registro: %s (%s)\ncódigo=0x%08X",
/* English: Installing: %s (%s)... */
[SM_L10N_INSTALLING] = "Instalando: %s (%s)...",
/* English: 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: 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: Missing/invalid param.json:\n%s */
[SM_L10N_MISSING_PARAM] = "param.json faltante o no válido:\n%s",
/* English: Found %d new games. Processing... */
[SM_L10N_FOUND_NEW_GAMES] = "Se encontraron %d juegos nuevos. Procesando...",
/* English: Library synchronized.\nFound %d games. */
[SM_L10N_LIBRARY_SYNCED] = "Biblioteca sincronizada.\nSe encontraron %d juegos.",
/* English: ShadowMount+: config reloaded. */
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: configuración recargada.",
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
[SM_L10N_DUPLICATE_IGNORED] = "Duplicado %s ignorado:\n%s\nexistente: %s",
/* English: AMPR emulator updated */
[SM_L10N_AMPR_EMULATOR_UPDATED] = "Emulador AMPR actualizado",
/* English: ShadowMount+: failed to install ShellCore hooks */
[SM_L10N_SHELLCORE_HOOKS_FAILED] = "ShadowMount+: no se pudieron instalar los hooks de ShellCore",
/* English: Target temperature %u °C → %u °C */
[SM_L10N_TEMPERATURE_TRANSITION] = "Temperatura objetivo %u °C → %u °C",
/* English: External drive %s connected\nFree %llu.%llu GB of %llu.%llu (Block size %llu KB)\nGames on drive: %d%s */
[SM_L10N_USB_CONNECTED_SCANNING] = "Unidad externa %s conectada.\nLibres: %llu.%llu de %llu.%llu GB (bloque %llu KB)\nJuegos en la unidad: %d%s",
/* English: \nFor maximum performance, use a cluster size of no more than 64 KB */
[SM_L10N_USB_CLUSTER_RECOMMENDATION] = "\nPara obtener el máximo rendimiento, use un tamaño de clúster no superior a 64 KB",
/* English: External drive %s disconnected */
[SM_L10N_USB_DISCONNECTED] = "Unidad externa %s desconectada",
/* English: Game %s crashed. */
[SM_L10N_GAME_CRASHED] = "El juego %s se cerró inesperadamente.",
/* English: Error in game %s:\n%s */
[SM_L10N_GAME_ERROR] = "Error en el juego %s:\n%s",
/* English: External drive %s connected */
[SM_L10N_USB_CONNECTED] = "Unidad externa %s conectada.",
};
+81
View File
@@ -0,0 +1,81 @@
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\n© Drakmor and Community */
[SM_L10N_STARTUP] = "ShadowMount+ v%s\n© Drakmor ja yhteisö",
/* English: ShadowMount+ v%s\n© Drakmor and Community\nControl: http://%s:%u */
[SM_L10N_STARTUP_WEB] =
"ShadowMount+ v%s\n© Drakmor ja yhteisö\nHallinta: http://%s:%u",
/* 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: Emulators applied to game: %s */
[SM_L10N_GAME_EMULATORS_APPLIED] = "Emulaattorit otettu käyttöön pelissä: %s",
/* English: Backport applied to game: %s */
[SM_L10N_GAME_BACKPORTED] = "Peliin käytettiin backportia: %s",
/* English: Backport applied to game: %s\nEmulators updated */
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Peliin käytettiin backportia: %s\nEmulaattorit päivitetty",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Imagen liittäminen hylätty:\n%s\nKlusterikoko on liian pieni (%llu < %u).\nimage_sector-ohitus tallennettu: %u.\nYritä liittää uudelleen.",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "Levykuvan liittäminen hylättiin:\n%s\nKlusterikoko on liian pieni (%llu < %u).\nKäytä levykuvassa suurempaa klusteria tai määrityksissä pienempää sektorikokoa.",
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "Rekisteröinti epäonnistui: %s (%s)\nAppInstallTitleDir ei ole käytettävissä",
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "Rekisteröinti epäonnistui: %s (%s)\nTitleDir-silta ei ole käytettävissä",
/* English: Register failed: %s (%s)\ncode=0x%08X */
[SM_L10N_REGISTER_FAILED_CODE] = "Rekisteröinti epäonnistui: %s (%s)\nkoodi=0x%08X",
/* English: Installing: %s (%s)... */
[SM_L10N_INSTALLING] = "Asennetaan: %s (%s)...",
/* English: 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: 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: Missing/invalid param.json:\n%s */
[SM_L10N_MISSING_PARAM] = "param.json puuttuu tai on virheellinen:\n%s",
/* English: Found %d new games. Processing... */
[SM_L10N_FOUND_NEW_GAMES] = "Löytyi %d uutta peliä. Käsitellään...",
/* English: Library synchronized.\nFound %d games. */
[SM_L10N_LIBRARY_SYNCED] = "Kirjasto synkronoitu.\nLöytyi %d peliä.",
/* English: ShadowMount+: config reloaded. */
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: määritykset ladattiin uudelleen.",
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
[SM_L10N_DUPLICATE_IGNORED] = "Kaksoiskappale %s ohitettu:\n%s\nolemassa: %s",
/* English: AMPR emulator updated */
[SM_L10N_AMPR_EMULATOR_UPDATED] = "AMPR-emulaattori päivitetty",
/* English: ShadowMount+: failed to install ShellCore hooks */
[SM_L10N_SHELLCORE_HOOKS_FAILED] = "ShadowMount+: ShellCore-hookkien asennus epäonnistui",
/* English: Target temperature %u °C → %u °C */
[SM_L10N_TEMPERATURE_TRANSITION] = "Tavoitelämpötila %u °C → %u °C",
/* English: External drive %s connected\nFree %llu.%llu GB of %llu.%llu (Block size %llu KB)\nGames on drive: %d%s */
[SM_L10N_USB_CONNECTED_SCANNING] = "Ulkoinen asema %s yhdistetty.\nVapaana: %llu.%llu / %llu.%llu Gt (lohko %llu kt)\nPelejä asemalla: %d%s",
/* English: \nFor maximum performance, use a cluster size of no more than 64 KB */
[SM_L10N_USB_CLUSTER_RECOMMENDATION] = "\nSaat parhaan suorituskyvyn käyttämällä enintään 64 kt:n klusterikokoa",
/* English: External drive %s disconnected */
[SM_L10N_USB_DISCONNECTED] = "Ulkoinen asema %s irrotettu",
/* English: Game %s crashed. */
[SM_L10N_GAME_CRASHED] = "Peli %s kaatui.",
/* English: Error in game %s:\n%s */
[SM_L10N_GAME_ERROR] = "Virhe pelissä %s:\n%s",
/* English: External drive %s connected */
[SM_L10N_USB_CONNECTED] = "Ulkoinen asema %s yhdistetty.",
};
+81
View File
@@ -0,0 +1,81 @@
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\n© Drakmor and Community */
[SM_L10N_STARTUP] = "ShadowMount+ v%s\n© Drakmor et la communauté",
/* English: ShadowMount+ v%s\n© Drakmor and Community\nControl: http://%s:%u */
[SM_L10N_STARTUP_WEB] =
"ShadowMount+ v%s\n© Drakmor et la communauté\nContrôle : http://%s:%u",
/* 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: Emulators applied to game: %s */
[SM_L10N_GAME_EMULATORS_APPLIED] = "Émulateurs appliqués au jeu : %s",
/* English: Backport applied to game: %s */
[SM_L10N_GAME_BACKPORTED] = "Backport appliqué au jeu : %s",
/* English: Backport applied to game: %s\nEmulators updated */
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Backport appliqué au jeu : %s\nÉmulateurs mis à jour",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Montage de l’image refusé :\n%s\nLa taille des grappes est trop petite (%llu < %u).\nValeur image_sector enregistrée : %u.\nRéessayez de monter l’image.",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "Montage de l’image refusé :\n%s\nLa taille des grappes est trop petite (%llu < %u).\nUtilisez des grappes plus grandes dans l’image ou une taille de secteur plus petite dans la configuration.",
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "Échec de l'enregistrement : %s (%s)\nAppInstallTitleDir indisponible",
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "Échec de l’enregistrement : %s (%s)\nLe pont TitleDir n’est pas disponible",
/* English: Register failed: %s (%s)\ncode=0x%08X */
[SM_L10N_REGISTER_FAILED_CODE] = "Échec de l'enregistrement : %s (%s)\ncode=0x%08X",
/* English: Installing: %s (%s)... */
[SM_L10N_INSTALLING] = "Installation : %s (%s)...",
/* English: 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: 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: Missing/invalid param.json:\n%s */
[SM_L10N_MISSING_PARAM] = "param.json manquant ou invalide :\n%s",
/* English: Found %d new games. Processing... */
[SM_L10N_FOUND_NEW_GAMES] = "%d nouveaux jeux trouvés. Traitement en cours...",
/* English: Library synchronized.\nFound %d games. */
[SM_L10N_LIBRARY_SYNCED] = "Bibliothèque synchronisée.\n%d jeux trouvés.",
/* English: ShadowMount+: config reloaded. */
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+ : configuration rechargée.",
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
[SM_L10N_DUPLICATE_IGNORED] = "Doublon %s ignoré :\n%s\nexistant : %s",
/* English: AMPR emulator updated */
[SM_L10N_AMPR_EMULATOR_UPDATED] = "Émulateur AMPR mis à jour",
/* English: ShadowMount+: failed to install ShellCore hooks */
[SM_L10N_SHELLCORE_HOOKS_FAILED] = "ShadowMount+ : échec de l’installation des hooks ShellCore",
/* English: Target temperature %u °C → %u °C */
[SM_L10N_TEMPERATURE_TRANSITION] = "Température cible %u °C → %u °C",
/* English: External drive %s connected\nFree %llu.%llu GB of %llu.%llu (Block size %llu KB)\nGames on drive: %d%s */
[SM_L10N_USB_CONNECTED_SCANNING] = "Lecteur externe %s connecté.\nLibre : %llu.%llu sur %llu.%llu Go (bloc %llu Ko)\nJeux sur le lecteur : %d%s",
/* English: \nFor maximum performance, use a cluster size of no more than 64 KB */
[SM_L10N_USB_CLUSTER_RECOMMENDATION] = "\nPour des performances maximales, utilisez une taille de cluster ne dépassant pas 64 Ko",
/* English: External drive %s disconnected */
[SM_L10N_USB_DISCONNECTED] = "Lecteur externe %s déconnecté",
/* English: Game %s crashed. */
[SM_L10N_GAME_CRASHED] = "Le jeu %s a planté.",
/* English: Error in game %s:\n%s */
[SM_L10N_GAME_ERROR] = "Erreur dans le jeu %s :\n%s",
/* English: External drive %s connected */
[SM_L10N_USB_CONNECTED] = "Lecteur externe %s connecté.",
};
+81
View File
@@ -0,0 +1,81 @@
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\n© Drakmor and Community */
[SM_L10N_STARTUP] = "ShadowMount+ v%s\n© Drakmor et la communauté",
/* English: ShadowMount+ v%s\n© Drakmor and Community\nControl: http://%s:%u */
[SM_L10N_STARTUP_WEB] =
"ShadowMount+ v%s\n© Drakmor et la communauté\nContrôle : http://%s:%u",
/* 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: Emulators applied to game: %s */
[SM_L10N_GAME_EMULATORS_APPLIED] = "Émulateurs appliqués au jeu : %s",
/* English: Backport applied to game: %s */
[SM_L10N_GAME_BACKPORTED] = "Backport appliqué au jeu : %s",
/* English: Backport applied to game: %s\nEmulators updated */
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Backport appliqué au jeu : %s\nÉmulateurs mis à jour",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Montage de l’image refusé :\n%s\nLa taille des clusters est trop petite (%llu < %u).\nValeur image_sector enregistrée : %u.\nRéessayez de monter l’image.",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "Montage de l’image refusé :\n%s\nLa taille des clusters est trop petite (%llu < %u).\nUtilisez des clusters plus grands dans l’image ou une taille de secteur plus petite dans la configuration.",
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "Échec de l'enregistrement : %s (%s)\nAppInstallTitleDir indisponible",
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "Échec de l’enregistrement : %s (%s)\nLe pont TitleDir n’est pas disponible",
/* English: Register failed: %s (%s)\ncode=0x%08X */
[SM_L10N_REGISTER_FAILED_CODE] = "Échec de l'enregistrement : %s (%s)\ncode=0x%08X",
/* English: Installing: %s (%s)... */
[SM_L10N_INSTALLING] = "Installation : %s (%s)...",
/* English: 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: 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: Missing/invalid param.json:\n%s */
[SM_L10N_MISSING_PARAM] = "param.json manquant ou invalide :\n%s",
/* English: Found %d new games. Processing... */
[SM_L10N_FOUND_NEW_GAMES] = "%d nouveaux jeux trouvés. Traitement en cours...",
/* English: Library synchronized.\nFound %d games. */
[SM_L10N_LIBRARY_SYNCED] = "Bibliothèque synchronisée.\n%d jeux trouvés.",
/* English: ShadowMount+: config reloaded. */
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+ : configuration rechargée.",
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
[SM_L10N_DUPLICATE_IGNORED] = "Doublon %s ignoré :\n%s\nexistant : %s",
/* English: AMPR emulator updated */
[SM_L10N_AMPR_EMULATOR_UPDATED] = "Émulateur AMPR mis à jour",
/* English: ShadowMount+: failed to install ShellCore hooks */
[SM_L10N_SHELLCORE_HOOKS_FAILED] = "ShadowMount+ : échec de l’installation des hooks ShellCore",
/* English: Target temperature %u °C → %u °C */
[SM_L10N_TEMPERATURE_TRANSITION] = "Température cible %u °C → %u °C",
/* English: External drive %s connected\nFree %llu.%llu GB of %llu.%llu (Block size %llu KB)\nGames on drive: %d%s */
[SM_L10N_USB_CONNECTED_SCANNING] = "Lecteur externe %s connecté.\nLibre : %llu.%llu sur %llu.%llu Go (bloc %llu Ko)\nJeux sur le lecteur : %d%s",
/* English: \nFor maximum performance, use a cluster size of no more than 64 KB */
[SM_L10N_USB_CLUSTER_RECOMMENDATION] = "\nPour des performances maximales, utilisez une taille de cluster ne dépassant pas 64 Ko",
/* English: External drive %s disconnected */
[SM_L10N_USB_DISCONNECTED] = "Lecteur externe %s déconnecté",
/* English: Game %s crashed. */
[SM_L10N_GAME_CRASHED] = "Le jeu %s a planté.",
/* English: Error in game %s:\n%s */
[SM_L10N_GAME_ERROR] = "Erreur dans le jeu %s :\n%s",
/* English: External drive %s connected */
[SM_L10N_USB_CONNECTED] = "Lecteur externe %s connecté.",
};
+81
View File
@@ -0,0 +1,81 @@
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\n© Drakmor and Community */
[SM_L10N_STARTUP] = "ShadowMount+ v%s\n© Drakmor és a közösség",
/* English: ShadowMount+ v%s\n© Drakmor and Community\nControl: http://%s:%u */
[SM_L10N_STARTUP_WEB] =
"ShadowMount+ v%s\n© Drakmor és a közösség\nVezérlés: http://%s:%u",
/* 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: Emulators applied to game: %s */
[SM_L10N_GAME_EMULATORS_APPLIED] = "Emulátorok alkalmazva a játékhoz: %s",
/* English: Backport applied to game: %s */
[SM_L10N_GAME_BACKPORTED] = "Játék backportolva: %s",
/* English: Backport applied to game: %s\nEmulators updated */
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Játék backportolva: %s\nEmulátorok frissítve",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "A lemezkép csatolása elutasítva:\n%s\nA klaszter mérete túl kicsi (%llu < %u).\nAz image_sector felülírása mentve: %u.\nPróbáld újra csatolni.",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "A lemezkép csatolása elutasítva:\n%s\nA klaszter mérete túl kicsi (%llu < %u).\nHasználj nagyobb klasztert a lemezképben, vagy kisebb szektorméretet a beállításokban.",
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "Regisztráció sikertelen: %s (%s)\nAppInstallTitleDir nem elérhető",
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "A regisztráció sikertelen: %s (%s)\nA TitleDir-híd nem érhető el",
/* English: Register failed: %s (%s)\ncode=0x%08X */
[SM_L10N_REGISTER_FAILED_CODE] = "Regisztráció sikertelen: %s (%s)\nkód=0x%08X",
/* English: Installing: %s (%s)... */
[SM_L10N_INSTALLING] = "Telepítés: %s (%s)...",
/* English: 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: 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: Missing/invalid param.json:\n%s */
[SM_L10N_MISSING_PARAM] = "Hiányzó vagy érvénytelen param.json:\n%s",
/* English: Found %d new games. Processing... */
[SM_L10N_FOUND_NEW_GAMES] = "%d új játék található. Feldolgozás...",
/* English: Library synchronized.\nFound %d games. */
[SM_L10N_LIBRARY_SYNCED] = "Könyvtár szinkronizálva.\n%d játék található.",
/* English: ShadowMount+: config reloaded. */
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: a beállítások újratöltve.",
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
[SM_L10N_DUPLICATE_IGNORED] = "Duplikált %s kihagyva:\n%s\nlétező: %s",
/* English: AMPR emulator updated */
[SM_L10N_AMPR_EMULATOR_UPDATED] = "Az AMPR-emulátor frissítve",
/* English: ShadowMount+: failed to install ShellCore hooks */
[SM_L10N_SHELLCORE_HOOKS_FAILED] = "ShadowMount+: a ShellCore hookok telepítése sikertelen",
/* English: Target temperature %u °C → %u °C */
[SM_L10N_TEMPERATURE_TRANSITION] = "Célhőmérséklet %u °C → %u °C",
/* English: External drive %s connected\nFree %llu.%llu GB of %llu.%llu (Block size %llu KB)\nGames on drive: %d%s */
[SM_L10N_USB_CONNECTED_SCANNING] = "Külső meghajtó csatlakoztatva: %s.\nSzabad: %llu.%llu / %llu.%llu GB (blokkméret %llu KB)\nJátékok a meghajtón: %d%s",
/* English: \nFor maximum performance, use a cluster size of no more than 64 KB */
[SM_L10N_USB_CLUSTER_RECOMMENDATION] = "\nA maximális teljesítményhez használjon legfeljebb 64 KB-os klaszterméretet",
/* English: External drive %s disconnected */
[SM_L10N_USB_DISCONNECTED] = "Külső meghajtó leválasztva: %s",
/* English: Game %s crashed. */
[SM_L10N_GAME_CRASHED] = "A(z) %s játék összeomlott.",
/* English: Error in game %s:\n%s */
[SM_L10N_GAME_ERROR] = "Hiba a(z) %s játékban:\n%s",
/* English: External drive %s connected */
[SM_L10N_USB_CONNECTED] = "Külső meghajtó csatlakoztatva: %s.",
};
+81
View File
@@ -0,0 +1,81 @@
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\n© Drakmor and Community */
[SM_L10N_STARTUP] = "ShadowMount+ v%s\n© Drakmor dan Komunitas",
/* English: ShadowMount+ v%s\n© Drakmor and Community\nControl: http://%s:%u */
[SM_L10N_STARTUP_WEB] =
"ShadowMount+ v%s\n© Drakmor dan Komunitas\nKontrol: http://%s:%u",
/* 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: Emulators applied to game: %s */
[SM_L10N_GAME_EMULATORS_APPLIED] = "Emulator diterapkan pada game: %s",
/* English: Backport applied to game: %s */
[SM_L10N_GAME_BACKPORTED] = "Backport game diterapkan: %s",
/* English: Backport applied to game: %s\nEmulators updated */
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Backport game diterapkan: %s\nSemua emulator diperbarui",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Pemasangan image ditolak:\n%s\nUkuran cluster terlalu kecil (%llu < %u).\nNilai image_sector disimpan: %u.\nCoba pasang lagi.",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "Pemasangan image ditolak:\n%s\nUkuran cluster terlalu kecil (%llu < %u).\nGunakan cluster yang lebih besar pada image atau ukuran sektor yang lebih kecil pada konfigurasi.",
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "Registrasi gagal: %s (%s)\nAppInstallTitleDir tidak tersedia",
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "Registrasi gagal: %s (%s)\nBridge TitleDir tidak tersedia",
/* English: Register failed: %s (%s)\ncode=0x%08X */
[SM_L10N_REGISTER_FAILED_CODE] = "Registrasi gagal: %s (%s)\nkode=0x%08X",
/* English: Installing: %s (%s)... */
[SM_L10N_INSTALLING] = "Menginstal: %s (%s)...",
/* English: 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: 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: Missing/invalid param.json:\n%s */
[SM_L10N_MISSING_PARAM] = "param.json hilang atau tidak valid:\n%s",
/* English: Found %d new games. Processing... */
[SM_L10N_FOUND_NEW_GAMES] = "Menemukan %d game baru. Memproses...",
/* English: Library synchronized.\nFound %d games. */
[SM_L10N_LIBRARY_SYNCED] = "Pustaka disinkronkan.\nMenemukan %d game.",
/* English: ShadowMount+: config reloaded. */
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: konfigurasi dimuat ulang.",
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
[SM_L10N_DUPLICATE_IGNORED] = "Duplikat %s diabaikan:\n%s\nyang ada: %s",
/* English: AMPR emulator updated */
[SM_L10N_AMPR_EMULATOR_UPDATED] = "Emulator AMPR diperbarui",
/* English: ShadowMount+: failed to install ShellCore hooks */
[SM_L10N_SHELLCORE_HOOKS_FAILED] = "ShadowMount+: gagal memasang hook ShellCore",
/* English: Target temperature %u °C → %u °C */
[SM_L10N_TEMPERATURE_TRANSITION] = "Suhu target %u °C → %u °C",
/* English: External drive %s connected\nFree %llu.%llu GB of %llu.%llu (Block size %llu KB)\nGames on drive: %d%s */
[SM_L10N_USB_CONNECTED_SCANNING] = "Drive eksternal %s tersambung.\nKosong: %llu.%llu dari %llu.%llu GB (ukuran blok %llu KB)\nGame di drive: %d%s",
/* English: \nFor maximum performance, use a cluster size of no more than 64 KB */
[SM_L10N_USB_CLUSTER_RECOMMENDATION] = "\nUntuk performa maksimal, gunakan ukuran klaster tidak lebih dari 64 KB",
/* English: External drive %s disconnected */
[SM_L10N_USB_DISCONNECTED] = "Drive eksternal %s terputus",
/* English: Game %s crashed. */
[SM_L10N_GAME_CRASHED] = "Game %s mengalami crash.",
/* English: Error in game %s:\n%s */
[SM_L10N_GAME_ERROR] = "Kesalahan pada game %s:\n%s",
/* English: External drive %s connected */
[SM_L10N_USB_CONNECTED] = "Drive eksternal %s tersambung.",
};
+81
View File
@@ -0,0 +1,81 @@
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\n© Drakmor and Community */
[SM_L10N_STARTUP] = "ShadowMount+ v%s\n© Drakmor e la community",
/* English: ShadowMount+ v%s\n© Drakmor and Community\nControl: http://%s:%u */
[SM_L10N_STARTUP_WEB] =
"ShadowMount+ v%s\n© Drakmor e la community\nControllo: http://%s:%u",
/* 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: Emulators applied to game: %s */
[SM_L10N_GAME_EMULATORS_APPLIED] = "Emulatori applicati al gioco: %s",
/* English: Backport applied to game: %s */
[SM_L10N_GAME_BACKPORTED] = "Backport applicato al gioco: %s",
/* English: Backport applied to game: %s\nEmulators updated */
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Backport applicato al gioco: %s\nEmulatori aggiornati",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Montaggio immagine rifiutato:\n%s\nLa dimensione cluster è troppo piccola (%llu < %u).\nOverride image_sector salvato: %u.\nProva a montare di nuovo.",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "Montaggio dell’immagine rifiutato:\n%s\nLa dimensione del cluster è troppo piccola (%llu < %u).\nUsa un cluster più grande nell’immagine o una dimensione del settore più piccola nella configurazione.",
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "Registrazione non riuscita: %s (%s)\nAppInstallTitleDir non disponibile",
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "Registrazione non riuscita: %s (%s)\nBridge TitleDir non disponibile",
/* English: Register failed: %s (%s)\ncode=0x%08X */
[SM_L10N_REGISTER_FAILED_CODE] = "Registrazione non riuscita: %s (%s)\ncodice=0x%08X",
/* English: Installing: %s (%s)... */
[SM_L10N_INSTALLING] = "Installazione: %s (%s)...",
/* English: 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: 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: Missing/invalid param.json:\n%s */
[SM_L10N_MISSING_PARAM] = "param.json mancante o non valido:\n%s",
/* English: Found %d new games. Processing... */
[SM_L10N_FOUND_NEW_GAMES] = "Trovati %d nuovi giochi. Elaborazione in corso...",
/* English: Library synchronized.\nFound %d games. */
[SM_L10N_LIBRARY_SYNCED] = "Libreria sincronizzata.\nTrovati %d giochi.",
/* English: ShadowMount+: config reloaded. */
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: configurazione ricaricata.",
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
[SM_L10N_DUPLICATE_IGNORED] = "Duplicato %s ignorato:\n%s\nesistente: %s",
/* English: AMPR emulator updated */
[SM_L10N_AMPR_EMULATOR_UPDATED] = "Emulatore AMPR aggiornato",
/* English: ShadowMount+: failed to install ShellCore hooks */
[SM_L10N_SHELLCORE_HOOKS_FAILED] = "ShadowMount+: impossibile installare gli hook ShellCore",
/* English: Target temperature %u °C → %u °C */
[SM_L10N_TEMPERATURE_TRANSITION] = "Temperatura obiettivo %u °C → %u °C",
/* English: External drive %s connected\nFree %llu.%llu GB of %llu.%llu (Block size %llu KB)\nGames on drive: %d%s */
[SM_L10N_USB_CONNECTED_SCANNING] = "Unità esterna %s collegata.\nLiberi: %llu.%llu di %llu.%llu GB (blocco %llu KB)\nGiochi sull'unità: %d%s",
/* English: \nFor maximum performance, use a cluster size of no more than 64 KB */
[SM_L10N_USB_CLUSTER_RECOMMENDATION] = "\nPer prestazioni ottimali, usa una dimensione cluster non superiore a 64 KB",
/* English: External drive %s disconnected */
[SM_L10N_USB_DISCONNECTED] = "Unità esterna %s scollegata",
/* English: Game %s crashed. */
[SM_L10N_GAME_CRASHED] = "Il gioco %s si è arrestato in modo anomalo.",
/* English: Error in game %s:\n%s */
[SM_L10N_GAME_ERROR] = "Errore nel gioco %s:\n%s",
/* English: External drive %s connected */
[SM_L10N_USB_CONNECTED] = "Unità esterna %s collegata.",
};
+81
View File
@@ -0,0 +1,81 @@
static const char *const g_ja_jp[SM_L10N_COUNT] = {
/* English: Go to Debug Settings */
[SM_L10N_ACTION_DEBUG_SETTINGS] = "デバッグ設定へ移動",
/* English: ShadowMount+ v%s\n© Drakmor and Community */
[SM_L10N_STARTUP] = "ShadowMount+ v%s\n© Drakmor とコミュニティ",
/* English: ShadowMount+ v%s\n© Drakmor and Community\nControl: http://%s:%u */
[SM_L10N_STARTUP_WEB] =
"ShadowMount+ v%s\n© Drakmor とコミュニティ\n管理: http://%s:%u",
/* 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: Emulators applied to game: %s */
[SM_L10N_GAME_EMULATORS_APPLIED] = "ゲーム %s にエミュレーターを適用しました",
/* English: Backport applied to game: %s */
[SM_L10N_GAME_BACKPORTED] = "ゲームにバックポートを適用しました: %s",
/* English: Backport applied to game: %s\nEmulators updated */
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "ゲームにバックポートを適用しました: %s\n複数のエミュレーターを更新しました",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "イメージのマウントが拒否されました:\n%s\nクラスタサイズが小さすぎます (%llu < %u)。\nimage_sector override を保存しました: %u。\nもう一度マウントしてください。",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "イメージのマウントが拒否されました:\n%s\nクラスタサイズが小さすぎます (%llu < %u)。\nイメージのクラスタサイズを大きくするか、設定のセクタサイズを小さくしてください。",
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "登録に失敗しました: %s (%s)\nAppInstallTitleDir が利用できません",
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "登録に失敗しました: %s (%s)\nTitleDir ブリッジを利用できません",
/* English: Register failed: %s (%s)\ncode=0x%08X */
[SM_L10N_REGISTER_FAILED_CODE] = "登録に失敗しました: %s (%s)\nコード=0x%08X",
/* English: Installing: %s (%s)... */
[SM_L10N_INSTALLING] = "インストール中: %s (%s)...",
/* English: 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: 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: Missing/invalid param.json:\n%s */
[SM_L10N_MISSING_PARAM] = "param.json が見つからないか無効です:\n%s",
/* English: Found %d new games. Processing... */
[SM_L10N_FOUND_NEW_GAMES] = "%d 本の新しいゲームを検出しました。処理中...",
/* English: Library synchronized.\nFound %d games. */
[SM_L10N_LIBRARY_SYNCED] = "ライブラリを同期しました。\n%d 本のゲームを検出しました。",
/* English: ShadowMount+: config reloaded. */
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: 設定を再読み込みしました。",
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
[SM_L10N_DUPLICATE_IGNORED] = "重複 %s を無視しました:\n%s\n既存: %s",
/* English: AMPR emulator updated */
[SM_L10N_AMPR_EMULATOR_UPDATED] = "AMPRエミュレーターを更新しました",
/* English: ShadowMount+: failed to install ShellCore hooks */
[SM_L10N_SHELLCORE_HOOKS_FAILED] = "ShadowMount+: ShellCoreフックのインストールに失敗しました",
/* English: Target temperature %u °C → %u °C */
[SM_L10N_TEMPERATURE_TRANSITION] = "目標温度 %u °C → %u °C",
/* English: External drive %s connected\nFree %llu.%llu GB of %llu.%llu (Block size %llu KB)\nGames on drive: %d%s */
[SM_L10N_USB_CONNECTED_SCANNING] = "外付けドライブ %s が接続されました。\n空き容量: %llu.%llu / %llu.%llu GB(ブロックサイズ: %llu KB)\nドライブ上のゲーム: %d%s",
/* English: \nFor maximum performance, use a cluster size of no more than 64 KB */
[SM_L10N_USB_CLUSTER_RECOMMENDATION] = "\n最大限のパフォーマンスを得るには、クラスターサイズを 64 KB 以下にしてください",
/* English: External drive %s disconnected */
[SM_L10N_USB_DISCONNECTED] = "外付けドライブ %s が切断されました",
/* English: Game %s crashed. */
[SM_L10N_GAME_CRASHED] = "ゲーム %s がクラッシュしました。",
/* English: Error in game %s:\n%s */
[SM_L10N_GAME_ERROR] = "ゲーム %s のエラー:\n%s",
/* English: External drive %s connected */
[SM_L10N_USB_CONNECTED] = "外付けドライブ %s が接続されました。",
};
+81
View File
@@ -0,0 +1,81 @@
static const char *const g_ko_kr[SM_L10N_COUNT] = {
/* English: Go to Debug Settings */
[SM_L10N_ACTION_DEBUG_SETTINGS] = "디버그 설정으로 이동",
/* English: ShadowMount+ v%s\n© Drakmor and Community */
[SM_L10N_STARTUP] = "ShadowMount+ v%s\n© Drakmor 및 커뮤니티",
/* English: ShadowMount+ v%s\n© Drakmor and Community\nControl: http://%s:%u */
[SM_L10N_STARTUP_WEB] =
"ShadowMount+ v%s\n© Drakmor 및 커뮤니티\n관리: http://%s:%u",
/* 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: Emulators applied to game: %s */
[SM_L10N_GAME_EMULATORS_APPLIED] = "게임 %s에 에뮬레이터가 적용되었습니다",
/* English: Backport applied to game: %s */
[SM_L10N_GAME_BACKPORTED] = "게임에 백포트 적용됨: %s",
/* English: Backport applied to game: %s\nEmulators updated */
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "게임에 백포트 적용됨: %s\n에뮬레이터들이 업데이트됨",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "이미지 마운트 거부됨:\n%s\n클러스터 크기가 너무 작습니다 (%llu < %u).\nimage_sector override 저장됨: %u.\n다시 마운트해 보세요.",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "이미지 마운트가 거부되었습니다:\n%s\n클러스터 크기가 너무 작습니다(%llu < %u).\n이미지의 클러스터 크기를 늘리거나 설정의 섹터 크기를 줄이세요.",
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "등록 실패: %s (%s)\nAppInstallTitleDir 사용 불가",
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "등록 실패: %s (%s)\nTitleDir 브리지를 사용할 수 없음",
/* English: Register failed: %s (%s)\ncode=0x%08X */
[SM_L10N_REGISTER_FAILED_CODE] = "등록 실패: %s (%s)\n코드=0x%08X",
/* English: Installing: %s (%s)... */
[SM_L10N_INSTALLING] = "설치 중: %s (%s)...",
/* English: 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: 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: Missing/invalid param.json:\n%s */
[SM_L10N_MISSING_PARAM] = "param.json 누락 또는 잘못됨:\n%s",
/* English: Found %d new games. Processing... */
[SM_L10N_FOUND_NEW_GAMES] = "새 게임 %d개를 찾았습니다. 처리 중...",
/* English: Library synchronized.\nFound %d games. */
[SM_L10N_LIBRARY_SYNCED] = "라이브러리를 동기화했습니다.\n게임 %d개를 찾았습니다.",
/* English: ShadowMount+: config reloaded. */
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: 설정을 다시 불러왔습니다.",
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
[SM_L10N_DUPLICATE_IGNORED] = "중복 %s 무시됨:\n%s\n기존: %s",
/* English: AMPR emulator updated */
[SM_L10N_AMPR_EMULATOR_UPDATED] = "AMPR 에뮬레이터가 업데이트되었습니다",
/* English: ShadowMount+: failed to install ShellCore hooks */
[SM_L10N_SHELLCORE_HOOKS_FAILED] = "ShadowMount+: ShellCore 후크 설치에 실패했습니다",
/* English: Target temperature %u °C → %u °C */
[SM_L10N_TEMPERATURE_TRANSITION] = "목표 온도 %u °C → %u °C",
/* English: External drive %s connected\nFree %llu.%llu GB of %llu.%llu (Block size %llu KB)\nGames on drive: %d%s */
[SM_L10N_USB_CONNECTED_SCANNING] = "외장 드라이브 %s 연결됨.\n여유 공간: %llu.%llu / %llu.%llu GB (블록 크기: %llu KB)\n드라이브의 게임: %d%s",
/* English: \nFor maximum performance, use a cluster size of no more than 64 KB */
[SM_L10N_USB_CLUSTER_RECOMMENDATION] = "\n최대 성능을 위해 64 KB 이하의 클러스터 크기를 사용하세요",
/* English: External drive %s disconnected */
[SM_L10N_USB_DISCONNECTED] = "외장 드라이브 %s 연결 해제됨",
/* English: Game %s crashed. */
[SM_L10N_GAME_CRASHED] = "게임 %s이(가) 비정상 종료되었습니다.",
/* English: Error in game %s:\n%s */
[SM_L10N_GAME_ERROR] = "게임 %s 오류:\n%s",
/* English: External drive %s connected */
[SM_L10N_USB_CONNECTED] = "외장 드라이브 %s 연결됨.",
};
+81
View File
@@ -0,0 +1,81 @@
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\n© Drakmor and Community */
[SM_L10N_STARTUP] = "ShadowMount+ v%s\n© Drakmor en de community",
/* English: ShadowMount+ v%s\n© Drakmor and Community\nControl: http://%s:%u */
[SM_L10N_STARTUP_WEB] =
"ShadowMount+ v%s\n© Drakmor en de community\nBeheer: http://%s:%u",
/* 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: Emulators applied to game: %s */
[SM_L10N_GAME_EMULATORS_APPLIED] = "Emulators toegepast op spel: %s",
/* English: Backport applied to game: %s */
[SM_L10N_GAME_BACKPORTED] = "Backport op game toegepast: %s",
/* English: Backport applied to game: %s\nEmulators updated */
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Backport op game toegepast: %s\nEmulators bijgewerkt",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Koppelen van image geweigerd:\n%s\nDe clustergrootte is te klein (%llu < %u).\nimage_sector-waarde opgeslagen: %u.\nProbeer opnieuw te koppelen.",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "Koppelen van image geweigerd:\n%s\nDe clustergrootte is te klein (%llu < %u).\nGebruik een groter cluster in de image of een kleinere sectorgrootte in de configuratie.",
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "Registreren mislukt: %s (%s)\nAppInstallTitleDir niet beschikbaar",
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "Registratie mislukt: %s (%s)\nTitleDir-bridge niet beschikbaar",
/* English: Register failed: %s (%s)\ncode=0x%08X */
[SM_L10N_REGISTER_FAILED_CODE] = "Registreren mislukt: %s (%s)\ncode=0x%08X",
/* English: Installing: %s (%s)... */
[SM_L10N_INSTALLING] = "Installeren: %s (%s)...",
/* English: 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: 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: Missing/invalid param.json:\n%s */
[SM_L10N_MISSING_PARAM] = "param.json ontbreekt of is ongeldig:\n%s",
/* English: Found %d new games. Processing... */
[SM_L10N_FOUND_NEW_GAMES] = "%d nieuwe games gevonden. Verwerken...",
/* English: Library synchronized.\nFound %d games. */
[SM_L10N_LIBRARY_SYNCED] = "Bibliotheek gesynchroniseerd.\n%d games gevonden.",
/* English: ShadowMount+: config reloaded. */
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: configuratie opnieuw geladen.",
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
[SM_L10N_DUPLICATE_IGNORED] = "Duplicaat %s genegeerd:\n%s\nbestaand: %s",
/* English: AMPR emulator updated */
[SM_L10N_AMPR_EMULATOR_UPDATED] = "AMPR-emulator bijgewerkt",
/* English: ShadowMount+: failed to install ShellCore hooks */
[SM_L10N_SHELLCORE_HOOKS_FAILED] = "ShadowMount+: installeren van ShellCore-hooks mislukt",
/* English: Target temperature %u °C → %u °C */
[SM_L10N_TEMPERATURE_TRANSITION] = "Doeltemperatuur %u °C → %u °C",
/* English: External drive %s connected\nFree %llu.%llu GB of %llu.%llu (Block size %llu KB)\nGames on drive: %d%s */
[SM_L10N_USB_CONNECTED_SCANNING] = "Externe schijf %s aangesloten.\nVrij: %llu.%llu van %llu.%llu GB (blokgrootte %llu KB)\nGames op de schijf: %d%s",
/* English: \nFor maximum performance, use a cluster size of no more than 64 KB */
[SM_L10N_USB_CLUSTER_RECOMMENDATION] = "\nGebruik voor maximale prestaties een clustergrootte van maximaal 64 KB",
/* English: External drive %s disconnected */
[SM_L10N_USB_DISCONNECTED] = "Externe schijf %s losgekoppeld",
/* English: Game %s crashed. */
[SM_L10N_GAME_CRASHED] = "Game %s is gecrasht.",
/* English: Error in game %s:\n%s */
[SM_L10N_GAME_ERROR] = "Fout in game %s:\n%s",
/* English: External drive %s connected */
[SM_L10N_USB_CONNECTED] = "Externe schijf %s aangesloten.",
};
+81
View File
@@ -0,0 +1,81 @@
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\n© Drakmor and Community */
[SM_L10N_STARTUP] = "ShadowMount+ v%s\n© Drakmor og fellesskapet",
/* English: ShadowMount+ v%s\n© Drakmor and Community\nControl: http://%s:%u */
[SM_L10N_STARTUP_WEB] =
"ShadowMount+ v%s\n© Drakmor og fellesskapet\nKontroll: http://%s:%u",
/* 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: Emulators applied to game: %s */
[SM_L10N_GAME_EMULATORS_APPLIED] = "Emulatorer brukt på spillet: %s",
/* English: Backport applied to game: %s */
[SM_L10N_GAME_BACKPORTED] = "Backport brukt på spillet: %s",
/* English: Backport applied to game: %s\nEmulators updated */
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Backport brukt på spillet: %s\nEmulatorer oppdatert",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Montering av image avvist:\n%s\nKlyngestørrelsen er for liten (%llu < %u).\nimage_sector-overstyring lagret: %u.\nPrøv å montere igjen.",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "Montering av image avvist:\n%s\nKlyngestørrelsen er for liten (%llu < %u).\nBruk en større klyngestørrelse i imaget eller en mindre sektorstørrelse i konfigurasjonen.",
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "Registrering mislyktes: %s (%s)\nAppInstallTitleDir utilgjengelig",
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "Registrering mislyktes: %s (%s)\nTitleDir-broen er ikke tilgjengelig",
/* English: Register failed: %s (%s)\ncode=0x%08X */
[SM_L10N_REGISTER_FAILED_CODE] = "Registrering mislyktes: %s (%s)\nkode=0x%08X",
/* English: Installing: %s (%s)... */
[SM_L10N_INSTALLING] = "Installerer: %s (%s)...",
/* English: 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: 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: Missing/invalid param.json:\n%s */
[SM_L10N_MISSING_PARAM] = "param.json mangler eller er ugyldig:\n%s",
/* English: Found %d new games. Processing... */
[SM_L10N_FOUND_NEW_GAMES] = "Fant %d nye spill. Behandler...",
/* English: Library synchronized.\nFound %d games. */
[SM_L10N_LIBRARY_SYNCED] = "Bibliotek synkronisert.\nFant %d spill.",
/* English: ShadowMount+: config reloaded. */
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: konfigurasjonen ble lastet inn på nytt.",
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
[SM_L10N_DUPLICATE_IGNORED] = "Duplikat %s ignorert:\n%s\neksisterende: %s",
/* English: AMPR emulator updated */
[SM_L10N_AMPR_EMULATOR_UPDATED] = "AMPR-emulator oppdatert",
/* English: ShadowMount+: failed to install ShellCore hooks */
[SM_L10N_SHELLCORE_HOOKS_FAILED] = "ShadowMount+: kunne ikke installere ShellCore-hooks",
/* English: Target temperature %u °C → %u °C */
[SM_L10N_TEMPERATURE_TRANSITION] = "Måltemperatur %u °C → %u °C",
/* English: External drive %s connected\nFree %llu.%llu GB of %llu.%llu (Block size %llu KB)\nGames on drive: %d%s */
[SM_L10N_USB_CONNECTED_SCANNING] = "Ekstern disk %s tilkoblet.\nLedig: %llu.%llu av %llu.%llu GB (blokkstørrelse %llu KB)\nSpill på disken: %d%s",
/* English: \nFor maximum performance, use a cluster size of no more than 64 KB */
[SM_L10N_USB_CLUSTER_RECOMMENDATION] = "\nBruk en klyngestørrelse på maksimalt 64 KB for maksimal ytelse",
/* English: External drive %s disconnected */
[SM_L10N_USB_DISCONNECTED] = "Ekstern disk %s frakoblet",
/* English: Game %s crashed. */
[SM_L10N_GAME_CRASHED] = "Spillet %s krasjet.",
/* English: Error in game %s:\n%s */
[SM_L10N_GAME_ERROR] = "Feil i spillet %s:\n%s",
/* English: External drive %s connected */
[SM_L10N_USB_CONNECTED] = "Ekstern disk %s tilkoblet.",
};
+81
View File
@@ -0,0 +1,81 @@
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\n© Drakmor and Community */
[SM_L10N_STARTUP] = "ShadowMount+ v%s\n© Drakmor i społeczność",
/* English: ShadowMount+ v%s\n© Drakmor and Community\nControl: http://%s:%u */
[SM_L10N_STARTUP_WEB] =
"ShadowMount+ v%s\n© Drakmor i społeczność\nSterowanie: http://%s:%u",
/* 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: Emulators applied to game: %s */
[SM_L10N_GAME_EMULATORS_APPLIED] = "Zastosowano emulatory w grze: %s",
/* English: Backport applied to game: %s */
[SM_L10N_GAME_BACKPORTED] = "Zastosowano backport gry: %s",
/* English: Backport applied to game: %s\nEmulators updated */
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Zastosowano backport gry: %s\nEmulatory zaktualizowane",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Odrzucono montowanie obrazu:\n%s\nRozmiar klastra jest za mały (%llu < %u).\nZastosowano nadpisanie image_sector: %u.\nSpróbuj zamontować ponownie.",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "Odrzucono montowanie obrazu:\n%s\nRozmiar klastra jest za mały (%llu < %u).\nUżyj większego rozmiaru klastra w obrazie lub mniejszego rozmiaru sektora w konfiguracji.",
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "Rejestracja nie powiodła się: %s (%s)\nAppInstallTitleDir niedostępne",
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "Rejestracja nie powiodła się: %s (%s)\nMost TitleDir jest niedostępny",
/* English: Register failed: %s (%s)\ncode=0x%08X */
[SM_L10N_REGISTER_FAILED_CODE] = "Rejestracja nie powiodła się: %s (%s)\nkod=0x%08X",
/* English: Installing: %s (%s)... */
[SM_L10N_INSTALLING] = "Instalowanie: %s (%s)...",
/* English: 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: 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: Missing/invalid param.json:\n%s */
[SM_L10N_MISSING_PARAM] = "Brak pliku param.json lub plik jest nieprawidłowy:\n%s",
/* English: Found %d new games. Processing... */
[SM_L10N_FOUND_NEW_GAMES] = "Znaleziono %d nowych gier. Przetwarzanie...",
/* English: Library synchronized.\nFound %d games. */
[SM_L10N_LIBRARY_SYNCED] = "Biblioteka zsynchronizowana.\nZnaleziono %d gier.",
/* English: ShadowMount+: config reloaded. */
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: konfiguracja przeładowana.",
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
[SM_L10N_DUPLICATE_IGNORED] = "Duplikat %s zignorowany:\n%s\nistniejący: %s",
/* English: AMPR emulator updated */
[SM_L10N_AMPR_EMULATOR_UPDATED] = "Emulator AMPR został zaktualizowany",
/* English: ShadowMount+: failed to install ShellCore hooks */
[SM_L10N_SHELLCORE_HOOKS_FAILED] = "ShadowMount+: nie udało się zainstalować hooków ShellCore",
/* English: Target temperature %u °C → %u °C */
[SM_L10N_TEMPERATURE_TRANSITION] = "Temperatura docelowa %u °C → %u °C",
/* English: External drive %s connected\nFree %llu.%llu GB of %llu.%llu (Block size %llu KB)\nGames on drive: %d%s */
[SM_L10N_USB_CONNECTED_SCANNING] = "Dysk zewnętrzny %s podłączony.\nWolne: %llu.%llu z %llu.%llu GB (rozmiar bloku %llu KB)\nGry na dysku: %d%s",
/* English: \nFor maximum performance, use a cluster size of no more than 64 KB */
[SM_L10N_USB_CLUSTER_RECOMMENDATION] = "\nAby uzyskać maksymalną wydajność, użyj rozmiaru klastra nie większego niż 64 KB",
/* English: External drive %s disconnected */
[SM_L10N_USB_DISCONNECTED] = "Dysk zewnętrzny %s odłączony",
/* English: Game %s crashed. */
[SM_L10N_GAME_CRASHED] = "Gra %s uległa awarii.",
/* English: Error in game %s:\n%s */
[SM_L10N_GAME_ERROR] = "Błąd w grze %s:\n%s",
/* English: External drive %s connected */
[SM_L10N_USB_CONNECTED] = "Dysk zewnętrzny %s podłączony.",
};
+81
View File
@@ -0,0 +1,81 @@
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\n© Drakmor and Community */
[SM_L10N_STARTUP] = "ShadowMount+ v%s\n© Drakmor e a comunidade",
/* English: ShadowMount+ v%s\n© Drakmor and Community\nControl: http://%s:%u */
[SM_L10N_STARTUP_WEB] =
"ShadowMount+ v%s\n© Drakmor e a comunidade\nControle: http://%s:%u",
/* 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: Emulators applied to game: %s */
[SM_L10N_GAME_EMULATORS_APPLIED] = "Emuladores aplicados ao jogo: %s",
/* English: Backport applied to game: %s */
[SM_L10N_GAME_BACKPORTED] = "Backport aplicado ao jogo: %s",
/* English: Backport applied to game: %s\nEmulators updated */
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Backport aplicado ao jogo: %s\nEmuladores atualizados",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Montagem da imagem rejeitada:\n%s\nO tamanho do cluster é pequeno demais (%llu < %u).\nNovo valor para o parâmetro image_sector salvo: %u.\nTente montar novamente.",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "Montagem da imagem rejeitada:\n%s\nO tamanho do cluster é pequeno demais (%llu < %u).\nUse um cluster maior na imagem ou um tamanho de setor menor na configuração.",
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "Falha no registro: %s (%s)\nAppInstallTitleDir indisponível",
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "Falha no registro: %s (%s)\nA ponte TitleDir não está disponível",
/* English: Register failed: %s (%s)\ncode=0x%08X */
[SM_L10N_REGISTER_FAILED_CODE] = "Registro falhou: %s (%s)\ncódigo=0x%08X",
/* English: Installing: %s (%s)... */
[SM_L10N_INSTALLING] = "Instalando: %s (%s)...",
/* English: 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: 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: Missing/invalid param.json:\n%s */
[SM_L10N_MISSING_PARAM] = "Arquivo param.json ausente ou inválido:\n%s",
/* English: Found %d new games. Processing... */
[SM_L10N_FOUND_NEW_GAMES] = "%d novos jogos encontrados. Processando...",
/* English: Library synchronized.\nFound %d games. */
[SM_L10N_LIBRARY_SYNCED] = "Biblioteca sincronizada.\n%d jogos encontrados.",
/* English: ShadowMount+: config reloaded. */
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: configuração recarregada.",
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
[SM_L10N_DUPLICATE_IGNORED] = "Cópia duplicada de %s ignorada:\n%s\nExistente: %s",
/* English: AMPR emulator updated */
[SM_L10N_AMPR_EMULATOR_UPDATED] = "Emulador AMPR atualizado",
/* English: ShadowMount+: failed to install ShellCore hooks */
[SM_L10N_SHELLCORE_HOOKS_FAILED] = "ShadowMount+: falha ao instalar hooks do ShellCore",
/* English: Target temperature %u °C → %u °C */
[SM_L10N_TEMPERATURE_TRANSITION] = "Temperatura-alvo %u °C → %u °C",
/* English: External drive %s connected\nFree %llu.%llu GB of %llu.%llu (Block size %llu KB)\nGames on drive: %d%s */
[SM_L10N_USB_CONNECTED_SCANNING] = "Unidade externa %s conectada.\nLivre: %llu.%llu de %llu.%llu GB (bloco %llu KB)\nJogos na unidade: %d%s",
/* English: \nFor maximum performance, use a cluster size of no more than 64 KB */
[SM_L10N_USB_CLUSTER_RECOMMENDATION] = "\nPara obter o máximo desempenho, use clusters de no máximo 64 KB",
/* English: External drive %s disconnected */
[SM_L10N_USB_DISCONNECTED] = "Unidade externa %s desconectada",
/* English: Game %s crashed. */
[SM_L10N_GAME_CRASHED] = "O jogo %s travou.",
/* English: Error in game %s:\n%s */
[SM_L10N_GAME_ERROR] = "Erro no jogo %s:\n%s",
/* English: External drive %s connected */
[SM_L10N_USB_CONNECTED] = "Unidade externa %s conectada.",
};
+81
View File
@@ -0,0 +1,81 @@
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\n© Drakmor and Community */
[SM_L10N_STARTUP] = "ShadowMount+ v%s\n© Drakmor e a comunidade",
/* English: ShadowMount+ v%s\n© Drakmor and Community\nControl: http://%s:%u */
[SM_L10N_STARTUP_WEB] =
"ShadowMount+ v%s\n© Drakmor e a comunidade\nControlo: http://%s:%u",
/* 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: Emulators applied to game: %s */
[SM_L10N_GAME_EMULATORS_APPLIED] = "Emuladores aplicados ao jogo: %s",
/* English: Backport applied to game: %s */
[SM_L10N_GAME_BACKPORTED] = "Jogo com backport aplicado: %s",
/* English: Backport applied to game: %s\nEmulators updated */
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Jogo com backport aplicado: %s\nEmuladores atualizados",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Montagem da imagem rejeitada:\n%s\nO tamanho do cluster é demasiado pequeno (%llu < %u).\nSubstituição image_sector guardada: %u.\nTente montar novamente.",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "Montagem da imagem rejeitada:\n%s\nO tamanho do cluster é demasiado pequeno (%llu < %u).\nUtilize um cluster maior na imagem ou um tamanho de setor menor na configuração.",
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "Registo falhou: %s (%s)\nAppInstallTitleDir indisponível",
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "Falha no registo: %s (%s)\nA ponte TitleDir não está disponível",
/* English: Register failed: %s (%s)\ncode=0x%08X */
[SM_L10N_REGISTER_FAILED_CODE] = "Registo falhou: %s (%s)\ncódigo=0x%08X",
/* English: Installing: %s (%s)... */
[SM_L10N_INSTALLING] = "A instalar: %s (%s)...",
/* English: 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: 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: Missing/invalid param.json:\n%s */
[SM_L10N_MISSING_PARAM] = "param.json em falta ou inválido:\n%s",
/* English: Found %d new games. Processing... */
[SM_L10N_FOUND_NEW_GAMES] = "%d novos jogos encontrados. A processar...",
/* English: Library synchronized.\nFound %d games. */
[SM_L10N_LIBRARY_SYNCED] = "Biblioteca sincronizada.\n%d jogos encontrados.",
/* English: ShadowMount+: config reloaded. */
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: configuração recarregada.",
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
[SM_L10N_DUPLICATE_IGNORED] = "Duplicado %s ignorado:\n%s\nexistente: %s",
/* English: AMPR emulator updated */
[SM_L10N_AMPR_EMULATOR_UPDATED] = "Emulador AMPR atualizado",
/* English: ShadowMount+: failed to install ShellCore hooks */
[SM_L10N_SHELLCORE_HOOKS_FAILED] = "ShadowMount+: não foi possível instalar os hooks do ShellCore",
/* English: Target temperature %u °C → %u °C */
[SM_L10N_TEMPERATURE_TRANSITION] = "Temperatura-alvo %u °C → %u °C",
/* English: External drive %s connected\nFree %llu.%llu GB of %llu.%llu (Block size %llu KB)\nGames on drive: %d%s */
[SM_L10N_USB_CONNECTED_SCANNING] = "Unidade externa %s ligada.\nLivre: %llu.%llu de %llu.%llu GB (bloco %llu KB)\nJogos na unidade: %d%s",
/* English: \nFor maximum performance, use a cluster size of no more than 64 KB */
[SM_L10N_USB_CLUSTER_RECOMMENDATION] = "\nPara obter o máximo desempenho, utilize clusters de no máximo 64 KB",
/* English: External drive %s disconnected */
[SM_L10N_USB_DISCONNECTED] = "Unidade externa %s desligada",
/* English: Game %s crashed. */
[SM_L10N_GAME_CRASHED] = "O jogo %s falhou.",
/* English: Error in game %s:\n%s */
[SM_L10N_GAME_ERROR] = "Erro no jogo %s:\n%s",
/* English: External drive %s connected */
[SM_L10N_USB_CONNECTED] = "Unidade externa %s ligada.",
};
+81
View File
@@ -0,0 +1,81 @@
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\n© Drakmor and Community */
[SM_L10N_STARTUP] = "ShadowMount+ v%s\n© Drakmor și comunitatea",
/* English: ShadowMount+ v%s\n© Drakmor and Community\nControl: http://%s:%u */
[SM_L10N_STARTUP_WEB] =
"ShadowMount+ v%s\n© Drakmor și comunitatea\nControl: http://%s:%u",
/* 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: Emulators applied to game: %s */
[SM_L10N_GAME_EMULATORS_APPLIED] = "Emulatoare aplicate jocului: %s",
/* English: Backport applied to game: %s */
[SM_L10N_GAME_BACKPORTED] = "Backport aplicat jocului: %s",
/* English: Backport applied to game: %s\nEmulators updated */
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Backport aplicat jocului: %s\nEmulatoare actualizate",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Montarea imaginii respinsă:\n%s\nDimensiunea clusterului este prea mică (%llu < %u).\nOverride image_sector salvat: %u.\nÎncercați montarea din nou.",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "Montarea imaginii a fost respinsă:\n%s\nDimensiunea clusterului este prea mică (%llu < %u).\nFolosiți un cluster mai mare în imagine sau o dimensiune mai mică a sectorului în configurație.",
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "Înregistrare eșuată: %s (%s)\nAppInstallTitleDir indisponibil",
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "Înregistrarea a eșuat: %s (%s)\nPuntea TitleDir nu este disponibilă",
/* English: Register failed: %s (%s)\ncode=0x%08X */
[SM_L10N_REGISTER_FAILED_CODE] = "Înregistrare eșuată: %s (%s)\ncod=0x%08X",
/* English: Installing: %s (%s)... */
[SM_L10N_INSTALLING] = "Se instalează: %s (%s)...",
/* English: 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: 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: Missing/invalid param.json:\n%s */
[SM_L10N_MISSING_PARAM] = "param.json lipsește sau este invalid:\n%s",
/* English: Found %d new games. Processing... */
[SM_L10N_FOUND_NEW_GAMES] = "S-au găsit %d jocuri noi. Se procesează...",
/* English: Library synchronized.\nFound %d games. */
[SM_L10N_LIBRARY_SYNCED] = "Bibliotecă sincronizată.\nS-au găsit %d jocuri.",
/* English: ShadowMount+: config reloaded. */
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: configurația a fost reîncărcată.",
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
[SM_L10N_DUPLICATE_IGNORED] = "Duplicatul %s ignorat:\n%s\nexistent: %s",
/* English: AMPR emulator updated */
[SM_L10N_AMPR_EMULATOR_UPDATED] = "Emulatorul AMPR a fost actualizat",
/* English: ShadowMount+: failed to install ShellCore hooks */
[SM_L10N_SHELLCORE_HOOKS_FAILED] = "ShadowMount+: instalarea hook-urilor ShellCore a eșuat",
/* English: Target temperature %u °C → %u °C */
[SM_L10N_TEMPERATURE_TRANSITION] = "Temperatura țintă %u °C → %u °C",
/* English: External drive %s connected\nFree %llu.%llu GB of %llu.%llu (Block size %llu KB)\nGames on drive: %d%s */
[SM_L10N_USB_CONNECTED_SCANNING] = "Unitatea externă %s a fost conectată.\nLiber: %llu.%llu din %llu.%llu GB (dimensiunea blocului %llu KB)\nJocuri pe unitate: %d%s",
/* English: \nFor maximum performance, use a cluster size of no more than 64 KB */
[SM_L10N_USB_CLUSTER_RECOMMENDATION] = "\nPentru performanță maximă, utilizați o dimensiune a clusterului de cel mult 64 KB",
/* English: External drive %s disconnected */
[SM_L10N_USB_DISCONNECTED] = "Unitatea externă %s a fost deconectată",
/* English: Game %s crashed. */
[SM_L10N_GAME_CRASHED] = "Jocul %s s-a blocat.",
/* English: Error in game %s:\n%s */
[SM_L10N_GAME_ERROR] = "Eroare în jocul %s:\n%s",
/* English: External drive %s connected */
[SM_L10N_USB_CONNECTED] = "Unitatea externă %s a fost conectată.",
};
+81
View File
@@ -0,0 +1,81 @@
static const char *const g_ru_ru[SM_L10N_COUNT] = {
/* English: Go to Debug Settings */
[SM_L10N_ACTION_DEBUG_SETTINGS] = "Перейти к настройкам отладки",
/* English: ShadowMount+ v%s\n© Drakmor and Community */
[SM_L10N_STARTUP] = "ShadowMount+ v%s\n© Drakmor и Сообщество",
/* English: ShadowMount+ v%s\n© Drakmor and Community\nControl: http://%s:%u */
[SM_L10N_STARTUP_WEB] =
"ShadowMount+ v%s\n© Drakmor и Сообщество\nУправление: http://%s:%u",
/* 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: Emulators applied to game: %s */
[SM_L10N_GAME_EMULATORS_APPLIED] = "К игре %s применены эмуляторы",
/* English: Backport applied to game: %s\nEmulators updated */
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "К игре %s применён бэкпорт\nЭмуляторы обновлены",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Не удалось смонтировать образ:\n%s\nРазмер кластера слишком мал (%llu < %u).\nСохранено значение image_sector: %u.\nПопробуйте смонтировать снова.",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "Не удалось смонтировать образ:\n%s\nРазмер кластера слишком мал (%llu < %u).\nУвеличьте размер кластера в образе или уменьшите размер сектора в конфигурации.",
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "Не удалось зарегистрировать: %s (%s)\nAppInstallTitleDir недоступен",
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "Не удалось зарегистрировать: %s (%s)\nМост TitleDir недоступен",
/* English: Register failed: %s (%s)\ncode=0x%08X */
[SM_L10N_REGISTER_FAILED_CODE] = "Не удалось зарегистрировать: %s (%s)\nкод=0x%08X",
/* English: Installing: %s (%s)... */
[SM_L10N_INSTALLING] = "Устанавливается: %s (%s)...",
/* English: 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: 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: Missing/invalid param.json:\n%s */
[SM_L10N_MISSING_PARAM] = "Отсутствует или некорректен файл param.json:\n%s",
/* English: Found %d new games. Processing... */
[SM_L10N_FOUND_NEW_GAMES] = "Найдено %d новых игр.\nВыполняется обновление библиотеки.",
/* English: Library synchronized.\nFound %d games. */
[SM_L10N_LIBRARY_SYNCED] = "Библиотека обновлена\nНайдено %d игр(ы)",
/* English: ShadowMount+: config reloaded. */
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: конфигурация перезагружена.",
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
[SM_L10N_DUPLICATE_IGNORED] = "Дубликат %s пропущен:\n%s\nуже существует: %s",
/* English: AMPR emulator updated */
[SM_L10N_AMPR_EMULATOR_UPDATED] = "Эмулятор AMPR обновлён",
/* English: ShadowMount+: failed to install ShellCore hooks */
[SM_L10N_SHELLCORE_HOOKS_FAILED] = "ShadowMount+: не удалось установить хуки ShellCore",
/* English: Target temperature %u °C → %u °C */
[SM_L10N_TEMPERATURE_TRANSITION] = "Целевая температура %u °C → %u °C",
/* English: External drive %s connected\nFree %llu.%llu GB of %llu.%llu (Block size %llu KB)\nGames on drive: %d%s */
[SM_L10N_USB_CONNECTED_SCANNING] = "Внешний диск %s подключён.\nСвободно %llu.%llu из %llu.%llu ГБ (блок %llu КБ)\nИгр на диске: %d%s",
/* English: \nFor maximum performance, use a cluster size of no more than 64 KB */
[SM_L10N_USB_CLUSTER_RECOMMENDATION] = "\nДля максимальной производительности используйте кластер не более 64 КБ",
/* English: External drive %s disconnected */
[SM_L10N_USB_DISCONNECTED] = "Внешний диск %s отключён.",
/* English: Game %s crashed. */
[SM_L10N_GAME_CRASHED] = "Игра %s аварийно завершилась.",
/* English: Error in game %s:\n%s */
[SM_L10N_GAME_ERROR] = "Ошибка в игре %s:\n%s",
/* English: External drive %s connected */
[SM_L10N_USB_CONNECTED] = "Внешний диск %s подключён.",
};
+81
View File
@@ -0,0 +1,81 @@
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\n© Drakmor and Community */
[SM_L10N_STARTUP] = "ShadowMount+ v%s\n© Drakmor och gemenskapen",
/* English: ShadowMount+ v%s\n© Drakmor and Community\nControl: http://%s:%u */
[SM_L10N_STARTUP_WEB] =
"ShadowMount+ v%s\n© Drakmor och gemenskapen\nStyrning: http://%s:%u",
/* 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: Emulators applied to game: %s */
[SM_L10N_GAME_EMULATORS_APPLIED] = "Emulatorer tillämpade på spelet: %s",
/* English: Backport applied to game: %s */
[SM_L10N_GAME_BACKPORTED] = "Backport tillämpad på spelet: %s",
/* English: Backport applied to game: %s\nEmulators updated */
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Backport tillämpad på spelet: %s\nEmulatorer uppdaterade",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Image-montering nekades:\n%s\nKlusterstorleken är för liten (%llu < %u).\nimage_sector-åsidosättning sparad: %u.\nFörsök montera igen.",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "Montering av image nekades:\n%s\nKlusterstorleken är för liten (%llu < %u).\nAnvänd en större klusterstorlek i imagen eller en mindre sektorstorlek i konfigurationen.",
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "Registrering misslyckades: %s (%s)\nAppInstallTitleDir inte tillgänglig",
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "Registreringen misslyckades: %s (%s)\nTitleDir-bryggan är inte tillgänglig",
/* English: Register failed: %s (%s)\ncode=0x%08X */
[SM_L10N_REGISTER_FAILED_CODE] = "Registrering misslyckades: %s (%s)\nkod=0x%08X",
/* English: Installing: %s (%s)... */
[SM_L10N_INSTALLING] = "Installerar: %s (%s)...",
/* English: 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: 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: Missing/invalid param.json:\n%s */
[SM_L10N_MISSING_PARAM] = "param.json saknas eller är ogiltig:\n%s",
/* English: Found %d new games. Processing... */
[SM_L10N_FOUND_NEW_GAMES] = "Hittade %d nya spel. Bearbetar...",
/* English: Library synchronized.\nFound %d games. */
[SM_L10N_LIBRARY_SYNCED] = "Bibliotek synkroniserat.\nHittade %d spel.",
/* English: ShadowMount+: config reloaded. */
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: konfigurationen lästes in på nytt.",
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
[SM_L10N_DUPLICATE_IGNORED] = "Dubblett %s ignorerad:\n%s\nbefintlig: %s",
/* English: AMPR emulator updated */
[SM_L10N_AMPR_EMULATOR_UPDATED] = "AMPR-emulatorn har uppdaterats",
/* English: ShadowMount+: failed to install ShellCore hooks */
[SM_L10N_SHELLCORE_HOOKS_FAILED] = "ShadowMount+: det gick inte att installera ShellCore-hooks",
/* English: Target temperature %u °C → %u °C */
[SM_L10N_TEMPERATURE_TRANSITION] = "Måltemperatur %u °C → %u °C",
/* English: External drive %s connected\nFree %llu.%llu GB of %llu.%llu (Block size %llu KB)\nGames on drive: %d%s */
[SM_L10N_USB_CONNECTED_SCANNING] = "Extern enhet %s ansluten.\nLedigt: %llu.%llu av %llu.%llu GB (blockstorlek %llu KB)\nSpel på enheten: %d%s",
/* English: \nFor maximum performance, use a cluster size of no more than 64 KB */
[SM_L10N_USB_CLUSTER_RECOMMENDATION] = "\nAnvänd en klusterstorlek på högst 64 KB för maximal prestanda",
/* English: External drive %s disconnected */
[SM_L10N_USB_DISCONNECTED] = "Extern enhet %s frånkopplad",
/* English: Game %s crashed. */
[SM_L10N_GAME_CRASHED] = "Spelet %s kraschade.",
/* English: Error in game %s:\n%s */
[SM_L10N_GAME_ERROR] = "Fel i spelet %s:\n%s",
/* English: External drive %s connected */
[SM_L10N_USB_CONNECTED] = "Extern enhet %s ansluten.",
};
+81
View File
@@ -0,0 +1,81 @@
static const char *const g_th_th[SM_L10N_COUNT] = {
/* English: Go to Debug Settings */
[SM_L10N_ACTION_DEBUG_SETTINGS] = "ไปที่การตั้งค่าดีบัก",
/* English: ShadowMount+ v%s\n© Drakmor and Community */
[SM_L10N_STARTUP] = "ShadowMount+ v%s\n© Drakmor และชุมชน",
/* English: ShadowMount+ v%s\n© Drakmor and Community\nControl: http://%s:%u */
[SM_L10N_STARTUP_WEB] =
"ShadowMount+ v%s\n© Drakmor และชุมชน\nควบคุม: http://%s:%u",
/* 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: Emulators applied to game: %s */
[SM_L10N_GAME_EMULATORS_APPLIED] = "ใช้โปรแกรมจำลองกับเกม %s แล้ว",
/* English: Backport applied to game: %s */
[SM_L10N_GAME_BACKPORTED] = "ใช้ backport กับเกมแล้ว: %s",
/* English: Backport applied to game: %s\nEmulators updated */
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "ใช้ backport กับเกมแล้ว: %s\nอัปเดตอีมูเลเตอร์หลายตัวแล้ว",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "ปฏิเสธการเมานต์อิมเมจ:\n%s\nขนาดคลัสเตอร์เล็กเกินไป (%llu < %u)\nบันทึกค่า image_sector แล้ว: %u\nลองเมานต์อีกครั้ง",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "ปฏิเสธการเมานต์อิมเมจ:\n%s\nขนาดคลัสเตอร์เล็กเกินไป (%llu < %u)\nใช้คลัสเตอร์ที่ใหญ่ขึ้นในอิมเมจ หรือลดขนาดเซกเตอร์ในการตั้งค่า",
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "ลงทะเบียนล้มเหลว: %s (%s)\nAppInstallTitleDir ไม่พร้อมใช้งาน",
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "ลงทะเบียนไม่สำเร็จ: %s (%s)\nบริดจ์ TitleDir ไม่พร้อมใช้งาน",
/* English: Register failed: %s (%s)\ncode=0x%08X */
[SM_L10N_REGISTER_FAILED_CODE] = "ลงทะเบียนล้มเหลว: %s (%s)\nรหัส=0x%08X",
/* English: Installing: %s (%s)... */
[SM_L10N_INSTALLING] = "กำลังติดตั้ง: %s (%s)...",
/* English: 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: 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: Missing/invalid param.json:\n%s */
[SM_L10N_MISSING_PARAM] = "param.json หายไปหรือไม่ถูกต้อง:\n%s",
/* English: Found %d new games. Processing... */
[SM_L10N_FOUND_NEW_GAMES] = "พบเกมใหม่ %d เกม กำลังประมวลผล...",
/* English: Library synchronized.\nFound %d games. */
[SM_L10N_LIBRARY_SYNCED] = "ซิงค์ไลบรารีแล้ว\nพบ %d เกม",
/* English: ShadowMount+: config reloaded. */
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: โหลดการตั้งค่าใหม่แล้ว",
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
[SM_L10N_DUPLICATE_IGNORED] = "ข้ามรายการซ้ำ %s:\n%s\nที่มีอยู่: %s",
/* English: AMPR emulator updated */
[SM_L10N_AMPR_EMULATOR_UPDATED] = "อัปเดตโปรแกรมจำลอง AMPR แล้ว",
/* English: ShadowMount+: failed to install ShellCore hooks */
[SM_L10N_SHELLCORE_HOOKS_FAILED] = "ShadowMount+: ไม่สามารถติดตั้งฮุก ShellCore ได้",
/* English: Target temperature %u °C → %u °C */
[SM_L10N_TEMPERATURE_TRANSITION] = "อุณหภูมิเป้าหมาย %u °C → %u °C",
/* English: External drive %s connected\nFree %llu.%llu GB of %llu.%llu (Block size %llu KB)\nGames on drive: %d%s */
[SM_L10N_USB_CONNECTED_SCANNING] = "เชื่อมต่อไดรฟ์ภายนอก %s แล้ว\nว่าง: %llu.%llu จาก %llu.%llu GB (ขนาดบล็อก %llu KB)\nเกมในไดรฟ์: %d%s",
/* English: \nFor maximum performance, use a cluster size of no more than 64 KB */
[SM_L10N_USB_CLUSTER_RECOMMENDATION] = "\nเพื่อประสิทธิภาพสูงสุด ให้ใช้ขนาดคลัสเตอร์ไม่เกิน 64 KB",
/* English: External drive %s disconnected */
[SM_L10N_USB_DISCONNECTED] = "ตัดการเชื่อมต่อไดรฟ์ภายนอก %s แล้ว",
/* English: Game %s crashed. */
[SM_L10N_GAME_CRASHED] = "เกม %s หยุดทำงาน",
/* English: Error in game %s:\n%s */
[SM_L10N_GAME_ERROR] = "ข้อผิดพลาดในเกม %s:\n%s",
/* English: External drive %s connected */
[SM_L10N_USB_CONNECTED] = "เชื่อมต่อไดรฟ์ภายนอก %s แล้ว",
};
+81
View File
@@ -0,0 +1,81 @@
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\n© Drakmor and Community */
[SM_L10N_STARTUP] = "ShadowMount+ v%s\n© Drakmor ve Topluluk",
/* English: ShadowMount+ v%s\n© Drakmor and Community\nControl: http://%s:%u */
[SM_L10N_STARTUP_WEB] =
"ShadowMount+ v%s\n© Drakmor ve Topluluk\nKontrol: http://%s:%u",
/* 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: Emulators applied to game: %s */
[SM_L10N_GAME_EMULATORS_APPLIED] = "Oyuna emülatörler uygulandı: %s",
/* English: Backport applied to game: %s */
[SM_L10N_GAME_BACKPORTED] = "Oyuna backport uygulandı: %s",
/* English: Backport applied to game: %s\nEmulators updated */
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Oyuna backport uygulandı: %s\nEmülatörler güncellendi",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "İmaj bağlama işlemi reddedildi:\n%s\nKüme boyutu çok küçük (%llu < %u).\nimage_sector değeri kaydedildi: %u.\nBağlamayı yeniden deneyin.",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "İmaj bağlama işlemi reddedildi:\n%s\nKüme boyutu çok küçük (%llu < %u).\nİmajda daha büyük bir küme veya yapılandırmada daha küçük bir sektör boyutu kullanın.",
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "Kayıt başarısız: %s (%s)\nAppInstallTitleDir kullanılamıyor",
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "Kayıt başarısız: %s (%s)\nTitleDir köprüsü kullanılamıyor",
/* English: Register failed: %s (%s)\ncode=0x%08X */
[SM_L10N_REGISTER_FAILED_CODE] = "Kayıt başarısız: %s (%s)\nkod=0x%08X",
/* English: Installing: %s (%s)... */
[SM_L10N_INSTALLING] = "Yükleniyor: %s (%s)...",
/* English: 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: 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: Missing/invalid param.json:\n%s */
[SM_L10N_MISSING_PARAM] = "param.json eksik veya geçersiz:\n%s",
/* English: Found %d new games. Processing... */
[SM_L10N_FOUND_NEW_GAMES] = "%d yeni oyun bulundu. İşleniyor...",
/* English: Library synchronized.\nFound %d games. */
[SM_L10N_LIBRARY_SYNCED] = "Kütüphane senkronize edildi.\n%d oyun bulundu.",
/* English: ShadowMount+: config reloaded. */
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: yapılandırma yeniden yüklendi.",
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
[SM_L10N_DUPLICATE_IGNORED] = "Yinelenen %s yok sayıldı:\n%s\nmevcut: %s",
/* English: AMPR emulator updated */
[SM_L10N_AMPR_EMULATOR_UPDATED] = "AMPR emülatörü güncellendi",
/* English: ShadowMount+: failed to install ShellCore hooks */
[SM_L10N_SHELLCORE_HOOKS_FAILED] = "ShadowMount+: ShellCore hook'ları yüklenemedi",
/* English: Target temperature %u °C → %u °C */
[SM_L10N_TEMPERATURE_TRANSITION] = "Hedef sıcaklık %u °C → %u °C",
/* English: External drive %s connected\nFree %llu.%llu GB of %llu.%llu (Block size %llu KB)\nGames on drive: %d%s */
[SM_L10N_USB_CONNECTED_SCANNING] = "Harici sürücü %s bağlandı.\nBoş: %llu.%llu / %llu.%llu GB (blok boyutu %llu KB)\nSürücüdeki oyunlar: %d%s",
/* English: \nFor maximum performance, use a cluster size of no more than 64 KB */
[SM_L10N_USB_CLUSTER_RECOMMENDATION] = "\nMaksimum performans için en fazla 64 KB küme boyutu kullanın",
/* English: External drive %s disconnected */
[SM_L10N_USB_DISCONNECTED] = "Harici sürücü %s çıkarıldı",
/* English: Game %s crashed. */
[SM_L10N_GAME_CRASHED] = "%s oyunu çöktü.",
/* English: Error in game %s:\n%s */
[SM_L10N_GAME_ERROR] = "%s oyununda hata:\n%s",
/* English: External drive %s connected */
[SM_L10N_USB_CONNECTED] = "Harici sürücü %s bağlandı.",
};
+81
View File
@@ -0,0 +1,81 @@
static const char *const g_uk_ua[SM_L10N_COUNT] = {
/* English: Go to Debug Settings */
[SM_L10N_ACTION_DEBUG_SETTINGS] = "Перейти до налаштувань налагодження",
/* English: ShadowMount+ v%s\n© Drakmor and Community */
[SM_L10N_STARTUP] = "ShadowMount+ v%s\n© Drakmor і спільнота",
/* English: ShadowMount+ v%s\n© Drakmor and Community\nControl: http://%s:%u */
[SM_L10N_STARTUP_WEB] =
"ShadowMount+ v%s\n© Drakmor і спільнота\nКерування: http://%s:%u",
/* 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: Emulators applied to game: %s */
[SM_L10N_GAME_EMULATORS_APPLIED] = "До гри %s застосовано емулятори",
/* English: Backport applied to game: %s */
[SM_L10N_GAME_BACKPORTED] = "До гри %s застосовано бекпорт",
/* English: Backport applied to game: %s\nEmulators updated */
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "До гри %s застосовано бекпорт\nЕмулятори оновлено",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Монтування образу відхилено:\n%s\nРозмір кластера замалий (%llu < %u).\nЗбережено перевизначення image_sector: %u.\nСпробуйте змонтувати знову.",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "Монтування образу відхилено:\n%s\nРозмір кластера замалий (%llu < %u).\nЗбільште розмір кластера в образі або зменште розмір сектора в конфігурації.",
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "Не вдалося зареєструвати: %s (%s)\nAppInstallTitleDir недоступний",
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "Не вдалося зареєструвати: %s (%s)\nМіст TitleDir недоступний",
/* English: Register failed: %s (%s)\ncode=0x%08X */
[SM_L10N_REGISTER_FAILED_CODE] = "Не вдалося зареєструвати: %s (%s)\nкод=0x%08X",
/* English: Installing: %s (%s)... */
[SM_L10N_INSTALLING] = "Встановлення: %s (%s)...",
/* English: 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: 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: Missing/invalid param.json:\n%s */
[SM_L10N_MISSING_PARAM] = "Файл param.json відсутній або некоректний:\n%s",
/* English: Found %d new games. Processing... */
[SM_L10N_FOUND_NEW_GAMES] = "Знайдено %d нових ігор. Обробка...",
/* English: Library synchronized.\nFound %d games. */
[SM_L10N_LIBRARY_SYNCED] = "Бібліотеку синхронізовано.\nЗнайдено %d ігор.",
/* English: ShadowMount+: config reloaded. */
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: конфігурацію перезавантажено.",
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
[SM_L10N_DUPLICATE_IGNORED] = "Дублікат %s проігноровано:\n%s\nнаявний: %s",
/* English: AMPR emulator updated */
[SM_L10N_AMPR_EMULATOR_UPDATED] = "Емулятор AMPR оновлено",
/* English: ShadowMount+: failed to install ShellCore hooks */
[SM_L10N_SHELLCORE_HOOKS_FAILED] = "ShadowMount+: не вдалося встановити хуки ShellCore",
/* English: Target temperature %u °C → %u °C */
[SM_L10N_TEMPERATURE_TRANSITION] = "Цільова температура %u °C → %u °C",
/* English: External drive %s connected\nFree %llu.%llu GB of %llu.%llu (Block size %llu KB)\nGames on drive: %d%s */
[SM_L10N_USB_CONNECTED_SCANNING] = "Зовнішній диск %s підключено.\nВільно %llu.%llu із %llu.%llu ГБ (блок %llu КБ)\nІгор на диску: %d%s",
/* English: \nFor maximum performance, use a cluster size of no more than 64 KB */
[SM_L10N_USB_CLUSTER_RECOMMENDATION] = "\nДля максимальної продуктивності використовуйте кластер не більше 64 КБ",
/* English: External drive %s disconnected */
[SM_L10N_USB_DISCONNECTED] = "Зовнішній диск %s відключено",
/* English: Game %s crashed. */
[SM_L10N_GAME_CRASHED] = "Гра %s аварійно завершилася.",
/* English: Error in game %s:\n%s */
[SM_L10N_GAME_ERROR] = "Помилка у грі %s:\n%s",
/* English: External drive %s connected */
[SM_L10N_USB_CONNECTED] = "Зовнішній диск %s підключено.",
};
+81
View File
@@ -0,0 +1,81 @@
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\n© Drakmor and Community */
[SM_L10N_STARTUP] = "ShadowMount+ v%s\n© Drakmor và Cộng đồng",
/* English: ShadowMount+ v%s\n© Drakmor and Community\nControl: http://%s:%u */
[SM_L10N_STARTUP_WEB] =
"ShadowMount+ v%s\n© Drakmor và Cộng đồng\nĐiều khiển: http://%s:%u",
/* 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: Emulators applied to game: %s */
[SM_L10N_GAME_EMULATORS_APPLIED] = "Đã áp dụng trình giả lập cho trò chơi: %s",
/* English: Backport applied to game: %s */
[SM_L10N_GAME_BACKPORTED] = "Đã áp dụng backport cho game: %s",
/* English: Backport applied to game: %s\nEmulators updated */
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "Đã áp dụng backport cho game: %s\nĐã cập nhật các trình giả lập",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "Từ chối gắn ảnh đĩa:\n%s\nKích thước cụm quá nhỏ (%llu < %u).\nĐã lưu giá trị image_sector: %u.\nHãy thử gắn lại.",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "Từ chối gắn ảnh đĩa:\n%s\nKích thước cụm quá nhỏ (%llu < %u).\nHãy dùng cụm lớn hơn trong ảnh đĩa hoặc kích thước cung từ nhỏ hơn trong cấu hình.",
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "Đăng ký thất bại: %s (%s)\nAppInstallTitleDir không khả dụng",
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "Đăng ký thất bại: %s (%s)\nCầu nối TitleDir không khả dụng",
/* English: Register failed: %s (%s)\ncode=0x%08X */
[SM_L10N_REGISTER_FAILED_CODE] = "Đăng ký thất bại: %s (%s)\nmã=0x%08X",
/* English: Installing: %s (%s)... */
[SM_L10N_INSTALLING] = "Đang cài đặt: %s (%s)...",
/* English: 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: 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: Missing/invalid param.json:\n%s */
[SM_L10N_MISSING_PARAM] = "param.json thiếu hoặc không hợp lệ:\n%s",
/* English: Found %d new games. Processing... */
[SM_L10N_FOUND_NEW_GAMES] = "Tìm thấy %d trò chơi mới. Đang xử lý...",
/* English: Library synchronized.\nFound %d games. */
[SM_L10N_LIBRARY_SYNCED] = "Đã đồng bộ thư viện.\nTìm thấy %d trò chơi.",
/* English: ShadowMount+: config reloaded. */
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+: đã tải lại cấu hình.",
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
[SM_L10N_DUPLICATE_IGNORED] = "Đã bỏ qua bản trùng %s:\n%s\nđã có: %s",
/* English: AMPR emulator updated */
[SM_L10N_AMPR_EMULATOR_UPDATED] = "Đã cập nhật trình giả lập AMPR",
/* English: ShadowMount+: failed to install ShellCore hooks */
[SM_L10N_SHELLCORE_HOOKS_FAILED] = "ShadowMount+: không thể cài đặt hook ShellCore",
/* English: Target temperature %u °C → %u °C */
[SM_L10N_TEMPERATURE_TRANSITION] = "Nhiệt độ mục tiêu %u °C → %u °C",
/* English: External drive %s connected\nFree %llu.%llu GB of %llu.%llu (Block size %llu KB)\nGames on drive: %d%s */
[SM_L10N_USB_CONNECTED_SCANNING] = "Đã kết nối ổ đĩa ngoài %s.\nTrống: %llu.%llu / %llu.%llu GB (kích thước khối %llu KB)\nTrò chơi trên ổ đĩa: %d%s",
/* English: \nFor maximum performance, use a cluster size of no more than 64 KB */
[SM_L10N_USB_CLUSTER_RECOMMENDATION] = "\nĐể đạt hiệu suất tối đa, hãy sử dụng kích thước cụm không quá 64 KB",
/* English: External drive %s disconnected */
[SM_L10N_USB_DISCONNECTED] = "Đã ngắt kết nối ổ đĩa ngoài %s",
/* English: Game %s crashed. */
[SM_L10N_GAME_CRASHED] = "Trò chơi %s bị lỗi và thoát.",
/* English: Error in game %s:\n%s */
[SM_L10N_GAME_ERROR] = "Lỗi trong trò chơi %s:\n%s",
/* English: External drive %s connected */
[SM_L10N_USB_CONNECTED] = "Đã kết nối ổ đĩa ngoài %s.",
};
+81
View File
@@ -0,0 +1,81 @@
static const char *const g_zh_cn[SM_L10N_COUNT] = {
/* English: Go to Debug Settings */
[SM_L10N_ACTION_DEBUG_SETTINGS] = "前往调试设置",
/* English: ShadowMount+ v%s\n© Drakmor and Community */
[SM_L10N_STARTUP] = "ShadowMount+ v%s\n© Drakmor 与社区",
/* English: ShadowMount+ v%s\n© Drakmor and Community\nControl: http://%s:%u */
[SM_L10N_STARTUP_WEB] =
"ShadowMount+ v%s\n© Drakmor 与社区\n管理: http://%s:%u",
/* 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: Emulators applied to game: %s */
[SM_L10N_GAME_EMULATORS_APPLIED] = "已为游戏 %s 应用模拟器",
/* English: Backport applied to game: %s */
[SM_L10N_GAME_BACKPORTED] = "已为游戏应用向下兼容补丁:%s",
/* English: Backport applied to game: %s\nEmulators updated */
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "已为游戏应用向下兼容补丁:%s\n多个模拟器已更新",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "镜像挂载被拒绝:\n%s\n簇大小太小 (%llu < %u)。\n已保存 image_sector 覆盖值: %u。\n请再次尝试挂载。",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "镜像挂载被拒绝:\n%s\n簇大小过小(%llu < %u)。\n请增大镜像的簇大小,或在配置中减小扇区大小。",
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "注册失败: %s (%s)\nAppInstallTitleDir 不可用",
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "注册失败:%s(%s)\nTitleDir 桥接不可用",
/* English: Register failed: %s (%s)\ncode=0x%08X */
[SM_L10N_REGISTER_FAILED_CODE] = "注册失败: %s (%s)\n代码=0x%08X",
/* English: Installing: %s (%s)... */
[SM_L10N_INSTALLING] = "正在安装: %s (%s)...",
/* English: 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: 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: Missing/invalid param.json:\n%s */
[SM_L10N_MISSING_PARAM] = "param.json 缺失或无效:\n%s",
/* English: Found %d new games. Processing... */
[SM_L10N_FOUND_NEW_GAMES] = "发现 %d 个新游戏。正在处理...",
/* English: Library synchronized.\nFound %d games. */
[SM_L10N_LIBRARY_SYNCED] = "游戏库已同步。\n共发现 %d 个游戏。",
/* English: ShadowMount+: config reloaded. */
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+:配置已重新加载。",
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
[SM_L10N_DUPLICATE_IGNORED] = "已忽略重复项 %s:\n%s\n现有项:%s",
/* English: AMPR emulator updated */
[SM_L10N_AMPR_EMULATOR_UPDATED] = "AMPR 模拟器已更新",
/* English: ShadowMount+: failed to install ShellCore hooks */
[SM_L10N_SHELLCORE_HOOKS_FAILED] = "ShadowMount+:无法安装 ShellCore 钩子",
/* English: Target temperature %u °C → %u °C */
[SM_L10N_TEMPERATURE_TRANSITION] = "目标温度 %u °C → %u °C",
/* English: External drive %s connected\nFree %llu.%llu GB of %llu.%llu (Block size %llu KB)\nGames on drive: %d%s */
[SM_L10N_USB_CONNECTED_SCANNING] = "外部驱动器 %s 已连接。\n可用:%llu.%llu / %llu.%llu GB(块大小 %llu KB)\n驱动器上的游戏:%d%s",
/* English: \nFor maximum performance, use a cluster size of no more than 64 KB */
[SM_L10N_USB_CLUSTER_RECOMMENDATION] = "\n为获得最佳性能,请使用不超过 64 KB 的簇大小",
/* English: External drive %s disconnected */
[SM_L10N_USB_DISCONNECTED] = "外部驱动器 %s 已断开",
/* English: Game %s crashed. */
[SM_L10N_GAME_CRASHED] = "游戏 %s 崩溃了。",
/* English: Error in game %s:\n%s */
[SM_L10N_GAME_ERROR] = "游戏 %s 出错:\n%s",
/* English: External drive %s connected */
[SM_L10N_USB_CONNECTED] = "外部驱动器 %s 已连接。",
};
+81
View File
@@ -0,0 +1,81 @@
static const char *const g_zh_tw[SM_L10N_COUNT] = {
/* English: Go to Debug Settings */
[SM_L10N_ACTION_DEBUG_SETTINGS] = "前往除錯設定",
/* English: ShadowMount+ v%s\n© Drakmor and Community */
[SM_L10N_STARTUP] = "ShadowMount+ v%s\n© Drakmor 與社群",
/* English: ShadowMount+ v%s\n© Drakmor and Community\nControl: http://%s:%u */
[SM_L10N_STARTUP_WEB] =
"ShadowMount+ v%s\n© Drakmor 與社群\n管理: http://%s:%u",
/* 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: Emulators applied to game: %s */
[SM_L10N_GAME_EMULATORS_APPLIED] = "已為遊戲 %s 套用模擬器",
/* English: Backport applied to game: %s */
[SM_L10N_GAME_BACKPORTED] = "已為遊戲套用向下相容修補程式:%s",
/* English: Backport applied to game: %s\nEmulators updated */
[SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED] = "已為遊戲套用向下相容修補程式:%s\n多個模擬器已更新",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nSaved image_sector override: %u.\nTry mounting again. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED] = "映像掛載被拒絕:\n%s\n叢集大小太小 (%llu < %u)。\n已儲存 image_sector 覆寫值: %u。\n請再次嘗試掛載。",
/* English: Image mount rejected:\n%s\nCluster size is too small (%llu < %u).\nUse a larger cluster size in the image or lower sector size in config. */
[SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG] = "映像掛載遭拒:\n%s\n叢集大小過小(%llu < %u)。\n請增大映像的叢集大小,或在設定中減小磁區大小。",
/* English: Register failed: %s (%s)\nAppInstallTitleDir unavailable */
[SM_L10N_REGISTER_FAILED_UNAVAILABLE] = "註冊失敗: %s (%s)\nAppInstallTitleDir 無法使用",
/* English: Register failed: %s (%s)\nTitleDir bridge unavailable */
[SM_L10N_REGISTER_BRIDGE_UNAVAILABLE] = "註冊失敗:%s(%s)\nTitleDir 橋接無法使用",
/* English: Register failed: %s (%s)\ncode=0x%08X */
[SM_L10N_REGISTER_FAILED_CODE] = "註冊失敗: %s (%s)\n代碼=0x%08X",
/* English: Installing: %s (%s)... */
[SM_L10N_INSTALLING] = "正在安裝: %s (%s)...",
/* English: 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: 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: Missing/invalid param.json:\n%s */
[SM_L10N_MISSING_PARAM] = "param.json 遺失或無效:\n%s",
/* English: Found %d new games. Processing... */
[SM_L10N_FOUND_NEW_GAMES] = "找到 %d 個新遊戲。正在處理...",
/* English: Library synchronized.\nFound %d games. */
[SM_L10N_LIBRARY_SYNCED] = "遊戲庫已同步。\n共找到 %d 個遊戲。",
/* English: ShadowMount+: config reloaded. */
[SM_L10N_CONFIG_RELOADED] = "ShadowMount+:設定已重新載入。",
/* English: Duplicate %s ignored:\n%s\nexisting: %s */
[SM_L10N_DUPLICATE_IGNORED] = "已略過重複項目 %s:\n%s\n現有項目:%s",
/* English: AMPR emulator updated */
[SM_L10N_AMPR_EMULATOR_UPDATED] = "AMPR 模擬器已更新",
/* English: ShadowMount+: failed to install ShellCore hooks */
[SM_L10N_SHELLCORE_HOOKS_FAILED] = "ShadowMount+:無法安裝 ShellCore 掛鉤",
/* English: Target temperature %u °C → %u °C */
[SM_L10N_TEMPERATURE_TRANSITION] = "目標溫度 %u °C → %u °C",
/* English: External drive %s connected\nFree %llu.%llu GB of %llu.%llu (Block size %llu KB)\nGames on drive: %d%s */
[SM_L10N_USB_CONNECTED_SCANNING] = "外接磁碟機 %s 已連接。\n可用:%llu.%llu / %llu.%llu GB(區塊大小 %llu KB)\n磁碟機上的遊戲:%d%s",
/* English: \nFor maximum performance, use a cluster size of no more than 64 KB */
[SM_L10N_USB_CLUSTER_RECOMMENDATION] = "\n為獲得最佳效能,請使用不超過 64 KB 的叢集大小",
/* English: External drive %s disconnected */
[SM_L10N_USB_DISCONNECTED] = "外接磁碟機 %s 已中斷連接",
/* English: Game %s crashed. */
[SM_L10N_GAME_CRASHED] = "遊戲 %s 當機了。",
/* English: Error in game %s:\n%s */
[SM_L10N_GAME_ERROR] = "遊戲 %s 發生錯誤:\n%s",
/* English: External drive %s connected */
[SM_L10N_USB_CONNECTED] = "外接磁碟機 %s 已連接。",
};
+18
View File
@@ -0,0 +1,18 @@
#ifndef SM_AMPR_UPDATER_H
#define SM_AMPR_UPDATER_H
#include <stdbool.h>
#include "sm_types.h"
// Start the dormant-by-default AMPR update worker.
bool sm_ampr_updater_start(void);
// Interrupt any active request and join the update worker.
void sm_ampr_updater_stop(void);
// Cancel network work on sleep entry and wake the dormant worker on resume.
void sm_ampr_updater_on_sleep_change(bool active);
// Wake the worker when its enabled state, URL, or destination changes.
void sm_ampr_updater_on_config_reload(const runtime_config_t *old_cfg,
const runtime_config_t *new_cfg);
#endif
+36
View File
@@ -0,0 +1,36 @@
#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_INDEX "/"
#define SM_API_ROUTE_VERSION "/api/v1/version"
#define SM_API_ROUTE_STORAGE "/api/v1/storage"
#define SM_API_ROUTE_IMAGES "/api/v1/images"
#define SM_API_ROUTE_GAMES "/api/v1/games"
#define SM_API_ROUTE_GAME_INFO "/api/v1/games/info"
#define SM_API_ROUTE_GAME_FAKELIB "/api/v1/games/fakelib"
#define SM_API_ROUTE_GAME_ICON "/api/v1/games/icon"
#define SM_API_ROUTE_MOUNT "/api/v1/games/mount"
#define SM_API_ROUTE_UNMOUNT "/api/v1/games/unmount"
#define SM_API_ROUTE_UNINSTALL "/api/v1/games/uninstall"
#define SM_API_ROUTE_GAME_MOVE "/api/v1/games/move"
#define SM_API_ROUTE_GAME_COPY "/api/v1/games/copy"
#define SM_API_ROUTE_GAME_DELETE "/api/v1/games/delete"
#define SM_API_ROUTE_GAME_UNPACK "/api/v1/games/unpack"
#define SM_API_ROUTE_GAME_STORAGE_STATUS "/api/v1/games/storage/status"
#define SM_API_ROUTE_GAME_STORAGE_CANCEL "/api/v1/games/storage/cancel"
#define SM_API_ROUTE_MANUAL_LIST "/api/v1/manual/list"
#define SM_API_ROUTE_MANUAL_ADD "/api/v1/manual/add"
#define SM_API_ROUTE_MANUAL_REMOVE "/api/v1/manual/remove"
#define SM_API_ROUTE_SETTINGS "/api/v1/settings"
#define SM_API_ROUTE_SETTINGS_UPDATE "/api/v1/settings/update"
#define SM_API_ROUTE_DEBUG_LOG "/api/v1/debug-log"
#define SM_API_ROUTE_KERNEL_LOG "/api/v1/kernel-log"
#define SM_API_ROUTE_SCAN "/api/v1/scan"
#endif
+12
View File
@@ -0,0 +1,12 @@
#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);
// Stop the HTTP daemon on sleep entry and recreate it after resume.
void sm_api_service_on_sleep_change(bool active);
#endif
+25
View File
@@ -2,9 +2,26 @@
#define SM_APPDB_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "sm_limits.h"
struct AppDbTitleList;
typedef struct {
char title_id[MAX_TITLE_ID];
char content_id[MAX_CONTENT_ID];
char title_name[MAX_TITLE_NAME];
char version[MAX_GAME_VERSION];
char last_access_time[MAX_APP_DB_TIMESTAMP];
char install_time[MAX_APP_DB_TIMESTAMP];
char icon_path[MAX_PATH];
char metadata_path[MAX_PATH];
int platform;
uint64_t installed_size;
} sm_app_db_game_info_t;
// Close the cached app.db handle and release related resources.
void shutdown_app_db(void);
// Normalize existing snd0info rows during process startup.
@@ -22,7 +39,15 @@ 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);
// Allocate a read-only snapshot of game metadata from tbl_contentinfo.
bool app_db_game_info_snapshot(sm_app_db_game_info_t **entries_out,
size_t *count_out);
// Find one title in a snapshot sorted by title_id.
const sm_app_db_game_info_t *app_db_find_game_info(
const sm_app_db_game_info_t *entries, size_t count, const char *title_id);
#endif
+39 -17
View File
@@ -2,9 +2,10 @@
#define SM_CONFIG_MOUNT_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
typedef struct runtime_config runtime_config_t;
#include "sm_types.h"
// Ensure runtime configuration is loaded before use.
void ensure_runtime_config_ready(void);
@@ -12,12 +13,31 @@ void ensure_runtime_config_ready(void);
bool load_runtime_config(void);
// Reload runtime configuration from disk when config.ini changed.
bool reload_runtime_config_if_changed(bool *reloaded_out);
// Return the current runtime configuration.
const runtime_config_t *runtime_config(void);
// Atomically persist the settings exposed by the HTTP API while preserving
// unrelated config.ini keys and comments. Runtime reload remains scanner-owned.
bool sm_config_write_web_settings(bool debug_enabled, bool quiet_mode,
bool update_emulators_enabled,
bool auto_update_ampr_enabled,
bool auto_remove_missing_games,
uint32_t auto_remove_missing_delay_seconds,
bool allow_lan_access,
uint32_t fan_target_temperature_c,
const char *const *scan_paths,
size_t scan_path_count,
sm_fakelib_mode_t fakelib_default_mode);
// Return a coherent caller-owned copy of the current runtime configuration.
runtime_config_t runtime_config(void);
// Return the number of configured scan roots.
int get_scan_path_count(void);
// Return a scan root by index, or NULL if out of range.
const char *get_scan_path(int index);
// Copy a scan root; return false and an empty path if the index is invalid.
bool get_scan_path(int index, char path_out[MAX_PATH]);
// Backport lookup uses scan roots followed by the permanent internal fallback.
// These paths do not add roots to normal game scanning.
int get_backport_scan_path_count(void);
bool get_backport_scan_path(int index, char path_out[MAX_PATH]);
// Return only scan roots explicitly configured through scanpath entries.
int get_custom_scan_path_count(void);
bool get_custom_scan_path(int index, char path_out[MAX_PATH]);
// Return scan depth for a root, including managed container-root expansion.
uint32_t get_scan_depth_for_root(const char *scan_path);
// Resolve a per-image read-only override from the file name.
@@ -25,23 +45,25 @@ bool get_image_mode_override(const char *filename, bool *mount_read_only_out);
// Resolve a per-image sector-size override from autotune.ini or config.ini.
bool get_image_sector_size_override(const char *filename,
uint32_t *sector_size_out);
// Return true when kstuff auto-pause is disabled for the given title ID.
bool is_kstuff_pause_disabled_for_title(const char *title_id);
// Resolve a per-title kstuff pause-delay override in seconds.
bool get_kstuff_pause_delay_override_for_title(const char *title_id,
uint32_t *delay_seconds_out);
// Resolve a per-title autotuned kstuff pause-delay override in seconds.
bool get_kstuff_autotune_pause_delay_for_title(const char *title_id,
uint32_t *delay_seconds_out);
// Upsert an autotuned pause-delay override for the title.
bool upsert_kstuff_autotune_pause_delay(const char *title_id,
uint32_t current_delay_seconds,
uint32_t *delay_seconds_out);
// Upsert an autotuned per-image sector-size override.
bool upsert_image_sector_size_autotune(const char *filename,
uint32_t sector_size,
uint32_t *sector_size_out);
// Return true when the global fakelib overlay is disabled for this title.
bool is_global_fakelib_excluded_for_title(const char *title_id);
// Return true when all fakelib overlays are disabled for this title.
bool is_fakelib_excluded_for_title(const char *title_id);
// Resolve a mode from a snapshot or live config; absent rules use the default.
sm_fakelib_mode_t sm_config_title_fakelib_override(const runtime_config_t *cfg,
const char *title_id);
sm_fakelib_mode_t sm_config_title_fakelib_mode(const runtime_config_t *cfg,
const char *title_id);
sm_fakelib_mode_t get_fakelib_mode_for_title(const char *title_id);
const char *sm_config_fakelib_mode_name(sm_fakelib_mode_t mode);
bool sm_config_parse_fakelib_mode(const char *value, sm_fakelib_mode_t *mode);
// Persist the mode atomically without changing active mounts.
bool sm_config_set_title_fakelib_mode(const char *title_id, sm_fakelib_mode_t mode);
// Compatibility API: enabled selects full, false selects disabled.
bool sm_config_set_title_fakelib_enabled(const char *title_id, bool enabled);
#endif
+21 -4
View File
@@ -5,11 +5,28 @@
#include <stdint.h>
#include <sys/types.h>
// Return true when per-game fakelib backport automation is enabled.
// Return true when fakelib backport automation is enabled.
// Sandbox directory mounting is always enabled for supported game titles.
bool sm_fakelib_game_feature_enabled(void);
// Track a supported game launch and mount its fakelib overlay immediately.
void sm_fakelib_game_on_exec(pid_t pid, const char *title_id);
// Remove a tracked fakelib overlay for a stopped game.
// Prepare or refresh the combined fakelib cache before title runtime mounting.
void sm_fakelib_prepare_title_cache(const char *title_id,
const char *game_path);
// Remove invalid and unused fakelib caches during the scanner's rare cycle.
void sm_fakelib_cleanup_caches(void);
// Mount sandbox directories and an optional prepared fakelib overlay after ShellCore
// creates the sandbox but before it spawns the application process.
bool sm_fakelib_game_on_sandbox_ready(const char *title_id);
// Update one USB alias in the prepared or running game's sandbox on hotplug.
void sm_fakelib_game_on_usb_mount_change(const char *source_path, bool mounted);
// Reconcile USB, extended storage and disc aliases after filesystem events.
void sm_fakelib_game_refresh_storage(void);
// Roll back unbound pre-spawn mounts when ShellCore rejects the launch.
void sm_fakelib_game_on_launch_failed(const char *title_id);
// Bind pre-spawn mounts to the process, or mount as a NOTE_EXEC fallback.
// notify_user is false for an internal process handoff of the same application.
void sm_fakelib_game_on_exec(pid_t pid, const char *title_id,
bool notify_user);
// Remove tracked sandbox mounts for a stopped game.
void sm_fakelib_game_on_exit(pid_t pid);
// Unmount all tracked overlays during watcher shutdown.
void sm_fakelib_game_shutdown(void);
+8
View File
@@ -0,0 +1,8 @@
#ifndef SM_FAN_H
#define SM_FAN_H
// Apply the configured automatic fan-controller target for a confirmed running
// game. System-managed configuration is a no-op.
void sm_fan_apply_game_target(const char *title_id);
#endif
+37 -5
View File
@@ -4,6 +4,16 @@
#include <dirent.h>
#include <stdbool.h>
#include <stddef.h>
#include <sys/types.h>
#include "sm_limits.h"
#include "sm_types.h"
struct statfs;
// Return a private mount-table snapshot; the caller frees *mounts_out.
// Returns the entry count, or -1 with *mounts_out == NULL on failure.
int sm_mount_table_snapshot(struct statfs **mounts_out);
// Check whether a title is present in the installed app set.
bool is_installed(const char *title_id);
@@ -13,6 +23,18 @@ 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);
@@ -26,17 +48,23 @@ 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,
bool *overlay_active_out);
// Mount a prepared backport overlay on top of an already mounted title.
// Returns false only when nmount() itself fails.
bool mount_backport_overlay(const char *mount_point,
const char *backport_path,
const char *title_id);
// FAILED means nmount() failed; missing sources/preconditions are SKIPPED.
backport_overlay_result_t 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.
void shutdown_title_mounts(void);
bool shutdown_title_mounts(void);
// Remove stale mount links and optionally restore image-backed mounts.
void cleanup_mount_links(const char *removed_source_root,
bool unmount_system_ex_bind);
@@ -46,7 +74,11 @@ void cleanup_mount_links_for_source_unmount(const char *source_root);
void cleanup_usb_mount_links_for_suspend(void);
// Recursively copy a directory tree.
int copy_dir(const char *src, const char *dst);
// Merge a directory tree and set mode as each destination entry is created.
int copy_dir_with_mode(const char *src, const char *dst, mode_t mode);
// Copy a single file.
int copy_file(const char *src, const char *dst);
// Copy a single file and set its mode immediately after creation.
int copy_file_with_mode(const char *src, const char *dst, mode_t mode);
#endif
+19
View File
@@ -2,6 +2,15 @@
#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,
@@ -14,12 +23,22 @@ void cache_game_entry(const char *path, const char *title_id,
void prune_game_cache(void);
// Drop invalid or stale entries that belong to a specific scan root.
void prune_game_cache_for_root(const char *root);
// Cancel pending removal when the same title is discovered at a valid source.
void note_game_cache_source_seen(const char *path, const char *title_id,
const char *title_name);
// Reconcile app.db auto-remove candidates and uninstall expired missing entries.
void reconcile_missing_app_db_games(void);
// Reset pending missing timers after relevant runtime config changes.
void reset_missing_game_cache_timers(void);
// Look up a cached game entry by path or title ID.
bool find_cached_game(const char *path, const char *title_id,
const char **existing_path_out);
// Visit cached games, optionally limited to one source root.
void for_each_cached_game_entry(const char *root, game_cache_iter_fn fn,
void *ctx);
// 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);
+3
View File
@@ -2,6 +2,7 @@
#define SM_GAME_LIFECYCLE_H
#include <stdbool.h>
#include <stdint.h>
// Start the shared game lifecycle watcher used by runtime game features.
bool start_game_lifecycle_watcher(void);
@@ -11,6 +12,8 @@ void wake_game_lifecycle_watcher(void);
void stop_game_lifecycle_watcher(void);
// Return true when a supported game is currently being tracked as running.
bool sm_game_lifecycle_has_active_game(void);
// Publish and wake lifecycle processing for an AppFocus change.
void sm_game_lifecycle_note_app_focus(uint32_t app_id);
// Ensure the shared watcher is running for runtime game-state tracking.
bool refresh_game_lifecycle_watcher(void);
+2
View File
@@ -7,6 +7,8 @@
// 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 PPSA/CUSA/LAPY/FAKE followed by five digits for game/homebrew 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);
+16
View File
@@ -0,0 +1,16 @@
#ifndef SM_ICON_THUMB_H
#define SM_ICON_THUMB_H
#include <stdbool.h>
#include <sys/stat.h>
#include "sm_limits.h"
#define SM_ICON_THUMB_SIZE 128u
// Return a cached thumbnail path, creating it atomically when necessary.
bool sm_icon_thumbnail_path(const char *title_id, const char *source_path,
const struct stat *source_st,
char out[MAX_PATH]);
#endif
+26 -2
View File
@@ -5,12 +5,31 @@
#include "sm_types.h"
typedef struct unmount_result {
bool filesystem_released;
bool device_released;
bool directory_removed;
int error;
} unmount_result_t;
// Log filesystem statistics for a mounted path.
void log_fs_stats(const char *tag, const char *path, const char *type_hint);
// Attach and mount an image file to its runtime mount point.
bool mount_image(const char *file_path, image_fs_type_t fs_type);
// Attach and mount with a request-scoped mode override. A NULL override uses
// the configured per-image/global mode.
bool mount_image_with_mode(const char *file_path, image_fs_type_t fs_type,
const bool *mount_read_only_override);
// 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_result_t unmount_image(const char *file_path, int unit_id,
attach_backend_t backend);
// Unmount a runtime image without deleting persistent title metadata links.
unmount_result_t 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.
@@ -22,8 +41,11 @@ 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 *name,
bool maybe_mount_image_file(const char *full_path, const char *display_name,
bool *unstable_out);
bool maybe_mount_image_file_with_mode(
const char *full_path, const char *display_name, bool *unstable_out,
const bool *mount_read_only_override);
// Build the deterministic runtime mount point for an image source path.
void get_image_mount_point_for_source(const char *file_path,
char mount_point[MAX_PATH]);
@@ -31,5 +53,7 @@ void get_image_mount_point_for_source(const char *file_path,
bool is_supported_image_file_name(const char *name);
// Return true when the source path is a supported image, including nested names.
bool is_supported_image_file_path(const char *full_path, const char *name);
// Return the public filesystem type name for a supported image path.
const char *image_fs_type_name_for_path(const char *path);
#endif
+47 -4
View File
@@ -6,22 +6,54 @@
#include "sm_types.h"
typedef struct {
uint64_t generation;
char path[MAX_PATH];
char mount_point[MAX_PATH];
int unit_id;
attach_backend_t backend;
attached_device_state_t state;
attached_unit_detach_state_t detach_state;
} image_cache_entry_t;
// Reserve a cache record before issuing an attach ioctl.
bool begin_image_attachment(const char *path, const char *mount_point,
uint64_t *generation_out);
// Cancel an unpublished attachment reservation of the same generation.
bool cancel_image_attachment(const char *path, uint64_t generation);
// Publish the kernel unit immediately after a successful attach ioctl.
bool publish_image_attachment(const char *path, uint64_t generation,
int unit_id, attach_backend_t backend);
// Mark a published device as a verified filesystem mount.
bool complete_image_attachment(const char *path, uint64_t generation,
int unit_id, attach_backend_t backend);
// Cache a successful image mount and its attached device.
// Returns false when no free cache slot is available.
// Returns false when no slot is available or either key has another owner.
bool cache_image_mount(const char *path, const char *mount_point,
int unit_id, attach_backend_t backend);
// Cache an image source mapping without attach metadata.
// Cache an image source mapping without attach metadata. Conflicting active
// source-path and mount-point owners are never retargeted.
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);
// Mark a cached image mount entry as invalid.
void invalidate_image_cache_entry(int index);
// 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);
// Keep a device in the detach-retry queue until its node is released.
bool mark_image_cache_detach_requested(const char *path, uint64_t generation,
int unit_id,
attach_backend_t backend);
// Claim one detach attempt and return a stable snapshot of its record.
bool claim_image_cache_detach(const char *path, uint64_t generation,
int unit_id, attach_backend_t backend,
image_cache_entry_t *entry_out);
// Publish the result of a claimed detach attempt and release its claim.
bool finish_image_cache_detach_attempt(
const char *path, uint64_t generation, int unit_id,
attach_backend_t backend,
const attached_unit_detach_state_t *detach_state);
// Invalidate only when the slot still owns the complete expected attachment.
bool invalidate_image_cache_entry(int index,
const image_cache_entry_t *expected);
// Resolve a device mapping from the in-memory mount cache.
bool resolve_device_from_mount_cache(const char *mount_point,
attach_backend_t *backend_out,
@@ -30,5 +62,16 @@ 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
+47
View File
@@ -0,0 +1,47 @@
#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,
const void *ctx);
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, const void *visitor_ctx);
// 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
+8
View File
@@ -1,8 +1,16 @@
#ifndef SM_INSTALL_H
#define SM_INSTALL_H
#include <stdbool.h>
typedef struct scan_candidate scan_candidate_t;
// Register one already-staged title directory. Returns false when no usable
// TitleDir registration path is available; the API result is returned intact.
bool sm_install_register_title_dir(const char *title_id,
const char *install_dir,
int *result_out);
// Install or remount the collected scan candidates.
void process_scan_candidates(const scan_candidate_t *candidates,
int candidate_count);
+2
View File
@@ -24,5 +24,7 @@ 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
+7 -31
View File
@@ -5,37 +5,13 @@
#include <stdint.h>
#include <sys/types.h>
// Resolve firmware-specific kstuff sysentvec addresses and initialize state.
void sm_kstuff_init(void);
// Shut down runtime kstuff control state.
void sm_kstuff_shutdown(void);
// Return true when firmware-specific kstuff control is available.
bool sm_kstuff_is_supported(void);
// Return true when both tracked kstuff sysentvecs are enabled.
bool sm_kstuff_is_enabled(void);
// Set both tracked kstuff sysentvecs and return the resulting enabled state.
bool sm_kstuff_set_enabled(bool enabled, bool notify_user);
#define SM_KSTUFF_KEKCALL_CHECK UINT64_C(0xffffffff00000027)
#define SM_KSTUFF_KEKCALL_REMOTE_SYSCALL UINT64_C(0x500000027)
// Return true when game-driven kstuff automation should run.
bool sm_kstuff_game_feature_enabled(void);
// Track a supported game launch for delayed auto-pause handling.
void sm_kstuff_game_on_exec(pid_t pid, const char *title_id, uint32_t app_id,
uint64_t exec_time_us);
// Publish an AppFocus change for processing on the lifecycle/kstuff thread.
void sm_kstuff_note_app_focus(uint32_t app_id);
// Return the next wake deadline in monotonic microseconds, or 0 when idle.
uint64_t sm_kstuff_game_next_wake_us(uint64_t now_us);
// Forget a tracked game and restore kstuff when no paused entries remain.
void sm_kstuff_game_on_exit(pid_t pid);
// Apply any delayed kstuff pause whose deadline has elapsed.
void sm_kstuff_game_poll(void);
// Clear tracked game state and restore kstuff if the watcher paused it.
void sm_kstuff_game_shutdown(void);
// Pause kstuff for runtime sleep without restoring game-driven pauses.
void sm_kstuff_sleep_enter(void);
// Restore kstuff after runtime sleep when sleep entry disabled it.
void sm_kstuff_sleep_leave(void);
// Re-apply runtime config changes to the currently tracked game, if any.
void sm_kstuff_on_config_reload(void);
// Return true when kstuff answers its own KEKCALL_CHECK readiness probe.
bool sm_kstuff_is_loaded(void);
// Run mprotect for another process through kstuff's remote-syscall service.
bool sm_kstuff_remote_mprotect(pid_t pid, uintptr_t address, size_t size,
int protection);
#endif
+56
View File
@@ -0,0 +1,56 @@
#ifndef SM_L10N_H
#define SM_L10N_H
#include "sm_platform.h"
#define SM_LANGUAGE_AUTO (-1)
typedef enum {
SM_L10N_ACTION_DEBUG_SETTINGS = 0,
SM_L10N_STARTUP,
SM_L10N_RECURSIVE_SCAN_DEPRECATED,
SM_L10N_SCAN_DEPTH_ENABLED,
SM_L10N_BACKPORT_OVERLAY_FAILED,
SM_L10N_GAME_BACKPORTED,
SM_L10N_GAME_BACKPORTED_EMULATORS_UPDATED,
SM_L10N_IMAGE_REJECTED_CLUSTER_AUTOTUNED,
SM_L10N_IMAGE_REJECTED_CLUSTER_CONFIG,
SM_L10N_REGISTER_FAILED_UNAVAILABLE,
SM_L10N_REGISTER_BRIDGE_UNAVAILABLE,
SM_L10N_REGISTER_FAILED_CODE,
SM_L10N_INSTALLING,
SM_L10N_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_INSTALL_FAILED_TIMEOUT,
SM_L10N_BATCH_INSTALL_QUEUED,
SM_L10N_BATCH_INSTALL_AND_MORE,
SM_L10N_BATCH_INSTALL_FAILED,
SM_L10N_GAME_CRASHED,
SM_L10N_GAME_ERROR,
SM_L10N_MISSING_PARAM,
SM_L10N_FOUND_NEW_GAMES,
SM_L10N_LIBRARY_SYNCED,
SM_L10N_CONFIG_RELOADED,
SM_L10N_DUPLICATE_IGNORED,
SM_L10N_AMPR_EMULATOR_UPDATED,
SM_L10N_SHELLCORE_HOOKS_FAILED,
SM_L10N_TEMPERATURE_TRANSITION,
SM_L10N_USB_CONNECTED_SCANNING,
SM_L10N_USB_CLUSTER_RECOMMENDATION,
SM_L10N_USB_DISCONNECTED,
SM_L10N_STARTUP_WEB,
SM_L10N_GAME_EMULATORS_APPLIED,
SM_L10N_USB_CONNECTED,
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
+26 -7
View File
@@ -4,13 +4,14 @@
#define DEFAULT_SCAN_INTERVAL_US 15000000u
#define DEFAULT_SCAN_DEPTH 1u
#define DEFAULT_STABILITY_WAIT_SECONDS 10u
#define DEFAULT_KSTUFF_PAUSE_DELAY_IMAGE_SECONDS 25u
#define DEFAULT_KSTUFF_PAUSE_DELAY_DIRECT_SECONDS 15u
#define DEFAULT_AUTO_REMOVE_MISSING_DELAY_SECONDS 300u
#define FAN_TARGET_TEMPERATURE_SYSTEM 0u
#define MIN_FAN_TARGET_TEMPERATURE_C 50u
#define MAX_FAN_TARGET_TEMPERATURE_C 91u
#define MAX_PENDING 512
#define MAX_IMAGE_MOUNTS 256
#define MAX_IMAGE_MOUNTS 2048
#define MAX_IMAGE_TITLES 8192
#define MAX_IMAGE_MODE_RULES 128
#define MAX_KSTUFF_TITLE_RULES 128
#define MAX_FAKELIB_EXCLUDE_RULES 128
#define PATH_STATE_CAPACITY MAX_PENDING
#define TITLE_STATE_CAPACITY MAX_PENDING
@@ -22,19 +23,33 @@
#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 KSTUFF_RESTORE_RETRY_US 1000000u
// Keep a dead game PID eligible for ExitSpawn/LoadExec replacement while the
// title sandbox still exists. Once the sandbox disappears, retain a short
// grace window so a transient sandbox transition cannot tear down the runtime
// before the replacement NOTE_EXEC is resolved.
#define GAME_PROCESS_HANDOFF_GRACE_US 1000000u
// Keep a slow fallback in case the /mnt/sandbox vnode watch is revoked.
#define GAME_PROCESS_HANDOFF_FALLBACK_POLL_US 5000000u
#define GAME_SLEEP_EXIT_TIMEOUT_US 5000000u
#define RUNTIME_RESUME_GRACE_US 10000000u
#define RUNTIME_RESUME_POLL_US 100000u
#define SHELLCORE_MPROTECT_WAIT_MAX_US 30000000u
#define SHELLCORE_MPROTECT_WAIT_POLL_US 100000u
#define SHELLCORE_FLAG_POLL_INTERVAL_US 100000u
#define SHELLCORE_FLAG_SLEEP_POLL_INTERVAL_US 500000u
#define MIN_SCAN_DEPTH 1u
#define MAX_SCAN_DEPTH 2u
#define MIN_SCAN_INTERVAL_SECONDS 1u
#define MAX_SCAN_INTERVAL_SECONDS 3600u
#define MAX_STABILITY_WAIT_SECONDS 3600u
#define MAX_KSTUFF_PAUSE_DELAY_SECONDS 3600u
#define MIN_AUTO_REMOVE_MISSING_DELAY_SECONDS 1u
#define MAX_AUTO_REMOVE_MISSING_DELAY_SECONDS 86400u
#define APP_DB_QUERY_BUSY_RETRIES 3
#define APP_DB_UPDATE_BUSY_RETRIES 25
@@ -45,6 +60,10 @@
#define MAX_PATH 1024
#define MAX_TITLE_ID 32
#define MAX_TITLE_NAME 256
#define MAX_CONTENT_ID 128
#define MAX_GAME_VERSION 32
#define MAX_APP_DB_TIMESTAMP 64
#define MAX_API_BIND_ADDRESS 16
#define MAX_PARAM_JSON_SIZE (1024u * 1024u)
#endif
+8 -2
View File
@@ -3,6 +3,8 @@
#include <stdbool.h>
#include "sm_l10n.h"
typedef struct sm_error sm_error_t;
// Write a formatted line to the debug log.
@@ -11,19 +13,23 @@ void log_debug(const char *fmt, ...);
void sm_notifications_init(void);
// Flush and close persistent log resources.
void sm_log_shutdown(void);
// Send a rich toast notification with packaged icon and version header.
void notify_system_rich(bool allow_in_quiet_mode, const char *fmt, ...);
// Send a localized rich toast notification with packaged icon and version header.
void notify_system_rich_l10n(bool allow_in_quiet_mode, sm_l10n_key_t key, ...);
// Send the "game installed" rich toast for the given title ID.
void notify_game_installed_rich(const char *title_id);
// Send an informational system notification (suppressed in quiet mode).
void notify_system_info(const char *fmt, ...);
void notify_system_info_l10n(sm_l10n_key_t key, ...);
// Send a system notification with formatted text.
void notify_system(const char *fmt, ...);
void notify_system_l10n(sm_l10n_key_t key, ...);
// Clear the last recorded subsystem error.
void sm_error_clear(void);
// Store the last subsystem error with formatted details.
void sm_error_set(const char *subsystem, int code, const char *path,
const char *fmt, ...);
void sm_error_set_l10n(const char *subsystem, int code, const char *path,
sm_l10n_key_t key, ...);
// Return the last recorded subsystem error.
const sm_error_t *sm_last_error(void);
// Return whether the current error was already notified.
+8
View File
@@ -3,11 +3,19 @@
#include <stdbool.h>
#include "sm_limits.h"
struct AppDbTitleList;
// Normalize one absolute manual source path using manual.lst rules.
bool sm_manual_normalize_path(const char *path, char out[MAX_PATH]);
// Visit each manual source path from manual.lst.
bool sm_manual_for_each_path(bool (*visit)(const char *path, void *ctx),
void *ctx);
// Idempotently add or remove one normalized absolute source path. The list is
// replaced atomically and changed_out reports whether its contents changed.
bool sm_manual_add_path(const char *path, bool *changed_out);
bool sm_manual_remove_path(const char *path, bool *changed_out);
// Mark previously installed manual titles as deleted when they disappear from
// app.db, and remove their source lines from manual.lst.
bool sm_manual_reconcile_deleted_titles(const struct AppDbTitleList *app_db_titles,
+19 -13
View File
@@ -1,26 +1,32 @@
#ifndef SM_MDBG_H
#define SM_MDBG_H
#include <stddef.h>
#include <stdbool.h>
#include <stdint.h>
#include <sys/types.h>
// Initialize mdbg-backed crash monitoring state.
// Initialize game-error monitoring and kernel-log access state.
void sm_mdbg_init(void);
// Shut down mdbg-backed crash monitoring state.
// Release monitoring state after the lifecycle watcher and API service stop.
void sm_mdbg_shutdown(void);
// Start monitoring the tracked game process for crash-candidate state.
void sm_mdbg_game_on_exec(pid_t pid, const char *title_id, uint32_t app_id);
// Record that kstuff auto-pause was actually applied to the tracked game.
void sm_mdbg_game_on_kstuff_pause(pid_t pid, uint64_t pause_time_us,
uint32_t pause_delay_seconds);
// Stop monitoring the tracked game process on exit.
// Game-monitor callbacks are owned by the lifecycle watcher thread.
// Monitor errors for at most 60 seconds from the original game exec timestamp.
void sm_mdbg_game_on_exec(pid_t pid, const char *title_id,
uint64_t exec_time_us);
// Rebind a same-game process replacement without extending the deadline.
void sm_mdbg_game_handoff(pid_t old_pid, pid_t new_pid);
// Drain pending errors and stop monitoring the finalized game exit.
void sm_mdbg_game_on_exit(pid_t pid);
// Clear tracked game monitoring state unconditionally.
// Stop monitoring when the lifecycle watcher sleeps or shuts down.
void sm_mdbg_game_shutdown(void);
// Return the next wake deadline in monotonic microseconds, or 0 when idle.
uint64_t sm_mdbg_next_wake_us(uint64_t now_us);
// Poll the tracked process for mdbg crash-candidate state.
void sm_mdbg_poll(void);
// Return the next monotonic deadline, or 0 when monitoring is inactive.
uint64_t sm_mdbg_next_wake_us(void);
// Read new errors; query process flags only while the process is still active.
void sm_mdbg_poll(bool process_active);
// Return a caller-owned tail of the SDK log stream. The total byte count
// describes the current complete snapshot.
int sm_mdbg_get_log_tail(size_t max_bytes, char **text_out,
size_t *text_length_out, size_t *total_length_out);
#endif
+283 -39
View File
@@ -1,26 +1,232 @@
#ifndef SM_MOUNT_DEFS_H
#define SM_MOUNT_DEFS_H
#include <stdint.h>
#define IMAGE_MOUNT_READ_ONLY 1
// 1 = use legacy /dev/mdctl backend for .exfat images, 0 = use LVD for all.
// Build-time backend defaults. runtime config.ini can override both values.
// 1 = legacy /dev/mdctl, 0 = /dev/lvdctl.
#define EXFAT_ATTACH_USE_MDCTL 0
// 1 = allow mounting .ffpkg images via /dev/mdctl, 0 = keep UFS on LVD.
#define UFS_ATTACH_USE_MDCTL 0
// --- LVD Definitions ---
// Kernel exposes base attach (V0), extended attach (V1/Attach2) and detach.
// This code only builds V0 layer descriptors, so it uses the base attach path.
// Wrapper-side raw attach presets:
// - 0x8/0x9 -> single-image/save-data family (normalized 0x14/0x1C)
// - 0xC/0xD -> download-data/LWFS family (normalized 0x16/0x1E)
// raw bit3 (0x8) sets the DONT_MAP_BM-style mode, raw bit2 (0x4) switches to
// the DD/LWFS family, and raw bit0 (0x1) selects the alternate preset inside a
// family. Firmware pairs bit0 with read-only mount variants, but this is not a
// standalone LVD readonly bit by itself.
// image_type values accepted by validator: 0..0xC (13 values total).
// layer source_type observed: 1=file, 2=device/special source (/dev/sbram0, char/block).
// layer descriptor flag bit0 is "no bitmap file specified".
/*
* Mount profiles and cache layers
* ================================================================
*
* LVD geometry
* ------------
* sector_size is the logical sector exported by /dev/lvdN. It controls BIO
* alignment and is not a filesystem allocation block. ShadowMount defaults to
* 512 bytes for exFAT and 4096 bytes for UFS/PFS.
*
* secondary_unit is the LVD mapping/split granularity. The optimized profile
* uses 64 KiB for all supported image filesystems. The legacy 1.6 profile uses
* sector_size except for exFAT, which already used 64 KiB. The value must be
* divisible by sector_size; 64 KiB is required by the type-5 BFS sdimg path.
*
* LVD attach ABI and image types
* ------------------------------
* The kernel exposes base attach V0, extended attach V1/Attach2 and detach.
* ShadowMount builds one complete V0 layer and marks it NO_BITMAP. source_type
* 1 is a regular file; source_type 2 is a device/special source.
*
* Accepted image types and kernel names:
* 0 Dfl, 1 GmI, 2 GmO, 3 PtcI, 4 PtcO, 5 Sv, 6 Trp, 7 Dwl,
* 8 AcI, 9 AcO, 10 MaI, 11 MaO, 12 Pcdwl.
*
* Types 0/5/6/7/12 use lvdstart_vnode_direct. Types 1-4/8-11 use
* lvdstart_vnode and resolve each secondary unit through layered bitmap and
* optional OTBL metadata. Optimized ShadowMount profiles are:
*
* .exfat type 5 Sv, direct + BFS sdimg batching
* .ffpkg (UFS) type 5 Sv, direct + BFS sdimg batching
* standalone .ffpfs type 5 Sv, direct
* outer .ffpfsc type 9 AcO, layered package outer
* inner .ffpfs/pfs_image.dat type 8 AcI, layered package inner
*
* exFAT, UFS/.ffpkg and standalone PFS/.ffpfs use the optimized profiles above
* only when their direct source path is under /data or /user. Other direct
* sources use the version 1.6 profiles: type 0 for exFAT, type 7 for UFS and
* the sector-size mapping unit for standalone PFS. The outer .ffpfsc container
* and its nested .ffpfs/pfs_image.dat images use the version 1.6 sector-size
* mapping unit regardless of the container's source path.
*
* ShadowMount deliberately uses type 5 with single/save attach flags for UFS
* images so a standalone .ffpkg on BFS can use sdimg batching. Type 7 with
* Download Data instead favors HDD request isolation. UFS nmount has
* no separate download-data flag: 0x10000000 is the generic MNT_NOATIME bit,
* not an UFS or DD mode. Do not substitute GmO/PtcO only to request the UFS
* block-map cache: that replaces direct dispatch with layered dispatch. The
* kernel requests that cache only for type 2/4, an HDD-class regular backing
* vnode, and a backing filesystem that is UFS.
*
* Type-5 BFS sdimg fast path
* -------------------------
* Type 5 labels the LVD provider "Ssd" and may enable BFS sdimg when the image
* is a regular file on internal BFS and secondary_unit is exactly 64 KiB. The
* kernel also applies platform/storage-mode and lower-provider guards.
*
* When enabled, lvd_worker accepts only requests whose length is exactly
* 64 KiB and whose backing-file offset is 64-KiB aligned. It collects the
* initial request plus up to 30 matching queued requests and submits them via
* bfs_iosession_rw_sdimg/BfsSdimg. All other requests fall back to the normal
* vnode path. This is block-I/O batching, not an exFAT cache or decompressor.
*
* For exFAT keep sector_size at 512 and create the filesystem with a 64-KiB
* allocation unit. Setting sector_size to 65536 does not request this mode;
* the relevant LVD value is secondary_unit. The path is unavailable through
* MD or when the image is backed by USB/UFS/PFS/exFAT instead of BFS.
*
* Raw flags and validator
* -----------------------
* Download-data flags exist only in the LVD attach ABI. ShadowMount calls
* /dev/lvdctl directly and normalizes wrapper-style presets:
* raw 0x8/0x9 -> single/save-data family 0x14/0x1c;
* raw 0xc/0xd -> download-data family 0x16/0x1e.
* Firmware pairs raw bit 0 with read-only variants; it is not an independent
* LVD read-only flag. The normalized validator masks are:
*
* type 0,5,6,8-11: (flags & 0x92) == 0x10
* type 1: (flags & 0x82) == 0x80
* type 2,3,4: (flags & 0x82) == 0
* type 7,12: (flags & 0x92) == 0x12
*
* Download Data HDD worker policy
* -------------------------------
* Download Data is normalized LVD bit 0x02 and is required by types 7 and 12.
* On an HDD-class provider it makes LVD create two request workers instead of
* one: worker 0 is labelled System (lvdhdsw), worker 1 Game (lvdhdgw). The LVD
* start routine routes each BIO using the kernel classification byte at
* BIO + 0x172: value 1 selects Game and every other value selects System. LVD
* also tracks and drains the workers separately during shutdown. The flag is
* therefore an HDD queue-isolation/QoS policy, not an on-disk-format property.
*
* Download Data does not select direct BIO dispatch (image_type does), enable
* bitmap/OTBL interpretation, populate the UFS block-map cache (types 2/4),
* unlock BFS sdimg batching (type 5), or enable PFS/compression caches. For a
* standalone image, type 7 + Download Data is the HDD-oriented choice when
* System/Game queue separation matters. Type 5 + Single/Save is preferable for
* a BFS sdimg-backed .ffpkg when maximum sequential/batched throughput matters
* and the two-queue HDD policy is not needed.
*
* Layer metadata and independent caches
* -------------------------------------
* LVD bitmap says whether a logical unit is present in a layer. V1 OTBL maps a
* logical unit to a physical unit inside the layer file. It is unrelated to a
* PFSC compressed-offset table.
*
* LVD bitmap/OTBL image-layer presence/remapping
* UFS block-map cache backing UFS file blocks -> physical UFS blocks
* PFS GDDR5 ioctl PFSC compressed offsets and optional full ICV
*
* The UFS cache depends on the filesystem containing the image, not UFS stored
* inside .ffpkg, and is unavailable for BFS/PFS/exFAT backing files. The PFS
* metadata ioctl applies to every nested image backed by pfs, ppr_pfs,
* transaction_pfs, or a forwarding overlay; the filesystem inside the image
* does not need to be PFS.
*
* PFS/PPR compression dispatch
* ----------------------------
* Classic pfs and ppr_pfs are distinct kernel read paths. A classic compressed
* PFS block uses the classic PFSC header/table interpretation and the pfs path
* dispatches its compressed payload to zlib. The PPR variant interprets the
* PPR block metadata and can dispatch Kraken. LVD image_type, the nested
* selector and PFS_GDDR5_CACHE_IOCTL do not select a decompressor and cannot
* turn a classic zlib block header into a PPR/Kraken block. The image creator
* must emit the matching on-disk block records and offset table.
*
* Compression is decided per block. Files excluded from compression and blocks
* that are not smaller after compression are stored as raw blocks but remain
* represented in the same image offset mapping. This is independent of the
* mount-time caches described below.
*
* PFS compression caches and big-app reserve
* ------------------------------------------
* Three independent mechanisms have similar historical GDDR5/SBRAM cache
* names and must not be treated as one allocation:
*
* 1. A compressed PFS/PFSC mount can allocate its own cmp_offtbl cache. The
* kernel message
*
* pfs_allocate_cmp_offtbl_cache(): cmp_offtbl is cached
*
* confirms this mount-local compressed-block offset table. It is created
* by the PFS mount path and is not evidence that a big-app memory reserve
* exists.
*
* 2. PFS_GDDR5_CACHE_IOCTL can be issued on a nested image's backing vnode
* before LVD attach when nested_pfs_index_cache=1.
* The ioctl asks the containing PFS/PPR read path to cache the
* compressed-offset table used to reach that vnode's data; it does not
* inspect or impose a format on the bytes stored inside the file. Thus the
* nested file may contain PFS, UFS, exFAT or arbitrary block data and need
* not itself be compressed. The hard requirement for the offset request is
* that the containing PFS/PPR image has its compressed-image flag set. On
* an uncompressed containing image the kernel logs "NOT a compressed
* image" and accepts the request as a compatibility no-op.
*
* cmp_cache_units is a count of shared-cache allocation units, not bytes.
* The compressed-offset pool uses 0x10000-byte units and has 600 units
* (37.5 MiB) with at most eight simultaneous slots. A zero count makes the
* kernel derive the required count from the on-disk offset-table geometry;
* this is the preferred full-table/default request. An explicit nonzero
* count limits the reservation and can force more LRU refills. EBUSY is
* non-fatal: it means that this vnode already has a cache or that all eight
* slots are occupied. ENOMEM means that the requested units exceed the
* remaining total or contiguous shared-cache space.
*
* The optional full-ICV request is independent. It requires the containing
* image's signed-image flag and usable ICV metadata; otherwise the kernel
* rejects that half of the request. It also has its own eight-slot pool.
* Neither half selects zlib/Kraken or changes the block-record format.
*
* 3. The 180-MiB big-app reserve is application-lifecycle memory. It is not
* allocated by the ioctl, LVD, PFS nmount, EnsureBigAppBudget, or a visible
* /dev/sbram device. During BEGIN_APP_MOUNT_EVENT the kernel allocates
* 0x0B400000 bytes aligned to 0x200000 for selected category values:
*
* 0x0000F000, 0x0000F100, 0x0001F000, 0x0001F300,
* 0x0002F000, 0x0200F100.
*
* The allocation belongs to the big-app/application context. PFS can bind
* to it through the application lifecycle callbacks; userland is not given
* a buffer pointer. EndAppMount tears the context down and releases the
* reserve after the application pre-delete path. Consequently a managed
* image stack must remain mounted until the matching EndAppMount hook, and
* no explicit metadata-cache clear should run while its PFS vnode is live.
*
* For unsigned ShadowMount PFS profiles sigverify=0, so request only compressed
* offsets (cache_full_icv=0). Signing does not make ordinary reads faster: it
* enables integrity verification and can add work; a cached full ICV table
* only reduces metadata lookup cost when verification is actually enabled.
* Both compressed-offset caches are properties of the outer PFS read path.
* They can accelerate an LVD-backed nested exFAT/UFS/PFS image because the
* inner filesystem format is irrelevant at that layer.
*
* Backend and nmount policy
* -------------------------
* LVD is the default for every format. MD is a compatibility backend for
* UFS/exFAT; it uses AUTOUNIT|ASYNC and configurable logical sectors (modern
* UFS default 4096, exFAT default 512). Read-only state
* is applied to the backing device and nmount. The shared nmount profiles are:
*
* UFS ufs + budgetid + async + noatime + automounted
* exFAT exfatfs + large + static-timezone + async + noatime + ignoreacl
* PFS pfs + AC + game + sigverify/playgo/disc=0 + zero ekpfs
* + async + noatime
*
* The optimized profile passes only MNT_RDONLY (0x1) in nmount's numeric flags
* argument. Legacy UFS also passes MNT_NOATIME, matching version 1.6. Generic
* named options are decoded by the kernel as async=MNT_ASYNC (0x40),
* noatime=MNT_NOATIME (0x10000000), and automounted=MNT_AUTOMOUNTED
* (0x200000000). UFS options compressedfile, iochunk, largewrite and related
* zone-aware modes are separate filesystem features and are not DD flags.
*
* force is appended only for explicit recovery. Unsigned PFS keeps signature
* verification disabled. The source path selects one profile before attach;
*/
#define LVD_CTRL_PATH "/dev/lvdctl"
#define MD_CTRL_PATH "/dev/mdctl"
#define SCE_LVD_IOC_ATTACH_V0 0xC0286D00
@@ -28,43 +234,60 @@
#define SCE_LVD_IOC_DETACH 0xC0286D01
#define LVD_ATTACH_IO_VERSION_V0 0u
#define LVD_ATTACH_IO_VERSION_V1 1u
#define LVD_ATTACH_RAW_FLAGS_SINGLE_RO 0x9
#define LVD_ATTACH_RAW_FLAGS_SINGLE_RW 0x8
#define LVD_ATTACH_RAW_FLAGS_DD_RO 0xD
#define LVD_ATTACH_RAW_FLAGS_DD_RW 0xC
#
#define LVD_ATTACH_RAW_BIT_RO_VARIANT 0x0001u
#define LVD_ATTACH_RAW_BIT_INNER_PROFILE 0x0002u
#define LVD_ATTACH_RAW_BIT_DOWNLOAD_DATA_PROFILE 0x0004u
#define LVD_ATTACH_RAW_BIT_SINGLE_SAVE_PROFILE 0x0008u
#define LVD_ATTACH_RAW_PASSTHROUGH_MASK 0x8000u
#define LVD_ATTACH_FLAG_DOWNLOAD_DATA_PROFILE 0x0002u
#define LVD_ATTACH_FLAG_BASE 0x0004u
#define LVD_ATTACH_FLAG_RO_VARIANT 0x0008u
#define LVD_ATTACH_FLAG_SINGLE_SAVE_PROFILE 0x0010u
#define LVD_ATTACH_FLAG_INNER_PROFILE 0x0080u
#define LVD_ATTACH_RAW_FLAGS_SINGLE_RW LVD_ATTACH_RAW_BIT_SINGLE_SAVE_PROFILE
#define LVD_ATTACH_RAW_FLAGS_SINGLE_RO \
(LVD_ATTACH_RAW_FLAGS_SINGLE_RW | LVD_ATTACH_RAW_BIT_RO_VARIANT)
#define LVD_ATTACH_RAW_FLAGS_DD_RW \
(LVD_ATTACH_RAW_BIT_SINGLE_SAVE_PROFILE | \
LVD_ATTACH_RAW_BIT_DOWNLOAD_DATA_PROFILE)
#define LVD_ATTACH_RAW_FLAGS_DD_RO \
(LVD_ATTACH_RAW_FLAGS_DD_RW | LVD_ATTACH_RAW_BIT_RO_VARIANT)
#define LVD_ATTACH_FLAGS_SINGLE_RW \
(LVD_ATTACH_FLAG_BASE | LVD_ATTACH_FLAG_SINGLE_SAVE_PROFILE)
#define LVD_ATTACH_FLAGS_SINGLE_RO \
(LVD_ATTACH_FLAGS_SINGLE_RW | LVD_ATTACH_FLAG_RO_VARIANT)
#define LVD_ATTACH_FLAGS_DD_RW \
(LVD_ATTACH_FLAGS_SINGLE_RW | LVD_ATTACH_FLAG_DOWNLOAD_DATA_PROFILE)
#define LVD_ATTACH_FLAGS_DD_RO \
(LVD_ATTACH_FLAGS_DD_RW | LVD_ATTACH_FLAG_RO_VARIANT)
#define LVD_SECTOR_SIZE_EXFAT 512u
#define LVD_SECTOR_SIZE_UFS 4096u
#define LVD_SECTOR_SIZE_PFS 4096u
#define LVD_SECONDARY_UNIT_SINGLE_IMAGE 0x10000u
#define LVD_SECONDARY_UNIT_IMAGE_IO 0x10000u
#define MD_SECTOR_SIZE_EXFAT 512u
#define MD_SECTOR_SIZE_UFS 512u
// Raw option bits are normalized by sceFsLvdAttachCommon before validation:
// raw:0x1->norm:0x08, raw:0x2->norm:0x80, raw:0x4->norm:0x02, raw:0x8->norm:0x10.
// The normalized masks are then checked against validator constraints (0x82/0x92).
#define MD_SECTOR_SIZE_UFS_OPTIMIZED 4096u
_Static_assert(LVD_SECONDARY_UNIT_IMAGE_IO % LVD_SECTOR_SIZE_EXFAT == 0,
"exFAT LVD geometry must be aligned");
_Static_assert(LVD_SECONDARY_UNIT_IMAGE_IO % LVD_SECTOR_SIZE_UFS == 0,
"UFS LVD geometry must be aligned");
_Static_assert(LVD_SECONDARY_UNIT_IMAGE_IO % LVD_SECTOR_SIZE_PFS == 0,
"PFS LVD geometry must be aligned");
#define LVD_ATTACH_IMAGE_TYPE_SINGLE 0
#define LVD_ATTACH_IMAGE_TYPE_UFS_DOWNLOAD_DATA 7
#define LVD_ATTACH_IMAGE_TYPE_PFS_SAVE_DATA 5
#define LVD_ATTACH_IMAGE_TYPE_SAVE_DATA 5
#define LVD_ATTACH_IMAGE_TYPE_DOWNLOAD_DATA 7
#define PFS_NESTED_OUTER_IMG_TYPE 0x02u
#define PFS_NESTED_INNER_IMG_TYPE 0x82u
#define LVD_ATTACH_LAYER_COUNT 1
#define LVD_ATTACH_LAYER_ARRAY_SIZE 3
#define LVD_ENTRY_TYPE_FILE 1
#define LVD_ENTRY_TYPE_SPECIAL 2
#define LVD_ENTRY_FLAG_NO_BITMAP 0x1
#define LVD_NODE_WAIT_US 100000
#define LVD_NODE_WAIT_RETRIES 100
#define UFS_NMOUNT_FLAG_RW 0x10000000u
#define UFS_NMOUNT_FLAG_RO 0x10000001u
// --- devpfs/pfs option defaults ---
// PFS nmount key/value variants observed in refs:
// - fstype: "pfs", "transaction_pfs", "ppr_pfs"
// - mkeymode: "SD"
// - budgetid: "game"/"system" in init paths
// - sigverify/playgo/disc: "0" or "1"
// - optional keys in specific flows: ekpfs/eekpfs, eekc, pubkey_ver, key_ver,
// finalized, ppkg_opt, sblock_offset, maxpkgszingib
// PFS nmount values used by ShadowMount. Other firmware flows also expose
// transaction_pfs/ppr_pfs, SD/GD key modes and system budget IDs.
#define DEVPFS_BUDGET_GAME "game"
#define DEVPFS_BUDGET_SYSTEM "system"
#define DEVPFS_MKEYMODE_SD "SD"
@@ -76,6 +299,27 @@
#define PFS_MOUNT_PLAYGO 0
#define PFS_MOUNT_DISC 0
#define PFS_GDDR5_CACHE_IOCTL 0x80209168
// 0x80209168 is an IOC_IN request with a 0x20-byte payload and is accepted on a
// read-only fd. Despite the userland "GDDR5 cache" name, the pfs and ppr_pfs
// vnode handlers use it for two metadata caches belonging to the containing
// filesystem: compressed offsets (only when its compressed-image flag is set)
// and, optionally, full ICV (only for a signed image). The nested file's own
// magic, filesystem and compression method are not inspected. Counts are
// allocation units rather than bytes; zero asks the kernel to derive the full
// size. The caller must have the kernel privilege checked as privilege 0x2ab.
typedef struct pfs_gddr5_cache_request {
uint8_t cache_cmp_offsets;
uint8_t reserved01[7];
uint64_t cmp_cache_units; // 0 = auto; each cmp unit is 0x10000 bytes
uint8_t cache_full_icv;
uint8_t reserved11[7];
uint64_t icv_cache_units; // 0 = auto; independent signed-image ICV pool
} pfs_gddr5_cache_request_t;
_Static_assert(sizeof(pfs_gddr5_cache_request_t) == 0x20,
"PFS GDDR5 cache request must be 0x20 bytes");
// 4x64-bit PFS key encoded as 64 hex chars.
#define PFS_ZERO_EKPFS_KEY_HEX \
"0000000000000000000000000000000000000000000000000000000000000000"
+8 -2
View File
@@ -19,7 +19,13 @@ bool resolve_device_from_mount(const char *mount_point,
bool is_active_image_mount_point(const char *path);
// Wait until /dev/lvd2 is no longer mounted during startup.
bool wait_for_lvd_release(void);
// Detach a previously attached MD or LVD unit.
bool detach_attached_unit(attach_backend_t backend, int unit_id);
// Detach a previously attached MD or LVD unit. State and node identity are
// required so an accepted asynchronous detach remains bound to one device.
bool detach_attached_unit(attach_backend_t backend, int unit_id,
attached_unit_detach_state_t *state);
// Wait for an accepted detach to remove its node, or detect unit reuse.
bool wait_for_attached_unit_release(
attach_backend_t backend, int unit_id,
const attached_unit_detach_state_t *state);
#endif
+7
View File
@@ -0,0 +1,7 @@
#ifndef SM_MOUNT_DIAG_H
#define SM_MOUNT_DIAG_H
// Log read-only evidence explaining why unmount(path) returned EBUSY.
void sm_mount_diag_log_busy(const char *mount_point);
#endif
+7
View File
@@ -2,6 +2,7 @@
#define SM_PATH_STATE_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <sys/stat.h>
@@ -28,12 +29,18 @@ bool is_image_mount_limited(const char *path);
uint8_t bump_image_mount_attempts(const char *path);
// Clear image mount retry state for a path.
void clear_image_mount_attempts(const char *path);
// Clear image-mount retry counters for every tracked source path.
size_t reset_all_image_mount_attempts(void);
// Return whether backport overlay remounts are suppressed for a path.
bool is_backport_mount_blocked(const char *path);
// Record the first failed backport overlay mount for a path.
void note_backport_mount_failure(const char *path);
// Clear failed backport overlay mount state for a path.
void clear_backport_mount_failure(const char *path);
// Return true once while permission repair for a backport root is incomplete.
bool note_backport_permissions_incomplete_once(const char *path);
// Allow a recovered backport root to report a future permission failure.
void clear_backport_permissions_incomplete(const char *path);
// Return true once for a missing manual source until the path appears again.
bool note_manual_missing_source_once(const char *path);
// Allow a manual source to be reported missing again after it becomes visible.
+3
View File
@@ -18,6 +18,9 @@ bool is_pfsc_image_mount_base_or_child(const char *path);
bool path_matches_root_or_child(const char *path, const char *root);
// Return true when a path lives on USB-backed storage.
bool is_usb_storage_path(const char *path);
// Return true when the path's USB root is mounted, not just an empty mount
// point under /mnt.
bool usb_storage_root_mounted(const char *path);
// Build "<scan_path>/backports" for a managed scan root.
bool build_backports_root_path(const char *scan_path, char out[MAX_PATH]);
+10 -1
View File
@@ -5,14 +5,23 @@
#define PFSC_IMAGE_MOUNT_BASE "/mnt/shadowmnt/pfsc"
#define DEFAULT_BACKPORTS_DIR_NAME "backports"
#define DEFAULT_BACKPORT_SCAN_PATH "/data/homebrew"
#define DEFAULT_GLOBAL_FAKELIB_PATH "/data/shadowmount/fakelib"
#define DEFAULT_EMULATORS_PATH "/data/shadowmount/emus"
#define DEFAULT_AMPR_UPDATE_URL \
"https://github.com/drakmor/ampr_emu/releases/latest/download/" \
"libSceAmpr.sprx"
#define FAKELIB_CACHE_PATH "/data/shadowmount/cache"
#define ICON_CACHE_DIR "/data/shadowmount/icon-cache"
#define LOG_DIR "/data/shadowmount"
#define LOG_FILE "/data/shadowmount/debug.log"
#define LOG_FILE_PREV "/data/shadowmount/debug.log.1"
#define WEB_INDEX_FILE "/data/shadowmount/index.html"
#define CONFIG_FILE "/data/shadowmount/config.ini"
#define AUTOTUNE_FILE "/data/shadowmount/autotune.ini"
#define MANUAL_LIST_FILE "/data/shadowmount/manual.lst"
#define MANUAL_STATUS_FILE "/data/shadowmount/manual.status"
#define IMAGE_INDEX_FILE_PATH "/data/shadowmount/image_index.bin"
#define APPMETA_BASE "/user/appmeta"
#define APP_BASE "/user/app"
#define KSTUFF_NOAUTOMOUNT_FILE "/data/.kstuff_noautomount"
@@ -26,7 +35,7 @@
#define SM_DEFAULT_SCAN_PATHS_INITIALIZER \
{ \
/* Internal */ \
"/data/homebrew", "/data/etaHEN/games", \
DEFAULT_BACKPORT_SCAN_PATH, "/data/etaHEN/games", \
/* Extended Storage */ \
"/mnt/ext0/homebrew", "/mnt/ext0/etaHEN/games", \
/* M.2 Drive */ \
+22
View File
@@ -0,0 +1,22 @@
#ifndef SM_PKG_BACKPORT_H
#define SM_PKG_BACKPORT_H
#include <stdbool.h>
#include <sys/types.h>
// Add child NSFS redirects immediately before the package process is spawned.
// Files use plain redirects; missing directory subtrees use overlays. Up to
// 256 redirect entries are tracked per sandbox. Missing backports and
// non-NSFS app0 paths are harmless skips.
bool sm_pkg_backport_on_sandbox_ready(const char *title_id);
// Remove an unbound redirect when ShellCore aborts the launch.
void sm_pkg_backport_on_launch_failed(const char *title_id);
// Bind an installed redirect to the process that consumed the sandbox.
// This deliberately does not create a late redirect or retry a failed ioctl.
void sm_pkg_backport_on_exec(pid_t pid, const char *title_id);
// Remove the redirect owned by an exited process.
void sm_pkg_backport_on_exit(pid_t pid);
// Best-effort cleanup of every redirect owned by this payload.
void sm_pkg_backport_shutdown(void);
#endif
+9 -1
View File
@@ -41,12 +41,20 @@ 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);
int sceAppInstUtilAppInstallAll(void *reserved);
int sceAppInstUtilAppUnInstall(const char *title_id);
int sceKernelGetAppInfo(pid_t pid, app_info_t *info);
int sceKernelUsleep(unsigned int microseconds);
int32_t sceSystemServiceParamGetInt(int32_t param_id, int32_t *value);
int sceUserServiceInitialize(void *);
void sceUserServiceTerminate(void);
+10 -3
View File
@@ -15,6 +15,8 @@ bool should_stop_requested(void);
void request_shutdown_stop(const char *reason);
// Return true when the payload is paused for system suspend/resume.
bool runtime_sleep_mode_active(void);
// Return true while a launch may briefly wait for USB storage after resume.
bool runtime_resume_grace_active(void);
// Enter or leave sleep mode with a descriptive source string.
// Returns true when the runtime sleep state changed.
bool request_runtime_sleep_mode(bool active, const char *reason);
@@ -22,9 +24,14 @@ bool request_runtime_sleep_mode(bool active, const char *reason);
void runtime_mount_state_lock(void);
void runtime_mount_state_unlock(void);
// Request an immediate scan cycle with a descriptive source string.
void request_scan_now(const char *reason);
// Consume a pending immediate scan request and copy its reason into caller storage.
bool consume_scan_now_request(char *reason_out, size_t reason_out_size);
// Return false when rest mode prevents accepting the request.
bool request_scan_now(const char *reason);
// Request an immediate scan and optionally reset mount/install retry counters
// immediately before that scan starts.
bool request_scan_now_with_options(const char *reason, bool reset_attempts);
// Consume a pending immediate scan request and its merged options.
bool consume_scan_now_request(char *reason_out, size_t reason_out_size,
bool *reset_attempts_out);
// Sleep in chunks and stop early if shutdown was requested.
bool sleep_with_stop_check(unsigned int total_us);
+14 -2
View File
@@ -3,6 +3,8 @@
#include <stdbool.h>
#include "sm_limits.h"
typedef struct scan_candidate scan_candidate_t;
// Unmount and clean up mounts whose backing sources disappeared.
@@ -21,7 +23,17 @@ int collect_scan_candidates_for_scan_root(const char *scan_root,
int max_candidates,
int *total_found_out,
bool *unstable_found_out);
// Mount stable backport overlays for already mounted titles.
void mount_backport_overlays(bool *unstable_found_out);
// Resolve the source directory of a title backport without mounting it.
bool resolve_backport_path_for_title(const char *title_id,
const char *owning_scan_root,
char backport_path[MAX_PATH]);
// Ensure a stable backport overlay is mounted when one is present.
// Absence is harmless; an unusable or failed required overlay returns false.
bool mount_backport_overlay_for_title(const char *source_path,
const char *title_id,
const char *owning_scan_root,
bool *unstable_found_out);
// Remove discovery title/image mounts while preserving persistent metadata.
bool release_scan_runtime_mounts(void);
#endif
+1
View File
@@ -23,6 +23,7 @@ typedef struct {
} sm_scan_tree_callbacks_t;
// Walk a managed scan subtree using the shared scan-depth and path filtering rules.
// Return false for an incomplete walk, including directory I/O failures.
bool sm_scan_tree_walk(const char *scan_root, const char *dir_path,
unsigned int depth_from_root,
unsigned int remaining_depth,
+6
View File
@@ -7,6 +7,12 @@
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);
// Exclude an external mutation from full/targeted scan and install work.
// Returns false immediately when scanner-owned work is already active.
bool sm_scanner_try_begin_external_mutation(void);
void sm_scanner_end_external_mutation(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.
+7
View File
@@ -0,0 +1,7 @@
#ifndef SM_SHELL_ICON_H
#define SM_SHELL_ICON_H
// Install the Shell UI launcher once. Existing installations are untouched.
void sm_shell_icon_install_if_missing(void);
#endif
+22
View File
@@ -0,0 +1,22 @@
#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);
// Check/repair previously installed hooks; disabled hooks remain disabled.
bool sm_shellcore_hooks_refresh(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
+33
View File
@@ -0,0 +1,33 @@
#ifndef SM_SHELLCORE_OFFSETS_H
#define SM_SHELLCORE_OFFSETS_H
#include <stddef.h>
#include <stdint.h>
typedef enum {
SM_SHELLCORE_TARGET_LAUNCH_APP = 0,
SM_SHELLCORE_TARGET_SANDBOX_READY,
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;
uintptr_t bridge_cave_offset;
size_t bridge_cave_size;
uintptr_t sandbox_call_target_offset;
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
+28
View File
@@ -0,0 +1,28 @@
#ifndef SM_SHELLCORE_PROTOCOL_H
#define SM_SHELLCORE_PROTOCOL_H
#include <stdint.h>
#include "sm_limits.h"
#include "sm_shellcore_protocol_defs.h"
typedef enum {
SM_SHELLCORE_REQUEST_LAUNCH = SM_SHELLCORE_REQUEST_LAUNCH_VALUE,
SM_SHELLCORE_REQUEST_LAUNCH_FAILED =
SM_SHELLCORE_REQUEST_LAUNCH_FAILED_VALUE,
SM_SHELLCORE_REQUEST_SANDBOX_READY =
SM_SHELLCORE_REQUEST_SANDBOX_READY_VALUE
} sm_shellcore_request_op_t;
typedef struct {
uint32_t magic;
uint16_t version;
uint16_t operation;
char title_id[MAX_TITLE_ID];
} sm_shellcore_request_t;
typedef struct {
int32_t status;
} sm_shellcore_response_t;
#endif
+30
View File
@@ -0,0 +1,30 @@
#ifndef SM_SHELLCORE_PROTOCOL_DEFS_H
#define SM_SHELLCORE_PROTOCOL_DEFS_H
/* Shared by C and the position-independent ShellCore bridge assembly. */
#define SM_SHELLCORE_SOCKET_PATH "/system_tmp/shadowmount.sock"
#define SM_SHELLCORE_PROTOCOL_MAGIC 0x534d5343
#define SM_SHELLCORE_PROTOCOL_VERSION 1
#define SM_SHELLCORE_REQUEST_LAUNCH_VALUE 1
#define SM_SHELLCORE_REQUEST_LAUNCH_FAILED_VALUE 2
#define SM_SHELLCORE_REQUEST_SANDBOX_READY_VALUE 3
#define SM_SHELLCORE_REQUEST_TITLE_ID_SIZE 32
#define SM_SHELLCORE_REQUEST_SIZE 40
#define SM_SHELLCORE_RESPONSE_SIZE 4
#define SM_SHELLCORE_BRIDGE_INSTALL_STRING_SIZE 16
#define SM_SHELLCORE_BRIDGE_MAX_PROLOGUE_SIZE 16
#define SM_SHELLCORE_BRIDGE_ABSOLUTE_JUMP_SIZE 12
#define SM_SHELLCORE_BRIDGE_RELATIVE_JUMP_SIZE 5
#define SM_SHELLCORE_BRIDGE_MAX_BLOB_SIZE 2048
#define SM_SHELLCORE_BRIDGE_IO_TIMEOUT_SEC 15
#define SM_SHELLCORE_BRIDGE_SANDBOX_TIMEOUT_SEC 3
#define SM_SHELLCORE_BRIDGE_SOCKADDR_SIZE 31
#define SM_SHELLCORE_BRIDGE_SOL_SOCKET 0xffff
#define SM_SHELLCORE_BRIDGE_SO_SNDTIMEO 0x1005
#define SM_SHELLCORE_BRIDGE_SO_RCVTIMEO 0x1006
#define SM_SHELLCORE_BRIDGE_TIMEVAL_SIZE 16
#define SM_SHELLCORE_BRIDGE_TRAMPOLINE_SIZE \
(SM_SHELLCORE_BRIDGE_MAX_PROLOGUE_SIZE + \
SM_SHELLCORE_BRIDGE_RELATIVE_JUMP_SIZE)
#endif
+29
View File
@@ -0,0 +1,29 @@
#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);
// Stop/continue a remote process while executable bytes are inspected/patched.
bool sm_remote_process_attach(pid_t pid);
bool sm_remote_process_detach(pid_t pid);
// 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
+48
View File
@@ -0,0 +1,48 @@
#ifndef SM_SHELLCORE_SERVICE_H
#define SM_SHELLCORE_SERVICE_H
#include <stdbool.h>
#include <stdint.h>
#include <sys/types.h>
#include "sm_limits.h"
// Start/stop the local Unix-socket owner used by the SceShellCore bridge.
bool sm_shellcore_service_start(void);
void sm_shellcore_service_stop(void);
// Close the bridge socket on sleep entry and recreate it after resume.
void sm_shellcore_service_on_sleep_change(bool active);
// Release transient title/image mounts after launch failure or explicit API use.
bool sm_shellcore_release_title_runtime(const char *title_id);
// Public API entry points. Return 0 or a positive errno value. A NULL mount
// mode uses the configured per-image/global mode.
int sm_shellcore_mount_title_runtime(const char *title_id,
const bool *mount_read_only_override);
int sm_shellcore_unmount_title_runtime(const char *title_id);
// Bind the prepared managed title to the app id stored in LncApplication.
void sm_shellcore_service_bind_prepared_app(const char *title_id,
uint32_t app_id, pid_t pid);
// Publish process exit while ShellCore still owns the title sandbox and copy
// the matching owned title to title_id_out.
// Returns true when an owned incoming/outgoing title became exit-pending.
bool sm_shellcore_service_note_game_exit(pid_t pid,
char title_id_out[MAX_TITLE_ID]);
// Release exit-pending runtime stacks whose /mnt/sandbox/<TITLE_ID>_* entries
// have all disappeared. Returns 0 when no exit-pending runtime remains, EAGAIN
// while a sandbox remains, or another positive errno after a release failure.
int sm_shellcore_service_release_exited_titles(void);
// Return true while ShellCore still exposes a sandbox for the title.
bool sm_shellcore_service_title_sandbox_exists(const char *title_id);
// Ensure that a managed title has its image/nullfs/backport runtime stack.
// Unmanaged stock titles are treated as already ready.
bool sm_shellcore_ensure_title_runtime(const char *title_id);
// Return true when the named title owns an incoming or outgoing runtime mount.
bool sm_shellcore_service_title_is_prepared(const char *title_id);
// Return true while ShellCore owns any title mount prepared for launch or exit.
bool sm_shellcore_service_has_prepared_mount(void);
// Exclude new launch preparation while an external AppInst mutation runs.
bool sm_shellcore_try_begin_external_mutation(void);
void sm_shellcore_end_external_mutation(void);
#endif
+10
View File
@@ -0,0 +1,10 @@
#ifndef SM_SOCKET_IO_H
#define SM_SOCKET_IO_H
#include <stdbool.h>
#include <stddef.h>
bool sm_socket_read_full(int fd, void *buffer, size_t size);
bool sm_socket_write_full(int fd, const void *buffer, size_t size);
#endif
+48
View File
@@ -0,0 +1,48 @@
#ifndef SM_STORAGE_H
#define SM_STORAGE_H
#include <stdbool.h>
#include <stdint.h>
typedef bool (*sm_storage_cancel_fn)(void *ctx);
typedef void (*sm_storage_progress_fn)(uint64_t bytes_delta,
uint64_t files_delta, void *ctx);
typedef bool (*sm_storage_finalize_fn)(void *ctx);
// Sum regular-file sizes under a file or directory without following links.
int sm_storage_measure_path(const char *path, uint64_t *size_out);
// Measure a source and count regular files, with optional cooperative cancel.
int sm_storage_measure_path_progress(const char *path, uint64_t *size_out,
uint64_t *file_count_out,
sm_storage_cancel_fn cancel, void *ctx);
// Measure every non-directory entry for deletion; only regular files add bytes.
int sm_storage_measure_delete_path_progress(
const char *path, uint64_t *size_out, uint64_t *file_count_out,
sm_storage_cancel_fn cancel, void *ctx);
// Copy a regular file or directory tree exactly. Destination must not exist.
int sm_storage_copy_path(const char *source, const char *destination);
// Copy while reporting written-byte and completed-file deltas.
int sm_storage_copy_path_progress(const char *source, const char *destination,
sm_storage_progress_fn progress,
sm_storage_cancel_fn cancel, void *ctx);
// Move a file or directory, falling back to copy+delete across filesystems.
int sm_storage_move_path(const char *source, const char *destination);
// Move while reporting progress for a cross-filesystem copy. begin_finalize
// atomically closes cancellation before source deletion; returning false keeps
// the source and removes the destination copy. renamed_out is true when an
// atomic same-filesystem rename completed without copying.
int sm_storage_move_path_progress(const char *source, const char *destination,
sm_storage_progress_fn progress,
sm_storage_cancel_fn cancel,
sm_storage_finalize_fn begin_finalize,
void *ctx,
bool *renamed_out);
// Recursively remove a regular file or directory without following links.
int sm_storage_delete_path(const char *path);
// Delete while reporting removed-byte and removed-file deltas. Cancellation
// must be decided before calling because recursive deletion is irreversible.
int sm_storage_delete_path_progress(const char *path,
sm_storage_progress_fn progress,
void *ctx);
#endif
+9
View File
@@ -2,6 +2,7 @@
#define SM_TITLE_STATE_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
// Return whether registration was already attempted for a title.
@@ -23,5 +24,13 @@ uint8_t get_failed_mount_attempts(const char *title_id);
void clear_failed_mount_attempts(const char *title_id);
// Increment and return failed install/remount attempts for a title.
uint8_t bump_failed_mount_attempts(const char *title_id);
// Atomically return and clear both title-level registration and
// install/remount attempt counters.
void reset_title_attempts(const char *title_id,
uint8_t *register_attempts_out,
uint8_t *mount_install_attempts_out);
// Clear registration and install/remount attempt counters for every title and
// return the number of title entries that changed.
size_t reset_all_title_attempts(void);
#endif
+54 -8
View File
@@ -6,6 +6,12 @@
#include "sm_limits.h"
typedef enum {
BACKPORT_OVERLAY_SKIPPED = 0,
BACKPORT_OVERLAY_ACTIVE,
BACKPORT_OVERLAY_FAILED,
} backport_overlay_result_t;
typedef struct {
// +0x00: 1 -> mount read-only, 0 -> allow write.
uint32_t ro;
@@ -103,31 +109,71 @@ typedef enum {
ATTACH_BACKEND_MD,
} attach_backend_t;
typedef enum {
ATTACHED_DEVICE_RELEASED = 0,
ATTACHED_DEVICE_ATTACHING,
ATTACHED_DEVICE_MOUNTED,
ATTACHED_DEVICE_DETACH_REQUESTED,
} attached_device_state_t;
typedef struct {
// A detach ioctl was accepted for this specific device instance. Once set,
// the same unit number must not receive another detach request because the
// kernel may already have reused it for a different application.
bool requested;
bool node_identity_valid;
uint64_t node_device;
uint64_t node_inode;
uint64_t node_rdev;
} attached_unit_detach_state_t;
typedef enum {
SM_FAKELIB_FULL = 0,
SM_FAKELIB_EMULATORS,
SM_FAKELIB_DISABLED,
SM_FAKELIB_DEFAULT, // Per-title inheritance; never a global default.
} sm_fakelib_mode_t;
typedef struct {
char title_id[MAX_TITLE_ID];
sm_fakelib_mode_t mode;
} sm_fakelib_rule_t;
typedef struct runtime_config {
bool api_enabled;
bool debug_enabled;
bool quiet_mode;
bool mount_read_only;
bool force_mount;
bool persistent_image_mounts;
bool app_install_all_enabled;
bool app_install_all_forced;
bool auto_remove_missing_games;
bool auto_remove_games_with_dlc;
bool backport_fakelib_enabled;
bool global_fakelib_enabled;
bool global_fakelib_mount_first;
bool kstuff_game_auto_toggle;
bool kstuff_crash_detection_enabled;
bool global_fakelib_game_priority;
bool update_emulators_enabled;
bool auto_update_ampr_enabled;
bool legacy_recursive_scan_forced;
bool watch_subdirs;
bool watch_image_files;
char api_bind_address[MAX_API_BIND_ADDRESS];
uint32_t api_port;
char global_fakelib_path[MAX_PATH];
char emulators_path[MAX_PATH];
char ampr_update_url[MAX_PATH];
sm_fakelib_mode_t fakelib_default_mode;
uint32_t fakelib_rule_count;
sm_fakelib_rule_t fakelib_rules[MAX_FAKELIB_EXCLUDE_RULES];
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 kstuff_pause_delay_image_seconds;
uint32_t kstuff_pause_delay_direct_seconds;
uint32_t auto_remove_missing_delay_seconds;
uint32_t fan_target_temperature_c;
int32_t language_id;
attach_backend_t exfat_backend;
attach_backend_t ufs_backend;
bool nested_pfs_index_cache_enabled;
uint32_t lvd_sector_exfat;
uint32_t lvd_sector_ufs;
uint32_t lvd_sector_pfs;
+4 -18
View File
@@ -1,6 +1,6 @@
#!/bin/sh
# For WSL2/Ubuntu/Debian: sudo apt-get install -y exfatprogs exfat-fuse fuse3 rsync
# Create an exFAT image from a directory
# Create a 64 KiB-cluster exFAT image for the LVD type-5/BFS sdimg profile.
# Usage: mkexfat.sh <input_dir> [output_file]
set -e
@@ -28,9 +28,8 @@ fi
# - FAT + allocation bitmap estimates from cluster count
# - directory/entry metadata estimate
# - fixed metadata and runtime headroom
CLUSTER_SIZE=32768
MKFS_CLUSTER_ARG="32K"
LARGE_FILE_THRESHOLD=$((1024 * 1024))
CLUSTER_SIZE=65536
MKFS_CLUSTER_ARG="64K"
META_FIXED=$((32 * 1024 * 1024)) # boot region, upcase, root and misc
MIN_SLACK=$((64 * 1024 * 1024)) # minimum copy/runtime safety margin
SPARE_MIN=$((64 * 1024 * 1024)) # lower bound for dynamic headroom
@@ -42,19 +41,6 @@ DIR_COUNT=$(find "$INPUT_DIR" -type d | wc -l | tr -d ' ')
RAW_FILE_BYTES=$(find "$INPUT_DIR" -type f -printf '%s\n' | \
awk '{s += $1} END {print s + 0}')
AVG_FILE_BYTES=0
if [ "$FILE_COUNT" -gt 0 ]; then
AVG_FILE_BYTES=$((RAW_FILE_BYTES / FILE_COUNT))
fi
# exFAT profile selection (same idea as UFS2 profile):
# - large-file sets: 64K cluster
# - small/mixed-file sets: 32K cluster
if [ "$AVG_FILE_BYTES" -ge "$LARGE_FILE_THRESHOLD" ]; then
CLUSTER_SIZE=65536
MKFS_CLUSTER_ARG="64K"
fi
DATA_BYTES=$(find "$INPUT_DIR" -type f -printf '%s\n' | \
awk -v cls="$CLUSTER_SIZE" '{s += int(($1 + cls - 1) / cls) * cls} END {print s + 0}')
DATA_CLUSTERS=$(( (DATA_BYTES + CLUSTER_SIZE - 1) / CLUSTER_SIZE ))
@@ -82,7 +68,7 @@ MB=$(( (TOTAL + 1024*1024 - 1) / (1024*1024) ))
echo "Input size (raw files): $RAW_FILE_BYTES bytes"
echo "Input size (exFAT alloc): $DATA_BYTES bytes"
echo "Files: $FILE_COUNT, Dirs: $DIR_COUNT"
echo "exFAT profile: -c $MKFS_CLUSTER_ARG (avg file=$AVG_FILE_BYTES bytes)"
echo "exFAT LVD/BFS profile: -c $MKFS_CLUSTER_ARG (fixed 64 KiB cluster)"
echo "Image size: ${MB}MB"
truncate -s "${MB}M" "$OUTPUT"
+2 -15
View File
@@ -29,8 +29,7 @@ fi
# ── sizing constants ──────────────────────────────────────────────────────────
CLUSTER_SIZE=32768
LARGE_FILE_THRESHOLD=$((1024 * 1024))
CLUSTER_SIZE=65536
META_FIXED=$((32 * 1024 * 1024)) # boot region, upcase, root and misc
MIN_SLACK=$((64 * 1024 * 1024)) # minimum copy/runtime safety margin
SPARE_MIN=$((64 * 1024 * 1024)) # lower bound for dynamic headroom
@@ -66,18 +65,6 @@ EOF
DIR_COUNT=$(find "$INPUT_DIR" -type d | wc -l | tr -d ' ')
AVG_FILE_BYTES=0
if [ "$FILE_COUNT" -gt 0 ]; then
AVG_FILE_BYTES=$((RAW_FILE_BYTES / FILE_COUNT))
fi
# exFAT profile selection:
# - large-file sets: 64K cluster
# - small/mixed-file sets: 32K cluster
if [ "$AVG_FILE_BYTES" -ge "$LARGE_FILE_THRESHOLD" ]; then
CLUSTER_SIZE=65536
fi
# DATA_BYTES re-reads the already-collected sizes from the temp file — no second traversal
DATA_BYTES=$(awk -v cls="$CLUSTER_SIZE" \
'{s += int(($1 + cls - 1) / cls) * cls} END {print s + 0}' "$SIZES_FILE")
@@ -102,7 +89,7 @@ MB=$(( (TOTAL + 1024*1024 - 1) / (1024*1024) ))
echo "Input size (raw files): $RAW_FILE_BYTES bytes"
echo "Input size (exFAT alloc): $DATA_BYTES bytes"
echo "Files: $FILE_COUNT, Dirs: $DIR_COUNT"
echo "exFAT profile: -b ${CLUSTER_SIZE} (avg file=$AVG_FILE_BYTES bytes)"
echo "exFAT LVD/BFS profile: -b ${CLUSTER_SIZE} (fixed 64 KiB cluster)"
echo "Image size: ${MB}MB"
# ── create, attach, format, mount ────────────────────────────────────────────
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 101 KiB

+220 -55
View File
@@ -1,16 +1,24 @@
#include "sm_platform.h"
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <netinet/in.h>
#include <pthread.h>
#include <stdatomic.h>
#include <sys/socket.h>
#include <sys/sysctl.h>
#include "sm_runtime.h"
#include "sm_ampr_updater.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_shell_icon.h"
#include "sm_config_mount.h"
#include "sm_game_lifecycle.h"
#include "sm_kstuff.h"
#include "sm_mount_device.h"
#include "sm_filesystem.h"
#include "sm_image.h"
@@ -28,6 +36,10 @@
#define SHADOWMOUNT_VERSION "unknown"
#endif
#ifndef SHADOWMOUNT_BUILD_TIME
#define SHADOWMOUNT_BUILD_TIME __DATE__ " " __TIME__
#endif
#define PAYLOAD_NAME "shadowmountplus.elf"
#define BACKPORK_PROCESS_NAME "backpork.elf"
#define BACKPORK_PROCESS_NAME_ALT "ps5-backpork.elf"
@@ -44,6 +56,7 @@
static volatile sig_atomic_t g_stop_requested = 0;
static atomic_bool g_shutdown_on_going_stop_requested = false;
static atomic_bool g_runtime_sleep_mode_active = false;
static atomic_uint_fast64_t g_runtime_resume_grace_deadline_us = 0;
static _Atomic(uintptr_t) g_shutdown_stop_reason_bits = 0;
static atomic_uint_fast64_t g_next_stop_file_poll_us = 0;
static pthread_mutex_t g_runtime_mount_state_mutex = PTHREAD_MUTEX_INITIALIZER;
@@ -51,6 +64,7 @@ static pthread_mutex_t g_runtime_mount_state_mutex = PTHREAD_MUTEX_INITIALIZER;
typedef struct {
pthread_mutex_t reason_mutex;
char reason[128];
bool reset_attempts;
} immediate_scan_request_t;
static immediate_scan_request_t g_scan_now = {
@@ -61,10 +75,41 @@ static immediate_scan_request_t g_scan_now = {
extern unsigned char config_ini_example[];
extern unsigned int config_ini_example_len;
static void resolve_web_interface_address(const char *bind_address,
char *address,
size_t address_size) {
(void)strlcpy(address, bind_address, address_size);
if (strcmp(bind_address, "0.0.0.0") != 0)
return;
struct ifaddrs *ifaddr = NULL;
if (getifaddrs(&ifaddr) != 0)
return;
for (const struct ifaddrs *ifa = ifaddr; ifa != NULL;
ifa = ifa->ifa_next) {
if (ifa->ifa_addr == NULL || ifa->ifa_addr->sa_family != AF_INET ||
strncmp(ifa->ifa_name, "lo", 2) == 0)
continue;
const struct sockaddr_in *in =
(const struct sockaddr_in *)ifa->ifa_addr;
if (inet_ntop(AF_INET, &in->sin_addr, address, address_size) != NULL &&
strncmp(address, "0.", 2) != 0)
break;
(void)strlcpy(address, bind_address, address_size);
}
freeifaddrs(ifaddr);
}
static void on_signal(int sig) {
int saved_errno = errno;
(void)sig;
g_stop_requested = 1;
sm_scanner_wake();
errno = saved_errno;
}
void install_signal_handlers(void) {
@@ -77,10 +122,14 @@ 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) {
if (g_stop_requested)
if (g_stop_requested ||
atomic_load_explicit(&g_shutdown_on_going_stop_requested,
memory_order_acquire))
return true;
uint64_t now_us = monotonic_time_us();
@@ -89,13 +138,15 @@ bool should_stop_requested(void) {
atomic_load_explicit(&g_next_stop_file_poll_us, memory_order_acquire);
if (next_poll_us != 0 && now_us < next_poll_us)
return false;
atomic_store_explicit(&g_next_stop_file_poll_us,
now_us + STOP_FILE_POLL_INTERVAL_US,
memory_order_release);
if (!atomic_compare_exchange_strong_explicit(
&g_next_stop_file_poll_us, &next_poll_us,
now_us + STOP_FILE_POLL_INTERVAL_US, memory_order_acq_rel,
memory_order_acquire))
return false;
}
if (remove(KILL_FILE) == 0) {
g_stop_requested = 1;
request_shutdown_stop("STOP file " KILL_FILE);
return true;
}
return false;
@@ -116,7 +167,6 @@ void request_shutdown_stop(const char *reason) {
memory_order_release);
log_debug("[SHUTDOWN] requested by %s", g_shutdown_stop_reason);
}
g_stop_requested = 1;
sm_scanner_wake();
wake_game_lifecycle_watcher();
}
@@ -126,25 +176,52 @@ 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';
g_scan_now.reset_attempts = false;
pthread_mutex_unlock(&g_scan_now.reason_mutex);
}
bool request_runtime_sleep_mode(bool active, const char *reason) {
bool previous = atomic_exchange_explicit(&g_runtime_sleep_mode_active, active,
memory_order_acq_rel);
if (previous == active)
return false;
if (active)
if (active) {
bool previous = atomic_exchange_explicit(&g_runtime_sleep_mode_active, true,
memory_order_acq_rel);
if (previous)
return false;
clear_scan_now_request();
atomic_store_explicit(&g_runtime_resume_grace_deadline_us, 0,
memory_order_release);
} else {
if (!runtime_sleep_mode_active())
return false;
uint64_t now_us = monotonic_time_us();
uint64_t deadline_us =
now_us != 0 ? now_us + RUNTIME_RESUME_GRACE_US : 0;
// Publish the deadline before allowing launch requests to observe that
// sleep mode ended.
atomic_store_explicit(&g_runtime_resume_grace_deadline_us, deadline_us,
memory_order_release);
bool previous = atomic_exchange_explicit(&g_runtime_sleep_mode_active,
false, memory_order_acq_rel);
if (!previous)
return false;
}
const char *resolved_reason =
(reason && reason[0] != '\0') ? reason : "unknown sleep source";
log_debug("[SLEEP] %s by %s", active ? "entered" : "left",
resolved_reason);
sm_api_service_on_sleep_change(active);
sm_shellcore_service_on_sleep_change(active);
sm_ampr_updater_on_sleep_change(active);
sm_scanner_wake();
wake_game_lifecycle_watcher();
return true;
@@ -158,34 +235,45 @@ void runtime_mount_state_unlock(void) {
pthread_mutex_unlock(&g_runtime_mount_state_mutex);
}
void request_scan_now(const char *reason) {
bool request_scan_now(const char *reason) {
return request_scan_now_with_options(reason, false);
}
bool request_scan_now_with_options(const char *reason, bool reset_attempts) {
const char *resolved_reason =
(reason && reason[0] != '\0') ? reason : "unknown scan source";
bool resume_scan =
strcmp(resolved_reason, "SceSystemStateMgrInfo=WORKING") == 0;
if (runtime_sleep_mode_active() && !resume_scan)
return;
if (runtime_sleep_mode_active())
return false;
char log_reason[sizeof(g_scan_now.reason)];
bool should_log = !resume_scan;
bool should_log = false;
pthread_mutex_lock(&g_scan_now.reason_mutex);
if (runtime_sleep_mode_active()) {
pthread_mutex_unlock(&g_scan_now.reason_mutex);
return false;
}
if (g_scan_now.reason[0] == '\0') {
(void)strlcpy(g_scan_now.reason, resolved_reason, sizeof(g_scan_now.reason));
(void)strlcpy(log_reason, g_scan_now.reason, sizeof(log_reason));
} else {
should_log = false;
should_log = true;
}
if (reset_attempts)
g_scan_now.reset_attempts = true;
pthread_mutex_unlock(&g_scan_now.reason_mutex);
if (should_log)
log_debug("[SCAN] immediate scan requested by %s", log_reason);
sm_scanner_wake();
return true;
}
bool consume_scan_now_request(char *reason_out, size_t reason_out_size) {
bool consume_scan_now_request(char *reason_out, size_t reason_out_size,
bool *reset_attempts_out) {
if (reason_out && reason_out_size > 0)
reason_out[0] = '\0';
if (reset_attempts_out)
*reset_attempts_out = false;
pthread_mutex_lock(&g_scan_now.reason_mutex);
if (g_scan_now.reason[0] == '\0') {
pthread_mutex_unlock(&g_scan_now.reason_mutex);
@@ -193,7 +281,10 @@ bool consume_scan_now_request(char *reason_out, size_t reason_out_size) {
}
if (reason_out && reason_out_size > 0)
(void)strlcpy(reason_out, g_scan_now.reason, reason_out_size);
if (reset_attempts_out)
*reset_attempts_out = g_scan_now.reset_attempts;
g_scan_now.reason[0] = '\0';
g_scan_now.reset_attempts = false;
pthread_mutex_unlock(&g_scan_now.reason_mutex);
return true;
}
@@ -250,12 +341,23 @@ 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 (ptr < end) {
int ki_structsize = *(int *)ptr;
pid_t ki_pid = *(pid_t *)&ptr[KINFO_PID_OFFSET];
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));
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) && strcmp(ki_tdname, name) == 0) {
if ((!exclude_self || ki_pid != mypid) &&
strnlen(ki_tdname, tdname_size) < tdname_size &&
strcmp(ki_tdname, name) == 0) {
found_pid = ki_pid;
break;
}
@@ -298,7 +400,9 @@ static bool wait_for_existing_instance_exit(pid_t target_pid) {
static void log_non_empty_scan_paths(void) {
for (int i = 0; i < get_scan_path_count(); i++) {
const char *scan_path = get_scan_path(i);
char scan_path[MAX_PATH];
if (!get_scan_path(i, scan_path))
continue;
DIR *d = opendir(scan_path);
if (!d)
continue;
@@ -321,20 +425,21 @@ static void log_non_empty_scan_paths(void) {
}
}
static void ensure_kstuff_noautomount_file(void) {
static bool ensure_kstuff_noautomount_file(void) {
if (path_exists(KSTUFF_NOAUTOMOUNT_FILE))
return;
return true;
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;
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;
}
(void)close(fd);
printf("[KSTUFF] Failed to create %s: %s\n", KSTUFF_NOAUTOMOUNT_FILE,
strerror(errno));
printf("[KSTUFF] Created automount sentinel: %s\n",
KSTUFF_NOAUTOMOUNT_FILE);
return true;
}
static bool write_buffer_to_fd(int fd, const unsigned char *buf, size_t size) {
@@ -381,7 +486,19 @@ static void ensure_runtime_config_file(void) {
printf("[CFG] Created default config from template: %s\n", CONFIG_FILE);
}
static void cleanup_kstuff_noautomount_files(void) {
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;
}
if (unlink(KSTUFF_NOAUTOMOUNT_FILE) == 0) {
log_debug("[KSTUFF] removed shutdown sentinel: %s",
KSTUFF_NOAUTOMOUNT_FILE);
@@ -392,7 +509,7 @@ static void cleanup_kstuff_noautomount_files(void) {
}
static void stop_conflicting_backpork(void) {
if (!runtime_config()->backport_fakelib_enabled)
if (!runtime_config().backport_fakelib_enabled)
return;
const char *names[] = {BACKPORK_PROCESS_NAME, BACKPORK_PROCESS_NAME_ALT};
@@ -428,21 +545,31 @@ 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) {
@@ -461,20 +588,32 @@ int main(void) {
log_debug(
"ShadowMount+ v%s exFAT/UFS/PFS/LVD/MD. "
"FW: %s. "
"Build: %s %s. "
"Build: %s. "
"Thx to VoidWhisper/Gezine/Earthonion/EchoStretch/Drakmor",
SHADOWMOUNT_VERSION, firmware_version, __DATE__, __TIME__);
SHADOWMOUNT_VERSION, firmware_version, SHADOWMOUNT_BUILD_TIME);
if (restarted_previous_instance)
log_debug("[RESTART] Previous instance stopped, continuing startup");
load_runtime_config();
const runtime_config_t startup_cfg = runtime_config();
sm_notifications_init();
stop_conflicting_backpork();
if (!sm_shellcore_flags_start())
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");
notify_system_l10n(SM_L10N_SHELLCORE_HOOKS_FAILED);
}
if (startup_cfg.api_enabled)
sm_shell_icon_install_if_missing();
if (!refresh_game_lifecycle_watcher())
log_debug(" [GAME] lifecycle watcher unavailable");
// Publish the initial AppFocus only after its lifecycle consumer.
if (!sm_shellcore_flags_start())
log_debug(" [SHELLFLAG] monitor unavailable");
if (!sm_ampr_updater_start())
log_debug(" [AMPR] update service unavailable: %s", strerror(errno));
if (mkdir("/system_ex/app", 0777) != 0 && errno != EEXIST) {
log_debug(" [MOUNT] failed to create /system_ex/app: %s", strerror(errno));
@@ -483,14 +622,22 @@ int main(void) {
log_debug(" [MOUNT] remount_system_ex failed: %s", strerror(errno));
}
notify_system("ShadowMount+ v%s exFAT/UFS/PFS", SHADOWMOUNT_VERSION);
if (startup_cfg.api_enabled) {
char web_address[MAX_API_BIND_ADDRESS];
resolve_web_interface_address(startup_cfg.api_bind_address, web_address,
sizeof(web_address));
notify_system_l10n(SM_L10N_STARTUP_WEB, SHADOWMOUNT_VERSION,
web_address, startup_cfg.api_port);
} else {
notify_system_l10n(SM_L10N_STARTUP, SHADOWMOUNT_VERSION);
}
log_non_empty_scan_paths();
if (runtime_config()->legacy_recursive_scan_forced) {
notify_system_info("ShadowMount+: recursive_scan=1 deprecated, using scan_depth=2.");
} else if (runtime_config()->scan_depth > 1u) {
notify_system_info("ShadowMount+: scan depth %u enabled.",
runtime_config()->scan_depth);
if (runtime_config().legacy_recursive_scan_forced) {
notify_system_info_l10n(SM_L10N_RECURSIVE_SCAN_DEPRECATED);
} else if (runtime_config().scan_depth > 1u) {
notify_system_info_l10n(SM_L10N_SCAN_DEPTH_ENABLED,
runtime_config().scan_depth);
}
cleanup_mount_dirs();
@@ -500,9 +647,11 @@ 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");
@@ -511,19 +660,35 @@ 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_ampr_updater_stop();
sm_api_service_stop();
// Stop ShellCore producers before their lifecycle and mount-state owners.
sm_shellcore_flags_stop();
sm_shellcore_hooks_stop();
stop_game_lifecycle_watcher();
sm_shellcore_service_stop();
sm_scanner_shutdown();
sm_kstuff_shutdown();
sm_mdbg_shutdown();
cleanup_kstuff_noautomount_files();
shutdown_title_mounts();
if (!shutdown_image_mounts()) {
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) {
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,
+109
View File
@@ -0,0 +1,109 @@
/* PEM trust anchors for the certificate chains used by GitHub releases. */
static char g_ampr_ca_sectigo_e46_usertrust[] =
"-----BEGIN CERTIFICATE-----\n"
"MIIDRjCCAsugAwIBAgIQGp6v7G3o4ZtcGTFBto2Q3TAKBggqhkjOPQQDAzCBiDEL\n"
"MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl\n"
"eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT\n"
"JVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMjEwMzIy\n"
"MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjBfMQswCQYDVQQGEwJHQjEYMBYGA1UEChMP\n"
"U2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1YmxpYyBTZXJ2ZXIg\n"
"QXV0aGVudGljYXRpb24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAR2\n"
"+pmpbiDt+dd34wc7qNs9Xzjoq1WmVk/WSOrsfy2qw7LFeeyZYX8QeccCWvkEN/U0\n"
"NSt3zn8gj1KjAIns1aeibVvjS5KToID1AZTc8GgHHs3u/iVStSBDHBv+6xnOQ6Oj\n"
"ggEgMIIBHDAfBgNVHSMEGDAWgBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAdBgNVHQ4E\n"
"FgQU0SLaTFnxS18mOKqd1u7rDcP7qWEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB\n"
"/wQFMAMBAf8wHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMBEGA1UdIAQK\n"
"MAgwBgYEVR0gADBQBgNVHR8ESTBHMEWgQ6BBhj9odHRwOi8vY3JsLnVzZXJ0cnVz\n"
"dC5jb20vVVNFUlRydXN0RUNDQ2VydGlmaWNhdGlvbkF1dGhvcml0eS5jcmwwNQYI\n"
"KwYBBQUHAQEEKTAnMCUGCCsGAQUFBzABhhlodHRwOi8vb2NzcC51c2VydHJ1c3Qu\n"
"Y29tMAoGCCqGSM49BAMDA2kAMGYCMQCMCyBit99vX2ba6xEkDe+YO7vC0twjbkv9\n"
"PKpqGGuZ61JZryjFsp+DFpEclCVy4noCMQCwvZDXD/m2Ko1HA5Bkmz7YQOFAiNDD\n"
"49IWa2wdT7R3DtODaSXH/BiXv8fwB9su4tU=\n"
"-----END CERTIFICATE-----\n";
static char g_ampr_ca_isrg_root_yr_by_x1[] =
"-----BEGIN CERTIFICATE-----\n"
"MIIF9DCCA9ygAwIBAgIRAPJLbRf52a18scn+p4eCaZ8wDQYJKoZIhvcNAQELBQAw\n"
"TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\n"
"cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjYwNTEzMDAwMDAw\n"
"WhcNMzIwOTAyMjM1OTU5WjAuMQswCQYDVQQGEwJVUzENMAsGA1UEChMESVNSRzEQ\n"
"MA4GA1UEAxMHUm9vdCBZUjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB\n"
"ANvGJnN78CTJdWL3+eGfsLN5TrNBJs+VH9hRXqRbwxu9sGNiB0BD1fcOxbSUQCJI\n"
"M1xE13Db+5Cw1w0s0EBYsvuIP/6joF0w8cuImbgR1OGgYbSQ4OpzI+DG8SGuTlcE\n"
"873OCS+kh3srlo6vl43M5OJg4Aeo1sfHp6kTJDoIiFBNJAY+OKfX/FUvYKuhjT+n\n"
"o49lmqmupSBI5PkBQiqrEGtWU5uxU/cQWHGu8jSjFBznZqvbNPLMXMLFxCb3WTfr\n"
"JBXXjqvWG+v4bjzxjjeAtOlU7qarRDvNOyAuQYLln904M+faKx8hnLCpJ15ZqaEg\n"
"cNlY+9MMWcC5yvL2A2j3l9+2buggZX+dOE91zYmIdawTvSZuVvlbRrAlLxIB6pwM\n"
"BjneXCjYQ8+3BCCjssbSNpZU3hTcBDdhfAlEDlYr6pEatnMdmDT5BqnKC92bd0Eh\n"
"M1fbLHioLccLCuievT8ZkPhZrq7Mii7gNXAcUEAR8+lzYal+9zTg7C5DALyVOeG/\n"
"CqfRAMn1KSHCR0NSA6P8tn/mGRlnCct5rtVCLnVySVpU6H1qGg3DgTOuskf8eahT\n"
"MiYbI5ezPJmO5ertalskQ1utp74+eDy92PI4ftHKTbq9IWhH4YZKh3WnJEIt+oQv\n"
"lYZbY8tpEroKrFB6PFGzrJIDRyts4HqvuH52RFj2zv/BAgMBAAGjgeswgegwDgYD\n"
"VR0PAQH/BAQDAgEGMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQFMAMB\n"
"Af8wHQYDVR0OBBYEFN7nW2DQIm1AKH0/DQH+pLVStFGUMB8GA1UdIwQYMBaAFHm0\n"
"WeZ7tuXkAXOACIjIGlj26ZtuMDIGCCsGAQUFBwEBBCYwJDAiBggrBgEFBQcwAoYW\n"
"aHR0cDovL3gxLmkubGVuY3Iub3JnLzATBgNVHSAEDDAKMAgGBmeBDAECATAnBgNV\n"
"HR8EIDAeMBygGqAYhhZodHRwOi8veDEuYy5sZW5jci5vcmcvMA0GCSqGSIb3DQEB\n"
"CwUAA4ICAQA8spSI95KKfn2W6GMmDpHBJSPaLbsS3W93cijJCRCYAc1fsJgL1FIL\n"
"7C0C9ecPOdcwB2fi0Dk2p94j9iTJCxmt5CFSKLRWwnXT2MMSXexVxqoVB79BdWPx\n"
"VXETkVme/qYSAuKVHh5Ps+5BixgmwS1JkjSAc+MfrUbNssVEEnH0aEiAh+rotXAV\n"
"JSP/Ye7LJPEwD9DWG72vVWbhAcuOf5OLjz57Ctk7MgQHynZ7+PlHJtajroCaIbtC\n"
"r6tcZZaAwUQm+jQyeWdV+2hv9deOYFmKeQyjjcSrN5Nadrw+L9DZJLbA1HqeNvLh\n"
"BgqpP0fvJq2N6EtD574N6eMI7uMsJTnji2UDz9el5XLSv9fqJMuDQtYVb2oTNoKp\n"
"oUqhxPVC0aq4eG5MESaIdn8b5ZGSSeAJLMHXljEdlNza+ncfkviXk1POLnnFdvx8\n"
"/gk6M374WbLWFXw8N141B/Rl/tINGfl1TxOIiqtiMYkL02RSGb1kq34BL9NPP27z\n"
"RGMuHGnzS3hFIrRTfKxrzUZ9RzQWzEG3K6fJ3r2nqSltkeytis9DIBoFY9VmVyjL\n"
"M71DMi+y1+TRSJVClEMwvA4yL++7q9XZx5r5wBRWB4kQTKH5qyoZnDw7iiuh1lID\n"
"yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==\n"
"-----END CERTIFICATE-----\n";
static char g_ampr_ca_usertrust_ecc[] =
"-----BEGIN CERTIFICATE-----\n"
"MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL\n"
"MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl\n"
"eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT\n"
"JVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMjAx\n"
"MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT\n"
"Ck5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUg\n"
"VVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlm\n"
"aWNhdGlvbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqflo\n"
"I+d61SRvU8Za2EurxtW20eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinng\n"
"o4N+LZfQYcTxmdwlkWOrfzCjtHDix6EznPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0G\n"
"A1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNVHQ8BAf8EBAMCAQYwDwYD\n"
"VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBBHU6+4WMB\n"
"zzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbW\n"
"RNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg=\n"
"-----END CERTIFICATE-----\n";
static char g_ampr_ca_isrg_root_x1[] =
"-----BEGIN CERTIFICATE-----\n"
"MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw\n"
"TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\n"
"cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4\n"
"WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu\n"
"ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY\n"
"MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc\n"
"h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+\n"
"0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U\n"
"A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW\n"
"T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH\n"
"B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC\n"
"B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv\n"
"KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn\n"
"OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn\n"
"jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw\n"
"qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI\n"
"rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV\n"
"HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq\n"
"hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL\n"
"ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ\n"
"3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK\n"
"NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5\n"
"ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur\n"
"TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC\n"
"jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc\n"
"oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq\n"
"4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA\n"
"mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d\n"
"emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=\n"
"-----END CERTIFICATE-----\n";
File diff suppressed because it is too large. Load diff
+3661
View File
File diff suppressed because it is too large. Load diff
+397 -206
View File
@@ -1,5 +1,6 @@
#include "sm_platform.h"
#include <json-c/json.h>
#include <pthread.h>
#include <sqlite3.h>
@@ -11,32 +12,41 @@
#include "sm_paths.h"
static sqlite3 *g_app_db;
static sqlite3_stmt *g_app_db_stmt_update_snd0;
static sqlite3_stmt *g_app_db_stmt_normalize_snd0;
static struct AppDbTitleList g_app_db_title_cache;
static struct AppDbTitleList g_app_db_blocked_uninstall_ppsa_cache;
static bool g_app_db_title_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_blocked_uninstall_ppsa_cache_mtime = 0;
static pthread_mutex_t g_app_db_mutex = PTHREAD_MUTEX_INITIALIZER;
#define APP_DB_API_BUSY_TIMEOUT_MS 250
struct AppDbTitleCache {
struct AppDbTitleList list;
uint64_t revision;
time_t mtime;
bool ready;
};
static struct AppDbTitleCache g_app_db_title_cache;
static struct AppDbTitleCache g_app_db_auto_remove_cache;
static struct AppDbTitleCache g_app_db_blocked_uninstall_ppsa_cache;
static pthread_mutex_t g_app_db_write_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t g_app_db_cache_mutex = PTHREAD_MUTEX_INITIALIZER;
#define APP_DB_STARTUP_MAINTENANCE_RETRIES 3
#define APP_DB_STARTUP_MAINTENANCE_BUSY_TIMEOUT_MS 250
static void close_app_db(void) {
if (g_app_db_stmt_update_snd0) {
sqlite3_finalize(g_app_db_stmt_update_snd0);
g_app_db_stmt_update_snd0 = NULL;
}
if (g_app_db_stmt_normalize_snd0) {
sqlite3_finalize(g_app_db_stmt_normalize_snd0);
g_app_db_stmt_normalize_snd0 = NULL;
}
if (g_app_db) {
sqlite3_close(g_app_db);
g_app_db = NULL;
static bool close_sqlite_handle(sqlite3 **db, const char *label) {
if (!*db)
return true;
int rc = sqlite3_close_v2(*db);
if (rc != SQLITE_OK) {
log_debug(" [DB] close failed for %s: rc=%d err=%s", label, rc,
sqlite3_errmsg(*db));
return false;
}
*db = NULL;
return true;
}
static bool close_app_db(void) {
return close_sqlite_handle(&g_app_db, "write connection");
}
void free_app_db_title_list(struct AppDbTitleList *list) {
@@ -46,16 +56,27 @@ void free_app_db_title_list(struct AppDbTitleList *list) {
list->capacity = 0;
}
static void invalidate_app_db_title_cache_entry(
struct AppDbTitleCache *cache) {
cache->ready = false;
cache->mtime = 0;
++cache->revision;
}
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_blocked_uninstall_ppsa_cache);
g_app_db_title_cache_ready = false;
g_app_db_blocked_uninstall_ppsa_cache_ready = false;
g_app_db_title_cache_mtime = 0;
g_app_db_blocked_uninstall_ppsa_cache_mtime = 0;
close_app_db();
pthread_mutex_unlock(&g_app_db_mutex);
pthread_mutex_lock(&g_app_db_write_mutex);
(void)close_app_db();
pthread_mutex_unlock(&g_app_db_write_mutex);
pthread_mutex_lock(&g_app_db_cache_mutex);
free_app_db_title_list(&g_app_db_title_cache.list);
free_app_db_title_list(&g_app_db_auto_remove_cache.list);
free_app_db_title_list(&g_app_db_blocked_uninstall_ppsa_cache.list);
invalidate_app_db_title_cache_entry(&g_app_db_title_cache);
invalidate_app_db_title_cache_entry(&g_app_db_auto_remove_cache);
invalidate_app_db_title_cache_entry(
&g_app_db_blocked_uninstall_ppsa_cache);
pthread_mutex_unlock(&g_app_db_cache_mutex);
}
static bool ensure_app_db_open(void) {
@@ -175,7 +196,7 @@ static int app_db_query_int(const char *sql, const char *label) {
bool app_db_run_startup_maintenance(void) {
bool ok = false;
pthread_mutex_lock(&g_app_db_mutex);
pthread_mutex_lock(&g_app_db_write_mutex);
if (!ensure_app_db_open())
goto out;
@@ -215,110 +236,72 @@ bool app_db_run_startup_maintenance(void) {
ok = true;
out:
pthread_mutex_unlock(&g_app_db_mutex);
pthread_mutex_unlock(&g_app_db_write_mutex);
return ok;
}
int update_snd0info(const char *title_id) {
static int app_db_update_title(const char *title_id, const char *sql,
const char *label) {
int result = -1;
pthread_mutex_lock(&g_app_db_mutex);
sqlite3_stmt *stmt = NULL;
int prep_rc = app_db_prepare_with_retry(
sql, &stmt, APP_DB_PREPARE_BUSY_RETRIES, label);
if (prep_rc != SQLITE_OK)
return -1;
if (!g_app_db_stmt_update_snd0) {
const char *sql =
"UPDATE tbl_contentinfo "
"SET snd0info = '/user/appmeta/' || ?1 || '/snd0.at9' "
"WHERE titleId = ?1;";
int prep_rc = app_db_prepare_with_retry(sql, &g_app_db_stmt_update_snd0,
APP_DB_PREPARE_BUSY_RETRIES,
"snd0info update");
if (prep_rc != SQLITE_OK)
goto out;
}
for (int attempt = 0; attempt < APP_DB_UPDATE_BUSY_RETRIES; attempt++) {
sqlite3_reset(g_app_db_stmt_update_snd0);
sqlite3_clear_bindings(g_app_db_stmt_update_snd0);
if (sqlite3_bind_text(g_app_db_stmt_update_snd0, 1, title_id, -1,
SQLITE_TRANSIENT) != SQLITE_OK) {
log_debug(" [DB] bind failed for snd0info update: %s",
for (int attempt = 0; attempt < APP_DB_UPDATE_BUSY_RETRIES; ++attempt) {
(void)sqlite3_reset(stmt);
(void)sqlite3_clear_bindings(stmt);
if (sqlite3_bind_text(stmt, 1, title_id, -1, SQLITE_TRANSIENT) !=
SQLITE_OK) {
log_debug(" [DB] bind failed for %s: %s", label,
sqlite3_errmsg(g_app_db));
close_app_db();
goto out;
break;
}
int rc = sqlite3_step(g_app_db_stmt_update_snd0);
int rc = sqlite3_step(stmt);
if (rc == SQLITE_DONE) {
int changes = sqlite3_changes(g_app_db);
close_app_db();
result = changes;
goto out;
result = sqlite3_changes(g_app_db);
break;
}
if (app_db_wait_retry(rc, attempt, APP_DB_UPDATE_BUSY_RETRIES, false))
continue;
log_debug(" [DB] step failed for snd0info update: rc=%d err=%s", rc,
log_debug(" [DB] step failed for %s: rc=%d err=%s", label, rc,
sqlite3_errmsg(g_app_db));
close_app_db();
goto out;
break;
}
close_app_db();
out:
pthread_mutex_unlock(&g_app_db_mutex);
(void)sqlite3_finalize(stmt);
(void)close_app_db();
return result;
}
int update_snd0info(const char *title_id) {
const char *sql =
"UPDATE tbl_contentinfo "
"SET snd0info = '/user/appmeta/' || ?1 || '/snd0.at9' "
"WHERE titleId = ?1;";
pthread_mutex_lock(&g_app_db_write_mutex);
int result = app_db_update_title(title_id, sql, "snd0info update");
pthread_mutex_unlock(&g_app_db_write_mutex);
return result;
}
int normalize_snd0info_for_title(const char *title_id) {
int result = -1;
pthread_mutex_lock(&g_app_db_mutex);
const char *sql =
"UPDATE tbl_contentinfo "
"SET snd0Info = replace(replace(snd0Info, '/user/app/', "
"'/user/appmeta/'), '/sce_sys', '') "
"WHERE titleId = ?1 "
"AND (instr(snd0Info, '/user/app/') > 0 "
"OR instr(snd0Info, '/sce_sys') > 0);";
if (!g_app_db_stmt_normalize_snd0) {
const char *sql =
"UPDATE tbl_contentinfo "
"SET snd0Info = replace(replace(snd0Info, '/user/app/', "
"'/user/appmeta/'), '/sce_sys', '') "
"WHERE titleId = ?1 "
"AND (instr(snd0Info, '/user/app/') > 0 "
"OR instr(snd0Info, '/sce_sys') > 0);";
int prep_rc = app_db_prepare_with_retry(sql, &g_app_db_stmt_normalize_snd0,
APP_DB_PREPARE_BUSY_RETRIES,
"snd0info normalize");
if (prep_rc != SQLITE_OK)
goto out;
}
for (int attempt = 0; attempt < APP_DB_UPDATE_BUSY_RETRIES; attempt++) {
sqlite3_reset(g_app_db_stmt_normalize_snd0);
sqlite3_clear_bindings(g_app_db_stmt_normalize_snd0);
if (sqlite3_bind_text(g_app_db_stmt_normalize_snd0, 1, title_id, -1,
SQLITE_TRANSIENT) != SQLITE_OK) {
log_debug(" [DB] bind failed for snd0info normalize: %s",
sqlite3_errmsg(g_app_db));
close_app_db();
goto out;
}
int rc = sqlite3_step(g_app_db_stmt_normalize_snd0);
if (rc == SQLITE_DONE) {
int changes = sqlite3_changes(g_app_db);
close_app_db();
result = changes;
goto out;
}
if (app_db_wait_retry(rc, attempt, APP_DB_UPDATE_BUSY_RETRIES, false))
continue;
log_debug(" [DB] step failed for snd0info normalize: rc=%d err=%s", rc,
sqlite3_errmsg(g_app_db));
close_app_db();
goto out;
}
close_app_db();
out:
pthread_mutex_unlock(&g_app_db_mutex);
pthread_mutex_lock(&g_app_db_write_mutex);
int result = app_db_update_title(title_id, sql, "snd0info normalize");
pthread_mutex_unlock(&g_app_db_write_mutex);
return result;
}
@@ -354,87 +337,115 @@ bool app_db_title_list_contains(const struct AppDbTitleList *list,
compare_title_id_str) != NULL;
}
static bool step_app_db_title_query(struct AppDbTitleList *list,
sqlite3_stmt *stmt,
const char *label) {
static bool step_app_db_title_query(struct AppDbTitleList *list, sqlite3 *db,
sqlite3_stmt *stmt, const char *label) {
int busy_attempts = 0;
while (!should_stop_requested()) {
int rc = sqlite3_step(stmt);
if (rc == SQLITE_ROW) {
const char *title_id = (const char *)sqlite3_column_text(stmt, 0);
if (!append_app_db_title(list, title_id)) {
log_debug(" [DB] %s allocation failed", label);
sqlite3_finalize(stmt);
close_app_db();
free_app_db_title_list(list);
return false;
}
continue;
if (append_app_db_title(list, title_id))
continue;
log_debug(" [DB] %s allocation failed", label);
return false;
}
if (rc == SQLITE_DONE) {
sqlite3_finalize(stmt);
close_app_db();
if (rc == SQLITE_DONE)
return true;
}
if ((rc == SQLITE_BUSY || rc == SQLITE_LOCKED) &&
busy_attempts + 1 < APP_DB_QUERY_BUSY_RETRIES) {
busy_attempts++;
++busy_attempts;
sceKernelUsleep(APP_DB_BUSY_RETRY_SLEEP_US);
continue;
}
log_debug(" [DB] %s query failed: rc=%d err=%s", label, rc,
sqlite3_errmsg(g_app_db));
sqlite3_finalize(stmt);
close_app_db();
free_app_db_title_list(list);
sqlite3_errmsg(db));
return false;
}
sqlite3_finalize(stmt);
close_app_db();
free_app_db_title_list(list);
return false;
}
static bool load_app_db_title_list(struct AppDbTitleList *list) {
static bool load_app_db_title_query(struct AppDbTitleList *list,
const char *sql, const char *label) {
free_app_db_title_list(list);
if (should_stop_requested())
return false;
sqlite3 *db = NULL;
int rc = sqlite3_open_v2(APP_DB_PATH, &db, SQLITE_OPEN_READONLY, NULL);
if (rc != SQLITE_OK) {
log_debug(" [DB] open failed for %s: rc=%d err=%s", label, rc,
db ? sqlite3_errmsg(db) : APP_DB_PATH);
if (db)
(void)close_sqlite_handle(&db, label);
return false;
}
(void)sqlite3_busy_timeout(db, APP_DB_BUSY_TIMEOUT_MS);
sqlite3_stmt *stmt = NULL;
for (int attempt = 0; attempt < APP_DB_PREPARE_BUSY_RETRIES; ++attempt) {
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
if (rc == SQLITE_OK)
break;
if ((rc != SQLITE_BUSY && rc != SQLITE_LOCKED) ||
attempt + 1 >= APP_DB_PREPARE_BUSY_RETRIES ||
should_stop_requested()) {
log_debug(" [DB] prepare failed for %s: rc=%d err=%s", label, rc,
sqlite3_errmsg(db));
break;
}
sceKernelUsleep(APP_DB_BUSY_RETRY_SLEEP_US);
}
bool loaded = rc == SQLITE_OK &&
step_app_db_title_query(list, db, stmt, label);
if (stmt)
(void)sqlite3_finalize(stmt);
if (!close_sqlite_handle(&db, label))
loaded = false;
if (!loaded)
free_app_db_title_list(list);
return loaded;
}
static bool load_app_db_title_list(struct AppDbTitleList *list) {
const char *sql =
"SELECT DISTINCT titleId "
"FROM tbl_contentinfo "
"WHERE titleId != '' "
"ORDER BY titleId;";
sqlite3_stmt *stmt = NULL;
int prep_rc = app_db_prepare_with_retry(sql, &stmt,
APP_DB_PREPARE_BUSY_RETRIES,
"title list query");
if (prep_rc != SQLITE_OK)
return false;
bool loaded = step_app_db_title_query(list, stmt, "title list");
bool loaded = load_app_db_title_query(list, sql, "title list");
if (loaded)
log_debug(" [DB] loaded app.db 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);
static bool load_app_db_auto_remove_title_list(struct AppDbTitleList *list) {
const char *sql =
"SELECT DISTINCT titleId "
"FROM tbl_contentinfo "
"WHERE metaDataPath GLOB '/user/app/PPSA*/sce_sys' "
"ORDER BY titleId;";
bool loaded =
load_app_db_title_query(list, sql, "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) {
const char *sql =
"SELECT DISTINCT titleId "
"FROM tbl_contentinfo "
"WHERE titleId LIKE 'PPSA%' "
"AND uninstallable = 0 "
"ORDER BY titleId;";
sqlite3_stmt *stmt = NULL;
int prep_rc =
app_db_prepare_with_retry(sql, &stmt, APP_DB_PREPARE_BUSY_RETRIES,
"blocked PPSA uninstallable query");
if (prep_rc != SQLITE_OK)
return false;
bool loaded = step_app_db_title_query(list, stmt, "blocked PPSA uninstall");
bool loaded =
load_app_db_title_query(list, sql, "blocked PPSA uninstall");
if (loaded)
log_debug(" [DB] loaded blocked PPSA uninstall list: %d entries",
list->count);
@@ -442,12 +453,12 @@ 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_blocked_uninstall_ppsa_cache_ready = false;
g_app_db_title_cache_mtime = 0;
g_app_db_blocked_uninstall_ppsa_cache_mtime = 0;
pthread_mutex_unlock(&g_app_db_mutex);
pthread_mutex_lock(&g_app_db_cache_mutex);
invalidate_app_db_title_cache_entry(&g_app_db_title_cache);
invalidate_app_db_title_cache_entry(&g_app_db_auto_remove_cache);
invalidate_app_db_title_cache_entry(
&g_app_db_blocked_uninstall_ppsa_cache);
pthread_mutex_unlock(&g_app_db_cache_mutex);
}
static bool copy_app_db_title_list(struct AppDbTitleList *dst,
@@ -470,69 +481,249 @@ static bool copy_app_db_title_list(struct AppDbTitleList *dst,
return true;
}
bool get_app_db_title_list_cached(struct AppDbTitleList *list_out) {
if (!list_out)
return false;
typedef bool (*load_app_db_title_list_fn)(struct AppDbTitleList *list);
static bool get_cached_app_db_title_list(
struct AppDbTitleList *list_out, struct AppDbTitleCache *cache,
load_app_db_title_list_fn load_fn) {
free_app_db_title_list(list_out);
pthread_mutex_lock(&g_app_db_mutex);
struct stat st;
int app_db_stat_rc = stat(APP_DB_PATH, &st);
if (!g_app_db_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;
}
}
// 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.
pthread_mutex_lock(&g_app_db_cache_mutex);
bool cache_is_current =
cache->ready &&
(runtime_resume_grace_active() || app_db_stat_rc != 0 ||
cache->mtime == st.st_mtime);
if (cache_is_current) {
bool copied = copy_app_db_title_list(list_out, &cache->list);
pthread_mutex_unlock(&g_app_db_cache_mutex);
return copied;
}
uint64_t revision = cache->revision;
pthread_mutex_unlock(&g_app_db_cache_mutex);
bool copied = copy_app_db_title_list(list_out, &g_app_db_title_cache);
pthread_mutex_unlock(&g_app_db_mutex);
struct AppDbTitleList fresh = {0};
bool loaded = app_db_stat_rc == 0 && load_fn(&fresh);
struct stat verified_st;
bool source_unchanged =
loaded && stat(APP_DB_PATH, &verified_st) == 0 &&
verified_st.st_mtime == st.st_mtime;
pthread_mutex_lock(&g_app_db_cache_mutex);
if (source_unchanged && cache->revision == revision) {
free_app_db_title_list(&cache->list);
cache->list = fresh;
memset(&fresh, 0, sizeof(fresh));
cache->mtime = st.st_mtime;
cache->ready = true;
++cache->revision;
}
bool copied = cache->ready &&
copy_app_db_title_list(list_out, &cache->list);
pthread_mutex_unlock(&g_app_db_cache_mutex);
free_app_db_title_list(&fresh);
return copied;
}
bool get_app_db_title_list_cached(struct AppDbTitleList *list_out) {
if (!list_out)
return false;
return get_cached_app_db_title_list(list_out, &g_app_db_title_cache,
load_app_db_title_list);
}
bool get_app_db_auto_remove_title_list(struct AppDbTitleList *list_out) {
if (!list_out)
return false;
return get_cached_app_db_title_list(
list_out, &g_app_db_auto_remove_cache,
load_app_db_auto_remove_title_list);
}
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);
return get_cached_app_db_title_list(
list_out, &g_app_db_blocked_uninstall_ppsa_cache,
load_app_db_blocked_uninstall_ppsa_list);
}
struct stat st;
int app_db_stat_rc = stat(APP_DB_PATH, &st);
static void copy_sqlite_text(sqlite3_stmt *stmt, int column, char *out,
size_t out_size) {
const unsigned char *value = sqlite3_column_text(stmt, column);
(void)strlcpy(out, value ? (const char *)value : "", out_size);
}
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;
}
}
static bool copy_json_string(struct json_object *object, const char *key,
char *out, size_t out_size) {
struct json_object *value = NULL;
if (!json_object_object_get_ex(object, key, &value) ||
!json_object_is_type(value, json_type_string)) {
return false;
}
const char *text = json_object_get_string(value);
size_t length = (size_t)json_object_get_string_len(value);
if (length == 0 || length >= out_size || memchr(text, '\0', length))
return false;
memcpy(out, text, length + 1u);
return true;
}
static void apply_app_info_metadata(sqlite3_stmt *stmt, int column,
sm_app_db_game_info_t *entry) {
// ShellCore updates these JSON values while the top-level timestamp columns
// can remain at their initial install values.
const unsigned char *text = sqlite3_column_text(stmt, column);
if (!text || text[0] == '\0')
return;
struct json_object *app_info =
json_tokener_parse((const char *)text);
if (!json_object_is_type(app_info, json_type_object)) {
json_object_put(app_info);
return;
}
copy_json_string(app_info, "#_last_access_time", entry->last_access_time,
sizeof(entry->last_access_time));
copy_json_string(app_info, "#_install_time", entry->install_time,
sizeof(entry->install_time));
if (!copy_json_string(app_info, "CONTENT_VERSION", entry->version,
sizeof(entry->version)))
copy_json_string(app_info, "APP_VER", entry->version, sizeof(entry->version));
json_object_put(app_info);
}
bool app_db_game_info_snapshot(sm_app_db_game_info_t **entries_out,
size_t *count_out) {
if (!entries_out || !count_out)
return false;
*entries_out = NULL;
*count_out = 0;
sqlite3 *db = NULL;
int rc = sqlite3_open_v2(APP_DB_PATH, &db, SQLITE_OPEN_READONLY, NULL);
if (rc != SQLITE_OK) {
if (db)
(void)close_sqlite_handle(&db, "game metadata snapshot");
errno = EIO;
return false;
}
(void)sqlite3_busy_timeout(db, APP_DB_API_BUSY_TIMEOUT_MS);
static const char sql[] =
"SELECT titleId, contentId, titleName, lastAccessTime, installTime, "
"icon0Info, platform, size, AppInfoJson, metaDataPath FROM tbl_contentinfo "
"WHERE titleId != '' ORDER BY titleId COLLATE BINARY;";
sqlite3_stmt *stmt = NULL;
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
if (rc != SQLITE_OK) {
log_debug(" [DB] prepare failed for game metadata snapshot: rc=%d err=%s",
rc, sqlite3_errmsg(db));
(void)close_sqlite_handle(&db, "game metadata snapshot");
errno = EIO;
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;
sm_app_db_game_info_t *entries = NULL;
size_t count = 0;
size_t capacity = 0;
int busy_attempts = 0;
bool loaded = false;
while (!should_stop_requested() && !runtime_sleep_mode_active()) {
rc = sqlite3_step(stmt);
if (rc == SQLITE_ROW) {
busy_attempts = 0;
if (count == capacity) {
size_t new_capacity = capacity > 0 ? capacity * 2u : 128u;
if (new_capacity < capacity ||
new_capacity > SIZE_MAX / sizeof(*entries)) {
errno = ENOMEM;
break;
}
sm_app_db_game_info_t *new_entries =
realloc(entries, new_capacity * sizeof(*new_entries));
if (!new_entries) {
errno = ENOMEM;
break;
}
entries = new_entries;
capacity = new_capacity;
}
sm_app_db_game_info_t *entry = &entries[count++];
memset(entry, 0, sizeof(*entry));
copy_sqlite_text(stmt, 0, entry->title_id, sizeof(entry->title_id));
copy_sqlite_text(stmt, 1, entry->content_id, sizeof(entry->content_id));
copy_sqlite_text(stmt, 2, entry->title_name, sizeof(entry->title_name));
copy_sqlite_text(stmt, 3, entry->last_access_time,
sizeof(entry->last_access_time));
copy_sqlite_text(stmt, 4, entry->install_time,
sizeof(entry->install_time));
copy_sqlite_text(stmt, 5, entry->icon_path, sizeof(entry->icon_path));
entry->platform = sqlite3_column_type(stmt, 6) == SQLITE_NULL
? -1
: sqlite3_column_int(stmt, 6);
sqlite3_int64 installed_size = sqlite3_column_int64(stmt, 7);
entry->installed_size =
installed_size > 0 ? (uint64_t)installed_size : 0;
apply_app_info_metadata(stmt, 8, entry);
copy_sqlite_text(stmt, 9, entry->metadata_path,
sizeof(entry->metadata_path));
continue;
}
if (rc == SQLITE_DONE) {
loaded = true;
break;
}
if ((rc == SQLITE_BUSY || rc == SQLITE_LOCKED) &&
++busy_attempts < APP_DB_QUERY_BUSY_RETRIES) {
sceKernelUsleep(APP_DB_BUSY_RETRY_SLEEP_US);
continue;
}
log_debug(" [DB] game metadata snapshot failed: rc=%d err=%s", rc,
sqlite3_errmsg(db));
errno = EIO;
break;
}
(void)sqlite3_finalize(stmt);
if (!close_sqlite_handle(&db, "game metadata snapshot"))
loaded = false;
if (!loaded) {
if (should_stop_requested() || runtime_sleep_mode_active())
errno = ECANCELED;
else if (errno == 0)
errno = EIO;
free(entries);
return false;
}
*entries_out = entries;
*count_out = count;
return true;
}
const sm_app_db_game_info_t *app_db_find_game_info(
const sm_app_db_game_info_t *entries, size_t count, const char *title_id) {
size_t low = 0;
size_t high = count;
while (low < high) {
size_t mid = low + (high - low) / 2u;
int comparison = strcmp(entries[mid].title_id, title_id);
if (comparison < 0)
low = mid + 1u;
else
high = mid;
}
return low < count && strcmp(entries[low].title_id, title_id) == 0
? &entries[low]
: NULL;
}
+861 -523
View File
File diff suppressed because it is too large. Load diff
+1784 -117
View File
File diff suppressed because it is too large. Load diff
+86
View File
@@ -0,0 +1,86 @@
#include "sm_platform.h"
#include "sm_config_mount.h"
#include "sm_fan.h"
#include "sm_limits.h"
#include "sm_log.h"
#include "sm_types.h"
#define FAN_DEVICE "/dev/icc_fan"
#define FAN_GET_AUTOSERVO 0xC01C8F08UL
#define FAN_SET_AUTOSERVO 0xC01C8F07UL
#define FAN_CONFIG_SIZE 28u
#define FAN_SELECTOR 0u
#define FAN_TARGET_OFFSET 5u
static int get_fan_config(int fd, uint8_t config[FAN_CONFIG_SIZE]) {
memset(config, 0, FAN_CONFIG_SIZE);
config[2] = FAN_SELECTOR;
return ioctl(fd, FAN_GET_AUTOSERVO, config);
}
static int set_fan_target(int fd, uint8_t target_c,
uint8_t *previous_target_out) {
uint8_t current[FAN_CONFIG_SIZE];
uint8_t verified[FAN_CONFIG_SIZE];
if (get_fan_config(fd, current) != 0)
return -1;
*previous_target_out = current[FAN_TARGET_OFFSET];
if (*previous_target_out == target_c)
return 0;
current[FAN_TARGET_OFFSET] = target_c;
if (ioctl(fd, FAN_SET_AUTOSERVO, current) != 0)
return -1;
if (get_fan_config(fd, verified) != 0)
return -1;
if (verified[FAN_TARGET_OFFSET] == target_c)
return 0;
errno = EIO;
return -1;
}
static int apply_fan_target(uint8_t target_c,
uint8_t *previous_target_out) {
int fd = open(FAN_DEVICE, O_RDWR | O_DIRECT);
if (fd < 0)
return -1;
int result = set_fan_target(fd, target_c, previous_target_out);
int saved_errno = errno;
close(fd);
errno = saved_errno;
return result;
}
void sm_fan_apply_game_target(const char *title_id) {
static bool target_notification_sent = false;
uint32_t target_c = runtime_config().fan_target_temperature_c;
if (target_c == FAN_TARGET_TEMPERATURE_SYSTEM)
return;
uint8_t previous_target = 0;
if (apply_fan_target((uint8_t)target_c, &previous_target) != 0) {
log_debug(" [FAN] target apply failed for %s: %s", title_id,
strerror(errno));
return;
}
if (previous_target == target_c) {
log_debug(" [FAN] target already %u C for %s", (unsigned)target_c,
title_id);
} else {
log_debug(" [FAN] target applied for %s: %u C -> %u C", title_id,
(unsigned)previous_target, (unsigned)target_c);
}
if (!target_notification_sent) {
target_notification_sent = true;
notify_system_info_l10n(SM_L10N_TEMPERATURE_TRANSITION,
(unsigned)previous_target, (unsigned)target_c);
}
}
+591 -130
View File
File diff suppressed because it is too large. Load diff
Loaded 100 of 144 files, more files were not shown because too many files have changed in this diff. Show more