Example #1
0
def _add_wad_metadata(rom: FileROM, metadata: 'Metadata'):
	header = rom.read(amount=0x40)
	header_size = int.from_bytes(header[0:4], 'big')
	#WAD type: 4-8
	cert_chain_size = int.from_bytes(header[8:12], 'big')
	#Reserved: 12-16
	ticket_size = int.from_bytes(header[16:20], 'big')
	tmd_size = int.from_bytes(header[20:24], 'big')
	data_size = int.from_bytes(header[24:28], 'big')
	footer_size = int.from_bytes(header[28:32], 'big')

	#All blocks are stored in that order: header > cert chain > ticket > TMD > data; aligned to multiple of 64 bytes

	cert_chain_offset = _round_up_to_multiple(header_size, 64)
	ticket_offset = cert_chain_offset + _round_up_to_multiple(cert_chain_size, 64)
	tmd_offset = ticket_offset + _round_up_to_multiple(ticket_size, 64)

	real_tmd_size = _round_up_to_multiple(tmd_size, 64)
	if real_tmd_size >= 768:
		tmd = rom.read(seek_to=tmd_offset, amount=real_tmd_size)
		_parse_tmd(metadata, tmd)

	data_offset = tmd_offset + _round_up_to_multiple(tmd_size, 64)
	footer_offset = data_offset + _round_up_to_multiple(data_size, 64)
	#Dolphin suggests that this is opening.bnr actually
	footer = rom.read(seek_to=footer_offset, amount=_round_up_to_multiple(footer_size, 64))
	_parse_opening_bnr(metadata, footer)
Example #2
0
def add_tgc_metadata(rom: FileROM, metadata: Metadata):
	tgc_header = rom.read(0, 60) #Actually it is bigger than that
	magic = tgc_header[0:4]
	if magic != b'\xae\x0f8\xa2':
		if main_config.debug:
			print('Hmm', rom.path, 'is .tgc but TGC magic is invalid', magic)
		return
	tgc_header_size = int.from_bytes(tgc_header[8:12], 'big')
	fst_real_offset = int.from_bytes(tgc_header[16:20], 'big')
	fst_real_size = int.from_bytes(tgc_header[20:24], 'big')
	#apploader_real_offset = int.from_bytes(tgc_header[28:32], 'big')
	#apploader_real_size = int.from_bytes(tgc_header[32:36], 'big')
	#These fields are "?" in YAGD, but Dolphin uses them, so they probably know what they're doing and this is probably the right way
	real_offset = int.from_bytes(tgc_header[36:40], 'big')
	virtual_offset = int.from_bytes(tgc_header[52:56], 'big')
	file_offset = real_offset - virtual_offset

	header = rom.read(tgc_header_size, 0x460)

	_add_gamecube_disc_metadata(rom, metadata, header, {
		#'apploader offset': apploader_real_offset,
		#'apploader size': apploader_real_size,
		'fst offset': fst_real_offset,
		'fst size': fst_real_size,
		'file offset': file_offset,
	})
Example #3
0
def parse_ncsd(rom: FileROM, metadata: 'Metadata'):
    #Assuming CCI (.3ds) here
    #Skip over SHA-256 signature and magic
    header = rom.read(seek_to=0x104, amount=0x100)
    #ROM size: 0-4
    #Media ID: 4-12
    #Partition types: 12-20
    #Partition crypt types: 20-28
    partition_offsets = []
    partition_lengths = []

    for i in range(0, 8):
        partition_offset = int.from_bytes(
            header[28 + (i * 4):28 + (i * 4) + 4], 'little') * media_unit
        partition_length = int.from_bytes(
            header[32 + (i * 4):32 + (i * 4) + 4], 'little') * media_unit
        partition_offsets.append(partition_offset)
        partition_lengths.append(partition_length)
    if partition_lengths[0]:
        #Ignore lengths, we're not just gonna read the whole NCCH in one block because that would use a heckton of memory and whatnot
        _parse_ncch(rom, metadata, partition_offsets[0])
    #Partition 1: Electronic manual
    #Partition 2: Download Play child
    #Partition 6: New 3DS update data
    #Partition 7: Update data
    card_info_header = rom.read(seek_to=0x200, amount=0x314)
    card2_writeable_address = int.from_bytes(card_info_header[:4], 'little')
    if card2_writeable_address != 0xffffffff:
        metadata.save_type = SaveType.Cart
    metadata.specific_info['Title Version'] = int.from_bytes(
        card_info_header[0x210:0x212], 'little')
    metadata.specific_info['Card Version'] = int.from_bytes(
        card_info_header[0x212:0x214], 'little')
