Beispiel #1
0
	def _add_version_resource_info(self, vers: 'macresources.Resource') -> None:
		version, revision = vers[0:2]
		self.metadata.specific_info['Version'] = str(version) + '.' + '.'.join('{0:x}'.format(revision))
		if vers[2] != 0x80:
			try:
				self.metadata.specific_info['Build Stage'] = BuildStage(vers[2])
			except ValueError:
				pass
			if not self.metadata.categories:
				self.metadata.categories = ('Betas', )
		if vers[3]: #"Non-release" / build number
			self.metadata.specific_info['Revision'] = vers[3]

		language_code = int.from_bytes(vers[4:6], 'big') #Or is it a country? I don't know
		try:
			#TODO: Fill out region/language fields using this
			self.metadata.specific_info['Language Code'] = CountryCode(language_code)
		except ValueError:
			self.metadata.specific_info['Language Code'] = language_code
			
		try:
			short_version_length = vers[6] #Pascal style strings
			long_version_length = vers[7+short_version_length]
			actual_short_version = None
			actual_long_version = None
			if short_version_length:
				short_version = vers[7:7+short_version_length].decode('mac-roman')
				if short_version.startswith('©'):
					self.metadata.specific_info['Short Copyright'] = short_version
				else:
					actual_short_version = short_version
			if long_version_length:
				long_version = vers[7+short_version_length + 1:7+short_version_length + 1 + long_version_length].decode('mac-roman')
				copyright_string = None
				if ', ©' in long_version:
					actual_long_version, copyright_string = long_version.split(', ©')
				elif ' ©' in long_version:
					actual_long_version, copyright_string = long_version.split(' ©')
				elif '©' in long_version:
					actual_long_version, copyright_string = long_version.split('©')
				else:
					actual_long_version = long_version
				if copyright_string:
					copyright_string = copyright_string.rstrip('\0')
					if copyright_string[:4].isdigit() and (len(copyright_string) == 4 or copyright_string[5] in {',', ' '}):
						copyright_year = Date(year=copyright_string[:4], is_guessed=True)
						if copyright_year.is_better_than(self.metadata.release_date):
							self.metadata.release_date = copyright_year
					self.metadata.specific_info['Copyright'] = '©' + copyright_string
			if actual_short_version:
				self.metadata.specific_info['Version'] = actual_short_version
			if actual_long_version and actual_long_version != actual_short_version:
				self.metadata.specific_info['Long Version'] = actual_long_version
		except UnicodeDecodeError:
			pass
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
Beispiel #3
0
def add_ps2_custom_info(game: 'ROMGame'):
    #.bin/cue also has this system.cnf but I'd need to know how to get pycdlib to work with that
    if game.rom.extension == 'iso' and have_pycdlib:
        iso = PyCdlib()
        try:
            iso.open(game.rom.path)
            system_cnf_buf = io.BytesIO()
            try:
                #I dunno what the ;1 is for
                iso.get_file_from_iso_fp(system_cnf_buf,
                                         iso_path='/SYSTEM.CNF;1')
                date_record = iso.get_record(iso_path='/SYSTEM.CNF;1').date
                #This would be more like a build date (seems to be the same across all files) rather than the release date, but it seems to be close enough
                year = date_record.years_since_1900 + 1900
                month = date_record.month
                day = date_record.day_of_month
                build_date = Date(year, month, day)
                game.metadata.specific_info['Build Date'] = build_date
                guessed_date = Date(year, month, day, True)
                if guessed_date.is_better_than(game.metadata.release_date):
                    game.metadata.release_date = guessed_date

                system_cnf = system_cnf_buf.getvalue().decode(
                    'utf-8', errors='backslashreplace')
                add_info_from_system_cnf(game.metadata, system_cnf)
            except PyCdlibInvalidInput:
                if main_config.debug:
                    print(game.rom.path, 'has no SYSTEM.CNF inside')
            #Modules are in IOP, MODULES or IRX but I don't know if we can get any interesting info from that
            #TODO: Sometimes there is a system.ini that looks like this:
            #[SYSTEM]
            #NUMBER = SLUS-21448
            #VERSION = 100
            #VMODE = NTSC
            #COUNTRY = AMERICA
            #LANGUAGE = ENGLISH
            #WARNING = NO
        except PyCdlibInvalidISO as ex:
            if main_config.debug:
                print(game.rom.path, 'is invalid ISO', ex)
        except struct.error as ex:
            print(game.rom.path, 'is invalid ISO and has some struct.error',
                  ex)
        finally:
            iso.close()
    #.elf is just a standard ordinary whole entire .elf
    if game.metadata.product_code:
        parse_product_code(game.metadata, game.metadata.product_code)
