From 7200db92c09f8b345f7c515cceab10c06acaa1e5 Mon Sep 17 00:00:00 2001 From: Alex Marshall Date: Sun, 21 Jun 2009 03:02:56 -0700 Subject: [PATCH] Added some new commands to other.py, fixed some non-matching capitalization styles (the new form for all future capitalizations is likeThis not LikeThis, nor like_this.) and removed the wx and png dependancies on quite a few modules. I'll add some interfacing to the other.py classes from nand.py some time soon. I have confirmed that CONF works 100%. --- U8.py | 2 - Wii.py | 1 + common.py | 28 +++++----- compression.py | 2 - disc.py | 4 +- nand.py | 2 - other.py | 138 +++++++++++++++++++++++++++++++++++++------------ savedata.py | 6 +-- title.py | 20 ++++--- 9 files changed, 131 insertions(+), 72 deletions(-) diff --git a/U8.py b/U8.py index 3af4f54..e895fe6 100644 --- a/U8.py +++ b/U8.py @@ -1,6 +1,4 @@ import os, hashlib, struct, subprocess, fnmatch, shutil, urllib, array -import wx -import png from Crypto.Cipher import AES from Struct import Struct diff --git a/Wii.py b/Wii.py index e116eda..6fc7b16 100644 --- a/Wii.py +++ b/Wii.py @@ -5,6 +5,7 @@ from savedata import * from banner import * from disc import * from nand import * +from other import * from title import * from TPL import * from U8 import * diff --git a/common.py b/common.py index 29cecd0..e1bcd61 100644 --- a/common.py +++ b/common.py @@ -1,6 +1,4 @@ import os, hashlib, struct, subprocess, fnmatch, shutil, urllib, array -import wx -import png from Crypto.Cipher import AES from Struct import Struct @@ -20,39 +18,39 @@ class Crypto: def __init__(self): self.align = 64 return - def DecryptData(self, key, iv, data, align = True): + def decryptData(self, key, iv, data, align = True): """Decrypts some data (aligns to 64 bytes, if needed).""" if((len(data) % self.align) != 0 and align): return AES.new(key, AES.MODE_CBC, iv).decrypt(data + ("\x00" * (self.align - (len(data) % self.align)))) else: return AES.new(key, AES.MODE_CBC, iv).decrypt(data) - def EncryptData(self, key, iv, data, align = True): + def encryptData(self, key, iv, data, align = True): """Encrypts some data (aligns to 64 bytes, if needed).""" if((len(data) % self.align) != 0 and align): return AES.new(key, AES.MODE_CBC, iv).encrypt(data + ("\x00" * (self.align - (len(data) % self.align)))) else: return AES.new(key, AES.MODE_CBC, iv).encrypt(data) - def DecryptContent(self, titlekey, idx, data): + def decryptContent(self, titlekey, idx, data): """Decrypts a Content.""" iv = struct.pack(">H", idx) + "\x00" * 14 - return self.DecryptData(titlekey, iv, data) - def DecryptTitleKey(self, commonkey, tid, enckey): + return self.decryptData(titlekey, iv, data) + def decryptTitleKey(self, commonkey, tid, enckey): """Decrypts a Content.""" iv = struct.pack(">Q", tid) + "\x00" * 8 - return self.DecryptData(commonkey, iv, enckey, False) - def EncryptContent(self, titlekey, idx, data): + return self.decryptData(commonkey, iv, enckey, False) + def encryptContent(self, titlekey, idx, data): """Encrypts a Content.""" iv = struct.pack(">H", idx) + "\x00" * 14 - return self.EncryptData(titlekey, iv, data) - def CreateSHAHash(self, data): #tested WORKING (without padding) + return self.encryptData(titlekey, iv, data) + def createSHAHash(self, data): #tested WORKING (without padding) return hashlib.sha1(data).digest() - def CreateSHAHashHex(self, data): + def createSHAHashHex(self, data): return hashlib.sha1(data).hexdigest() - def CreateMD5HashHex(self, data): + def createMD5HashHex(self, data): return hashlib.md5(data).hexdigest() - def CreateMD5Hash(self, data): + def createMD5Hash(self, data): return hashlib.md5(data).digest() - def ValidateSHAHash(self, data, hash): + def validateSHAHash(self, data, hash): """Validates a hash. Not checking currently because we have some...issues with hashes.""" return 1 #hack if((len(data) % self.align) != 0): diff --git a/compression.py b/compression.py index 8710c95..3b1c7c0 100644 --- a/compression.py +++ b/compression.py @@ -1,6 +1,4 @@ import os, hashlib, struct, subprocess, fnmatch, shutil, urllib, array -import wx -import png import zlib from Crypto.Cipher import AES diff --git a/disc.py b/disc.py index e583336..0887697 100644 --- a/disc.py +++ b/disc.py @@ -1,6 +1,4 @@ import os, hashlib, struct, subprocess, fnmatch, shutil, urllib, array -import wx -import png import time from title import * @@ -124,7 +122,7 @@ class WOD: #WiiOpticalDisc print 'IV %s (len %i)\n' % (hexdump(blockIV), len(blockIV)) blockData = block[0x0400:0x7FFF] - return Crypto().DecryptData(self.partitionKey, blockIV, blockData, True) + return Crypto().decryptData(self.partitionKey, blockIV, blockData, True) def readPartition(self, offset, size): diff --git a/nand.py b/nand.py index 1c834df..5be73de 100644 --- a/nand.py +++ b/nand.py @@ -1,6 +1,4 @@ import os, hashlib, struct, subprocess, fnmatch, shutil, urllib, array -import wx -import png from binascii import * from Crypto.Cipher import AES diff --git a/other.py b/other.py index 398d52d..73731d9 100644 --- a/other.py +++ b/other.py @@ -1,6 +1,4 @@ import os, hashlib, struct, subprocess, fnmatch, shutil, urllib, array -import wx -import png from binascii import * from Crypto.Cipher import AES @@ -11,8 +9,7 @@ from common import * from title import * class CONF: - """ This class deal with setting.txt wich hold some wii info like game area and wii serial number """ - + """This class deals with setting.txt which holds some important information like region and serial number """ def __init__(self, f): self.conf = '' self.keys = {} @@ -27,7 +24,7 @@ class CONF: return self.conf = self.fp.read(0x100) - self.conf = self.xorConf(self.conf) + self.conf = self.XORConf(self.conf) self.fp.seek(0) keys = self.conf.split('\r\n') @@ -43,19 +40,23 @@ class CONF: 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.keyNames 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): - if self.getKeyValue(key.upper()) != 'Key not found': + """Sets the value of key ``key'' to ``value''.""" + if(self.keyExist(key)): self.keys[key.upper()] = value.upper() self.conf = '' @@ -67,18 +68,20 @@ class CONF: self.conf += '\r\n' self.fp.seek(0) - self.fp.write(self.xorConf(self.conf)) + 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): + """Returns 1 if key ``key'' exists, 0 otherwise.""" if self.getKeyValue(key.upper()) != 'Key not found': return 0 else: return 1 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): @@ -93,18 +96,20 @@ class CONF: self.lastKeyOffset += len(key) + 1 + len(value) + 2 self.fp.seek(0) - self.fp.write(self.xorConf(self.conf)) + self.fp.write(self.XORConf(self.conf)) - def xorConf(self, conf): - xorKey = 0x73B5DBFA + 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) + out += chr(ord(conf[x]) ^ XORKey & 0xFF) + XORKey = (XORKey << 1) | (XORKey >> 31) return out - def delKey(self, key): + def DeleteKey(self, key): + """Deletes the key ``key''.""" try: del self.keys[key.upper()] self.keyNames.remove(key.upper()) @@ -119,14 +124,16 @@ class CONF: self.conf += '\r\n' self.fp.seek(0) - self.fp.write(self.xorConf(self.conf)) + 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 delKeyByValue(self, value): +# 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: @@ -142,15 +149,73 @@ class CONF: 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(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 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): @@ -201,17 +266,19 @@ class iplsave: fp.close() self.UpdateMD5() - def UpdateMD5(self): + 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) + md5 = Crypto().createMD5Hash(data) fp = open(self.f, "wb") fp.write(data) fp.write(md5) fp.close() - def AddTitleBase(self, x, y, page, tid, movable, type, overwrite, clear, isdisc): + 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 @@ -222,7 +289,7 @@ class iplsave: baseipl_ent.type1 = fp.read(1) fp.close() if((baseipl_ent.type1 != "\0") and (not overwrite)): - return self.AddTitle(x + 1, y, page, tid, movable, type) + 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) @@ -250,19 +317,23 @@ class iplsave: fp.write(a2b_hex("%04X" % baseipl_ent.flags)) fp.write(a2b_hex("%016X" % baseipl_ent.titleid)) fp.close() - self.UpdateMD5() + self.updateMD5() return (x + (y * 4) + (page * 12)) - def AddTitle(self, x, y, page, tid, movable, type): - return self.AddTitleBase(x, y, page, tid, movable, type, 0, 0, 0) + 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): - return self.AddTitleBase(x, y, page, 0, movable, 0, 0, 0, 1) + 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): - return self.AddTitleBase(x, y, page, 0, 0, 0, 1, 1, 0) + 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): + 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): @@ -273,10 +344,11 @@ class iplsave: 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) + self.deletePosition(i, 0, 0) fp.close() - def MoveTitle(self, x1, y1, page1, x2, y2, page2): + 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)) @@ -286,5 +358,5 @@ class iplsave: 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) + self.deletePosition(x1, y1, page1) + return self.addTitle(x2, y2, page2, baseipl_ent.titleid, (baseipl_ent.flags - 0xE) ^ 1, baseipl_ent.type2) diff --git a/savedata.py b/savedata.py index bea24fd..b3e6e38 100644 --- a/savedata.py +++ b/savedata.py @@ -1,6 +1,4 @@ import os, hashlib, struct, subprocess, fnmatch, shutil, urllib, array -import wx -import png from hashlib import md5 from Crypto.Cipher import AES @@ -157,7 +155,7 @@ class Savegame(): print 'Extracted %s (%ib)' % (name, fileHdr.size) fileBuffer = self.fd.read(fileHdr.size) - fileBuffer = Crypto().DecryptData(self.sdKey, fileIV, fileBuffer, True) + fileBuffer = Crypto().decryptData(self.sdKey, fileIV, fileBuffer, True) try: open(name, 'w+b').write(fileBuffer) except: @@ -176,7 +174,7 @@ class Savegame(): def analyzeHeader(self): headerBuffer = self.fd.read(0xF0C0) - headerBuffer = Crypto().DecryptData(self.sdKey, self.sdIv, headerBuffer, True) + headerBuffer = Crypto().decryptData(self.sdKey, self.sdIv, headerBuffer, True) self.hdr = self.savegameHeader().unpack(headerBuffer[:0x20]) diff --git a/title.py b/title.py index 08bfc9f..3e4a4c5 100644 --- a/title.py +++ b/title.py @@ -1,6 +1,4 @@ import os, struct, subprocess, fnmatch, shutil, urllib, array -import wx -import png from Struct import Struct @@ -53,7 +51,7 @@ class Ticket: if(self.tik.commonkey_index == 1): #korean, kekekekek! commonkey = koreankey - self.titlekey = Crypto().DecryptTitleKey(commonkey, self.tik.titleid, self.tik.enctitlekey) + self.titlekey = Crypto().decryptTitleKey(commonkey, self.tik.titleid, self.tik.enctitlekey) def getTitleKey(self): """Returns a string containing the title key.""" return self.titlekey @@ -87,7 +85,7 @@ class Ticket: self.rsamod = self.rsamod = "\x00" * 256 for i in range(65536): self.tik.unk2 = i - if(Crypto().CreateSHAHashHex(self.tik.pack())[:2] == "00"): + if(Crypto().createSHAHashHex(self.tik.pack())[:2] == "00"): break if(i == 65535): raise ValueError("Failed to fakesign. Aborting...") @@ -204,7 +202,7 @@ class TMD: data += self.tmd.pack() for i in range(self.tmd.numcontents): data += self.contents[i].pack() - if(Crypto().CreateSHAHashHex(data)[:2] == "00"): + if(Crypto().createSHAHashHex(data)[:2] == "00"): break if(i == 65535): raise ValueError("Failed to fakesign! Aborting...") @@ -271,7 +269,7 @@ class NUS: certs += rawtik[0x2A4 + 0x300:] #CA (tik) certs += rawtmd[0x328:0x328 + 0x300] #CP - if(Crypto().CreateMD5HashHex(certs) != "7ff50e2733f7a6be1677b6f6c9b625dd"): + if(Crypto().createMD5HashHex(certs) != "7ff50e2733f7a6be1677b6f6c9b625dd"): raise ValueError("Failed to create certs! MD5 mistatch.") open("cert", "wb").write(certs) @@ -301,8 +299,8 @@ class NUS: if(decrypt): data = open("%08x.app" % output, "rb").read(content.size) - tmpdata = Crypto().DecryptContent(titlekey, content.index, data) - if(Crypto().ValidateSHAHash(tmpdata, content.hash) == 0): + tmpdata = Crypto().decryptContent(titlekey, content.index, data) + if(Crypto().validateSHAHash(tmpdata, content.hash) == 0): raise ValueError("Decryption failed! SHA1 mismatch.") open("%08x.app" % output, "wb").write(tmpdata) @@ -330,10 +328,10 @@ class WAD: if(decrypted): if(fakesign): - content.hash = str(Crypto().CreateSHAHash(tmpdata)) + content.hash = str(Crypto().createSHAHash(tmpdata)) content.size = len(tmpdata) - encdata = Crypto().EncryptContent(titlekey, content.index, tmpdata) + encdata = Crypto().encryptContent(titlekey, content.index, tmpdata) else: encdata = tmpdata @@ -427,7 +425,7 @@ class WAD: if(tmpsize % 16 != 0): tmpsize += 16 - (tmpsize % 16) tmptmpdata = fd.read(tmpsize) - tmpdata = Crypto().DecryptContent(titlekey, contents[i].index, tmptmpdata) + tmpdata = Crypto().decryptContent(titlekey, contents[i].index, tmptmpdata) open("%08x.app" % contents[i].index, "wb").write(tmpdata) if(tmpsize % 64 != 0): fd.seek(64 - (tmpsize % 64), 1)