def just_read_the_wia_rvz_header_for_now(rom: FileROM, metadata: Metadata):
	#I'll get around to it I swear
	wia_header = rom.read(amount=0x48)
	wia_disc_struct_size = int.from_bytes(wia_header[12:16], 'big')
	wia_disc_struct = rom.read(seek_to=0x48, amount=wia_disc_struct_size)
	disc_header = wia_disc_struct[16:128]
	add_gamecube_wii_disc_metadata(rom, metadata, disc_header)
Example #5
0
def add_fds_metadata(rom: FileROM, metadata: Metadata):
	if _nes_config and _nes_config.options.get('set_fds_as_different_platform'):
		metadata.platform = 'FDS'

	header = rom.read(amount=56)
	if header[:4] == b'FDS\x1a':
		metadata.specific_info['Headered?'] = True
		metadata.specific_info['Header Format'] = 'fwNES'
		rom.header_length_for_crc_calculation = 16
		header = rom.read(seek_to=16, amount=56)
	else:
		metadata.specific_info['Headered?'] = False

	licensee_code = f'{header[15]:02X}'
	if licensee_code in _nintendo_licensee_codes:
		metadata.publisher = _nintendo_licensee_codes[licensee_code]

	metadata.specific_info['Revision'] = header[20]
	#Uses Showa years (hence 1925), in theory... but then some disks (notably Zelda) seem to use 19xx years, as it has an actual value of 0x86 which results in it being Showa 86 = 2011, but it should be [Feb 21] 1986, so... hmm
	year = decode_bcd(header[31])
	#Showa 61 = 1986 when the FDS was released. Year > 99 wouldn't be valid BCD, so... I'll check back in 2025 to see if anyone's written homebrew for the FDS in that year and then I'll figure out what I'm doing. But homebrew right now seems to leave the year as 00 anyway, though
	year = 1925 + year if 61 <= year <= 99 else 1900 + year
	month = decode_bcd(header[32])
	day = decode_bcd(header[33])
	if not metadata.release_date:
		metadata.release_date = Date(year, month, day, True)
Example #6
0
def add_unif_metadata(rom: FileROM, metadata: Metadata):
	metadata.specific_info['Headered?'] = True
	metadata.specific_info['Header Format'] = 'UNIF'

	pos = 32
	size = rom.size
	while pos < size:
		chunk = rom.read(amount=8, seek_to=pos)
		chunk_type = chunk[0:4].decode('ascii', errors='ignore')
		chunk_length = int.from_bytes(chunk[4:8], 'little')	
		
		chunk_data = rom.read(amount=chunk_length, seek_to=pos+8)
		parse_unif_chunk(metadata, chunk_type, chunk_data)

		pos += 8 + chunk_length
Example #7
0
def _parse_smdh(rom: FileROM,
                metadata: 'Metadata',
                offset: int = 0,
                length: int = -1):
    metadata.specific_info['Has SMDH?'] = True
    #At this point it's fine to just read in the whole thing
    smdh = rom.read(seek_to=offset, amount=length)
    _parse_smdh_data(metadata, smdh)
