from common import * from title import * class Savegame(): class savegameHeader(Struct): __endian__ = Struct.BE def __format__(self): self.savegameId = Struct.uint32[2] self.bannerSize = Struct.uint32 self.permissions = Struct.uint8 self.unknown1 = Struct.uint8 self.md5hash = Struct.string(16) self.unknown2 = Struct.uint16 class savegameBanner(Struct): __endian__ = Struct.BE def __format__(self): self.magic = Struct.string(4) self.reserved = Struct.uint8[4] self.flags = Struct.uint32 self.reserved = Struct.uint32[5] self.gameTitle = Struct.string(64) self.gameSubTitle = Struct.string(64) self.banner = Struct.string(24576) self.icon0 = Struct.string(4608) self.icon1 = Struct.string(4608) self.icon2 = Struct.string(4608) self.icon3 = Struct.string(4608) self.icon4 = Struct.string(4608) self.icon5 = Struct.string(4608) self.icon6 = Struct.string(4608) self.icon7 = Struct.string(4608) class backupHeader(Struct): __endian__ = Struct.BE def __format__(self): self.hdrSize = Struct.uint32 self.magic = Struct.string(2) self.version = Struct.uint16 self.NGid = Struct.uint32 self.filesCount = Struct.uint32 self.filesSize = Struct.uint32 self.unknown1 = Struct.uint32 self.unknown2 = Struct.uint32 self.totalSize = Struct.uint32 self.unknown3 = Struct.uint8[64] self.unknown4 = Struct.uint32 self.gameId = Struct.string(4) self.wiiMacAddr = Struct.uint8[6] self.unknown6 = Struct.uint16 self.padding = Struct.uint32[4] class fileHeader(Struct): __endian__ = Struct.BE def __format__(self): self.magic = Struct.uint32 self.size = Struct.uint32 self.permissions = Struct.uint8 self.attribute = Struct.uint8 self.type = Struct.uint8 self.nameIV = Struct.string(0x45) def __init__(self, f): self.f = f try: self.fd = open(f, 'r+b') except: raise Exception('Cannot open input') 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.iconCount = 1 def __str__(self): ret = '' ret += '\nSavegame header \n' ret += 'Savegame ID : 0x%x%x\n' % (self.hdr.savegameId[0], self.hdr.savegameId[1]) ret += 'Banner size : 0x%x\n' % self.hdr.bannerSize ret += 'Permissions : 0x%x\n' % self.hdr.permissions ret += 'Unknown : 0x%x\n' % self.hdr.unknown1 ret += 'MD5 hash : %s\n' % hexdump(self.hdr.md5hash) ret += 'Unknown : 0x%x\n' % self.hdr.unknown2 ret += '\nBanner header \n' ret += 'Flags : 0x%x\n' % self.bnr.flags ret += 'Game title : %s\n' % self.bnr.gameTitle ret += 'Game subtitle : %s\n' % self.bnr.gameSubTitle ret += 'Icons found : %i\n' % self.iconCount ret += '\nBackup header \n' ret += 'Header size : 0x%x (+ 0x10 of padding) version 0x%x\n' % (self.bkHdr.hdrSize, self.bkHdr.version) if self.bkHdr.gameId[3] == 'P': ret += 'Region : PAL\n' elif self.bkHdr.gameId[3] == 'E': ret += 'Region : NTSC\n' elif self.bkHdr.gameId[3] == 'J': ret += 'Region : JAP\n' ret += 'Game ID : %s\n' % self.bkHdr.gameId ret += 'Wii unique ID : 0x%x\n' % self.bkHdr.NGid ret += 'Wii MAC address %02x:%02x:%02x:%02x:%02x:%02x\n' % (self.bkHdr.wiiMacAddr[0], self.bkHdr.wiiMacAddr[1], self.bkHdr.wiiMacAddr[2], self.bkHdr.wiiMacAddr[3], self.bkHdr.wiiMacAddr[4], self.bkHdr.wiiMacAddr[5]) ret += 'Found %i files for %i bytes\n' % (self.bkHdr.filesCount, self.bkHdr.filesSize) ret += 'Total size : %i bytes\n' % self.bkHdr.totalSize ret += 'This save is %i blocks wise' % (self.bkHdr.totalSize / 0x20000) return ret def extractFiles(self): try: os.mkdir(os.path.dirname(self.f) + '/' + self.bkHdr.gameId) except: pass os.chdir(os.path.dirname(self.f) + '/' + self.bkHdr.gameId) self.fd.seek(self.fileStartOffset) for i in range(self.bkHdr.filesCount): fileHdr = self.fd.read(0x80) fileHdr = self.fileHeader().unpack(fileHdr) if fileHdr.magic != 0x03adf17e: raise Exception('Wrong file magic') fileHdr.size = align(fileHdr.size, 64) ivpos = 0 name = "" i = 0 for char in list(fileHdr.nameIV): if(char == "\x00"): i -= 1 ivpos = i break else: name += char i += 1 fileIV = fileHdr.nameIV[ivpos:ivpos + 16] if len(fileIV) != 16: raise Exception('IV alignment issue') if fileHdr.type == 1: print 'Extracted %s (%ib)' % (name, fileHdr.size) fileBuffer = self.fd.read(fileHdr.size) fileBuffer = Crypto().decryptData(self.sdKey, fileIV, fileBuffer, True) try: open(name, 'w+b').write(fileBuffer) except: raise Exception('Cannot write the output') elif fileHdr.type == 2: print 'Extracted folder %s' % name try: os.mkdir(name) except: pass print 'Attribute %i Permission %i' % (fileHdr.attribute, fileHdr.permissions) print 'File IV : 0x%s' % hexdump(fileIV, '') os.chdir('..') def analyzeHeader(self): headerBuffer = self.fd.read(0xF0C0) headerBuffer = Crypto().decryptData(self.sdKey, self.sdIv, headerBuffer, True) self.hdr = self.savegameHeader().unpack(headerBuffer[:0x20]) #headerBuffer.replace(self.hdr.md5hash, '\x0e\x65\x37\x81\x99\xbe\x45\x17\xab\x06\xec\x22\x45\x1a\x57\x93') #print 'Reashed md5 : %s' % hexdump(Crypto().createMD5Hash(headerBuffer)) self.bnr = self.savegameBanner().unpack(headerBuffer[0x20:]) if self.bnr.magic != 'WIBN': raise Exception ('Wrong magic, should be WIBN') if self.hdr.bannerSize != 0x72A0: self.iconCount += 7 bkHdrBuffer = self.fd.read(0x80) self.bkHdr = self.backupHeader().unpack(bkHdrBuffer) if self.bkHdr.magic != 'Bk' or self.bkHdr.hdrSize != 0x70: raise Exception ('Bk header error') self.fileStartOffset = self.fd.tell() def eraseWiiMac(self): self.fd.seek(0xF128) print self.fd.write('\x00' * 6) def getBanner(self): try: os.mkdir(os.path.dirname(self.f) + '/' + self.bkHdr.gameId) except: pass os.chdir(os.path.dirname(self.f) + '/' + self.bkHdr.gameId) return Image.fromstring("RGBA", (192, 64), TPL('').RGB5A3((192, 64), self.bnr.banner)).save('banner', 'png') def getIcon(self, index): if index < 0 or index > 7 or index > self.iconCount: return -1 try: os.mkdir(os.path.dirname(self.f) + '/' + self.bkHdr.gameId) except: pass os.chdir(os.path.dirname(self.f) + '/' + self.bkHdr.gameId) if index == 0: return Image.fromstring("RGBA", (48, 48), TPL('').RGB5A3((48, 48), self.bnr.icon0)).save('icon' + str(index), 'png') if index == 1: return Image.fromstring("RGBA", (48, 48), TPL('').RGB5A3((48, 48), self.bnr.icon1)).save('icon' + str(index), 'png') if index == 2: return Image.fromstring("RGBA", (48, 48), TPL('').RGB5A3((48, 48), self.bnr.icon2)).save('icon' + str(index), 'png') if index == 3: return Image.fromstring("RGBA", (48, 48), TPL('').RGB5A3((48, 48), self.bnr.icon3)).save('icon' + str(index), 'png') if index == 4: return Image.fromstring("RGBA", (48, 48), TPL('').RGB5A3((48, 48), self.bnr.icon4)).save('icon' + str(index), 'png') if index == 5: return Image.fromstring("RGBA", (48, 48), TPL('').RGB5A3((48, 48), self.bnr.icon5)).save('icon' + str(index), 'png') if index == 6: return Image.fromstring("RGBA", (48, 48), TPL('').RGB5A3((48, 48), self.bnr.icon6)).save('icon' + str(index), 'png') if index == 7: return Image.fromstring("RGBA", (48, 48), TPL('').RGB5A3((48, 48), self.bnr.icon7)).save('icon' + str(index), 'png') def getIconsCount(self): return self.iconCount def getSaveString(self, string): if string == 'id': return self.bkHdr.gameId elif string == 'title': return self.bnr.gameTitle elif string == 'subtitle': return self.bnr.gameSubTitle elif string == 'mac': return self.bkHdr.wiiMacAddr[0] + ':' + self.bkHdr.wiiMacAddr[1] + ':' + self.bkHdr.wiiMacAddr[2] + ':' + self.bkHdr.wiiMacAddr[3] + ':' + self.bkHdr.wiiMacAddr[4] + ':' + self.bkHdr.wiiMacAddr[5] def getFilesCount(self): return self.bkHdr.filesCount