Example #1
0
def _add_info_from_ds_header(rom: FileROM, metadata: 'Metadata',
                             header: bytes):
    if header[0:4] == b'.\0\0\xea':
        metadata.specific_info['PassMe?'] = True
    else:
        internal_title = header[0:12].decode(
            'ascii', errors='backslashreplace').rstrip('\0')
        if internal_title:
            metadata.specific_info['Internal Title'] = internal_title

        try:
            product_code = convert_alphanumeric(header[12:16])
            metadata.product_code = product_code
            add_info_from_tdb(_tdb, metadata, product_code)
            _add_cover(metadata, product_code)

        except NotAlphanumericException:
            pass

        try:
            if not metadata.publisher:
                licensee_code = convert_alphanumeric(header[16:18])
                if licensee_code in _nintendo_licensee_codes:
                    metadata.publisher = _nintendo_licensee_codes[
                        licensee_code]
        except NotAlphanumericException:
            pass

    is_dsi = False
    unit_code = header[18]
    if unit_code == 0:
        metadata.specific_info['DSi Enhanced?'] = False
    elif unit_code == 2:
        is_dsi = True
        metadata.specific_info['DSi Enhanced?'] = True
    elif unit_code == 3:
        is_dsi = True
        metadata.platform = "DSi"

    if is_dsi:
        region_flags = int.from_bytes(header[0x1b0:0x1b4], 'little')
        if region_flags < 0xffff0000:
            #If they're set any higher than this, it's region free
            #GBATEK says region free is 0xffffffff specifically but Pokemon gen 5 is 0xffffffef so who knows
            #Although either way, it doesn't imply regions is world, it just means it'll work worldwide, so like... ehh... regions is a weird metadata field tbh
            metadata.regions = _parse_dsi_region_flags(region_flags)
        parse_ratings(metadata, header[0x2f0:0x300], True, False)
    else:
        region = header[29]
        if region == 0x40:
            metadata.regions = {regions_by_name['Korea']}
        elif region == 0x80:
            metadata.regions = {regions_by_name['China']}
            metadata.specific_info['Is iQue?'] = True
        #If 0, could be anywhere else
    metadata.specific_info['Revision'] = header[30]

    banner_offset = int.from_bytes(header[0x68:0x6C], 'little')
    if banner_offset:
        _parse_banner(rom, metadata, header, is_dsi, banner_offset)
def add_gamecube_wii_disc_metadata(rom: FileROM, metadata: Metadata, header: bytes):
	internal_title = header[32:128]
	metadata.specific_info['Internal Title'] = internal_title.decode('ascii', errors='backslashreplace').rstrip('\0 ')
	if internal_title[:28] == b'GAMECUBE HOMEBREW BOOTLOADER':
		return

	product_code = None
	try:
		product_code = convert_alphanumeric(header[:4])
	except NotAlphanumericException:
		pass

	publisher = None
	licensee_code = None
	try:
		licensee_code = convert_alphanumeric(header[4:6])
		publisher = nintendo_licensee_codes.get(licensee_code)
	except NotAlphanumericException:
		pass

	if not (product_code == 'RELS' and licensee_code == 'AB'):
		# This is found on a few prototype discs, it's not valid
		metadata.product_code = product_code
		metadata.publisher = publisher
		if product_code and licensee_code:
			add_info_from_tdb(_tdb, metadata, product_code + licensee_code)
			add_cover(metadata, product_code, licensee_code)

	disc_number = header[6] + 1
	if disc_number:
		metadata.disc_number = disc_number

	metadata.specific_info['Revision'] = header[7]

	#Audio streaming: header[8] > 1
	#Audio streaming buffer size: header[9]
	#Unused: 10-24

	is_wii = header[0x18:0x1c] == b']\x1c\x9e\xa3'
	is_gamecube = header[0x1c:0x20] == b'\xc23\x9f='
	# Is this ever set to both? In theory no, but... hmm

	if not is_wii and not is_gamecube:
		metadata.specific_info['No Disc Magic?'] = True
	elif main_config.debug:
		if metadata.platform == 'Wii' and not is_wii:
			print(rom.path, 'lacks Wii disc magic')
		if metadata.platform == 'GameCube' and not is_gamecube:
			print(rom.path, 'lacks GameCube disc magic')