Example #8
0
def add_fst_info(rom: FileROM, metadata: Metadata, fst_offset: int, fst_size: int, offset: int=0):
	if fst_offset and fst_size and fst_size < (128 * 1024 * 1024):
		fst = rom.read(fst_offset, fst_size)
		number_of_fst_entries = int.from_bytes(fst[8:12], 'big')
		if fst_size < (number_of_fst_entries * 12):
			if main_config.debug:
				print('Invalid FST in', rom.path, ':', fst_size, '<', number_of_fst_entries * 12)
			return
		string_table = fst[number_of_fst_entries * 12:]
		for i in range(1, number_of_fst_entries):
			entry = fst[i * 12: (i * 12) + 12]
			if entry[0] != 0:
				continue
			offset_into_string_table = int.from_bytes(entry[1:4], 'big')
			#Actually it's a null terminated string but we only care about the one file, so cbf finding a null, I'll just check for the expected length
			banner_name = string_table[offset_into_string_table:offset_into_string_table+len('opening.bnr')]
			if banner_name == b'opening.bnr':
				file_offset = int.from_bytes(entry[4:8], 'big') + offset
				file_length = int.from_bytes(entry[8:12], 'big')
				banner = rom.read(file_offset, file_length)
				add_banner_info(rom, metadata, banner)
Example #9
0
def parse_3dsx(rom: FileROM, metadata: 'Metadata'):
    header = rom.read(amount=0x20)
    header_size = int.from_bytes(header[4:6], 'little')
    has_extended_header = header_size > 32

    look_for_smdh_file = True
    if has_extended_header:
        extended_header = rom.read(seek_to=0x20, amount=12)
        smdh_offset = int.from_bytes(extended_header[0:4], 'little')
        smdh_size = int.from_bytes(extended_header[4:8], 'little')

        if smdh_size:
            look_for_smdh_file = False
            _parse_smdh(rom, metadata, smdh_offset, smdh_size)

    if look_for_smdh_file:
        smdh_name = rom.path.with_suffix('.smdh')
        try:
            with smdh_name.open('rb') as smdh_file:
                _parse_smdh_data(metadata, smdh_file.read())
        except FileNotFoundError:
            pass
Example #10
0
def _parse_exefs(rom: FileROM, metadata: 'Metadata', offset: int):
    header = rom.read(seek_to=offset, amount=0x200)
    for i in range(0, 10):
        try:
            filename = header[(i * 16):(i * 16) +
                              8].decode('ascii').rstrip('\x00')
        except UnicodeDecodeError:
            continue
        file_offset = int.from_bytes(header[(i * 16) + 8:(i * 16) + 8 + 4],
                                     'little') + 0x200 + offset
        file_length = int.from_bytes(header[(i * 16) + 12:(i * 16) + 12 + 4],
                                     'little')
        if filename == 'icon':
            _parse_smdh(rom, metadata, file_offset, file_length)
Example #11
0
def add_dreamcast_rom_info(rom: FileROM, metadata: Metadata):
    if rom.extension == 'gdi':
        data = rom.read().decode('utf8', errors='backslashreplace')
        for line in data.splitlines():
            match = _gdi_regex.match(line)
            if match:
                track_number = int(match['trackNumber'])
                #is_data = match['type'] == '4'
                sector_size = int(match['sectorSize'])
                filename = match['name_unquoted'] if match[
                    'name_unquoted'] else match['name']
                #print(game.rom.path, track_number, is_data, sector_size, filename)
                if track_number == 3:
                    full_name = Path(filename) if filename.startswith(
                        '/') else rom.path.parent.joinpath(filename)
                    add_info_from_main_track(metadata, full_name, sector_size)
