mirror of
https://gitlab.com/Nanolx/qwad.git
synced 2024-11-22 10:39:19 +01:00
888 lines
24 KiB
Python
888 lines
24 KiB
Python
|
from binascii import *
|
||
|
import struct
|
||
|
|
||
|
from common import *
|
||
|
from title import *
|
||
|
|
||
|
class locDat:
|
||
|
class locHeader(Struct):
|
||
|
def __format__(self):
|
||
|
self.magic = Struct.string(4)
|
||
|
self.md5 = Struct.string(16)
|
||
|
|
||
|
def __init__(self, f):
|
||
|
self.sdKey = '\xab\x01\xb9\xd8\xe1\x62\x2b\x08\xaf\xba\xd8\x4d\xbf\xc2\xa5\x5d'
|
||
|
self.sdIv = '\x21\x67\x12\xe6\xaa\x1f\x68\x9f\x95\xc5\xa2\x23\x24\xdc\x6a\x98'
|
||
|
|
||
|
self.titles = []
|
||
|
self.usedBlocks = 0
|
||
|
self.freeBlocks = 0
|
||
|
|
||
|
try:
|
||
|
self.fp = open(f, 'r+')
|
||
|
except:
|
||
|
raise Exception('File not found')
|
||
|
|
||
|
plainBuffer = Crypto().decryptData(self.sdKey, self.sdIv, self.fp.read(), False)
|
||
|
|
||
|
self.hdr = self.locHeader().unpack(plainBuffer[:0x14])
|
||
|
|
||
|
for x in range(240):
|
||
|
self.titles.append(plainBuffer[0x14 + x * 4:0x14 + (x + 1) * 4])
|
||
|
if self.titles[x] == '\x00\x00\x00\x00':
|
||
|
self.freeBlocks += 1
|
||
|
|
||
|
self.usedBlocks = 240 - self.freeBlocks
|
||
|
|
||
|
def __str__(self):
|
||
|
out = ''
|
||
|
out += 'Used %i blocks out of 240\n\n' % self.usedBlocks
|
||
|
for x in range(240):
|
||
|
if self.titles[x] == '\x00\x00\x00\x00':
|
||
|
out += 'Block %i on page %i is empty\n' % (x, x / 12)
|
||
|
else:
|
||
|
out += 'Block %i on page %i hold title %s\n' % (x, x / 12, self.titles[x])
|
||
|
|
||
|
return out
|
||
|
|
||
|
def getFreeBlocks(self):
|
||
|
return self.freeBlocks
|
||
|
|
||
|
def getUsedBlocks(self):
|
||
|
return self.usedBlocks
|
||
|
|
||
|
def isBlockFree(self, x, y, page):
|
||
|
if self.titles[((x + (y * 4) + (page * 12)))] == '\x00\x00\x00\x00':
|
||
|
return 1
|
||
|
|
||
|
return 0
|
||
|
|
||
|
def isTitleInList(self, title):
|
||
|
try:
|
||
|
return self.titles.index(title.upper())
|
||
|
except:
|
||
|
return -1
|
||
|
|
||
|
def getPageTitles(self, page):
|
||
|
if page > 19:
|
||
|
raise Exception('Out of bounds')
|
||
|
|
||
|
return self.titles[12 * page:12 * (page + 1)]
|
||
|
|
||
|
def getTitle(self, x, y, page):
|
||
|
if x > 3 or y > 2 or page > 19:
|
||
|
raise Exception('Out of bounds')
|
||
|
|
||
|
return self.titles[((x + (y * 4) + (page * 12)))]
|
||
|
|
||
|
def setTitle(self, x, y, page, element):
|
||
|
if x > 3 or y > 2 or page > 19 or len(element) > 4:
|
||
|
raise Exception('Out of bounds')
|
||
|
|
||
|
self.titles[((x + (y * 4) + (page * 12)))] = element.upper()
|
||
|
|
||
|
titles = ''
|
||
|
|
||
|
titles += self.hdr.magic
|
||
|
titles += self.hdr.md5
|
||
|
|
||
|
for x in range(240):
|
||
|
titles += self.titles[x]
|
||
|
|
||
|
titles += '\x00' * 12
|
||
|
|
||
|
titles = titles[:0x4] + Crypto().createMD5Hash(titles) + titles[0x14:]
|
||
|
|
||
|
self.fp.seek(0)
|
||
|
self.fp.write(Crypto().encryptData(self.sdKey, self.sdIv, titles))
|
||
|
|
||
|
def delTitle(self, x, y, page):
|
||
|
self.setTitle(x, y, page, '\x00\x00\x00\x00')
|
||
|
|
||
|
class CONF:
|
||
|
"""This class deals with setting.txt which holds some important information like region and serial number """
|
||
|
def __init__(self, f):
|
||
|
self.conf = ''
|
||
|
self.keys = {}
|
||
|
self.lastKeyOffset = 0
|
||
|
self.totalKeys = 0
|
||
|
|
||
|
try:
|
||
|
self.fp = open(f, 'r+b')
|
||
|
except:
|
||
|
self.fp = open(f, 'w+b')
|
||
|
return
|
||
|
|
||
|
self.conf = self.fp.read(0x100)
|
||
|
self.conf = self.XORConf(self.conf)
|
||
|
self.fp.seek(0)
|
||
|
|
||
|
keys = self.conf.split('\r\n')
|
||
|
|
||
|
self.lastKeyOffset = self.conf.rfind('\r\n') + 2
|
||
|
self.totalKeys = len(keys) - 1
|
||
|
|
||
|
for x in range(self.totalKeys):
|
||
|
keyName = keys[x].split('=')[0]
|
||
|
keyVal = keys[x].split('=')[1]
|
||
|
|
||
|
self.keys[keyName] = keyVal
|
||
|
|
||
|
def getKeysCount(self):
|
||
|
"""Gets how many keys exist."""
|
||
|
return self.totalKeys
|
||
|
|
||
|
def getKeysName(self):
|
||
|
"""Returns the list of key names."""
|
||
|
return self.keys.keys()
|
||
|
|
||
|
def getKeyValue(self, key):
|
||
|
"""Returns the value of the key ``key''."""
|
||
|
try:
|
||
|
return self.keys[key.upper()]
|
||
|
except KeyError:
|
||
|
return 'Key not found'
|
||
|
|
||
|
def setKeyValue(self, key, value):
|
||
|
"""Sets the value of key ``key'' to ``value''."""
|
||
|
if(self.keyExist(key)):
|
||
|
self.keys[key.upper()] = value.upper()
|
||
|
|
||
|
self.conf = ''
|
||
|
|
||
|
for key in self.keys:
|
||
|
self.conf += key
|
||
|
self.conf += '='
|
||
|
self.conf += self.keys[key]
|
||
|
self.conf += '\r\n'
|
||
|
|
||
|
self.fp.seek(0)
|
||
|
self.fp.write(self.XORConf(self.conf))
|
||
|
self.fp.write('\x00' * (0x100 - len(self.conf)))
|
||
|
|
||
|
self.lastKeyOffset = self.conf.rfind('\r\n') + 2
|
||
|
|
||
|
def keyExist(self, key):
|
||
|
return self.keys.has_key(key.upper())
|
||
|
|
||
|
def addKey(self, key, value):
|
||
|
"""Adds key ``key'' with value ``value'' to the list."""
|
||
|
if self.lastKeyOffset + len(key) + 1 + len(value) + 2 > 0x100:
|
||
|
return -1
|
||
|
if not self.keyExist(key):
|
||
|
return -2
|
||
|
|
||
|
self.keys[key.upper()] = value.upper()
|
||
|
self.totalKeys +=1
|
||
|
|
||
|
self.conf = self.conf[:self.lastKeyOffset] + key.upper() + '=' + value.upper() + '\r\n'
|
||
|
|
||
|
self.lastKeyOffset += len(key) + 1 + len(value) + 2
|
||
|
|
||
|
self.fp.seek(0)
|
||
|
self.fp.write(self.XORConf(self.conf))
|
||
|
|
||
|
def XORConf(self, conf):
|
||
|
"""Encrypts/decrypts the setting.txt file."""
|
||
|
XORKey = 0x73B5DBFA
|
||
|
out = ''
|
||
|
for x in range(len(conf)):
|
||
|
out += chr(ord(conf[x]) ^ XORKey & 0xFF)
|
||
|
XORKey = (XORKey << 1) | (XORKey >> 31)
|
||
|
|
||
|
return out
|
||
|
|
||
|
def deleteKey(self, key):
|
||
|
"""Deletes the key ``key''."""
|
||
|
try:
|
||
|
del self.keys[key.upper()]
|
||
|
self.totalKeys -=1
|
||
|
|
||
|
self.conf = ''
|
||
|
|
||
|
for key in self.keys:
|
||
|
self.conf += key
|
||
|
self.conf += '='
|
||
|
self.conf += self.keys[key]
|
||
|
self.conf += '\r\n'
|
||
|
|
||
|
self.fp.seek(0)
|
||
|
self.fp.write(self.XORConf(self.conf))
|
||
|
self.fp.write('\x00' * (0x100 - len(self.conf)))
|
||
|
|
||
|
self.lastKeyOffset = self.conf.rfind('\r\n') + 2
|
||
|
except KeyError:
|
||
|
return 'Key not found'
|
||
|
|
||
|
# This function is fucking dangerous. It deletes all keys with that value. Really not a good idea.
|
||
|
def deleteKeyByValue(self, value):
|
||
|
"""Deletes all keys with value ``value''. WATCH OUT, YOU MIGHT ACCIDENTALLY DELETE WRONG KEYS."""
|
||
|
try:
|
||
|
for key in self.keys.keys():
|
||
|
if self.keys.get(key) == value:
|
||
|
del self.keys[key]
|
||
|
self.totalKeys -=1
|
||
|
|
||
|
|
||
|
self.conf = ''
|
||
|
|
||
|
for key in self.keys:
|
||
|
self.conf += key
|
||
|
self.conf += '='
|
||
|
self.conf += self.keys[key]
|
||
|
self.conf += '\r\n'
|
||
|
|
||
|
self.fp.seek(0)
|
||
|
self.fp.write(self.XORConf(self.conf))
|
||
|
self.fp.write('\x00' * (0x100 - len(self.conf)))
|
||
|
|
||
|
self.lastKeyOffset = self.conf.rfind('\r\n') + 2
|
||
|
except KeyError:
|
||
|
return 'Key not found'
|
||
|
|
||
|
def getRegion(self):
|
||
|
"""gets the Region key. (Shortcut for getKeyValue("GAME"))"""
|
||
|
return self.getKeyValue("GAME")
|
||
|
|
||
|
def getArea(self):
|
||
|
"""gets the Area key. (Shortcut for getKeyValue("AREA"))"""
|
||
|
return self.getKeyValue("AREA")
|
||
|
|
||
|
def getVideoMode(self):
|
||
|
"""gets the Video Mode key. (Shortcut for getKeyValue("VIDEO"))"""
|
||
|
return self.getKeyValue("VIDEO")
|
||
|
|
||
|
def getSerialCode(self):
|
||
|
"""gets the Serial Code key. (Shortcut for getKeyValue("CODE"))"""
|
||
|
return self.getKeyValue("CODE")
|
||
|
|
||
|
def getDVDModel(self): # Might not be model =/
|
||
|
"""gets the DVD Model (?) key. (Shortcut for getKeyValue("DVD"))"""
|
||
|
return self.getKeyValue("DVD")
|
||
|
|
||
|
def getHardwareModel(self):
|
||
|
"""gets the Hardware Model key. (Shortcut for getKeyValue("MODEL"))"""
|
||
|
return self.getKeyValue("MODEL")
|
||
|
|
||
|
def getSerialNumber(self):
|
||
|
"""gets the Serial Number key. (Shortcut for getKeyValue("SERNO"))"""
|
||
|
return self.getKeyValue("SERNO")
|
||
|
|
||
|
|
||
|
def setRegion(self, value):
|
||
|
"""sets the Region key. (Shortcut for setKeyValue("GAME", value))"""
|
||
|
return self.setKeyValue("GAME", value)
|
||
|
|
||
|
def setArea(self, value):
|
||
|
"""sets the Area key. (Shortcut for setKeyValue("AREA", value))"""
|
||
|
return self.setKeyValue("AREA", value)
|
||
|
|
||
|
def setVideoMode(self, value):
|
||
|
"""sets the Video Mode key. (Shortcut for setKeyValue("VIDEO", value))"""
|
||
|
return self.setKeyValue("VIDEO", value)
|
||
|
|
||
|
def setSerialCode(self, value):
|
||
|
"""sets the Serial Code key. (Shortcut for setKeyValue("CODE", value))"""
|
||
|
return self.setKeyValue("CODE", value)
|
||
|
|
||
|
def setDVDModel(self, value): # Might not be model =/
|
||
|
"""sets the DVD Model (?) key. (Shortcut for setKeyValue("DVD", value))"""
|
||
|
return self.setKeyValue("DVD", value)
|
||
|
|
||
|
def setHardwareModel(self, value):
|
||
|
"""sets the Hardware Model key. (Shortcut for setKeyValue("MODEL", value))"""
|
||
|
return self.setKeyValue("MODEL", value)
|
||
|
|
||
|
def setSerialNumber(self, value):
|
||
|
"""sets the Serial Number key. (Shortcut for setKeyValue("SERNO", value))"""
|
||
|
return self.setKeyValue("SERNO", value)
|
||
|
|
||
|
class netConfig:
|
||
|
"""This class performs network configuration. The file is located on the NAND at /shared2/sys/net/02/config.dat."""
|
||
|
class configEntry(Struct):
|
||
|
__endian__ = Struct.BE
|
||
|
def __format__(self):
|
||
|
self.selected = Struct.uint8
|
||
|
self.padding_1 = Struct.string(1987)
|
||
|
self.ssid = Struct.string(32)
|
||
|
self.padding_2 = Struct.uint8
|
||
|
self.ssid_len = Struct.uint8
|
||
|
self.padding_3 = Struct.string(2)
|
||
|
self.padding_4 = Struct.uint8
|
||
|
self.encryption = Struct.uint8 # OPEN: 0x00, WEP: 0x01, WPA-PSK (TKIP): 0x04, WPA2-PSK (AES): 0x05, WPA-PSK (AES): 0x06
|
||
|
self.padding_5 = Struct.string(2)
|
||
|
self.padding_6 = Struct.uint8
|
||
|
self.key_len = Struct.uint8
|
||
|
self.padding_7 = Struct.string(2)
|
||
|
self.key = Struct.string(64)
|
||
|
self.padding_3 = Struct.string(236)
|
||
|
|
||
|
def __init__(self, conf):
|
||
|
self.f = conf
|
||
|
if(not os.path.isfile(self.f)):
|
||
|
fp = open(self.f, "wb")
|
||
|
fp.write("\x00\x00\x00\x00\x01\x07\x00\x00")
|
||
|
fp.write("\x00" * 0x91C * 3)
|
||
|
fp.close()
|
||
|
fp = open(self.f, "rb")
|
||
|
head = fp.read(8)
|
||
|
if(head != "\x00\x00\x00\x00\x01\x07\x00\x00"):
|
||
|
print("Config file is invalid!\n")
|
||
|
|
||
|
def getNotBlank(self, config):
|
||
|
fp = open(self.f, "rb")
|
||
|
fp.seek(8 + (0x91C * config))
|
||
|
sel = struct.unpack(">B", fp.read(1))[0]
|
||
|
fp.close()
|
||
|
if(sel & 0x20):
|
||
|
return 1
|
||
|
return 0
|
||
|
|
||
|
|
||
|
def getIPType(self, config):
|
||
|
if(not self.getNotBlank(config)):
|
||
|
return None
|
||
|
fp = open(self.f, "rb")
|
||
|
fp.seek(8 + (0x91C * config))
|
||
|
sel = struct.unpack(">B", fp.read(1))[0]
|
||
|
if(sel & 0x04):
|
||
|
return 0
|
||
|
else:
|
||
|
return 1
|
||
|
fp.close()
|
||
|
return sel
|
||
|
|
||
|
def getWireType(self, config):
|
||
|
if(not self.getNotBlank(config)):
|
||
|
return None
|
||
|
fp = open(self.f, "rb")
|
||
|
fp.seek(8 + (0x91C * config))
|
||
|
sel = struct.unpack(">B", fp.read(1))[0]
|
||
|
if(sel & 0x02):
|
||
|
return 0
|
||
|
else:
|
||
|
return 1
|
||
|
fp.close()
|
||
|
|
||
|
def getSSID(self, config):
|
||
|
if(not self.getNotBlank(config)):
|
||
|
return None
|
||
|
fp = open(self.f, "rb")
|
||
|
fp.seek(8 + (0x91C * config) + 2021)
|
||
|
len = struct.unpack(">B", fp.read(1))[0]
|
||
|
fp.seek(8 + (0x91C * config) + 1988)
|
||
|
ssid = fp.read(len)
|
||
|
fp.close()
|
||
|
return ssid
|
||
|
|
||
|
def getEncryptionType(self, config):
|
||
|
if(not self.getNotBlank(config)):
|
||
|
return None
|
||
|
fp = open(self.f, "rb")
|
||
|
fp.seek(8 + (0x91C * config) + 2025)
|
||
|
crypt = struct.unpack(">B", fp.read(1))[0]
|
||
|
type = ""
|
||
|
if(crypt == 0):
|
||
|
type = "OPEN"
|
||
|
elif(crypt == 1):
|
||
|
type = "WEP"
|
||
|
elif(crypt == 4):
|
||
|
type = "WPA (TKIP)"
|
||
|
elif(crypt == 5):
|
||
|
type = "WPA2"
|
||
|
elif(crypt == 6):
|
||
|
type = "WPA (AES)"
|
||
|
else:
|
||
|
print("Invalid crypto type %02X. Valid types are: ``OPEN'', ``WEP'', ``WPA (TKIP)'', ``WPA2'', or ``WPA (AES)''\n" % crypt)
|
||
|
fp.close()
|
||
|
return None
|
||
|
fp.close()
|
||
|
return type
|
||
|
|
||
|
def getEncryptionKey(self, config):
|
||
|
if(not self.getNotBlank(config)):
|
||
|
return None
|
||
|
fp = open(self.f, "rb")
|
||
|
fp.seek(8 + (0x91C * config) + 2025)
|
||
|
crypt = struct.unpack(">B", fp.read(1))[0]
|
||
|
type = ""
|
||
|
if(crypt == 0):
|
||
|
type = "OPEN"
|
||
|
elif(crypt == 1):
|
||
|
type = "WEP"
|
||
|
elif(crypt == 4):
|
||
|
type = "WPA (TKIP)"
|
||
|
elif(crypt == 5):
|
||
|
type = "WPA2"
|
||
|
elif(crypt == 6):
|
||
|
type = "WPA (AES)"
|
||
|
else:
|
||
|
print("Invalid crypto type %02X. Valid types are: ``OPEN'', ``WEP'', ``WPA (TKIP)'', ``WPA2'', or ``WPA (AES)''\n" % crypt)
|
||
|
fp.close()
|
||
|
return None
|
||
|
if(crypt != "\x00"):
|
||
|
fp.seek(8 + (0x91C * config) + 2029)
|
||
|
keylen = struct.unpack(">B", fp.read(1))[0]
|
||
|
fp.seek(8 + (0x91C * config) + 2032)
|
||
|
key = fp.read(keylen)
|
||
|
fp.close()
|
||
|
return key
|
||
|
fp.close()
|
||
|
return None
|
||
|
|
||
|
def clearConfig(self, config):
|
||
|
fp = open(self.f, "rb+")
|
||
|
fp.seek(8 + (0x91C * config))
|
||
|
sel = struct.unpack(">B", fp.read(1))[0]
|
||
|
sel &= 0xDF
|
||
|
fp.seek(8 + (0x91C * config))
|
||
|
fp.write(struct.pack(">B", sel))
|
||
|
fp.close()
|
||
|
|
||
|
def setNotBlank(self, config):
|
||
|
fp = open(self.f, "rb+")
|
||
|
fp.seek(8 + (0x91C * config))
|
||
|
sel = struct.unpack(">B", fp.read(1))[0]
|
||
|
sel |= 0x20
|
||
|
fp.seek(8 + (0x91C * config))
|
||
|
fp.write(struct.pack(">B", sel))
|
||
|
fp.close()
|
||
|
|
||
|
def setIPType(self, config, static):
|
||
|
fp = open(self.f, "rb+")
|
||
|
fp.seek(8 + (0x91C * config))
|
||
|
sel = struct.unpack(">B", fp.read(1))[0]
|
||
|
if(not static):
|
||
|
sel |= 0x04
|
||
|
else:
|
||
|
sel &= 0xFB
|
||
|
fp.seek(8 + (0x91C * config))
|
||
|
fp.write(struct.pack(">B", sel))
|
||
|
fp.close()
|
||
|
self.setNotBlank(config)
|
||
|
|
||
|
def setWireType(self, config, wired):
|
||
|
fp = open(self.f, "rb+")
|
||
|
fp.seek(8 + (0x91C * config))
|
||
|
sel = struct.unpack(">B", fp.read(1))[0]
|
||
|
if(not wired):
|
||
|
sel |= 0x02
|
||
|
else:
|
||
|
sel &= 0xFD
|
||
|
fp.seek(8 + (0x91C * config))
|
||
|
fp.write(struct.pack(">B", sel))
|
||
|
fp.close()
|
||
|
self.setNotBlank(config)
|
||
|
|
||
|
def setSSID(self, config, ssid):
|
||
|
fp = open(self.f, "rb+")
|
||
|
fp.seek(8 + (0x91C * config) + 1988)
|
||
|
fp.write(ssid)
|
||
|
fp.seek(8 + (0x91C * config) + 2021)
|
||
|
fp.write(a2b_hex("%02X" % len(ssid)))
|
||
|
fp.close()
|
||
|
self.setNotBlank(config)
|
||
|
|
||
|
def setEncryption(self, config, crypt, key):
|
||
|
fp = open(self.f, "rb+")
|
||
|
fp.seek(8 + (0x91C * config) + 2025)
|
||
|
if(crypt == "OPEN"):
|
||
|
fp.write("\x00")
|
||
|
elif(crypt == "WEP"):
|
||
|
fp.write("\x01")
|
||
|
elif(crypt == "WPA (TKIP)"):
|
||
|
fp.write("\x04")
|
||
|
elif(crypt == "WPA2"):
|
||
|
fp.write("\x05")
|
||
|
elif(crypt == "WPA (AES)"):
|
||
|
fp.write("\x06")
|
||
|
else:
|
||
|
print("Invalid crypto type. Valid types are: ``OPEN'', ``WEP'', ``WPA (TKIP)'', ``WPA2'', or ``WPA (AES)''\n")
|
||
|
fp.close()
|
||
|
return
|
||
|
if(crypt != "OPEN"):
|
||
|
fp.seek(8 + (0x91C * config) + 2029)
|
||
|
fp.write(a2b_hex("%02X" % len(key)))
|
||
|
fp.seek(8 + (0x91C * config) + 2032)
|
||
|
fp.write(key)
|
||
|
fp.close()
|
||
|
self.setNotBlank(config)
|
||
|
|
||
|
def selectConfig(self, config):
|
||
|
fp = open(self.f, "rb+")
|
||
|
fp.seek(8 + (0x91C * 0))
|
||
|
sel = struct.unpack(">B", fp.read(1))[0]
|
||
|
if(config == 0):
|
||
|
sel |= 0x80
|
||
|
else:
|
||
|
sel &= 0x7F
|
||
|
fp.seek(8 + (0x91C * 0))
|
||
|
fp.write(struct.pack(">B", sel))
|
||
|
fp.seek(8 + (0x91C * 1))
|
||
|
sel = struct.unpack(">B", fp.read(1))[0]
|
||
|
if(config == 1):
|
||
|
sel |= 0x80
|
||
|
else:
|
||
|
sel &= 0x7F
|
||
|
fp.seek(8 + (0x91C * 1))
|
||
|
fp.write(struct.pack(">B", sel))
|
||
|
fp.seek(8 + (0x91C * 2))
|
||
|
sel = struct.unpack(">B", fp.read(1))[0]
|
||
|
if(config == 2):
|
||
|
sel |= 0x80
|
||
|
else:
|
||
|
sel &= 0x7F
|
||
|
fp.seek(8 + (0x91C * 2))
|
||
|
fp.write(struct.pack(">B", sel))
|
||
|
self.setNotBlank(config)
|
||
|
fp.close()
|
||
|
|
||
|
|
||
|
class ContentMap:
|
||
|
"""This class performs all content.map related actions. Has functions to add contents, and find contents by hash.
|
||
|
The ``map'' parameter is the location of the content.map file."""
|
||
|
def __init__(self, map):
|
||
|
self.f = map
|
||
|
if(not os.path.isfile(map)):
|
||
|
open(map, "wb").close()
|
||
|
def contentByHash(self, hash):
|
||
|
"""When passed a sha1 hash (string of length 20), this will return the filename of the shared content (/shared1/%08x.app, no NAND prefix) specified by the hash in content.map. Note that if the content is not found, it will return False - not an empty string."""
|
||
|
cmfp = open(self.f, "rb")
|
||
|
cmdict = {}
|
||
|
num = len(cmfp.read()) / 28
|
||
|
cmfp.seek(0)
|
||
|
for z in range(num):
|
||
|
name = cmfp.read(8)
|
||
|
hash = cmfp.read(20)
|
||
|
cmdict[name] = hash
|
||
|
for key, value in cmdict.iteritems():
|
||
|
if(value == hash):
|
||
|
return "/shared1/%s.app" % key
|
||
|
return False #not found
|
||
|
|
||
|
def addContentToMap(self, contentid, hash):
|
||
|
"""Adds a content to the content.map file for the contentid and hash.
|
||
|
Returns the content id."""
|
||
|
cmfp = open(self.f, "rb")
|
||
|
cmdict = {}
|
||
|
num = len(cmfp.read()) / 28
|
||
|
cmfp.seek(0)
|
||
|
for z in range(num):
|
||
|
name = cmfp.read(8)
|
||
|
hash = cmfp.read(20)
|
||
|
cmdict[name] = hash
|
||
|
cmdict["%08x" % contentid] = hash
|
||
|
cmfp.close()
|
||
|
cmfp = open(self.f, "wb")
|
||
|
for key, value in cmdict.iteritems():
|
||
|
cmfp.write(key)
|
||
|
cmfp.write(value)
|
||
|
cmfp.close()
|
||
|
return contentid
|
||
|
|
||
|
def addHashToMap(self, hash):
|
||
|
"""Adds a content to the content.map file for the hash (uses next unavailable content id)
|
||
|
Returns the content id."""
|
||
|
cmfp = open(self.f, "rb")
|
||
|
cmdict = {}
|
||
|
cnt = 0
|
||
|
num = len(cmfp.read()) / 28
|
||
|
cmfp.seek(0)
|
||
|
for z in range(num):
|
||
|
name = cmfp.read(8)
|
||
|
hasho = cmfp.read(20)
|
||
|
cmdict[name] = hasho
|
||
|
cnt += 1
|
||
|
cmdict["%08x" % cnt] = hash
|
||
|
cmfp.close()
|
||
|
cmfp = open(self.f, "wb")
|
||
|
for key, value in cmdict.iteritems():
|
||
|
cmfp.write(key)
|
||
|
cmfp.write(value)
|
||
|
cmfp.close()
|
||
|
return cnt
|
||
|
|
||
|
def contentCount(self):
|
||
|
cmfp = open(self.f, "rb")
|
||
|
cmdict = {}
|
||
|
cnt = 0
|
||
|
num = len(cmfp.read()) / 28
|
||
|
cmfp.seek(0)
|
||
|
for z in range(num):
|
||
|
name = cmfp.read(8)
|
||
|
hash = cmfp.read(20)
|
||
|
cmdict[name] = hash
|
||
|
cnt += 1
|
||
|
cmfp.close()
|
||
|
return cnt
|
||
|
|
||
|
def contentHashes(self, count):
|
||
|
cmfp = open(self.f, "rb")
|
||
|
num = len(cmfp.read()) / 28
|
||
|
if(num > count):
|
||
|
num = count
|
||
|
cmfp.seek(0)
|
||
|
hashout = ""
|
||
|
for z in range(num):
|
||
|
name = cmfp.read(8)
|
||
|
hashout += cmfp.read(20)
|
||
|
cmfp.close()
|
||
|
return hashout
|
||
|
|
||
|
class uidsys:
|
||
|
"""This class performs all uid.sys related actions. It includes functions to add titles and find titles from the uid.sys file.
|
||
|
The ``uid'' parameter is the location of the uid.sys file."""
|
||
|
class UIDSYSStruct(Struct):
|
||
|
__endian__ = Struct.BE
|
||
|
def __format__(self):
|
||
|
self.titleid = Struct.uint64
|
||
|
self.padding = Struct.uint16
|
||
|
self.uid = Struct.uint16
|
||
|
|
||
|
def __init__(self, uid):
|
||
|
self.f = uid
|
||
|
if(not os.path.isfile(uid)):
|
||
|
uidfp = open(uid, "wb")
|
||
|
uiddat = self.UIDSYSStruct()
|
||
|
uiddat.titleid = 0x0000000100000002
|
||
|
uiddat.padding = 0
|
||
|
uiddat.uid = 0x1000
|
||
|
uidfp.write(uiddat.pack())
|
||
|
uidfp.close()
|
||
|
if((os.path.isfile(uid)) and (len(open(uid, "rb").read()) == 0)):
|
||
|
uidfp = open(uid, "wb")
|
||
|
uiddat = self.UIDSYSStruct()
|
||
|
uiddat.titleid = 0x0000000100000002
|
||
|
uiddat.padding = 0
|
||
|
uiddat.uid = 0x1000
|
||
|
uidfp.write(uiddat.pack())
|
||
|
uidfp.close()
|
||
|
|
||
|
def getUIDForTitle(self, title):
|
||
|
uidfp = open(self.f, "rb")
|
||
|
uiddat = uidfp.read()
|
||
|
cnt = len(uiddat) / 12
|
||
|
uidfp.seek(0)
|
||
|
uidstr = self.UIDSYSStruct()
|
||
|
uidict = {}
|
||
|
for i in range(cnt):
|
||
|
uidstr.titleid = uidfp.read(8)
|
||
|
uidstr.padding = uidfp.read(2)
|
||
|
uidstr.uid = uidfp.read(2)
|
||
|
uidict[uidstr.titleid] = uidstr.uid
|
||
|
for key, value in uidict.iteritems():
|
||
|
if(hexdump(key, "") == ("%016X" % title)):
|
||
|
return value
|
||
|
return self.addTitle(title)
|
||
|
|
||
|
def getTitle(self, uid):
|
||
|
uidfp = open(self.f, "rb")
|
||
|
uiddat = uidfp.read()
|
||
|
cnt = len(uiddat) / 12
|
||
|
uidfp.seek(0)
|
||
|
uidstr = self.UIDSYSStruct()
|
||
|
uidict = {}
|
||
|
for i in range(cnt):
|
||
|
uidstr.titleid = uidfp.read(8)
|
||
|
uidstr.padding = uidfp.read(2)
|
||
|
uidstr.uid = uidfp.read(2)
|
||
|
uidict[uidstr.titleid] = uidstr.uid
|
||
|
for key, value in uidict.iteritems():
|
||
|
if(hexdump(value, "") == ("%04X" % uid)):
|
||
|
return key
|
||
|
return None
|
||
|
|
||
|
def addTitle(self, title):
|
||
|
uidfp = open(self.f, "rb")
|
||
|
uiddat = uidfp.read()
|
||
|
cnt = len(uiddat) / 12
|
||
|
uidfp.seek(0)
|
||
|
uidstr = self.UIDSYSStruct()
|
||
|
uidict = {}
|
||
|
enduid = "\x10\x01"
|
||
|
for i in range(cnt):
|
||
|
uidstr.titleid = uidfp.read(8)
|
||
|
uidstr.padding = uidfp.read(2)
|
||
|
uidstr.uid = uidfp.read(2)
|
||
|
if(hexdump(uidstr.titleid, "") == ("%016X" % title)):
|
||
|
uidfp.close()
|
||
|
return uidstr.uid
|
||
|
if(struct.unpack(">H", uidstr.uid) >= struct.unpack(">H", enduid)):
|
||
|
enduid = a2b_hex("%04X" % (struct.unpack(">H", uidstr.uid)[0] + 1))
|
||
|
uidict[uidstr.titleid] = uidstr.uid
|
||
|
uidict[a2b_hex("%016X" % title)] = enduid
|
||
|
uidfp.close()
|
||
|
uidfp = open(self.f, "wb")
|
||
|
for key, value in uidict.iteritems():
|
||
|
uidfp.write(key)
|
||
|
uidfp.write("\0\0")
|
||
|
uidfp.write(value)
|
||
|
uidfp.close()
|
||
|
return enduid
|
||
|
|
||
|
class iplsave:
|
||
|
"""This class performs all iplsave.bin related things. It includes functions to add a title to the list, remove a title based upon position or title, and move a title from one position to another."""
|
||
|
class IPLSAVE_Entry(Struct):
|
||
|
__endian__ = Struct.BE
|
||
|
def __format__(self):
|
||
|
self.type1 = Struct.uint8
|
||
|
self.type2 = Struct.uint8
|
||
|
self.unk = Struct.uint32
|
||
|
self.flags = Struct.uint16
|
||
|
self.titleid = Struct.uint64
|
||
|
|
||
|
class IPLSAVE_Header(Struct):
|
||
|
__endian__ = Struct.BE
|
||
|
def __format__(self):
|
||
|
self.magic = Struct.string(4)
|
||
|
self.filesize = Struct.uint32
|
||
|
self.unk = Struct.uint64
|
||
|
# 0x30 Entries go here.
|
||
|
self.unk2 = Struct.string(0x20)
|
||
|
self.md5 = Struct.string(0x10)
|
||
|
|
||
|
def __init__(self, f, nand = False):
|
||
|
self.f = f
|
||
|
if(not os.path.isfile(f)):
|
||
|
if(nand != False):
|
||
|
nand.newFile("/title/00000001/00000002/data/iplsave.bin", "rw----", 0x0001, 0x0000000100000002)
|
||
|
baseipl_h = self.IPLSAVE_Header
|
||
|
baseipl_ent = self.IPLSAVE_Entry
|
||
|
baseipl_ent.type1 = 0
|
||
|
baseipl_ent.type2 = 0
|
||
|
baseipl_ent.unk = 0
|
||
|
baseipl_ent.flags = 0
|
||
|
baseipl_ent.titleid = 0
|
||
|
baseipl_h.magic = "RIPL"
|
||
|
baseipl_h.filesize = 0x340
|
||
|
baseipl_h.unk = 0x0000000200000000
|
||
|
baseipl_h.unk2 = "\0" * 0x20
|
||
|
fp = open(f, "wb")
|
||
|
fp.write(baseipl_h.magic)
|
||
|
fp.write(a2b_hex("%08X" % baseipl_h.filesize))
|
||
|
fp.write(a2b_hex("%016X" % baseipl_h.unk))
|
||
|
i = 0
|
||
|
for i in range(0x30):
|
||
|
fp.write(a2b_hex("%02X" % baseipl_ent.type1))
|
||
|
fp.write(a2b_hex("%02X" % baseipl_ent.type2))
|
||
|
fp.write(a2b_hex("%08X" % baseipl_ent.unk))
|
||
|
fp.write(a2b_hex("%04X" % baseipl_ent.flags))
|
||
|
fp.write(a2b_hex("%016X" % baseipl_ent.titleid))
|
||
|
fp.write(baseipl_h.unk2)
|
||
|
fp.close()
|
||
|
self.updateMD5()
|
||
|
|
||
|
def updateMD5(self):
|
||
|
"""Updates the MD5 hash in the iplsave.bin file. Used by other functions here."""
|
||
|
fp = open(self.f, "rb")
|
||
|
data = fp.read()
|
||
|
fp.close()
|
||
|
md5 = Crypto().createMD5Hash(data)
|
||
|
fp = open(self.f, "wb")
|
||
|
fp.write(data)
|
||
|
fp.write(md5)
|
||
|
fp.close()
|
||
|
|
||
|
def slotUsed(self, x, y, page):
|
||
|
"""Returns whether or not the slot at (x,y) on page ``page'' is used."""
|
||
|
if((x + (y * 4) + (page * 12)) >= 0x30):
|
||
|
print "Too far!"
|
||
|
return None
|
||
|
fp = open(self.f, "rb")
|
||
|
data = fp.read()
|
||
|
fp.seek(16 + ((x + (y * 4) + (page * 12))) * 16)
|
||
|
baseipl_ent = self.IPLSAVE_Entry
|
||
|
baseipl_ent.type1 = fp.read(1)
|
||
|
baseipl_ent.type2 = fp.read(1)
|
||
|
baseipl_ent.unk = fp.read(4)
|
||
|
baseipl_ent.flags = fp.read(2)
|
||
|
baseipl_ent.titleid = fp.read(8)
|
||
|
fp.close()
|
||
|
if(baseipl_ent.type1 == "\0"):
|
||
|
return 0
|
||
|
return baseipl_ent.titleid
|
||
|
|
||
|
def addTitleBase(self, x, y, page, tid, movable, type, overwrite, clear, isdisc):
|
||
|
"""A base addTitle function that is used by others. Don't use this."""
|
||
|
if((x + (y * 4) + (page * 12)) >= 0x30):
|
||
|
print "Too far!"
|
||
|
return None
|
||
|
fp = open(self.f, "rb")
|
||
|
data = fp.read()
|
||
|
fp.seek(16 + ((x + (y * 4) + (page * 12))) * 16)
|
||
|
baseipl_ent = self.IPLSAVE_Entry
|
||
|
baseipl_ent.type1 = fp.read(1)
|
||
|
fp.close()
|
||
|
if((self.slotUsed(x, y, page)) and (not overwrite)):
|
||
|
return self.addTitleBase(x + 1, y, page, tid, movable, type, overwrite, clear, isdisc)
|
||
|
fp = open(self.f, "wb")
|
||
|
fp.write(data)
|
||
|
fp.seek(16 + ((x + (y * 4) + (page * 12))) * 16)
|
||
|
if((not clear) and (not isdisc)):
|
||
|
baseipl_ent.type1 = 3
|
||
|
baseipl_ent.type2 = type
|
||
|
baseipl_ent.unk = 0
|
||
|
baseipl_ent.flags = (movable ^ 1) + 0x0E
|
||
|
baseipl_ent.titleid = tid
|
||
|
if((clear) and (not isdisc)):
|
||
|
baseipl_ent.type1 = 0
|
||
|
baseipl_ent.type2 = 0
|
||
|
baseipl_ent.unk = 0
|
||
|
baseipl_ent.flags = 0
|
||
|
baseipl_ent.titleid = 0
|
||
|
if(isdisc):
|
||
|
baseipl_ent.type1 = 1
|
||
|
baseipl_ent.type2 = 1
|
||
|
baseipl_ent.unk = 0
|
||
|
baseipl_ent.flags = (movable ^ 1) + 0x0E
|
||
|
baseipl_ent.titleid = 0
|
||
|
fp.write(a2b_hex("%02X" % baseipl_ent.type1))
|
||
|
fp.write(a2b_hex("%02X" % baseipl_ent.type2))
|
||
|
fp.write(a2b_hex("%08X" % baseipl_ent.unk))
|
||
|
fp.write(a2b_hex("%04X" % baseipl_ent.flags))
|
||
|
fp.write(a2b_hex("%016X" % baseipl_ent.titleid))
|
||
|
fp.close()
|
||
|
self.updateMD5()
|
||
|
return (x + (y * 4) + (page * 12))
|
||
|
|
||
|
def addTitle(self, x, y, page, tid, movable, type):
|
||
|
"""Adds a title with title ID ``tid'' at location (x,y) on page ``page''. ``movable'' specifies whether the title is movable, and ``type'' specifies the type of title (00 for most titles.)"""
|
||
|
return self.addTitleBase(x, y, page, tid, movable, type, 0, 0, 0)
|
||
|
|
||
|
def addDisc(self, x, y, page, movable):
|
||
|
"""Adds the Disc Channel at location (x,y) on page ``page''. ``movable'' specifies whether it can be moved."""
|
||
|
return self.addTitleBase(x, y, page, 0, movable, 0, 0, 0, 1)
|
||
|
|
||
|
def deletePosition(self, x, y, page):
|
||
|
"""Deletes the title at (x,y) on page ``page''"""
|
||
|
return self.addTitleBase(x, y, page, 0, 0, 0, 1, 1, 0)
|
||
|
|
||
|
def deleteTitle(self, tid):
|
||
|
"""Deletes the title with title ID ``tid''"""
|
||
|
fp = open(self.f, "rb")
|
||
|
baseipl_ent = self.IPLSAVE_Entry
|
||
|
for i in range(0x30):
|
||
|
fp.seek(16 + (i * 16))
|
||
|
baseipl_ent.type1 = fp.read(1)
|
||
|
baseipl_ent.type2 = fp.read(1)
|
||
|
baseipl_ent.unk = fp.read(4)
|
||
|
baseipl_ent.flags = fp.read(2)
|
||
|
baseipl_ent.titleid = fp.read(8)
|
||
|
if(baseipl_ent.titleid == a2b_hex("%016X" % tid)):
|
||
|
self.deletePosition(i, 0, 0)
|
||
|
fp.close()
|
||
|
|
||
|
def moveTitle(self, x1, y1, page1, x2, y2, page2):
|
||
|
"""Moves a title from (x1,y1) on page ``page1'' to (x2,y2) on page ``page2''"""
|
||
|
fp = open(self.f, "rb")
|
||
|
baseipl_ent = self.IPLSAVE_Entry
|
||
|
fp.seek(16 + ((x1 + (y1 * 4) + (page1 * 12)) * 16))
|
||
|
baseipl_ent.type1 = fp.read(1)
|
||
|
baseipl_ent.type2 = fp.read(1)
|
||
|
baseipl_ent.unk = fp.read(4)
|
||
|
baseipl_ent.flags = fp.read(2)
|
||
|
baseipl_ent.titleid = fp.read(8)
|
||
|
fp.close()
|
||
|
self.deletePosition(x1, y1, page1)
|
||
|
return self.addTitle(x2, y2, page2, baseipl_ent.titleid, (baseipl_ent.flags - 0xE) ^ 1, baseipl_ent.type2)
|