Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
#!/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
You can’t perform that action at this time.