Beispiel #4
0
def add_metadata_for_raw_exe(path: str, metadata: 'Metadata'):
    props = get_exe_properties(path)
    if not props:
        return

    #Possible values to expect: https://docs.microsoft.com/en-us/windows/win32/api/winver/nf-winver-verqueryvaluea#remarks

    # if props.get('InternalName'):
    # 	if props.get('InternalName') != props.get('OriginalFilename'):
    # 		print(path, props.get('InternalName'), props.get('OriginalFilename'))

    if not metadata.publisher and not metadata.developer:
        company_name = props.get('CompanyName')
        if company_name:
            while junk_suffixes.search(company_name):
                company_name = junk_suffixes.sub('', company_name)
            metadata.publisher = company_name

    product_name = props.get('ProductName')
    if product_name:
        metadata.add_alternate_name(product_name, 'Name')
    copyright_string = props.get('LegalCopyright')
    if copyright_string:
        metadata.specific_info['Copyright'] = copyright_string
    description = props.get('FileDescription')
    if description and description != product_name:
        metadata.descriptions['File Description'] = description
    comments = props.get('Comments')
    if comments and comments != product_name:
        metadata.specific_info['File Comment'] = comments
    trademarks = props.get('LegalTrademarks')
    if trademarks and trademarks != copyright_string:
        metadata.specific_info['Trademarks'] = trademarks

    timedatestamp = props.get('TimeDateStamp')
    if timedatestamp:
        if not (timedatestamp > datetime.datetime.now()
                or timedatestamp.year < 1993):
            #If the date has not even happened yet, or is before Windows NT 3.1 and hence the PE format was even invented, I think the f**k not

            build_date = Date(timedatestamp.year, timedatestamp.month,
                              timedatestamp.day)
            metadata.specific_info['Build Date'] = build_date
            guessed_date = Date(build_date.year, build_date.month,
                                build_date.day, True)
            if guessed_date.is_better_than(metadata.release_date):
                metadata.release_date = guessed_date
Beispiel #5
0
def add_psp_iso_info(path: str, metadata: 'Metadata'):
    iso = PyCdlib()
    try:
        iso.open(path)
        param_sfo_buf = io.BytesIO()

        try:
            iso.get_file_from_iso_fp(param_sfo_buf,
                                     iso_path='/PSP_GAME/PARAM.SFO')
            date = iso.get_record(iso_path='/PSP_GAME/PARAM.SFO').date
            #This would be more like a build date (seems to be the same across all files) rather than the release date
            year = date.years_since_1900 + 1900
            month = date.month
            day = date.day_of_month
            metadata.specific_info['Build Date'] = Date(year, month, day)
            guessed = Date(year, month, day, True)
            if guessed.is_better_than(metadata.release_date):
                metadata.release_date = guessed
            parse_param_sfo(path, metadata, param_sfo_buf.getvalue())
        except PyCdlibInvalidInput:
            try:
                iso.get_record(iso_path='/UMD_VIDEO/PARAM.SFO')
                #We could parse this PARAM.SFO but there's not much point given we aren't going to make a launcher for UMD videos at this stage
                #TODO There is also potentially /UMD_AUDIO/ I think too so I should rewrite this one day
                metadata.specific_info['PlayStation Category'] = 'UMD Video'
                return
            except PyCdlibInvalidInput:
                if main_config.debug:
                    print(path, 'has no PARAM.SFO inside')
        if have_pillow:
            metadata.images['Banner'] = get_image_from_iso(
                iso, '/PSP_GAME/ICON0.PNG')
            metadata.images['Icon 1'] = get_image_from_iso(
                iso, '/PSP_GAME/ICON1.PNG')
            metadata.images['Picture 0'] = get_image_from_iso(
                iso, '/PSP_GAME/PIC0.PNG')
            metadata.images['Background Image'] = get_image_from_iso(
                iso, '/PSP_GAME/PIC1.PNG')
    except PyCdlibInvalidISO as ex:
        if main_config.debug:
            print(path, 'is invalid ISO', ex)
    except struct.error as ex:
        print(path, 'is invalid ISO and has some struct.error', ex)
    finally:
        iso.close()
Beispiel #6
0
def add_atari_5200_footer_garbage_info(rom: 'FileROM', metadata: Metadata):
    footer = rom.read(seek_to=rom.size - 24, amount=24)
    year = footer[20:22]  #Y2K incompliant whee
    #Entry point: 22-23, lil' endian
    if year[1] != 255:  #If set to this, the BIOS is skipped?
        title_bytes = footer[:20].rstrip(b'\0')
        if title_bytes:
            title = ''.join(
                atari_5200_charset.get(b, '\0x{0:x}'.format(b))
                for b in title_bytes)
            metadata.add_alternate_name(title.strip(), 'Banner Title')
        try:
            year_first_digit = int(atari_5200_charset[year[0]])
            year_second_digit = int(atari_5200_charset[year[1]])
            terrible_date = Date(year=1900 + (year_first_digit * 10) +
                                 year_second_digit,
                                 is_guessed=True)
            if terrible_date.is_better_than(metadata.release_date):
                metadata.release_date = terrible_date
        except (ValueError, KeyError):
            pass