Example #3
0
def _parse_satellaview_header(rom: 'FileROM',
                              base_offset: int) -> Mapping[str, Any]:
    #TODO Use namedtuple/dataclass
    header = rom.read(seek_to=base_offset, amount=0xe0)
    metadata: dict[str, Any] = {}

    try:
        publisher = convert_alphanumeric(header[0xb0:0xb2])
        metadata['Publisher'] = publisher
    except NotAlphanumericException as nae:
        raise BadSNESHeaderException("Publisher not alphanumeric") from nae

    try:
        title = header[0xc0:0xd0].decode('shift_jis')
        metadata['Title'] = title
    except UnicodeDecodeError as ude:
        raise BadSNESHeaderException(
            'Title not ASCII or Shift-JIS: %s' % header[0xc0:0xd0].decode(
                'shift_jis', errors='backslashreplace')) from ude

    month = (header[0xd6] & 0b_1111_0000) >> 4
    day = (header[0xd7] & 0b_1111_1000) >> 3
    if month == 0 or month > 12:
        raise BadSNESHeaderException('Month not valid: %d' % month)
    if day > 31:
        raise BadSNESHeaderException('Day not valid: %d' % day)
    metadata['Month'] = calendar.month_name[month]
    metadata['Day'] = day

    rom_layout = header[0xd8]
    if rom_layout not in _rom_layouts:
        raise BadSNESHeaderException('ROM layout is weird: %d' % rom_layout)
    metadata['ROM layout'] = _rom_layouts[rom_layout]

    return metadata
Example #4
0
def _parse_gameboy_header(metadata: 'Metadata', header: bytes):
	nintendo_logo = header[4:0x34]
	nintendo_logo_valid = crc32(nintendo_logo) == _nintendo_logo_crc32
	metadata.specific_info['Nintendo Logo Valid'] = nintendo_logo_valid
	
	title = header[0x34:0x44]
	cgb_flag = title[15]
	title_length = 16
	try:
		metadata.specific_info['Is Colour?'] = GameBoyColourFlag(cgb_flag)
		title_length = 15
	except ValueError:
		#On older carts, this would just be the last character of the title, so it would be some random value
		#Anyway, that would logically mean it is not in colour
		metadata.specific_info['Is Colour?'] = GameBoyColourFlag.No
	#On newer games, product code is at title[11:15], the tricky part is what exactly is a newer game and what isn't, because if the product code isn't there then those characters are just the last 4 characters of the title. It seems that it's always there on GBC exclusives, and _maybe_ there on GBC-enhanced games. And that's only for officially licensed stuff of course.
	#Well, might as well try that. If it's junk, we're looking up the software list later for the proper serial anyway.
	if cgb_flag == 0xc0:
		try:
			metadata.product_code = title[11:15].decode('ascii').rstrip('\0')
			title_length = 11
		except UnicodeDecodeError:
			pass
	else:
		#Things end in null chars, so if there's null chars in the middle, that indicates if nothing else that the title ends there. If there is then 4 chars left over, that would probably be the product code
		maybe_title_and_serial = re.split(b'\0+', title[:title_length].rstrip(b'\0'))
		if len(maybe_title_and_serial) == 2:
			title_length = len(maybe_title_and_serial[0])
			if len(maybe_title_and_serial[1]) == 4:
				metadata.product_code = maybe_title_and_serial[1].decode('ascii').rstrip('\0')

	#Might as well add that to the info. I thiiink it's just ASCII and not Shift-JIS
	metadata.specific_info['Internal Title'] = title[:title_length].decode('ascii', errors='backslashreplace').rstrip('\0')
	
	metadata.specific_info['SGB Enhanced?'] = header[0x46] == 3
	if header[0x47] in game_boy_mappers:
		mapper = game_boy_mappers[header[0x47]]
		metadata.specific_info['Mapper'] = mapper.name
		metadata.save_type = SaveType.Cart if mapper.has_battery else SaveType.Nothing
		metadata.specific_info['Force Feedback?'] = mapper.has_rumble
		metadata.specific_info['Has RTC?'] = mapper.has_rtc
		if mapper.has_accelerometer:
			metadata.input_info.input_options[0].inputs.append(input_metadata.MotionControls())

	metadata.specific_info['Destination Code'] = header[0x4a]
	#0 means Japan and 1 means not Japan. Not sure how reliable that is.
	licensee_code_int = header[0x4b]
	if licensee_code_int == 0x33:
		try:
			licensee_code = convert_alphanumeric(header[0x44:0x46])
			if licensee_code in _nintendo_licensee_codes:
				metadata.publisher = _nintendo_licensee_codes[licensee_code]
		except NotAlphanumericException:
			pass
	else:
		licensee_code = '{:02X}'.format(licensee_code_int)
		if licensee_code in _nintendo_licensee_codes:
			metadata.publisher = _nintendo_licensee_codes[licensee_code]
	metadata.specific_info['Revision'] = header[0x4c]
