mirror of
https://github.com/knutwurst/patchdl.git
synced 2026-10-20 15:00:34 +02:00
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.
103 lines
3.4 KiB
C
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);
|
|
}
|