mirror of
https://github.com/grp/Wii.py.git
synced 2025-06-18 23:05:48 -04:00
Ack, there was some nand.py stuff from the deleted commit that needed to be added.
This commit is contained in:
parent
ecf16a1a18
commit
6c6deea939
238
nand.py
238
nand.py
@ -9,17 +9,11 @@ from common import *
|
|||||||
from title import *
|
from title import *
|
||||||
from formats import *
|
from formats import *
|
||||||
|
|
||||||
|
|
||||||
class NAND:
|
class NAND:
|
||||||
"""This class performs all NAND related things. It includes functions to copy a title (given the TMD) into the correct structure as the Wii does, and will eventually have an entire ES-like system. Parameter f to the initializer is the folder that will be used as the NAND root."""
|
"""This class performs all NAND related things. It includes functions to copy a title (given the TMD) into the correct structure as the Wii does, and has an entire ES-like system. Parameter f to the initializer is the folder that will be used as the NAND root."""
|
||||||
class UIDSYS(Struct):
|
|
||||||
__endian__ = Struct.BE
|
|
||||||
def __format__(self):
|
|
||||||
self.titleid = Struct.uint64
|
|
||||||
self.padding = Struct.uint16
|
|
||||||
self.uid = Struct.uint16
|
|
||||||
def __init__(self, f):
|
def __init__(self, f):
|
||||||
self.f = f
|
self.f = f
|
||||||
self.ES = ESClass(self)
|
|
||||||
if(not os.path.isdir(f)):
|
if(not os.path.isdir(f)):
|
||||||
os.mkdir(f)
|
os.mkdir(f)
|
||||||
if(not os.path.isdir(f + "/import")):
|
if(not os.path.isdir(f + "/import")):
|
||||||
@ -28,8 +22,6 @@ class NAND:
|
|||||||
os.mkdir(f + "/meta")
|
os.mkdir(f + "/meta")
|
||||||
if(not os.path.isdir(f + "/shared1")):
|
if(not os.path.isdir(f + "/shared1")):
|
||||||
os.mkdir(f + "/shared1")
|
os.mkdir(f + "/shared1")
|
||||||
if(not os.path.isfile(f + "/shared1/content.map")):
|
|
||||||
open(f + "/shared1/content.map", "wb").close()
|
|
||||||
if(not os.path.isdir(f + "/shared2")):
|
if(not os.path.isdir(f + "/shared2")):
|
||||||
os.mkdir(f + "/shared2")
|
os.mkdir(f + "/shared2")
|
||||||
if(not os.path.isdir(f + "/sys")):
|
if(not os.path.isdir(f + "/sys")):
|
||||||
@ -40,134 +32,69 @@ class NAND:
|
|||||||
open(f + "/sys/cert.sys", "wb").close()
|
open(f + "/sys/cert.sys", "wb").close()
|
||||||
if(not os.path.isfile(f + "/sys/space.sys")):
|
if(not os.path.isfile(f + "/sys/space.sys")):
|
||||||
open(f + "/sys/space.sys", "wb").close()
|
open(f + "/sys/space.sys", "wb").close()
|
||||||
if(not os.path.isfile(f + "/sys/uid.sys")):
|
|
||||||
uidfp = open(f + "/sys/uid.sys", "wb")
|
|
||||||
uiddat = self.UIDSYS()
|
|
||||||
uiddat.titleid = 0x0000000100000002
|
|
||||||
uiddat.padding = 0
|
|
||||||
uiddat.uid = 0x1000
|
|
||||||
uidfp.write(uiddat.pack())
|
|
||||||
uidfp.close()
|
|
||||||
if(not os.path.isdir(f + "/ticket")):
|
if(not os.path.isdir(f + "/ticket")):
|
||||||
os.mkdir(f + "/ticket")
|
os.mkdir(f + "/ticket")
|
||||||
if(not os.path.isdir(f + "/title")):
|
if(not os.path.isdir(f + "/title")):
|
||||||
os.mkdir(f + "/title")
|
os.mkdir(f + "/title")
|
||||||
if(not os.path.isdir(f + "/tmp")):
|
if(not os.path.isdir(f + "/tmp")):
|
||||||
os.mkdir(f + "/tmp")
|
os.mkdir(f + "/tmp")
|
||||||
def getUIDForTitle(self, title):
|
self.ES = ESClass(self)
|
||||||
uidfp = open(self.f + "/sys/uid.sys", "rb")
|
self.ISFS = ISFSClass(self)
|
||||||
uiddat = uidfp.read()
|
self.UID = uidsys(self.f + "/sys/uid.sys")
|
||||||
cnt = len(uiddat) / 12
|
self.contentmap = ContentMap(self.f + "/shared1/content.map")
|
||||||
uidfp.seek(0)
|
|
||||||
uidstr = self.UIDSYS()
|
def getContentByHashFromContentMap(self, hash):
|
||||||
uidict = {}
|
"""Gets the filename of a shared content with SHA1 hash ``hash''. This includes the NAND prefix."""
|
||||||
for i in range(cnt):
|
return self.f + self.contentmap.contentByHash(hash)
|
||||||
uidstr.titleid = uidfp.read(8)
|
|
||||||
uidstr.padding = uidfp.read(2)
|
def addContentToContentMap(self, contentid, hash):
|
||||||
uidstr.uid = uidfp.read(2)
|
"""Adds a content with content ID ``contentid'' and SHA1 hash ``hash'' to the content.map."""
|
||||||
uidict[uidstr.titleid] = uidstr.uid
|
return self.contentmap.addContentToMap(contentid, hash)
|
||||||
for key, value in uidict.iteritems():
|
|
||||||
if(hexdump(key, "") == ("%016X" % title)):
|
def addHashToContentMap(self, hash):
|
||||||
return value
|
"""Adds a content with SHA1 hash ``hash'' to the content.map. It returns the content ID used."""
|
||||||
return None
|
return self.contentmap.addHashToMap(hash)
|
||||||
def getTitleForUID(self, uid):
|
|
||||||
uidfp = open(self.f + "/sys/uid.sys", "rb")
|
def getContentCountFromContentMap(self):
|
||||||
uiddat = uidfp.read()
|
"""Returns the number of contents in the content.map."""
|
||||||
cnt = len(uiddat) / 12
|
return self.contentmap.contentCount()
|
||||||
uidfp.seek(0)
|
|
||||||
uidstr = self.UIDSYS()
|
def getContentHashesFromContentMap(self, count):
|
||||||
uidict = {}
|
"""Returns the hashes of ``count'' contents in the content.map."""
|
||||||
for i in range(cnt):
|
return self.contentmap.contentHashes(count)
|
||||||
uidstr.titleid = uidfp.read(8)
|
|
||||||
uidstr.padding = uidfp.read(2)
|
def addTitleToUIDSYS(self, title):
|
||||||
uidstr.uid = uidfp.read(2)
|
"""Adds the title with title ID ``title'' to the uid.sys file."""
|
||||||
uidict[uidstr.titleid] = uidstr.uid
|
return self.UID.addTitle(title)
|
||||||
for key, value in uidict.iteritems():
|
|
||||||
if(hexdump(value, "") == ("%04X" % uid)):
|
def getTitleFromUIDSYS(self, uid):
|
||||||
return key
|
"""Gets the title ID with UID ``uid'' from the uid.sys file."""
|
||||||
return None
|
return self.UID.getTitle(uid)
|
||||||
def addTitleToUID(self, title):
|
|
||||||
uidfp = open(self.f + "/sys/uid.sys", "rb")
|
def getUIDForTitleFromUIDSYS(self, title):
|
||||||
uiddat = uidfp.read()
|
"""Gets the UID for title ID ``title'' from the uid.sys file."""
|
||||||
cnt = len(uiddat) / 12
|
return self.UID.getUIDForTitle(uid)
|
||||||
uidfp.seek(0)
|
|
||||||
uidstr = self.UIDSYS()
|
def addTitleToMenu(self, tid):
|
||||||
uidict = {}
|
"""Adds a title to the System Menu."""
|
||||||
enduid = "\x10\x01"
|
a = iplsave(self.f + "/title/00000001/00000002/data/iplsave.bin")
|
||||||
for i in range(cnt):
|
type = 0
|
||||||
uidstr.titleid = uidfp.read(8)
|
if(((tid & 0xFFFFFFFFFFFFFF00) == 0x0001000248414300) or ((tid & 0xFFFFFFFFFFFFFF00) == 0x0001000248414200)):
|
||||||
uidstr.padding = uidfp.read(2)
|
type = 1
|
||||||
uidstr.uid = uidfp.read(2)
|
a.addTitle(0,0, 0, tid, 1, type)
|
||||||
if(hexdump(uidstr.titleid, "") == ("%016X" % title)):
|
|
||||||
uidfp.close()
|
def addDiscChannelToMenu(self, x, y, page, movable):
|
||||||
return uidstr.uid
|
"""Adds the disc channel to the System Menu."""
|
||||||
if(unpack(">H", uidstr.uid) >= unpack(">H", enduid)):
|
a = iplsave(self.f + "/title/00000001/00000002/data/iplsave.bin")
|
||||||
enduid = a2b_hex("%04X" % (unpack(">H", uidstr.uid)[0] + 1))
|
a.addDisc(x, y, page, movable)
|
||||||
uidict[uidstr.titleid] = uidstr.uid
|
|
||||||
uidict[a2b_hex("%016X" % title)] = enduid
|
def deleteTitleFromMenu(self, tid):
|
||||||
uidfp.close()
|
"""Deletes a title from the System Menu."""
|
||||||
uidfp = open(self.f + "/sys/uid.sys", "wb")
|
a = iplsave(self.f + "/title/00000001/00000002/data/iplsave.bin")
|
||||||
for key, value in uidict.iteritems():
|
a.deleteTitle(tid)
|
||||||
uidfp.write(key)
|
|
||||||
uidfp.write("\0\0")
|
def importTitle(self, prefix, tmd, tik, add_to_menu = True, is_decrypted = False, result_decrypted = False):
|
||||||
uidfp.write(value)
|
"""When passed a prefix (the directory to obtain the .app files from, sorted by content id), a TMD instance, and a Ticket instance, this will add that title to the NAND base folder specified in the constructor. If add_to_menu is True, the title (if neccessary) will be added to the menu. The default is True. Unless is_decrypted is set, the contents are assumed to be encrypted. If result_decrypted is True, then the contents will not end up decrypted."""
|
||||||
uidfp.close()
|
|
||||||
return enduid
|
|
||||||
def contentByHash(self, hash):
|
|
||||||
"""When passed a sha1 hash (string of length 20), this will return the path name (including the NAND FS prefix) to the shared content 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 + "/shared1/content.map", "rb")
|
|
||||||
cmdict = {}
|
|
||||||
num = len(data) / 28
|
|
||||||
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 self.f + "/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 + "/shared1/content.map", "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 + "/shared1/content.map", "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 + "/shared1/content.map", "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 + "/shared1/content.map", "wb")
|
|
||||||
for key, value in cmdict.iteritems():
|
|
||||||
cmfp.write(key)
|
|
||||||
cmfp.write(value)
|
|
||||||
cmfp.close()
|
|
||||||
return cnt
|
|
||||||
def importTitle(self, prefix, tmd, tik, is_decrypted = False, result_decrypted = False):
|
|
||||||
"""When passed a prefix (the directory to obtain the .app files from, sorted by content id), a TMD instance, and a Ticket instance, this will add that title to the NAND base folder specified in the constructor. Unless is_decrypted is set, the contents are assumed to be encrypted. If result_decrypted is True, then the contents will not end up decrypted."""
|
|
||||||
self.ES.AddTitleStart(tmd, None, None, is_decrypted, result_decrypted, use_version = True)
|
self.ES.AddTitleStart(tmd, None, None, is_decrypted, result_decrypted, use_version = True)
|
||||||
self.ES.AddTitleTMD(tmd)
|
self.ES.AddTitleTMD(tmd)
|
||||||
self.ES.AddTicket(tik)
|
self.ES.AddTicket(tik)
|
||||||
@ -180,7 +107,17 @@ class NAND:
|
|||||||
self.ES.AddContentData(contents[i].cid, data)
|
self.ES.AddContentData(contents[i].cid, data)
|
||||||
self.ES.AddContentFinish(contents[i].cid)
|
self.ES.AddContentFinish(contents[i].cid)
|
||||||
self.ES.AddTitleFinish()
|
self.ES.AddTitleFinish()
|
||||||
|
if(add_to_menu == True):
|
||||||
|
if((tmd.tmd.titleid >> 32) != 0x00010008):
|
||||||
|
self.addTitleToMenu(tmd.tmd.titleid)
|
||||||
|
|
||||||
|
class ISFSClass:
|
||||||
|
"""This class contains an interface to the NAND that simulates the permissions system and all other aspects of the ISFS.
|
||||||
|
The nand argument to the initializer is a NAND object."""
|
||||||
|
def __init__(self, nand):
|
||||||
|
self.nand = nand
|
||||||
|
self.f = nand.f
|
||||||
|
|
||||||
class ESClass:
|
class ESClass:
|
||||||
"""This class performs all services relating to titles installed on the Wii. It is a clone of the libogc ES interface.
|
"""This class performs all services relating to titles installed on the Wii. It is a clone of the libogc ES interface.
|
||||||
The nand argument to the initializer is a NAND object."""
|
The nand argument to the initializer is a NAND object."""
|
||||||
@ -213,31 +150,22 @@ class ESClass:
|
|||||||
if(tmd == None):
|
if(tmd == None):
|
||||||
return 0
|
return 0
|
||||||
return tmd.tmd.numcontents
|
return tmd.tmd.numcontents
|
||||||
|
def GetTitleContents(self, titleid, version, count):
|
||||||
|
"""Returns a list of content IDs for title id ``titleid'' and version ``version''. It will return, at maximum, ``count'' entries."""
|
||||||
|
tmd = self.GetStoredTMD(titleid, version)
|
||||||
|
if(tmd == None):
|
||||||
|
return 0
|
||||||
|
contents = tmd.getContents()
|
||||||
|
out = ""
|
||||||
|
for z in range(count):
|
||||||
|
out += a2b_hex("%08X" % contents[z].cid)
|
||||||
|
return out
|
||||||
def GetNumSharedContents(self):
|
def GetNumSharedContents(self):
|
||||||
"""Gets how many shared contents exist on the NAND"""
|
"""Gets how many shared contents exist on the NAND"""
|
||||||
cmfp = open(self.f + "/shared1/content.map", "rb")
|
return self.nand.getContentCountFromContentMap()
|
||||||
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 GetSharedContents(self, cnt):
|
def GetSharedContents(self, cnt):
|
||||||
"""Gets cnt amount of shared content hashes"""
|
"""Gets cnt amount of shared content hashes"""
|
||||||
cmfp = open(self.f + "/shared1/content.map", "rb")
|
return self.nand.getContentHashesFromContentMap(cnt)
|
||||||
num = len(cmfp.read()) / 28
|
|
||||||
cmfp.seek(0)
|
|
||||||
hashout = ""
|
|
||||||
for z in range(num):
|
|
||||||
name = cmfp.read(8)
|
|
||||||
hashout += cmfp.read(20)
|
|
||||||
cmfp.close()
|
|
||||||
return hashout
|
|
||||||
def AddTitleStart(self, tmd, certs, crl, is_decrypted = False, result_decrypted = True, use_version = False):
|
def AddTitleStart(self, tmd, certs, crl, is_decrypted = False, result_decrypted = True, use_version = False):
|
||||||
if(not os.path.isdir(self.f + "/title/%08x" % (tmd.tmd.titleid >> 32))):
|
if(not os.path.isdir(self.f + "/title/%08x" % (tmd.tmd.titleid >> 32))):
|
||||||
os.mkdir(self.f + "/title/%08x" % (tmd.tmd.titleid >> 32))
|
os.mkdir(self.f + "/title/%08x" % (tmd.tmd.titleid >> 32))
|
||||||
@ -351,7 +279,7 @@ class ESClass:
|
|||||||
fp.close()
|
fp.close()
|
||||||
outfp.write(tmpdata)
|
outfp.write(tmpdata)
|
||||||
outfp.close()
|
outfp.close()
|
||||||
self.nand.addTitleToUID(self.wtitleid)
|
self.nand.addTitleToUIDSYS(self.wtitleid)
|
||||||
if(self.tmdadded and self.use_version):
|
if(self.tmdadded and self.use_version):
|
||||||
tmd.rawdump(self.f + "/title/%08x/%08x/content/title.tmd.%d" % (self.wtitleid >> 32, self.wtitleid & 0xFFFFFFFF, tmd.tmd.title_version))
|
tmd.rawdump(self.f + "/title/%08x/%08x/content/title.tmd.%d" % (self.wtitleid >> 32, self.wtitleid & 0xFFFFFFFF, tmd.tmd.title_version))
|
||||||
elif(self.tmdadded):
|
elif(self.tmdadded):
|
||||||
|
Loading…
Reference in New Issue
Block a user