From 712ff87d6adcaa7381baa6248b79091f8fb688bc Mon Sep 17 00:00:00 2001 From: phantomptr Date: Sat, 17 Oct 2026 16:19:19 -0700 Subject: [PATCH] console page: final hardware-pass fixes Web threads get the 512 KiB mgmt stack (they run mgmt handlers); a recent folder that is gone (404) is dropped via a new prefs "forget" op; a non-exFAT image inside a .ffpfsc reads as unsupported, not damaged, and is logged to stderr; text find shows its count after a pause in typing; free-name probes use /api/fs/stat missing_ok (200 {"missing":true}) so no 404 noise. hw_info comment updated for the page's 10 s HW_TEMPS poll on a web worker. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01RxCGb8Asp1A1AQYiv1kbgS --- Makefile | 2 +- client/src/console/files/Files.tsx | 10 ++++-- client/src/console/files/UnpackDialog.tsx | 3 +- client/src/console/files/moreActions.test.tsx | 5 +++ client/src/console/files/navigation.test.tsx | 5 +++ client/src/console/files/paste.ts | 5 ++- client/src/console/files/prefs.ts | 13 ++++++-- client/src/console/files/viewer/TextView.tsx | 32 ++++++++++++++++++- client/src/console/files/viewerFlows.test.tsx | 16 +++++++++- client/src/console/locales/ar.json | 1 + client/src/console/locales/bn.json | 1 + client/src/console/locales/de.json | 1 + client/src/console/locales/en.json | 1 + client/src/console/locales/es.json | 1 + client/src/console/locales/fa.json | 1 + client/src/console/locales/fr.json | 1 + client/src/console/locales/hi.json | 1 + client/src/console/locales/hu.json | 1 + client/src/console/locales/id.json | 1 + client/src/console/locales/it.json | 1 + client/src/console/locales/ja.json | 1 + client/src/console/locales/ko.json | 1 + client/src/console/locales/pl.json | 1 + client/src/console/locales/pt-BR.json | 1 + client/src/console/locales/ru.json | 1 + client/src/console/locales/th.json | 1 + client/src/console/locales/tr.json | 1 + client/src/console/locales/vi.json | 1 + client/src/console/locales/zh-CN.json | 1 + client/src/console/locales/zh-TW.json | 1 + client/src/i18n/locales/ar.ts | 1 + client/src/i18n/locales/bn.ts | 1 + client/src/i18n/locales/de.ts | 1 + client/src/i18n/locales/en.ts | 1 + client/src/i18n/locales/es.ts | 1 + client/src/i18n/locales/fa.ts | 1 + client/src/i18n/locales/fr.ts | 1 + client/src/i18n/locales/hi.ts | 1 + client/src/i18n/locales/hu.ts | 1 + client/src/i18n/locales/id.ts | 1 + client/src/i18n/locales/it.ts | 1 + client/src/i18n/locales/ja.ts | 1 + client/src/i18n/locales/ko.ts | 1 + client/src/i18n/locales/pl.ts | 1 + client/src/i18n/locales/pt-BR.ts | 1 + client/src/i18n/locales/ru.ts | 1 + client/src/i18n/locales/th.ts | 1 + client/src/i18n/locales/tr.ts | 1 + client/src/i18n/locales/vi.ts | 1 + client/src/i18n/locales/zh-CN.ts | 1 + client/src/i18n/locales/zh-TW.ts | 1 + payload/include/files_prefs.h | 9 ++++-- payload/src/ffpfsc_unpack.c | 2 ++ payload/src/files_prefs.c | 18 +++++++++-- payload/src/hw_info.c | 10 ++++-- payload/src/webui_api.c | 13 +++++++- payload/src/webui_server.c | 3 +- payload/tests/files_prefs_selftest.c | 9 +++++- 58 files changed, 177 insertions(+), 20 deletions(-) diff --git a/Makefile b/Makefile index b3763550..52d0547c 100644 --- a/Makefile +++ b/Makefile @@ -908,7 +908,7 @@ test-payload: payload $(PAYLOAD_DIR)/tests/webui_auth_selftest.c $(PAYLOAD_DIR)/src/webui_auth.c @/tmp/ps5upload-webui-auth-selftest @echo "Running webui server self-test (host build, ~25 s)..." - @cc -O2 -Wall -Wextra -Werror -pthread -I$(PAYLOAD_DIR)/include \ + @cc -O2 -Wall -Wextra -Werror -pthread -I$(PAYLOAD_DIR)/include -I$(PAYLOAD_DIR)/ava1 \ -o /tmp/ps5upload-webui-server-selftest \ $(PAYLOAD_DIR)/tests/webui_server_selftest.c $(PAYLOAD_DIR)/src/webui_server.c \ $(PAYLOAD_DIR)/src/webui_http.c $(PAYLOAD_DIR)/src/webui_auth.c diff --git a/client/src/console/files/Files.tsx b/client/src/console/files/Files.tsx index 6148a243..5a14e1de 100644 --- a/client/src/console/files/Files.tsx +++ b/client/src/console/files/Files.tsx @@ -76,7 +76,7 @@ import { PathBar } from "./PathBar"; import { DEFAULT_VIEW, invertShown, loadView, saveView, selectAllShown, statusOf, visibleEntries, LOAD_ALL_CAP, type Status, type ViewPrefs } from "./listing"; import { checkFolder, typedPath } from "./pathParts"; import { PathPicker } from "./PathPicker"; -import { isPinned, usePrefs, type FilesPrefs } from "./prefs"; +import { cleanPath, isPinned, usePrefs, type FilesPrefs } from "./prefs"; import { TextPrompt } from "./TextPrompt"; import { ViewMenu } from "./ViewMenu"; import { useDir, type DirState, type Entry } from "./useDir"; @@ -1039,7 +1039,7 @@ export function Files({ ctx }: { ctx: SectionContext }) { const [message, setMessage] = useState(null); const [askBusy, setAskBusy] = useState(false); const [askError, setAskError] = useState(null); - const { prefs, settled: prefsSettled, pin, unpin, visit } = usePrefs(); + const { prefs, settled: prefsSettled, pin, unpin, visit, forget } = usePrefs(); /** Files was moved somewhere (or opened at a place): the last location is not restored any more. */ const moved = useRef(wanted != null); /** The folder restored from the last visit (left for the drive list when it is gone). */ @@ -1117,6 +1117,12 @@ export function Files({ ctx }: { ctx: SectionContext }) { go(null); }, [path, dir.error, go]); + // A recent folder that is gone (deleted, renamed, its drive unplugged) leaves Recent. + useEffect(() => { + if (path == null || !(dir.error instanceof ApiError) || dir.error.status !== 404) return; + if (prefs?.recent.includes(cleanPath(path))) forget(cleanPath(path)); + }, [path, dir.error, prefs, forget]); + // A folder that opened is a visit: recent folders and the last location (on the console). useEffect(() => { if (path == null || !dir.loaded || dir.error || visited.current === path) return; diff --git a/client/src/console/files/UnpackDialog.tsx b/client/src/console/files/UnpackDialog.tsx index ccd5b58c..d45c6d29 100644 --- a/client/src/console/files/UnpackDialog.tsx +++ b/client/src/console/files/UnpackDialog.tsx @@ -17,7 +17,8 @@ export function unpackCauseText(code: string): string | null { if (/no_space/.test(code)) return t("cw_files_unpack_no_space_short"); if (/destination_exists/.test(code)) return t("cw_files_err_exists"); if (/unpack_busy/.test(code)) return t("cw_files_unpack_busy"); - if (/corrupt_block|inner_missing|size_mismatch|not_exfat/.test(code)) return t("cw_files_unpack_damaged"); + if (/not_exfat/.test(code)) return t("cw_files_unpack_not_image"); + if (/corrupt_block|inner_missing|size_mismatch/.test(code)) return t("cw_files_unpack_damaged"); if (/path_not_allowed/.test(code)) return t("cw_files_err_denied"); return null; } diff --git a/client/src/console/files/moreActions.test.tsx b/client/src/console/files/moreActions.test.tsx index a5c661bc..39f4a4e7 100644 --- a/client/src/console/files/moreActions.test.tsx +++ b/client/src/console/files/moreActions.test.tsx @@ -291,6 +291,11 @@ describe("decompress", () => { ); const failed = unpackMessage({ end: "failed", status: { ...status, state: "failed", result: null, message: "fs_unpack_no_space: needs 9 bytes, 1 free" } }, "g"); expect(failed).toEqual({ tone: "error", text: t("cw_files_unpack_failed", { reason: t("cw_files_unpack_no_space_short") }) }); + // An image of another file system is unsupported, not damaged. + const other = unpackMessage({ end: "failed", status: { ...status, state: "failed", result: null, message: "fs_unpack_not_exfat" } }, "g"); + expect(other?.text).toBe(t("cw_files_unpack_failed", { reason: t("cw_files_unpack_not_image") })); + const bad = unpackMessage({ end: "failed", status: { ...status, state: "failed", result: null, message: "fs_unpack_corrupt_block 3" } }, "g"); + expect(bad?.text).toBe(t("cw_files_unpack_failed", { reason: t("cw_files_unpack_damaged") })); const odd = unpackMessage({ end: "failed", status: { ...status, state: "failed", result: null, message: "fs_unpack_weird" } }, "g"); expect(odd?.text).toContain("fs_unpack_weird"); expect(unpackMessage({ end: "cancelled", status }, "g")?.text).toBe(t("cw_files_unpack_stopped")); diff --git a/client/src/console/files/navigation.test.tsx b/client/src/console/files/navigation.test.tsx index c8d8f35e..9646e845 100644 --- a/client/src/console/files/navigation.test.tsx +++ b/client/src/console/files/navigation.test.tsx @@ -385,6 +385,11 @@ describe("eject and unmount", () => { expect(seen).toEqual([["/b", "/a"]]); mockCall.mockRejectedValueOnce(new ApiError(409, "prefs_full")); await expect(apply("pin", "/c")).rejects.toThrow(); + // A gone recent folder is forgotten through the same route. + mockCall.mockResolvedValueOnce({ favorites: [], recent: ["/a"], last: "/a" } as never); + await apply("forget", "/b"); + expect(mockCall).toHaveBeenLastCalledWith("/api/files/prefs/set", { op: "forget", path: "/b" }); + expect(seen[seen.length - 1]).toEqual(["/a"]); }); it("the helper's refusals read as what to do", () => { diff --git a/client/src/console/files/paste.ts b/client/src/console/files/paste.ts index 6400dd09..3de2752c 100644 --- a/client/src/console/files/paste.ts +++ b/client/src/console/files/paste.ts @@ -106,7 +106,10 @@ export function sameEntry(a: Stat | null | undefined, b: Stat | null | undefined /** The entry at `path`, or null when nothing is there (404). */ export async function statOrNull(path: string): Promise { try { - return await call("/api/fs/stat", { path }); + // missing_ok: the helper answers 200 {"missing":true} for a path that is not there (no 404 + // in the browser's console); an older helper ignores it and answers 404, read the same way. + const st = await call("/api/fs/stat", { path, missing_ok: true }); + return "missing" in st && st.missing === true ? null : (st as Stat); } catch (e) { if (e instanceof ApiError && e.status === 404) return null; throw e; diff --git a/client/src/console/files/prefs.ts b/client/src/console/files/prefs.ts index 9e5c9b47..3331bc27 100644 --- a/client/src/console/files/prefs.ts +++ b/client/src/console/files/prefs.ts @@ -14,7 +14,7 @@ export interface FilesPrefs { export const EMPTY_PREFS: FilesPrefs = { favorites: [], recent: [], last: null }; -export type PrefsOp = "pin" | "unpin" | "visit"; +export type PrefsOp = "pin" | "unpin" | "visit" | "forget"; const strings = (v: unknown): string[] => Array.isArray(v) ? v.filter((x): x is string => typeof x === "string" && x.startsWith("/")) : []; @@ -73,6 +73,8 @@ export interface PrefsState { pin: (path: string) => Promise; unpin: (path: string) => Promise; visit: (path: string) => void; + /** Drops a folder that no longer exists from Recent (fire-and-forget, like a visit). */ + forget: (path: string) => void; } /** @@ -122,5 +124,12 @@ export function usePrefs(): PrefsState { }, [apply], ); - return { prefs, settled, pin, unpin, visit }; + const forget = useCallback( + (path: string) => { + if (!supported.current) return; + apply("forget", path).catch(() => undefined); + }, + [apply], + ); + return { prefs, settled, pin, unpin, visit, forget }; } diff --git a/client/src/console/files/viewer/TextView.tsx b/client/src/console/files/viewer/TextView.tsx index 26a2e321..b1734b37 100644 --- a/client/src/console/files/viewer/TextView.tsx +++ b/client/src/console/files/viewer/TextView.tsx @@ -34,6 +34,29 @@ interface Found { const NONE: Found = { query: "", matches: [], capped: false }; +/** A pause in typing this long finds what the field holds (the count shows without pressing Next). */ +export const FIND_TYPED_MS = 350; + +export interface FindState { + found: Found; + current: number; + win: RowWindow; +} + +/** + * Find `field` as typed: a new query is scanned and its first match from the + * window's top becomes current; an empty field clears the find; the query + * already found is left as it is (Next / Previous step through it). + */ +export function findTyped(s: FindState, field: string, rows: Row[]): FindState { + if (field === s.found.query) return s; + if (!field) return { ...s, found: NONE, current: -1 }; + const found = { query: field, ...findMatches(rows, field) }; + const current = firstMatchFrom(found.matches, s.win.start); + const win = current >= 0 ? windowAround(found.matches[current].row, s.win, rows.length) : s.win; + return { found, current, win }; +} + /** One focusable block of rows. Memoized: Next / Previous re-render only the blocks whose current mark changes. */ const Block = memo(function Block({ rows, @@ -86,7 +109,7 @@ const Block = memo(function Block({ * D-pad steps through. Text is rendered as text nodes, never HTML. */ export function TextView({ rows, initialQuery }: { rows: Row[]; /** Tests: start with this found. */ initialQuery?: string }) { - const [state, setState] = useState(() => { + const [state, setState] = useState(() => { const found = initialQuery ? { query: initialQuery, ...findMatches(rows, initialQuery) } : NONE; const current = found.matches.length ? 0 : -1; const win0: RowWindow = { start: 0, end: Math.min(rows.length, ROW_WINDOW) }; @@ -128,6 +151,13 @@ export function TextView({ rows, initialQuery }: { rows: Row[]; /** Tests: start if (el && typeof el.scrollIntoView === "function") el.scrollIntoView({ block: "center", inline: "nearest" }); }, [current, win]); + // Typing pauses: find it, so the count shows at once (the PS5 keyboard's Done may not submit). + useEffect(() => { + if (field === found.query) return; + const id = setTimeout(() => setState((s) => findTyped(s, field, rows)), FIND_TYPED_MS); + return () => clearTimeout(id); + }, [field, found.query, rows]); + /** Find what the field holds: a new query (scanned once) starts at its first match; the same one steps on. */ const go = (dir: 1 | -1) => { setState((s) => { diff --git a/client/src/console/files/viewerFlows.test.tsx b/client/src/console/files/viewerFlows.test.tsx index 90be2043..d1027971 100644 --- a/client/src/console/files/viewerFlows.test.tsx +++ b/client/src/console/files/viewerFlows.test.tsx @@ -14,7 +14,7 @@ import { FilesView, sheetActions, type FilesViewProps } from "./Files"; import type { DirState, Entry } from "./useDir"; import { GameBanner, GameBannerView } from "./viewer/GameBanner"; import { HexView } from "./viewer/HexView"; -import { TextView, findCountText } from "./viewer/TextView"; +import { TextView, findCountText, findTyped, type FindState } from "./viewer/TextView"; import { ROW_WINDOW, toRows } from "./viewer/text"; import { GameSummary, textModel, Viewer } from "./Viewer"; @@ -154,6 +154,20 @@ describe("viewer in Files", () => { expect(findCountText("x", 10000, 0, true)).toContain(t("cw_files_vw_find_capped")); }); + it("typing finds at once: the count shows without Next; Next then steps on", () => { + const rows = toRows("alpha\nbeta alpha\ngamma\nalpha"); + const s0: FindState = { found: { query: "", matches: [], capped: false }, current: -1, win: { start: 0, end: rows.length } }; + const s1 = findTyped(s0, "alpha", rows); + expect(s1.found.matches.length).toBe(3); + expect(s1.current).toBe(0); + expect(findCountText(s1.found.query, s1.found.matches.length, s1.current, false)).toBe(t("cw_files_vw_find_at", { at: 1, n: "3" })); + expect(findTyped(s1, "alpha", rows)).toBe(s1); // already found: unchanged + expect(findTyped(s1, "zzz", rows).found.matches).toEqual([]); + const cleared = findTyped(s1, "", rows); + expect(cleared.found.query).toBe(""); + expect(cleared.current).toBe(-1); + }); + it("a find jump in a 50,000-line file draws a bounded window around the match", () => { const lines = Array.from({ length: 50_000 }, (_, i) => (i === 40_000 ? "the needle is here" : `line ${i + 1}`)); const html = renderToStaticMarkup(); diff --git a/client/src/console/locales/ar.json b/client/src/console/locales/ar.json index 698f1647..4628487a 100644 --- a/client/src/console/locales/ar.json +++ b/client/src/console/locales/ar.json @@ -279,6 +279,7 @@ "cw_files_unpack_no_space": "لا توجد مساحة كافية: يحتاج {need} (الصورة مع 1 GiB)، والمتاح {free}.", "cw_files_unpack_no_space_short": "لا توجد مساحة كافية على ذلك المحرك.", "cw_files_unpack_not_container": "هذا ليس ملف .ffpfsc يستطيع الجهاز قراءته.", + "cw_files_unpack_not_image": "الصورة الموجودة بداخله ليست صورة exFAT، لذا فهي غير مدعومة.", "cw_files_unpack_stopped": "أُوقف فك الضغط. لم يُحتفظ بشيء.", "cw_files_unpack_title": "فك ضغط «{name}»", "cw_files_unpack_wait_batch": "مهمة أخرى قيد التشغيل. يمكن أن يبدأ فك الضغط بعد انتهائها.", diff --git a/client/src/console/locales/bn.json b/client/src/console/locales/bn.json index b0919219..bbced57e 100644 --- a/client/src/console/locales/bn.json +++ b/client/src/console/locales/bn.json @@ -279,6 +279,7 @@ "cw_files_unpack_no_space": "যথেষ্ট খালি জায়গা নেই: {need} প্রয়োজন (ইমেজ ও 1 GiB), খালি আছে {free}।", "cw_files_unpack_no_space_short": "ওই ড্রাইভে যথেষ্ট খালি জায়গা নেই।", "cw_files_unpack_not_container": "এটি এমন .ffpfsc নয় যা কনসোল পড়তে পারে।", + "cw_files_unpack_not_image": "ভেতরের ইমেজটি exFAT ইমেজ নয়, তাই এটি সমর্থিত নয়।", "cw_files_unpack_stopped": "ডিকম্প্রেস থামানো হয়েছে। কিছুই রাখা হয়নি।", "cw_files_unpack_title": "“{name}” ডিকম্প্রেস করুন", "cw_files_unpack_wait_batch": "অন্য একটি কাজ চলছে। সেটি শেষ হলে ডিকম্প্রেস শুরু হতে পারে।", diff --git a/client/src/console/locales/de.json b/client/src/console/locales/de.json index ab9d9f87..ce056eab 100644 --- a/client/src/console/locales/de.json +++ b/client/src/console/locales/de.json @@ -279,6 +279,7 @@ "cw_files_unpack_no_space": "Nicht genug freier Speicher: Benötigt werden {need} (das Abbild plus 1 GiB), frei sind {free}.", "cw_files_unpack_no_space_short": "Nicht genug freier Speicher auf diesem Laufwerk.", "cw_files_unpack_not_container": "Das ist keine .ffpfsc-Datei, die die Konsole lesen kann.", + "cw_files_unpack_not_image": "Das Image darin ist kein exFAT-Image und wird daher nicht unterstützt.", "cw_files_unpack_stopped": "Entpacken gestoppt. Nichts wurde behalten.", "cw_files_unpack_title": "„{name}“ entpacken", "cw_files_unpack_wait_batch": "Eine andere Aufgabe läuft. Entpacken kann starten, wenn sie fertig ist.", diff --git a/client/src/console/locales/en.json b/client/src/console/locales/en.json index 12d74d59..cf210da7 100644 --- a/client/src/console/locales/en.json +++ b/client/src/console/locales/en.json @@ -279,6 +279,7 @@ "cw_files_unpack_no_space": "Not enough free space: it needs {need} (the image plus 1 GiB), and {free} is free.", "cw_files_unpack_no_space_short": "Not enough free space on that drive.", "cw_files_unpack_not_container": "This is not a .ffpfsc the console can read.", + "cw_files_unpack_not_image": "The image inside is not an exFAT image, so it isn't supported.", "cw_files_unpack_stopped": "Decompress stopped. Nothing was kept.", "cw_files_unpack_title": "Decompress “{name}”", "cw_files_unpack_wait_batch": "Another task is running. Decompress can start when it has finished.", diff --git a/client/src/console/locales/es.json b/client/src/console/locales/es.json index 1c972a46..796f8acb 100644 --- a/client/src/console/locales/es.json +++ b/client/src/console/locales/es.json @@ -279,6 +279,7 @@ "cw_files_unpack_no_space": "No hay espacio suficiente: necesita {need} (la imagen más 1 GiB) y hay {free} libres.", "cw_files_unpack_no_space_short": "No hay espacio suficiente en esa unidad.", "cw_files_unpack_not_container": "No es un .ffpfsc que la consola pueda leer.", + "cw_files_unpack_not_image": "La imagen que contiene no es una imagen exFAT, así que no es compatible.", "cw_files_unpack_stopped": "Descompresión detenida. No se conservó nada.", "cw_files_unpack_title": "Descomprimir «{name}»", "cw_files_unpack_wait_batch": "Hay otra tarea en curso. Descomprimir podrá empezar cuando termine.", diff --git a/client/src/console/locales/fa.json b/client/src/console/locales/fa.json index cc19f425..dccc4e38 100644 --- a/client/src/console/locales/fa.json +++ b/client/src/console/locales/fa.json @@ -279,6 +279,7 @@ "cw_files_unpack_no_space": "فضای آزاد کافی نیست: {need} لازم است (ایمیج به‌علاوهٔ 1 GiB) و {free} آزاد است.", "cw_files_unpack_no_space_short": "فضای آزاد آن درایو کافی نیست.", "cw_files_unpack_not_container": "این یک .ffpfsc قابل خواندن برای کنسول نیست.", + "cw_files_unpack_not_image": "ایمیج داخل آن ایمیج exFAT نیست، پس پشتیبانی نمی‌شود.", "cw_files_unpack_stopped": "باز کردن متوقف شد. چیزی نگه داشته نشد.", "cw_files_unpack_title": "باز کردن فشرده‌سازی «{name}»", "cw_files_unpack_wait_batch": "کار دیگری در حال اجراست. پس از پایان آن می‌توان شروع کرد.", diff --git a/client/src/console/locales/fr.json b/client/src/console/locales/fr.json index edec1b7c..16e04eb9 100644 --- a/client/src/console/locales/fr.json +++ b/client/src/console/locales/fr.json @@ -279,6 +279,7 @@ "cw_files_unpack_no_space": "Pas assez d’espace libre : il faut {need} (l’image plus 1 GiB) et {free} sont libres.", "cw_files_unpack_no_space_short": "Pas assez d’espace libre sur ce disque.", "cw_files_unpack_not_container": "Ce n’est pas un .ffpfsc que la console peut lire.", + "cw_files_unpack_not_image": "L’image qu’il contient n’est pas une image exFAT : elle n’est pas prise en charge.", "cw_files_unpack_stopped": "Décompression arrêtée. Rien n’a été conservé.", "cw_files_unpack_title": "Décompresser « {name} »", "cw_files_unpack_wait_batch": "Une autre tâche est en cours. La décompression pourra commencer quand elle sera finie.", diff --git a/client/src/console/locales/hi.json b/client/src/console/locales/hi.json index 4c191f06..2357bc26 100644 --- a/client/src/console/locales/hi.json +++ b/client/src/console/locales/hi.json @@ -279,6 +279,7 @@ "cw_files_unpack_no_space": "पर्याप्त खाली जगह नहीं: {need} चाहिए (इमेज और 1 GiB), खाली {free} है।", "cw_files_unpack_no_space_short": "उस ड्राइव पर पर्याप्त खाली जगह नहीं है।", "cw_files_unpack_not_container": "यह ऐसी .ffpfsc नहीं है जिसे कंसोल पढ़ सके।", + "cw_files_unpack_not_image": "इसके अंदर की इमेज exFAT इमेज नहीं है, इसलिए यह समर्थित नहीं है।", "cw_files_unpack_stopped": "डीकंप्रेस रोका गया। कुछ भी नहीं रखा गया।", "cw_files_unpack_title": "“{name}” डीकंप्रेस करें", "cw_files_unpack_wait_batch": "दूसरा काम चल रहा है। उसके पूरा होने पर डीकंप्रेस शुरू हो सकता है।", diff --git a/client/src/console/locales/hu.json b/client/src/console/locales/hu.json index 8587efce..be2a05fc 100644 --- a/client/src/console/locales/hu.json +++ b/client/src/console/locales/hu.json @@ -279,6 +279,7 @@ "cw_files_unpack_no_space": "Nincs elég szabad hely: {need} kell (a kép plusz 1 GiB), és {free} szabad.", "cw_files_unpack_no_space_short": "Nincs elég szabad hely azon a meghajtón.", "cw_files_unpack_not_container": "Ez nem olyan .ffpfsc, amelyet a konzol olvasni tud.", + "cw_files_unpack_not_image": "A benne lévő image nem exFAT image, ezért nem támogatott.", "cw_files_unpack_stopped": "Kicsomagolás leállítva. Semmi sem maradt meg.", "cw_files_unpack_title": "„{name}” kicsomagolása", "cw_files_unpack_wait_batch": "Egy másik feladat fut. A kicsomagolás akkor indulhat, ha az véget ért.", diff --git a/client/src/console/locales/id.json b/client/src/console/locales/id.json index d485c735..dd1b2809 100644 --- a/client/src/console/locales/id.json +++ b/client/src/console/locales/id.json @@ -279,6 +279,7 @@ "cw_files_unpack_no_space": "Ruang kosong tidak cukup: butuh {need} (image plus 1 GiB), tersedia {free}.", "cw_files_unpack_no_space_short": "Ruang kosong di drive itu tidak cukup.", "cw_files_unpack_not_container": "Ini bukan .ffpfsc yang bisa dibaca konsol.", + "cw_files_unpack_not_image": "Image di dalamnya bukan image exFAT, jadi tidak didukung.", "cw_files_unpack_stopped": "Ekstrak dihentikan. Tidak ada yang disimpan.", "cw_files_unpack_title": "Ekstrak “{name}”", "cw_files_unpack_wait_batch": "Tugas lain sedang berjalan. Ekstrak bisa mulai setelah selesai.", diff --git a/client/src/console/locales/it.json b/client/src/console/locales/it.json index 0486fae2..dfff20ab 100644 --- a/client/src/console/locales/it.json +++ b/client/src/console/locales/it.json @@ -279,6 +279,7 @@ "cw_files_unpack_no_space": "Spazio libero insufficiente: servono {need} (l’immagine più 1 GiB) e ne sono liberi {free}.", "cw_files_unpack_no_space_short": "Spazio libero insufficiente su quell’unità.", "cw_files_unpack_not_container": "Non è un .ffpfsc che la console può leggere.", + "cw_files_unpack_not_image": "L’immagine al suo interno non è un’immagine exFAT, quindi non è supportata.", "cw_files_unpack_stopped": "Decompressione interrotta. Non è stato tenuto nulla.", "cw_files_unpack_title": "Decomprimi «{name}»", "cw_files_unpack_wait_batch": "È in corso un’altra attività. La decompressione potrà iniziare quando sarà finita.", diff --git a/client/src/console/locales/ja.json b/client/src/console/locales/ja.json index c50ea35f..6755e06a 100644 --- a/client/src/console/locales/ja.json +++ b/client/src/console/locales/ja.json @@ -279,6 +279,7 @@ "cw_files_unpack_no_space": "空き容量が足りません: {need}(イメージ + 1 GiB)が必要で、空きは {free} です。", "cw_files_unpack_no_space_short": "そのドライブの空き容量が足りません。", "cw_files_unpack_not_container": "本体が読み取れる .ffpfsc ではありません。", + "cw_files_unpack_not_image": "中のイメージは exFAT イメージではないため、対応していません。", "cw_files_unpack_stopped": "展開を停止しました。何も残っていません。", "cw_files_unpack_title": "「{name}」を展開", "cw_files_unpack_wait_batch": "別のタスクが実行中です。終わると展開を開始できます。", diff --git a/client/src/console/locales/ko.json b/client/src/console/locales/ko.json index c2059564..e19a5cbc 100644 --- a/client/src/console/locales/ko.json +++ b/client/src/console/locales/ko.json @@ -279,6 +279,7 @@ "cw_files_unpack_no_space": "공간이 부족합니다: {need}(이미지 + 1 GiB)가 필요한데 {free}만 남아 있습니다.", "cw_files_unpack_no_space_short": "그 드라이브에 공간이 부족합니다.", "cw_files_unpack_not_container": "콘솔이 읽을 수 있는 .ffpfsc가 아닙니다.", + "cw_files_unpack_not_image": "안에 있는 이미지가 exFAT 이미지가 아니어서 지원되지 않습니다.", "cw_files_unpack_stopped": "압축 풀기를 중지했습니다. 남은 것은 없습니다.", "cw_files_unpack_title": "“{name}” 압축 풀기", "cw_files_unpack_wait_batch": "다른 작업이 진행 중입니다. 끝나면 압축 풀기를 시작할 수 있습니다.", diff --git a/client/src/console/locales/pl.json b/client/src/console/locales/pl.json index 0ae2c690..b352b758 100644 --- a/client/src/console/locales/pl.json +++ b/client/src/console/locales/pl.json @@ -279,6 +279,7 @@ "cw_files_unpack_no_space": "Za mało wolnego miejsca: potrzeba {need} (obraz plus 1 GiB), wolne: {free}.", "cw_files_unpack_no_space_short": "Za mało wolnego miejsca na tym dysku.", "cw_files_unpack_not_container": "To nie jest plik .ffpfsc, który konsola potrafi odczytać.", + "cw_files_unpack_not_image": "Obraz w środku nie jest obrazem exFAT, więc nie jest obsługiwany.", "cw_files_unpack_stopped": "Rozpakowywanie zatrzymane. Nic nie zostało zachowane.", "cw_files_unpack_title": "Rozpakuj „{name}”", "cw_files_unpack_wait_batch": "Trwa inne zadanie. Rozpakowywanie może się zacząć, gdy się skończy.", diff --git a/client/src/console/locales/pt-BR.json b/client/src/console/locales/pt-BR.json index ee02b3ae..fde189ca 100644 --- a/client/src/console/locales/pt-BR.json +++ b/client/src/console/locales/pt-BR.json @@ -279,6 +279,7 @@ "cw_files_unpack_no_space": "Espaço livre insuficiente: precisa de {need} (a imagem mais 1 GiB) e há {free} livres.", "cw_files_unpack_no_space_short": "Espaço livre insuficiente nessa unidade.", "cw_files_unpack_not_container": "Não é um .ffpfsc que o console consiga ler.", + "cw_files_unpack_not_image": "A imagem dentro dele não é uma imagem exFAT, então não é compatível.", "cw_files_unpack_stopped": "Descompactação parada. Nada foi mantido.", "cw_files_unpack_title": "Descompactar “{name}”", "cw_files_unpack_wait_batch": "Outra tarefa está em andamento. A descompactação pode começar quando ela terminar.", diff --git a/client/src/console/locales/ru.json b/client/src/console/locales/ru.json index bca9855e..4917bd51 100644 --- a/client/src/console/locales/ru.json +++ b/client/src/console/locales/ru.json @@ -279,6 +279,7 @@ "cw_files_unpack_no_space": "Недостаточно места: нужно {need} (образ плюс 1 GiB), свободно {free}.", "cw_files_unpack_no_space_short": "Недостаточно свободного места на этом диске.", "cw_files_unpack_not_container": "Это не .ffpfsc, который консоль может прочитать.", + "cw_files_unpack_not_image": "Образ внутри не является образом exFAT, поэтому не поддерживается.", "cw_files_unpack_stopped": "Распаковка остановлена. Ничего не сохранено.", "cw_files_unpack_title": "Распаковать «{name}»", "cw_files_unpack_wait_batch": "Выполняется другая задача. Распаковка начнётся, когда она закончится.", diff --git a/client/src/console/locales/th.json b/client/src/console/locales/th.json index e415e68d..e7f373c2 100644 --- a/client/src/console/locales/th.json +++ b/client/src/console/locales/th.json @@ -279,6 +279,7 @@ "cw_files_unpack_no_space": "พื้นที่ว่างไม่พอ: ต้องใช้ {need} (อิมเมจบวก 1 GiB) ว่างอยู่ {free}", "cw_files_unpack_no_space_short": "พื้นที่ว่างในไดรฟ์นั้นไม่พอ", "cw_files_unpack_not_container": "นี่ไม่ใช่ .ffpfsc ที่เครื่องอ่านได้", + "cw_files_unpack_not_image": "อิมเมจด้านในไม่ใช่อิมเมจ exFAT จึงไม่รองรับ", "cw_files_unpack_stopped": "หยุดการแตกไฟล์แล้ว ไม่มีอะไรถูกเก็บไว้", "cw_files_unpack_title": "แตกไฟล์ “{name}”", "cw_files_unpack_wait_batch": "มีงานอื่นกำลังทำงานอยู่ จะแตกไฟล์ได้เมื่องานนั้นเสร็จ", diff --git a/client/src/console/locales/tr.json b/client/src/console/locales/tr.json index 81c52a78..8cc92144 100644 --- a/client/src/console/locales/tr.json +++ b/client/src/console/locales/tr.json @@ -279,6 +279,7 @@ "cw_files_unpack_no_space": "Yeterli boş alan yok: {need} gerekli (görüntü artı 1 GiB), {free} boş.", "cw_files_unpack_no_space_short": "O sürücüde yeterli boş alan yok.", "cw_files_unpack_not_container": "Bu, konsolun okuyabildiği bir .ffpfsc değil.", + "cw_files_unpack_not_image": "İçindeki imaj bir exFAT imajı değil, bu yüzden desteklenmiyor.", "cw_files_unpack_stopped": "Sıkıştırma açma durduruldu. Hiçbir şey tutulmadı.", "cw_files_unpack_title": "“{name}” sıkıştırmasını aç", "cw_files_unpack_wait_batch": "Başka bir görev çalışıyor. Bitince sıkıştırma açma başlayabilir.", diff --git a/client/src/console/locales/vi.json b/client/src/console/locales/vi.json index 4d4469bb..ebbd07b0 100644 --- a/client/src/console/locales/vi.json +++ b/client/src/console/locales/vi.json @@ -279,6 +279,7 @@ "cw_files_unpack_no_space": "Không đủ dung lượng trống: cần {need} (ảnh đĩa cộng 1 GiB), còn trống {free}.", "cw_files_unpack_no_space_short": "Không đủ dung lượng trống trên ổ đó.", "cw_files_unpack_not_container": "Đây không phải tệp .ffpfsc mà máy đọc được.", + "cw_files_unpack_not_image": "Ảnh bên trong không phải ảnh exFAT nên không được hỗ trợ.", "cw_files_unpack_stopped": "Đã dừng giải nén. Không giữ lại gì.", "cw_files_unpack_title": "Giải nén “{name}”", "cw_files_unpack_wait_batch": "Một tác vụ khác đang chạy. Giải nén có thể bắt đầu khi nó xong.", diff --git a/client/src/console/locales/zh-CN.json b/client/src/console/locales/zh-CN.json index a94aaff3..28211e5a 100644 --- a/client/src/console/locales/zh-CN.json +++ b/client/src/console/locales/zh-CN.json @@ -279,6 +279,7 @@ "cw_files_unpack_no_space": "可用空间不足:需要 {need}(镜像加 1 GiB),可用 {free}。", "cw_files_unpack_no_space_short": "该驱动器可用空间不足。", "cw_files_unpack_not_container": "这不是主机能读取的 .ffpfsc。", + "cw_files_unpack_not_image": "其中的映像不是 exFAT 映像,因此不受支持。", "cw_files_unpack_stopped": "解压已停止。没有保留任何内容。", "cw_files_unpack_title": "解压“{name}”", "cw_files_unpack_wait_batch": "另一个任务正在进行。完成后才能开始解压。", diff --git a/client/src/console/locales/zh-TW.json b/client/src/console/locales/zh-TW.json index 3fa6c417..27072600 100644 --- a/client/src/console/locales/zh-TW.json +++ b/client/src/console/locales/zh-TW.json @@ -279,6 +279,7 @@ "cw_files_unpack_no_space": "可用空間不足:需要 {need}(映像檔加 1 GiB),可用 {free}。", "cw_files_unpack_no_space_short": "該磁碟機可用空間不足。", "cw_files_unpack_not_container": "這不是主機能讀取的 .ffpfsc。", + "cw_files_unpack_not_image": "其中的映像不是 exFAT 映像,因此不受支援。", "cw_files_unpack_stopped": "解壓縮已停止。沒有保留任何內容。", "cw_files_unpack_title": "解壓縮「{name}」", "cw_files_unpack_wait_batch": "另一個工作正在進行。完成後才能開始解壓縮。", diff --git a/client/src/i18n/locales/ar.ts b/client/src/i18n/locales/ar.ts index 5c296b2e..f4bf75e4 100644 --- a/client/src/i18n/locales/ar.ts +++ b/client/src/i18n/locales/ar.ts @@ -4457,6 +4457,7 @@ c2c_test_deep_bad: "لم يصل الإرسال التجريبي بحجم 4 KiB: "cw_files_unpack_failed_generic": "ردّ الجهاز بـ«{cause}».", "cw_files_unpack_not_container": "هذا ليس ملف .ffpfsc يستطيع الجهاز قراءته.", "cw_files_unpack_damaged": "الملف تالف.", +"cw_files_unpack_not_image": "الصورة الموجودة بداخله ليست صورة exFAT، لذا فهي غير مدعومة.", "cw_files_unpack_busy": "يجري فك ضغط آخر على الجهاز.", "cw_files_vw_find": "البحث في هذا الملف", "cw_files_vw_prev": "السابق", diff --git a/client/src/i18n/locales/bn.ts b/client/src/i18n/locales/bn.ts index c42f8cf9..d2ca6597 100644 --- a/client/src/i18n/locales/bn.ts +++ b/client/src/i18n/locales/bn.ts @@ -4457,6 +4457,7 @@ c2c_test_deep_bad: "4 KiB-এর পরীক্ষামূলক পাঠা "cw_files_unpack_failed_generic": "কনসোল বলেছে “{cause}”।", "cw_files_unpack_not_container": "এটি এমন .ffpfsc নয় যা কনসোল পড়তে পারে।", "cw_files_unpack_damaged": "ফাইলটি ক্ষতিগ্রস্ত।", +"cw_files_unpack_not_image": "ভেতরের ইমেজটি exFAT ইমেজ নয়, তাই এটি সমর্থিত নয়।", "cw_files_unpack_busy": "কনসোলে আরেকটি ডিকম্প্রেস চলছে।", "cw_files_vw_find": "এই ফাইলে খুঁজুন", "cw_files_vw_prev": "আগের", diff --git a/client/src/i18n/locales/de.ts b/client/src/i18n/locales/de.ts index 538612ac..694c56f0 100644 --- a/client/src/i18n/locales/de.ts +++ b/client/src/i18n/locales/de.ts @@ -4465,6 +4465,7 @@ c2c_test_deep_bad: "Die 4-KiB-Testsendung ist nicht angekommen: {detail}", "cw_files_unpack_failed_generic": "Die Konsole meldete „{cause}“.", "cw_files_unpack_not_container": "Das ist keine .ffpfsc-Datei, die die Konsole lesen kann.", "cw_files_unpack_damaged": "Die Datei ist beschädigt.", +"cw_files_unpack_not_image": "Das Image darin ist kein exFAT-Image und wird daher nicht unterstützt.", "cw_files_unpack_busy": "Auf der Konsole läuft schon ein anderes Entpacken.", "cw_files_vw_find": "In dieser Datei suchen", "cw_files_vw_prev": "Zurück", diff --git a/client/src/i18n/locales/en.ts b/client/src/i18n/locales/en.ts index b3d4dd5d..04dbd814 100644 --- a/client/src/i18n/locales/en.ts +++ b/client/src/i18n/locales/en.ts @@ -4780,6 +4780,7 @@ c2c_test_deep_bad: "The 4 KiB test send did not arrive: {detail}", "cw_files_unpack_failed_generic": "the console said “{cause}”.", "cw_files_unpack_not_container": "This is not a .ffpfsc the console can read.", "cw_files_unpack_damaged": "The file is damaged.", +"cw_files_unpack_not_image": "The image inside is not an exFAT image, so it isn't supported.", "cw_files_unpack_busy": "Another decompress is running on the console.", "cw_files_vw_find": "Find in this file", "cw_files_vw_prev": "Previous", diff --git a/client/src/i18n/locales/es.ts b/client/src/i18n/locales/es.ts index 40f1b41e..b83e1107 100644 --- a/client/src/i18n/locales/es.ts +++ b/client/src/i18n/locales/es.ts @@ -4407,6 +4407,7 @@ c2c_test_deep_bad: "El envío de prueba de 4 KiB no llegó: {detail}", "cw_files_unpack_failed_generic": "la consola dijo «{cause}».", "cw_files_unpack_not_container": "No es un .ffpfsc que la consola pueda leer.", "cw_files_unpack_damaged": "El archivo está dañado.", +"cw_files_unpack_not_image": "La imagen que contiene no es una imagen exFAT, así que no es compatible.", "cw_files_unpack_busy": "Ya hay otra descompresión en curso en la consola.", "cw_files_vw_find": "Buscar en este archivo", "cw_files_vw_prev": "Anterior", diff --git a/client/src/i18n/locales/fa.ts b/client/src/i18n/locales/fa.ts index 9756ab04..89a8b24e 100644 --- a/client/src/i18n/locales/fa.ts +++ b/client/src/i18n/locales/fa.ts @@ -4758,6 +4758,7 @@ c2c_test_deep_bad: "ارسال آزمایشی ۴ KiB نرسید: {detail}", "cw_files_unpack_failed_generic": "کنسول گفت «{cause}».", "cw_files_unpack_not_container": "این یک .ffpfsc قابل خواندن برای کنسول نیست.", "cw_files_unpack_damaged": "فایل آسیب دیده است.", +"cw_files_unpack_not_image": "ایمیج داخل آن ایمیج exFAT نیست، پس پشتیبانی نمی‌شود.", "cw_files_unpack_busy": "باز کردن دیگری روی کنسول در حال اجراست.", "cw_files_vw_find": "جستجو در این فایل", "cw_files_vw_prev": "قبلی", diff --git a/client/src/i18n/locales/fr.ts b/client/src/i18n/locales/fr.ts index 3526153e..15b09e3a 100644 --- a/client/src/i18n/locales/fr.ts +++ b/client/src/i18n/locales/fr.ts @@ -4475,6 +4475,7 @@ c2c_test_deep_bad: "L’envoi test de 4 Kio n’est pas arrivé : {detail}", "cw_files_unpack_failed_generic": "la console a répondu « {cause} ».", "cw_files_unpack_not_container": "Ce n’est pas un .ffpfsc que la console peut lire.", "cw_files_unpack_damaged": "Le fichier est endommagé.", +"cw_files_unpack_not_image": "L’image qu’il contient n’est pas une image exFAT : elle n’est pas prise en charge.", "cw_files_unpack_busy": "Une autre décompression est déjà en cours sur la console.", "cw_files_vw_find": "Rechercher dans ce fichier", "cw_files_vw_prev": "Précédent", diff --git a/client/src/i18n/locales/hi.ts b/client/src/i18n/locales/hi.ts index a2d9eb82..f09403d4 100644 --- a/client/src/i18n/locales/hi.ts +++ b/client/src/i18n/locales/hi.ts @@ -4473,6 +4473,7 @@ c2c_test_deep_bad: "4 KiB का परीक्षण नहीं पहुँ "cw_files_unpack_failed_generic": "कंसोल ने कहा “{cause}”।", "cw_files_unpack_not_container": "यह ऐसी .ffpfsc नहीं है जिसे कंसोल पढ़ सके।", "cw_files_unpack_damaged": "फ़ाइल खराब है।", +"cw_files_unpack_not_image": "इसके अंदर की इमेज exFAT इमेज नहीं है, इसलिए यह समर्थित नहीं है।", "cw_files_unpack_busy": "कंसोल पर दूसरा डीकंप्रेस चल रहा है।", "cw_files_vw_find": "इस फ़ाइल में खोजें", "cw_files_vw_prev": "पिछला", diff --git a/client/src/i18n/locales/hu.ts b/client/src/i18n/locales/hu.ts index d0cc76b9..1d61b821 100644 --- a/client/src/i18n/locales/hu.ts +++ b/client/src/i18n/locales/hu.ts @@ -4772,6 +4772,7 @@ c2c_test_deep_bad: "A 4 KiB-os tesztküldés nem érkezett meg: {detail}", "cw_files_unpack_failed_generic": "a konzol válasza: „{cause}”.", "cw_files_unpack_not_container": "Ez nem olyan .ffpfsc, amelyet a konzol olvasni tud.", "cw_files_unpack_damaged": "A fájl sérült.", +"cw_files_unpack_not_image": "A benne lévő image nem exFAT image, ezért nem támogatott.", "cw_files_unpack_busy": "Már fut egy másik kicsomagolás a konzolon.", "cw_files_vw_find": "Keresés ebben a fájlban", "cw_files_vw_prev": "Előző", diff --git a/client/src/i18n/locales/id.ts b/client/src/i18n/locales/id.ts index 6954e91c..998cd05a 100644 --- a/client/src/i18n/locales/id.ts +++ b/client/src/i18n/locales/id.ts @@ -4466,6 +4466,7 @@ c2c_test_deep_bad: "Kiriman uji 4 KiB tidak tiba: {detail}", "cw_files_unpack_failed_generic": "konsol menjawab “{cause}”.", "cw_files_unpack_not_container": "Ini bukan .ffpfsc yang bisa dibaca konsol.", "cw_files_unpack_damaged": "File rusak.", +"cw_files_unpack_not_image": "Image di dalamnya bukan image exFAT, jadi tidak didukung.", "cw_files_unpack_busy": "Ekstrak lain sedang berjalan di konsol.", "cw_files_vw_find": "Cari di file ini", "cw_files_vw_prev": "Sebelumnya", diff --git a/client/src/i18n/locales/it.ts b/client/src/i18n/locales/it.ts index f4ab359e..aedf46c7 100644 --- a/client/src/i18n/locales/it.ts +++ b/client/src/i18n/locales/it.ts @@ -4407,6 +4407,7 @@ c2c_test_deep_bad: "L’invio di prova di 4 KiB non è arrivato: {detail}", "cw_files_unpack_failed_generic": "la console ha risposto «{cause}».", "cw_files_unpack_not_container": "Non è un .ffpfsc che la console può leggere.", "cw_files_unpack_damaged": "Il file è danneggiato.", +"cw_files_unpack_not_image": "L’immagine al suo interno non è un’immagine exFAT, quindi non è supportata.", "cw_files_unpack_busy": "Sulla console è già in corso un’altra decompressione.", "cw_files_vw_find": "Cerca in questo file", "cw_files_vw_prev": "Precedente", diff --git a/client/src/i18n/locales/ja.ts b/client/src/i18n/locales/ja.ts index 0c33ee08..6f972c69 100644 --- a/client/src/i18n/locales/ja.ts +++ b/client/src/i18n/locales/ja.ts @@ -4478,6 +4478,7 @@ c2c_test_deep_bad: "4 KiB のテスト送信が届きませんでした: {detail "cw_files_unpack_failed_generic": "本体の応答:「{cause}」。", "cw_files_unpack_not_container": "本体が読み取れる .ffpfsc ではありません。", "cw_files_unpack_damaged": "ファイルが破損しています。", +"cw_files_unpack_not_image": "中のイメージは exFAT イメージではないため、対応していません。", "cw_files_unpack_busy": "本体で別の展開が実行中です。", "cw_files_vw_find": "このファイル内を検索", "cw_files_vw_prev": "前へ", diff --git a/client/src/i18n/locales/ko.ts b/client/src/i18n/locales/ko.ts index 83c1b260..b70fbb9c 100644 --- a/client/src/i18n/locales/ko.ts +++ b/client/src/i18n/locales/ko.ts @@ -4476,6 +4476,7 @@ c2c_test_deep_bad: "4 KiB 테스트 전송이 도착하지 않았습니다: {det "cw_files_unpack_failed_generic": "콘솔 응답: “{cause}”.", "cw_files_unpack_not_container": "콘솔이 읽을 수 있는 .ffpfsc가 아닙니다.", "cw_files_unpack_damaged": "파일이 손상되었습니다.", +"cw_files_unpack_not_image": "안에 있는 이미지가 exFAT 이미지가 아니어서 지원되지 않습니다.", "cw_files_unpack_busy": "콘솔에서 다른 압축 풀기가 진행 중입니다.", "cw_files_vw_find": "이 파일에서 찾기", "cw_files_vw_prev": "이전", diff --git a/client/src/i18n/locales/pl.ts b/client/src/i18n/locales/pl.ts index 2308a2e9..deb8b769 100644 --- a/client/src/i18n/locales/pl.ts +++ b/client/src/i18n/locales/pl.ts @@ -4712,6 +4712,7 @@ c2c_test_deep_bad: "Testowa wysyłka 4 KiB nie dotarła: {detail}", "cw_files_unpack_failed_generic": "konsola zgłosiła „{cause}”.", "cw_files_unpack_not_container": "To nie jest plik .ffpfsc, który konsola potrafi odczytać.", "cw_files_unpack_damaged": "Plik jest uszkodzony.", +"cw_files_unpack_not_image": "Obraz w środku nie jest obrazem exFAT, więc nie jest obsługiwany.", "cw_files_unpack_busy": "Na konsoli trwa już inne rozpakowywanie.", "cw_files_vw_find": "Znajdź w tym pliku", "cw_files_vw_prev": "Poprzednie", diff --git a/client/src/i18n/locales/pt-BR.ts b/client/src/i18n/locales/pt-BR.ts index 2391cbd5..b42460a0 100644 --- a/client/src/i18n/locales/pt-BR.ts +++ b/client/src/i18n/locales/pt-BR.ts @@ -4474,6 +4474,7 @@ c2c_test_deep_bad: "O envio de teste de 4 KiB não chegou: {detail}", "cw_files_unpack_failed_generic": "o console respondeu “{cause}”.", "cw_files_unpack_not_container": "Não é um .ffpfsc que o console consiga ler.", "cw_files_unpack_damaged": "O arquivo está danificado.", +"cw_files_unpack_not_image": "A imagem dentro dele não é uma imagem exFAT, então não é compatível.", "cw_files_unpack_busy": "Outra descompactação já está em andamento no console.", "cw_files_vw_find": "Procurar neste arquivo", "cw_files_vw_prev": "Anterior", diff --git a/client/src/i18n/locales/ru.ts b/client/src/i18n/locales/ru.ts index df2b57a7..6f57eb04 100644 --- a/client/src/i18n/locales/ru.ts +++ b/client/src/i18n/locales/ru.ts @@ -4407,6 +4407,7 @@ c2c_test_deep_bad: "Тестовая отправка 4 КиБ не дошла: "cw_files_unpack_failed_generic": "консоль ответила «{cause}».", "cw_files_unpack_not_container": "Это не .ffpfsc, который консоль может прочитать.", "cw_files_unpack_damaged": "Файл повреждён.", +"cw_files_unpack_not_image": "Образ внутри не является образом exFAT, поэтому не поддерживается.", "cw_files_unpack_busy": "На консоли уже идёт другая распаковка.", "cw_files_vw_find": "Найти в этом файле", "cw_files_vw_prev": "Назад", diff --git a/client/src/i18n/locales/th.ts b/client/src/i18n/locales/th.ts index 3f3685a0..620144a3 100644 --- a/client/src/i18n/locales/th.ts +++ b/client/src/i18n/locales/th.ts @@ -4466,6 +4466,7 @@ c2c_test_deep_bad: "การส่งทดสอบ 4 KiB ไปไม่ถ "cw_files_unpack_failed_generic": "เครื่องตอบว่า “{cause}”", "cw_files_unpack_not_container": "นี่ไม่ใช่ .ffpfsc ที่เครื่องอ่านได้", "cw_files_unpack_damaged": "ไฟล์เสียหาย", +"cw_files_unpack_not_image": "อิมเมจด้านในไม่ใช่อิมเมจ exFAT จึงไม่รองรับ", "cw_files_unpack_busy": "เครื่องกำลังแตกไฟล์อื่นอยู่", "cw_files_vw_find": "ค้นหาในไฟล์นี้", "cw_files_vw_prev": "ก่อนหน้า", diff --git a/client/src/i18n/locales/tr.ts b/client/src/i18n/locales/tr.ts index ecf2b84e..afd9220c 100644 --- a/client/src/i18n/locales/tr.ts +++ b/client/src/i18n/locales/tr.ts @@ -4464,6 +4464,7 @@ c2c_test_deep_bad: "4 KiB'lik test gönderimi ulaşmadı: {detail}", "cw_files_unpack_failed_generic": "konsol “{cause}” dedi.", "cw_files_unpack_not_container": "Bu, konsolun okuyabildiği bir .ffpfsc değil.", "cw_files_unpack_damaged": "Dosya bozuk.", +"cw_files_unpack_not_image": "İçindeki imaj bir exFAT imajı değil, bu yüzden desteklenmiyor.", "cw_files_unpack_busy": "Konsolda zaten başka bir sıkıştırma açma işlemi çalışıyor.", "cw_files_vw_find": "Bu dosyada bul", "cw_files_vw_prev": "Önceki", diff --git a/client/src/i18n/locales/vi.ts b/client/src/i18n/locales/vi.ts index 60171834..81300e40 100644 --- a/client/src/i18n/locales/vi.ts +++ b/client/src/i18n/locales/vi.ts @@ -4459,6 +4459,7 @@ c2c_test_deep_bad: "Lượt gửi thử 4 KiB không tới: {detail}", "cw_files_unpack_failed_generic": "máy báo “{cause}”.", "cw_files_unpack_not_container": "Đây không phải tệp .ffpfsc mà máy đọc được.", "cw_files_unpack_damaged": "Tệp bị hỏng.", +"cw_files_unpack_not_image": "Ảnh bên trong không phải ảnh exFAT nên không được hỗ trợ.", "cw_files_unpack_busy": "Máy đang chạy một lần giải nén khác.", "cw_files_vw_find": "Tìm trong tệp này", "cw_files_vw_prev": "Trước", diff --git a/client/src/i18n/locales/zh-CN.ts b/client/src/i18n/locales/zh-CN.ts index 76f82da9..da89c953 100644 --- a/client/src/i18n/locales/zh-CN.ts +++ b/client/src/i18n/locales/zh-CN.ts @@ -4413,6 +4413,7 @@ c2c_test_deep_bad: "4 KiB 测试发送未到达:{detail}", "cw_files_unpack_failed_generic": "主机返回“{cause}”。", "cw_files_unpack_not_container": "这不是主机能读取的 .ffpfsc。", "cw_files_unpack_damaged": "文件已损坏。", +"cw_files_unpack_not_image": "其中的映像不是 exFAT 映像,因此不受支持。", "cw_files_unpack_busy": "主机上已有另一个解压在进行。", "cw_files_vw_find": "在此文件中查找", "cw_files_vw_prev": "上一个", diff --git a/client/src/i18n/locales/zh-TW.ts b/client/src/i18n/locales/zh-TW.ts index c8dede51..371ac44a 100644 --- a/client/src/i18n/locales/zh-TW.ts +++ b/client/src/i18n/locales/zh-TW.ts @@ -4407,6 +4407,7 @@ c2c_test_deep_bad: "4 KiB 測試傳送未抵達:{detail}", "cw_files_unpack_failed_generic": "主機回應「{cause}」。", "cw_files_unpack_not_container": "這不是主機能讀取的 .ffpfsc。", "cw_files_unpack_damaged": "檔案已損毀。", +"cw_files_unpack_not_image": "其中的映像不是 exFAT 映像,因此不受支援。", "cw_files_unpack_busy": "主機上已有另一個解壓縮在進行。", "cw_files_vw_find": "在此檔案中尋找", "cw_files_vw_prev": "上一個", diff --git a/payload/include/files_prefs.h b/payload/include/files_prefs.h index 59e80862..7bea65c3 100644 --- a/payload/include/files_prefs.h +++ b/payload/include/files_prefs.h @@ -43,9 +43,9 @@ enum { FILES_PREFS_E_BAD_OP = -5, }; -typedef enum { FILES_PREFS_PIN = 1, FILES_PREFS_UNPIN = 2, FILES_PREFS_VISIT = 3 } files_prefs_op_t; +typedef enum { FILES_PREFS_PIN = 1, FILES_PREFS_UNPIN = 2, FILES_PREFS_VISIT = 3, FILES_PREFS_FORGET = 4 } files_prefs_op_t; -/* "pin" / "unpin" / "visit" -> the op; 0 for anything else. */ +/* "pin" / "unpin" / "visit" / "forget" -> the op; 0 for anything else. */ int files_prefs_op_from_name(const char *name); /* Copies `in` into `out` (cap FILES_PREFS_PATH_MAX) in its stored form: '//' collapsed, a trailing @@ -77,10 +77,13 @@ int files_prefs_save(const char *file, const files_prefs_t *p); * unpin: removed when present (absent = no change). * visit: `last` = path, and path moves to the front of recent (once). When the folder before it * was its parent or another ancestor (walking down a tree), that ancestor's entry is - * replaced, so one walk leaves one entry. Cut to FILES_PREFS_RECENT_MAX. */ + * replaced, so one walk leaves one entry. Cut to FILES_PREFS_RECENT_MAX. + * forget: removed from recent (a folder that no longer exists); `last` cleared when it is that + * path. Absent = no change. */ int files_prefs_pin(files_prefs_t *p, const char *path); int files_prefs_unpin(files_prefs_t *p, const char *path); int files_prefs_visit(files_prefs_t *p, const char *path); +int files_prefs_forget(files_prefs_t *p, const char *path); /* Locked: the file's prefs as JSON into out (cap >= FILES_PREFS_FILE_MAX + 1). Length, or an error. */ int files_prefs_get(const char *file, char *out, size_t cap); diff --git a/payload/src/ffpfsc_unpack.c b/payload/src/ffpfsc_unpack.c index c639de64..bce4e25e 100644 --- a/payload/src/ffpfsc_unpack.c +++ b/payload/src/ffpfsc_unpack.c @@ -421,6 +421,8 @@ int fsj_op_ffpfsc_unpack(void *arg, ava1_op_ctx_t *c, const uint8_t *args, size_ goto done; } if (ends_with_ci(L.name, ".exfat") && !exfat_boot_ok(part)) { + /* Decoded in full, but the image inside is another file system (not a damaged block). */ + fprintf(stderr, "ffpfsc unpack: %s holds %s, which is not an exFAT image; nothing kept\n", src, L.name); ava1_op_message(c, "fs_unpack_not_exfat"); goto done; } diff --git a/payload/src/files_prefs.c b/payload/src/files_prefs.c index 4e3ed6f8..b0669b6b 100644 --- a/payload/src/files_prefs.c +++ b/payload/src/files_prefs.c @@ -22,6 +22,7 @@ int files_prefs_op_from_name(const char *name) { if (strcmp(name, "pin") == 0) return FILES_PREFS_PIN; if (strcmp(name, "unpin") == 0) return FILES_PREFS_UNPIN; if (strcmp(name, "visit") == 0) return FILES_PREFS_VISIT; + if (strcmp(name, "forget") == 0) return FILES_PREFS_FORGET; return 0; } @@ -274,6 +275,17 @@ int files_prefs_visit(files_prefs_t *p, const char *path) { return FILES_PREFS_OK; } +int files_prefs_forget(files_prefs_t *p, const char *path) { + char clean[FILES_PREFS_PATH_MAX]; + if (files_prefs_clean_path(path, clean) != FILES_PREFS_OK) return FILES_PREFS_E_BAD_PATH; + remove_at(p->recent, &p->nrecent, find(p->recent, p->nrecent, clean)); + if (p->has_last && strcmp(p->last, clean) == 0) { + p->has_last = 0; + p->last[0] = '\0'; + } + return FILES_PREFS_OK; +} + int files_prefs_get(const char *file, char *out, size_t cap) { files_prefs_t *p = malloc(sizeof *p); int len; @@ -296,7 +308,8 @@ static int visit_is_noop(const files_prefs_t *p, const char *path) { int files_prefs_update(const char *file, int op, const char *path, char *out, size_t cap) { files_prefs_t *p; int rc, len; - if (op != FILES_PREFS_PIN && op != FILES_PREFS_UNPIN && op != FILES_PREFS_VISIT) return FILES_PREFS_E_BAD_OP; + if (op != FILES_PREFS_PIN && op != FILES_PREFS_UNPIN && op != FILES_PREFS_VISIT && op != FILES_PREFS_FORGET) + return FILES_PREFS_E_BAD_OP; p = malloc(sizeof *p); if (!p) return FILES_PREFS_E_NOMEM; pthread_mutex_lock(&g_prefs_mu); @@ -309,7 +322,8 @@ int files_prefs_update(const char *file, int op, const char *path, char *out, si } rc = op == FILES_PREFS_PIN ? files_prefs_pin(p, path) : op == FILES_PREFS_UNPIN ? files_prefs_unpin(p, path) - : files_prefs_visit(p, path); + : op == FILES_PREFS_FORGET ? files_prefs_forget(p, path) + : files_prefs_visit(p, path); /* favourites never take the room a visit needs: one recent folder and the last one, at their longest */ if (rc == FILES_PREFS_OK && op == FILES_PREFS_PIN && build_json(p, NULL, 0) > FILES_PREFS_FILE_MAX - PIN_RESERVE) rc = FILES_PREFS_E_FULL; diff --git a/payload/src/hw_info.c b/payload/src/hw_info.c index 674ef2b0..45e524c6 100644 --- a/payload/src/hw_info.c +++ b/payload/src/hw_info.c @@ -514,9 +514,13 @@ static hw_temps_cache_t g_temps_cache = {0}; * helper thread was strictly worse — it crashed the payload on the * first read of every console. Inline on the mgmt worker (which the * Sony runtime set up) is the only path that reads cleanly. The - * companion desktop change makes this safe to keep simple: live - * sensors are no longer auto-polled, only read on an explicit - * "Read sensors" click, so a read can never fire unattended. + * desktop app reads live sensors only on an explicit "Read sensors" + * click. The console page (PS5Upload Files) is the one unattended + * reader: its status polls HW_TEMPS every 10 s, inline on a web + * worker thread (webui_server.c, 512 KiB stack). That worker is also + * a pthread we spawned, and it read cleanly for whole sessions on + * FW 13.60 hardware, so the fresh-thread crash above did not + * reproduce there; keep these reads inline on the request thread. * * 1-second cache so a double-click / rapid re-read coalesces into one * round of syscalls. */ diff --git a/payload/src/webui_api.c b/payload/src/webui_api.c index 0b4d1193..59b1f9a5 100644 --- a/payload/src/webui_api.c +++ b/payload/src/webui_api.c @@ -372,6 +372,17 @@ static void r_fs_stat(const char *b, size_t n, uint8_t *out, int fd) { } st = call(AVA1_METHOD_FS_STAT, (const uint8_t *)req, w.len, out, &ol); if (st != AVA1_STATUS_OK) { + /* "missing_ok":true (the page probing for a free name): a path that is not there is an + * answer, 200 {"missing":true}, not a 404 the browser logs as an error. */ + int missing_ok = 0; + char c[API_CAUSE_MAX + 1]; + size_t cl = ol > API_CAUSE_MAX ? API_CAUSE_MAX : ol; + memcpy(c, out, cl); + c[cl] = '\0'; + if (webui_json_bool(b, n, "missing_ok", &missing_ok) == 1 && missing_ok && webui_http_status_for(st, c) == 404) { + send_json(fd, 200, "{\"missing\":true}", 16); + return; + } send_status_error(fd, st, out, ol); return; } @@ -905,7 +916,7 @@ static void r_files_prefs(const char *b, size_t n, uint8_t *out, int fd) { else send_json(fd, 200, (const char *)out, (size_t)len); } -/* {"op":"pin"|"unpin"|"visit","path":".."} -> the new prefs, as /api/files/prefs answers. */ +/* {"op":"pin"|"unpin"|"visit"|"forget","path":".."} -> the new prefs, as /api/files/prefs answers. */ static void r_files_prefs_set(const char *b, size_t n, uint8_t *out, int fd) { char op[16], path[API_PATH]; int o, len; diff --git a/payload/src/webui_server.c b/payload/src/webui_server.c index b6536ca9..72dca37f 100644 --- a/payload/src/webui_server.c +++ b/payload/src/webui_server.c @@ -34,6 +34,7 @@ #include #include +#include "ava1_thread.h" #include "proc_identity.h" #include "webui_auth.h" @@ -45,7 +46,7 @@ #define WEBUI_SLICE_MS 200 #define WEBUI_MIN_SEND_MS 1000u /* a route that computed long still gets this to write */ #define WEBUI_DRAIN_MS 250u -#define WEBUI_STACK (256u * 1024u) +#define WEBUI_STACK AVA1_MGMT_STACK /* routes run mgmt handlers sized for 512 KiB */ #define WEBUI_MAX_ADDRS 8 #define WEBUI_WATCH_MS 5000u /* the accept thread's listener check (resume, IP change) */ #define WEBUI_BROKEN_MS 5000u /* a listener failing this long is rebound */ diff --git a/payload/tests/files_prefs_selftest.c b/payload/tests/files_prefs_selftest.c index b1e11485..59b5a84a 100644 --- a/payload/tests/files_prefs_selftest.c +++ b/payload/tests/files_prefs_selftest.c @@ -71,7 +71,8 @@ static void test_clean_path(void) { big[FILES_PREFS_PATH_MAX] = '\0'; CHECK(files_prefs_clean_path(big, out) == FILES_PREFS_E_BAD_PATH); CHECK(files_prefs_op_from_name("pin") == FILES_PREFS_PIN && files_prefs_op_from_name("unpin") == FILES_PREFS_UNPIN && - files_prefs_op_from_name("visit") == FILES_PREFS_VISIT && files_prefs_op_from_name("delete") == 0 && + files_prefs_op_from_name("visit") == FILES_PREFS_VISIT && files_prefs_op_from_name("forget") == FILES_PREFS_FORGET && + files_prefs_op_from_name("delete") == 0 && files_prefs_op_from_name("") == 0); } @@ -116,6 +117,12 @@ static void test_recent_order(void) { files_prefs_visit(p, "/mnt/usb0b"); /* a sibling with the same prefix is not a child */ CHECK(p->nrecent == 4 && strcmp(p->recent[1], "/mnt/usb0") == 0); CHECK(strcmp(p->last, "/mnt/usb0b") == 0); + /* forget: a gone folder leaves recent (and last, when it is that one); others keep their order */ + CHECK(files_prefs_forget(p, "/mnt/usb0/") == 0 && p->nrecent == 3 && strcmp(p->recent[0], "/mnt/usb0b") == 0 && + strcmp(p->recent[1], "/data/homebrew") == 0 && p->has_last); + CHECK(files_prefs_forget(p, "/mnt/usb0b") == 0 && p->nrecent == 2 && !p->has_last); + CHECK(files_prefs_forget(p, "/never") == 0 && p->nrecent == 2); + CHECK(files_prefs_forget(p, "rel") == FILES_PREFS_E_BAD_PATH); free(p); }