Files
knutwurst--ps5-patchdl/src/patchdl_resolve.c
T
Knutwurst e72c25a56e Update PS4 games too
PS4 patches install on the PS5 through the same loopback route as PS5
updates; the console puts them in /user/patch/<id>/ next to the game.
Tested with Bloodborne (CUSA00207, 01.00 to 01.09) on FW 11.60.

- Installed version: PS4 titles have no CONTENT_VERSION in app.db; use
  APP_VER, which includes an installed patch.
- Update check: Sony lists PS4 patches at a gs-sec URL derived from the
  title id with HMAC-SHA256 (the well-known PS4 update key). A PS4
  package's system_ver is a PS4 firmware in decimal and never blocks it.
- Download: PS4 manifests are served over plain http on gs2 only (its
  https edge has no Sony certificate); allow http for that host's
  manifests, keep everything else https. Piece hashes are SHA-1 there, so
  verification picks SHA-1 or SHA-256 by length.
- After an install, check /user/patch/<id>/patch.json instead of app.json.
  PS4 installs fetch no .crc, so a minute of quiet after reading ends one.
- The UI shows a PS4 badge instead of "not supported".

Also: a list reload no longer ends an install on the card early. The
console writes the new version during the install, and the card briefly
showed Delete before the install poll put it back.
2026-10-16 13:56:41 +02:00

103 lines
3.4 KiB
C

/* SPDX-License-Identifier: GPL-3.0-or-later
* Copyright (C) 2026 Knutwurst
*
* PatchDL is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option)
* any later version. See the LICENSE file in the project root for details.
*/
#include "patchdl_resolve.h"
#include <openssl/hmac.h>
#include <stdio.h>
#include <string.h>
#define DB_PATH "/data/patchdl/db/ps5_xml.tsv"
static int
lookup_tsv(const char *title_id, char *url_out, size_t url_sz) {
FILE *fp;
char line[1024];
fp = fopen(DB_PATH, "r");
if (!fp) return -1;
while (fgets(line, sizeof(line), fp)) {
char *tab = strchr(line, '\t');
if (!tab) continue;
*tab = '\0';
if (strcmp(line, title_id)) continue;
char *url = tab + 1;
size_t len = strlen(url);
while (len > 0 && (url[len - 1] == '\n' || url[len - 1] == '\r'))
url[--len] = '\0';
if (len == 0 || len >= url_sz) {
fclose(fp);
return -1;
}
/* Defense in depth: the TSV file lives under /data/patchdl, writable
by anyone with /data access. patchdl_http_get also enforces
host_allowed, but rejecting non-https / non-Sony schemes here means
a poisoned line can't even reach the network layer. */
if (strncmp(url, "https://", 8) != 0) {
fclose(fp);
return -1;
}
memcpy(url_out, url, len + 1);
fclose(fp);
return 0;
}
fclose(fp);
return -1;
}
/* PS4 titles: Sony's update list lives at a path derived from the title id
with HMAC-SHA256 under a key that is part of the PS4 system software and
widely published (it's how every PS4 update checker finds patches). */
static const unsigned char PS4_UPDATE_KEY[32] = {
0xAD, 0x62, 0xE3, 0x7F, 0x90, 0x5E, 0x06, 0xBC, 0x19, 0x59, 0x31, 0x42,
0x28, 0x1C, 0x11, 0x2C, 0xEC, 0x0E, 0x7E, 0xC3, 0xE9, 0x7E, 0xFD, 0xCA,
0xEF, 0xCD, 0xBA, 0xAF, 0xA6, 0x37, 0x8D, 0x84,
};
static int
ps4_update_url(const char *title_id, char *url_out, size_t url_sz) {
unsigned char mac[32];
unsigned int mac_len = 0;
char msg[24], hex[65];
int n;
if (strlen(title_id) != 9) return -1;
n = snprintf(msg, sizeof msg, "np_%s", title_id);
if (!HMAC(EVP_sha256(), PS4_UPDATE_KEY, sizeof PS4_UPDATE_KEY,
(const unsigned char *)msg, (size_t)n, mac, &mac_len) ||
mac_len != sizeof mac)
return -1;
for (unsigned i = 0; i < mac_len; i++)
snprintf(hex + 2 * i, 3, "%02x", mac[i]);
n = snprintf(url_out, url_sz,
"https://gs-sec.ww.np.dl.playstation.net/plo/np/%s/%s/%s-ver.xml",
title_id, hex, title_id);
return (n > 0 && (size_t)n < url_sz) ? 0 : -1;
}
int
patchdl_resolve_url(const char *title_id, char *url_out, size_t url_sz) {
if (!title_id) return -1;
if (!strncmp(title_id, "CUSA", 4))
return ps4_update_url(title_id, url_out, url_sz);
/* Only PS5 native title IDs (PPSA/PPSE/PPSH/PPSJ/PPSC) */
if (strncmp(title_id, "PPSA", 4) &&
strncmp(title_id, "PPSE", 4) &&
strncmp(title_id, "PPSH", 4) &&
strncmp(title_id, "PPSJ", 4) &&
strncmp(title_id, "PPSC", 4))
return -1;
return lookup_tsv(title_id, url_out, url_sz);
}