Example #12
0
def _parse_banner(rom: FileROM, metadata: 'Metadata', header: bytes,
                  is_dsi: bool, banner_offset: int):
    #The extended part of the banner if is_dsi contains animated icon frames, so we don't really need it
    banner_size = int.from_bytes(header[0x208:0x20c],
                                 'little') if is_dsi else 0xA00
    banner = rom.read(seek_to=banner_offset, amount=banner_size)
    version = int.from_bytes(banner[0:2], 'little')
    metadata.specific_info['Banner Version'] = version
    #2 = has Chinese, 3 = has Korean, 0x103, has DSi stuff

    if version in {1, 2, 3, 0x103}:
        banner_titles = {}
        banner_languages = {
            0: 'Japanese',
            1: 'English',
            2: 'French',
            3: 'German',
            4: 'Italian',
            5: 'Spanish',
            6: 'Chinese',  #Version >= 2
            7: 'Korean'  #Version >= 3
        }

        for i in range(7):
            try:
                banner_title = banner[0x240 + (i * 256):0x240 + (
                    i * 256) + 256].decode('utf-16le').rstrip('\0 \uffff')
                #if banner_title and not all([c == '\uffff' for c in banner_title]):
                if banner_title:
                    banner_titles[banner_languages[i]] = banner_title
            except (UnicodeDecodeError, IndexError):
                continue

        for lang, title in banner_titles.items():
            _add_banner_title_metadata(metadata, title, lang)

        if banner_titles:
            banner_title = banner_titles.get(
                'English', next(iter(banner_titles.values())))
            _add_banner_title_metadata(metadata, banner_title)

        if len(banner) >= 0x240:
            if have_pillow:
                icon_bitmap = banner[0x20:0x220]
                icon_palette = struct.unpack('H' * 16, banner[0x220:0x240])
                metadata.images['Icon'] = _decode_icon(icon_bitmap,
                                                       icon_palette)
Example #13
0
def _get_smd_header(rom: FileROM):
    #Just get the first block which is all that's needed for the header, otherwise this would be a lot more complicated (just something to keep in mind if you ever need to convert a whole-ass .smd ROM)
    block = rom.read(seek_to=512, amount=16384)

    buf = bytearray(16386)
    midpoint = 8192
    even = 1  #Hmm, maybe I have these the wrong way around
    odd = 2

    for i, b in enumerate(block):
        if i <= midpoint:
            buf[even] = b
            even += 2
        else:
            buf[odd] = b
            odd += 2

    return bytes(buf[0x100:0x200])
Example #14
0
def _parse_gbx_footer(rom: FileROM, metadata: 'Metadata'):
	footer = rom.read(seek_to=rom.size - 64, amount=64)
	if footer[60:64] != b'GBX!':
		if main_config.debug:
			print(rom.path, 'GBX footer is invalid, siggy is', footer[60:64])
		return
	if int.from_bytes(footer[48:52], 'big') != 64 or int.from_bytes(footer[52:56], 'big') != 1:
		if main_config.debug:
			print(rom.path, 'GBX has unsupported major version:', int.from_bytes(footer[52:56], 'big'), 'or size:', int.from_bytes(footer[48:52], 'big'))
		return
	#56:60 is minor version, which we expect to be 0, but it'd be okay if not

	original_mapper = metadata.specific_info.get('Mapper', 'None')
	metadata.specific_info['Stated Mapper'] = original_mapper
	new_mapper = _gbx_mappers.get(footer[0:4])
	if not new_mapper:
		if main_config.debug:
			print(rom.path, 'GBX has unknown spooky mapper:', footer[0:4])
		new_mapper = footer[0:4].decode()

	if new_mapper != original_mapper:
		#For now we're going to assume other emus don't actually do .gbx properly
		metadata.specific_info['Override Mapper?'] = True
		metadata.specific_info['Mapper'] = new_mapper