Example #5
0
def add_pokemini_rom_file_info(rom: 'FileROM', metadata: 'Metadata'):
    header = rom.read(seek_to=0x21ac, amount=16)
    #https://github.com/pokemon-mini/pm-dev-docs/wiki/PM_Cartridge - we are only bothering to read a small part of the thing, which is really all that's there
    product_code_bytes = header[0:4]
    try:
        product_code = convert_alphanumeric(product_code_bytes)
        metadata.product_code = product_code
    except NotAlphanumericException:
        pass
    title = header[4:16].decode('shift_jis',
                                errors='backslashreplace').rstrip('\0 ')
    if title:
        metadata.specific_info['Internal Title'] = title
Example #6
0
def _parse_gba_header(metadata: 'Metadata', header: bytes):
	#Entry point: 0-4
	nintendo_logo = header[4:0xa0]
	nintendo_logo_valid = crc32(nintendo_logo) == nintendo_gba_logo_crc32
	metadata.specific_info['Nintendo Logo Valid?'] = nintendo_logo_valid
	
	internal_title = header[0xa0:0xac].decode('ascii', errors='backslashreplace').rstrip('\0')
	metadata.specific_info['Internal Title'] = internal_title
	if internal_title == 'mb2gba':
		return
	
	product_code = None
	try:
		product_code = convert_alphanumeric(header[0xac:0xb0])
		if len(product_code) == 4:
			game_type = product_code[0]
			if game_type in {'K', 'R'}:
				metadata.input_info.input_options[0].inputs.append(input_metadata.MotionControls())
			metadata.specific_info['Force Feedback?'] = game_type in {'R', 'V'}

			metadata.product_code = product_code
	except NotAlphanumericException:
		pass

	licensee_code = None
	try:
		licensee_code = convert_alphanumeric(header[0xb0:0xb2])

		if licensee_code in nintendo_licensee_codes:
			metadata.publisher = nintendo_licensee_codes[licensee_code]
	except NotAlphanumericException:
		pass

	#"Fixed value": 0xb2, apparently should be 0x96
	#Main unit code: 0xb3, apparently should be 0
	#Device type: 0xb4, apparently normally should be 0
	#Reserved: 0xb5 - 0xbc

	metadata.specific_info['Revision'] = header[0xbc]
Example #7
0
def add_vectrex_header_info(rom: 'FileROM', metadata: 'Metadata'):
    try:
        year = convert_alphanumeric(rom.read(seek_to=6, amount=4))
        try:
            if int(
                    year
            ) > 1982:  #If it's any less than that, we know it was invalid (or maybe it was a prototype, but then I especially don't trust the header)
                year_date = Date(year, is_guessed=True)
                if year_date.is_better_than(metadata.release_date):
                    metadata.release_date = metadata.release_date
        except ValueError:
            pass
    except NotAlphanumericException:
        pass
Example #8
0
def _parse_tmd(metadata: 'Metadata', tmd: bytes):
	#Stuff that I dunno about: 0 - 388
	if tmd[387]:
		metadata.specific_info['Is vWii?'] = True
	#IOS version: 388-396
	#Title ID is just title type + hex product code, so don't worry about that

	try:
		metadata.specific_info['Title Type'] = WiiTitleType(int.from_bytes(tmd[396:400], 'big'))
	except ValueError:
		pass

	product_code = None
	try:
		product_code = tmd[400:404].decode('ascii')
		metadata.product_code = product_code

		if product_code:
			try:
				metadata.specific_info['Virtual Console Platform'] = WiiVirtualConsolePlatform(product_code[0])
			except ValueError:
				pass
	except UnicodeDecodeError:
		pass
	#Title flags: 404-408
	maker_code = None
	try:
		maker_code = convert_alphanumeric(tmd[408:410])
		if maker_code in _nintendo_licensee_codes:
			metadata.publisher = _nintendo_licensee_codes[maker_code]
	except NotAlphanumericException:
		pass
	
	if product_code:
		#Inconsistently enough WiiWare doesn't require appending the maker code, apparently
		add_info_from_tdb(_tdb, metadata, product_code)

	#Unused: 410-412
	region_code = int.from_bytes(tmd[412:414], 'big')
	try:
		metadata.specific_info['Region Code'] = NintendoDiscRegion(region_code)
	except ValueError:
		pass
	parse_ratings(metadata, tmd[414:430])
	#Reserved: 430-442
	#IPC mask: 442-454 (wat?)
	#Reserved 2: 454-472
	#Access rights: 472-476
	metadata.specific_info['Revision'] = int.from_bytes(tmd[476:478], 'big')
Example #9
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 #10
0
def parse_n64_header(metadata: 'Metadata', header: bytes):
    #Clock rate, apparently? 0:4
    #Program counter: 4-8
    #Release address: 8-12
    #Checksum: 12-16
    #Checksum 2: 16-20
    #Zero filled: 20-28
    internal_title = header[28:52].decode(
        'shift_jis', errors='backslashreplace').rstrip('\0')
    if internal_title:
        metadata.specific_info['Internal Title'] = internal_title
    #Unknown: 52-59
    try:
        product_code = convert_alphanumeric(header[59:63])
        metadata.product_code = product_code
    except NotAlphanumericException:
        pass
    metadata.specific_info['Revision'] = header[63]
