Ejemplo n.º 1
0
    def write_savegame(self, savefile):
        """Restore savegame from file to sdcard

        This function (re)stores a given savegame file to the corresponding
        location on sdcard.
        Since the savegame backup has the product-code inside this function
        doesn't need any further arguments.

        It first opens the savegame backup, validates the header and retrieves
        the product-code to use find_game(product_code) for looking up the slot
        of the corresponding game.
        The savegame file itself stores the information wether it's Card1 or
        Card2, but i found it easier to just read the ncsd-header of the game.

        For Card1 savegames the file gets written to the corresponding slot
        in the region of Card1-savegames.

        For Card2 savegames it gets written to the writable_address offset of
        the game."""

        self.fail_on_non_sky3ds()

        savegamefp = open(savefile, "rb")

        # CTR_SAVE
        ctr_save = savegamefp.read(0x8)
        if ctr_save != b'CTR_SAVE':
            raise Exception("Not a valid savegame")

        # Product Code
        product_code = savegamefp.read(0xa).decode('ascii')
        slot, ncsd_header = self.find_game(product_code)
        if slot == None:
            raise Exception("Game not on disk")

        # Zero Padding (ignored)
        savegamefp.read(0x1)

        # Save Type (ignored, read directly from ncsd_header)
        savegamefp.read(0x1)

        # NAND save offset (ignored, read directly from ncsd_header)
        savegamefp.read(0x4)

        # Unique ID (+ recalculate crc)
        self.diskfp.seek(self.rom_list[slot][1] + 0x1440)
        self.diskfp.write(savegamefp.read(0x40))

        self.diskfp.seek(self.rom_list[slot][1] + 0x1400)
        card_data = self.diskfp.read(0x200)
        crc16 = titles.crc16(bytearray(card_data[:-2]))
        self.diskfp.seek(self.rom_list[slot][1] + 0x1400 + 0x200 - 0x2)
        self.diskfp.write(bytearray([(crc16 & 0xFF00) >> 8, crc16 & 0x00FF]))

        # Savegame data
        if ncsd_header['card_type'] == 'Card1':
            self.diskfp.seek(0x100000 * (slot + 1))
            self.diskfp.write(savegamefp.read(0x100000))
            os.fsync(self.diskfp)
        elif ncsd_header['card_type'] == 'Card2':
            self.diskfp.seek(self.rom_list[slot][1] +
                             ncsd_header['writable_address'])
            for i in range(0, 10):
                self.diskfp.write(savegamefp.read(0x100000))
                os.fsync(self.diskfp)

        savegamefp.close()
Ejemplo n.º 2
0
    def write_savegame(self, savefile):
        """Restore savegame from file to sdcard

        This function (re)stores a given savegame file to the corresponding
        location on sdcard.
        Since the savegame backup has the product-code inside this function
        doesn't need any further arguments.

        It first opens the savegame backup, validates the header and retrieves
        the product-code to use find_game(product_code) for looking up the slot
        of the corresponding game.
        The savegame file itself stores the information wether it's Card1 or
        Card2, but i found it easier to just read the ncsd-header of the game.

        For Card1 savegames the file gets written to the corresponding slot
        in the region of Card1-savegames.

        For Card2 savegames it gets written to the writable_address offset of
        the game."""

        self.fail_on_non_sky3ds()

        savegamefp = open(savefile, "rb")

        # CTR_SAVE
        ctr_save = savegamefp.read(0x8)
        if ctr_save != b'CTR_SAVE':
            raise Exception("Not a valid savegame")

        # Product Code
        product_code = savegamefp.read(0xa).decode('ascii')
        slot,ncsd_header = self.find_game(product_code)
        if slot == None:
            raise Exception("Game not on disk")

        # Zero Padding (ignored)
        savegamefp.read(0x1)

        # Save Type (ignored, read directly from ncsd_header)
        savegamefp.read(0x1)

        # NAND save offset (ignored, read directly from ncsd_header)
        savegamefp.read(0x4)

        # Unique ID (+ recalculate crc)
        self.diskfp.seek(self.rom_list[slot][1] + 0x1440)
        self.diskfp.write(savegamefp.read(0x40))

        self.diskfp.seek(self.rom_list[slot][1] + 0x1400)
        card_data = self.diskfp.read(0x200)
        crc16 = titles.crc16(bytearray(card_data[:-2]))
        self.diskfp.seek(self.rom_list[slot][1] + 0x1400 + 0x200 - 0x2)
        self.diskfp.write(bytearray([(crc16 & 0xFF00) >> 8, crc16 & 0x00FF]))

        # Savegame data
        if ncsd_header['card_type'] == 'Card1':
            self.diskfp.seek(0x100000 * (slot + 1))
            self.diskfp.write(savegamefp.read(0x100000))
            os.fsync(self.diskfp)
        elif ncsd_header['card_type'] == 'Card2':
            self.diskfp.seek(self.rom_list[slot][1] + ncsd_header['writable_address'])
            for i in range(0, 10):
                self.diskfp.write(savegamefp.read(0x100000))
                os.fsync(self.diskfp)

        savegamefp.close()
Ejemplo n.º 3
0
    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()
Ejemplo n.º 4
0
    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()