Example #15
0
def add_virtual_boy_rom_info(rom: FileROM, metadata: 'Metadata'):
	rom_size = rom.size
	header_start_position = rom_size - 544 #Wait wouldn't that make it a footer sorta
	header = rom.read(seek_to=header_start_position, amount=32)
	title = header[0:20].decode('shift_jis', errors='backslashreplace').rstrip('\0 ')
	if title:
		metadata.specific_info['Internal Title'] = title
	
	try:
		licensee_code = convert_alphanumeric(header[25:27])
		if licensee_code in nintendo_licensee_codes:
			metadata.publisher = nintendo_licensee_codes[licensee_code]
		elif licensee_code in unofficial_vb_publishers:
			metadata.publisher = unofficial_vb_publishers[licensee_code]
	except NotAlphanumericException:
		pass

	try:
		metadata.product_code = convert_alphanumeric(header[27:31])
	except NotAlphanumericException:
		pass
	#Can get country from product_code[3] if needed

	metadata.specific_info['Revision'] = header[31]
Example #16
0
def _parse_plain_region(rom: FileROM, metadata: 'Metadata', offset: int,
                        length: int):
    #Plain region stores the libraries used, at least for official games
    #See also: https://github.com/Zowayix/ROMniscience/wiki/3DS-libraries-used for research
    #Hmm… since I sort of abandoned ROMniscience I should put that somewhere else
    plain_region = rom.read(seek_to=offset, amount=length)
    libraries = (lib.decode('ascii', errors='backslashreplace')
                 for lib in plain_region.split(b'\x00') if lib)

    #TODO: If a game has an update which adds functionality identified by one of these library names, then that'll be a separate file, so it's like... how do we know that Super Smash Bros the .3ds file has amiibo support when Super Smash Bros 1.1.7 update data the .cxi is where it says that, because with and only with the update data it would support amiibos, etc; if that all makes sense
    #Unless like... I search ~/.local/share/citra-emu/sdmc/Nintendo 3DS for what update CIAs are installed and... aaaaaaaaaaaaaaaa
    for library in libraries:
        if library.startswith('[SDK+ISP:QRDec'):
            metadata.specific_info['Reads QR Codes?'] = True
        elif library.startswith('[SDK+ISP:QREnc'):
            metadata.specific_info['Makes QR Codes?'] = True
        elif library == '[SDK+NINTENDO:ExtraPad]':
            metadata.specific_info['Uses Circle Pad Pro?'] = True
            #ZL + ZR + right analog stick; New 3DS has these too but the extra controls there are internally represented as a Circle Pad Pro for compatibility so this all works out I think
            inbuilt_controller = cast(
                input_metadata.NormalController,
                cast(input_metadata.CombinedController, metadata.input_info.
                     input_options[0].inputs[0]).components[0])
            inbuilt_controller.analog_sticks += 1
            inbuilt_controller.shoulder_buttons += 2
        elif library == '[SDK+NINTENDO:Gyroscope]':
            metadata.specific_info['Uses Gyroscope?'] = True
            metadata.input_info.input_options[0].inputs.append(
                input_metadata.MotionControls())
        elif library == '[SDK+NINTENDO:IsRunOnSnake]':
            #There's also an IsRunOnSnakeForApplet found in some not-completely-sure-what-they-are builtin apps and amiibo Settings. Not sure if it does what I think it does
            metadata.specific_info['New 3DS Enhanced?'] = True
        elif library == '[SDK+NINTENDO:NFP]':
            metadata.specific_info['Uses Amiibo?'] = True
        elif library.startswith('[SDK+NINTENDO:CTRFaceLibrary-'):
            metadata.specific_info['Uses Miis?'] = True
