Files
ps5upload/client/src/lib/installErrorDoc.ts
T
phantomptrandClaude Opus 5.5 5f93259bf5 Fixes from issues and Discord: a full drive's partial file, stopping an install early, 0x80B21104, VPN
- A single-file upload that fills the drive keeps <file>.ava-part so
  Retry resumes it, but nothing said so: the failed row now names it and
  offers Delete partial file (the next attempt starts fresh).
- Stopping a stream install before the engine had its job dropped the
  row and left the install running unseen; Stop now waits for the job
  and stops it.
- 0x80B21104 explains that the console will not take this package and
  links its FAQ entry, instead of the generic PlayGo text.
- The Connection screen's "port not open" step suggests turning a VPN
  off (reported twice on Discord).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ub1mez9eaVtTF8pMEFXvA9
2026-10-09 11:29:37 -07:00

68 lines
2.8 KiB
TypeScript

// Maps an install error (raw message and/or Sony code) to the FAQ section that
// explains it, so an error toast can link straight to the support matrix.
//
// The FAQ screen filters its sections by a search string (`/faq?q=…`), so an
// "anchor" here is a string the target section is guaranteed to contain: the
// error code itself, or the exact phrase the explainer is titled with. The FAQ
// sections that carry these strings are "Install routes: what works for what"
// and "My uploaded game won't launch" in FAQ.md; installErrorDoc.test.ts reads
// FAQ.md and fails if an anchor stops resolving.
export interface InstallErrorDoc {
/** The FAQ search string that lands on the explanation. */
query: string;
}
/** Anchor strings. Each must appear (case-insensitively) in FAQ.md. */
export const DOC_ANCHORS = {
stagedRefused: "0x80b2116f",
streamUnreachable: "PS5UPLOAD_PKG_HOST_IP",
proxy: "0x80431084",
entitlement: "missing base entitlement",
viewProduct: "View product",
ps4OnPs5: "isn't playable on PS5",
e2: "E2-80B22410",
ce: "CE-108255-1",
wontLaunch: "won't launch",
matrix: "support matrix",
declined1104: "0x80B21104",
} as const;
const RULES: Array<[RegExp, string]> = [
[/0x80b21104/i, DOC_ANCHORS.declined1104],
[/0x80b2116f|0x80b2150f|staged route|refused this package from its own storage/i, DOC_ANCHORS.stagedRefused],
[/0x80431084|proxy server|proxy setting/i, DOC_ANCHORS.proxy],
[
/0x80431064|0x80431068|0x8041013d|never reached (this computer|the engine)|cannot connect to this computer|could not reach/i,
DOC_ANCHORS.streamUnreachable,
],
[/e2-?80b22410/i, DOC_ANCHORS.e2],
[/ce-?108255-?1/i, DOC_ANCHORS.ce],
[/base entitlement|missing base/i, DOC_ANCHORS.entitlement],
[/view product/i, DOC_ANCHORS.viewProduct],
[/ps4 game.*(not|n't) playable on ps5|isn't playable on ps5/i, DOC_ANCHORS.ps4OnPs5],
];
/** The explanation for an install error, or null when there is none to link.
* `code` is Sony's numeric code when the caller has it. */
export function installErrorDoc(raw: string | null | undefined, code?: number): InstallErrorDoc | null {
const hex = code && code > 0 ? ` 0x${(code >>> 0).toString(16).padStart(8, "0")}` : "";
const text = `${raw ?? ""}${hex}`;
if (!text.trim()) return null;
for (const [re, query] of RULES) {
if (re.test(text)) return { query };
}
return null;
}
/** The in-app route for a FAQ search. */
export function faqLink(query: string): string {
return `/faq?q=${encodeURIComponent(query)}`;
}
/** The route an install-error toast links to: its explanation when it has
* one, else the install-routes matrix (the page users should read first). */
export function installErrorLink(raw: string | null | undefined, code?: number): string {
return faqLink(installErrorDoc(raw, code)?.query ?? DOC_ANCHORS.matrix);
}