Example #11
0
def _parse_snes_header(rom: 'FileROM', base_offset: int) -> Mapping[str, Any]:
    #TODO: Use namedtuple/dataclass
    #In order to make things simpler, we'll just ignore any carts that are out of line. You wouldn't be able to get interesting results from homebrew or bootleg games anyway
    #Hence why we won't add metadata to the game object straight away, we'll store it in a dict first and add it all later, so we add nothing at all from invalid headers
    metadata: dict[str, Any] = {}

    #Note that amount goes up to 256 if you include exception vectors, but... nah
    header = rom.read(seek_to=base_offset, amount=0xe0)
    title = None
    try:
        title = header[0xc0:0xd5].decode('shift_jis')
        metadata['Title'] = title
    except UnicodeDecodeError as ude:
        raise BadSNESHeaderException(
            'Title not ASCII or Shift-JIS: %s' % header[0xc0:0xd5].decode(
                'shift_jis', errors='backslashreplace')) from ude

    rom_layout = header[0xd5]
    if rom_layout in _rom_layouts:
        metadata['ROM layout'] = _rom_layouts[rom_layout]
    elif title == "HAL'S HOLE IN ONE GOL":
        #HAL's Hole in Golf has 70 here, because that's the letter F, and the title immediately preceding this is "HAL HOLE IN ONE GOL". Looks like they overwrote this part of the header with the letter F. Whoops.
        #Anyway the internet says it's LoROM + SlowROM
        metadata['ROM layout'] = _rom_layouts[0x20]
    else:
        raise BadSNESHeaderException('ROM layout is weird: %s' %
                                     hex(rom_layout))

    rom_type = header[0xd6]
    if rom_type in _rom_types:
        metadata['ROM type'] = _rom_types[rom_type]
    else:
        raise BadSNESHeaderException('ROM type is weird: %d' % rom_type)

    rom_size = header[0xd7]
    if rom_size not in _ram_rom_sizes:
        raise BadSNESHeaderException('ROM size is weird: %d' % rom_size)
    ram_size = header[0xd8]
    #We'll just use ROM type to detect presence of save data rather than this
    if ram_size not in _ram_rom_sizes:
        raise BadSNESHeaderException('RAM size is weird: %d' % ram_size)
    country = header[0xd9]
    #Dunno if I want to validate against countries, honestly. Might go wrong
    if country in _countries:
        metadata['Country'] = _countries[country]

    licensee = header[0xda]
    #Hmm.. not sure if I should validate that, but... it shouldn't be 0x00 or 0xff, maybe?

    metadata['Revision'] = header[0xdb]

    inverse_checksum = int.from_bytes(header[0xdc:0xde], 'little')
    checksum = int.from_bytes(header[0xde:0xe0], 'little')
    #Can't be arsed calculating the checksum because it's complicated (especially with some weird ROM sizes), but we know they have to add up to 0xffff
    if (checksum | inverse_checksum) != 0xffff:
        raise BadSNESHeaderException(
            "Checksum and inverse checksum don't add up: %s %s" %
            (hex(checksum), hex(inverse_checksum)))

    if licensee == 0x33:
        try:
            maker_code = convert_alphanumeric(header[0xb0:0xb2])
            metadata['Licensee'] = maker_code
        except NotAlphanumericException as nae:
            raise BadSNESHeaderException(
                'Licensee code in extended header not alphanumeric: %s' %
                header[0xb0:0xb2].decode('ascii',
                                         errors='backslashreplace')) from nae

        try:
            product_code = convert_alphanumeric(header[0xb2:0xb6])
            metadata['Product code'] = product_code
        except NotAlphanumericException as nae:
            if header[0xb4:0xb6] == b'  ':
                try:
                    product_code = convert_alphanumeric(header[0xb2:0xb4])
                    metadata['Product code'] = product_code
                except NotAlphanumericException as naenae:  #get naenaed
                    raise BadSNESHeaderException(
                        '2 char product code not alphanumeric: %s' %
                        header[0xb2:0xb4].decode(
                            'ascii', errors='backslashreplace')) from naenae
            else:
                raise BadSNESHeaderException(
                    '4 char product code not alphanumeric: %s' %
                    header[0xb2:0xb6].decode(
                        'ascii', errors='backslashreplace')) from nae
    else:
        metadata['Licensee'] = '{:02X}'.format(licensee)

    return metadata
Example #12
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