Example #17
0
def _parse_ncch(rom: FileROM, metadata: 'Metadata', offset: int):
    #Skip over SHA-256 siggy and magic
    header = rom.read(seek_to=offset + 0x104, amount=0x100)
    #Content size: 0-4 (media unit)
    #Partition ID: 4-12
    try:
        maker = convert_alphanumeric(header[12:14])
        if maker in nintendo_licensee_codes:
            metadata.publisher = nintendo_licensee_codes[maker]
    except NotAlphanumericException:
        pass
    metadata.specific_info['NCCH Version'] = int.from_bytes(
        header[14:16], 'little')  #Always 2?
    #Something about a hash: 16-20
    #Program ID: 20-28
    #Reserved: 28-44
    #Logo region hash: 44-76
    try:
        product_code = header[76:86].decode('ascii')
        metadata.product_code = product_code
        #As usual, can get country and type from here, but it has more letters and as such you can also get category as well, or like... type 2 electric boogaloo. This also means we can't use convert_alphanumeric because it contains dashes, so I guess I need to fiddle with that method if I want to use it like that
        #(To be precise: P = retail/cart, N = digital only, M = DLC, T = demos, U = patches)
        try:
            metadata.specific_info[
                'Virtual Console Platform'] = _3DSVirtualConsolePlatform(
                    product_code[6])
        except ValueError:
            pass
        if len(product_code) == 10 and '\0' not in product_code:
            short_product_code = product_code[6:]
            add_info_from_tdb(_tdb, metadata, short_product_code)
            add_cover(metadata, short_product_code)
    except UnicodeDecodeError:
        pass
    #Extended header hash: 92-124
    #Extended header size: 124-128
    #Reserved: 128-132
    flags = header[132:140]
    is_data = (flags[5] & 1) > 0
    is_executable = (flags[5] & 2) > 0
    is_not_cxi = is_data and not is_executable
    metadata.specific_info['Is CXI?'] = not is_not_cxi
    #Is system update = flags[5] & 4
    #Is electronic manual = flags[5] & 8
    metadata.specific_info['Is Trial?'] = (flags[5] & 16) > 0
    #Is zero key encrypted = flags[7] & 1
    is_decrypted = (flags[7] & 4) > 0
    metadata.specific_info['Decrypted'] = is_decrypted

    plain_region_offset = (int.from_bytes(header[140:144], 'little') *
                           media_unit) + offset
    plain_region_length = (int.from_bytes(header[144:148], 'little') *
                           media_unit)
    #logo_region_offset = (int.from_bytes(header[148:152], 'little') * media_unit) + offset
    #logo_region_length = (int.from_bytes(header[152:156], 'little') * media_unit)
    exefs_offset = (int.from_bytes(header[156:160], 'little') *
                    media_unit) + offset
    exefs_length = (int.from_bytes(header[160:164], 'little') * media_unit)
    #romfs_offset = (int.from_bytes(header[172:176], 'little') * media_unit) + offset
    #romfs_length = (int.from_bytes(header[176:180], 'little') * media_unit)

    if plain_region_length:
        _parse_plain_region(rom, metadata, plain_region_offset,
                            plain_region_length)
    #Logo region: Stuff and things
    if exefs_length:
        _parse_exefs(rom, metadata, exefs_offset)
    #RomFS: Filesystem really

    if (not is_not_cxi) and is_decrypted:
        extended_header = rom.read(seek_to=offset + 0x200, amount=0x800)
        system_control_info = extended_header[0:0x200]
        #Access control info: 0x200:0x400
        #AccessDesc signature: 0x400:0x500
        #RSA-2048 public key: 0x500:0x600
        #Access control info 2: 0x600:0x800

        metadata.specific_info['Executable Name'] = system_control_info[
            0:8].decode('ascii', errors='ignore').rstrip('\0')
        #Reserved: 0x8:0xd
        #Flags (bit 0 = CompressExefsCode, bit 1 = SDApplication): 0xd
        #Remaster version: 0xe:0x10
        #Text code set info: 0x10:1c
        #Stack size: 0x1c:0x20
        #Read only code set info: 0x20:0x2c
        #Reserved: 0x2c:0x30
        #Data code set info: 0x30:0x3c
        #BSS size: 0x3c:0x40
        #Dependency module ID list: 0x40:0x1c0
        #SystemInfo: 0x1c0:0x200
        save_size = int.from_bytes(system_control_info[0x1c0:0x1c8], 'little')
        metadata.save_type = SaveType.Internal if save_size > 0 else SaveType.Nothing