Beispiel #7
0
def _add_meta_xml_metadata(metadata: 'Metadata', meta_xml: ElementTree.ElementTree):
	#version = 33 for digital stuff, sometimes 32 otherwise?, content_platform = WUP, ext_dev_urcc = some kiosk related thingo
	#logo_type = 2 on third party stuff?, app_launch_type = 1 on parental controls/H&S/Wii U Chat and 0 on everything else?, invisible_flag = maybe just for keeping stuff out of the daily log?, no_managed_flag, no_event_log, no_icon_database, launching_flag, install_flag, closing_msg, group_id, boss_id, os_version, app_size, common_boss_size, account_boss_size, save_no_rollback, join_game_id, join_game_mode_mask, bg_daemon_enable, olv_accesskey, wood_tin, e_manual = I guess it's 1 if it has a manual, e_manual_version, eula_version, direct_boot, reserved_flag{0-7}, add_on_unique_id{0-31} = DLC probs?
	product_code = meta_xml.findtext('product_code')
	if product_code:
		metadata.product_code = product_code
		try:
			metadata.specific_info['Virtual Console Platform'] = WiiUVirtualConsolePlatform(metadata.product_code[6])
		except ValueError:
			pass
		gametdb_id = product_code[-4:]
		add_info_from_tdb(_tdb, metadata, gametdb_id)

	company_code = meta_xml.findtext('company_code')
	if company_code:
		if company_code in _nintendo_licensee_codes:
			metadata.publisher = _nintendo_licensee_codes[company_code]
		elif len(company_code) == 4 and company_code.startswith('00'):
			if company_code[2:] in _nintendo_licensee_codes:
				metadata.publisher = _nintendo_licensee_codes[company_code[2:]]

	if product_code and company_code:
		_add_cover(metadata, product_code[-4:], company_code[2:])

	mastering_date_text = meta_xml.findtext('mastering_date')
	#Usually blank? Sometimes exists though
	if mastering_date_text:
		try:
			mastering_datetime = datetime.fromisoformat(mastering_date_text[:10])
			mastering_date = Date(mastering_datetime.year, mastering_datetime.month, mastering_datetime.day)
			metadata.specific_info['Mastering Date'] = mastering_date
			guessed_date = Date(mastering_date.year, mastering_date.month, mastering_date.day, True)
			if guessed_date.is_better_than(metadata.release_date):
				metadata.release_date = guessed_date
		except ValueError:
			#print(mastering_date_text)
			pass
	#Maybe we can use these to figure out if it creates a save file or not…
	metadata.specific_info['Common Save Size'] = int(meta_xml.findtext('common_save_size') or '0', 16)
	metadata.specific_info['Account Save Size'] = int(meta_xml.findtext('account_save_size') or '0', 16)

	metadata.specific_info['Title ID'] = meta_xml.findtext('title_id')
	version = meta_xml.findtext('title_version')
	if version:
		metadata.specific_info['Version'] = 'v' + version

	region = meta_xml.findtext('region')
	region_codes = set()
	if region:
		try:
			region_flags = int(region, 16)
			for region_code in WiiU3DSRegionCode:
				if region_code in (WiiU3DSRegionCode.RegionFree, WiiU3DSRegionCode.WiiURegionFree):
					continue
				if region_code.value & region_flags:
					region_codes.add(region_code)
			metadata.specific_info['Region Code'] = region_codes
		except ValueError:
			metadata.specific_info['Region Code'] = '0x' + region

	#Tempted to reuse wii.parse_ratings, but I might not because it's just a bit different
	rating_tags = {tag: int(tag.text) for tag in meta_xml.iter() if tag.tag.startswith('pc_') and tag.text}
	ratings = {tag.tag: rating & 0b0001_1111 for tag, rating in rating_tags.items() if (rating & 0b1000_0000) == 0 and (rating & 0b0100_0000) == 0}
	if ratings:
		try:
			rating = statistics.mode(ratings.values())
		except statistics.StatisticsError:
			rating = max(ratings.values())
		metadata.specific_info['Age Rating'] = rating
		if 'pc_cero' in ratings:
			metadata.specific_info['CERO Rating'] = ratings['pc_cero']
		if 'pc_esrb' in ratings:
			metadata.specific_info['ESRB Rating'] = ratings['pc_esrb']
		if 'pc_usk' in ratings:
			metadata.specific_info['USK Rating'] = ratings['pc_usk']
		if 'pc_pegi_gen' in ratings:
			metadata.specific_info['PEGI Rating'] = ratings['pc_pegi_gen']
		#There are more but that will do

	# #These may not be accurate at all?
	# metadata.specific_info['Uses-Nunchuk'] = meta_xml.findtext('ext_dev_nunchaku') != '0'
	# metadata.specific_info['Uses-Classic-Controller'] = meta_xml.findtext('ext_dev_classic') != '0'
	# metadata.specific_info['Uses-Balance-Board'] = meta_xml.findtext('ext_dev_board') != '0' #maybe?
	# metadata.specific_info['Uses-USB-Keyboard'] = meta_xml.findtext('ext_dev_usb_keyboard') != '0'
	# uses_etc = meta_xml.findtext('ext_dev_etc') != '0' #???
	# if uses_etc:
	# 	metadata.specific_info['Uses-Etc'] = meta_xml.findtext('ext_dev_etc_name')

	#drc = meta_xml.findtext('drc_use') != '0'
	#network = meta_xml.findtext('network_use') != '0'
	#online_account = meta_xml.findtext('online_account_use') != '0'
	
	short_names = {}
	long_names = {}
	publishers = {}
	for lang_code, lang_name in _languages.items():
		short_name = meta_xml.findtext('shortname_' + lang_code)
		if short_name:
			short_names[lang_name] = short_name
		long_name = meta_xml.findtext('longname_' + lang_code)
		if long_name:
			long_names[lang_name] = long_name.replace('\n', ': ') #Newlines seem to be used here to separate subtitles
		publisher = meta_xml.findtext('publisher_' + lang_code)
		if publisher:
			publishers[lang_name] = publisher

	add_info_from_local_titles(metadata, short_names, long_names, publishers, region_codes)

