def get_rom_template_data(self, rom_path): # open rom file romfp = open(rom_path, "rb") # get card specific data from template.txt serial = gamecard.ncsd_serial(romfp) sha1 = gamecard.ncch_sha1sum(romfp) template_data = titles.get_template(serial, sha1) if not template_data: return None else: return template_data
def write_rom(self, rom, silent=False, progress=None, use_header_bin=False, verbose=False): """Write rom to sdcard. Roms are stored at the position marked in the position headers (starting at 0x2000000). This code first looks for a free block with enough space to hold the specified rom, then continues to write the data to that location. After successful writing the savegame slot for this game is filled with zero. The last thing to do is to find the game in template.txt and write the data from that file to offset 0x1400 inside the rom on sdcard. Keyword Arguments: rom -- path to rom file""" self.fail_on_non_sky3ds() # follow symlink rom = os.path.realpath(rom) # get rom size and calculate block count rom_size = os.path.getsize(rom) rom_blocks = int(rom_size / 0x200) # get free blocks on sd-card and search for a block big enough for the rom start_block = 0 for free_block in self.free_blocks[::-1]: if free_block[1] >= rom_blocks: start_block = free_block[0] break if start_block == 0: raise Exception("Not enough free continous blocks") self.diskfp.seek(0) position_header_length = 0x100 # find free slot for game (card format is limited to 31 games) free_slot = -1 for i in range(0, int(position_header_length / 0x8) - 1): position = struct.unpack("ii", self.diskfp.read(0x8)) if position == (-1, -1): free_slot = i break if free_slot == -1: raise Exception( "No free slot found. There can be a maximum of %d games on one card." % int(position_header_length / 0x8)) # seek to start of rom on sd-card self.diskfp.seek(start_block * 0x200) # open rom file romfp = open(rom, "rb") # get card specific data from template.txt serial = gamecard.ncsd_serial(romfp) sha1 = gamecard.ncch_sha1sum(romfp) template_data = titles.get_template(serial, sha1) if template_data: generated_template = False card_data = bytearray.fromhex(template_data['card_data']) else: generated_template = True logging.warning( "Automagically creating sky3ds header (this will fail, lol)") card_data = bytearray() # card crypto + card id + eeprom id(?) romfp.seek(0x1244) card_data += romfp.read(0x4) romfp.seek(0x1240) card_data += romfp.read(0x4) romfp.seek(0x1248) card_data += romfp.read(0x4) # CRC16 of NCCH header? romfp.seek(0x1000) crc16 = titles.crc16(bytearray(romfp.read(0x200))) card_data += bytearray(struct.pack("H", crc16)[::-1]) card_data += bytearray( struct.pack("H", (crc16 << 16 | crc16 ^ 0xffff) & 0xFFFF)[::-1]) # CTRIMAGE + zero-padding card_data += bytearray("CTRIMAGE", "ascii") card_data += bytearray([0x00] * 0x8) # ?!?!?!? card_data += bytearray([0x00] * 0x10) # zero-padding card_data += bytearray([0x00] * 0x10) # unique id romfp.seek(0x1200) card_data += romfp.read(0x40) # name romfp.seek(0x1150) card_data += romfp.read(0x10) card_data += bytearray([0x00] * 0xf0) # zero-padding card_data += bytearray([0x00] * 0x80) if use_header_bin: header_bin = os.path.join(data_dir, 'header.bin') if os.path.exists(header_bin): logging.info( "Injecting headers from header.bin instead of template.txt!" ) try: header_bin_fp = open(header_bin, "rb") rom_header = bytearray(header_bin_fp.read(0x44)) header_bin_fp.close() for byte in range(0x40): card_data[0x40 + byte] = rom_header[byte] except: raise Exception( "Error: Can't inject headers from header.bin") elif rom[-4:] == ".3dz": romfp.seek(0x1200) rom_header = romfp.read(0x44) if rom_header[0x00:0x10] != bytearray([0xff] * 0x10): logging.info( "Injecting headers from 3dz file instead of template.txt!") for byte in range(0x40): card_data[0x40 + byte] = rom_header[byte] for byte in range(0x4): card_data[0x4 + byte] = rom_header[0x40 + byte] # recalculate checksum for sky3ds header (important after injection from 3dz or header.bin) crc16 = titles.crc16(card_data[:-2]) card_data[-2] = (crc16 & 0xFF00) >> 8 card_data[-1] = (crc16 & 0x00FF) if len(card_data) != 0x200: raise Exception("Invalid template data") if verbose: template = "\nUsed template:\n" template += "** : %s\n" % card_data[0x80:0x90].decode("ascii") template += "SHA1: %s\n" % gamecard.ncch_sha1sum(romfp).upper() for i in range(0, 0x20): line = "" for j in range(0, 0x10): line += ("%.2x " % card_data[i * 0x10 + j]).upper() template += line + "\n" template += "\n" logging.info(template) # write rom (with fancy progressbar!) romfp.seek(0) try: if not silent and not progress: progress = ProgressBar( widgets=[Percentage(), Bar(), FileTransferSpeed()], maxval=rom_size).start() except: pass written = 0 while written < rom_size: chunk = romfp.read(1024 * 1024 * 8) self.diskfp.write(chunk) os.fsync(self.diskfp) written = written + len(chunk) try: if not silent: progress.update(written) except: pass try: if not silent: progress.finish() except: pass # seek to slot header and write position + block-count of rom self.diskfp.seek(free_slot * 0x8) self.diskfp.write(struct.pack("ii", start_block, rom_blocks)) # add savegame slot self.diskfp.seek(0x100000 * (1 + len(self.rom_list))) self.diskfp.write(bytearray([0xff] * 0x100000)) self.diskfp.seek(start_block * 0x200 + 0x1400) self.diskfp.write(card_data) # cleanup romfp.close() os.fsync(self.diskfp) self.update_rom_list()
def write_rom(self, rom, silent=False, progress=None, use_header_bin=False, verbose=False): """Write rom to sdcard. Roms are stored at the position marked in the position headers (starting at 0x2000000). This code first looks for a free block with enough space to hold the specified rom, then continues to write the data to that location. After successful writing the savegame slot for this game is filled with zero. The last thing to do is to find the game in template.txt and write the data from that file to offset 0x1400 inside the rom on sdcard. Keyword Arguments: rom -- path to rom file""" self.fail_on_non_sky3ds() # follow symlink rom = os.path.realpath(rom) # get rom size and calculate block count rom_size = os.path.getsize(rom) rom_blocks = int(rom_size / 0x200) # get free blocks on sd-card and search for a block big enough for the rom start_block = 0 for free_block in self.free_blocks[::-1]: if free_block[1] >= rom_blocks: start_block = free_block[0] break if start_block == 0: raise Exception("Not enough free continous blocks") self.diskfp.seek(0) position_header_length = 0x100 # find free slot for game (card format is limited to 31 games) free_slot = -1 for i in range(0, int(position_header_length / 0x8) - 1): position = struct.unpack("ii", self.diskfp.read(0x8)) if position == (-1, -1): free_slot = i break if free_slot == -1: raise Exception("No free slot found. There can be a maximum of %d games on one card." % int(position_header_length / 0x8)) # seek to start of rom on sd-card self.diskfp.seek(start_block * 0x200) # open rom file romfp = open(rom, "rb") # get card specific data from template.txt serial = gamecard.ncsd_serial(romfp) sha1 = gamecard.ncch_sha1sum(romfp) template_data = titles.get_template(serial, sha1) if template_data: generated_template = False card_data = bytearray.fromhex(template_data['card_data']) else: generated_template = True logging.warning("Automagically creating sky3ds header (this will fail, lol)") card_data = bytearray() # card crypto + card id + eeprom id(?) romfp.seek(0x1244) card_data += romfp.read(0x4) romfp.seek(0x1240) card_data += romfp.read(0x4) romfp.seek(0x1248) card_data += romfp.read(0x4) # CRC16 of NCCH header? romfp.seek(0x1000) crc16 = titles.crc16(bytearray(romfp.read(0x200))) card_data += bytearray(struct.pack("H", crc16)[::-1]) card_data += bytearray(struct.pack("H", (crc16 << 16 | crc16 ^ 0xffff) & 0xFFFF)[::-1]) # CTRIMAGE + zero-padding card_data += bytearray("CTRIMAGE", "ascii") card_data += bytearray([0x00]*0x8) # ?!?!?!? card_data += bytearray([0x00] * 0x10) # zero-padding card_data += bytearray([0x00] * 0x10) # unique id romfp.seek(0x1200) card_data += romfp.read(0x40) # name romfp.seek(0x1150) card_data += romfp.read(0x10) card_data += bytearray([0x00] * 0xf0) # zero-padding card_data += bytearray([0x00] * 0x80) if use_header_bin: header_bin = os.path.join(data_dir,'header.bin') if os.path.exists(header_bin): logging.info("Injecting headers from header.bin instead of template.txt!") try: header_bin_fp = open(header_bin, "rb") rom_header = bytearray(header_bin_fp.read(0x44)) header_bin_fp.close() for byte in range(0x40): card_data[0x40+byte] = rom_header[byte] except: raise Exception("Error: Can't inject headers from header.bin") elif rom[-4:] == ".3dz": romfp.seek(0x1200) rom_header = romfp.read(0x44) if rom_header[0x00:0x10] != bytearray([0xff]*0x10): logging.info("Injecting headers from 3dz file instead of template.txt!") for byte in range(0x40): card_data[0x40+byte] = rom_header[byte] for byte in range(0x4): card_data[0x4+byte] = rom_header[0x40+byte] # recalculate checksum for sky3ds header (important after injection from 3dz or header.bin) crc16 = titles.crc16(card_data[:-2]) card_data[-2] = (crc16 & 0xFF00) >> 8 card_data[-1] = (crc16 & 0x00FF) if len(card_data) != 0x200: raise Exception("Invalid template data") if verbose: template = "\nUsed template:\n" template += "** : %s\n" % card_data[0x80:0x90].decode("ascii") template += "SHA1: %s\n" % gamecard.ncch_sha1sum(romfp).upper() for i in range(0, 0x20): line = "" for j in range(0, 0x10): line += ("%.2x " % card_data[i*0x10+j]).upper() template += line + "\n" template += "\n" logging.info(template) # write rom (with fancy progressbar!) romfp.seek(0) try: if not silent and not progress: progress = ProgressBar(widgets=[Percentage(), Bar(), FileTransferSpeed()], maxval=rom_size).start() except: pass written = 0 while written < rom_size: chunk = romfp.read(1024*1024*8) self.diskfp.write(chunk) os.fsync(self.diskfp) written = written + len(chunk) try: if not silent: progress.update(written) except: pass try: if not silent: progress.finish() except: pass # seek to slot header and write position + block-count of rom self.diskfp.seek(free_slot * 0x8) self.diskfp.write(struct.pack("ii", start_block, rom_blocks)) # add savegame slot self.diskfp.seek(0x100000 * (1 + len(self.rom_list))) self.diskfp.write(bytearray([0xff]*0x100000)) self.diskfp.seek(start_block * 0x200 + 0x1400) self.diskfp.write(card_data) # cleanup romfp.close() os.fsync(self.diskfp) self.update_rom_list()