Example #18
0
def add_ines_metadata(rom: FileROM, metadata: Metadata, header: bytes):
	metadata.specific_info['Headered?'] = True
	#Some emulators are okay with not having a header if they have something like an internal database, others are not.
	#Note that \x00 at the end instead of \x1a indicates this is actually Wii U VC, but it's still the same header format
	rom.header_length_for_crc_calculation = 16 #We use a custom software list matcher anyway, but we need to just chop the header off to find it in libretro-database
	prg_size = header[4]
	chr_size = header[5]

	flags = header[6]
	has_battery = (flags & 2) > 0
	metadata.save_type = SaveType.Cart if has_battery else SaveType.Nothing
	if (flags & 4) > 0:
		metadata.specific_info['Has iNES Trainer?'] = True
	mapper_lower_nibble = (flags & 0b1111_0000) >> 4

	more_flags = header[7]
	if (more_flags & 3) == 1:
		metadata.specific_info['Arcade System'] = 'VS Unisystem'
	elif (more_flags & 3) == 2:
		metadata.specific_info['Arcade System'] = 'PlayChoice-10'

	mapper_upper_nibble = more_flags & 0b1111_0000
	is_nes_2_0 = ((more_flags & 0b_00_00_11_00) >> 2) == 2
	if is_nes_2_0:
		metadata.specific_info['Header Format'] = 'NES 2.0'
		mapper_upper_upper_nibble = header[8] & 0b1111
		mapper = mapper_lower_nibble | mapper_upper_nibble | (mapper_upper_upper_nibble << 8)
		metadata.specific_info['Mapper Number'] = mapper
		if mapper in _ines_mappers:
			metadata.specific_info['Mapper'] = _ines_mappers[mapper]
		else:
			metadata.specific_info['Mapper'] = 'NES 2.0 Mapper %d' % mapper

		metadata.specific_info['Submapper'] = (header[8] & 0b1111_0000) >> 4
		
		prg_size_msb = ((header[9] & 0b1111) << 4)
		metadata.specific_info['PRG Size'] = (prg_size_msb | prg_size) * 16 * 1024 if prg_size_msb != 15 else (2 ** ((prg_size & 0b1111_1100) >> 2)) * (((prg_size & 0b11) * 2) + 1)
		chr_size_msb = (header[9] & 0b1111_0000)
		metadata.specific_info['CHR Size'] = (chr_size_msb | chr_size) * 8 * 1024 if chr_size_msb != 15 else (2 ** ((chr_size & 0b1111_1100) >> 2)) * (((chr_size & 0b11) * 2) + 1)

		#9/10: PRG/CHR RAM and NVRAM size

		cpu_ppu_timing = header[12] & 0b11
		if cpu_ppu_timing == 0:
			metadata.specific_info['TV Type'] = TVSystem.NTSC
		elif cpu_ppu_timing == 1:
			metadata.specific_info['TV Type'] = TVSystem.PAL
		elif cpu_ppu_timing == 2:
			metadata.specific_info['TV Type'] = TVSystem.Agnostic
		elif cpu_ppu_timing == 3:
			metadata.specific_info['Is Dendy?'] = True

		if (header[7] & 3) == 3:
			#If header[7] = 1, specifies VS System type
			metadata.specific_info['Extended Console Type'] = extended_console_types.get(header[13], header[13])
		if header[15]:
			default_expansion_device = default_expansion_devices.get(header[15], header[15])
			metadata.specific_info['Default Expansion Device'] = default_expansion_device
			if default_expansion_device == 1:
				metadata.specific_info['Peripheral'] = NESPeripheral.NormalController
			#42 = multicart also exists I guess but it doesn't mean much to us
	else:
		metadata.specific_info['Header Format'] = 'iNES'
		mapper = mapper_lower_nibble | mapper_upper_nibble
		metadata.specific_info['Mapper Number'] = mapper
		if mapper in _ines_mappers:
			metadata.specific_info['Mapper'] = _ines_mappers[mapper]
		else:
			metadata.specific_info['Mapper'] = 'iNES Mapper %d' % mapper

		metadata.specific_info['PRG Size'] = prg_size * 16 * 1024
		metadata.specific_info['CHR Size'] = chr_size * 8 * 1024