def _add_homebrew_meta_xml_metadata(rom: ROM, metadata: 'Metadata', meta_xml: ElementTree.ElementTree):
	name = meta_xml.findtext('name')
	if name:
		rom.ignore_name = True
		metadata.add_alternate_name(name, 'Banner Title')
	metadata.developer = metadata.publisher = meta_xml.findtext('coder')
	metadata.specific_info['Version'] = meta_xml.findtext('version')
	url = meta_xml.findtext('url')
	if url:
		metadata.documents['Homepage'] = url
	release_date_text = meta_xml.findtext('release_date')
	if release_date_text:
		metadata.release_date = Date(release_date_text[0:4], release_date_text[4:6], release_date_text[6:8])

	short_description = meta_xml.findtext('short_description')
	if short_description:
		metadata.descriptions['Short Description'] = short_description
	long_description = meta_xml.findtext('long_description')
	if long_description:
		metadata.descriptions['Long Description'] = long_description
	metadata.specific_info['Homebrew Category'] = meta_xml.findtext('category') or 'None' #Makes me wonder if it's feasible to include an option to get categories not from folders…

def _add_rpx_metadata(rom: ROM, metadata: 'Metadata'):
	#The .rpx itself is not interesting and basically just a spicy ELF
	#This is going to assume we are looking at a homebrew folder

	try:
		#info.json has the same info? But it's not always there
		_add_homebrew_meta_xml_metadata(rom, metadata, ElementTree.parse(rom.path.with_name('meta.xml')))
		if metadata.categories[-1] == rom.path.parent.name:
			metadata.categories = metadata.categories[:-1]
	except FileNotFoundError:
		pass
	homebrew_banner_path = rom.path.with_name('icon.png')
	if homebrew_banner_path.is_file():
		metadata.images['Banner'] = homebrew_banner_path

def add_folder_metadata(rom: FolderROM, metadata: 'Metadata'):
	content_dir = rom.get_subfolder('content')
	meta_dir = rom.get_subfolder('meta')
	assert content_dir and meta_dir, 'It should be impossible for content_dir or meta_dir to be none, otherwise this would not have even been detected as a folder'
	
	metadata.specific_info['Executable Name'] = rom.relevant_files['rpx'].name

	#TODO: Move this over to engine_detect
	if rom.path.joinpath('code', 'UnityEngine_dll.rpl').is_file():
		#Unity games on Wii U just have a "Data" folder under content with no executable (because it's over here in code), so our usual detection won't work; not sure about other cross platform engines
		metadata.specific_info['Engine'] = 'Unity'
	if content_dir.joinpath('assets').is_dir() and all(content_dir.joinpath('app', file).is_dir() for file in ('appinfo.xml', 'config.xml', 'index.html')):
		metadata.specific_info['Engine'] = 'Nintendo Web Framework'
	
	engine = try_and_detect_engine_from_folder(content_dir, metadata)
	if engine:
		metadata.specific_info['Engine'] = engine

	#Seemingly this can actually sometimes be all lowercase? I should make this check case insensitive but I don't really care too much
	icon_path = meta_dir.joinpath('iconTex.tga')
	if icon_path.is_file():
		metadata.images['Icon'] = icon_path
	boot_drc_path = meta_dir.joinpath('bootDrcTex.tga') #Image displayed on the gamepad while loading
	if boot_drc_path.is_file():
		metadata.images['Gamepad Boot Image'] = boot_drc_path
	boot_tv_path = meta_dir.joinpath('bootTvTex.tga') #Generally just bootDrcTex but higher resolution (and for the TV)
	if boot_tv_path.is_file():
		metadata.images['TV Boot Image'] = boot_tv_path
	boot_logo_path = meta_dir.joinpath('bootLogoTex.tga')
	if boot_logo_path.is_file():
		metadata.images['Boot Logo'] = boot_logo_path
	#There is also a Manual.bfma in here, bootMovie.h264 and bootSound.btsnd, and some ratings images like "CERO_ja.jpg" and "PEGI_en.jpg" except they're 1 byte so I dunno

	meta_xml_path = meta_dir.joinpath('meta.xml')
	try:
		meta_xml = ElementTree.parse(meta_xml_path)
		_add_meta_xml_metadata(metadata, meta_xml)
	except FileNotFoundError:
		pass

	if metadata.specific_info.get('Virtual Console Platform') == WiiUVirtualConsolePlatform.GBAOrPCEngine:
		metadata.specific_info['Virtual Console Platform'] = WiiUVirtualConsolePlatform.GBA if rom.name == 'm2engage' else WiiUVirtualConsolePlatform.PCEngine

def add_wii_u_custom_info(game: 'ROMGame'):
	if game.rom.is_folder:
		add_folder_metadata(cast(FolderROM, game.rom), game.metadata)
	if game.rom.extension == 'rpx':
		_add_rpx_metadata(game.rom, game.metadata)
Beispiel #8
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])
Beispiel #9
0
	def add_standard_metadata(self, metadata: Metadata):
		metadata.specific_info['MAME Software'] = self
		#We'll need to use that as more than just a name, though, I think; and by that I mean I get dizzy if I think about whether I need to do that or not right now
		#TODO: Whatever is checking metadata.names needs to just check for game.software etc manually rather than this being here, I think
		metadata.add_alternate_name(self.description, 'Software List Name')

		metadata.specific_info['MAME Software List'] = self.software_list

		if not metadata.product_code:
			metadata.product_code = self.serial
		barcode = self.infos.get('barcode')
		if barcode:
			metadata.specific_info['Barcode'] = barcode
		ring_code = self.infos.get('ring_code')
		if ring_code:
			metadata.specific_info['Ring Code'] = ring_code
		
		version = self.infos.get('version')
		if version:
			if version[0].isdigit():
				version = 'v' + version
			metadata.specific_info['Version'] = version

		alt_title = self.infos.get('alt_title', self.infos.get('alt_name', self.infos.get('alt_disk')))
		if alt_title:
			_add_alt_titles(metadata, alt_title)

		year_text = self.xml.findtext('year')
		if year_text:
			year_guessed = False
			if len(year_text) == 5 and year_text[-1] == '?':
				#Guess I've created a year 10000 problem, please fix this code in several millennia to be more smart
				year_guessed = True
				year_text = year_text[:-1]
			year = Date(year_text, is_guessed=year_guessed)
			if year.is_better_than(metadata.release_date):
				metadata.release_date = year

		release = self.infos.get('release')
		release_date: Optional[Date] = None
		if release:
			release_date = _parse_release_date(release)

		if release_date:
			if release_date.is_better_than(metadata.release_date):
				metadata.release_date = release_date

		developer = consistentify_manufacturer(self.infos.get('developer'))
		if not developer:
			developer = consistentify_manufacturer(self.infos.get('author'))
		if not developer:
			developer = consistentify_manufacturer(self.infos.get('programmer'))
		if developer:
			metadata.developer = developer

		publisher = consistentify_manufacturer(self.xml.findtext('publisher'))
		if publisher:
			already_has_publisher = metadata.publisher and (not metadata.publisher.startswith('<unknown'))
			if publisher in {'<doujin>', '<homebrew>', '<unlicensed>'} and developer:
				metadata.publisher = developer
			elif not (already_has_publisher and (publisher == '<unknown>')):
				if ' / ' in publisher:
					publishers: Collection[str] = set(cast(str, consistentify_manufacturer(p)) for p in publisher.split(' / '))
					if main_config.sort_multiple_dev_names:
						publishers = sorted(publishers)
					publisher = ', '.join(publishers)

				metadata.publisher = publisher

		self.add_related_images(metadata)

		add_history(metadata, self.software_list_name, self.name)
Beispiel #10
0
def add_saturn_info(rom_path_for_warning: str, metadata: 'Metadata',
                    header: bytes):
    hardware_id = header[0:16].decode('ascii', errors='ignore')
    if hardware_id != 'SEGA SEGASATURN ':
        #Won't boot on a real Saturn, also if this is some emulator only thing then nothing in the header can be considered valid
        metadata.specific_info['Hardware ID'] = hardware_id
        metadata.specific_info['Invalid Hardware ID?'] = True
        return

    try:
        maker = header[16:32].decode('ascii').rstrip()
        if maker.startswith('SEGA TP '):
            #"Sega Third Party", I guess
            maker_code = maker.removeprefix('SEGA TP ')
            if maker_code.startswith('T '):
                #You're not supposed to do that, stop that
                maker_code = 'T-' + maker_code[2:]
            if maker_code in _licensee_codes:
                metadata.publisher = _licensee_codes[maker_code]
        elif maker == 'SEGA ENTERPRISES':
            metadata.publisher = 'Sega'
        else:
            metadata.publisher = maker
    except UnicodeDecodeError:
        pass

    try:
        metadata.product_code = header[32:42].decode('ascii').rstrip()
    except UnicodeDecodeError:
        pass

    try:
        version = header[42:48].decode('ascii').rstrip()
        if version[0] == 'V' and version[2] == '.':
            metadata.specific_info['Version'] = 'v' + version[1:]
    except UnicodeDecodeError:
        pass

    release_date = header[48:56].decode('ascii',
                                        errors='backslashreplace').rstrip()

    if not release_date.startswith('0') and '-' not in release_date:
        #If it starts with 0 the date format is WRONG stop it because I know the Saturn wasn't invented yet before 1000 AD
        #Also sometimes it's formatted with dashes which means there are 2 bytes that shouldn't be there and are technically part of device info? Weird
        try:
            year = release_date[0:4]
            month = release_date[4:6]
            day = release_date[6:8]
            metadata.specific_info['Header Date'] = Date(year, month, day)
            guessed = Date(year, month, day, True)
            if guessed.is_better_than(metadata.release_date):
                metadata.release_date = guessed
        except IndexError:
            if main_config.debug:
                print(rom_path_for_warning, 'has invalid date in header:',
                      release_date)
        except ValueError:
            pass

    device_info = header[56:64].decode('ascii', errors='ignore').rstrip()
    if device_info.startswith('CD-'):
        #CART16M is seen here instead of "CD-1/1" on some protos?
        disc_number, _, disc_total = device_info[3:].partition('/')
        try:
            metadata.disc_number = int(disc_number)
            metadata.disc_total = int(disc_total)
        except ValueError:
            pass

    region_info = header[64:80].rstrip()
    #Only 10 characters are used
    region_codes = set()
    if b'J' in region_info:
        region_codes.add(SaturnRegionCodes.Japan)
    if b'U' in region_info:
        region_codes.add(SaturnRegionCodes.USA)
    if b'E' in region_info:
        region_codes.add(SaturnRegionCodes.Europe)

    #Some other region codes appear sometimes, but I haven't been able to verify _exactly_ what they are, and I don't really wanna make guesses
    #T = Taiwan?
    #K = Korea?
    #B = Brazil?
    #A and L seen on some homebrews and devkits?

    metadata.specific_info['Region Code'] = region_codes

    peripherals = header[80:96].decode('ascii',
                                       errors='backslashreplace').rstrip()
    _parse_peripherals(metadata, peripherals)

    internal_name = header[96:208].decode('ascii',
                                          errors='backslashreplace').rstrip()
    #Sometimes / : - are used as delimiters, and there can also be J:JapaneseNameU:USAName
    if internal_name:
        metadata.specific_info['Internal Title'] = internal_name
Beispiel #11
0
	def additional_metadata(self) -> None:
		self.metadata.specific_info['Executable Name'] = self.path.split(':')[-1]
		if have_machfs:
			file = self._get_file()
			if not file:
				raise ValueError('Somehow MacApp.additional_metadata was called with invalid file')

			carbon_path = self._carbon_path
			if carbon_path:
				self.metadata.specific_info['Is Carbon?'] = True
				self.metadata.specific_info['Carbon Path'] = carbon_path
				self.metadata.specific_info['Architecture'] = 'PPC' #This has to be manually specified because some pretend to be fat binaries?
			creator = file.creator
			if creator in {b'PJ93', b'PJ97'}:
				self.metadata.specific_info['Engine'] = 'Macromedia Director'
			self.metadata.specific_info['Creator Code'] = creator.decode('mac-roman', errors='backslashreplace')

			#Can also get mddate if wanted
			creation_datetime = mac_epoch + datetime.timedelta(seconds=file.crdate)
			creation_date = Date(creation_datetime.year, creation_datetime.month, creation_datetime.day, True)
			if creation_date.is_better_than(self.metadata.release_date):
				self.metadata.release_date = creation_date

			#self.metadata.specific_info['File Flags'] = file.flags
			if have_macresources:
				#If you have machfs you do have macresources too, but still
				if have_pillow:
					self.metadata.images['Icon'] = self._get_icon()

				sizes = self._get_resources().get(b'SIZE')
				if sizes:
					#Supposed to be -1, 0 and 1 are created when user manually changes preferred/minimum RAM?
					size = sizes.get(-1, sizes.get(0, sizes.get(1)))
					if size:
						#Remember this is big endian so you will need to go backwards
						#Bit 0: Save screen (obsolete)
						#Bit 1: Accept suspend/resume events
						#Bit 2: Disable option (obsolete)
						#Bit 3: Can background
						#Bit 4: Does activate on FG switch
						#Bit 6: Get front clicks
						#Bit 7: Accept app died events (debuggers) (the good book says "app launchers use this" and apparently applications use ignoreAppDiedEvents)
						#Bit 9 (bit 1 of second byte): High level event aware
						#Bit 10: Local and remote high level events
						#Bit 11: Stationery aware
						#Bit 12: Use text edit services ("inline services"?)
						if size[0] or size[1]: #If all flags are 0 then this is probably lies
							#if size[0] & (1 << (8 - 5)) != 0:
							#	#Documented as "Only background"? But also that
							#TODO: I don't think this does what I think it does
							#	self.metadata.specific_info['Has User Interface?'] = False
							if size[1] & (1 << (15 - 8)) == 0: #Wait is that even correct, and if these size resources are just ints, should they be combined to make this easier
								self.metadata.specific_info['Not 32 Bit Clean?'] = True
						self.metadata.specific_info['Minimum RAM'] = format_byte_size(int.from_bytes(size[6:10], 'big'))

				if file.type == b'APPL' and 'Architecture' not in self.metadata.specific_info:
					#According to https://support.apple.com/kb/TA21606?locale=en_AU this should work
					has_ppc = b'cfrg' in self._get_resources() #Code fragment, ID always 0
					has_68k = b'CODE' in self._get_resources()
					if has_ppc:
						if has_68k:
							self.metadata.specific_info['Architecture'] = 'Fat'
						else:
							self.metadata.specific_info['Architecture'] = 'PPC'
					elif has_68k:
						self.metadata.specific_info['Architecture'] = '68k'
					else:
						self.metadata.specific_info['Architecture'] = 'Unknown' #Maybe this will happen for really old stuff
			
				verses = self._get_resources().get(b'vers', {})
				vers = verses.get(1, verses.get(128)) #There are other vers resources too but 1 is the main one (I think?), 128 is used in older apps? maybe?
				if vers:
					self._add_version_resource_info(vers)

		if 'arch' in self.info:
			#Allow manual override (sometimes apps are jerks and have 68K code just for the sole purpose of showing you a dialog box saying you can't run it on a 68K processor)
			self.metadata.specific_info['Architecture'] = self.info['arch']
