Convert: Stored (uncompressed) package option as the fallback for consoles that refuse a compressed package

Compressed packages are verified to play on FW 5.10 only; stored blocks are the other form
that has played. Level::Stored writes the block layout with raw blocks.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EU1Z2mdhAxBrH1yb48pxik
This commit is contained in:
phantomptrandClaude Fable 5.1 committed 2026-10-11 13:03:18 -07:00
1 parent 28b12bf5d1
commit ab8cbcdd44
29 files changed
+84 -13

No files matched your search

+14
View File
@@ -4,6 +4,20 @@ What's new in ps5upload, written for humans.
---
## 7.0.12
- **Convert: a "Stored" package option (no compression).** Reports of packages that install but
will not start on FW 8.60 and 10.40 arrived while compressed packages have only been proven
to play on FW 5.10. Stored writes the same block layout with every block uncompressed, the
one form that has played on hardware; the package is the game's size. Pick it under
Compression if a compressed package installs but will not launch, and please say so on
GitHub, with your firmware, so the default can be set right.
- **Helper mismatch notice** now says there is nothing to do on the PS5 itself: Reload helper
sends the matching helper from the app.
- **RAR errors keep the library's reason** after the advice, so a report can be diagnosed.
---
## 7.0.11
- **Game page: Install on a package card.** "On your drives" showed a package with only Copy
+2 -1
View File
@@ -82,7 +82,8 @@ export interface FpkgBuildRequest {
language?: string;
}
export type FpkgCompression = "fast" | "balanced" | "smallest";
/** `stored` = no compression: the fallback for a console that refuses a compressed package. */
export type FpkgCompression = "fast" | "balanced" | "smallest" | "stored";
export const fpkg = {
inspect: (source: string, outputDir?: string) =>
+2
View File
@@ -2405,6 +2405,8 @@ helper_mismatch_go: "إعادة تحميل المساعد",
"fpkg.compressionFast": "سريع",
"fpkg.compressionBalanced": "متوازن",
"fpkg.compressionSmallest": "الأصغر",
"fpkg.compressionStored": "بدون ضغط",
"fpkg.compressionStoredHint": "بدون ضغط: الحزمة بحجم اللعبة. جرّب هذا إذا كانت الحزمة المضغوطة تُثبَّت لكنها لا تعمل.",
"fpkg.cancel": "إلغاء",
"fpkg.done": "كُتبت الحزمة",
"fpkg.error": "خطأ في التحويل",
+2
View File
@@ -2405,6 +2405,8 @@ helper_mismatch_go: "হেল্পার লোড করুন",
"fpkg.compressionFast": "দ্রুত",
"fpkg.compressionBalanced": "ভারসাম্যপূর্ণ",
"fpkg.compressionSmallest": "সবচেয়ে ছোট",
"fpkg.compressionStored": "সংকোচনহীন",
"fpkg.compressionStoredHint": "কোনো সংকোচন নেই: প্যাকেজ গেমের সমান আকারের। সংকুচিত প্যাকেজ ইনস্টল হলেও চালু না হলে এটি চেষ্টা করুন।",
"fpkg.cancel": "বাতিল",
"fpkg.done": "প্যাকেজ লেখা হয়েছে",
"fpkg.error": "রূপান্তর ত্রুটি",
+2
View File
@@ -2413,6 +2413,8 @@ helper_mismatch_go: "Helfer neu laden",
"fpkg.compressionFast": "Schnell",
"fpkg.compressionBalanced": "Ausgewogen",
"fpkg.compressionSmallest": "Am kleinsten",
"fpkg.compressionStored": "Unkomprimiert",
"fpkg.compressionStoredHint": "Keine Kompression: Das Paket ist so groß wie das Spiel. Probiere das, wenn ein komprimiertes Paket installiert wird, aber nicht startet.",
"fpkg.cancel": "Abbrechen",
"fpkg.done": "Paket geschrieben",
"fpkg.error": "Umwandlungsfehler",
+2
View File
@@ -933,6 +933,8 @@ queue_will_mount: "mount after upload",
"fpkg.compressionFast": "Fast",
"fpkg.compressionBalanced": "Balanced",
"fpkg.compressionSmallest": "Smallest",
"fpkg.compressionStored": "Stored",
"fpkg.compressionStoredHint": "No compression: the package is the game's size. Try this if a compressed package installs but will not start.",
"fpkg.cancel": "Cancel",
"fpkg.done": "Package written",
"fpkg.error": "Conversion error",
+2
View File
@@ -2355,6 +2355,8 @@ helper_mismatch_go: "Recargar asistente",
"fpkg.compressionFast": "Rápida",
"fpkg.compressionBalanced": "Equilibrada",
"fpkg.compressionSmallest": "Mínima",
"fpkg.compressionStored": "Sin comprimir",
"fpkg.compressionStoredHint": "Sin compresión: el paquete ocupa lo que el juego. Pruébalo si un paquete comprimido se instala pero no arranca.",
"fpkg.cancel": "Cancelar",
"fpkg.done": "Paquete escrito",
"fpkg.error": "Error de conversión",
+2
View File
@@ -865,6 +865,8 @@ queue_will_mount: "اتصال پس از بارگذاری",
"fpkg.compressionFast": "سریع",
"fpkg.compressionBalanced": "متعادل",
"fpkg.compressionSmallest": "کوچک‌ترین",
"fpkg.compressionStored": "بدون فشرده‌سازی",
"fpkg.compressionStoredHint": "بدون فشرده‌سازی: بسته هم‌اندازهٔ بازی است. اگر بستهٔ فشرده نصب می‌شود اما اجرا نمی‌شود، این را امتحان کنید.",
"fpkg.cancel": "لغو",
"fpkg.done": "بسته نوشته شد",
"fpkg.error": "خطای تبدیل",
+2
View File
@@ -2427,6 +2427,8 @@ helper_mismatch_go: "Recharger l'assistant",
"fpkg.compressionFast": "Rapide",
"fpkg.compressionBalanced": "Équilibrée",
"fpkg.compressionSmallest": "La plus compacte",
"fpkg.compressionStored": "Non compressé",
"fpkg.compressionStoredHint": "Aucune compression : le paquet fait la taille du jeu. À essayer si un paquet compressé s'installe mais ne démarre pas.",
"fpkg.cancel": "Annuler",
"fpkg.done": "Paquet écrit",
"fpkg.error": "Erreur de conversion",
+2
View File
@@ -2425,6 +2425,8 @@ helper_mismatch_go: "हेल्पर फिर लोड करें",
"fpkg.compressionFast": "तेज़",
"fpkg.compressionBalanced": "संतुलित",
"fpkg.compressionSmallest": "सबसे छोटा",
"fpkg.compressionStored": "बिना संपीड़न",
"fpkg.compressionStoredHint": "कोई संपीड़न नहीं: पैकेज गेम जितना बड़ा होता है। अगर संपीड़ित पैकेज इंस्टॉल तो होता है पर शुरू नहीं होता, तो यह आज़माएँ।",
"fpkg.cancel": "रद्द करें",
"fpkg.done": "पैकेज लिखा गया",
"fpkg.error": "बदलने में त्रुटि",
+2
View File
@@ -2899,6 +2899,8 @@ task_recheck: "Újraellenőrzés",
"fpkg.compressionFast": "Gyors",
"fpkg.compressionBalanced": "Kiegyensúlyozott",
"fpkg.compressionSmallest": "Legkisebb",
"fpkg.compressionStored": "Tömörítetlen",
"fpkg.compressionStoredHint": "Nincs tömörítés: a csomag akkora, mint a játék. Próbáld ki, ha egy tömörített csomag települ, de nem indul.",
"fpkg.lowSpace": "Kevés a szabad hely: az átalakításhoz akár {need} is kellhet a kimeneti meghajtón, ahol {free} szabad.",
"activity_ago_hours": "{hours} órája",
"activity_ago_min": "{min} perce",
+2
View File
@@ -2418,6 +2418,8 @@ helper_mismatch_go: "Muat ulang helper",
"fpkg.compressionFast": "Cepat",
"fpkg.compressionBalanced": "Seimbang",
"fpkg.compressionSmallest": "Terkecil",
"fpkg.compressionStored": "Tanpa kompresi",
"fpkg.compressionStoredHint": "Tanpa kompresi: paket sebesar gamenya. Coba ini jika paket terkompresi terpasang tetapi tidak mau mulai.",
"fpkg.cancel": "Batal",
"fpkg.done": "Paket ditulis",
"fpkg.error": "Kesalahan konversi",
+2
View File
@@ -2359,6 +2359,8 @@ helper_mismatch_go: "Ricarica helper",
"fpkg.compressionFast": "Veloce",
"fpkg.compressionBalanced": "Bilanciata",
"fpkg.compressionSmallest": "Minima",
"fpkg.compressionStored": "Non compresso",
"fpkg.compressionStoredHint": "Nessuna compressione: il pacchetto è grande quanto il gioco. Provalo se un pacchetto compresso si installa ma non parte.",
"fpkg.cancel": "Annulla",
"fpkg.done": "Pacchetto scritto",
"fpkg.error": "Errore di conversione",
+2
View File
@@ -2430,6 +2430,8 @@ helper_mismatch_go: "ヘルパーを再読み込み",
"fpkg.compressionFast": "高速",
"fpkg.compressionBalanced": "バランス",
"fpkg.compressionSmallest": "最小",
"fpkg.compressionStored": "無圧縮",
"fpkg.compressionStoredHint": "圧縮なし: パッケージはゲームと同じサイズです。圧縮したパッケージがインストールできても起動しない場合に試してください。",
"fpkg.cancel": "キャンセル",
"fpkg.done": "パッケージを書き込みました",
"fpkg.error": "変換エラー",
+2
View File
@@ -2428,6 +2428,8 @@ helper_mismatch_go: "헬퍼 다시 불러오기",
"fpkg.compressionFast": "빠름",
"fpkg.compressionBalanced": "균형",
"fpkg.compressionSmallest": "최소",
"fpkg.compressionStored": "무압축",
"fpkg.compressionStoredHint": "압축 없음: 패키지가 게임 크기 그대로입니다. 압축된 패키지가 설치는 되지만 시작되지 않을 때 시도해 보세요.",
"fpkg.cancel": "취소",
"fpkg.done": "패키지 기록됨",
"fpkg.error": "변환 오류",
+2
View File
@@ -2665,6 +2665,8 @@ helper_mismatch_go: "Załaduj pomocnika",
"fpkg.compressionFast": "Szybka",
"fpkg.compressionBalanced": "Zrównoważona",
"fpkg.compressionSmallest": "Najmniejsza",
"fpkg.compressionStored": "Bez kompresji",
"fpkg.compressionStoredHint": "Bez kompresji: pakiet ma rozmiar gry. Wypróbuj, jeśli skompresowany pakiet instaluje się, ale nie uruchamia.",
"fpkg.cancel": "Anuluj",
"fpkg.done": "Pakiet zapisany",
"fpkg.error": "Błąd konwersji",
+2
View File
@@ -2426,6 +2426,8 @@ helper_mismatch_go: "Recarregar auxiliar",
"fpkg.compressionFast": "Rápida",
"fpkg.compressionBalanced": "Equilibrada",
"fpkg.compressionSmallest": "Menor",
"fpkg.compressionStored": "Sem compressão",
"fpkg.compressionStoredHint": "Sem compressão: o pacote tem o tamanho do jogo. Tente isto se um pacote comprimido instala mas não abre.",
"fpkg.cancel": "Cancelar",
"fpkg.done": "Pacote gravado",
"fpkg.error": "Erro de conversão",
+2
View File
@@ -2359,6 +2359,8 @@ helper_mismatch_go: "Перезагрузить помощник",
"fpkg.compressionFast": "Быстрое",
"fpkg.compressionBalanced": "Сбалансированное",
"fpkg.compressionSmallest": "Максимальное",
"fpkg.compressionStored": "Без сжатия",
"fpkg.compressionStoredHint": "Без сжатия: пакет размером с игру. Попробуйте, если сжатый пакет устанавливается, но не запускается.",
"fpkg.cancel": "Отмена",
"fpkg.done": "Пакет записан",
"fpkg.error": "Ошибка преобразования",
+2
View File
@@ -2418,6 +2418,8 @@ helper_mismatch_go: "โหลดตัวช่วยใหม่",
"fpkg.compressionFast": "เร็ว",
"fpkg.compressionBalanced": "สมดุล",
"fpkg.compressionSmallest": "เล็กที่สุด",
"fpkg.compressionStored": "ไม่บีบอัด",
"fpkg.compressionStoredHint": "ไม่บีบอัด: แพ็กเกจมีขนาดเท่าเกม ลองใช้หากแพ็กเกจที่บีบอัดติดตั้งได้แต่เปิดไม่ขึ้น",
"fpkg.cancel": "ยกเลิก",
"fpkg.done": "เขียนแพ็กเกจแล้ว",
"fpkg.error": "ข้อผิดพลาดในการแปลง",
+2
View File
@@ -2416,6 +2416,8 @@ helper_mismatch_go: "Yardımcıyı yükle",
"fpkg.compressionFast": "Hızlı",
"fpkg.compressionBalanced": "Dengeli",
"fpkg.compressionSmallest": "En küçük",
"fpkg.compressionStored": "Sıkıştırmasız",
"fpkg.compressionStoredHint": "Sıkıştırma yok: paket oyunun boyutundadır. Sıkıştırılmış bir paket yüklenip başlamıyorsa bunu deneyin.",
"fpkg.cancel": "İptal",
"fpkg.done": "Paket yazıldı",
"fpkg.error": "Dönüştürme hatası",
+2
View File
@@ -2411,6 +2411,8 @@ helper_mismatch_go: "Tải lại trợ giúp",
"fpkg.compressionFast": "Nhanh",
"fpkg.compressionBalanced": "Cân bằng",
"fpkg.compressionSmallest": "Nhỏ nhất",
"fpkg.compressionStored": "Không nén",
"fpkg.compressionStoredHint": "Không nén: gói có kích thước bằng trò chơi. Hãy thử nếu gói nén cài được nhưng không khởi động.",
"fpkg.cancel": "Hủy",
"fpkg.done": "Đã ghi gói",
"fpkg.error": "Lỗi chuyển đổi",
+2
View File
@@ -2365,6 +2365,8 @@ helper_mismatch_go: "重新加载助手",
"fpkg.compressionFast": "快速",
"fpkg.compressionBalanced": "均衡",
"fpkg.compressionSmallest": "最小",
"fpkg.compressionStored": "不压缩",
"fpkg.compressionStoredHint": "不压缩:包与游戏同样大小。如果压缩包能安装但无法启动,请试试这个。",
"fpkg.cancel": "取消",
"fpkg.done": "软件包已写入",
"fpkg.error": "转换错误",
+2
View File
@@ -2359,6 +2359,8 @@ helper_mismatch_go: "重新載入小幫手",
"fpkg.compressionFast": "快速",
"fpkg.compressionBalanced": "平衡",
"fpkg.compressionSmallest": "最小",
"fpkg.compressionStored": "不壓縮",
"fpkg.compressionStoredHint": "不壓縮:套件與遊戲同樣大小。如果壓縮套件能安裝但無法啟動,請試試這個。",
"fpkg.cancel": "取消",
"fpkg.done": "套件已寫入",
"fpkg.error": "轉換錯誤",
@@ -1,6 +1,6 @@
// The compression choice as three tiles, each with what it costs for this game.
import { Minimize2, Scale, Zap, type LucideIcon } from "lucide-react";
import { Archive, Minimize2, Scale, Zap, type LucideIcon } from "lucide-react";
import type { FpkgCompression, FpkgEstimate } from "../../api/fpkg";
import { useTr } from "../../state/lang";
@@ -13,6 +13,7 @@ const TILES: { value: FpkgCompression; icon: LucideIcon; key: string; label: str
{ value: "fast", icon: Zap, key: "fpkg.compressionFast", label: "Fast" },
{ value: "balanced", icon: Scale, key: "fpkg.compressionBalanced", label: "Balanced" },
{ value: "smallest", icon: Minimize2, key: "fpkg.compressionSmallest", label: "Smallest" },
{ value: "stored", icon: Archive, key: "fpkg.compressionStored", label: "Stored" },
];
export interface CompressionTilesProps {
@@ -20,7 +21,7 @@ export interface CompressionTilesProps {
onChange: (value: FpkgCompression) => void;
/** This game's estimates; "pending" while they are worked out; null/undefined when there is
* no game yet or the estimate failed (shown as a dash, never as a spinner that never ends). */
estimates: Record<FpkgCompression, FpkgEstimate> | "pending" | null | undefined;
estimates: Partial<Record<FpkgCompression, FpkgEstimate>> | "pending" | null | undefined;
disabled?: boolean;
}
@@ -30,7 +31,7 @@ export function CompressionTiles({ value, onChange, estimates, disabled }: Compr
<div
role="radiogroup"
aria-label={tr("fpkg.compression", undefined, "Compression")}
className="grid grid-cols-1 gap-2 min-[420px]:grid-cols-3"
className="grid grid-cols-1 gap-2 min-[420px]:grid-cols-2 lg:grid-cols-4"
>
{TILES.map((t) => {
const checked = t.value === value;
@@ -59,11 +60,17 @@ export function CompressionTiles({ value, onChange, estimates, disabled }: Compr
)}
</span>
<span className="text-xs text-[var(--color-muted)]">
{e
? `~${gb(e.bytes)} · ~${prettyDuration(e.seconds * 1000)}`
: estimates === "pending"
? tr("fpkg.estimating", undefined, "Estimating…")
: "—"}
{t.value === "stored"
? tr(
"fpkg.compressionStoredHint",
undefined,
"No compression: the package is the game's size. Try this if a compressed package installs but will not start.",
)
: e
? `~${gb(e.bytes)} · ~${prettyDuration(e.seconds * 1000)}`
: estimates === "pending"
? tr("fpkg.estimating", undefined, "Estimating…")
: "—"}
</span>
</button>
);
@@ -21,7 +21,7 @@ export interface OptionsCardProps {
canBrowse: boolean;
compression: FpkgCompression;
onCompression: (c: FpkgCompression) => void;
estimates: Record<FpkgCompression, FpkgEstimate> | "pending" | null | undefined;
estimates: Partial<Record<FpkgCompression, FpkgEstimate>> | "pending" | null | undefined;
locked: boolean;
/** The package's planned (uncompressed) size, once the game is checked. */
plannedSize?: number;
@@ -32,7 +32,8 @@ describe("CompressionTiles", () => {
<CompressionTiles value="balanced" onChange={() => {}} estimates={estimates} />,
);
expect(out).toContain('role="radiogroup"');
expect((out.match(/role="radio"/g) ?? []).length).toBe(3);
expect((out.match(/role="radio"/g) ?? []).length).toBe(4);
expect(out).toContain("Stored");
expect(out).toMatch(/aria-checked="true"[^>]*>[\s\S]*?Balanced/);
expect(out).toContain("Recommended");
});
+1 -1
View File
@@ -57,7 +57,7 @@ pub struct BuildRequest {
/// layout.
pub kraken: bool,
/// How hard the Kraken encoder works: `PS5UPLOAD_FPKG_LEVEL` (`fast`, `balanced`,
/// `smallest`) overrides the default, Balanced.
/// `smallest`, or `stored` for no compression) overrides the default, Balanced.
pub level: crate::kraken::Level,
/// Narrows the package's PlayGo languages to this one (`fr-FR`, …; see
/// [`cnt_write::playgo_scenario_json`]). Absent, it declares all of them with `en-US` first.
@@ -286,6 +286,9 @@ pub enum Level {
Balanced,
/// An optimal parse with deeper match searches, priced three times.
Smallest,
/// No compression: every block stored raw in the same block layout. The fallback when a
/// console refuses to play a compressed package (compression is verified on FW 5.10 only).
Stored,
}
impl std::str::FromStr for Level {
@@ -297,6 +300,7 @@ impl std::str::FromStr for Level {
"fast" => Ok(Level::Fast),
"balanced" => Ok(Level::Balanced),
"smallest" => Ok(Level::Smallest),
"stored" | "store" | "none" => Ok(Level::Stored),
other => Err(format!("unknown compression level {other:?}")),
}
}
@@ -309,6 +313,7 @@ impl Level {
Level::Fast => 16,
Level::Balanced => 32,
Level::Smallest => 128,
Level::Stored => 0,
}
}
@@ -318,6 +323,7 @@ impl Level {
Level::Fast => 0,
Level::Balanced => 2,
Level::Smallest => 3,
Level::Stored => 0,
}
}
}
@@ -291,7 +291,7 @@ pub fn compress(
|(t, bytes)| {
// Stored (`PS5UPLOAD_FPKG_KRAKEN_STORE=1`, see `store_only`): every half raw, the same
// layout with nothing for the console's decoder to decompress.
let halves = if store_only() {
let halves = if store_only() || level == kraken::Level::Stored {
bytes
.chunks(kraken::HALF)
.map(|h| Half::Raw(h.to_vec()))