Example #19
0
def _add_wii_disc_metadata(rom: FileROM, metadata: 'Metadata'):
	wii_header = rom.read(0x40_000, 0xf000)

	game_partition_offset = None
	for i in range(4):
		partition_group = wii_header[8 * i: (8 * i) + 8]
		partition_count = int.from_bytes(partition_group[0:4], 'big')
		partition_table_entry_offset = int.from_bytes(partition_group[4:8], 'big') << 2
		for j in range(partition_count):
			seek_to = partition_table_entry_offset + (j * 8)
			partition_table_entry = rom.read(seek_to, 8)
			partition_offset = int.from_bytes(partition_table_entry[0:4], 'big') << 2
			partition_type = int.from_bytes(partition_table_entry[4:8], 'big')
			#if partition_type > 0xf:
			#	#SSBB Masterpiece partitions use ASCII title IDs here; realistically other partition types should be 0 (game) 1 (update) or 2 (channel)
			#	partition_type = partition_table_entry[4:8].decode('ascii', errors='backslashreplace')

			#Seemingly most games have an update partition at 0x50_000 and a game partition at 0xf_800_000. That's just an observation though and may not be 100% the case
			#print(rom.path, 'has partition type', partition_type, 'at', hex(partition_offset))
			if partition_type == 1:
				metadata.specific_info['Has Update Partition?'] = True
			elif partition_type == 0 and game_partition_offset is None:
				game_partition_offset = partition_offset

	common_key = None
	if _wii_config:
		common_key = _wii_config.options.get('common_key')
	if common_key:
		if game_partition_offset and have_pycrypto:
			game_partition_header = rom.read(game_partition_offset, 0x2c0)
			title_iv = game_partition_header[0x1dc:0x1e4] + (b'\x00' * 8)
			data_offset = int.from_bytes(game_partition_header[0x2b8:0x2bc], 'big') << 2

			master_key = bytes.fromhex(common_key)
			aes = AES.new(master_key, AES.MODE_CBC, title_iv)
			encrypted_key = game_partition_header[0x1bf:0x1cf]
			key = aes.decrypt(encrypted_key)

			chunk_offset = game_partition_offset + data_offset # + (index * 0x8000) but we only need 1st chunk (0x7c00 bytes of encrypted data each chunk)
			chunk = rom.read(chunk_offset, 0x8000)
			chunk_iv = chunk[0x3d0:0x3e0]
			aes = AES.new(key, AES.MODE_CBC, chunk_iv)
			decrypted_chunk = aes.decrypt(chunk[0x400:])

			#TODO: Try and read filesystem to see if there is an opening.bnr in there (should be)

			try:
				apploader_date = decrypted_chunk[0x2440:0x2450].decode('ascii').rstrip('\0')
				try:
					d = datetime.strptime(apploader_date, '%Y/%m/%d')
					metadata.specific_info['Build Date'] = Date(d.year, d.month, d.day)
					guessed = Date(d.year, d.month, d.day, True)
					if guessed.is_better_than(metadata.release_date):
						metadata.release_date = guessed
				except ValueError:
					pass
			except UnicodeDecodeError:
				pass

	#Unused (presumably would be region-related stuff): 0xe004:0xe010
	region_code = int.from_bytes(wii_header[0xe000:0xe004], 'big')
	try:
		metadata.specific_info['Region Code'] = NintendoDiscRegion(region_code)
	except ValueError:
		pass
	parse_ratings(metadata, wii_header[0xe010:0xe020])