Beispiel #12
0
def _add_metadata_from_libretro_database_entry(metadata: 'Metadata',
                                               database: LibretroDatabaseType,
                                               key: Union[str, int]):
    database_entry = cast(
        Optional[dict[str, Any]], database.get(key)
    )  #TODO: Hmm what's the best way to do this - we don't want mypy complaining about all the different things GameValueType could be
    if database_entry:
        name = database_entry.get('comment', database_entry.get('name'))
        if name:
            metadata.add_alternate_name(name, 'Libretro Database Name')
        if 'serial' in database_entry and not metadata.product_code:
            metadata.product_code = database_entry['serial']
        #seems name = description = comment = usually just the name of the file from No-Intro/Redump, region we already know, enhancement_hw we already know (just SNES and Mega Drive)
        if 'description' in database_entry:
            description = database_entry['description']
            if description not in (database_entry.get('comment'),
                                   database_entry.get('name')):
                metadata.descriptions['Libretro Description'] = description

        date = Date()
        if 'releaseyear' in database_entry:
            date.year = database_entry['releaseyear']
        elif 'year' in database_entry:
            #Unusual but can happen apparently
            date.year = database_entry['year']
        if 'releasemonth' in database_entry:
            date.month = database_entry['releasemonth']
        if 'releaseday' in database_entry:
            date.day = database_entry['releaseday']
        if date.is_better_than(metadata.release_date):
            metadata.release_date = date

        if 'developer' in database_entry:
            developer = database_entry['developer']
            while junk_suffixes.search(developer):
                developer = junk_suffixes.sub('', developer)
            metadata.developer = company_name_overrides.get(
                developer, developer)
        if 'publisher' in database_entry:
            publisher = database_entry['publisher']
            while junk_suffixes.search(publisher):
                publisher = junk_suffixes.sub('', publisher)
            metadata.publisher = company_name_overrides.get(
                publisher, publisher)
        if 'manufacturer' in database_entry:
            publisher = database_entry['manufacturer']
            while junk_suffixes.search(publisher):
                publisher = junk_suffixes.sub('', publisher)
            metadata.publisher = company_name_overrides.get(
                publisher, publisher)

        if 'genre' in database_entry:
            genre = database_entry['genre']
            if '/' in genre:
                metadata.genre, metadata.subgenre = genre.split('/', 1)
            else:
                metadata.genre = genre
        if 'franchise' in database_entry:
            metadata.series = database_entry['franchise']
        if 'version' in database_entry:
            metadata.specific_info['Version'] = database_entry['version']

        if 'users' in database_entry:
            metadata.specific_info['Number of Players'] = database_entry[
                'users']
        if 'homepage' in database_entry:
            metadata.documents['Homepage'] = database_entry['homepage']
        if 'patch' in database_entry:
            metadata.documents['Patch Homepage'] = database_entry['patch']
        if 'esrb_rating' in database_entry:
            metadata.specific_info['ESRB Rating'] = database_entry[
                'esrb_rating']
        if 'bbfc_rating' in database_entry:
            metadata.specific_info['BBFC Rating'] = database_entry[
                'bbfc_rating']
        if 'elspa_rating' in database_entry:
            metadata.specific_info['ELSPA Rating'] = database_entry[
                'elspa_rating']
        if 'origin' in database_entry:
            metadata.specific_info['Development Origin'] = database_entry[
                'origin']
        if 'edge_review' in database_entry:
            metadata.descriptions['EDGE Review'] = database_entry[
                'edge_review']
        if 'edge_rating' in database_entry:
            metadata.specific_info['EDGE Rating'] = database_entry[
                'edge_rating']
        if 'edge_issue' in database_entry:
            metadata.specific_info['EDGE Issue'] = database_entry['edge_issue']
        if 'famitsu_rating' in database_entry:
            metadata.specific_info['Famitsu Rating'] = database_entry[
                'famitsu_rating']

        if database_entry.get('analog', 0) == 1:
            #This is PS1 specific
            metadata.specific_info['Uses Analog?'] = True
        if database_entry.get('rumble', 0) == 1:
            metadata.specific_info['Force Feedback?'] = True

        # for k, v in database_entry.items():
        # 	if k not in ('name', 'description', 'region', 'releaseyear', 'releasemonth', 'releaseday', 'genre', 'developer', 'serial', 'comment', 'franchise', 'version', 'homepage', 'patch', 'publisher', 'users', 'esrb_rating', 'origin', 'enhancement_hw', 'edge_review', 'edge_rating', 'edge_issue', 'famitsu_rating', 'analog', 'rumble'):
        # 		print('uwu', database_entry.get('comment'), k, v)
        return True
    return False
