Skip to content

Instantly share code, notes, and snippets.

@windsurfer1122
Forked from mmozeiko/get_update_pkg.py
Last active April 9, 2021 15:59
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save windsurfer1122/37997c4e2495ce60940d1edf21d91a53 to your computer and use it in GitHub Desktop.
Save windsurfer1122/37997c4e2495ce60940d1edf21d91a53 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
# https://stackoverflow.com/questions/5574702/how-to-print-to-stderr-in-python
from __future__ import print_function
import sys
def eprint(*args, **kwargs):
print(*args, file=sys.stderr, **kwargs)
import os
import re
import csv
import sys
import hmac
import hashlib
import struct
import urllib.request
import urllib.error
import xml.etree.ElementTree as ET
import xml.dom.minidom as minidom
# http://wololo.net/talk/viewtopic.php?f=54&t=44091
KEY = bytes.fromhex("E5E278AA1EE34082A088279C83F9BBC806821C52F2AB5D2B4ABD995450355114")
def ripemd160(value):
h1 = hashlib.new('ripemd160')
h = hmac.new(KEY, (value).encode("ascii"), lambda: h1)
return h
def md5(value):
h = hmac.new(KEY, (value).encode("ascii"), hashlib.md5)
return h
def sha1(value):
h = hmac.new(KEY, (value).encode("ascii"), hashlib.sha1)
return h
def sha256(value):
h = hmac.new(KEY, (value).encode("ascii"), hashlib.sha256)
return h
def xmlurl(title):
h = sha256("np_" + title)
url = "http://gs-sec.ww.np.dl.playstation.net/pl/np/%s/%s/%s-ver.xml" % (title, h.hexdigest(), title)
return url
def title2info(title):
url = xmlurl(title)
try:
with urllib.request.urlopen(url) as f:
data = f.read()
except urllib.error.HTTPError as e:
if e.code == 404:
return None
raise e
if data:
return data.decode("utf-8")
return None
def parse_sfo(sfo):
header, version, key_table, data_table, count = struct.unpack_from("<IIIII", sfo)
assert header == 0x46535000
assert version == 0x00000101
ret = {}
for i in range(count):
key_offset, param_type, param_len, param_maxlen, data_offset = struct.unpack_from("<HHIII", sfo, 0x14 + 0x10 * i)
key_end = sfo.find(b"\0", key_table + key_offset)
key = struct.unpack_from("%ds" % (key_end - (key_table + key_offset)), sfo, key_table + key_offset)
key = key[0].decode("ascii")
if param_type == 0x0204:
value = struct.unpack_from("%ds" % (param_len - 1), sfo, data_table + data_offset)
ret[key] = value[0].decode("utf-8")
elif param_type == 0x0404:
assert param_len == 4
value = struct.unpack_from("<I", sfo, data_table + data_offset)
ret[key] = value[0]
else:
assert not "unknown type"
return ret
def get_pkg_start(pkg, size):
req = urllib.request.Request(pkg, headers={"Range": "bytes=0-%d" % size})
with urllib.request.urlopen(req) as f:
return f.read()
def get_info(patchurl):
MaxVersion = "99.99"
data = get_pkg_start(patchurl, 16*1024)
magic1 = struct.unpack_from(">I", data)[0]
magic2 = struct.unpack_from(">I", data, 192)[0]
if magic1 != 0x7f504b47 or magic2 != 0x7f657874:
eprint(patchurl)
eprint("corrupted pkg?")
return MaxVersion
meta_offset, meta_count = struct.unpack_from(">II", data, 8)
sfo_offset = None
for i in range(meta_count):
meta_type, meta_size = struct.unpack_from(">II", data, meta_offset)
if meta_type == 14:
sfo_offset, sfo_size = struct.unpack_from(">II", data, meta_offset + 8)
break
meta_offset += 8 + meta_size
if sfo_offset is None:
eprint(patchurl)
eprint("cannot find sfo in pkg")
return MaxVersion
if sfo_offset + sfo_size > len(data):
eprint(patchurl)
eprint("sfo is not in beginning of pkg [%d..%d]" % (sfo_offset, sfo_offset + sfo_size-1))
return MaxVersion
sfo = parse_sfo(data[sfo_offset:sfo_offset+sfo_size])
fw = sfo["PSP2_DISP_VER"]
name = sfo["STITLE"] if "STITLE" in sfo else sfo["TITLE"]
name = name.replace("\n"," ").replace("\r"," ").strip()
m = re.match(r"(\d+\.\d\d).*", fw)
return m.group(1), name
def get_supported(xml, supported = "03.60"):
packages = list(ET.fromstring(xml).findall("./tag/package"))
for p in reversed(packages):
patchurl = p.attrib["url"]
size = p.attrib["size"]
version = p.attrib["version"]
fw, name = get_info(patchurl)
if fw <= supported:
return patchurl, size, version.lstrip("0"), fw.lstrip("0"), name
return None
def info(title):
xml = title2info(title)
if xml is None:
eprint(title + ": None")
else:
xml = minidom.parseString(xml)
xml = xml.toprettyxml(indent=" ")
print(xml)
def patch(title):
xml = title2info(title)
if xml is None:
eprint(title + ": None")
else:
supported = get_supported(xml)
if supported is None:
eprint(title + ": None")
else:
patchurl, size, version, fw, name = supported
print("Name:", name)
print("Patch:", patchurl)
print("Version:", version)
print("FW:", fw)
print("Size:", size)
def latest(title):
xml = title2info(title)
if xml is None:
eprint(title + ": None")
else:
supported = get_supported(xml, "99.99")
if supported is None:
eprint(title + ": None")
else:
patchurl, size, version, fw, name = supported
print("Name:", name)
print("Patch:", patchurl)
print("Version:", version)
print("FW:", fw)
print("Size:", size)
def nps(title):
url = xmlurl(title)
xml = title2info(title)
if xml is None:
eprint(title + ": None")
else:
packages = list()
name = ""
for x in ET.fromstring(xml).findall("./tag/package/paramsfo/title"):
name = x.text.replace("\n"," ").replace("\r"," ").strip()
for x in ET.fromstring(xml).findall("./tag/package"):
version = x.attrib["version"]
p = None
if x.get("type") is None or x.attrib["type"] == "cumulative":
p = x
else:
for y in x.findall("./hybrid_package"):
p = y
if p is not None:
patchurl = p.attrib["url"]
size = p.attrib["size"]
sha1 = p.attrib["sha1sum"]
fw, name2 = get_info(patchurl)
if name == "":
name = name2
print("{0}\t\t{1}\t{2}\t{3}\t{4}\t\t\t{5}\t\t{6}\t{7}".format(title, name, version.lstrip("0"), fw.lstrip("0"), patchurl, size, url, sha1))
if __name__ == "__main__":
do_help = 0
if len(sys.argv) != 3:
do_help = 1
else:
if sys.argv[1] == "xml":
info(sys.argv[2])
elif sys.argv[1] == "patch":
patch(sys.argv[2])
elif sys.argv[1] == "latest":
latest(sys.argv[2])
elif sys.argv[1] == "nps":
nps(sys.argv[2])
elif sys.argv[1] == "sha256":
h = sha256(sys.argv[2])
print(h.hexdigest())
elif sys.argv[1] == "sha1":
h = sha1(sys.argv[2])
print(h.hexdigest())
elif sys.argv[1] == "md5":
h = md5(sys.argv[2])
print(h.hexdigest())
elif sys.argv[1] == "ripemd160":
h = ripemd160(sys.argv[2])
print(h.hexdigest())
else:
do_help = 1
if do_help:
eprint("Usage: %s COMMAND TITLEID" % (sys.argv[0]))
eprint()
eprint("Available commands:")
eprint(" xml TITLEID - retrieve title patch information xml file, prints to stdout")
eprint(" patch TITLEID - retrieve latest usable (on 3.60) patch for title, prints to stdout")
eprint(" latest TITLEID - retrieve latest patch (may not work on 3.60) for title, prints to stdout")
eprint(" nps TITLEID - retrieve all cumulative patches (may not work on 3.60) for title, prints to stdout as TSV lines for NPS")
eprint(" sha256 VALUE - returns PSV SHA256 hash hex digest for string")
eprint(" sha1 VALUE - returns PSV SHA1 hash hex digest for string")
eprint(" md5 VALUE - returns PSV MD5 hash hex digest for string")
eprint(" ripemd160 VALUE - returns PSV RIPEMD-160 hash hex digest for string")
exit(0)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment