Files
knutwurst--patchdl/src/patchdl_verxml.c
T
Knutwurst 970c7d8f1d Install patches via sceAppInstUtilAppInstallPkg; fix GetInstallStatus ABI
sceAppInstUtilInstallByPackage returns 0x80B21163 from payload context
(process privilege rejection). sceAppInstUtilAppInstallPkg accepts the
same PKG with rc=0 and does install it. Switch all install paths to use
AppInstallPkg.

do_install (cross-region): require assembled PKG from the manifest
download; InstallByPackage and DP.pkg fallbacks are removed since both
are dead ends in this process context. do_install (same-region): also
switched to AppInstallPkg. do_download: removed the DP.pkg shortcut so
the manifest-assembled full PKG is downloaded as before.

sceAppInstUtilGetInstallStatus ABI: first arg is an output buffer for
the current install's content_id, not an input query. Passing our
tracking buffer there was overwriting it with zeros (disc game has no
explicit content_id). Fix: use a fresh output buffer; keep `cid` from
g_last_content_id untouched. Also add 2048-byte padding to
ai_install_status_t against firmware struct size variance.

New APIs: patchdl_install_by_uri, patchdl_install_app_pkg,
patchdl_install_debug_state. New endpoints: /api/install_uri,
/api/install_aip, /api/debug_install. delta_url propagated through
verxml → scan → websrv for future DP.pkg tracking.

Verified on device (FW 11.60, BD-JB+PPPwn): Dead Island 2 PPSA03099
updated from 01.000.001 to 01.000.011.
2026-06-24 20:04:11 +02:00

188 lines
6.2 KiB
C

#include "patchdl_verxml.h"
#include "patchdl_net.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* Convert packed fw bin to "11.60" string */
static void
fw_bin_to_str(uint32_t bin, char *out, size_t sz) {
/* Packed firmware: hex digits read as decimal (0x10200006 -> "10.20"),
same scheme as patchdl_fw. */
snprintf(out, sz, "%x.%02x",
(bin >> 24) & 0xff,
(bin >> 16) & 0xff);
}
static uint32_t
parse_hex(const char *s) {
uint32_t v = 0;
if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X'))
sscanf(s + 2, "%x", &v);
else
sscanf(s, "%u", &v);
return v;
}
/* Extract attribute value from within a <package ...> tag span.
tag_start points to '<', tag_end points past '>'. */
static int
attr_val(const char *tag_start, const char *tag_end, const char *attr,
char *out, size_t out_sz) {
char needle[64];
const char *p;
size_t len;
if ((size_t)snprintf(needle, sizeof(needle), " %s=\"", attr) >= sizeof(needle))
return -1;
p = tag_start;
while (p < tag_end) {
p = strstr(p, needle);
if (!p || p >= tag_end) return -1;
p += strlen(needle);
for (len = 0; p + len < tag_end && p[len] != '"' && len < out_sz - 1; len++)
;
/* The value must actually end on its closing quote inside the tag —
otherwise we hit tag_end or the buffer limit and would return a
silently truncated URL/title as if it were valid. */
if (p + len >= tag_end || p[len] != '"')
return -1;
memcpy(out, p, len);
out[len] = '\0';
return 0;
}
return -1;
}
/* version strings are zero-padded (e.g. "01.041.000") — strcmp orders correctly */
static int
ver_gt(const char *a, const char *b) {
return strcmp(a, b) > 0;
}
/* Extract the first PS4/PS5 title id token ([A-Z]{4}[0-9]{5}, e.g. PPSA03099)
from a string such as nptitleid, manifest_url, or delta_url. */
static void
extract_title_id(const char *s, char *out, size_t sz) {
out[0] = '\0';
if (sz < 10 || !s) return;
for (const char *p = s; p[0] && p[8]; p++) {
int ok = 1;
for (int i = 0; i < 4 && ok; i++)
if (p[i] < 'A' || p[i] > 'Z') ok = 0;
for (int i = 4; i < 9 && ok; i++)
if (p[i] < '0' || p[i] > '9') ok = 0;
if (ok) {
memcpy(out, p, 9);
out[9] = '\0';
return;
}
}
}
static void
parse_root_title_id(const char *xml, char *out, size_t sz) {
const char *p;
const char *tag_end;
char nptitleid[64] = {0};
out[0] = '\0';
if (!xml || sz < 10) return;
p = strstr(xml, "<title_patch");
if (!p) return;
tag_end = strchr(p, '>');
if (!tag_end) return;
tag_end++;
if (!attr_val(p, tag_end, "nptitleid", nptitleid, sizeof(nptitleid)))
extract_title_id(nptitleid, out, sz);
}
static void
parse_packages(const char *xml, uint32_t fw_bin, patchdl_verinfo_t *out) {
const char *p = xml;
char root_title[16] = {0};
parse_root_title_id(xml, root_title, sizeof(root_title));
while ((p = strstr(p, "<package "))) {
const char *tag_end = strchr(p, '>');
if (!tag_end) break;
tag_end++;
char ver[16] = {0};
char sver[16] = {0};
char durl[512] = {0};
char murl[512] = {0};
/* PS5 version.xml uses content_ver; PS4 uses version. */
if (attr_val(p, tag_end, "content_ver", ver, sizeof(ver)))
attr_val(p, tag_end, "version", ver, sizeof(ver));
attr_val(p, tag_end, "system_ver", sver, sizeof(sver));
attr_val(p, tag_end, "delta_url", durl, sizeof(durl));
attr_val(p, tag_end, "manifest_url", murl, sizeof(murl));
if (ver[0] && sver[0]) {
uint32_t pkg_sver = parse_hex(sver);
/* Track latest overall */
if (!out->latest_version[0] || ver_gt(ver, out->latest_version)) {
strncpy(out->latest_version, ver, sizeof(out->latest_version) - 1);
fw_bin_to_str(pkg_sver, out->latest_required_fw,
sizeof(out->latest_required_fw));
}
/* Track latest compatible + its installable patch source. PS5
updates expose a small delta_url (DP.pkg) plus a manifest_url
containing the actual split package pieces. Feeding the DP
bootstrap directly can make the system download the full patch
under the storage/master title id, so prefer the target-title
manifest whenever present. */
if (pkg_sver <= fw_bin) {
if (!out->compatible_version[0] ||
ver_gt(ver, out->compatible_version)) {
strncpy(out->compatible_version, ver,
sizeof(out->compatible_version) - 1);
strncpy(out->compatible_url, murl[0] ? murl : durl,
sizeof(out->compatible_url) - 1);
if (durl[0])
strncpy(out->delta_url, durl, sizeof(out->delta_url) - 1);
extract_title_id(durl, out->compatible_storage_title,
sizeof(out->compatible_storage_title));
if (root_title[0]) {
strncpy(out->compatible_title, root_title,
sizeof(out->compatible_title) - 1);
} else {
extract_title_id(murl, out->compatible_title,
sizeof(out->compatible_title));
if (!out->compatible_title[0])
extract_title_id(durl, out->compatible_title,
sizeof(out->compatible_title));
}
}
}
}
p = tag_end;
}
}
int
patchdl_verxml_query(const char *url, uint32_t fw_bin, patchdl_verinfo_t *out) {
patchdl_buf_t buf;
if (!url || !out) return -1;
memset(out, 0, sizeof(*out));
if (patchdl_http_get(url, &buf)) return -1;
if (!buf.data || !buf.size) { free(buf.data); return -1; }
parse_packages(buf.data, fw_bin, out);
free(buf.data);
return (out->latest_version[0]) ? 0 : -1;
}