Beispiel #13
0
def add_info_from_main_track(metadata: Metadata, track_path: Path,
                             sector_size: int):
    try:
        header = cd_read.read_mode_1_cd(track_path, sector_size, amount=256)
    except NotImplementedError:
        return

    hardware_id = header[0:16].decode('ascii', errors='ignore')
    if hardware_id != 'SEGA SEGAKATANA ':
        #Won't boot on a real Dreamcast. I should check how much emulators care...
        metadata.specific_info['Hardware ID'] = hardware_id
        metadata.specific_info['Invalid Hardware ID?'] = True
        return

    copyright_info = header[16:32].decode('ascii', errors='ignore')
    #Seems to be always "SEGA ENTERPRISES"?
    metadata.specific_info['Copyright'] = copyright_info

    device_info = header[32:48].decode('ascii', errors='ignore').rstrip()
    device_info_match = _device_info_regex.match(device_info)
    if device_info_match:
        try:
            metadata.disc_number = int(device_info_match['discNum'])
            metadata.disc_total = int(device_info_match['totalDiscs'])
        except ValueError:
            pass

    region_info = header[48:56].rstrip()
    region_codes = set()
    if b'J' in region_info:
        region_codes.add(SaturnRegionCodes.Japan)
    if b'U' in region_info:
        region_codes.add(SaturnRegionCodes.USA)
    if b'E' in region_info:
        region_codes.add(SaturnRegionCodes.Europe)
    #Some other region codes appear sometimes but they might not be entirely valid
    metadata.specific_info['Region Code'] = region_codes

    try:
        peripherals = int(header[56:64], 16)
        add_peripherals_info(metadata, peripherals)
    except ValueError:
        pass

    metadata.product_code = header[64:74].decode(
        'ascii', errors='backslashreplace').rstrip()
    try:
        version = header[74:80].decode('ascii').rstrip()
        if version[0] == 'V' and version[2] == '.':
            metadata.specific_info['Version'] = 'v' + version[1:]
    except UnicodeDecodeError:
        pass

    release_date = header[80:96].decode('ascii',
                                        errors='backslashreplace').rstrip()

    try:
        year = release_date[0:4]
        month = release_date[4:6]
        day = release_date[6:8]
        metadata.specific_info['Header Date'] = Date(year, month, day)
        guessed = Date(year, month, day, True)
        if guessed.is_better_than(metadata.release_date):
            metadata.release_date = guessed
    except ValueError:
        pass

    try:
        metadata.specific_info['Executable Name'] = header[96:112].decode(
            'ascii').rstrip()
    except UnicodeDecodeError:
        pass

    try:
        maker = header[112:128].decode('ascii').rstrip()
        if maker == 'SEGA ENTERPRISES':
            metadata.publisher = 'Sega'
        elif maker.startswith(('SEGA LC-', 'SEGA-LC-')):
            maker_code = maker[len('SEGA LC-'):]
            if maker_code in _licensee_codes:
                metadata.publisher = _licensee_codes[maker_code]
        elif maker:
            metadata.publisher = maker
    except UnicodeDecodeError:
        pass

    metadata.specific_info['Internal Title'] = header[128:256].decode(
        'ascii', errors='backslashreplace').rstrip('\0 ')